storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/lock/lock_windows.go (about) 1 //go:build windows 2 // +build windows 3 4 /* 5 * MinIO Cloud Storage, (C) 2016, 2017 MinIO, Inc. 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 */ 19 20 package lock 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "syscall" 27 "unsafe" 28 29 "golang.org/x/sys/windows" 30 ) 31 32 var ( 33 modkernel32 = windows.NewLazySystemDLL("kernel32.dll") 34 procLockFileEx = modkernel32.NewProc("LockFileEx") 35 ) 36 37 const ( 38 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx 39 lockFileExclusiveLock = 2 40 lockFileFailImmediately = 1 41 42 // see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx 43 errLockViolation syscall.Errno = 0x21 44 ) 45 46 // lockedOpenFile is an internal function. 47 func lockedOpenFile(path string, flag int, perm os.FileMode, lockType uint32) (*LockedFile, error) { 48 f, err := Open(path, flag, perm) 49 if err != nil { 50 return nil, err 51 } 52 53 if err = lockFile(syscall.Handle(f.Fd()), lockType); err != nil { 54 f.Close() 55 return nil, err 56 } 57 58 st, err := os.Stat(path) 59 if err != nil { 60 f.Close() 61 return nil, err 62 } 63 64 if st.IsDir() { 65 f.Close() 66 return nil, &os.PathError{ 67 Op: "open", 68 Path: path, 69 Err: syscall.EISDIR, 70 } 71 } 72 73 return &LockedFile{File: f}, nil 74 } 75 76 // TryLockedOpenFile - tries a new write lock, functionality 77 // it is similar to LockedOpenFile with with syscall.LOCK_EX 78 // mode but along with syscall.LOCK_NB such that the function 79 // doesn't wait forever but instead returns if it cannot 80 // acquire a write lock. 81 func TryLockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { 82 var lockType uint32 = lockFileFailImmediately | lockFileExclusiveLock 83 switch flag { 84 case syscall.O_RDONLY: 85 // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-lockfileex 86 //lint:ignore SA4016 Reasons 87 lockType = lockFileFailImmediately | 0 // Set this to enable shared lock and fail immediately. 88 } 89 return lockedOpenFile(path, flag, perm, lockType) 90 } 91 92 // LockedOpenFile - initializes a new lock and protects 93 // the file from concurrent access. 94 func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) { 95 var lockType uint32 = lockFileExclusiveLock 96 switch flag { 97 case syscall.O_RDONLY: 98 // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-lockfileex 99 lockType = 0 // Set this to enable shared lock. 100 } 101 return lockedOpenFile(path, flag, perm, lockType) 102 } 103 104 // fixLongPath returns the extended-length (\\?\-prefixed) form of 105 // path when needed, in order to avoid the default 260 character file 106 // path limit imposed by Windows. If path is not easily converted to 107 // the extended-length form (for example, if path is a relative path 108 // or contains .. elements), or is short enough, fixLongPath returns 109 // path unmodified. 110 // 111 // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath 112 func fixLongPath(path string) string { 113 // Do nothing (and don't allocate) if the path is "short". 114 // Empirically (at least on the Windows Server 2013 builder), 115 // the kernel is arbitrarily okay with < 248 bytes. That 116 // matches what the docs above say: 117 // "When using an API to create a directory, the specified 118 // path cannot be so long that you cannot append an 8.3 file 119 // name (that is, the directory name cannot exceed MAX_PATH 120 // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. 121 // 122 // The MSDN docs appear to say that a normal path that is 248 bytes long 123 // will work; empirically the path must be less then 248 bytes long. 124 if len(path) < 248 { 125 // Don't fix. (This is how Go 1.7 and earlier worked, 126 // not automatically generating the \\?\ form) 127 return path 128 } 129 130 // The extended form begins with \\?\, as in 131 // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. 132 // The extended form disables evaluation of . and .. path 133 // elements and disables the interpretation of / as equivalent 134 // to \. The conversion here rewrites / to \ and elides 135 // . elements as well as trailing or duplicate separators. For 136 // simplicity it avoids the conversion entirely for relative 137 // paths or paths containing .. elements. For now, 138 // \\server\share paths are not converted to 139 // \\?\UNC\server\share paths because the rules for doing so 140 // are less well-specified. 141 if len(path) >= 2 && path[:2] == `\\` { 142 // Don't canonicalize UNC paths. 143 return path 144 } 145 if !filepath.IsAbs(path) { 146 // Relative path 147 return path 148 } 149 150 const prefix = `\\?` 151 152 pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) 153 copy(pathbuf, prefix) 154 n := len(path) 155 r, w := 0, len(prefix) 156 for r < n { 157 switch { 158 case os.IsPathSeparator(path[r]): 159 // empty block 160 r++ 161 case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): 162 // /./ 163 r++ 164 case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): 165 // /../ is currently unhandled 166 return path 167 default: 168 pathbuf[w] = '\\' 169 w++ 170 for ; r < n && !os.IsPathSeparator(path[r]); r++ { 171 pathbuf[w] = path[r] 172 w++ 173 } 174 } 175 } 176 // A drive's root directory needs a trailing \ 177 if w == len(`\\?\c:`) { 178 pathbuf[w] = '\\' 179 w++ 180 } 181 return string(pathbuf[:w]) 182 } 183 184 // Open - perm param is ignored, on windows file perms/NT acls 185 // are not octet combinations. Providing access to NT 186 // acls is out of scope here. 187 func Open(path string, flag int, perm os.FileMode) (*os.File, error) { 188 if path == "" { 189 return nil, syscall.ERROR_FILE_NOT_FOUND 190 } 191 192 pathp, err := syscall.UTF16PtrFromString(fixLongPath(path)) 193 if err != nil { 194 return nil, err 195 } 196 197 var access uint32 198 switch flag { 199 case syscall.O_RDONLY: 200 access = syscall.GENERIC_READ 201 case syscall.O_WRONLY: 202 access = syscall.GENERIC_WRITE 203 case syscall.O_RDWR: 204 fallthrough 205 case syscall.O_RDWR | syscall.O_CREAT: 206 fallthrough 207 case syscall.O_WRONLY | syscall.O_CREAT: 208 access = syscall.GENERIC_READ | syscall.GENERIC_WRITE 209 case syscall.O_WRONLY | syscall.O_CREAT | syscall.O_APPEND: 210 access = syscall.FILE_APPEND_DATA 211 default: 212 return nil, fmt.Errorf("Unsupported flag (%d)", flag) 213 } 214 215 var createflag uint32 216 switch { 217 case flag&syscall.O_CREAT == syscall.O_CREAT: 218 createflag = syscall.OPEN_ALWAYS 219 default: 220 createflag = syscall.OPEN_EXISTING 221 } 222 223 shareflag := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE) 224 accessAttr := uint32(syscall.FILE_ATTRIBUTE_NORMAL | 0x80000000) 225 226 fd, err := syscall.CreateFile(pathp, access, shareflag, nil, createflag, accessAttr, 0) 227 if err != nil { 228 return nil, err 229 } 230 231 return os.NewFile(uintptr(fd), path), nil 232 } 233 234 func lockFile(fd syscall.Handle, flags uint32) error { 235 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx 236 if fd == syscall.InvalidHandle { 237 return nil 238 } 239 240 err := lockFileEx(fd, flags, 1, 0, &syscall.Overlapped{}) 241 if err == nil { 242 return nil 243 } else if err.Error() == "The process cannot access the file because another process has locked a portion of the file." { 244 return ErrAlreadyLocked 245 } else if err != errLockViolation { 246 return err 247 } 248 249 return nil 250 } 251 252 func lockFileEx(h syscall.Handle, flags, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) { 253 var reserved = uint32(0) 254 r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6, uintptr(h), uintptr(flags), 255 uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol))) 256 if r1 == 0 { 257 if e1 != 0 { 258 err = error(e1) 259 } else { 260 err = syscall.EINVAL 261 } 262 } 263 return 264 }