gojs: implements remaining link functions (#1198)

This implements the last remaining link functions using the same logic
as WASI. In doing so, this changes the signature for FS.ReadLink to be
more similar to the rest of the functions. Particularly, it stops
reading the result into a buffer.

After this, the only syscalls left to implement in gojs are chown.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2023-03-05 16:11:36 +08:00
committed by GitHub
parent b533540485
commit a9b3301862
15 changed files with 122 additions and 79 deletions

View File

@@ -1730,17 +1730,16 @@ func pathReadlinkFn(_ context.Context, mod api.Module, params []uint64) Errno {
return en
}
buf, ok := mem.Read(bufPtr, bufLen)
if !ok {
return ErrnoFault
}
n, err := preopen.Readlink(p, buf)
dst, err := preopen.Readlink(p)
if err != nil {
return ToErrno(err)
}
if !mem.WriteUint32Le(resultBufUsedPtr, uint32(n)) {
if ok := mem.WriteString(bufPtr, dst); !ok {
return ErrnoFault
}
if !mem.WriteUint32Le(resultBufUsedPtr, uint32(len(dst))) {
return ErrnoFault
}
return ErrnoSuccess

View File

@@ -3826,14 +3826,30 @@ func Test_pathReadlink(t *testing.T) {
t.Run("errors", func(t *testing.T) {
for _, tc := range []struct {
name string
errno Errno
dirFd, pathPtr, pathLen, bufPtr, bufLen, resultBufUsedPtr uint64
}{
{errno: ErrnoInval},
{errno: ErrnoInval, pathLen: 100},
{errno: ErrnoInval, bufLen: 100},
{errno: ErrnoFault, dirFd: uint64(topFd), bufLen: 100, pathLen: 100, bufPtr: math.MaxUint32},
{errno: ErrnoFault, bufLen: 100, pathLen: 100, bufPtr: 50, pathPtr: math.MaxUint32},
{
name: "bufLen too short",
errno: ErrnoFault,
dirFd: uint64(topFd),
bufLen: 10,
pathPtr: destinationPathNamePtr,
pathLen: uint64(len(destinationPathName)),
bufPtr: math.MaxUint32,
},
{
name: "pathPtr past memory",
errno: ErrnoFault,
bufLen: 100,
pathLen: 100,
bufPtr: 50,
pathPtr: math.MaxUint32,
},
{errno: ErrnoNotdir, bufLen: 100, pathLen: 100, bufPtr: 50, pathPtr: 50, dirFd: 1},
{errno: ErrnoBadf, bufLen: 100, pathLen: 100, bufPtr: 50, pathPtr: 50, dirFd: 1000},
{
@@ -3843,12 +3859,15 @@ func Test_pathReadlink(t *testing.T) {
dirFd: uint64(topFd),
},
} {
name := ErrnoName(tc.errno)
name := tc.name
if name == "" {
name = ErrnoName(tc.errno)
}
t.Run(name, func(t *testing.T) {
requireErrnoResult(t, tc.errno, mod, PathReadlinkName,
tc.dirFd, tc.pathPtr, tc.pathLen, tc.bufPtr,
tc.bufLen, tc.resultBufUsedPtr)
require.Contains(t, log.String(), name)
require.Contains(t, log.String(), ErrnoName(tc.errno))
})
}
})

View File

@@ -664,9 +664,8 @@ func (jsfsReadlink) invoke(ctx context.Context, mod api.Module, args ...interfac
path := args[0].(string)
callback := args[1].(funcWrapper)
_ = path // TODO
var dst string
var err error = syscall.ENOSYS
fsc := mod.(*wasm.CallContext).Sys.FS()
dst, err := fsc.RootFS().Readlink(path)
return callback.invoke(ctx, mod, goos.RefJsfs, err, dst) // note: error first
}
@@ -681,8 +680,8 @@ func (jsfsLink) invoke(ctx context.Context, mod api.Module, args ...interface{})
link := args[1].(string)
callback := args[2].(funcWrapper)
_, _ = path, link // TODO
var err error = syscall.ENOSYS
fsc := mod.(*wasm.CallContext).Sys.FS()
err := fsc.RootFS().Link(path, link)
return jsfsInvoke(ctx, mod, callback, err)
}

View File

@@ -122,22 +122,53 @@ func Main() {
log.Panicln("unexpected contents:", string(bytes))
}
// Test lstat which should be about the link not its target.
// create a hard link
link := file1 + "-link"
if err = os.Symlink(file1, link); err != nil {
if err = os.Link(file1, link); err != nil {
log.Panicln(err)
}
lstat, err := os.Lstat(link)
// Ensure this is a hard link, so they have the same inode.
file1Stat, err := os.Lstat(file1)
if err != nil {
log.Panicln(err)
}
linkStat, err := os.Lstat(link)
if err != nil {
log.Panicln(err)
}
if !os.SameFile(file1Stat, linkStat) {
log.Panicln("expected file == link stat", file1Stat, linkStat)
}
// create a symbolic link
symlink := file1 + "-symlink"
if err = os.Symlink(file1, symlink); err != nil {
log.Panicln(err)
}
// verify we can read the symbolic link back
if dst, err := os.Readlink(symlink); err != nil {
log.Panicln(err)
} else if dst != dst {
log.Panicln("expected link dst = old value", dst, dst)
}
// Test lstat which should be about the link not its target.
symlinkStat, err := os.Lstat(symlink)
if err != nil {
log.Panicln(err)
}
if lstat.Mode().Type() != fs.ModeSymlink {
log.Panicln("expected type = symlink", lstat.Mode().Type())
if symlinkStat.Mode().Type() != fs.ModeSymlink {
log.Panicln("expected type = symlink", symlinkStat.Mode().Type())
}
if size := int64(len(file1)); lstat.Size() != size {
log.Panicln("unexpected symlink size", lstat.Size(), size)
if size := int64(len(file1)); symlinkStat.Size() != size {
log.Panicln("unexpected symlink size", symlinkStat.Size(), size)
}
// A symbolic link isn't the same file as what it points to.
if os.SameFile(file1Stat, symlinkStat) {
log.Panicln("expected file != link stat", file1Stat, symlinkStat)
}
// Test removing a non-empty empty directory

View File

@@ -0,0 +1,6 @@
//go:build !windows
package platform
// ToPosixPath returns the input, as only windows might return backslashes.
func ToPosixPath(in string) string { return in }

View File

@@ -0,0 +1,14 @@
package platform
import (
"path/filepath"
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func TestToPosixPath(t *testing.T) {
orig := filepath.Join("a", "b", "c")
fixed := ToPosixPath(orig)
require.Equal(t, "a/b/c", fixed)
}

View File

@@ -0,0 +1,17 @@
package platform
import "strings"
// ToPosixPath returns the input, converting any backslashes to forward ones.
func ToPosixPath(in string) string {
// strings.Map only allocates on change, which is good enough especially as
// path.Join uses forward slash even on windows.
return strings.Map(windowsToPosixSeparator, in)
}
func windowsToPosixSeparator(r rune) rune {
if r == '\\' {
return '/'
}
return r
}

View File

@@ -1,7 +0,0 @@
//go:build !windows
package platform
// SanitizeSeparator sanitizes the path separator in the given buffer.
// This does nothing on the non-windows platforms.
func SanitizeSeparator([]byte) {}

View File

@@ -1,14 +0,0 @@
package platform
import (
"path/filepath"
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func TestSanitizeSeparator(t *testing.T) {
orig := []byte(filepath.Join("a", "b", "c"))
SanitizeSeparator(orig)
require.Equal(t, "a/b/c", string(orig))
}

View File

@@ -1,10 +0,0 @@
package platform
// SanitizeSeparator sanitizes the path separator in the given buffer.
func SanitizeSeparator(in []byte) {
for i := range in {
if in[i] == '\\' {
in[i] = '/'
}
}
}

View File

@@ -78,23 +78,14 @@ func (d *dirFS) Rename(from, to string) error {
}
// Readlink implements FS.Readlink
func (d *dirFS) Readlink(path string, buf []byte) (n int, err error) {
func (d *dirFS) Readlink(path string) (string, error) {
// Note: do not use syscall.Readlink as that causes race on Windows.
// In any case, syscall.Readlink does almost the same logic as os.Readlink.
res, err := os.Readlink(d.join(path))
dst, err := os.Readlink(d.join(path))
if err != nil {
err = platform.UnwrapOSError(err)
return
return "", platform.UnwrapOSError(err)
}
// We need to copy here, but syscall.Readlink does copy internally, so the cost is the same.
copy(buf, res)
n = len(res)
if n > len(buf) {
n = len(buf)
}
platform.SanitizeSeparator(buf[:n])
return
return platform.ToPosixPath(dst), nil
}
// Link implements FS.Link.

View File

@@ -152,6 +152,6 @@ func (r *readFS) Stat(path string, stat *platform.Stat_t) error {
}
// Readlink implements FS.Readlink
func (r *readFS) Readlink(path string, buf []byte) (n int, err error) {
return r.fs.Readlink(path, buf)
func (r *readFS) Readlink(path string) (dst string, err error) {
return r.fs.Readlink(path)
}

View File

@@ -225,7 +225,7 @@ type FS interface {
// - On Windows, the path separator is different from other platforms,
// but to provide consistent results to Wasm, this normalizes to a "/"
// separator.
Readlink(path string, buf []byte) (n int, err error)
Readlink(path string) (string, error)
// Truncate is similar to syscall.Truncate, except the path is relative to
// this file system.

View File

@@ -370,23 +370,21 @@ func testReadlink(t *testing.T, readFS, writeFS FS) {
{old: "sub/test.txt", dst: "symlinked-zoo.txt"},
}
buf := make([]byte, 200)
for _, tl := range testLinks {
err := writeFS.Symlink(tl.old, tl.dst) // not os.Symlink for windows compat
require.NoError(t, err, "%v", tl)
n, err := readFS.Readlink(tl.dst, buf)
dst, err := readFS.Readlink(tl.dst)
require.NoError(t, err)
require.Equal(t, tl.old, string(buf[:n]))
require.Equal(t, len(tl.old), n)
require.Equal(t, tl.old, dst)
}
t.Run("errors", func(t *testing.T) {
_, err := readFS.Readlink("sub/test.txt", buf)
_, err := readFS.Readlink("sub/test.txt")
require.Error(t, err)
_, err = readFS.Readlink("", buf)
_, err = readFS.Readlink("")
require.Error(t, err)
_, err = readFS.Readlink("animals.txt", buf)
_, err = readFS.Readlink("animals.txt")
require.Error(t, err)
})
}

View File

@@ -57,8 +57,8 @@ func (UnimplementedFS) Rmdir(path string) error {
}
// Readlink implements FS.Readlink
func (UnimplementedFS) Readlink(string, []byte) (int, error) {
return 0, syscall.ENOSYS
func (UnimplementedFS) Readlink(path string) (string, error) {
return "", syscall.ENOSYS
}
// Link implements FS.Link