2024-05-27 15:46:15 +00:00
|
|
|
package osutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/fs"
|
|
|
|
"os"
|
|
|
|
. "syscall"
|
|
|
|
"unsafe"
|
|
|
|
)
|
|
|
|
|
|
|
|
// OpenFile behaves the same as [os.OpenFile],
|
|
|
|
// except on Windows it sets [syscall.FILE_SHARE_DELETE].
|
|
|
|
//
|
|
|
|
// See: https://go.dev/issue/32088#issuecomment-502850674
|
|
|
|
func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
|
|
|
|
if name == "" {
|
|
|
|
return nil, &os.PathError{Op: "open", Path: name, Err: ENOENT}
|
|
|
|
}
|
2024-10-08 09:15:09 +00:00
|
|
|
r, e := syscallOpen(name, flag|O_CLOEXEC, uint32(perm.Perm()))
|
2024-05-27 15:46:15 +00:00
|
|
|
if e != nil {
|
|
|
|
return nil, &os.PathError{Op: "open", Path: name, Err: e}
|
|
|
|
}
|
|
|
|
return os.NewFile(uintptr(r), name), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// syscallOpen is a copy of [syscall.Open]
|
|
|
|
// that uses [syscall.FILE_SHARE_DELETE].
|
|
|
|
//
|
|
|
|
// https://go.dev/src/syscall/syscall_windows.go
|
|
|
|
func syscallOpen(path string, mode int, perm uint32) (fd Handle, err error) {
|
|
|
|
if len(path) == 0 {
|
|
|
|
return InvalidHandle, ERROR_FILE_NOT_FOUND
|
|
|
|
}
|
|
|
|
pathp, err := UTF16PtrFromString(path)
|
|
|
|
if err != nil {
|
|
|
|
return InvalidHandle, err
|
|
|
|
}
|
|
|
|
var access uint32
|
|
|
|
switch mode & (O_RDONLY | O_WRONLY | O_RDWR) {
|
|
|
|
case O_RDONLY:
|
|
|
|
access = GENERIC_READ
|
|
|
|
case O_WRONLY:
|
|
|
|
access = GENERIC_WRITE
|
|
|
|
case O_RDWR:
|
|
|
|
access = GENERIC_READ | GENERIC_WRITE
|
|
|
|
}
|
|
|
|
if mode&O_CREAT != 0 {
|
|
|
|
access |= GENERIC_WRITE
|
|
|
|
}
|
|
|
|
if mode&O_APPEND != 0 {
|
|
|
|
access &^= GENERIC_WRITE
|
|
|
|
access |= FILE_APPEND_DATA
|
|
|
|
}
|
|
|
|
sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
|
|
|
|
var sa *SecurityAttributes
|
|
|
|
if mode&O_CLOEXEC == 0 {
|
|
|
|
sa = makeInheritSa()
|
|
|
|
}
|
|
|
|
var createmode uint32
|
|
|
|
switch {
|
|
|
|
case mode&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL):
|
|
|
|
createmode = CREATE_NEW
|
|
|
|
case mode&(O_CREAT|O_TRUNC) == (O_CREAT | O_TRUNC):
|
|
|
|
createmode = CREATE_ALWAYS
|
|
|
|
case mode&O_CREAT == O_CREAT:
|
|
|
|
createmode = OPEN_ALWAYS
|
|
|
|
case mode&O_TRUNC == O_TRUNC:
|
|
|
|
createmode = TRUNCATE_EXISTING
|
|
|
|
default:
|
|
|
|
createmode = OPEN_EXISTING
|
|
|
|
}
|
|
|
|
var attrs uint32 = FILE_ATTRIBUTE_NORMAL
|
|
|
|
if perm&S_IWRITE == 0 {
|
|
|
|
attrs = FILE_ATTRIBUTE_READONLY
|
|
|
|
if createmode == CREATE_ALWAYS {
|
|
|
|
const _ERROR_BAD_NETPATH = Errno(53)
|
|
|
|
// We have been asked to create a read-only file.
|
|
|
|
// If the file already exists, the semantics of
|
|
|
|
// the Unix open system call is to preserve the
|
|
|
|
// existing permissions. If we pass CREATE_ALWAYS
|
|
|
|
// and FILE_ATTRIBUTE_READONLY to CreateFile,
|
|
|
|
// and the file already exists, CreateFile will
|
|
|
|
// change the file permissions.
|
|
|
|
// Avoid that to preserve the Unix semantics.
|
|
|
|
h, e := CreateFile(pathp, access, sharemode, sa, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
|
|
|
|
switch e {
|
|
|
|
case ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, ERROR_PATH_NOT_FOUND:
|
|
|
|
// File does not exist. These are the same
|
|
|
|
// errors as Errno.Is checks for ErrNotExist.
|
|
|
|
// Carry on to create the file.
|
|
|
|
default:
|
|
|
|
// Success or some different error.
|
|
|
|
return h, e
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if createmode == OPEN_EXISTING && access == GENERIC_READ {
|
|
|
|
// Necessary for opening directory handles.
|
|
|
|
attrs |= FILE_FLAG_BACKUP_SEMANTICS
|
|
|
|
}
|
|
|
|
if mode&O_SYNC != 0 {
|
|
|
|
const _FILE_FLAG_WRITE_THROUGH = 0x80000000
|
|
|
|
attrs |= _FILE_FLAG_WRITE_THROUGH
|
|
|
|
}
|
|
|
|
return CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeInheritSa() *SecurityAttributes {
|
|
|
|
var sa SecurityAttributes
|
|
|
|
sa.Length = uint32(unsafe.Sizeof(sa))
|
|
|
|
sa.InheritHandle = 1
|
|
|
|
return &sa
|
|
|
|
}
|