Skip to content

Commit

Permalink
[1/2] Add openat functionality
Browse files Browse the repository at this point in the history
This commit adds the `openat` functionality to `sys.FS`.  On UNIX-like
platforms, this is done with the `openat(2)` syscall.  On Windows, this
is done with [`NtCreateFile`][1].  This is the first in a series of
commit that overhauls how paths are resolved.  The wasi-filesystem spec
now [describes][2] a path resolution implementation that uses `openat`.

[1]: https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntcreatefile
[2]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md

Signed-off-by: Yage Hu <me@huyage.dev>
  • Loading branch information
yagehu committed Jun 17, 2024
1 parent ec36887 commit 26f2ef6
Show file tree
Hide file tree
Showing 14 changed files with 666 additions and 2 deletions.
2 changes: 2 additions & 0 deletions experimental/sys/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ type FS interface {
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
OpenFile(path string, flag Oflag, perm fs.FileMode) (File, Errno)

OpenFileAt(dir File, path string, flag Oflag, perm fs.FileMode) (File, Errno)

// Lstat gets file status without following symbolic links.
//
// # Errors
Expand Down
4 changes: 4 additions & 0 deletions experimental/sys/unimplemented.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ func (UnimplementedFS) OpenFile(path string, flag Oflag, perm fs.FileMode) (File
return nil, ENOSYS
}

func (UnimplementedFS) OpenFileAt(dir File, path string, flag Oflag, perm fs.FileMode) (File, Errno) {
return nil, ENOSYS
}

// Lstat implements FS.Lstat
func (UnimplementedFS) Lstat(path string) (sys.Stat_t, Errno) {
return sys.Stat_t{}, ENOSYS
Expand Down
9 changes: 9 additions & 0 deletions internal/sysfs/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ func (a *AdaptFS) OpenFile(path string, flag experimentalsys.Oflag, perm fs.File
return OpenFSFile(a.FS, cleanPath(path), flag, perm)
}

func (a *AdaptFS) OpenFileAt(
dir experimentalsys.File,
path string,
flag experimentalsys.Oflag,
perm fs.FileMode,
) (experimentalsys.File, experimentalsys.Errno) {
panic("todo")
}

// Lstat implements the same method as documented on sys.FS
func (a *AdaptFS) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) {
// At this time, we make the assumption sys.FS instances do not support
Expand Down
9 changes: 9 additions & 0 deletions internal/sysfs/dirfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ func (d *dirFS) OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMo
return OpenOSFile(d.join(path), flag, perm)
}

func (d *dirFS) OpenFileAt(
dir experimentalsys.File,
path string,
flag experimentalsys.Oflag,
perm fs.FileMode,
) (experimentalsys.File, experimentalsys.Errno) {
return OpenOSFileAt(dir, path, flag, perm)
}

// Lstat implements the same method as documented on sys.FS
func (d *dirFS) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) {
return lstat(d.join(path))
Expand Down
19 changes: 19 additions & 0 deletions internal/sysfs/dirfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,25 @@ func TestDirFS_OpenFile(t *testing.T) {
})
}

func TestDirFS_OpenFileAt(t *testing.T) {
tmpDir := t.TempDir()

dirPath := path.Join(tmpDir, t.Name())
require.NoError(t, os.Mkdir(dirPath, 0o700))
require.NoError(t, fstest.WriteTestFiles(dirPath))

testFS := DirFS(tmpDir)

dir, errno := testFS.OpenFile(t.Name(), sys.O_RDONLY, 0)
require.EqualErrno(t, 0, errno)

t.Run("open file under dirfd", func(t *testing.T) {
file, errno := testFS.OpenFileAt(dir, "empty.txt", sys.O_RDONLY, 0)
require.EqualErrno(t, 0, errno)
require.EqualErrno(t, 0, file.Close())
})
}

func TestDirFS_Stat(t *testing.T) {
tmpDir := t.TempDir()
require.NoError(t, fstest.WriteTestFiles(tmpDir))
Expand Down
29 changes: 29 additions & 0 deletions internal/sysfs/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,40 @@ func OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (*os.Fi
return openFile(path, flag, perm)
}

func OpenFileAt(
dir *os.File,
path string,
flag experimentalsys.Oflag,
perm fs.FileMode,
) (*os.File, experimentalsys.Errno) {
return openFileAt(dir, path, flag, perm)
}

func OpenOSFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) {
f, errno := OpenFile(path, flag, perm)
if errno != 0 {
return nil, errno
}

return newOsFile(path, flag, perm, f), 0
}

