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  )
    10  
    11  // Openat flags not supported by syscall.Open.
    12  //
    13  // These are invented values.
    14  //
    15  // When adding a new flag here, add an unexported version to
    16  // the set of invented O_ values in syscall/types_windows.go
    17  // to avoid overlap.
    18  const (
    19  	O_DIRECTORY    = 0x100000   // target must be a directory
    20  	O_NOFOLLOW_ANY = 0x20000000 // disallow symlinks anywhere in the path
    21  )
    22  
    23  func Openat(dirfd syscall.Handle, name string, flag int, perm uint32) (_ syscall.Handle, e1 error) {
    24  	if len(name) == 0 {
    25  		return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
    26  	}
    27  
    28  	var access, options uint32
    29  	switch flag & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
    30  	case syscall.O_RDONLY:
    31  		// FILE_GENERIC_READ includes FILE_LIST_DIRECTORY.
    32  		access = FILE_GENERIC_READ
    33  	case syscall.O_WRONLY:
    34  		access = FILE_GENERIC_WRITE
    35  		options |= FILE_NON_DIRECTORY_FILE
    36  	case syscall.O_RDWR:
    37  		access = FILE_GENERIC_READ | FILE_GENERIC_WRITE
    38  		options |= FILE_NON_DIRECTORY_FILE
    39  	}
    40  	if flag&syscall.O_CREAT != 0 {
    41  		access |= FILE_GENERIC_WRITE
    42  	}
    43  	if flag&syscall.O_APPEND != 0 {
    44  		access |= FILE_APPEND_DATA
    45  		// Remove GENERIC_WRITE access unless O_TRUNC is set,
    46  		// in which case we need it to truncate the file.
    47  		if flag&syscall.O_TRUNC == 0 {
    48  			access &^= FILE_WRITE_DATA
    49  		}
    50  	}
    51  	if flag&O_DIRECTORY != 0 {
    52  		options |= FILE_DIRECTORY_FILE
    53  		access |= FILE_LIST_DIRECTORY
    54  	}
    55  	if flag&syscall.O_SYNC != 0 {
    56  		options |= FILE_WRITE_THROUGH
    57  	}
    58  	// Allow File.Stat.
    59  	access |= STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | FILE_READ_EA
    60  
    61  	objAttrs := &OBJECT_ATTRIBUTES{}
    62  	if flag&O_NOFOLLOW_ANY != 0 {
    63  		objAttrs.Attributes |= OBJ_DONT_REPARSE
    64  	}
    65  	if flag&syscall.O_CLOEXEC == 0 {
    66  		objAttrs.Attributes |= OBJ_INHERIT
    67  	}
    68  	if err := objAttrs.init(dirfd, name); err != nil {
    69  		return syscall.InvalidHandle, err
    70  	}
    71  
    72  	// We don't use FILE_OVERWRITE/FILE_OVERWRITE_IF, because when opening
    73  	// a file with FILE_ATTRIBUTE_READONLY these will replace an existing
    74  	// file with a new, read-only one.
    75  	//
    76  	// Instead, we ftruncate the file after opening when O_TRUNC is set.
    77  	var disposition uint32
    78  	switch {
    79  	case flag&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
    80  		disposition = FILE_CREATE
    81  	case flag&syscall.O_CREAT == syscall.O_CREAT:
    82  		disposition = FILE_OPEN_IF
    83  	default:
    84  		disposition = FILE_OPEN
    85  	}
    86  
    87  	fileAttrs := uint32(FILE_ATTRIBUTE_NORMAL)
    88  	if perm&syscall.S_IWRITE == 0 {
    89  		fileAttrs = FILE_ATTRIBUTE_READONLY
    90  	}
    91  
    92  	var h syscall.Handle
    93  	err := NtCreateFile(
    94  		&h,
    95  		SYNCHRONIZE|access,
    96  		objAttrs,
    97  		&IO_STATUS_BLOCK{},
    98  		nil,
    99  		fileAttrs,
   100  		FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
   101  		disposition,
   102  		FILE_SYNCHRONOUS_IO_NONALERT|options,
   103  		0,
   104  		0,
   105  	)
   106  	if err != nil {
   107  		return h, ntCreateFileError(err, flag)
   108  	}
   109  
   110  	if flag&syscall.O_TRUNC != 0 {
   111  		err = syscall.Ftruncate(h, 0)
   112  		if err != nil {
   113  			syscall.CloseHandle(h)
   114  			return syscall.InvalidHandle, err
   115  		}
   116  	}
   117  
   118  	return h, nil
   119  }
   120  
   121  // ntCreateFileError maps error returns from NTCreateFile to user-visible errors.
   122  func ntCreateFileError(err error, flag int) error {
   123  	s, ok := err.(NTStatus)
   124  	if !ok {
   125  		// Shouldn't really be possible, NtCreateFile always returns NTStatus.
   126  		return err
   127  	}
   128  	switch s {
   129  	case STATUS_REPARSE_POINT_ENCOUNTERED:
   130  		return syscall.ELOOP
   131  	case STATUS_NOT_A_DIRECTORY:
   132  		// ENOTDIR is the errno returned by open when O_DIRECTORY is specified
   133  		// and the target is not a directory.
   134  		//
   135  		// NtCreateFile can return STATUS_NOT_A_DIRECTORY under other circumstances,
   136  		// such as when opening "file/" where "file" is not a directory.
   137  		// (This might be Windows version dependent.)
   138  		//
   139  		// Only map STATUS_NOT_A_DIRECTORY to ENOTDIR when O_DIRECTORY is specified.
   140  		if flag&O_DIRECTORY != 0 {
   141  			return syscall.ENOTDIR
   142  		}
   143  	case STATUS_FILE_IS_A_DIRECTORY:
   144  		return syscall.EISDIR
   145  	}
   146  	return s.Errno()
   147  }
   148  
   149  func Mkdirat(dirfd syscall.Handle, name string, mode uint32) error {
   150  	objAttrs := &OBJECT_ATTRIBUTES{}
   151  	if err := objAttrs.init(dirfd, name); err != nil {
   152  		return err
   153  	}
   154  	var h syscall.Handle
   155  	err := NtCreateFile(
   156  		&h,
   157  		FILE_GENERIC_READ,
   158  		objAttrs,
   159  		&IO_STATUS_BLOCK{},
   160  		nil,
   161  		syscall.FILE_ATTRIBUTE_NORMAL,
   162  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   163  		FILE_CREATE,
   164  		FILE_DIRECTORY_FILE,
   165  		0,
   166  		0,
   167  	)
   168  	if err != nil {
   169  		return ntCreateFileError(err, 0)
   170  	}
   171  	syscall.CloseHandle(h)
   172  	return nil
   173  }
   174  

View as plain text