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 }