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

View as plain text