github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/logger/loggerutils/file_windows.go (about) 1 package loggerutils 2 3 import ( 4 "os" 5 "path/filepath" 6 "syscall" 7 "unsafe" 8 ) 9 10 func open(name string) (*os.File, error) { 11 return openFile(name, os.O_RDONLY, 0) 12 } 13 14 func openFile(name string, flag int, perm os.FileMode) (*os.File, error) { 15 if name == "" { 16 return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT} 17 } 18 h, err := syscallOpen(fixLongPath(name), flag|syscall.O_CLOEXEC, syscallMode(perm)) 19 if err != nil { 20 return nil, &os.PathError{Op: "open", Path: name, Err: err} 21 } 22 return os.NewFile(uintptr(h), name), nil 23 } 24 25 // syscallOpen is copied from syscall.Open but is modified to 26 // always open a file with FILE_SHARE_DELETE 27 func syscallOpen(path string, mode int, perm uint32) (fd syscall.Handle, err error) { 28 if len(path) == 0 { 29 return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND 30 } 31 32 pathp, err := syscall.UTF16PtrFromString(path) 33 if err != nil { 34 return syscall.InvalidHandle, err 35 } 36 var access uint32 37 switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) { 38 case syscall.O_RDONLY: 39 access = syscall.GENERIC_READ 40 case syscall.O_WRONLY: 41 access = syscall.GENERIC_WRITE 42 case syscall.O_RDWR: 43 access = syscall.GENERIC_READ | syscall.GENERIC_WRITE 44 } 45 if mode&syscall.O_CREAT != 0 { 46 access |= syscall.GENERIC_WRITE 47 } 48 if mode&syscall.O_APPEND != 0 { 49 access &^= syscall.GENERIC_WRITE 50 access |= syscall.FILE_APPEND_DATA 51 } 52 sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE) 53 var sa *syscall.SecurityAttributes 54 if mode&syscall.O_CLOEXEC == 0 { 55 sa = makeInheritSa() 56 } 57 var createmode uint32 58 switch { 59 case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL): 60 createmode = syscall.CREATE_NEW 61 case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC): 62 createmode = syscall.CREATE_ALWAYS 63 case mode&syscall.O_CREAT == syscall.O_CREAT: 64 createmode = syscall.OPEN_ALWAYS 65 case mode&syscall.O_TRUNC == syscall.O_TRUNC: 66 createmode = syscall.TRUNCATE_EXISTING 67 default: 68 createmode = syscall.OPEN_EXISTING 69 } 70 h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, syscall.FILE_ATTRIBUTE_NORMAL, 0) 71 return h, e 72 } 73 74 func makeInheritSa() *syscall.SecurityAttributes { 75 var sa syscall.SecurityAttributes 76 sa.Length = uint32(unsafe.Sizeof(sa)) 77 sa.InheritHandle = 1 78 return &sa 79 } 80 81 // fixLongPath returns the extended-length (\\?\-prefixed) form of 82 // path when needed, in order to avoid the default 260 character file 83 // path limit imposed by Windows. If path is not easily converted to 84 // the extended-length form (for example, if path is a relative path 85 // or contains .. elements), or is short enough, fixLongPath returns 86 // path unmodified. 87 // 88 // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath 89 // 90 // Copied from os.OpenFile 91 func fixLongPath(path string) string { 92 // Do nothing (and don't allocate) if the path is "short". 93 // Empirically (at least on the Windows Server 2013 builder), 94 // the kernel is arbitrarily okay with < 248 bytes. That 95 // matches what the docs above say: 96 // "When using an API to create a directory, the specified 97 // path cannot be so long that you cannot append an 8.3 file 98 // name (that is, the directory name cannot exceed MAX_PATH 99 // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. 100 // 101 // The MSDN docs appear to say that a normal path that is 248 bytes long 102 // will work; empirically the path must be less then 248 bytes long. 103 if len(path) < 248 { 104 // Don't fix. (This is how Go 1.7 and earlier worked, 105 // not automatically generating the \\?\ form) 106 return path 107 } 108 109 // The extended form begins with \\?\, as in 110 // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. 111 // The extended form disables evaluation of . and .. path 112 // elements and disables the interpretation of / as equivalent 113 // to \. The conversion here rewrites / to \ and elides 114 // . elements as well as trailing or duplicate separators. For 115 // simplicity it avoids the conversion entirely for relative 116 // paths or paths containing .. elements. For now, 117 // \\server\share paths are not converted to 118 // \\?\UNC\server\share paths because the rules for doing so 119 // are less well-specified. 120 if len(path) >= 2 && path[:2] == `\\` { 121 // Don't canonicalize UNC paths. 122 return path 123 } 124 if !isAbs(path) { 125 // Relative path 126 return path 127 } 128 129 const prefix = `\\?` 130 131 pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) 132 copy(pathbuf, prefix) 133 n := len(path) 134 r, w := 0, len(prefix) 135 for r < n { 136 switch { 137 case os.IsPathSeparator(path[r]): 138 // empty block 139 r++ 140 case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): 141 // /./ 142 r++ 143 case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): 144 // /../ is currently unhandled 145 return path 146 default: 147 pathbuf[w] = '\\' 148 w++ 149 for ; r < n && !os.IsPathSeparator(path[r]); r++ { 150 pathbuf[w] = path[r] 151 w++ 152 } 153 } 154 } 155 // A drive's root directory needs a trailing \ 156 if w == len(`\\?\c:`) { 157 pathbuf[w] = '\\' 158 w++ 159 } 160 return string(pathbuf[:w]) 161 } 162 163 // copied from os package for os.OpenFile 164 func syscallMode(i os.FileMode) (o uint32) { 165 o |= uint32(i.Perm()) 166 if i&os.ModeSetuid != 0 { 167 o |= syscall.S_ISUID 168 } 169 if i&os.ModeSetgid != 0 { 170 o |= syscall.S_ISGID 171 } 172 if i&os.ModeSticky != 0 { 173 o |= syscall.S_ISVTX 174 } 175 // No mapping for Go's ModeTemporary (plan9 only). 176 return 177 } 178 179 func isAbs(path string) (b bool) { 180 v := volumeName(path) 181 if v == "" { 182 return false 183 } 184 path = path[len(v):] 185 if path == "" { 186 return false 187 } 188 return os.IsPathSeparator(path[0]) 189 } 190 191 func volumeName(path string) (v string) { 192 if len(path) < 2 { 193 return "" 194 } 195 // with drive letter 196 c := path[0] 197 if path[1] == ':' && 198 ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 199 'A' <= c && c <= 'Z') { 200 return path[:2] 201 } 202 // is it UNC 203 if l := len(path); l >= 5 && os.IsPathSeparator(path[0]) && os.IsPathSeparator(path[1]) && 204 !os.IsPathSeparator(path[2]) && path[2] != '.' { 205 // first, leading `\\` and next shouldn't be `\`. its server name. 206 for n := 3; n < l-1; n++ { 207 // second, next '\' shouldn't be repeated. 208 if os.IsPathSeparator(path[n]) { 209 n++ 210 // third, following something characters. its share name. 211 if !os.IsPathSeparator(path[n]) { 212 if path[n] == '.' { 213 break 214 } 215 for ; n < l; n++ { 216 if os.IsPathSeparator(path[n]) { 217 break 218 } 219 } 220 return path[:n] 221 } 222 break 223 } 224 } 225 } 226 return "" 227 } 228 229 func unlink(name string) error { 230 // Rename the file before deleting it so that the original name is freed 231 // up to be reused, even while there are still open FILE_SHARE_DELETE 232 // file handles. Emulate POSIX unlink() semantics, essentially. 233 name, err := filepath.Abs(name) 234 if err != nil { 235 return err 236 } 237 dir, fname := filepath.Split(name) 238 f, err := os.CreateTemp(dir, fname+".*.deleted") 239 if err != nil { 240 return err 241 } 242 tmpname := f.Name() 243 if err := f.Close(); err != nil { 244 return err 245 } 246 err = os.Rename(name, tmpname) 247 rmErr := os.Remove(tmpname) 248 if err != nil { 249 return err 250 } 251 return rmErr 252 }