Source file src/internal/syscall/windows/at_windows.go

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

View as plain text