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:
40
internal/platform/chown.go
Normal file
40
internal/platform/chown.go
Normal 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
|
||||
}
|
||||
9
internal/platform/chown_unix.go
Normal file
9
internal/platform/chown_unix.go
Normal 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)
|
||||
}
|
||||
174
internal/platform/chown_unix_test.go
Normal file
174
internal/platform/chown_unix_test.go
Normal 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)
|
||||
}
|
||||
11
internal/platform/chown_unsupported.go
Normal file
11
internal/platform/chown_unsupported.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user