gojs: implements umask (#1230)
This implements host-side umask for gojs, which allows `os.TestMkdirStickyUmask` to pass. Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
@@ -55,7 +55,7 @@ var (
|
||||
addFunction(custom.NameProcessGetgid, returnZero{}). // syscall.Getgid in syscall_js.go
|
||||
addFunction(custom.NameProcessGeteuid, returnZero{}). // syscall.Geteuid in syscall_js.go
|
||||
addFunction(custom.NameProcessGetgroups, returnSliceOfZero{}). // syscall.Getgroups in syscall_js.go
|
||||
addFunction(custom.NameProcessUmask, returnArg0{}) // syscall.Umask in syscall_js.go
|
||||
addFunction(custom.NameProcessUmask, processUmask{}) // syscall.Umask in syscall_js.go
|
||||
|
||||
// uint8ArrayConstructor = js.Global().Get("Uint8Array")
|
||||
// // fs_js.go, rand_js.go, roundtrip_js.go init
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package custom
|
||||
|
||||
import "io/fs"
|
||||
|
||||
const (
|
||||
NameFs = "fs"
|
||||
NameFsOpen = "open"
|
||||
@@ -153,3 +155,94 @@ var FsNameSection = map[string]*Names{
|
||||
ResultNames: []string{"err", "ok"},
|
||||
},
|
||||
}
|
||||
|
||||
// mode constants from syscall_js.go
|
||||
const (
|
||||
S_IFSOCK = uint32(0o000140000)
|
||||
S_IFLNK = uint32(0o000120000)
|
||||
S_IFREG = uint32(0o000100000)
|
||||
S_IFBLK = uint32(0o000060000)
|
||||
S_IFDIR = uint32(0o000040000)
|
||||
S_IFCHR = uint32(0o000020000)
|
||||
S_IFIFO = uint32(0o000010000)
|
||||
|
||||
S_ISUID = uint32(0o004000)
|
||||
S_ISGID = uint32(0o002000)
|
||||
S_ISVTX = uint32(0o001000)
|
||||
)
|
||||
|
||||
// ToJsMode is required because the mode property read in `GOOS=js` is
|
||||
// incompatible with normal go. Particularly the directory flag isn't the same.
|
||||
func ToJsMode(fm fs.FileMode) (jsMode uint32) {
|
||||
switch {
|
||||
case fm.IsRegular():
|
||||
jsMode = S_IFREG
|
||||
case fm.IsDir():
|
||||
jsMode = S_IFDIR
|
||||
case fm&fs.ModeSymlink != 0:
|
||||
jsMode = S_IFLNK
|
||||
case fm&fs.ModeDevice != 0:
|
||||
// Unlike ModeDevice and ModeCharDevice, S_IFCHR and S_IFBLK are set
|
||||
// mutually exclusively.
|
||||
if fm&fs.ModeCharDevice != 0 {
|
||||
jsMode = S_IFCHR
|
||||
} else {
|
||||
jsMode = S_IFBLK
|
||||
}
|
||||
case fm&fs.ModeNamedPipe != 0:
|
||||
jsMode = S_IFIFO
|
||||
case fm&fs.ModeSocket != 0:
|
||||
jsMode = S_IFSOCK
|
||||
default: // unknown
|
||||
jsMode = 0
|
||||
}
|
||||
|
||||
jsMode |= uint32(fm & fs.ModePerm)
|
||||
|
||||
if fm&fs.ModeSetgid != 0 {
|
||||
jsMode |= S_ISGID
|
||||
}
|
||||
if fm&fs.ModeSetuid != 0 {
|
||||
jsMode |= S_ISUID
|
||||
}
|
||||
if fm&fs.ModeSticky != 0 {
|
||||
jsMode |= S_ISVTX
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FromJsMode is required because the mode property read in `GOOS=js` is
|
||||
// incompatible with normal go. Particularly the directory flag isn't the same.
|
||||
func FromJsMode(jsMode, umask uint32) (fm fs.FileMode) {
|
||||
switch {
|
||||
case jsMode&S_IFREG != 0: // zero
|
||||
case jsMode&S_IFDIR != 0:
|
||||
fm = fs.ModeDir
|
||||
case jsMode&S_IFLNK != 0:
|
||||
fm = fs.ModeSymlink
|
||||
case jsMode&S_IFCHR != 0:
|
||||
fm = fs.ModeDevice | fs.ModeCharDevice
|
||||
case jsMode&S_IFBLK != 0:
|
||||
fm = fs.ModeDevice
|
||||
case jsMode&S_IFIFO != 0:
|
||||
fm = fs.ModeNamedPipe
|
||||
case jsMode&S_IFSOCK != 0:
|
||||
fm = fs.ModeSocket
|
||||
default: // unknown
|
||||
fm = 0
|
||||
}
|
||||
|
||||
fm |= fs.FileMode(jsMode) & fs.ModePerm
|
||||
|
||||
if jsMode&S_ISGID != 0 {
|
||||
fm |= fs.ModeSetgid
|
||||
}
|
||||
if jsMode&S_ISUID != 0 {
|
||||
fm |= fs.ModeSetuid
|
||||
}
|
||||
if jsMode&S_ISVTX != 0 {
|
||||
fm |= fs.ModeSticky
|
||||
}
|
||||
fm &= ^(fs.FileMode(umask))
|
||||
return
|
||||
}
|
||||
|
||||
28
internal/gojs/custom/fs_test.go
Normal file
28
internal/gojs/custom/fs_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
func Test_ToJsMode(t *testing.T) {
|
||||
t.Run("/dev/null", func(t *testing.T) {
|
||||
st, err := os.Stat(os.DevNull)
|
||||
require.NoError(t, err)
|
||||
|
||||
fm := ToJsMode(st.Mode())
|
||||
|
||||
// Should be a character device, and retain the permissions.
|
||||
require.Equal(t, S_IFCHR|uint32(st.Mode().Perm()), fm)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_FromJsMode(t *testing.T) {
|
||||
t.Run("sticky bit", func(t *testing.T) {
|
||||
jsMode := ToJsMode(0o0755 | fs.ModeSticky)
|
||||
require.Equal(t, 0o0755|S_IFREG|S_ISVTX, jsMode)
|
||||
})
|
||||
}
|
||||
@@ -101,12 +101,12 @@ type jsfsOpen struct{}
|
||||
func (jsfsOpen) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
|
||||
path := resolvePath(ctx, args[0].(string))
|
||||
flags := toUint64(args[1]) // flags are derived from constants like oWRONLY
|
||||
perm := goos.ValueToUint32(args[2])
|
||||
perm := getPerm(ctx, goos.ValueToUint32(args[2]))
|
||||
callback := args[3].(funcWrapper)
|
||||
|
||||
fsc := mod.(*wasm.CallContext).Sys.FS()
|
||||
|
||||
fd, err := fsc.OpenFile(fsc.RootFS(), path, int(flags), fs.FileMode(perm))
|
||||
fd, err := fsc.OpenFile(fsc.RootFS(), path, int(flags), perm)
|
||||
|
||||
return callback.invoke(ctx, mod, goos.RefJsfs, err, fd) // note: error first
|
||||
}
|
||||
@@ -175,21 +175,6 @@ func (jsfsFstat) invoke(ctx context.Context, mod api.Module, args ...interface{}
|
||||
return callback.invoke(ctx, mod, goos.RefJsfs, err, fstat) // note: error first
|
||||
}
|
||||
|
||||
// mode constants from syscall_js.go
|
||||
const (
|
||||
S_IFSOCK = uint32(0o000140000)
|
||||
S_IFLNK = uint32(0o000120000)
|
||||
S_IFREG = uint32(0o000100000)
|
||||
S_IFBLK = uint32(0o000060000)
|
||||
S_IFDIR = uint32(0o000040000)
|
||||
S_IFCHR = uint32(0o000020000)
|
||||
S_IFIFO = uint32(0o000010000)
|
||||
|
||||
S_ISUID = uint32(0o004000)
|
||||
S_ISGID = uint32(0o002000)
|
||||
S_ISVTX = uint32(0o001000)
|
||||
)
|
||||
|
||||
// syscallFstat is like syscall.Fstat
|
||||
func syscallFstat(fsc *internalsys.FSContext, fd uint32) (*jsSt, error) {
|
||||
f, ok := fsc.LookupFile(fd)
|
||||
@@ -209,7 +194,7 @@ func newJsSt(st *platform.Stat_t) *jsSt {
|
||||
ret.isDir = st.Mode.IsDir()
|
||||
ret.dev = st.Dev
|
||||
ret.ino = st.Ino
|
||||
ret.mode = getJsMode(st.Mode)
|
||||
ret.mode = custom.ToJsMode(st.Mode)
|
||||
ret.nlink = uint32(st.Nlink)
|
||||
ret.size = st.Size
|
||||
ret.atimeMs = st.Atim / 1e6
|
||||
@@ -218,46 +203,6 @@ func newJsSt(st *platform.Stat_t) *jsSt {
|
||||
return ret
|
||||
}
|
||||
|
||||
// getJsMode is required because the mode property read in `GOOS=js` is
|
||||
// incompatible with normal go. Particularly the directory flag isn't the same.
|
||||
func getJsMode(fm fs.FileMode) (jsMode uint32) {
|
||||
switch {
|
||||
case fm.IsRegular():
|
||||
jsMode = S_IFREG
|
||||
case fm.IsDir():
|
||||
jsMode = S_IFDIR
|
||||
case fm&fs.ModeSymlink != 0:
|
||||
jsMode = S_IFLNK
|
||||
case fm&fs.ModeDevice != 0:
|
||||
// Unlike ModeDevice and ModeCharDevice, S_IFCHR and S_IFBLK are set
|
||||
// mutually exclusively.
|
||||
if fm&fs.ModeCharDevice != 0 {
|
||||
jsMode = S_IFCHR
|
||||
} else {
|
||||
jsMode = S_IFBLK
|
||||
}
|
||||
case fm&fs.ModeNamedPipe != 0:
|
||||
jsMode = S_IFIFO
|
||||
case fm&fs.ModeSocket != 0:
|
||||
jsMode = S_IFSOCK
|
||||
default: // unknown
|
||||
jsMode = 0
|
||||
}
|
||||
|
||||
jsMode |= uint32(fm & fs.ModePerm)
|
||||
|
||||
if fm&fs.ModeSetgid != 0 {
|
||||
jsMode |= S_ISGID
|
||||
}
|
||||
if fm&fs.ModeSetuid != 0 {
|
||||
jsMode |= S_ISUID
|
||||
}
|
||||
if fm&fs.ModeSticky != 0 {
|
||||
jsMode |= S_ISVTX
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// jsfsClose implements jsFn for syscall.Close
|
||||
type jsfsClose struct{}
|
||||
|
||||
@@ -415,13 +360,6 @@ func (returnSliceOfZero) invoke(context.Context, api.Module, ...interface{}) (in
|
||||
return &objectArray{slice: []interface{}{goos.RefValueZero}}, nil
|
||||
}
|
||||
|
||||
// returnArg0 implements jsFn
|
||||
type returnArg0 struct{}
|
||||
|
||||
func (returnArg0) invoke(_ context.Context, _ api.Module, args ...interface{}) (interface{}, error) {
|
||||
return args[0], nil
|
||||
}
|
||||
|
||||
// processCwd implements jsFn for fs.Open syscall.Getcwd in fs_js.go
|
||||
type processCwd struct{}
|
||||
|
||||
@@ -445,6 +383,19 @@ func (processChdir) invoke(ctx context.Context, mod api.Module, args ...interfac
|
||||
}
|
||||
}
|
||||
|
||||
// processUmask implements jsFn for fs.Open syscall.Umask in fs_js.go
|
||||
type processUmask struct{}
|
||||
|
||||
func (processUmask) invoke(ctx context.Context, _ api.Module, args ...interface{}) (interface{}, error) {
|
||||
mask := goos.ValueToUint32(args[0])
|
||||
|
||||
s := getState(ctx)
|
||||
oldmask := s.umask
|
||||
s.umask = mask
|
||||
|
||||
return oldmask, nil
|
||||
}
|
||||
|
||||
// jsfsMkdir implements implements jsFn for fs.Mkdir
|
||||
//
|
||||
// jsFD /* Int */, err := fsCall("mkdir", path, perm)
|
||||
@@ -452,7 +403,7 @@ type jsfsMkdir struct{}
|
||||
|
||||
func (jsfsMkdir) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
|
||||
path := resolvePath(ctx, args[0].(string))
|
||||
perm := goos.ValueToUint32(args[1])
|
||||
perm := getPerm(ctx, goos.ValueToUint32(args[1]))
|
||||
callback := args[2].(funcWrapper)
|
||||
|
||||
fsc := mod.(*wasm.CallContext).Sys.FS()
|
||||
@@ -464,7 +415,7 @@ func (jsfsMkdir) invoke(ctx context.Context, mod api.Module, args ...interface{}
|
||||
if perm == 0 {
|
||||
perm = 0o0500
|
||||
}
|
||||
if err = root.Mkdir(path, fs.FileMode(perm)); err == nil {
|
||||
if err = root.Mkdir(path, perm); err == nil {
|
||||
fd, err = fsc.OpenFile(root, path, os.O_RDONLY, 0)
|
||||
}
|
||||
|
||||
@@ -544,11 +495,11 @@ type jsfsChmod struct{}
|
||||
|
||||
func (jsfsChmod) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
|
||||
path := resolvePath(ctx, args[0].(string))
|
||||
mode := goos.ValueToUint32(args[1])
|
||||
mode := custom.FromJsMode(goos.ValueToUint32(args[1]), 0)
|
||||
callback := args[2].(funcWrapper)
|
||||
|
||||
fsc := mod.(*wasm.CallContext).Sys.FS()
|
||||
err := fsc.RootFS().Chmod(path, fs.FileMode(mode))
|
||||
err := fsc.RootFS().Chmod(path, mode)
|
||||
|
||||
return jsfsInvoke(ctx, mod, callback, err)
|
||||
}
|
||||
@@ -560,7 +511,7 @@ type jsfsFchmod struct{}
|
||||
|
||||
func (jsfsFchmod) invoke(ctx context.Context, mod api.Module, args ...interface{}) (interface{}, error) {
|
||||
fd := goos.ValueToUint32(args[0])
|
||||
mode := goos.ValueToUint32(args[1])
|
||||
mode := custom.FromJsMode(goos.ValueToUint32(args[1]), 0)
|
||||
callback := args[2].(funcWrapper)
|
||||
|
||||
// Check to see if the file descriptor is available
|
||||
@@ -571,7 +522,7 @@ func (jsfsFchmod) invoke(ctx context.Context, mod api.Module, args ...interface{
|
||||
} else if chmodFile, ok := f.File.(chmodFile); !ok {
|
||||
err = syscall.EBADF // possibly a fake file
|
||||
} else {
|
||||
err = chmodFile.Chmod(fs.FileMode(mode))
|
||||
err = chmodFile.Chmod(mode)
|
||||
}
|
||||
|
||||
return jsfsInvoke(ctx, mod, callback, err)
|
||||
@@ -764,7 +715,7 @@ type jsSt struct {
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (s *jsSt) String() string {
|
||||
return fmt.Sprintf("{isDir=%v,mode=%s,size=%d,mtimeMs=%d}", s.isDir, fs.FileMode(s.mode), s.size, s.mtimeMs)
|
||||
return fmt.Sprintf("{isDir=%v,mode=%s,size=%d,mtimeMs=%d}", s.isDir, custom.FromJsMode(s.mode, 0), s.size, s.mtimeMs)
|
||||
}
|
||||
|
||||
// Get implements the same method as documented on goos.GetFunction
|
||||
@@ -826,3 +777,10 @@ func resolvePath(ctx context.Context, path string) string {
|
||||
func joinPath(dirName, baseName string) string {
|
||||
return path.Join(dirName, baseName)
|
||||
}
|
||||
|
||||
// getPerm converts the input js permissions to a go-compatible one, after
|
||||
// subtracting the current umask.
|
||||
func getPerm(ctx context.Context, perm uint32) fs.FileMode {
|
||||
umask := getState(ctx).umask
|
||||
return custom.FromJsMode(perm, umask)
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package gojs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
)
|
||||
|
||||
func Test_getWasiFiletype_DevNull(t *testing.T) {
|
||||
st, err := os.Stat(os.DevNull)
|
||||
require.NoError(t, err)
|
||||
|
||||
fm := getJsMode(st.Mode())
|
||||
|
||||
// Should be a character device, and retain the permissions.
|
||||
require.Equal(t, S_IFCHR|uint32(st.Mode().Perm()), fm)
|
||||
}
|
||||
@@ -298,11 +298,15 @@ func writeVal(w logging.Writer, name string, val interface{}) {
|
||||
w.WriteString(name) //nolint
|
||||
w.WriteString("_len=") //nolint
|
||||
writeI32(w, uint32(len(b.Unwrap()))) //nolint
|
||||
} else if name == "perm" {
|
||||
w.WriteString("perm=") //nolint
|
||||
perm := goos.ValueToUint32(val)
|
||||
w.WriteString(fs.FileMode(perm).String()) //nolint
|
||||
} else {
|
||||
return
|
||||
}
|
||||
switch name {
|
||||
case "mask", "mode", "oldmask", "perm":
|
||||
w.WriteString(name) //nolint
|
||||
w.WriteByte('=') //nolint
|
||||
perm := custom.FromJsMode(goos.ValueToUint32(val), 0)
|
||||
w.WriteString(perm.String()) //nolint
|
||||
default:
|
||||
w.WriteString(name) //nolint
|
||||
w.WriteByte('=') //nolint
|
||||
w.WriteString(fmt.Sprintf("%v", val)) //nolint
|
||||
|
||||
@@ -24,6 +24,7 @@ func NewState(ctx context.Context) *State {
|
||||
values: values.NewValues(),
|
||||
valueGlobal: newJsGlobal(getRoundTripper(ctx)),
|
||||
cwd: getWorkdir(ctx),
|
||||
umask: 0o0022,
|
||||
_nextCallbackTimeoutID: 1,
|
||||
_scheduledTimeouts: map[uint32]chan bool{},
|
||||
}
|
||||
@@ -190,6 +191,8 @@ type State struct {
|
||||
|
||||
// cwd is initially "/"
|
||||
cwd string
|
||||
// umask is initially 0022
|
||||
umask uint32
|
||||
}
|
||||
|
||||
// Get implements the same method as documented on goos.GetFunction
|
||||
@@ -226,6 +229,7 @@ func (s *State) close() {
|
||||
s._lastEvent = nil
|
||||
s._nextCallbackTimeoutID = 1
|
||||
s.cwd = "/"
|
||||
s.umask = 0o0022
|
||||
}
|
||||
|
||||
func toInt64(arg interface{}) int64 {
|
||||
|
||||
@@ -19,7 +19,7 @@ syscall.Getppid()=0
|
||||
syscall.Getuid()=0
|
||||
syscall.Getgid()=0
|
||||
syscall.Geteuid()=0
|
||||
syscall.Umask(0077)=0o77
|
||||
syscall.Umask(0077)=0o22
|
||||
syscall.Getgroups()=[0]
|
||||
os.FindProcess(1).Pid=1
|
||||
`, stdout)
|
||||
|
||||
Reference in New Issue
Block a user