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

View as plain text