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  // newFileFromNewFile is called by [NewFile].
    86  func newFileFromNewFile(fd uintptr, name string) *File {
    87  	h := syscall.Handle(fd)
    88  	if h == syscall.InvalidHandle {
    89  		return nil
    90  	}
    91  	nonBlocking, _ := windows.IsNonblock(syscall.Handle(fd))
    92  	return newFile(h, name, "file", nonBlocking)
    93  }
    94  
    95  // net_newWindowsFile is a hidden entry point called by net.conn.File.
    96  // This is used so that the File.pfd.close method calls [syscall.Closesocket]
    97  // instead of [syscall.CloseHandle].
    98  //
    99  //go:linkname net_newWindowsFile net.newWindowsFile
   100  func net_newWindowsFile(h syscall.Handle, name string) *File {
   101  	if h == syscall.InvalidHandle {
   102  		panic("invalid FD")
   103  	}
   104  	return newFile(h, name, "file+net", true)
   105  }
   106  
   107  func epipecheck(file *File, e error) {
   108  }
   109  
   110  // DevNull is the name of the operating system's “null device.”
   111  // On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
   112  const DevNull = "NUL"
   113  
   114  // openFileNolog is the Windows implementation of OpenFile.
   115  func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
   116  	if name == "" {
   117  		return nil, &PathError{Op: "open", Path: name, Err: syscall.ENOENT}
   118  	}
   119  	path := fixLongPath(name)
   120  	r, err := syscall.Open(path, flag|syscall.O_CLOEXEC, syscallMode(perm))
   121  	if err != nil {
   122  		return nil, &PathError{Op: "open", Path: name, Err: err}
   123  	}
   124  	// syscall.Open always returns a non-blocking handle.
   125  	return newFile(r, name, "file", false), nil
   126  }
   127  
   128  func openDirNolog(name string) (*File, error) {
   129  	return openFileNolog(name, O_RDONLY, 0)
   130  }
   131  
   132  func (file *file) close() error {
   133  	if file == nil {
   134  		return syscall.EINVAL
   135  	}
   136  	if info := file.dirinfo.Swap(nil); info != nil {
   137  		info.close()
   138  	}
   139  	var err error
   140  	if e := file.pfd.Close(); e != nil {
   141  		if e == poll.ErrFileClosing {
   142  			e = ErrClosed
   143  		}
   144  		err = &PathError{Op: "close", Path: file.name, Err: e}
   145  	}
   146  
   147  	// There is no need for a cleanup at this point. File must be alive at the point
   148  	// where cleanup.stop is called.
   149  	file.cleanup.Stop()
   150  	return err
   151  }
   152  
   153  // seek sets the offset for the next Read or Write on file to offset, interpreted
   154  // according to whence: 0 means relative to the origin of the file, 1 means
   155  // relative to the current offset, and 2 means relative to the end.
   156  // It returns the new offset and an error, if any.
   157  func (f *File) seek(offset int64, whence int) (ret int64, err error) {
   158  	if info := f.dirinfo.Swap(nil); info != nil {
   159  		// Free cached dirinfo, so we allocate a new one if we
   160  		// access this file as a directory again. See #35767 and #37161.
   161  		info.close()
   162  	}
   163  	ret, err = f.pfd.Seek(offset, whence)
   164  	runtime.KeepAlive(f)
   165  	return ret, err
   166  }
   167  
   168  // Truncate changes the size of the named file.
   169  // If the file is a symbolic link, it changes the size of the link's target.
   170  func Truncate(name string, size int64) error {
   171  	f, e := OpenFile(name, O_WRONLY, 0666)
   172  	if e != nil {
   173  		return e
   174  	}
   175  	defer f.Close()
   176  	e1 := f.Truncate(size)
   177  	if e1 != nil {
   178  		return e1
   179  	}
   180  	return nil
   181  }
   182  
   183  // Remove removes the named file or directory.
   184  // If there is an error, it will be of type [*PathError].
   185  func Remove(name string) error {
   186  	p, e := syscall.UTF16PtrFromString(fixLongPath(name))
   187  	if e != nil {
   188  		return &PathError{Op: "remove", Path: name, Err: e}
   189  	}
   190  
   191  	// Go file interface forces us to know whether
   192  	// name is a file or directory. Try both.
   193  	e = syscall.DeleteFile(p)
   194  	if e == nil {
   195  		return nil
   196  	}
   197  	e1 := syscall.RemoveDirectory(p)
   198  	if e1 == nil {
   199  		return nil
   200  	}
   201  
   202  	// Both failed: figure out which error to return.
   203  	if e1 != e {
   204  		a, e2 := syscall.GetFileAttributes(p)
   205  		if e2 != nil {
   206  			e = e2
   207  		} else {
   208  			if a&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
   209  				e = e1
   210  			} else if a&syscall.FILE_ATTRIBUTE_READONLY != 0 {
   211  				if e1 = syscall.SetFileAttributes(p, a&^syscall.FILE_ATTRIBUTE_READONLY); e1 == nil {
   212  					if e = syscall.DeleteFile(p); e == nil {
   213  						return nil
   214  					}
   215  				}
   216  			}
   217  		}
   218  	}
   219  	return &PathError{Op: "remove", Path: name, Err: e}
   220  }
   221  
   222  func rename(oldname, newname string) error {
   223  	e := windows.Rename(fixLongPath(oldname), fixLongPath(newname))
   224  	if e != nil {
   225  		return &LinkError{"rename", oldname, newname, e}
   226  	}
   227  	return nil
   228  }
   229  
   230  // Pipe returns a connected pair of Files; reads from r return bytes written to w.
   231  // It returns the files and an error, if any. The Windows handles underlying
   232  // the returned files are marked as inheritable by child processes.
   233  func Pipe() (r *File, w *File, err error) {
   234  	var p [2]syscall.Handle
   235  	e := syscall.Pipe(p[:])
   236  	if e != nil {
   237  		return nil, nil, NewSyscallError("pipe", e)
   238  	}
   239  	// syscall.Pipe always returns a non-blocking handle.
   240  	return newFile(p[0], "|0", "pipe", false), newFile(p[1], "|1", "pipe", false), nil
   241  }
   242  
   243  var useGetTempPath2 = sync.OnceValue(func() bool {
   244  	return windows.ErrorLoadingGetTempPath2() == nil
   245  })
   246  
   247  func tempDir() string {
   248  	getTempPath := syscall.GetTempPath
   249  	if useGetTempPath2() {
   250  		getTempPath = windows.GetTempPath2
   251  	}
   252  	n := uint32(syscall.MAX_PATH)
   253  	for {
   254  		b := make([]uint16, n)
   255  		n, _ = getTempPath(uint32(len(b)), &b[0])
   256  		if n > uint32(len(b)) {
   257  			continue
   258  		}
   259  		if n == 3 && b[1] == ':' && b[2] == '\\' {
   260  			// Do nothing for path, like C:\.
   261  		} else if n > 0 && b[n-1] == '\\' {
   262  			// Otherwise remove terminating \.
   263  			n--
   264  		}
   265  		return syscall.UTF16ToString(b[:n])
   266  	}
   267  }
   268  
   269  // Link creates newname as a hard link to the oldname file.
   270  // If there is an error, it will be of type *LinkError.
   271  func Link(oldname, newname string) error {
   272  	n, err := syscall.UTF16PtrFromString(fixLongPath(newname))
   273  	if err != nil {
   274  		return &LinkError{"link", oldname, newname, err}
   275  	}
   276  	o, err := syscall.UTF16PtrFromString(fixLongPath(oldname))
   277  	if err != nil {
   278  		return &LinkError{"link", oldname, newname, err}
   279  	}
   280  	err = syscall.CreateHardLink(n, o, 0)
   281  	if err != nil {
   282  		return &LinkError{"link", oldname, newname, err}
   283  	}
   284  	return nil
   285  }
   286  
   287  // Symlink creates newname as a symbolic link to oldname.
   288  // On Windows, a symlink to a non-existent oldname creates a file symlink;
   289  // if oldname is later created as a directory the symlink will not work.
   290  // If there is an error, it will be of type *LinkError.
   291  func Symlink(oldname, newname string) error {
   292  	// '/' does not work in link's content
   293  	oldname = filepathlite.FromSlash(oldname)
   294  
   295  	// need the exact location of the oldname when it's relative to determine if it's a directory
   296  	destpath := oldname
   297  	if v := filepathlite.VolumeName(oldname); v == "" {
   298  		if len(oldname) > 0 && IsPathSeparator(oldname[0]) {
   299  			// oldname is relative to the volume containing newname.
   300  			if v = filepathlite.VolumeName(newname); v != "" {
   301  				// Prepend the volume explicitly, because it may be different from the
   302  				// volume of the current working directory.
   303  				destpath = v + oldname
   304  			}
   305  		} else {
   306  			// oldname is relative to newname.
   307  			destpath = dirname(newname) + `\` + oldname
   308  		}
   309  	}
   310  
   311  	fi, err := Stat(destpath)
   312  	isdir := err == nil && fi.IsDir()
   313  
   314  	n, err := syscall.UTF16PtrFromString(fixLongPath(newname))
   315  	if err != nil {
   316  		return &LinkError{"symlink", oldname, newname, err}
   317  	}
   318  	var o *uint16
   319  	if filepathlite.IsAbs(oldname) {
   320  		o, err = syscall.UTF16PtrFromString(fixLongPath(oldname))
   321  	} else {
   322  		// Do not use fixLongPath on oldname for relative symlinks,
   323  		// as it would turn the name into an absolute path thus making
   324  		// an absolute symlink instead.
   325  		// Notice that CreateSymbolicLinkW does not fail for relative
   326  		// symlinks beyond MAX_PATH, so this does not prevent the
   327  		// creation of an arbitrary long path name.
   328  		o, err = syscall.UTF16PtrFromString(oldname)
   329  	}
   330  	if err != nil {
   331  		return &LinkError{"symlink", oldname, newname, err}
   332  	}
   333  
   334  	var flags uint32 = windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
   335  	if isdir {
   336  		flags |= syscall.SYMBOLIC_LINK_FLAG_DIRECTORY
   337  	}
   338  	err = syscall.CreateSymbolicLink(n, o, flags)
   339  	if err != nil {
   340  		// the unprivileged create flag is unsupported
   341  		// below Windows 10 (1703, v10.0.14972). retry without it.
   342  		flags &^= windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
   343  		err = syscall.CreateSymbolicLink(n, o, flags)
   344  		if err != nil {
   345  			return &LinkError{"symlink", oldname, newname, err}
   346  		}
   347  	}
   348  	return nil
   349  }
   350  
   351  // openSymlink calls CreateFile Windows API with FILE_FLAG_OPEN_REPARSE_POINT
   352  // parameter, so that Windows does not follow symlink, if path is a symlink.
   353  // openSymlink returns opened file handle.
   354  func openSymlink(path string) (syscall.Handle, error) {
   355  	p, err := syscall.UTF16PtrFromString(path)
   356  	if err != nil {
   357  		return 0, err
   358  	}
   359  	attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
   360  	// Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
   361  	// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
   362  	attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
   363  	h, err := syscall.CreateFile(p, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
   364  	if err != nil {
   365  		return 0, err
   366  	}
   367  	return h, nil
   368  }
   369  
   370  var winreadlinkvolume = godebug.New("winreadlinkvolume")
   371  
   372  // normaliseLinkPath converts absolute paths returned by
   373  // DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, ...)
   374  // into paths acceptable by all Windows APIs.
   375  // For example, it converts
   376  //
   377  //	\??\C:\foo\bar into C:\foo\bar
   378  //	\??\UNC\foo\bar into \\foo\bar
   379  //	\??\Volume{abc}\ into \\?\Volume{abc}\
   380  func normaliseLinkPath(path string) (string, error) {
   381  	if len(path) < 4 || path[:4] != `\??\` {
   382  		// unexpected path, return it as is
   383  		return path, nil
   384  	}
   385  	// we have path that start with \??\
   386  	s := path[4:]
   387  	switch {
   388  	case len(s) >= 2 && s[1] == ':': // \??\C:\foo\bar
   389  		return s, nil
   390  	case len(s) >= 4 && s[:4] == `UNC\`: // \??\UNC\foo\bar
   391  		return `\\` + s[4:], nil
   392  	}
   393  
   394  	// \??\Volume{abc}\
   395  	if winreadlinkvolume.Value() != "0" {
   396  		return `\\?\` + path[4:], nil
   397  	}
   398  	winreadlinkvolume.IncNonDefault()
   399  
   400  	h, err := openSymlink(path)
   401  	if err != nil {
   402  		return "", err
   403  	}
   404  	defer syscall.CloseHandle(h)
   405  
   406  	buf := make([]uint16, 100)
   407  	for {
   408  		n, err := windows.GetFinalPathNameByHandle(h, &buf[0], uint32(len(buf)), windows.VOLUME_NAME_DOS)
   409  		if err != nil {
   410  			return "", err
   411  		}
   412  		if n < uint32(len(buf)) {
   413  			break
   414  		}
   415  		buf = make([]uint16, n)
   416  	}
   417  	s = syscall.UTF16ToString(buf)
   418  	if len(s) > 4 && s[:4] == `\\?\` {
   419  		s = s[4:]
   420  		if len(s) > 3 && s[:3] == `UNC` {
   421  			// return path like \\server\share\...
   422  			return `\` + s[3:], nil
   423  		}
   424  		return s, nil
   425  	}
   426  	return "", errors.New("GetFinalPathNameByHandle returned unexpected path: " + s)
   427  }
   428  
   429  func readReparseLink(path string) (string, error) {
   430  	h, err := openSymlink(path)
   431  	if err != nil {
   432  		return "", err
   433  	}
   434  	defer syscall.CloseHandle(h)
   435  	return readReparseLinkHandle(h)
   436  }
   437  
   438  func readReparseLinkHandle(h syscall.Handle) (string, error) {
   439  	rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
   440  	var bytesReturned uint32
   441  	err := syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
   442  	if err != nil {
   443  		return "", err
   444  	}
   445  
   446  	rdb := (*windows.REPARSE_DATA_BUFFER)(unsafe.Pointer(&rdbbuf[0]))
   447  	switch rdb.ReparseTag {
   448  	case syscall.IO_REPARSE_TAG_SYMLINK:
   449  		rb := (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME))
   450  		s := rb.Path()
   451  		if rb.Flags&windows.SYMLINK_FLAG_RELATIVE != 0 {
   452  			return s, nil
   453  		}
   454  		return normaliseLinkPath(s)
   455  	case windows.IO_REPARSE_TAG_MOUNT_POINT:
   456  		return normaliseLinkPath((*windows.MountPointReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME)).Path())
   457  	default:
   458  		// the path is not a symlink or junction but another type of reparse
   459  		// point
   460  		return "", syscall.ENOENT
   461  	}
   462  }
   463  
   464  func readlink(name string) (string, error) {
   465  	s, err := readReparseLink(fixLongPath(name))
   466  	if err != nil {
   467  		return "", &PathError{Op: "readlink", Path: name, Err: err}
   468  	}
   469  	return s, nil
   470  }
   471  

View as plain text