func OpenOSFileAt(
dir experimentalsys.File,
path string,
flag experimentalsys.Oflag,
perm fs.FileMode,
) (experimentalsys.File, experimentalsys.Errno) {
dirOsFile, ok := dir.(*osFile)
if !ok {
return nil, experimentalsys.EBADF
}

f, errno := OpenFileAt(dirOsFile.file, path, flag, perm)
if errno != 0 {
return nil, errno
}

return newOsFile(path, flag, perm, f), 0
}

Expand Down
28 changes: 26 additions & 2 deletions internal/sysfs/file_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,47 @@ import (
"github.com/tetratelabs/wazero/experimental/sys"
)

// NTStatus corresponds with NTSTATUS, error values returned by ntdll.dll and
// other native functions.
type NTStatus uint32

func rtlNtStatusToDosErrorNoTeb(ntstatus NTStatus) syscall.Errno {
r0, _, _ := syscall.SyscallN(procRtlNtStatusToDosErrorNoTeb.Addr(), uintptr(ntstatus))

return syscall.Errno(r0)
}

func (s NTStatus) Errno() syscall.Errno {
return rtlNtStatusToDosErrorNoTeb(s)
}

const (
nonBlockingFileReadSupported = true
nonBlockingFileWriteSupported = false

_ERROR_IO_INCOMPLETE = syscall.Errno(996)
)

var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
ntdll = syscall.NewLazyDLL("ntdll.dll")
)

// procPeekNamedPipe is the syscall.LazyProc in kernel32 for PeekNamedPipe
var (
// procFormatMessageW is the syscall.LazyProc in kernel32 for FormatMessageW
procFormatMessageW = kernel32.NewProc("FormatMessageW")
// procPeekNamedPipe is the syscall.LazyProc in kernel32 for PeekNamedPipe
procPeekNamedPipe = kernel32.NewProc("PeekNamedPipe")
// procGetOverlappedResult is the syscall.LazyProc in kernel32 for GetOverlappedResult
procGetOverlappedResult = kernel32.NewProc("GetOverlappedResult")
// procCreateEventW is the syscall.LazyProc in kernel32 for CreateEventW
procCreateEventW = kernel32.NewProc("CreateEventW")
// procNtCreateFile is the syscall.LazyProc in ntdll for NtCreateFile
procNtCreateFile = ntdll.NewProc("NtCreateFile")
// procRtlInitUnicodeString is the syscall.LazyProc in ntdll for RtlInitUnicodeString
procRtlInitUnicodeString = ntdll.NewProc("RtlInitUnicodeString")
// procRtlNtStatusToDosErrorNoTeb is the syscall.LazyProc in ntdll for RtlNtStatusToDosErrorNoTeb
procRtlNtStatusToDosErrorNoTeb = ntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
)

// readFd returns ENOSYS on unsupported platforms.
Expand Down
197 changes: 197 additions & 0 deletions internal/sysfs/nt_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package sysfs

import (
"syscall"
"unsafe"
)

// The security identifier (SID) structure is a variable-length
// structure used to uniquely identify users or groups.
type SID struct{}

type ACL struct {
aclRevision byte
sbz1 byte
aclSize uint16
aceCount uint16
sbz2 uint16
}

type SECURITY_DESCRIPTOR_CONTROL uint16

type SECURITY_DESCRIPTOR struct {
revision byte
sbz1 byte
control SECURITY_DESCRIPTOR_CONTROL
owner *SID
group *SID
sacl *ACL
dacl *ACL
}

type SECURITY_QUALITY_OF_SERVICE struct {
Length uint32
ImpersonationLevel uint32
ContextTrackingMode byte
EffectiveOnly byte
}

// NTUnicodeString is a UTF-16 string for NT native APIs, corresponding to UNICODE_STRING.
type NTUnicodeString struct {
Length uint16
MaximumLength uint16
Buffer *uint16
}

type OBJECT_ATTRIBUTES struct {
Length uint32
RootDirectory syscall.Handle
ObjectName *NTUnicodeString
Attributes uint32
SecurityDescriptor *SECURITY_DESCRIPTOR
SecurityQoS *SECURITY_QUALITY_OF_SERVICE
}

type IO_STATUS_BLOCK struct {
Status NTStatus
Information uintptr
}

// https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask
type ACCESS_MASK uint32

