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:
Crypt Keeper
2023-03-14 08:01:50 +08:00
committed by GitHub
parent c45f0ef062
commit ec6a054119
8 changed files with 166 additions and 97 deletions

View File

@@ -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

View File

@@ -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
}

View 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)
})
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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)