Source file src/os/root_windows.go

     1  // Copyright 2024 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  //go:build windows
     6  
     7  package os
     8  
     9  import (
    10  	"errors"
    11  	"internal/filepathlite"
    12  	"internal/stringslite"
    13  	"internal/syscall/windows"
    14  	"runtime"
    15  	"syscall"
    16  	"unsafe"
    17  )
    18  
    19  // rootCleanPath uses GetFullPathName to perform lexical path cleaning.
    20  //
    21  // On Windows, file names are lexically cleaned at the start of a file operation.
    22  // For example, on Windows the path `a\..\b` is exactly equivalent to `b` alone,
    23  // even if `a` does not exist or is not a directory.
    24  //
    25  // We use the Windows API function GetFullPathName to perform this cleaning.
    26  // We could do this ourselves, but there are a number of subtle behaviors here,
    27  // and deferring to the OS maintains consistency.
    28  // (For example, `a\.\` cleans to `a\`.)
    29  //
    30  // GetFullPathName operates on absolute paths, and our input path is relative.
    31  // We make the path absolute by prepending a fixed prefix of \\?\?\.
    32  //
    33  // We want to detect paths which use .. components to escape the root.
    34  // We do this by ensuring the cleaned path still begins with \\?\?\.
    35  // We catch the corner case of a path which includes a ..\?\. component
    36  // by rejecting any input paths which contain a ?, which is not a valid character
    37  // in a Windows filename.
    38  func rootCleanPath(s string, prefix, suffix []string) (string, error) {
    39  	// Reject paths which include a ? component (see above).
    40  	if stringslite.IndexByte(s, '?') >= 0 {
    41  		return "", windows.ERROR_INVALID_NAME
    42  	}
    43  
    44  	const fixedPrefix = `\\?\?`
    45  	buf := []byte(fixedPrefix)
    46  	for _, p := range prefix {
    47  		buf = append(buf, '\\')
    48  		buf = append(buf, []byte(p)...)
    49  	}
    50  	buf = append(buf, '\\')
    51  	buf = append(buf, []byte(s)...)
    52  	for _, p := range suffix {
    53  		buf = append(buf, '\\')
    54  		buf = append(buf, []byte(p)...)
    55  	}
    56  	s = string(buf)
    57  
    58  	s, err := syscall.FullPath(s)
    59  	if err != nil {
    60  		return "", err
    61  	}
    62  
    63  	s, ok := stringslite.CutPrefix(s, fixedPrefix)
    64  	if !ok {
    65  		return "", errPathEscapes
    66  	}
    67  	s = stringslite.TrimPrefix(s, `\`)
    68  	if s == "" {
    69  		s = "."
    70  	}
    71  
    72  	if !filepathlite.IsLocal(s) {
    73  		return "", errPathEscapes
    74  	}
    75  
    76  	return s, nil
    77  }
    78  
    79  type sysfdType = syscall.Handle
    80  
    81  // openRootNolog is OpenRoot.
    82  func openRootNolog(name string) (*Root, error) {
    83  	if name == "" {
    84  		return nil, &PathError{Op: "open", Path: name, Err: syscall.ENOENT}
    85  	}
    86  	path := fixLongPath(name)
    87  	fd, err := syscall.Open(path, syscall.O_RDONLY|syscall.O_CLOEXEC, 0)
    88  	if err != nil {
    89  		return nil, &PathError{Op: "open", Path: name, Err: err}
    90  	}
    91  	return newRoot(fd, name)
    92  }
    93  
    94  // newRoot returns a new Root.
    95  // If fd is not a directory, it closes it and returns an error.
    96  func newRoot(fd syscall.Handle, name string) (*Root, error) {
    97  	// Check that this is a directory.
    98  	//
    99  	// If we get any errors here, ignore them; worst case we create a Root
   100  	// which returns errors when you try to use it.
   101  	var fi syscall.ByHandleFileInformation
   102  	err := syscall.GetFileInformationByHandle(fd, &fi)
   103  	if err == nil && fi.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 {
   104  		syscall.CloseHandle(fd)
   105  		return nil, &PathError{Op: "open", Path: name, Err: errors.New("not a directory")}
   106  	}
   107  
   108  	r := &Root{root{
   109  		fd:   fd,
   110  		name: name,
   111  	}}
   112  	runtime.SetFinalizer(&r.root, (*root).Close)
   113  	return r, nil
   114  }
   115  
   116  // openRootInRoot is Root.OpenRoot.
   117  func openRootInRoot(r *Root, name string) (*Root, error) {
   118  	fd, err := doInRoot(r, name, rootOpenDir)
   119  	if err != nil {
   120  		return nil, &PathError{Op: "openat", Path: name, Err: err}
   121  	}
   122  	return newRoot(fd, name)
   123  }
   124  
   125  // rootOpenFileNolog is Root.OpenFile.
   126  func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File, error) {
   127  	fd, err := doInRoot(root, name, func(parent syscall.Handle, name string) (syscall.Handle, error) {
   128  		return openat(parent, name, flag, perm)
   129  	})
   130  	if err != nil {
   131  		return nil, &PathError{Op: "openat", Path: name, Err: err}
   132  	}
   133  	return newFile(fd, joinPath(root.Name(), name), "file"), nil
   134  }
   135  
   136  func openat(dirfd syscall.Handle, name string, flag int, perm FileMode) (syscall.Handle, error) {
   137  	h, err := windows.Openat(dirfd, name, flag|syscall.O_CLOEXEC|windows.O_NOFOLLOW_ANY, syscallMode(perm))
   138  	if err == syscall.ELOOP || err == syscall.ENOTDIR {
   139  		if link, err := readReparseLinkAt(dirfd, name); err == nil {
   140  			return syscall.InvalidHandle, errSymlink(link)
   141  		}
   142  	}
   143  	return h, err
   144  }
   145  
   146  func readReparseLinkAt(dirfd syscall.Handle, name string) (string, error) {
   147  	objectName, err := windows.NewNTUnicodeString(name)
   148  	if err != nil {
   149  		return "", err
   150  	}
   151  	objAttrs := &windows.OBJECT_ATTRIBUTES{
   152  		ObjectName: objectName,
   153  	}
   154  	if dirfd != syscall.InvalidHandle {
   155  		objAttrs.RootDirectory = dirfd
   156  	}
   157  	objAttrs.Length = uint32(unsafe.Sizeof(*objAttrs))
   158  	var h syscall.Handle
   159  	err = windows.NtCreateFile(
   160  		&h,
   161  		windows.FILE_GENERIC_READ,
   162  		objAttrs,
   163  		&windows.IO_STATUS_BLOCK{},
   164  		nil,
   165  		uint32(syscall.FILE_ATTRIBUTE_NORMAL),
   166  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   167  		windows.FILE_OPEN,
   168  		windows.FILE_SYNCHRONOUS_IO_NONALERT|windows.FILE_OPEN_REPARSE_POINT,
   169  		0,
   170  		0,
   171  	)
   172  	if err != nil {
   173  		return "", err
   174  	}
   175  	defer syscall.CloseHandle(h)
   176  	return readReparseLinkHandle(h)
   177  }
   178  
   179  func rootOpenDir(parent syscall.Handle, name string) (syscall.Handle, error) {
   180  	h, err := openat(parent, name, syscall.O_RDONLY|syscall.O_CLOEXEC|windows.O_DIRECTORY, 0)
   181  	if err == syscall.ERROR_FILE_NOT_FOUND {
   182  		// Windows returns:
   183  		//   - ERROR_PATH_NOT_FOUND if any path compoenent before the leaf
   184  		//     does not exist or is not a directory.
   185  		//   - ERROR_FILE_NOT_FOUND if the leaf does not exist.
   186  		//
   187  		// This differs from Unix behavior, which is:
   188  		//   - ENOENT if any path component does not exist, including the leaf.
   189  		//   - ENOTDIR if any path component before the leaf is not a directory.
   190  		//
   191  		// We map syscall.ENOENT to ERROR_FILE_NOT_FOUND and syscall.ENOTDIR
   192  		// to ERROR_PATH_NOT_FOUND, but the Windows errors don't quite match.
   193  		//
   194  		// For consistency with os.Open, convert ERROR_FILE_NOT_FOUND here into
   195  		// ERROR_PATH_NOT_FOUND, since we're opening a non-leaf path component.
   196  		err = syscall.ERROR_PATH_NOT_FOUND
   197  	}
   198  	return h, err
   199  }
   200  
   201  func rootStat(r *Root, name string, lstat bool) (FileInfo, error) {
   202  	if len(name) > 0 && IsPathSeparator(name[len(name)-1]) {
   203  		// When a filename ends with a path separator,
   204  		// Lstat behaves like Stat.
   205  		//
   206  		// This behavior is not based on a principled decision here,
   207  		// merely the empirical evidence that Lstat behaves this way.
   208  		lstat = false
   209  	}
   210  	fi, err := doInRoot(r, name, func(parent syscall.Handle, n string) (FileInfo, error) {
   211  		fd, err := openat(parent, n, windows.O_OPEN_REPARSE, 0)
   212  		if err != nil {
   213  			return nil, err
   214  		}
   215  		defer syscall.CloseHandle(fd)
   216  		fi, err := statHandle(name, fd)
   217  		if err != nil {
   218  			return nil, err
   219  		}
   220  		if !lstat && fi.(*fileStat).isReparseTagNameSurrogate() {
   221  			link, err := readReparseLinkHandle(fd)
   222  			if err != nil {
   223  				return nil, err
   224  			}
   225  			return nil, errSymlink(link)
   226  		}
   227  		return fi, nil
   228  	})
   229  	if err != nil {
   230  		return nil, &PathError{Op: "statat", Path: name, Err: err}
   231  	}
   232  	return fi, nil
   233  }
   234  
   235  func mkdirat(dirfd syscall.Handle, name string, perm FileMode) error {
   236  	return windows.Mkdirat(dirfd, name, syscallMode(perm))
   237  }
   238  
   239  func removeat(dirfd syscall.Handle, name string) error {
   240  	return windows.Deleteat(dirfd, name)
   241  }
   242  

View as plain text