// Constants for type ACCESS_MASK
const (
DELETE = 0x00010000
READ_CONTROL = 0x00020000
WRITE_DAC = 0x00040000
WRITE_OWNER = 0x00080000
SYNCHRONIZE = 0x00100000
STANDARD_RIGHTS_REQUIRED = 0x000F0000
STANDARD_RIGHTS_READ = READ_CONTROL
STANDARD_RIGHTS_WRITE = READ_CONTROL
STANDARD_RIGHTS_EXECUTE = READ_CONTROL
STANDARD_RIGHTS_ALL = 0x001F0000
SPECIFIC_RIGHTS_ALL = 0x0000FFFF
ACCESS_SYSTEM_SECURITY = 0x01000000
MAXIMUM_ALLOWED = 0x02000000
GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
GENERIC_EXECUTE = 0x20000000
GENERIC_ALL = 0x10000000
)

// File access rights constants.
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants
const (
FILE_READ_DATA = 0x00000001
FILE_READ_ATTRIBUTES = 0x00000080
FILE_READ_EA = 0x00000008
FILE_WRITE_DATA = 0x00000002
FILE_WRITE_ATTRIBUTES = 0x00000100
FILE_WRITE_EA = 0x00000010
FILE_APPEND_DATA = 0x00000004
FILE_EXECUTE = 0x00000020
)

const (
FILE_GENERIC_READ = STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE
FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE
FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE
)

// NtCreateFile CreateDisposition
const (
FILE_SUPERSEDE = 0x00000000
FILE_OPEN = 0x00000001
FILE_CREATE = 0x00000002
FILE_OPEN_IF = 0x00000003
FILE_OVERWRITE = 0x00000004
FILE_OVERWRITE_IF = 0x00000005
FILE_MAXIMUM_DISPOSITION = 0x00000005
)

// NtCreateFile CreateOptions
const (
FILE_DIRECTORY_FILE = 0x00000001
FILE_WRITE_THROUGH = 0x00000002
FILE_SEQUENTIAL_ONLY = 0x00000004
FILE_NO_INTERMEDIATE_BUFFERING = 0x00000008
FILE_SYNCHRONOUS_IO_ALERT = 0x00000010
FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020
FILE_NON_DIRECTORY_FILE = 0x00000040
FILE_CREATE_TREE_CONNECTION = 0x00000080
FILE_COMPLETE_IF_OPLOCKED = 0x00000100
FILE_NO_EA_KNOWLEDGE = 0x00000200
FILE_OPEN_FOR_RECOVERY = 0x00000400
FILE_RANDOM_ACCESS = 0x00000800
FILE_DELETE_ON_CLOSE = 0x00001000
FILE_OPEN_BY_FILE_ID = 0x00002000
FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000
FILE_NO_COMPRESSION = 0x00008000
FILE_RESERVE_OPFILTER = 0x00100000
FILE_OPEN_REPARSE_POINT = 0x00200000
FILE_OPEN_NO_RECALL = 0x00400000
FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000
)

// https://learn.microsoft.com/en-us/windows/win32/api/ntdef/nf-ntdef-initializeobjectattributes
const (
OBJ_CASE_INSENSITIVE = 0x00000040
)

func NtCreateFile(
handle *syscall.Handle,
access uint32,
oa *OBJECT_ATTRIBUTES,
iosb *IO_STATUS_BLOCK,
allocationSize *int64,
attributes uint32,
share uint32,
disposition uint32,
options uint32,
eabuffer uintptr,
ealength uint32,
) syscall.Errno {
r0, _, _ := syscall.SyscallN(
procNtCreateFile.Addr(),
uintptr(unsafe.Pointer(handle)),
uintptr(access),
uintptr(unsafe.Pointer(oa)),
uintptr(unsafe.Pointer(iosb)),
uintptr(unsafe.Pointer(allocationSize)),
uintptr(attributes),
uintptr(share),
uintptr(disposition),
uintptr(options),
uintptr(eabuffer),
uintptr(ealength),
)

return NTStatus(r0).Errno()
}

// NewNTUnicodeString returns a new NTUnicodeString structure for use with native
// NT APIs that work over the NTUnicodeString type. Note that most Windows APIs
// do not use NTUnicodeString, and instead UTF16PtrFromString should be used for
// the more common *uint16 string type.
func NewNTUnicodeString(s string) (*NTUnicodeString, error) {
var u NTUnicodeString

s16, err := syscall.UTF16PtrFromString(s)
if err != nil {
return nil, err
}

RtlInitUnicodeString(&u, s16)

return &u, nil
}

func RtlInitUnicodeString(destinationString *NTUnicodeString, sourceString *uint16) {
syscall.SyscallN(
procRtlInitUnicodeString.Addr(),
uintptr(unsafe.Pointer(destinationString)),
uintptr(unsafe.Pointer(sourceString)),
)
}
Loading

0 comments on commit 26f2ef6

Please sign in to comment.