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  		FILE_READ_ATTRIBUTES|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|options,
   212  	)
   213  	if err != nil {
   214  		if ntStatus, ok := err.(NTStatus); !ok || ntStatus != STATUS_ACCESS_DENIED {
   215  			return ntCreateFileError(err, 0)
   216  		}
   217  
   218  		// Access denied, try opening with DELETE only.
   219  		// This may succeed if the file has restrictive permissions
   220  		// but the caller has delete child permission on the parent directory.
   221  		err = NtOpenFile(
   222  			&h,
   223  			DELETE,
   224  			objAttrs,
   225  			&IO_STATUS_BLOCK{},
   226  			FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
   227  			FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|options,
   228  		)
   229  		if err != nil {
   230  			return ntCreateFileError(err, 0)
   231  		}
   232  	}
   233  	defer syscall.CloseHandle(h)
   234  
   235  	if TestDeleteatFallback {
   236  		return deleteatFallback(h)
   237  	}
   238  
   239  	const FileDispositionInformationEx = 64
   240  
   241  	// First, attempt to delete the file using POSIX semantics
   242  	// (which permit a file to be deleted while it is still open).
   243  	// This matches the behavior of DeleteFileW.
   244  	//
   245  	// The following call uses features available on different Windows versions:
   246  	// - FILE_DISPOSITION_INFORMATION_EX: Windows 10, version 1607 (aka RS1)
   247  	// - FILE_DISPOSITION_POSIX_SEMANTICS: Windows 10, version 1607 (aka RS1)
   248  	// - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: Windows 10, version 1809 (aka RS5)
   249  	//
   250  	// Also, some file systems, like FAT32, don't support POSIX semantics.
   251  	err = NtSetInformationFile(
   252  		h,
   253  		&IO_STATUS_BLOCK{},
   254  		unsafe.Pointer(&FILE_DISPOSITION_INFORMATION_EX{
   255  			Flags: FILE_DISPOSITION_DELETE |
   256  				FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK |
   257  				FILE_DISPOSITION_POSIX_SEMANTICS |
   258  				// This differs from DeleteFileW, but matches os.Remove's
   259  				// behavior on Unix platforms of permitting deletion of
   260  				// read-only files.
   261  				FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE,
   262  		}),
   263  		uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION_EX{})),
   264  		FileDispositionInformationEx,
   265  	)
   266  	switch err {
   267  	case nil:
   268  		return nil
   269  	case STATUS_INVALID_INFO_CLASS, // the operating system doesn't support FileDispositionInformationEx
   270  		STATUS_INVALID_PARAMETER, // the operating system doesn't support one of the flags
   271  		STATUS_NOT_SUPPORTED:     // the file system doesn't support FILE_DISPOSITION_INFORMATION_EX or one of the flags
   272  		return deleteatFallback(h)
   273  	default:
   274  		return err.(NTStatus).Errno()
   275  	}
   276  }
   277  
   278  // TestDeleteatFallback should only be used for testing purposes.
   279  // When set, [Deleteat] uses the fallback path unconditionally.
   280  var TestDeleteatFallback bool
   281  
   282  // deleteatFallback is a deleteat implementation that strives
   283  // for compatibility with older Windows versions and file systems
   284  // over performance.
   285  func deleteatFallback(h syscall.Handle) error {
   286  	var data syscall.ByHandleFileInformation
   287  	if err := syscall.GetFileInformationByHandle(h, &data); err == nil && data.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
   288  		// Remove read-only attribute. Reopen the file, as it was previously open without FILE_WRITE_ATTRIBUTES access
   289  		// in order to maximize compatibility in the happy path.
   290  		wh, err := ReOpenFile(h,
   291  			FILE_WRITE_ATTRIBUTES,
   292  			FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
   293  			syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS,
   294  		)
   295  		if err != nil {
   296  			return err
   297  		}
   298  		err = SetFileInformationByHandle(
   299  			wh,
   300  			FileBasicInfo,
   301  			unsafe.Pointer(&FILE_BASIC_INFO{
   302  				FileAttributes: data.FileAttributes &^ FILE_ATTRIBUTE_READONLY,
   303  			}),
   304  			uint32(unsafe.Sizeof(FILE_BASIC_INFO{})),
   305  		)
   306  		syscall.CloseHandle(wh)
   307  		if err != nil {
   308  			return err
   309  		}
   310  	}
   311  
   312  	return SetFileInformationByHandle(
   313  		h,
   314  		FileDispositionInfo,
   315  		unsafe.Pointer(&FILE_DISPOSITION_INFO{
   316  			DeleteFile: true,
   317  		}),
   318  		uint32(unsafe.Sizeof(FILE_DISPOSITION_INFO{})),
   319  	)
   320  }
   321  
   322  func Renameat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, newpath string) error {
   323  	objAttrs := &OBJECT_ATTRIBUTES{}
   324  	if err := objAttrs.init(olddirfd, oldpath); err != nil {
   325  		return err
   326  	}
   327  	var h syscall.Handle
   328  	err := NtOpenFile(
   329  		&h,
   330  		SYNCHRONIZE|DELETE,
   331  		objAttrs,
   332  		&IO_STATUS_BLOCK{},
   333  		FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
   334  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT,
   335  	)
   336  	if err != nil {
   337  		return ntCreateFileError(err, 0)
   338  	}
   339  	defer syscall.CloseHandle(h)
   340  
   341  	renameInfoEx := FILE_RENAME_INFORMATION_EX{
   342  		Flags: FILE_RENAME_REPLACE_IF_EXISTS |
   343  			FILE_RENAME_POSIX_SEMANTICS,
   344  		RootDirectory: newdirfd,
   345  	}
   346  	p16, err := syscall.UTF16FromString(newpath)
   347  	if err != nil {
   348  		return err
   349  	}
   350  	if len(p16) > len(renameInfoEx.FileName) {
   351  		return syscall.EINVAL
   352  	}
   353  	copy(renameInfoEx.FileName[:], p16)
   354  	renameInfoEx.FileNameLength = uint32((len(p16) - 1) * 2)
   355  
   356  	const (
   357  		FileRenameInformation   = 10
   358  		FileRenameInformationEx = 65
   359  	)
   360  	err = NtSetInformationFile(
   361  		h,
   362  		&IO_STATUS_BLOCK{},
   363  		unsafe.Pointer(&renameInfoEx),
   364  		uint32(unsafe.Sizeof(FILE_RENAME_INFORMATION_EX{})),
   365  		FileRenameInformationEx,
   366  	)
   367  	if err == nil {
   368  		return nil
   369  	}
   370  
   371  	// If the prior rename failed, the filesystem might not support
   372  	// POSIX semantics (for example, FAT), or might not have implemented
   373  	// FILE_RENAME_INFORMATION_EX.
   374  	//
   375  	// Try again.
   376  	renameInfo := FILE_RENAME_INFORMATION{
   377  		ReplaceIfExists: true,
   378  		RootDirectory:   newdirfd,
   379  	}
   380  	copy(renameInfo.FileName[:], p16)
   381  	renameInfo.FileNameLength = renameInfoEx.FileNameLength
   382  
   383  	err = NtSetInformationFile(
   384  		h,
   385  		&IO_STATUS_BLOCK{},
   386  		unsafe.Pointer(&renameInfo),
   387  		uint32(unsafe.Sizeof(FILE_RENAME_INFORMATION{})),
   388  		FileRenameInformation,
   389  	)
   390  	if st, ok := err.(NTStatus); ok {
   391  		return st.Errno()
   392  	}
   393  	return err
   394  }
   395  
   396  func Linkat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, newpath string) error {
   397  	objAttrs := &OBJECT_ATTRIBUTES{}
   398  	if err := objAttrs.init(olddirfd, oldpath); err != nil {
   399  		return err
   400  	}
   401  	var h syscall.Handle
   402  	err := NtOpenFile(
   403  		&h,
   404  		SYNCHRONIZE|FILE_WRITE_ATTRIBUTES,
   405  		objAttrs,
   406  		&IO_STATUS_BLOCK{},
   407  		FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
   408  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT,
   409  	)
   410  	if err != nil {
   411  		return ntCreateFileError(err, 0)
   412  	}
   413  	defer syscall.CloseHandle(h)
   414  
   415  	linkInfo := FILE_LINK_INFORMATION{
   416  		RootDirectory: newdirfd,
   417  	}
   418  	p16, err := syscall.UTF16FromString(newpath)
   419  	if err != nil {
   420  		return err
   421  	}
   422  	if len(p16) > len(linkInfo.FileName) {
   423  		return syscall.EINVAL
   424  	}
   425  	copy(linkInfo.FileName[:], p16)
   426  	linkInfo.FileNameLength = uint32((len(p16) - 1) * 2)
   427  
   428  	const (
   429  		FileLinkInformation = 11
   430  	)
   431  	err = NtSetInformationFile(
   432  		h,
   433  		&IO_STATUS_BLOCK{},
   434  		unsafe.Pointer(&linkInfo),
   435  		uint32(unsafe.Sizeof(FILE_LINK_INFORMATION{})),
   436  		FileLinkInformation,
   437  	)
   438  	if st, ok := err.(NTStatus); ok {
   439  		return st.Errno()
   440  	}
   441  	return err
   442  }
   443  
   444  // SymlinkatFlags configure Symlinkat.
   445  //
   446  // Symbolic links have two properties: They may be directory or file links,
   447  // and they may be absolute or relative.
   448  //
   449  // The Windows API defines flags describing these properties
   450  // (SYMBOLIC_LINK_FLAG_DIRECTORY and SYMLINK_FLAG_RELATIVE),
   451  // but the flags are passed to different system calls and
   452  // do not have distinct values, so we define our own enumeration
   453  // that permits expressing both.
   454  type SymlinkatFlags uint
   455  
   456  const (
   457  	SYMLINKAT_DIRECTORY = SymlinkatFlags(1 << iota)
   458  	SYMLINKAT_RELATIVE
   459  )
   460  
   461  func Symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error {
   462  	// Temporarily acquire symlink-creating privileges if possible.
   463  	// This is the behavior of CreateSymbolicLinkW.
   464  	//
   465  	// (When passed the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag,
   466  	// CreateSymbolicLinkW ignores errors in acquiring privileges, as we do here.)
   467  	return withPrivilege("SeCreateSymbolicLinkPrivilege", func() error {
   468  		return symlinkat(oldname, newdirfd, newname, flags)
   469  	})
   470  }
   471  
   472  func symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error {
   473  	oldnameu16, err := syscall.UTF16FromString(oldname)
   474  	if err != nil {
   475  		return err
   476  	}
   477  	oldnameu16 = oldnameu16[:len(oldnameu16)-1] // trim off terminal NUL
   478  
   479  	var options uint32
   480  	if flags&SYMLINKAT_DIRECTORY != 0 {
   481  		options |= FILE_DIRECTORY_FILE
   482  	} else {
   483  		options |= FILE_NON_DIRECTORY_FILE
   484  	}
   485  
   486  	objAttrs := &OBJECT_ATTRIBUTES{}
   487  	if err := objAttrs.init(newdirfd, newname); err != nil {
   488  		return err
   489  	}
   490  	var h syscall.Handle
   491  	err = NtCreateFile(
   492  		&h,
   493  		SYNCHRONIZE|FILE_WRITE_ATTRIBUTES|DELETE,
   494  		objAttrs,
   495  		&IO_STATUS_BLOCK{},
   496  		nil,
   497  		syscall.FILE_ATTRIBUTE_NORMAL,
   498  		0,
   499  		FILE_CREATE,
   500  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT|options,
   501  		nil,
   502  		0,
   503  	)
   504  	if err != nil {
   505  		return ntCreateFileError(err, 0)
   506  	}
   507  	defer syscall.CloseHandle(h)
   508  
   509  	// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer
   510  	type reparseDataBufferT struct {
   511  		_ structs.HostLayout
   512  
   513  		ReparseTag        uint32
   514  		ReparseDataLength uint16
   515  		Reserved          uint16
   516  
   517  		SubstituteNameOffset uint16
   518  		SubstituteNameLength uint16
   519  		PrintNameOffset      uint16
   520  		PrintNameLength      uint16
   521  		Flags                uint32
   522  	}
   523  
   524  	const (
   525  		headerSize = uint16(unsafe.Offsetof(reparseDataBufferT{}.SubstituteNameOffset))
   526  		bufferSize = uint16(unsafe.Sizeof(reparseDataBufferT{}))
   527  	)
   528  
   529  	// Data buffer containing a SymbolicLinkReparseBuffer followed by the link target.
   530  	rdbbuf := make([]byte, bufferSize+uint16(2*len(oldnameu16)))
   531  
   532  	rdb := (*reparseDataBufferT)(unsafe.Pointer(&rdbbuf[0]))
   533  	rdb.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK
   534  	rdb.ReparseDataLength = uint16(len(rdbbuf)) - uint16(headerSize)
   535  	rdb.SubstituteNameOffset = 0
   536  	rdb.SubstituteNameLength = uint16(2 * len(oldnameu16))
   537  	rdb.PrintNameOffset = 0
   538  	rdb.PrintNameLength = rdb.SubstituteNameLength
   539  	if flags&SYMLINKAT_RELATIVE != 0 {
   540  		rdb.Flags = SYMLINK_FLAG_RELATIVE
   541  	}
   542  
   543  	namebuf := rdbbuf[bufferSize:]
   544  	copy(namebuf, unsafe.String((*byte)(unsafe.Pointer(&oldnameu16[0])), 2*len(oldnameu16)))
   545  
   546  	err = syscall.DeviceIoControl(
   547  		h,
   548  		FSCTL_SET_REPARSE_POINT,
   549  		&rdbbuf[0],
   550  		uint32(len(rdbbuf)),
   551  		nil,
   552  		0,
   553  		nil,
   554  		nil)
   555  	if err != nil {
   556  		// Creating the symlink has failed, so try to remove the file.
   557  		const FileDispositionInformation = 13
   558  		NtSetInformationFile(
   559  			h,
   560  			&IO_STATUS_BLOCK{},
   561  			unsafe.Pointer(&FILE_DISPOSITION_INFORMATION{
   562  				DeleteFile: true,
   563  			}),
   564  			uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION{})),
   565  			FileDispositionInformation,
   566  		)
   567  		return err
   568  	}
   569  
   570  	return nil
   571  }
   572  
   573  // withPrivilege temporariliy acquires the named privilege and runs f.
   574  // If the privilege cannot be acquired it runs f anyway,
   575  // which should fail with an appropriate error.
   576  func withPrivilege(privilege string, f func() error) error {
   577  	runtime.LockOSThread()
   578  	defer runtime.UnlockOSThread()
   579  
   580  	err := ImpersonateSelf(SecurityImpersonation)
   581  	if err != nil {
   582  		return f()
   583  	}
   584  	defer RevertToSelf()
   585  
   586  	curThread, err := GetCurrentThread()
   587  	if err != nil {
   588  		return f()
   589  	}
   590  	var token syscall.Token
   591  	err = OpenThreadToken(curThread, syscall.TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES, false, &token)
   592  	if err != nil {
   593  		return f()
   594  	}
   595  	defer syscall.CloseHandle(syscall.Handle(token))
   596  
   597  	privStr, err := syscall.UTF16PtrFromString(privilege)
   598  	if err != nil {
   599  		return f()
   600  	}
   601  	var tokenPriv TOKEN_PRIVILEGES
   602  	err = LookupPrivilegeValue(nil, privStr, &tokenPriv.Privileges[0].Luid)
   603  	if err != nil {
   604  		return f()
   605  	}
   606  
   607  	tokenPriv.PrivilegeCount = 1
   608  	tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
   609  	err = AdjustTokenPrivileges(token, false, &tokenPriv, 0, nil, nil)
   610  	if err != nil {
   611  		return f()
   612  	}
   613  
   614  	return f()
   615  }
   616  

View as plain text