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  	"internal/oserror"
     9  	"internal/stringslite"
    10  	"runtime"
    11  	"structs"
    12  	"syscall"
    13  	"unsafe"
    14  )
    15  
    16  // Openat flags supported by syscall.Open.
    17  const (
    18  	O_DIRECTORY = 0x04000 // target must be a directory
    19  )
    20  
    21  // Openat flags not supported by syscall.Open.
    22  //
    23  // These are invented values, use values in the 33-63 bit range
    24  // to avoid overlap with flags and attributes supported by [syscall.Open].
    25  //
    26  // When adding a new flag here, add an unexported version to
    27  // the set of invented O_ values in syscall/types_windows.go
    28  // to avoid overlap.
    29  const (
    30  	O_NOFOLLOW_ANY = 0x200000000 // disallow symlinks anywhere in the path
    31  	O_WRITE_ATTRS  = 0x800000000 // FILE_WRITE_ATTRIBUTES, used by Chmod
    32  )
    33  
    34  func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ syscall.Handle, e1 error) {
    35  	if len(name) == 0 {
    36  		return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
    37  	}
    38  
    39  	var access, options uint32
    40  	// Map Win32 file flags to NT create options.
    41  	fileFlags := uint32(flag) & FileFlagsMask
    42  	if fileFlags&^ValidFileFlagsMask != 0 {
    43  		return syscall.InvalidHandle, oserror.ErrInvalid
    44  	}
    45  	if fileFlags&O_FILE_FLAG_OVERLAPPED == 0 {
    46  		options |= FILE_SYNCHRONOUS_IO_NONALERT
    47  	}
    48  	if fileFlags&O_FILE_FLAG_DELETE_ON_CLOSE != 0 {
    49  		access |= DELETE
    50  	}
    51  	setOptionFlag := func(ntFlag, win32Flag uint32) {
    52  		if fileFlags&win32Flag != 0 {
    53  			options |= ntFlag
    54  		}
    55  	}
    56  	setOptionFlag(FILE_NO_INTERMEDIATE_BUFFERING, O_FILE_FLAG_NO_BUFFERING)
    57  	setOptionFlag(FILE_WRITE_THROUGH, O_FILE_FLAG_WRITE_THROUGH)
    58  	setOptionFlag(FILE_SEQUENTIAL_ONLY, O_FILE_FLAG_SEQUENTIAL_SCAN)
    59  	setOptionFlag(FILE_RANDOM_ACCESS, O_FILE_FLAG_RANDOM_ACCESS)
    60  	setOptionFlag(FILE_OPEN_FOR_BACKUP_INTENT, O_FILE_FLAG_BACKUP_SEMANTICS)
    61  	setOptionFlag(FILE_SESSION_AWARE, O_FILE_FLAG_SESSION_AWARE)
    62  	setOptionFlag(FILE_DELETE_ON_CLOSE, O_FILE_FLAG_DELETE_ON_CLOSE)
    63  	setOptionFlag(FILE_OPEN_NO_RECALL, O_FILE_FLAG_OPEN_NO_RECALL)
    64  	setOptionFlag(FILE_OPEN_REPARSE_POINT, O_FILE_FLAG_OPEN_REPARSE_POINT)
    65  
    66  	switch flag & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
    67  	case syscall.O_RDONLY:
    68  		// FILE_GENERIC_READ includes FILE_LIST_DIRECTORY.
    69  		access |= FILE_GENERIC_READ
    70  	case syscall.O_WRONLY:
    71  		access |= FILE_GENERIC_WRITE
    72  		options |= FILE_NON_DIRECTORY_FILE
    73  	case syscall.O_RDWR:
    74  		access |= FILE_GENERIC_READ | FILE_GENERIC_WRITE
    75  		options |= FILE_NON_DIRECTORY_FILE
    76  	default:
    77  		// Stat opens files without requesting read or write permissions,
    78  		// but we still need to request SYNCHRONIZE.
    79  		access |= SYNCHRONIZE
    80  	}
    81  	if flag&syscall.O_CREAT != 0 {
    82  		access |= FILE_GENERIC_WRITE
    83  	}
    84  	if fileFlags&O_FILE_FLAG_NO_BUFFERING != 0 {
    85  		// Disable buffering implies no implicit append access.
    86  		access &^= FILE_APPEND_DATA
    87  	}
    88  	if flag&syscall.O_APPEND != 0 {
    89  		access |= FILE_APPEND_DATA
    90  		// Remove FILE_WRITE_DATA access unless O_TRUNC is set,
    91  		// in which case we need it to truncate the file.
    92  		if flag&syscall.O_TRUNC == 0 {
    93  			access &^= FILE_WRITE_DATA
    94  		}
    95  	}
    96  	if flag&O_DIRECTORY != 0 {
    97  		options |= FILE_DIRECTORY_FILE
    98  		access |= FILE_LIST_DIRECTORY
    99  	}
   100  	if flag&syscall.O_SYNC != 0 {
   101  		options |= FILE_WRITE_THROUGH
   102  	}
   103  	if flag&O_WRITE_ATTRS != 0 {
   104  		access |= FILE_WRITE_ATTRIBUTES
   105  	}
   106  	// Allow File.Stat.
   107  	access |= STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | FILE_READ_EA
   108  
   109  	objAttrs := &OBJECT_ATTRIBUTES{}
   110  	if flag&O_NOFOLLOW_ANY != 0 {
   111  		objAttrs.Attributes |= OBJ_DONT_REPARSE
   112  	}
   113  	if flag&syscall.O_CLOEXEC == 0 {
   114  		objAttrs.Attributes |= OBJ_INHERIT
   115  	}
   116  	if fileFlags&O_FILE_FLAG_POSIX_SEMANTICS == 0 {
   117  		objAttrs.Attributes |= OBJ_CASE_INSENSITIVE
   118  	}
   119  	if err := objAttrs.init(dirfd, name); err != nil {
   120  		return syscall.InvalidHandle, err
   121  	}
   122  
   123  	// We don't use FILE_OVERWRITE/FILE_OVERWRITE_IF, because when opening
   124  	// a file with FILE_ATTRIBUTE_READONLY these will replace an existing
   125  	// file with a new, read-only one.
   126  	//
   127  	// Instead, we ftruncate the file after opening when O_TRUNC is set.
   128  	var disposition uint32
   129  	switch {
   130  	case flag&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
   131  		disposition = FILE_CREATE
   132  		options |= FILE_OPEN_REPARSE_POINT // don't follow symlinks
   133  	case flag&syscall.O_CREAT == syscall.O_CREAT:
   134  		disposition = FILE_OPEN_IF
   135  	default:
   136  		disposition = FILE_OPEN
   137  	}
   138  
   139  	fileAttrs := uint32(FILE_ATTRIBUTE_NORMAL)
   140  	if perm&syscall.S_IWRITE == 0 {
   141  		fileAttrs = FILE_ATTRIBUTE_READONLY
   142  	}
   143  
   144  	var h syscall.Handle
   145  	var err error
   146  	if TestOpenatFallback && flag&O_NOFOLLOW_ANY != 0 {
   147  		err = STATUS_INVALID_PARAMETER
   148  	} else {
   149  		err = NtCreateFile(
   150  			&h,
   151  			SYNCHRONIZE|access,
   152  			objAttrs,
   153  			&IO_STATUS_BLOCK{},
   154  			nil,
   155  			fileAttrs,
   156  			FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
   157  			disposition,
   158  			FILE_OPEN_FOR_BACKUP_INTENT|options,
   159  			nil,
   160  			0,
   161  		)
   162  	}
   163  	if err == STATUS_INVALID_PARAMETER && flag&O_NOFOLLOW_ANY != 0 {
   164  		h, err = openatFallback(name, SYNCHRONIZE|access, *objAttrs, fileAttrs, disposition, FILE_OPEN_FOR_BACKUP_INTENT|options)
   165  	}
   166  	if err != nil {
   167  		return h, ntCreateFileError(err, flag)
   168  	}
   169  
   170  	if flag&syscall.O_TRUNC != 0 {
   171  		err = syscall.Ftruncate(h, 0)
   172  		if err == ERROR_INVALID_PARAMETER {
   173  			// ERROR_INVALID_PARAMETER means truncation is not supported on this file handle.
   174  			// Unix's O_TRUNC specification says to ignore O_TRUNC on named pipes and terminal devices.
   175  			// We do the same here.
   176  			if t, err1 := syscall.GetFileType(h); err1 == nil && (t == syscall.FILE_TYPE_PIPE || t == syscall.FILE_TYPE_CHAR) {
   177  				err = nil
   178  			}
   179  		}
   180  		if err != nil {
   181  			syscall.CloseHandle(h)
   182  			return syscall.InvalidHandle, err
   183  		}
   184  	}
   185  
   186  	return h, nil
   187  }
   188  
   189  // TestOpenatFallback should only be used for testing purposes.
   190  // When set, Openat simulates a system that does not support OBJ_DONT_REPARSE.
   191  var TestOpenatFallback bool
   192  
   193  // openatFallback implements O_NOFOLLOW_ANY for a single path component on
   194  // Windows versions that do not support OBJ_DONT_REPARSE (including Windows 10
   195  // build 10240). See go.dev/issue/78131.
   196  func openatFallback(name string, access uint32, attrs OBJECT_ATTRIBUTES, fileAttrs, disposition, options uint32) (syscall.Handle, error) {
   197  	// FILE_OPEN_REPARSE_POINT only prevents following the final component.
   198  	// All current production callers that request O_NOFOLLOW_ANY pass a
   199  	// single component: os.Root splits paths (including symlink targets)
   200  	// before opening each component, and RemoveAll passes a basename or a
   201  	// directory entry name relative to an open parent directory. Thus this
   202  	// fallback also supports multi-component paths passed to those APIs.
   203  	// Reject other paths here rather than weaken O_NOFOLLOW_ANY: this is
   204  	// not a general replacement for OBJ_DONT_REPARSE.
   205  	if attrs.RootDirectory == 0 || name == ".." ||
   206  		stringslite.IndexByte(name, '\\') >= 0 || stringslite.IndexByte(name, '/') >= 0 || stringslite.IndexByte(name, ':') >= 0 {
   207  		return syscall.InvalidHandle, STATUS_INVALID_PARAMETER
   208  	}
   209  	attrs.Attributes &^= OBJ_DONT_REPARSE
   210  	var h syscall.Handle
   211  	err := NtCreateFile(
   212  		&h, access, &attrs, &IO_STATUS_BLOCK{}, nil, fileAttrs,
   213  		FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, disposition,
   214  		(options|FILE_OPEN_REPARSE_POINT)&^FILE_DELETE_ON_CLOSE, nil, 0,
   215  	)
   216  	if err != nil {
   217  		return syscall.InvalidHandle, err
   218  	}
   219  	// Inspect the handle, not the path, before truncation or delete-on-close.
   220  	// Skip this check if opening the reparse point itself was requested,
   221  	// or if O_CREAT|O_EXCL requires creating a new file without following links.
   222  	if options&FILE_OPEN_REPARSE_POINT == 0 {
   223  		var info syscall.ByHandleFileInformation
   224  		err = syscall.GetFileInformationByHandle(h, &info)
   225  		if err == nil && info.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
   226  			err = STATUS_REPARSE_POINT_ENCOUNTERED
   227  		}
   228  		if err != nil {
   229  			syscall.CloseHandle(h)
   230  			return syscall.InvalidHandle, err
   231  		}
   232  	}
   233  	if options&FILE_DELETE_ON_CLOSE == 0 {
   234  		return h, nil
   235  	}
   236  	defer syscall.CloseHandle(h)
   237  
   238  	// Only enable deletion after checking for a reparse point. An empty
   239  	// name relative to h reopens the same file, even if it has been renamed
   240  	// or its directory entry has been replaced since the first open.
   241  	// The file already exists, including when created with O_EXCL.
   242  	attrs.RootDirectory = h
   243  	attrs.ObjectName = &NTUnicodeString{}
   244  	var dh syscall.Handle
   245  	err = NtOpenFile(
   246  		&dh, access, &attrs, &IO_STATUS_BLOCK{},
   247  		FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
   248  		options|FILE_OPEN_REPARSE_POINT,
   249  	)
   250  	if err != nil {
   251  		return syscall.InvalidHandle, err
   252  	}
   253  	return dh, nil
   254  }
   255  
   256  // ntCreateFileError maps error returns from NTCreateFile to user-visible errors.
   257  func ntCreateFileError(err error, flag uint64) error {
   258  	s, ok := err.(NTStatus)
   259  	if !ok {
   260  		// The Openat fallback can also return Win32 errors.
   261  		return err
   262  	}
   263  	switch s {
   264  	case STATUS_REPARSE_POINT_ENCOUNTERED:
   265  		return syscall.ELOOP
   266  	case STATUS_NOT_A_DIRECTORY:
   267  		// ENOTDIR is the errno returned by open when O_DIRECTORY is specified
   268  		// and the target is not a directory.
   269  		//
   270  		// NtCreateFile can return STATUS_NOT_A_DIRECTORY under other circumstances,
   271  		// such as when opening "file/" where "file" is not a directory.
   272  		// (This might be Windows version dependent.)
   273  		//
   274  		// Only map STATUS_NOT_A_DIRECTORY to ENOTDIR when O_DIRECTORY is specified.
   275  		if flag&O_DIRECTORY != 0 {
   276  			return syscall.ENOTDIR
   277  		}
   278  	case STATUS_FILE_IS_A_DIRECTORY:
   279  		return syscall.EISDIR
   280  	case STATUS_OBJECT_NAME_COLLISION:
   281  		return syscall.EEXIST
   282  	}
   283  	return s.Errno()
   284  }
   285  
   286  func Mkdirat(dirfd syscall.Handle, name string, mode uint32) error {
   287  	objAttrs := &OBJECT_ATTRIBUTES{}
   288  	if err := objAttrs.init(dirfd, name); err != nil {
   289  		return err
   290  	}
   291  	var h syscall.Handle
   292  	err := NtCreateFile(
   293  		&h,
   294  		FILE_GENERIC_READ,
   295  		objAttrs,
   296  		&IO_STATUS_BLOCK{},
   297  		nil,
   298  		syscall.FILE_ATTRIBUTE_NORMAL,
   299  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   300  		FILE_CREATE,
   301  		FILE_DIRECTORY_FILE,
   302  		nil,
   303  		0,
   304  	)
   305  	if err != nil {
   306  		return ntCreateFileError(err, 0)
   307  	}
   308  	syscall.CloseHandle(h)
   309  	return nil
   310  }
   311  
   312  func Deleteat(dirfd syscall.Handle, name string, options uint32) error {
   313  	if name == "." {
   314  		// NtOpenFile's documentation isn't explicit about what happens when deleting ".".
   315  		// Make this an error consistent with that of POSIX.
   316  		return syscall.EINVAL
   317  	}
   318  	objAttrs := &OBJECT_ATTRIBUTES{}
   319  	if err := objAttrs.init(dirfd, name); err != nil {
   320  		return err
   321  	}
   322  	var h syscall.Handle
   323  	err := NtOpenFile(
   324  		&h,
   325  		FILE_READ_ATTRIBUTES|DELETE,
   326  		objAttrs,
   327  		&IO_STATUS_BLOCK{},
   328  		FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
   329  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|options,
   330  	)
   331  	if err != nil {
   332  		if ntStatus, ok := err.(NTStatus); !ok || ntStatus != STATUS_ACCESS_DENIED {
   333  			return ntCreateFileError(err, 0)
   334  		}
   335  
   336  		// Access denied, try opening with DELETE only.
   337  		// This may succeed if the file has restrictive permissions
   338  		// but the caller has delete child permission on the parent directory.
   339  		err = NtOpenFile(
   340  			&h,
   341  			DELETE,
   342  			objAttrs,
   343  			&IO_STATUS_BLOCK{},
   344  			FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
   345  			FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|options,
   346  		)
   347  		if err != nil {
   348  			return ntCreateFileError(err, 0)
   349  		}
   350  	}
   351  	defer syscall.CloseHandle(h)
   352  
   353  	if TestDeleteatFallback {
   354  		return deleteatFallback(h)
   355  	}
   356  
   357  	const FileDispositionInformationEx = 64
   358  
   359  	// First, attempt to delete the file using POSIX semantics
   360  	// (which permit a file to be deleted while it is still open).
   361  	// This matches the behavior of DeleteFileW.
   362  	//
   363  	// The following call uses features available on different Windows versions:
   364  	// - FILE_DISPOSITION_INFORMATION_EX: Windows 10, version 1607 (aka RS1)
   365  	// - FILE_DISPOSITION_POSIX_SEMANTICS: Windows 10, version 1607 (aka RS1)
   366  	// - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: Windows 10, version 1809 (aka RS5)
   367  	//
   368  	// Also, some file systems, like FAT32, don't support POSIX semantics.
   369  	err = NtSetInformationFile(
   370  		h,
   371  		&IO_STATUS_BLOCK{},
   372  		unsafe.Pointer(&FILE_DISPOSITION_INFORMATION_EX{
   373  			Flags: FILE_DISPOSITION_DELETE |
   374  				FILE_DISPOSITION_POSIX_SEMANTICS |
   375  				// This differs from DeleteFileW, but matches os.Remove's
   376  				// behavior on Unix platforms of permitting deletion of
   377  				// read-only files.
   378  				FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE,
   379  		}),
   380  		uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION_EX{})),
   381  		FileDispositionInformationEx,
   382  	)
   383  	switch err {
   384  	case nil:
   385  		return nil
   386  	case STATUS_INVALID_INFO_CLASS, // the operating system doesn't support FileDispositionInformationEx
   387  		STATUS_INVALID_PARAMETER, // the operating system doesn't support one of the flags
   388  		STATUS_NOT_SUPPORTED:     // the file system doesn't support FILE_DISPOSITION_INFORMATION_EX or one of the flags
   389  		return deleteatFallback(h)
   390  	default:
   391  		return err.(NTStatus).Errno()
   392  	}
   393  }
   394  
   395  // TestDeleteatFallback should only be used for testing purposes.
   396  // When set, [Deleteat] uses the fallback path unconditionally.
   397  var TestDeleteatFallback bool
   398  
   399  // deleteatFallback is a deleteat implementation that strives
   400  // for compatibility with older Windows versions and file systems
   401  // over performance.
   402  func deleteatFallback(h syscall.Handle) error {
   403  	var data syscall.ByHandleFileInformation
   404  	if err := syscall.GetFileInformationByHandle(h, &data); err == nil && data.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
   405  		// Remove read-only attribute. Reopen the file, as it was previously open without FILE_WRITE_ATTRIBUTES access
   406  		// in order to maximize compatibility in the happy path.
   407  		wh, err := ReOpenFile(h,
   408  			FILE_WRITE_ATTRIBUTES,
   409  			FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
   410  			syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS,
   411  		)
   412  		if err != nil {
   413  			return err
   414  		}
   415  		err = SetFileInformationByHandle(
   416  			wh,
   417  			FileBasicInfo,
   418  			unsafe.Pointer(&FILE_BASIC_INFO{
   419  				FileAttributes: data.FileAttributes &^ FILE_ATTRIBUTE_READONLY,
   420  			}),
   421  			uint32(unsafe.Sizeof(FILE_BASIC_INFO{})),
   422  		)
   423  		syscall.CloseHandle(wh)
   424  		if err != nil {
   425  			return err
   426  		}
   427  	}
   428  
   429  	return SetFileInformationByHandle(
   430  		h,
   431  		FileDispositionInfo,
   432  		unsafe.Pointer(&FILE_DISPOSITION_INFO{
   433  			DeleteFile: 1,
   434  		}),
   435  		uint32(unsafe.Sizeof(FILE_DISPOSITION_INFO{})),
   436  	)
   437  }
   438  
   439  func Renameat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, newpath string) error {
   440  	objAttrs := &OBJECT_ATTRIBUTES{}
   441  	if err := objAttrs.init(olddirfd, oldpath); err != nil {
   442  		return err
   443  	}
   444  	var h syscall.Handle
   445  	err := NtOpenFile(
   446  		&h,
   447  		SYNCHRONIZE|DELETE,
   448  		objAttrs,
   449  		&IO_STATUS_BLOCK{},
   450  		FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
   451  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT,
   452  	)
   453  	if err != nil {
   454  		return ntCreateFileError(err, 0)
   455  	}
   456  	defer syscall.CloseHandle(h)
   457  
   458  	renameInfoEx := FILE_RENAME_INFORMATION_EX{
   459  		Flags: FILE_RENAME_REPLACE_IF_EXISTS |
   460  			FILE_RENAME_POSIX_SEMANTICS,
   461  		RootDirectory: newdirfd,
   462  	}
   463  	p16, err := syscall.UTF16FromString(newpath)
   464  	if err != nil {
   465  		return err
   466  	}
   467  	if len(p16) > len(renameInfoEx.FileName) {
   468  		return syscall.EINVAL
   469  	}
   470  	copy(renameInfoEx.FileName[:], p16)
   471  	renameInfoEx.FileNameLength = uint32((len(p16) - 1) * 2)
   472  
   473  	const (
   474  		FileRenameInformation   = 10
   475  		FileRenameInformationEx = 65
   476  	)
   477  	err = NtSetInformationFile(
   478  		h,
   479  		&IO_STATUS_BLOCK{},
   480  		unsafe.Pointer(&renameInfoEx),
   481  		uint32(unsafe.Sizeof(FILE_RENAME_INFORMATION_EX{})),
   482  		FileRenameInformationEx,
   483  	)
   484  	if err == nil {
   485  		return nil
   486  	}
   487  
   488  	// If the prior rename failed, the filesystem might not support
   489  	// POSIX semantics (for example, FAT), or might not have implemented
   490  	// FILE_RENAME_INFORMATION_EX.
   491  	//
   492  	// Try again.
   493  	renameInfo := FILE_RENAME_INFORMATION{
   494  		ReplaceIfExists: 1,
   495  		RootDirectory:   newdirfd,
   496  	}
   497  	copy(renameInfo.FileName[:], p16)
   498  	renameInfo.FileNameLength = renameInfoEx.FileNameLength
   499  
   500  	err = NtSetInformationFile(
   501  		h,
   502  		&IO_STATUS_BLOCK{},
   503  		unsafe.Pointer(&renameInfo),
   504  		uint32(unsafe.Sizeof(FILE_RENAME_INFORMATION{})),
   505  		FileRenameInformation,
   506  	)
   507  	if st, ok := err.(NTStatus); ok {
   508  		return st.Errno()
   509  	}
   510  	return err
   511  }
   512  
   513  func Linkat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, newpath string) error {
   514  	objAttrs := &OBJECT_ATTRIBUTES{}
   515  	if err := objAttrs.init(olddirfd, oldpath); err != nil {
   516  		return err
   517  	}
   518  	var h syscall.Handle
   519  	err := NtOpenFile(
   520  		&h,
   521  		SYNCHRONIZE|FILE_WRITE_ATTRIBUTES,
   522  		objAttrs,
   523  		&IO_STATUS_BLOCK{},
   524  		FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
   525  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT,
   526  	)
   527  	if err != nil {
   528  		return ntCreateFileError(err, 0)
   529  	}
   530  	defer syscall.CloseHandle(h)
   531  
   532  	linkInfo := FILE_LINK_INFORMATION{
   533  		RootDirectory: newdirfd,
   534  	}
   535  	p16, err := syscall.UTF16FromString(newpath)
   536  	if err != nil {
   537  		return err
   538  	}
   539  	if len(p16) > len(linkInfo.FileName) {
   540  		return syscall.EINVAL
   541  	}
   542  	copy(linkInfo.FileName[:], p16)
   543  	linkInfo.FileNameLength = uint32((len(p16) - 1) * 2)
   544  
   545  	const (
   546  		FileLinkInformation = 11
   547  	)
   548  	err = NtSetInformationFile(
   549  		h,
   550  		&IO_STATUS_BLOCK{},
   551  		unsafe.Pointer(&linkInfo),
   552  		uint32(unsafe.Sizeof(FILE_LINK_INFORMATION{})),
   553  		FileLinkInformation,
   554  	)
   555  	if st, ok := err.(NTStatus); ok {
   556  		return st.Errno()
   557  	}
   558  	return err
   559  }
   560  
   561  // SymlinkatFlags configure Symlinkat.
   562  //
   563  // Symbolic links have two properties: They may be directory or file links,
   564  // and they may be absolute or relative.
   565  //
   566  // The Windows API defines flags describing these properties
   567  // (SYMBOLIC_LINK_FLAG_DIRECTORY and SYMLINK_FLAG_RELATIVE),
   568  // but the flags are passed to different system calls and
   569  // do not have distinct values, so we define our own enumeration
   570  // that permits expressing both.
   571  type SymlinkatFlags uint
   572  
   573  const (
   574  	SYMLINKAT_DIRECTORY = SymlinkatFlags(1 << iota)
   575  	SYMLINKAT_RELATIVE
   576  )
   577  
   578  func Symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error {
   579  	// Temporarily acquire symlink-creating privileges if possible.
   580  	// This is the behavior of CreateSymbolicLinkW.
   581  	//
   582  	// (When passed the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag,
   583  	// CreateSymbolicLinkW ignores errors in acquiring privileges, as we do here.)
   584  	return withPrivilege("SeCreateSymbolicLinkPrivilege", func() error {
   585  		return symlinkat(oldname, newdirfd, newname, flags)
   586  	})
   587  }
   588  
   589  func symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error {
   590  	oldnameu16, err := syscall.UTF16FromString(oldname)
   591  	if err != nil {
   592  		return err
   593  	}
   594  	oldnameu16 = oldnameu16[:len(oldnameu16)-1] // trim off terminal NUL
   595  
   596  	var options uint32
   597  	if flags&SYMLINKAT_DIRECTORY != 0 {
   598  		options |= FILE_DIRECTORY_FILE
   599  	} else {
   600  		options |= FILE_NON_DIRECTORY_FILE
   601  	}
   602  
   603  	objAttrs := &OBJECT_ATTRIBUTES{}
   604  	if err := objAttrs.init(newdirfd, newname); err != nil {
   605  		return err
   606  	}
   607  	var h syscall.Handle
   608  	err = NtCreateFile(
   609  		&h,
   610  		SYNCHRONIZE|FILE_WRITE_ATTRIBUTES|DELETE,
   611  		objAttrs,
   612  		&IO_STATUS_BLOCK{},
   613  		nil,
   614  		syscall.FILE_ATTRIBUTE_NORMAL,
   615  		0,
   616  		FILE_CREATE,
   617  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT|options,
   618  		nil,
   619  		0,
   620  	)
   621  	if err != nil {
   622  		return ntCreateFileError(err, 0)
   623  	}
   624  	defer syscall.CloseHandle(h)
   625  
   626  	// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer
   627  	type reparseDataBufferT struct {
   628  		_ structs.HostLayout
   629  
   630  		ReparseTag        uint32
   631  		ReparseDataLength uint16
   632  		Reserved          uint16
   633  
   634  		SubstituteNameOffset uint16
   635  		SubstituteNameLength uint16
   636  		PrintNameOffset      uint16
   637  		PrintNameLength      uint16
   638  		Flags                uint32
   639  	}
   640  
   641  	const (
   642  		headerSize = uint16(unsafe.Offsetof(reparseDataBufferT{}.SubstituteNameOffset))
   643  		bufferSize = uint16(unsafe.Sizeof(reparseDataBufferT{}))
   644  	)
   645  
   646  	// Data buffer containing a SymbolicLinkReparseBuffer followed by the link target.
   647  	rdbbuf := make([]byte, bufferSize+uint16(2*len(oldnameu16)))
   648  
   649  	rdb := (*reparseDataBufferT)(unsafe.Pointer(&rdbbuf[0]))
   650  	rdb.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK
   651  	rdb.ReparseDataLength = uint16(len(rdbbuf)) - uint16(headerSize)
   652  	rdb.SubstituteNameOffset = 0
   653  	rdb.SubstituteNameLength = uint16(2 * len(oldnameu16))
   654  	rdb.PrintNameOffset = 0
   655  	rdb.PrintNameLength = rdb.SubstituteNameLength
   656  	if flags&SYMLINKAT_RELATIVE != 0 {
   657  		rdb.Flags = SYMLINK_FLAG_RELATIVE
   658  	}
   659  
   660  	namebuf := rdbbuf[bufferSize:]
   661  	copy(namebuf, unsafe.String((*byte)(unsafe.Pointer(&oldnameu16[0])), 2*len(oldnameu16)))
   662  
   663  	var bytesReturned uint32
   664  	err = syscall.DeviceIoControl(
   665  		h,
   666  		FSCTL_SET_REPARSE_POINT,
   667  		&rdbbuf[0],
   668  		uint32(len(rdbbuf)),
   669  		nil,
   670  		0,
   671  		&bytesReturned,
   672  		nil)
   673  	if err != nil {
   674  		// Creating the symlink has failed, so try to remove the file.
   675  		const FileDispositionInformation = 13
   676  		NtSetInformationFile(
   677  			h,
   678  			&IO_STATUS_BLOCK{},
   679  			unsafe.Pointer(&FILE_DISPOSITION_INFORMATION{
   680  				DeleteFile: 1,
   681  			}),
   682  			uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION{})),
   683  			FileDispositionInformation,
   684  		)
   685  		return err
   686  	}
   687  
   688  	return nil
   689  }
   690  
   691  // withPrivilege temporariliy acquires the named privilege and runs f.
   692  // If the privilege cannot be acquired it runs f anyway,
   693  // which should fail with an appropriate error.
   694  func withPrivilege(privilege string, f func() error) error {
   695  	runtime.LockOSThread()
   696  	defer runtime.UnlockOSThread()
   697  
   698  	err := ImpersonateSelf(SecurityImpersonation)
   699  	if err != nil {
   700  		return f()
   701  	}
   702  	defer RevertToSelf()
   703  
   704  	curThread, err := GetCurrentThread()
   705  	if err != nil {
   706  		return f()
   707  	}
   708  	var token syscall.Token
   709  	err = OpenThreadToken(curThread, syscall.TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES, false, &token)
   710  	if err != nil {
   711  		return f()
   712  	}
   713  	defer syscall.CloseHandle(syscall.Handle(token))
   714  
   715  	privStr, err := syscall.UTF16PtrFromString(privilege)
   716  	if err != nil {
   717  		return f()
   718  	}
   719  	var tokenPriv TOKEN_PRIVILEGES
   720  	err = LookupPrivilegeValue(nil, privStr, &tokenPriv.Privileges[0].Luid)
   721  	if err != nil {
   722  		return f()
   723  	}
   724  
   725  	tokenPriv.PrivilegeCount = 1
   726  	tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
   727  	err = AdjustTokenPrivileges(token, false, &tokenPriv, 0, nil, nil)
   728  	if err != nil {
   729  		return f()
   730  	}
   731  
   732  	return f()
   733  }
   734  

View as plain text