Source file src/os/file_windows.go

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

View as plain text