wasi: support O_DIRECTORY and O_NOFOLLOW (#1093)

Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
Co-authored-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
Achille
2023-02-06 19:43:59 -08:00
committed by GitHub
parent 8918d73020
commit 5cdf32062c
5 changed files with 79 additions and 25 deletions

View File

@@ -1545,7 +1545,7 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
// TODO: dirflags is a lookupflags, and it only has one bit: symlink_follow // TODO: dirflags is a lookupflags, and it only has one bit: symlink_follow
// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#lookupflags // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#lookupflags
_ /* dirflags */ = uint32(params[1]) dirflags := uint16(params[1])
path := uint32(params[2]) path := uint32(params[2])
pathLen := uint32(params[3]) pathLen := uint32(params[3])
@@ -1563,7 +1563,8 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
return errno return errno
} }
fileOpenFlags, isDir := openFlags(oflags, fdflags) fileOpenFlags := openFlags(dirflags, oflags, fdflags)
isDir := fileOpenFlags&platform.O_DIRECTORY != 0
if isDir && oflags&O_CREAT != 0 { if isDir && oflags&O_CREAT != 0 {
return ErrnoInval // use pathCreateDirectory! return ErrnoInval // use pathCreateDirectory!
@@ -1635,8 +1636,16 @@ func preopenPath(fsc *sys.FSContext, dirFD uint32) (string, Errno) {
} }
} }
func openFlags(oflags, fdflags uint16) (openFlags int, isDir bool) { func openFlags(dirflags, oflags, fdflags uint16) (openFlags int) {
isDir = oflags&O_DIRECTORY != 0 if dirflags&LOOKUP_SYMLINK_FOLLOW == 0 {
openFlags |= platform.O_NOFOLLOW
}
if oflags&O_DIRECTORY != 0 {
openFlags |= platform.O_DIRECTORY
return // Early return for directories as the rest of flags doesn't make sense for it.
} else if oflags&O_EXCL != 0 {
openFlags |= syscall.O_EXCL
}
if oflags&O_TRUNC != 0 { if oflags&O_TRUNC != 0 {
openFlags |= syscall.O_RDWR | syscall.O_TRUNC openFlags |= syscall.O_RDWR | syscall.O_TRUNC
} }
@@ -1649,12 +1658,6 @@ func openFlags(oflags, fdflags uint16) (openFlags int, isDir bool) {
if openFlags == 0 { if openFlags == 0 {
openFlags = syscall.O_RDONLY openFlags = syscall.O_RDONLY
} }
if isDir {
return
}
if oflags&O_EXCL != 0 {
openFlags |= syscall.O_EXCL
}
return return
} }

View File

@@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/tetratelabs/wazero/internal/fstest" "github.com/tetratelabs/wazero/internal/fstest"
"github.com/tetratelabs/wazero/internal/platform"
"github.com/tetratelabs/wazero/internal/sys" "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/testing/require"
. "github.com/tetratelabs/wazero/internal/wasi_snapshot_preview1" . "github.com/tetratelabs/wazero/internal/wasi_snapshot_preview1"
@@ -363,45 +364,48 @@ func Test_writeDirents(t *testing.T) {
func Test_openFlags(t *testing.T) { func Test_openFlags(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
oflags, fdflags uint16 dirflags, oflags, fdflags uint16
expectedOpenFlags int expectedOpenFlags int
expectedIsDir bool
}{ }{
{ {
name: "oflags=0", name: "oflags=0",
expectedOpenFlags: syscall.O_RDONLY, expectedOpenFlags: platform.O_NOFOLLOW | syscall.O_RDONLY,
}, },
{ {
name: "oflags=O_CREAT", name: "oflags=O_CREAT",
oflags: O_CREAT, oflags: O_CREAT,
expectedOpenFlags: syscall.O_RDWR | syscall.O_CREAT, expectedOpenFlags: platform.O_NOFOLLOW | syscall.O_RDWR | syscall.O_CREAT,
}, },
{ {
name: "oflags=O_DIRECTORY", name: "oflags=O_DIRECTORY",
oflags: O_DIRECTORY, oflags: O_DIRECTORY,
expectedOpenFlags: syscall.O_RDONLY, expectedOpenFlags: platform.O_NOFOLLOW | platform.O_DIRECTORY,
expectedIsDir: true,
}, },
{ {
name: "oflags=O_EXCL", name: "oflags=O_EXCL",
oflags: O_EXCL, oflags: O_EXCL,
expectedOpenFlags: syscall.O_RDONLY | syscall.O_EXCL, expectedOpenFlags: platform.O_NOFOLLOW | syscall.O_RDONLY | syscall.O_EXCL,
}, },
{ {
name: "oflags=O_TRUNC", name: "oflags=O_TRUNC",
oflags: O_TRUNC, oflags: O_TRUNC,
expectedOpenFlags: syscall.O_RDWR | syscall.O_TRUNC, expectedOpenFlags: platform.O_NOFOLLOW | syscall.O_RDWR | syscall.O_TRUNC,
}, },
{ {
name: "fdflags=FD_APPEND", name: "fdflags=FD_APPEND",
fdflags: FD_APPEND, fdflags: FD_APPEND,
expectedOpenFlags: syscall.O_RDWR | syscall.O_APPEND, expectedOpenFlags: platform.O_NOFOLLOW | syscall.O_RDWR | syscall.O_APPEND,
}, },
{ {
name: "oflags=O_TRUNC|O_CREAT", name: "oflags=O_TRUNC|O_CREAT",
oflags: O_TRUNC | O_CREAT, oflags: O_TRUNC | O_CREAT,
expectedOpenFlags: syscall.O_RDWR | syscall.O_TRUNC | syscall.O_CREAT, expectedOpenFlags: platform.O_NOFOLLOW | syscall.O_RDWR | syscall.O_TRUNC | syscall.O_CREAT,
},
{
name: "dirflags=LOOKUP_SYMLINK_FOLLOW",
dirflags: LOOKUP_SYMLINK_FOLLOW,
expectedOpenFlags: syscall.O_RDONLY,
}, },
} }
@@ -409,9 +413,8 @@ func Test_openFlags(t *testing.T) {
tc := tt tc := tt
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
openFlags, isDir := openFlags(tc.oflags, tc.fdflags) openFlags := openFlags(tc.dirflags, tc.oflags, tc.fdflags)
require.Equal(t, tc.expectedOpenFlags, openFlags) require.Equal(t, tc.expectedOpenFlags, openFlags)
require.Equal(t, tc.expectedIsDir, isDir)
}) })
} }
} }

