github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/lib/file/file_windows.go (about)

     1  //go:build windows
     2  
     3  package file
     4  
     5  import (
     6  	"errors"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"syscall"
    11  )
    12  
    13  // OpenFile is the generalized open call; most users will use Open or Create
    14  // instead. It opens the named file with specified flag (O_RDONLY etc.) and
    15  // perm (before umask), if applicable. If successful, methods on the returned
    16  // File can be used for I/O. If there is an error, it will be of type
    17  // *PathError.
    18  //
    19  // Under both Unix and Windows this will allow open files to be
    20  // renamed and or deleted.
    21  func OpenFile(path string, mode int, perm os.FileMode) (*os.File, error) {
    22  	// This code copied from syscall_windows.go in the go source and then
    23  	// modified to support renaming and deleting open files by adding
    24  	// FILE_SHARE_DELETE.
    25  	//
    26  	// https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea#file_share_delete
    27  	if len(path) == 0 {
    28  		return nil, syscall.ERROR_FILE_NOT_FOUND
    29  	}
    30  	pathp, err := syscall.UTF16PtrFromString(path)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  	var access uint32
    35  	switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
    36  	case syscall.O_RDONLY:
    37  		access = syscall.GENERIC_READ
    38  	case syscall.O_WRONLY:
    39  		access = syscall.GENERIC_WRITE
    40  	case syscall.O_RDWR:
    41  		access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
    42  	}
    43  	if mode&syscall.O_CREAT != 0 {
    44  		access |= syscall.GENERIC_WRITE
    45  	}
    46  	if mode&syscall.O_APPEND != 0 {
    47  		access &^= syscall.GENERIC_WRITE
    48  		access |= syscall.FILE_APPEND_DATA
    49  	}
    50  	sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)
    51  	var createmode uint32
    52  	switch {
    53  	case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
    54  		createmode = syscall.CREATE_NEW
    55  	case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
    56  		createmode = syscall.CREATE_ALWAYS
    57  	case mode&syscall.O_CREAT == syscall.O_CREAT:
    58  		createmode = syscall.OPEN_ALWAYS
    59  	case mode&syscall.O_TRUNC == syscall.O_TRUNC:
    60  		createmode = syscall.TRUNCATE_EXISTING
    61  	default:
    62  		createmode = syscall.OPEN_EXISTING
    63  	}
    64  	h, e := syscall.CreateFile(pathp, access, sharemode, nil, createmode, syscall.FILE_ATTRIBUTE_NORMAL, 0)
    65  	if e != nil {
    66  		return nil, e
    67  	}
    68  	return os.NewFile(uintptr(h), path), nil
    69  }
    70  
    71  // IsReserved checks if path contains a reserved name
    72  func IsReserved(path string) error {
    73  	if path == "" {
    74  		return errors.New("path is empty")
    75  	}
    76  	base := filepath.Base(path)
    77  	// If the path is empty or reduces to ".", Base returns ".".
    78  	if base == "." {
    79  		return errors.New("path is '.'")
    80  	}
    81  	// If the path consists entirely of separators, Base returns a single separator.
    82  	if base == string(filepath.Separator) {
    83  		return errors.New("path consists entirely of separators")
    84  	}
    85  	// Do not end a file or directory name with a space or a period. Although the underlying
    86  	// file system may support such names, the Windows shell and user interface does not.
    87  	// (https://docs.microsoft.com/en-gb/windows/win32/fileio/naming-a-file)
    88  	suffix := base[len(base)-1]
    89  	switch suffix {
    90  	case ' ':
    91  		return errors.New("base file name ends with a space")
    92  	case '.':
    93  		return errors.New("base file name ends with a period")
    94  	}
    95  	// Do not use names of legacy (DOS) devices, not even as basename without extension,
    96  	// as this will refer to the actual device.
    97  	// (https://docs.microsoft.com/en-gb/windows/win32/fileio/naming-a-file)
    98  	if reserved, _ := regexp.MatchString(`^(?i:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)`, base); reserved {
    99  		return errors.New("base file name is reserved windows device name (CON, PRN, AUX, NUL, COM[1-9], LPT[1-9])")
   100  	}
   101  	return nil
   102  }