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  	"syscall"
     9  	"unsafe"
    10  )
    11  
    12  // Openat flags not supported by syscall.Open.
    13  //
    14  // These are invented values.
    15  //
    16  // When adding a new flag here, add an unexported version to
    17  // the set of invented O_ values in syscall/types_windows.go
    18  // to avoid overlap.
    19  const (
    20  	O_DIRECTORY    = 0x100000   // target must be a directory
    21  	O_NOFOLLOW_ANY = 0x20000000 // disallow symlinks anywhere in the path
    22  	O_OPEN_REPARSE = 0x40000000 // FILE_OPEN_REPARSE_POINT, used by Lstat
    23  )
    24  
    25  func Openat(dirfd syscall.Handle, name string, flag int, perm uint32) (_ syscall.Handle, e1 error) {
    26  	if len(name) == 0 {
    27  		return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
    28  	}
    29  
    30  	var access, options uint32
    31  	switch flag & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
    32  	case syscall.O_RDONLY:
    33  		// FILE_GENERIC_READ includes FILE_LIST_DIRECTORY.
    34  		access = FILE_GENERIC_READ
    35  	case syscall.O_WRONLY:
    36  		access = FILE_GENERIC_WRITE
    37  		options |= FILE_NON_DIRECTORY_FILE
    38  	case syscall.O_RDWR:
    39  		access = FILE_GENERIC_READ | FILE_GENERIC_WRITE
    40  		options |= FILE_NON_DIRECTORY_FILE
    41  	default:
    42  		// Stat opens files without requesting read or write permissions,
    43  		// but we still need to request SYNCHRONIZE.
    44  		access = SYNCHRONIZE
    45  	}
    46  	if flag&syscall.O_CREAT != 0 {
    47  		access |= FILE_GENERIC_WRITE
    48  	}
    49  	if flag&syscall.O_APPEND != 0 {
    50  		access |= FILE_APPEND_DATA
    51  		// Remove FILE_WRITE_DATA access unless O_TRUNC is set,
    52  		// in which case we need it to truncate the file.
    53  		if flag&syscall.O_TRUNC == 0 {
    54  			access &^= FILE_WRITE_DATA
    55  		}
    56  	}
    57  	if flag&O_DIRECTORY != 0 {
    58  		options |= FILE_DIRECTORY_FILE
    59  		access |= FILE_LIST_DIRECTORY
    60  	}
    61  	if flag&syscall.O_SYNC != 0 {
    62  		options |= FILE_WRITE_THROUGH
    63  	}
    64  	// Allow File.Stat.
    65  	access |= STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | FILE_READ_EA
    66  
    67  	objAttrs := &OBJECT_ATTRIBUTES{}
    68  	if flag&O_NOFOLLOW_ANY != 0 {
    69  		objAttrs.Attributes |= OBJ_DONT_REPARSE
    70  	}
    71  	if flag&syscall.O_CLOEXEC == 0 {
    72  		objAttrs.Attributes |= OBJ_INHERIT
    73  	}
    74  	if err := objAttrs.init(dirfd, name); err != nil {
    75  		return syscall.InvalidHandle, err
    76  	}
    77  
    78  	if flag&O_OPEN_REPARSE != 0 {
    79  		options |= FILE_OPEN_REPARSE_POINT
    80  	}
    81  
    82  	// We don't use FILE_OVERWRITE/FILE_OVERWRITE_IF, because when opening
    83  	// a file with FILE_ATTRIBUTE_READONLY these will replace an existing
    84  	// file with a new, read-only one.
    85  	//
    86  	// Instead, we ftruncate the file after opening when O_TRUNC is set.
    87  	var disposition uint32
    88  	switch {
    89  	case flag&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
    90  		disposition = FILE_CREATE
    91  		options |= FILE_OPEN_REPARSE_POINT // don't follow symlinks
    92  	case flag&syscall.O_CREAT == syscall.O_CREAT:
    93  		disposition = FILE_OPEN_IF
    94  	default:
    95  		disposition = FILE_OPEN
    96  	}
    97  
    98  	fileAttrs := uint32(FILE_ATTRIBUTE_NORMAL)
    99  	if perm&syscall.S_IWRITE == 0 {
   100  		fileAttrs = FILE_ATTRIBUTE_READONLY
   101  	}
   102  
   103  	var h syscall.Handle
   104  	err := NtCreateFile(
   105  		&h,
   106  		SYNCHRONIZE|access,
   107  		objAttrs,
   108  		&IO_STATUS_BLOCK{},
   109  		nil,
   110  		fileAttrs,
   111  		FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
   112  		disposition,
   113  		FILE_SYNCHRONOUS_IO_NONALERT|FILE_OPEN_FOR_BACKUP_INTENT|options,
   114  		0,
   115  		0,
   116  	)
   117  	if err != nil {
   118  		return h, ntCreateFileError(err, flag)
   119  	}
   120  
   121  	if flag&syscall.O_TRUNC != 0 {
   122  		err = syscall.Ftruncate(h, 0)
   123  		if err != nil {
   124  			syscall.CloseHandle(h)
   125  			return syscall.InvalidHandle, err
   126  		}
   127  	}
   128  
   129  	return h, nil
   130  }
   131  
   132  // ntCreateFileError maps error returns from NTCreateFile to user-visible errors.
   133  func ntCreateFileError(err error, flag int) error {
   134  	s, ok := err.(NTStatus)
   135  	if !ok {
   136  		// Shouldn't really be possible, NtCreateFile always returns NTStatus.
   137  		return err
   138  	}
   139  	switch s {
   140  	case STATUS_REPARSE_POINT_ENCOUNTERED:
   141  		return syscall.ELOOP
   142  	case STATUS_NOT_A_DIRECTORY:
   143  		// ENOTDIR is the errno returned by open when O_DIRECTORY is specified
   144  		// and the target is not a directory.
   145  		//
   146  		// NtCreateFile can return STATUS_NOT_A_DIRECTORY under other circumstances,
   147  		// such as when opening "file/" where "file" is not a directory.
   148  		// (This might be Windows version dependent.)
   149  		//
   150  		// Only map STATUS_NOT_A_DIRECTORY to ENOTDIR when O_DIRECTORY is specified.
   151  		if flag&O_DIRECTORY != 0 {
   152  			return syscall.ENOTDIR
   153  		}
   154  	case STATUS_FILE_IS_A_DIRECTORY:
   155  		return syscall.EISDIR
   156  	}
   157  	return s.Errno()
   158  }
   159  
   160  func Mkdirat(dirfd syscall.Handle, name string, mode uint32) error {
   161  	objAttrs := &OBJECT_ATTRIBUTES{}
   162  	if err := objAttrs.init(dirfd, name); err != nil {
   163  		return err
   164  	}
   165  	var h syscall.Handle
   166  	err := NtCreateFile(
   167  		&h,
   168  		FILE_GENERIC_READ,
   169  		objAttrs,
   170  		&IO_STATUS_BLOCK{},
   171  		nil,
   172  		syscall.FILE_ATTRIBUTE_NORMAL,
   173  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   174  		FILE_CREATE,
   175  		FILE_DIRECTORY_FILE,
   176  		0,
   177  		0,
   178  	)
   179  	if err != nil {
   180  		return ntCreateFileError(err, 0)
   181  	}
   182  	syscall.CloseHandle(h)
   183  	return nil
   184  }
   185  
   186  func Deleteat(dirfd syscall.Handle, name string) error {
   187  	objAttrs := &OBJECT_ATTRIBUTES{}
   188  	if err := objAttrs.init(dirfd, name); err != nil {
   189  		return err
   190  	}
   191  	var h syscall.Handle
   192  	err := NtOpenFile(
   193  		&h,
   194  		DELETE,
   195  		objAttrs,
   196  		&IO_STATUS_BLOCK{},
   197  		FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
   198  		FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT,
   199  	)
   200  	if err != nil {
   201  		return ntCreateFileError(err, 0)
   202  	}
   203  	defer syscall.CloseHandle(h)
   204  
   205  	const (
   206  		FileDispositionInformation   = 13
   207  		FileDispositionInformationEx = 64
   208  	)
   209  
   210  	// First, attempt to delete the file using POSIX semantics
   211  	// (which permit a file to be deleted while it is still open).
   212  	// This matches the behavior of DeleteFileW.
   213  	err = NtSetInformationFile(
   214  		h,
   215  		&IO_STATUS_BLOCK{},
   216  		uintptr(unsafe.Pointer(&FILE_DISPOSITION_INFORMATION_EX{
   217  			Flags: FILE_DISPOSITION_DELETE |
   218  				FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK |
   219  				FILE_DISPOSITION_POSIX_SEMANTICS |
   220  				// This differs from DeleteFileW, but matches os.Remove's
   221  				// behavior on Unix platforms of permitting deletion of
   222  				// read-only files.
   223  				FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE,
   224  		})),
   225  		uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION_EX{})),
   226  		FileDispositionInformationEx,
   227  	)
   228  	switch err {
   229  	case nil:
   230  		return nil
   231  	case STATUS_CANNOT_DELETE, STATUS_DIRECTORY_NOT_EMPTY:
   232  		return err.(NTStatus).Errno()
   233  	}
   234  
   235  	// If the prior deletion failed, the filesystem either doesn't support
   236  	// POSIX semantics (for example, FAT), or hasn't implemented
   237  	// FILE_DISPOSITION_INFORMATION_EX.
   238  	//
   239  	// Try again.
   240  	err = NtSetInformationFile(
   241  		h,
   242  		&IO_STATUS_BLOCK{},
   243  		uintptr(unsafe.Pointer(&FILE_DISPOSITION_INFORMATION{
   244  			DeleteFile: true,
   245  		})),
   246  		uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION{})),
   247  		FileDispositionInformation,
   248  	)
   249  	if st, ok := err.(NTStatus); ok {
   250  		return st.Errno()
   251  	}
   252  	return err
   253  }
   254  

View as plain text