Source file src/internal/syscall/windows/at_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  package windows
     6  
     7  import (
     8  	"runtime"
     9  	"structs"
    10  	"syscall"
    11  	"unsafe"
    12  )
    13  
    14  // Openat flags not supported by syscall.Open.
    15  //
    16  // These are invented values.
    17  //
    18  // When adding a new flag here, add an unexported version to
    19  // the set of invented O_ values in syscall/types_windows.go
    20  // to avoid overlap.
    21  const (
    22  	O_DIRECTORY    = 0x100000   // target must be a directory
    23  	O_NOFOLLOW_ANY = 0x20000000 // disallow symlinks anywhere in the path
    24  	O_OPEN_REPARSE = 0x40000000 // FILE_OPEN_REPARSE_POINT, used by Lstat
    25  	O_WRITE_ATTRS  = 0x80000000 // FILE_WRITE_ATTRIBUTES, used by Chmod
    26  )
    27  
    28  func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ syscall.Handle, e1 error) {
    29  	if len(name) == 0 {
    30  		return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
    31  	}
    32  
    33  	var access, options uint32
    34  	switch flag & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
    35  	case syscall.O_RDONLY:
    36  		// FILE_GENERIC_READ includes FILE_LIST_DIRECTORY.
    37  		access = FILE_GENERIC_READ
    38  	case syscall.O_WRONLY:
    39  		access = FILE_GENERIC_WRITE
    40  		options |= FILE_NON_DIRECTORY_FILE
    41  	case syscall.O_RDWR:
    42  		access = FILE_GENERIC_READ | FILE_GENERIC_WRITE
    43  		options |= FILE_NON_DIRECTORY_FILE
    44  	default:
    45  		// Stat opens files without requesting read or write permissions,
    46  		// but we still need to request SYNCHRONIZE.
    47  		access = SYNCHRONIZE
    48  	}
    49  	if flag&syscall.O_CREAT != 0 {
    50  		access |= FILE_GENERIC_WRITE
    51  	}
    52  	if flag&syscall.O_APPEND != 0 {
    53  		access |= FILE_APPEND_DATA
    54  		// Remove FILE_WRITE_DATA access unless O_TRUNC is set,
    55  		// in which case we need it to truncate the file.
    56  		if flag&syscall.O_TRUNC == 0 {
    57  			access &^= FILE_WRITE_DATA
    58  		}
    59  	}
    60  	if flag&O_DIRECTORY != 0 {
    61  		options |= FILE_DIRECTORY_FILE
    62  		access |= FILE_LIST_DIRECTORY
    63  	}
    64  	if flag&syscall.O_SYNC != 0 {
    65  		options |= FILE_WRITE_THROUGH
    66  	}
    67  	if flag&O_WRITE_ATTRS != 0 {
    68  		access |= FILE_WRITE_ATTRIBUTES
    69  	}
    70  	// Allow File.Stat.
    71  	access |= STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | FILE_READ_EA
    72  
    73  	objAttrs := &OBJECT_ATTRIBUTES{}
    74  	if flag&O_NOFOLLOW_ANY != 0 {
    75  		objAttrs.Attributes |= OBJ_DONT_REPARSE
    76  	}
    77  	if flag&syscall.O_CLOEXEC == 0 {
    78  		objAttrs.Attributes |= OBJ_INHERIT
    79  	}
    80  	if err := objAttrs.init(dirfd, name); err != nil {
    81  		return syscall.InvalidHandle, err
    82  	}
    83  
    84  	if flag&O_OPEN_REPARSE != 0 {
    85  		options |= FILE_OPEN_REPARSE_POINT
    86  	}
    87  
    88  	// We don't use FILE_OVERWRITE/FILE_OVERWRITE_IF, because when opening
    89  	// a file with FILE_ATTRIBUTE_READONLY these will replace an existing
    90  	// file with a new, read-only one.
    91  	//
    92  	// Instead, we ftruncate the file after opening when O_TRUNC is set.
    93  	var disposition uint32
    94  	switch {
    95  	case flag&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
    96  		disposition = FILE_CREATE
    97  		options |= FILE_OPEN_REPARSE_POINT // don't follow symlinks
    98  	case flag&syscall.O_CREAT == syscall.O_CREAT:
    99  		disposition = FILE_OPEN_IF
   100  	default:
   101  		disposition = FILE_OPEN
   102  	}
   103  
   104  	fileAttrs := uint32(FILE_ATTRIBUTE_NORMAL)
   105  	if perm&syscall.S_IWRITE == 0 {
   106  		fileAttrs = FILE_ATTRIBUTE_READONLY
   107  	}
   108  
   109  	var h syscall.Handle
   110  	err := NtCreateFile(
   111  		&h,
   112  		SYNCHRONIZE|access,
   113  		objAttrs,
   114  		&IO_STATUS_BLOCK{},
   115  		nil,
   116  		fileAttrs,
   117  		FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
   118  		disposition,
   119  		FILE_SYNCHRONOUS_IO_NONALERT|FILE_OPEN_FOR_BACKUP_INTENT|options,
   120  		nil,
   121  		0,
   122  	)
   123  	if err != nil {
   124  		return h, ntCreateFileError(err, flag)
   125  	}
   126  
   127  	if flag&syscall.O_TRUNC != 0 {
   128  		err = syscall.Ftruncate(h, 0)
   129  		if err != nil {
   130  			syscall.CloseHandle(h)
   131  			return syscall.InvalidHandle, err
   132  		}
   133  	}
   134  
   135  	return h, nil
   136  }
   137  
   138  // ntCreateFileError maps error returns from NTCreateFile to user-visible errors.
   139  func ntCreateFileError(err error, flag uint64) error {
   140  	s, ok := err.(NTStatus)
   141  	if !ok {
   142  		// Shouldn't really be possible, NtCreateFile always returns NTStatus.
   143  		return err
   144  	}
   145  	switch s {
   146  	case STATUS_REPARSE_POINT_ENCOUNTERED:
   147  		return syscall.ELOOP
   148  	case STATUS_NOT_A_DIRECTORY:
   149  		// ENOTDIR is the errno returned by open when O_DIRECTORY is specified
   150  		// and the target is not a directory.
   151  		//
   152  		// NtCreateFile can return STATUS_NOT_A_DIRECTORY under other circumstances,
   153  		// such as when opening "file/" where "file" is not a directory.
   154  		// (This might be Windows version dependent.)
   155  		//
   156  		// Only map STATUS_NOT_A_DIRECTORY to ENOTDIR when O_DIRECTORY is specified.
   157  		if flag&O_DIRECTORY != 0 {
   158  			return syscall.ENOTDIR
   159  		}
   160  	case STATUS_FILE_IS_A_DIRECTORY:
   161  		return syscall.EISDIR
   162  	case STATUS_OBJECT_NAME_COLLISION:
   163  		return syscall.EEXIST
   164  	}
   165  	return s.Errno()
   166  }
   167  
   168  func Mkdirat(dirfd syscall.Handle, name string, mode uint32) error {
   169  	objAttrs := &OBJECT_ATTRIBUTES{}
   170  	if err := objAttrs.init(dirfd, name); err != nil {
   171  		return err
   172  	}
   173  	var h syscall.Handle
   174  	err := NtCreateFile(
   175  		&h,
   176  		FILE_GENERIC_READ,
   177  		objAttrs,
   178  		&IO_STATUS_BLOCK{},
   179  		nil,
   180  		syscall.FILE_ATTRIBUTE_NORMAL,
   181  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   182  		FILE_CREATE,
   183  		FILE_DIRECTORY_FILE,
   184  		nil,
   185  		0,
   186  	)
   187  	if err != nil {
   188  		return ntCreateFileError(err, 0)
   189  	}
   190  	syscall.CloseHandle(h)
   191  	return nil
   192  }
   193  
   194  func Deleteat(dirfd syscall.Handle, name string, options uint32) error {
   195  	if name == "." {
   196  		// NtOpenFile's documentation isn't explicit about what happens when deleting ".".
   197  		// Make this an error consistent with that of POSIX.
   198  		return syscall.EINVAL
   199  	}
   200  	objAttrs := &OBJECT_ATTRIBUTES{}
   201  	if err := objAttrs.init(dirfd, name); err != nil {
   202  		return err
   203  	}
   204  	var h syscall.Handle
   205  	err := NtOpenFile(
   206  		&h,
   207  		SYNCHRONIZE|DELETE,
   208  		objAttrs,
   209  		&IO_STATUS_BLOCK{},
   210  		FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
   211  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT|options,
   212  	)
   213  	if err != nil {
   214  		return ntCreateFileError(err, 0)
   215  	}
   216  	defer syscall.CloseHandle(h)
   217  
   218  	const (
   219  		FileDispositionInformation   = 13
   220  		FileDispositionInformationEx = 64
   221  	)
   222  
   223  	// First, attempt to delete the file using POSIX semantics
   224  	// (which permit a file to be deleted while it is still open).
   225  	// This matches the behavior of DeleteFileW.
   226  	err = NtSetInformationFile(
   227  		h,
   228  		&IO_STATUS_BLOCK{},
   229  		unsafe.Pointer(&FILE_DISPOSITION_INFORMATION_EX{
   230  			Flags: FILE_DISPOSITION_DELETE |
   231  				FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK |
   232  				FILE_DISPOSITION_POSIX_SEMANTICS |
   233  				// This differs from DeleteFileW, but matches os.Remove's
   234  				// behavior on Unix platforms of permitting deletion of
   235  				// read-only files.
   236  				FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE,
   237  		}),
   238  		uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION_EX{})),
   239  		FileDispositionInformationEx,
   240  	)
   241  	switch err {
   242  	case nil:
   243  		return nil
   244  	case STATUS_CANNOT_DELETE, STATUS_DIRECTORY_NOT_EMPTY:
   245  		return err.(NTStatus).Errno()
   246  	}
   247  
   248  	// If the prior deletion failed, the filesystem either doesn't support
   249  	// POSIX semantics (for example, FAT), or hasn't implemented
   250  	// FILE_DISPOSITION_INFORMATION_EX.
   251  	//
   252  	// Try again.
   253  	err = NtSetInformationFile(
   254  		h,
   255  		&IO_STATUS_BLOCK{},
   256  		unsafe.Pointer(&FILE_DISPOSITION_INFORMATION{
   257  			DeleteFile: true,
   258  		}),
   259  		uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION{})),
   260  		FileDispositionInformation,
   261  	)
   262  	if st, ok := err.(NTStatus); ok {
   263  		return st.Errno()
   264  	}
   265  	return err
   266  }
   267  
   268  func Renameat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, newpath string) error {
   269  	objAttrs := &OBJECT_ATTRIBUTES{}
   270  	if err := objAttrs.init(olddirfd, oldpath); err != nil {
   271  		return err
   272  	}
   273  	var h syscall.Handle
   274  	err := NtOpenFile(
   275  		&h,
   276  		SYNCHRONIZE|DELETE,
   277  		objAttrs,
   278  		&IO_STATUS_BLOCK{},
   279  		FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
   280  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT,
   281  	)
   282  	if err != nil {
   283  		return ntCreateFileError(err, 0)
   284  	}
   285  	defer syscall.CloseHandle(h)
   286  
   287  	renameInfoEx := FILE_RENAME_INFORMATION_EX{
   288  		Flags: FILE_RENAME_REPLACE_IF_EXISTS |
   289  			FILE_RENAME_POSIX_SEMANTICS,
   290  		RootDirectory: newdirfd,
   291  	}
   292  	p16, err := syscall.UTF16FromString(newpath)
   293  	if err != nil {
   294  		return err
   295  	}
   296  	if len(p16) > len(renameInfoEx.FileName) {
   297  		return syscall.EINVAL
   298  	}
   299  	copy(renameInfoEx.FileName[:], p16)
   300  	renameInfoEx.FileNameLength = uint32((len(p16) - 1) * 2)
   301  
   302  	const (
   303  		FileRenameInformation   = 10
   304  		FileRenameInformationEx = 65
   305  	)
   306  	err = NtSetInformationFile(
   307  		h,
   308  		&IO_STATUS_BLOCK{},
   309  		unsafe.Pointer(&renameInfoEx),
   310  		uint32(unsafe.Sizeof(FILE_RENAME_INFORMATION_EX{})),
   311  		FileRenameInformationEx,
   312  	)
   313  	if err == nil {
   314  		return nil
   315  	}
   316  
   317  	// If the prior rename failed, the filesystem might not support
   318  	// POSIX semantics (for example, FAT), or might not have implemented
   319  	// FILE_RENAME_INFORMATION_EX.
   320  	//
   321  	// Try again.
   322  	renameInfo := FILE_RENAME_INFORMATION{
   323  		ReplaceIfExists: true,
   324  		RootDirectory:   newdirfd,
   325  	}
   326  	copy(renameInfo.FileName[:], p16)
   327  	renameInfo.FileNameLength = renameInfoEx.FileNameLength
   328  
   329  	err = NtSetInformationFile(
   330  		h,
   331  		&IO_STATUS_BLOCK{},
   332  		unsafe.Pointer(&renameInfo),
   333  		uint32(unsafe.Sizeof(FILE_RENAME_INFORMATION{})),
   334  		FileRenameInformation,
   335  	)
   336  	if st, ok := err.(NTStatus); ok {
   337  		return st.Errno()
   338  	}
   339  	return err
   340  }
   341  
   342  func Linkat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, newpath string) error {
   343  	objAttrs := &OBJECT_ATTRIBUTES{}
   344  	if err := objAttrs.init(olddirfd, oldpath); err != nil {
   345  		return err
   346  	}
   347  	var h syscall.Handle
   348  	err := NtOpenFile(
   349  		&h,
   350  		SYNCHRONIZE|FILE_WRITE_ATTRIBUTES,
   351  		objAttrs,
   352  		&IO_STATUS_BLOCK{},
   353  		FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
   354  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT,
   355  	)
   356  	if err != nil {
   357  		return ntCreateFileError(err, 0)
   358  	}
   359  	defer syscall.CloseHandle(h)
   360  
   361  	linkInfo := FILE_LINK_INFORMATION{
   362  		RootDirectory: newdirfd,
   363  	}
   364  	p16, err := syscall.UTF16FromString(newpath)
   365  	if err != nil {
   366  		return err
   367  	}
   368  	if len(p16) > len(linkInfo.FileName) {
   369  		return syscall.EINVAL
   370  	}
   371  	copy(linkInfo.FileName[:], p16)
   372  	linkInfo.FileNameLength = uint32((len(p16) - 1) * 2)
   373  
   374  	const (
   375  		FileLinkInformation = 11
   376  	)
   377  	err = NtSetInformationFile(
   378  		h,
   379  		&IO_STATUS_BLOCK{},
   380  		unsafe.Pointer(&linkInfo),
   381  		uint32(unsafe.Sizeof(FILE_LINK_INFORMATION{})),
   382  		FileLinkInformation,
   383  	)
   384  	if st, ok := err.(NTStatus); ok {
   385  		return st.Errno()
   386  	}
   387  	return err
   388  }
   389  
   390  // SymlinkatFlags configure Symlinkat.
   391  //
   392  // Symbolic links have two properties: They may be directory or file links,
   393  // and they may be absolute or relative.
   394  //
   395  // The Windows API defines flags describing these properties
   396  // (SYMBOLIC_LINK_FLAG_DIRECTORY and SYMLINK_FLAG_RELATIVE),
   397  // but the flags are passed to different system calls and
   398  // do not have distinct values, so we define our own enumeration
   399  // that permits expressing both.
   400  type SymlinkatFlags uint
   401  
   402  const (
   403  	SYMLINKAT_DIRECTORY = SymlinkatFlags(1 << iota)
   404  	SYMLINKAT_RELATIVE
   405  )
   406  
   407  func Symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error {
   408  	// Temporarily acquire symlink-creating privileges if possible.
   409  	// This is the behavior of CreateSymbolicLinkW.
   410  	//
   411  	// (When passed the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag,
   412  	// CreateSymbolicLinkW ignores errors in acquiring privileges, as we do here.)
   413  	return withPrivilege("SeCreateSymbolicLinkPrivilege", func() error {
   414  		return symlinkat(oldname, newdirfd, newname, flags)
   415  	})
   416  }
   417  
   418  func symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error {
   419  	oldnameu16, err := syscall.UTF16FromString(oldname)
   420  	if err != nil {
   421  		return err
   422  	}
   423  	oldnameu16 = oldnameu16[:len(oldnameu16)-1] // trim off terminal NUL
   424  
   425  	var options uint32
   426  	if flags&SYMLINKAT_DIRECTORY != 0 {
   427  		options |= FILE_DIRECTORY_FILE
   428  	} else {
   429  		options |= FILE_NON_DIRECTORY_FILE
   430  	}
   431  
   432  	objAttrs := &OBJECT_ATTRIBUTES{}
   433  	if err := objAttrs.init(newdirfd, newname); err != nil {
   434  		return err
   435  	}
   436  	var h syscall.Handle
   437  	err = NtCreateFile(
   438  		&h,
   439  		SYNCHRONIZE|FILE_WRITE_ATTRIBUTES|DELETE,
   440  		objAttrs,
   441  		&IO_STATUS_BLOCK{},
   442  		nil,
   443  		syscall.FILE_ATTRIBUTE_NORMAL,
   444  		0,
   445  		FILE_CREATE,
   446  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT|options,
   447  		nil,
   448  		0,
   449  	)
   450  	if err != nil {
   451  		return ntCreateFileError(err, 0)
   452  	}
   453  	defer syscall.CloseHandle(h)
   454  
   455  	// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer
   456  	type reparseDataBufferT struct {
   457  		_ structs.HostLayout
   458  
   459  		ReparseTag        uint32
   460  		ReparseDataLength uint16
   461  		Reserved          uint16
   462  
   463  		SubstituteNameOffset uint16
   464  		SubstituteNameLength uint16
   465  		PrintNameOffset      uint16
   466  		PrintNameLength      uint16
   467  		Flags                uint32
   468  	}
   469  
   470  	const (
   471  		headerSize = uint16(unsafe.Offsetof(reparseDataBufferT{}.SubstituteNameOffset))
   472  		bufferSize = uint16(unsafe.Sizeof(reparseDataBufferT{}))
   473  	)
   474  
   475  	// Data buffer containing a SymbolicLinkReparseBuffer followed by the link target.
   476  	rdbbuf := make([]byte, bufferSize+uint16(2*len(oldnameu16)))
   477  
   478  	rdb := (*reparseDataBufferT)(unsafe.Pointer(&rdbbuf[0]))
   479  	rdb.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK
   480  	rdb.ReparseDataLength = uint16(len(rdbbuf)) - uint16(headerSize)
   481  	rdb.SubstituteNameOffset = 0
   482  	rdb.SubstituteNameLength = uint16(2 * len(oldnameu16))
   483  	rdb.PrintNameOffset = 0
   484  	rdb.PrintNameLength = rdb.SubstituteNameLength
   485  	if flags&SYMLINKAT_RELATIVE != 0 {
   486  		rdb.Flags = SYMLINK_FLAG_RELATIVE
   487  	}
   488  
   489  	namebuf := rdbbuf[bufferSize:]
   490  	copy(namebuf, unsafe.String((*byte)(unsafe.Pointer(&oldnameu16[0])), 2*len(oldnameu16)))
   491  
   492  	err = syscall.DeviceIoControl(
   493  		h,
   494  		FSCTL_SET_REPARSE_POINT,
   495  		&rdbbuf[0],
   496  		uint32(len(rdbbuf)),
   497  		nil,
   498  		0,
   499  		nil,
   500  		nil)
   501  	if err != nil {
   502  		// Creating the symlink has failed, so try to remove the file.
   503  		const FileDispositionInformation = 13
   504  		NtSetInformationFile(
   505  			h,
   506  			&IO_STATUS_BLOCK{},
   507  			unsafe.Pointer(&FILE_DISPOSITION_INFORMATION{
   508  				DeleteFile: true,
   509  			}),
   510  			uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION{})),
   511  			FileDispositionInformation,
   512  		)
   513  		return err
   514  	}
   515  
   516  	return nil
   517  }
   518  
   519  // withPrivilege temporariliy acquires the named privilege and runs f.
   520  // If the privilege cannot be acquired it runs f anyway,
   521  // which should fail with an appropriate error.
   522  func withPrivilege(privilege string, f func() error) error {
   523  	runtime.LockOSThread()
   524  	defer runtime.UnlockOSThread()
   525  
   526  	err := ImpersonateSelf(SecurityImpersonation)
   527  	if err != nil {
   528  		return f()
   529  	}
   530  	defer RevertToSelf()
   531  
   532  	curThread, err := GetCurrentThread()
   533  	if err != nil {
   534  		return f()
   535  	}
   536  	var token syscall.Token
   537  	err = OpenThreadToken(curThread, syscall.TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES, false, &token)
   538  	if err != nil {
   539  		return f()
   540  	}
   541  	defer syscall.CloseHandle(syscall.Handle(token))
   542  
   543  	privStr, err := syscall.UTF16PtrFromString(privilege)
   544  	if err != nil {
   545  		return f()
   546  	}
   547  	var tokenPriv TOKEN_PRIVILEGES
   548  	err = LookupPrivilegeValue(nil, privStr, &tokenPriv.Privileges[0].Luid)
   549  	if err != nil {
   550  		return f()
   551  	}
   552  
   553  	tokenPriv.PrivilegeCount = 1
   554  	tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
   555  	err = AdjustTokenPrivileges(token, false, &tokenPriv, 0, nil, nil)
   556  	if err != nil {
   557  		return f()
   558  	}
   559  
   560  	return f()
   561  }
   562  

View as plain text