From 17ce949c55f7b2e878b9d6257d8b7b5e681b06c2 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 3 Jan 2024 12:47:49 +0000 Subject: [PATCH] Create osutil. --- ext/csv/csv.go | 4 +- ext/lines/lines.go | 4 +- util/ioutil/ioutil.go | 2 + util/ioutil/size.go | 1 - util/osutil/open.go | 16 +++++ util/osutil/open_windows.go | 108 ++++++++++++++++++++++++++++++++ util/{fsutil => osutil}/osfs.go | 15 +++-- util/osutil/osutil.go | 2 + vfs/file.go | 4 +- vfs/os_std_open.go | 12 ---- vfs/os_windows.go | 70 --------------------- 11 files changed, 142 insertions(+), 96 deletions(-) create mode 100644 util/ioutil/ioutil.go create mode 100644 util/osutil/open.go create mode 100644 util/osutil/open_windows.go rename util/{fsutil => osutil}/osfs.go (63%) create mode 100644 util/osutil/osutil.go delete mode 100644 vfs/os_std_open.go diff --git a/ext/csv/csv.go b/ext/csv/csv.go index a8a368b..efe8ca0 100644 --- a/ext/csv/csv.go +++ b/ext/csv/csv.go @@ -15,14 +15,14 @@ import ( "strings" "github.com/ncruces/go-sqlite3" - "github.com/ncruces/go-sqlite3/util/fsutil" + "github.com/ncruces/go-sqlite3/util/osutil" "github.com/ncruces/go-sqlite3/util/vtabutil" ) // Register registers the CSV virtual table. // If a filename is specified, [os.Open] is used to open the file. func Register(db *sqlite3.Conn) { - RegisterFS(db, fsutil.OSFS{}) + RegisterFS(db, osutil.FS{}) } // RegisterFS registers the CSV virtual table. diff --git a/ext/lines/lines.go b/ext/lines/lines.go index d7171a6..73bf075 100644 --- a/ext/lines/lines.go +++ b/ext/lines/lines.go @@ -18,7 +18,7 @@ import ( "io/fs" "github.com/ncruces/go-sqlite3" - "github.com/ncruces/go-sqlite3/util/fsutil" + "github.com/ncruces/go-sqlite3/util/osutil" ) // Register registers the lines and lines_read table-valued functions. @@ -26,7 +26,7 @@ import ( // The lines_read function reads from a file or an [io.Reader]. // If a filename is specified, [os.Open] is used to open the file. func Register(db *sqlite3.Conn) { - RegisterFS(db, fsutil.OSFS{}) + RegisterFS(db, osutil.FS{}) } // RegisterFS registers the lines and lines_read table-valued functions. diff --git a/util/ioutil/ioutil.go b/util/ioutil/ioutil.go new file mode 100644 index 0000000..479a5cf --- /dev/null +++ b/util/ioutil/ioutil.go @@ -0,0 +1,2 @@ +// Package ioutil implements I/O utility functions. +package ioutil diff --git a/util/ioutil/size.go b/util/ioutil/size.go index 8c40beb..1ed52d0 100644 --- a/util/ioutil/size.go +++ b/util/ioutil/size.go @@ -1,4 +1,3 @@ -// Package ioutil implements I/O utility functions. package ioutil import ( diff --git a/util/osutil/open.go b/util/osutil/open.go new file mode 100644 index 0000000..0242ad0 --- /dev/null +++ b/util/osutil/open.go @@ -0,0 +1,16 @@ +//go:build !windows + +package osutil + +import ( + "io/fs" + "os" +) + +// 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) { + return os.OpenFile(name, flag, perm) +} diff --git a/util/osutil/open_windows.go b/util/osutil/open_windows.go new file mode 100644 index 0000000..1116580 --- /dev/null +++ b/util/osutil/open_windows.go @@ -0,0 +1,108 @@ +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} + } + r, e := syscallOpen(name, flag, uint32(perm.Perm())) + 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 + } + 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 +} diff --git a/util/fsutil/osfs.go b/util/osutil/osfs.go similarity index 63% rename from util/fsutil/osfs.go rename to util/osutil/osfs.go index b807d1d..2e11959 100644 --- a/util/fsutil/osfs.go +++ b/util/osutil/osfs.go @@ -1,12 +1,11 @@ -// Package fsutil implements file system utility functions. -package fsutil +package osutil import ( "io/fs" "os" ) -// OSFS implements [fs.FS], [fs.StatFS], and [fs.ReadFileFS] +// FS implements [fs.FS], [fs.StatFS], and [fs.ReadFileFS] // using package [os]. // // This filesystem does not respect [fs.ValidPath] rules, @@ -16,19 +15,19 @@ import ( // that can access either the [os] filesystem or an [fs.FS]. // It's OK to use this to open files, but you should avoid // opening directories, resolving paths, or walking the file system. -type OSFS struct{} +type FS struct{} // Open implements [fs.FS]. -func (OSFS) Open(name string) (fs.File, error) { - return os.Open(name) +func (FS) Open(name string) (fs.File, error) { + return OpenFile(name, os.O_RDONLY, 0) } // ReadFileFS implements [fs.StatFS]. -func (OSFS) Stat(name string) (fs.FileInfo, error) { +func (FS) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) } // ReadFile implements [fs.ReadFileFS]. -func (OSFS) ReadFile(name string) ([]byte, error) { +func (FS) ReadFile(name string) ([]byte, error) { return os.ReadFile(name) } diff --git a/util/osutil/osutil.go b/util/osutil/osutil.go new file mode 100644 index 0000000..7fbd047 --- /dev/null +++ b/util/osutil/osutil.go @@ -0,0 +1,2 @@ +// Package osutil implements operating system utility functions. +package osutil diff --git a/vfs/file.go b/vfs/file.go index 160a949..705a13a 100644 --- a/vfs/file.go +++ b/vfs/file.go @@ -9,6 +9,8 @@ import ( "path/filepath" "runtime" "syscall" + + "github.com/ncruces/go-sqlite3/util/osutil" ) type vfsOS struct{} @@ -91,7 +93,7 @@ func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (File, O if name == "" { f, err = os.CreateTemp("", "*.db") } else { - f, err = osOpenFile(name, oflags, 0666) + f, err = osutil.OpenFile(name, oflags, 0666) } if err != nil { if errors.Is(err, syscall.EISDIR) { diff --git a/vfs/os_std_open.go b/vfs/os_std_open.go deleted file mode 100644 index 9a66081..0000000 --- a/vfs/os_std_open.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !windows || sqlite3_nosys - -package vfs - -import ( - "io/fs" - "os" -) - -func osOpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { - return os.OpenFile(name, flag, perm) -} diff --git a/vfs/os_windows.go b/vfs/os_windows.go index e2e2c55..b0d3c4d 100644 --- a/vfs/os_windows.go +++ b/vfs/os_windows.go @@ -3,30 +3,12 @@ package vfs import ( - "io/fs" "os" - "syscall" "time" "golang.org/x/sys/windows" ) -// osOpenFile is a simplified copy of [os.openFileNolog] -// that uses syscall.FILE_SHARE_DELETE. -// https://go.dev/src/os/file_windows.go -// -// See: https://go.dev/issue/32088 -func osOpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { - if name == "" { - return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT} - } - r, e := syscallOpen(name, flag, uint32(perm.Perm())) - if e != nil { - return nil, &os.PathError{Op: "open", Path: name, Err: e} - } - return os.NewFile(uintptr(r), name), nil -} - func osGetSharedLock(file *os.File) _ErrorCode { // Acquire the PENDING lock temporarily before acquiring a new SHARED lock. rc := osReadLock(file, _PENDING_BYTE, 1, 0) @@ -183,55 +165,3 @@ func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { } return def } - -// syscallOpen is a simplified 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 syscall.Handle, err error) { - if len(path) == 0 { - return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND - } - pathp, err := syscall.UTF16PtrFromString(path) - if err != nil { - return syscall.InvalidHandle, err - } - var access uint32 - switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) { - case syscall.O_RDONLY: - access = syscall.GENERIC_READ - case syscall.O_WRONLY: - access = syscall.GENERIC_WRITE - case syscall.O_RDWR: - access = syscall.GENERIC_READ | syscall.GENERIC_WRITE - } - if mode&syscall.O_CREAT != 0 { - access |= syscall.GENERIC_WRITE - } - if mode&syscall.O_APPEND != 0 { - access &^= syscall.GENERIC_WRITE - access |= syscall.FILE_APPEND_DATA - } - sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE) - var createmode uint32 - switch { - case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL): - createmode = syscall.CREATE_NEW - case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC): - createmode = syscall.CREATE_ALWAYS - case mode&syscall.O_CREAT == syscall.O_CREAT: - createmode = syscall.OPEN_ALWAYS - case mode&syscall.O_TRUNC == syscall.O_TRUNC: - createmode = syscall.TRUNCATE_EXISTING - default: - createmode = syscall.OPEN_EXISTING - } - var attrs uint32 = syscall.FILE_ATTRIBUTE_NORMAL - if perm&syscall.S_IWRITE == 0 { - attrs = syscall.FILE_ATTRIBUTE_READONLY - } - if createmode == syscall.OPEN_EXISTING && access == syscall.GENERIC_READ { - // Necessary for opening directory handles. - attrs |= syscall.FILE_FLAG_BACKUP_SEMANTICS - } - return syscall.CreateFile(pathp, access, sharemode, nil, createmode, attrs, 0) -}