github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/os/file_windows.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package os 6 7 import ( 8 "errors" 9 "internal/poll" 10 "internal/syscall/windows" 11 "runtime" 12 "sync" 13 "syscall" 14 "unsafe" 15 ) 16 17 // This matches the value in syscall/syscall_windows.go. 18 const _UTIME_OMIT = -1 19 20 // file is the real representation of *File. 21 // The extra level of indirection ensures that no clients of os 22 // can overwrite this data, which could cause the finalizer 23 // to close the wrong file descriptor. 24 type file struct { 25 pfd poll.FD 26 name string 27 dirinfo *dirInfo // nil unless directory being read 28 appendMode bool // whether file is opened for appending 29 } 30 31 // Fd returns the Windows handle referencing the open file. 32 // If f is closed, the file descriptor becomes invalid. 33 // If f is garbage collected, a finalizer may close the file descriptor, 34 // making it invalid; see runtime.SetFinalizer for more information on when 35 // a finalizer might be run. On Unix systems this will cause the SetDeadline 36 // methods to stop working. 37 func (file *File) Fd() uintptr { 38 if file == nil { 39 return uintptr(syscall.InvalidHandle) 40 } 41 return uintptr(file.pfd.Sysfd) 42 } 43 44 // newFile returns a new File with the given file handle and name. 45 // Unlike NewFile, it does not check that h is syscall.InvalidHandle. 46 func newFile(h syscall.Handle, name string, kind string) *File { 47 if kind == "file" { 48 var m uint32 49 if syscall.GetConsoleMode(h, &m) == nil { 50 kind = "console" 51 } 52 if t, err := syscall.GetFileType(h); err == nil && t == syscall.FILE_TYPE_PIPE { 53 kind = "pipe" 54 } 55 } 56 57 f := &File{&file{ 58 pfd: poll.FD{ 59 Sysfd: h, 60 IsStream: true, 61 ZeroReadIsEOF: true, 62 }, 63 name: name, 64 }} 65 runtime.SetFinalizer(f.file, (*file).close) 66 67 // Ignore initialization errors. 68 // Assume any problems will show up in later I/O. 69 f.pfd.Init(kind, false) 70 71 return f 72 } 73 74 // newConsoleFile creates new File that will be used as console. 75 func newConsoleFile(h syscall.Handle, name string) *File { 76 return newFile(h, name, "console") 77 } 78 79 // NewFile returns a new File with the given file descriptor and 80 // name. The returned value will be nil if fd is not a valid file 81 // descriptor. 82 func NewFile(fd uintptr, name string) *File { 83 h := syscall.Handle(fd) 84 if h == syscall.InvalidHandle { 85 return nil 86 } 87 return newFile(h, name, "file") 88 } 89 90 func epipecheck(file *File, e error) { 91 } 92 93 // DevNull is the name of the operating system's “null device.” 94 // On Unix-like systems, it is "/dev/null"; on Windows, "NUL". 95 const DevNull = "NUL" 96 97 // openFileNolog is the Windows implementation of OpenFile. 98 func openFileNolog(name string, flag int, perm FileMode) (*File, error) { 99 if name == "" { 100 return nil, &PathError{Op: "open", Path: name, Err: syscall.ENOENT} 101 } 102 path := fixLongPath(name) 103 r, e := syscall.Open(path, flag|syscall.O_CLOEXEC, syscallMode(perm)) 104 if e != nil { 105 // We should return EISDIR when we are trying to open a directory with write access. 106 if e == syscall.ERROR_ACCESS_DENIED && (flag&O_WRONLY != 0 || flag&O_RDWR != 0) { 107 pathp, e1 := syscall.UTF16PtrFromString(path) 108 if e1 == nil { 109 var fa syscall.Win32FileAttributeData 110 e1 = syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa))) 111 if e1 == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { 112 e = syscall.EISDIR 113 } 114 } 115 } 116 return nil, &PathError{Op: "open", Path: name, Err: e} 117 } 118 f, e := newFile(r, name, "file"), nil 119 if e != nil { 120 return nil, &PathError{Op: "open", Path: name, Err: e} 121 } 122 return f, nil 123 } 124 125 func (file *file) close() error { 126 if file == nil { 127 return syscall.EINVAL 128 } 129 if file.dirinfo != nil { 130 file.dirinfo.close() 131 file.dirinfo = nil 132 } 133 var err error 134 if e := file.pfd.Close(); e != nil { 135 if e == poll.ErrFileClosing { 136 e = ErrClosed 137 } 138 err = &PathError{Op: "close", Path: file.name, Err: e} 139 } 140 141 // no need for a finalizer anymore 142 runtime.SetFinalizer(file, nil) 143 return err 144 } 145 146 // seek sets the offset for the next Read or Write on file to offset, interpreted 147 // according to whence: 0 means relative to the origin of the file, 1 means 148 // relative to the current offset, and 2 means relative to the end. 149 // It returns the new offset and an error, if any. 150 func (f *File) seek(offset int64, whence int) (ret int64, err error) { 151 if f.dirinfo != nil { 152 // Free cached dirinfo, so we allocate a new one if we 153 // access this file as a directory again. See #35767 and #37161. 154 f.dirinfo.close() 155 f.dirinfo = nil 156 } 157 ret, err = f.pfd.Seek(offset, whence) 158 runtime.KeepAlive(f) 159 return ret, err 160 } 161 162 // Truncate changes the size of the named file. 163 // If the file is a symbolic link, it changes the size of the link's target. 164 func Truncate(name string, size int64) error { 165 f, e := OpenFile(name, O_WRONLY, 0666) 166 if e != nil { 167 return e 168 } 169 defer f.Close() 170 e1 := f.Truncate(size) 171 if e1 != nil { 172 return e1 173 } 174 return nil 175 } 176 177 // Remove removes the named file or directory. 178 // If there is an error, it will be of type *PathError. 179 func Remove(name string) error { 180 p, e := syscall.UTF16PtrFromString(fixLongPath(name)) 181 if e != nil { 182 return &PathError{Op: "remove", Path: name, Err: e} 183 } 184 185 // Go file interface forces us to know whether 186 // name is a file or directory. Try both. 187 e = syscall.DeleteFile(p) 188 if e == nil { 189 return nil 190 } 191 e1 := syscall.RemoveDirectory(p) 192 if e1 == nil { 193 return nil 194 } 195 196 // Both failed: figure out which error to return. 197 if e1 != e { 198 a, e2 := syscall.GetFileAttributes(p) 199 if e2 != nil { 200 e = e2 201 } else { 202 if a&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { 203 e = e1 204 } else if a&syscall.FILE_ATTRIBUTE_READONLY != 0 { 205 if e1 = syscall.SetFileAttributes(p, a&^syscall.FILE_ATTRIBUTE_READONLY); e1 == nil { 206 if e = syscall.DeleteFile(p); e == nil { 207 return nil 208 } 209 } 210 } 211 } 212 } 213 return &PathError{Op: "remove", Path: name, Err: e} 214 } 215 216 func rename(oldname, newname string) error { 217 e := windows.Rename(fixLongPath(oldname), fixLongPath(newname)) 218 if e != nil { 219 return &LinkError{"rename", oldname, newname, e} 220 } 221 return nil 222 } 223 224 // Pipe returns a connected pair of Files; reads from r return bytes written to w. 225 // It returns the files and an error, if any. The Windows handles underlying 226 // the returned files are marked as inheritable by child processes. 227 func Pipe() (r *File, w *File, err error) { 228 var p [2]syscall.Handle 229 e := syscall.Pipe(p[:]) 230 if e != nil { 231 return nil, nil, NewSyscallError("pipe", e) 232 } 233 return newFile(p[0], "|0", "pipe"), newFile(p[1], "|1", "pipe"), nil 234 } 235 236 var ( 237 useGetTempPath2Once sync.Once 238 useGetTempPath2 bool 239 ) 240 241 func tempDir() string { 242 useGetTempPath2Once.Do(func() { 243 useGetTempPath2 = (windows.ErrorLoadingGetTempPath2() == nil) 244 }) 245 getTempPath := syscall.GetTempPath 246 if useGetTempPath2 { 247 getTempPath = windows.GetTempPath2 248 } 249 n := uint32(syscall.MAX_PATH) 250 for { 251 b := make([]uint16, n) 252 n, _ = getTempPath(uint32(len(b)), &b[0]) 253 if n > uint32(len(b)) { 254 continue 255 } 256 if n == 3 && b[1] == ':' && b[2] == '\\' { 257 // Do nothing for path, like C:\. 258 } else if n > 0 && b[n-1] == '\\' { 259 // Otherwise remove terminating \. 260 n-- 261 } 262 return syscall.UTF16ToString(b[:n]) 263 } 264 } 265 266 // Link creates newname as a hard link to the oldname file. 267 // If there is an error, it will be of type *LinkError. 268 func Link(oldname, newname string) error { 269 n, err := syscall.UTF16PtrFromString(fixLongPath(newname)) 270 if err != nil { 271 return &LinkError{"link", oldname, newname, err} 272 } 273 o, err := syscall.UTF16PtrFromString(fixLongPath(oldname)) 274 if err != nil { 275 return &LinkError{"link", oldname, newname, err} 276 } 277 err = syscall.CreateHardLink(n, o, 0) 278 if err != nil { 279 return &LinkError{"link", oldname, newname, err} 280 } 281 return nil 282 } 283 284 // Symlink creates newname as a symbolic link to oldname. 285 // On Windows, a symlink to a non-existent oldname creates a file symlink; 286 // if oldname is later created as a directory the symlink will not work. 287 // If there is an error, it will be of type *LinkError. 288 func Symlink(oldname, newname string) error { 289 // '/' does not work in link's content 290 oldname = fromSlash(oldname) 291 292 // need the exact location of the oldname when it's relative to determine if it's a directory 293 destpath := oldname 294 if v := volumeName(oldname); v == "" { 295 if len(oldname) > 0 && IsPathSeparator(oldname[0]) { 296 // oldname is relative to the volume containing newname. 297 if v = volumeName(newname); v != "" { 298 // Prepend the volume explicitly, because it may be different from the 299 // volume of the current working directory. 300 destpath = v + oldname 301 } 302 } else { 303 // oldname is relative to newname. 304 destpath = dirname(newname) + `\` + oldname 305 } 306 } 307 308 fi, err := Stat(destpath) 309 isdir := err == nil && fi.IsDir() 310 311 n, err := syscall.UTF16PtrFromString(fixLongPath(newname)) 312 if err != nil { 313 return &LinkError{"symlink", oldname, newname, err} 314 } 315 o, err := syscall.UTF16PtrFromString(fixLongPath(oldname)) 316 if err != nil { 317 return &LinkError{"symlink", oldname, newname, err} 318 } 319 320 var flags uint32 = windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 321 if isdir { 322 flags |= syscall.SYMBOLIC_LINK_FLAG_DIRECTORY 323 } 324 err = syscall.CreateSymbolicLink(n, o, flags) 325 if err != nil { 326 // the unprivileged create flag is unsupported 327 // below Windows 10 (1703, v10.0.14972). retry without it. 328 flags &^= windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 329 err = syscall.CreateSymbolicLink(n, o, flags) 330 if err != nil { 331 return &LinkError{"symlink", oldname, newname, err} 332 } 333 } 334 return nil 335 } 336 337 // openSymlink calls CreateFile Windows API with FILE_FLAG_OPEN_REPARSE_POINT 338 // parameter, so that Windows does not follow symlink, if path is a symlink. 339 // openSymlink returns opened file handle. 340 func openSymlink(path string) (syscall.Handle, error) { 341 p, err := syscall.UTF16PtrFromString(path) 342 if err != nil { 343 return 0, err 344 } 345 attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) 346 // Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink. 347 // See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted 348 attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT 349 h, err := syscall.CreateFile(p, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0) 350 if err != nil { 351 return 0, err 352 } 353 return h, nil 354 } 355 356 // normaliseLinkPath converts absolute paths returned by 357 // DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, ...) 358 // into paths acceptable by all Windows APIs. 359 // For example, it converts 360 // 361 // \??\C:\foo\bar into C:\foo\bar 362 // \??\UNC\foo\bar into \\foo\bar 363 // \??\Volume{abc}\ into C:\ 364 func normaliseLinkPath(path string) (string, error) { 365 if len(path) < 4 || path[:4] != `\??\` { 366 // unexpected path, return it as is 367 return path, nil 368 } 369 // we have path that start with \??\ 370 s := path[4:] 371 switch { 372 case len(s) >= 2 && s[1] == ':': // \??\C:\foo\bar 373 return s, nil 374 case len(s) >= 4 && s[:4] == `UNC\`: // \??\UNC\foo\bar 375 return `\\` + s[4:], nil 376 } 377 378 // handle paths, like \??\Volume{abc}\... 379 380 err := windows.LoadGetFinalPathNameByHandle() 381 if err != nil { 382 // we must be using old version of Windows 383 return "", err 384 } 385 386 h, err := openSymlink(path) 387 if err != nil { 388 return "", err 389 } 390 defer syscall.CloseHandle(h) 391 392 buf := make([]uint16, 100) 393 for { 394 n, err := windows.GetFinalPathNameByHandle(h, &buf[0], uint32(len(buf)), windows.VOLUME_NAME_DOS) 395 if err != nil { 396 return "", err 397 } 398 if n < uint32(len(buf)) { 399 break 400 } 401 buf = make([]uint16, n) 402 } 403 s = syscall.UTF16ToString(buf) 404 if len(s) > 4 && s[:4] == `\\?\` { 405 s = s[4:] 406 if len(s) > 3 && s[:3] == `UNC` { 407 // return path like \\server\share\... 408 return `\` + s[3:], nil 409 } 410 return s, nil 411 } 412 return "", errors.New("GetFinalPathNameByHandle returned unexpected path: " + s) 413 } 414 415 func readlink(path string) (string, error) { 416 h, err := openSymlink(path) 417 if err != nil { 418 return "", err 419 } 420 defer syscall.CloseHandle(h) 421 422 rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE) 423 var bytesReturned uint32 424 err = syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil) 425 if err != nil { 426 return "", err 427 } 428 429 rdb := (*windows.REPARSE_DATA_BUFFER)(unsafe.Pointer(&rdbbuf[0])) 430 switch rdb.ReparseTag { 431 case syscall.IO_REPARSE_TAG_SYMLINK: 432 rb := (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME)) 433 s := rb.Path() 434 if rb.Flags&windows.SYMLINK_FLAG_RELATIVE != 0 { 435 return s, nil 436 } 437 return normaliseLinkPath(s) 438 case windows.IO_REPARSE_TAG_MOUNT_POINT: 439 return normaliseLinkPath((*windows.MountPointReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME)).Path()) 440 default: 441 // the path is not a symlink or junction but another type of reparse 442 // point 443 return "", syscall.ENOENT 444 } 445 } 446 447 // Readlink returns the destination of the named symbolic link. 448 // If there is an error, it will be of type *PathError. 449 func Readlink(name string) (string, error) { 450 s, err := readlink(fixLongPath(name)) 451 if err != nil { 452 return "", &PathError{Op: "readlink", Path: name, Err: err} 453 } 454 return s, nil 455 }