gojs: implements chown (#1204)

This finishes the last remaining syscalls in `GOOS=js`. After this is
merged, further bugs are easier to hunt down as we know ENOSYS is not
expected on writeable file systems.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2023-03-06 17:43:46 +08:00
committed by GitHub
parent 962a92fbc7
commit b742c7a8cc
11 changed files with 519 additions and 29 deletions

View File

@@ -0,0 +1,40 @@
package platform
import (
"io/fs"
"os"
"syscall"
)
// Chown is like os.Chown, except it returns a syscall.Errno, not a
// fs.PathError. For example, this returns syscall.ENOENT if the path doesn't
// exist. See https://linux.die.net/man/3/chown
//
// Note: This always returns syscall.ENOSYS on windows.
func Chown(path string, uid, gid int) error {
err := os.Chown(path, uid, gid)
return UnwrapOSError(err)
}
// Lchown is like os.Lchown, except it returns a syscall.Errno, not a
// fs.PathError. For example, this returns syscall.ENOENT if the path doesn't
// exist. See https://linux.die.net/man/3/lchown
//
// Note: This always returns syscall.ENOSYS on windows.
func Lchown(path string, uid, gid int) error {
err := os.Lchown(path, uid, gid)
return UnwrapOSError(err)
}
// ChownFile is like syscall.Fchown, but for nanosecond precision and
// fs.File instead of a file descriptor. This returns syscall.EBADF if the file
// or directory was closed. See https://linux.die.net/man/3/fchown
//
// Note: This always returns syscall.ENOSYS on windows.
func ChownFile(f fs.File, uid, gid int) error {
if f, ok := f.(fdFile); ok {
err := fchown(f.Fd(), uid, gid)
return UnwrapOSError(err)
}
return syscall.ENOSYS
}

View File

@@ -0,0 +1,9 @@
//go:build !windows
package platform
import "syscall"
func fchown(fd uintptr, uid, gid int) error {
return syscall.Fchown(int(fd), uid, gid)
}

View File

@@ -0,0 +1,174 @@
//go:build !windows
package platform
import (
"fmt"
"os"
"path"
"syscall"
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func TestChown(t *testing.T) {
tmpDir := t.TempDir()
dir := path.Join(tmpDir, "dir")
require.NoError(t, os.Mkdir(dir, 0o0777))
dirF, err := OpenFile(dir, syscall.O_RDONLY, 0)
require.NoError(t, err)
dirStat, err := dirF.Stat()
require.NoError(t, err)
dirSys := dirStat.Sys().(*syscall.Stat_t)
// Similar to TestChown in os_unix_test.go, we can't expect to change
// owner unless root, and with another user. Instead, test gid.
gid := os.Getgid()
groups, err := os.Getgroups()
require.NoError(t, err)
t.Run("-1 parameters means leave alone", func(t *testing.T) {
require.NoError(t, Chown(dir, -1, -1))
checkUidGid(t, dir, dirSys.Uid, dirSys.Gid)
})
t.Run("change gid, but not uid", func(t *testing.T) {
require.NoError(t, Chown(dir, -1, gid))
checkUidGid(t, dir, dirSys.Uid, uint32(gid))
})
// Now, try any other groups of the current user.
for _, g := range groups {
g := g
t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
// Test using our Chown
require.NoError(t, Chown(dir, -1, g))
checkUidGid(t, dir, dirSys.Uid, uint32(g))
// Revert back with os.File.Chown
require.NoError(t, dirF.Chown(-1, gid))
checkUidGid(t, dir, dirSys.Uid, uint32(gid))
})
}
t.Run("not found", func(t *testing.T) {
require.EqualErrno(t, syscall.ENOENT, Chown(path.Join(tmpDir, "a"), -1, gid))
})
}
func TestChownFile(t *testing.T) {
tmpDir := t.TempDir()
dir := path.Join(tmpDir, "dir")
require.NoError(t, os.Mkdir(dir, 0o0777))
dirF, err := OpenFile(dir, syscall.O_RDONLY, 0)
require.NoError(t, err)
dirStat, err := dirF.Stat()
require.NoError(t, err)
dirSys := dirStat.Sys().(*syscall.Stat_t)
// Similar to TestChownFile in os_unix_test.go, we can't expect to change
// owner unless root, and with another user. Instead, test gid.
gid := os.Getgid()
groups, err := os.Getgroups()
require.NoError(t, err)
t.Run("-1 parameters means leave alone", func(t *testing.T) {
require.NoError(t, ChownFile(dirF, -1, -1))
checkUidGid(t, dir, dirSys.Uid, dirSys.Gid)
})
t.Run("change gid, but not uid", func(t *testing.T) {
require.NoError(t, ChownFile(dirF, -1, gid))
checkUidGid(t, dir, dirSys.Uid, uint32(gid))
})
// Now, try any other groups of the current user.
for _, g := range groups {
g := g
t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
// Test using our ChownFile
require.NoError(t, ChownFile(dirF, -1, g))
checkUidGid(t, dir, dirSys.Uid, uint32(g))
// Revert back with os.File.Chown
require.NoError(t, dirF.Chown(-1, gid))
checkUidGid(t, dir, dirSys.Uid, uint32(gid))
})
}
t.Run("closed", func(t *testing.T) {
require.NoError(t, dirF.Close())
require.EqualErrno(t, syscall.EBADF, ChownFile(dirF, -1, gid))
})
}
func TestLchown(t *testing.T) {
tmpDir := t.TempDir()
dir := path.Join(tmpDir, "dir")
require.NoError(t, os.Mkdir(dir, 0o0777))
dirF, err := OpenFile(dir, syscall.O_RDONLY, 0)
require.NoError(t, err)
dirStat, err := dirF.Stat()
require.NoError(t, err)
dirSys := dirStat.Sys().(*syscall.Stat_t)
link := path.Join(tmpDir, "link")
require.NoError(t, os.Symlink(dir, link))
linkF, err := OpenFile(link, syscall.O_RDONLY, 0)
require.NoError(t, err)
linkStat, err := linkF.Stat()
require.NoError(t, err)
linkSys := linkStat.Sys().(*syscall.Stat_t)
// Similar to TestLchown in os_unix_test.go, we can't expect to change
// owner unless root, and with another user. Instead, test gid.
gid := os.Getgid()
groups, err := os.Getgroups()
require.NoError(t, err)
t.Run("-1 parameters means leave alone", func(t *testing.T) {
require.NoError(t, Lchown(link, -1, -1))
checkUidGid(t, link, linkSys.Uid, linkSys.Gid)
})
t.Run("change gid, but not uid", func(t *testing.T) {
require.NoError(t, Chown(dir, -1, gid))
checkUidGid(t, link, linkSys.Uid, uint32(gid))
// Make sure the target didn't change.
checkUidGid(t, dir, dirSys.Uid, dirSys.Gid)
})
// Now, try any other groups of the current user.
for _, g := range groups {
g := g
t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
// Test using our Lchown
require.NoError(t, Lchown(link, -1, g))
checkUidGid(t, link, linkSys.Uid, uint32(g))
// Make sure the target didn't change.
checkUidGid(t, dir, dirSys.Uid, dirSys.Gid)
// Revert back with syscall.Lchown
require.NoError(t, syscall.Lchown(link, -1, gid))
checkUidGid(t, link, linkSys.Uid, uint32(gid))
})
}
t.Run("not found", func(t *testing.T) {
require.EqualErrno(t, syscall.ENOENT, Lchown(path.Join(tmpDir, "a"), -1, gid))
})
}
// checkUidGid uses lstat to ensure the comparison is against the file, not the
// target of a symbolic link.
func checkUidGid(t *testing.T, path string, uid, gid uint32) {
ls, err := os.Lstat(path)
require.NoError(t, err)
sys := ls.Sys().(*syscall.Stat_t)
require.Equal(t, uid, sys.Uid)
require.Equal(t, gid, sys.Gid)
}

View File

@@ -0,0 +1,11 @@
//go:build windows
package platform
import "syscall"
// fchown is not supported on windows. For example, syscall.Fchown returns
// syscall.EWINDOWS, which is the same as syscall.ENOSYS.
func fchown(fd uintptr, uid, gid int) error {
return syscall.ENOSYS
}