View File

@@ -1,10 +1,18 @@
//go:build !windows //go:build !windows && !js
package platform package platform
import ( import (
"io/fs" "io/fs"
"os" "os"
"syscall"
)
// Simple aliases to constants in the syscall package for portability with
// platforms which do not have them (e.g. windows)
const (
O_DIRECTORY = syscall.O_DIRECTORY
O_NOFOLLOW = syscall.O_NOFOLLOW
) )
func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {

View File

@@ -0,0 +1,19 @@
//go:build js
package platform
import (
"io/fs"
"os"
)
// See the comments on the same constants in open_file_windows.go
const (
O_DIRECTORY = 1 << 29
O_NOFOLLOW = 1 << 30
)
func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
flag &= ^(O_DIRECTORY | O_NOFOLLOW) // erase placeholders
return os.OpenFile(name, flag, perm)
}

View File

@@ -7,6 +7,26 @@ import (
"unsafe" "unsafe"
) )
// Windows does not have these constants, we declare placeholders which should
// not conflict with other open flags. These placeholders are not declared as
// value zero so code written in a way which expects them to be bit flags still
// works as expected.
//
// Since those placeholder are not interpreted by the open function, the unix
// features they represent are also not implemented on windows:
//
// - O_DIRECTORY allows programs to ensure that the opened file is a directory.
// This could be emulated by doing a stat call on the file after opening it
// to verify that it is in fact a directory, then closing it and returning an
// error if it is not.
//
// - O_NOFOLLOW allows programs to ensure that if the opened file is a symbolic
// link, the link itself is opened instead of its target.
const (
O_DIRECTORY = 1 << 29
O_NOFOLLOW = 1 << 30
)
func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
fd, err := open(name, flag|syscall.O_CLOEXEC, uint32(perm)) fd, err := open(name, flag|syscall.O_CLOEXEC, uint32(perm))
if err == nil { if err == nil {
@@ -19,6 +39,7 @@ func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
// The following is lifted from syscall_windows.go to add support for setting FILE_SHARE_DELETE. // The following is lifted from syscall_windows.go to add support for setting FILE_SHARE_DELETE.
// https://github.com/golang/go/blob/go1.19.5/src/syscall/syscall_windows.go#L307-L375 // https://github.com/golang/go/blob/go1.19.5/src/syscall/syscall_windows.go#L307-L375
func open(path string, mode int, perm uint32) (fd syscall.Handle, err error) { func open(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
mode &= ^(O_DIRECTORY | O_NOFOLLOW) // erase placeholders
if len(path) == 0 { if len(path) == 0 {
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
} }