at_windows.mx raw

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