mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
The name "Callback" does not fit to all use cases of js.Callback. This commit changes its name to Func. Accordingly NewCallback gets renamed to FuncOf, which matches ValueOf and TypedArrayOf. The package syscall/js is currently exempt from Go's compatibility promise and js.Callback is already affected by a breaking change in this release cycle. See #28711 for details. Fixes #28711 Change-Id: I2c380970c3822bed6a3893909672c15d0cbe9da3 Reviewed-on: https://go-review.googlesource.com/c/153559 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
528 lines
9.7 KiB
Go
528 lines
9.7 KiB
Go
// Copyright 2018 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build js,wasm
|
|
|
|
package syscall
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"sync"
|
|
"syscall/js"
|
|
)
|
|
|
|
// Provided by package runtime.
|
|
func now() (sec int64, nsec int32)
|
|
|
|
var jsProcess = js.Global().Get("process")
|
|
var jsFS = js.Global().Get("fs")
|
|
var constants = jsFS.Get("constants")
|
|
|
|
var (
|
|
nodeWRONLY = constants.Get("O_WRONLY").Int()
|
|
nodeRDWR = constants.Get("O_RDWR").Int()
|
|
nodeCREATE = constants.Get("O_CREAT").Int()
|
|
nodeTRUNC = constants.Get("O_TRUNC").Int()
|
|
nodeAPPEND = constants.Get("O_APPEND").Int()
|
|
nodeEXCL = constants.Get("O_EXCL").Int()
|
|
)
|
|
|
|
type jsFile struct {
|
|
path string
|
|
entries []string
|
|
pos int64
|
|
seeked bool
|
|
}
|
|
|
|
var filesMu sync.Mutex
|
|
var files = map[int]*jsFile{
|
|
0: &jsFile{},
|
|
1: &jsFile{},
|
|
2: &jsFile{},
|
|
}
|
|
|
|
func fdToFile(fd int) (*jsFile, error) {
|
|
filesMu.Lock()
|
|
f, ok := files[fd]
|
|
filesMu.Unlock()
|
|
if !ok {
|
|
return nil, EBADF
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
func Open(path string, openmode int, perm uint32) (int, error) {
|
|
if err := checkPath(path); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
flags := 0
|
|
if openmode&O_WRONLY != 0 {
|
|
flags |= nodeWRONLY
|
|
}
|
|
if openmode&O_RDWR != 0 {
|
|
flags |= nodeRDWR
|
|
}
|
|
if openmode&O_CREATE != 0 {
|
|
flags |= nodeCREATE
|
|
}
|
|
if openmode&O_TRUNC != 0 {
|
|
flags |= nodeTRUNC
|
|
}
|
|
if openmode&O_APPEND != 0 {
|
|
flags |= nodeAPPEND
|
|
}
|
|
if openmode&O_EXCL != 0 {
|
|
flags |= nodeEXCL
|
|
}
|
|
if openmode&O_SYNC != 0 {
|
|
return 0, errors.New("syscall.Open: O_SYNC is not supported by js/wasm")
|
|
}
|
|
|
|
jsFD, err := fsCall("open", path, flags, perm)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
fd := jsFD.Int()
|
|
|
|
var entries []string
|
|
if stat, err := fsCall("fstat", fd); err == nil && stat.Call("isDirectory").Bool() {
|
|
dir, err := fsCall("readdir", path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
entries = make([]string, dir.Length())
|
|
for i := range entries {
|
|
entries[i] = dir.Index(i).String()
|
|
}
|
|
}
|
|
|
|
f := &jsFile{
|
|
path: path,
|
|
entries: entries,
|
|
}
|
|
filesMu.Lock()
|
|
files[fd] = f
|
|
filesMu.Unlock()
|
|
return fd, nil
|
|
}
|
|
|
|
func Close(fd int) error {
|
|
filesMu.Lock()
|
|
delete(files, fd)
|
|
filesMu.Unlock()
|
|
_, err := fsCall("close", fd)
|
|
return err
|
|
}
|
|
|
|
func CloseOnExec(fd int) {
|
|
// nothing to do - no exec
|
|
}
|
|
|
|
func Mkdir(path string, perm uint32) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
_, err := fsCall("mkdir", path, perm)
|
|
return err
|
|
}
|
|
|
|
func ReadDirent(fd int, buf []byte) (int, error) {
|
|
f, err := fdToFile(fd)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if f.entries == nil {
|
|
return 0, EINVAL
|
|
}
|
|
|
|
n := 0
|
|
for len(f.entries) > 0 {
|
|
entry := f.entries[0]
|
|
l := 2 + len(entry)
|
|
if l > len(buf) {
|
|
break
|
|
}
|
|
buf[0] = byte(l)
|
|
buf[1] = byte(l >> 8)
|
|
copy(buf[2:], entry)
|
|
buf = buf[l:]
|
|
n += l
|
|
f.entries = f.entries[1:]
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
func setStat(st *Stat_t, jsSt js.Value) {
|
|
st.Dev = int64(jsSt.Get("dev").Int())
|
|
st.Ino = uint64(jsSt.Get("ino").Int())
|
|
st.Mode = uint32(jsSt.Get("mode").Int())
|
|
st.Nlink = uint32(jsSt.Get("nlink").Int())
|
|
st.Uid = uint32(jsSt.Get("uid").Int())
|
|
st.Gid = uint32(jsSt.Get("gid").Int())
|
|
st.Rdev = int64(jsSt.Get("rdev").Int())
|
|
st.Size = int64(jsSt.Get("size").Int())
|
|
st.Blksize = int32(jsSt.Get("blksize").Int())
|
|
st.Blocks = int32(jsSt.Get("blocks").Int())
|
|
atime := int64(jsSt.Get("atimeMs").Int())
|
|
st.Atime = atime / 1000
|
|
st.AtimeNsec = (atime % 1000) * 1000000
|
|
mtime := int64(jsSt.Get("mtimeMs").Int())
|
|
st.Mtime = mtime / 1000
|
|
st.MtimeNsec = (mtime % 1000) * 1000000
|
|
ctime := int64(jsSt.Get("ctimeMs").Int())
|
|
st.Ctime = ctime / 1000
|
|
st.CtimeNsec = (ctime % 1000) * 1000000
|
|
}
|
|
|
|
func Stat(path string, st *Stat_t) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
jsSt, err := fsCall("stat", path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
setStat(st, jsSt)
|
|
return nil
|
|
}
|
|
|
|
func Lstat(path string, st *Stat_t) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
jsSt, err := fsCall("lstat", path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
setStat(st, jsSt)
|
|
return nil
|
|
}
|
|
|
|
func Fstat(fd int, st *Stat_t) error {
|
|
jsSt, err := fsCall("fstat", fd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
setStat(st, jsSt)
|
|
return nil
|
|
}
|
|
|
|
func Unlink(path string) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
_, err := fsCall("unlink", path)
|
|
return err
|
|
}
|
|
|
|
func Rmdir(path string) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
_, err := fsCall("rmdir", path)
|
|
return err
|
|
}
|
|
|
|
func Chmod(path string, mode uint32) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
_, err := fsCall("chmod", path, mode)
|
|
return err
|
|
}
|
|
|
|
func Fchmod(fd int, mode uint32) error {
|
|
_, err := fsCall("fchmod", fd, mode)
|
|
return err
|
|
}
|
|
|
|
func Chown(path string, uid, gid int) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
return ENOSYS
|
|
}
|
|
|
|
func Fchown(fd int, uid, gid int) error {
|
|
return ENOSYS
|
|
}
|
|
|
|
func Lchown(path string, uid, gid int) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
return ENOSYS
|
|
}
|
|
|
|
func UtimesNano(path string, ts []Timespec) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
if len(ts) != 2 {
|
|
return EINVAL
|
|
}
|
|
atime := ts[0].Sec
|
|
mtime := ts[1].Sec
|
|
_, err := fsCall("utimes", path, atime, mtime)
|
|
return err
|
|
}
|
|
|
|
func Rename(from, to string) error {
|
|
if err := checkPath(from); err != nil {
|
|
return err
|
|
}
|
|
if err := checkPath(to); err != nil {
|
|
return err
|
|
}
|
|
_, err := fsCall("rename", from, to)
|
|
return err
|
|
}
|
|
|
|
func Truncate(path string, length int64) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
_, err := fsCall("truncate", path, length)
|
|
return err
|
|
}
|
|
|
|
func Ftruncate(fd int, length int64) error {
|
|
_, err := fsCall("ftruncate", fd, length)
|
|
return err
|
|
}
|
|
|
|
func Getcwd(buf []byte) (n int, err error) {
|
|
defer recoverErr(&err)
|
|
cwd := jsProcess.Call("cwd").String()
|
|
n = copy(buf, cwd)
|
|
return
|
|
}
|
|
|
|
func Chdir(path string) (err error) {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
defer recoverErr(&err)
|
|
jsProcess.Call("chdir", path)
|
|
return
|
|
}
|
|
|
|
func Fchdir(fd int) error {
|
|
f, err := fdToFile(fd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return Chdir(f.path)
|
|
}
|
|
|
|
func Readlink(path string, buf []byte) (n int, err error) {
|
|
if err := checkPath(path); err != nil {
|
|
return 0, err
|
|
}
|
|
dst, err := fsCall("readlink", path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
n = copy(buf, dst.String())
|
|
return n, nil
|
|
}
|
|
|
|
func Link(path, link string) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
if err := checkPath(link); err != nil {
|
|
return err
|
|
}
|
|
_, err := fsCall("link", path, link)
|
|
return err
|
|
}
|
|
|
|
func Symlink(path, link string) error {
|
|
if err := checkPath(path); err != nil {
|
|
return err
|
|
}
|
|
if err := checkPath(link); err != nil {
|
|
return err
|
|
}
|
|
_, err := fsCall("symlink", path, link)
|
|
return err
|
|
}
|
|
|
|
func Fsync(fd int) error {
|
|
_, err := fsCall("fsync", fd)
|
|
return err
|
|
}
|
|
|
|
func Read(fd int, b []byte) (int, error) {
|
|
f, err := fdToFile(fd)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if f.seeked {
|
|
n, err := Pread(fd, b, f.pos)
|
|
f.pos += int64(n)
|
|
return n, err
|
|
}
|
|
|
|
a := js.TypedArrayOf(b)
|
|
n, err := fsCall("read", fd, a, 0, len(b), nil)
|
|
a.Release()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
n2 := n.Int()
|
|
f.pos += int64(n2)
|
|
return n2, err
|
|
}
|
|
|
|
func Write(fd int, b []byte) (int, error) {
|
|
f, err := fdToFile(fd)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if f.seeked {
|
|
n, err := Pwrite(fd, b, f.pos)
|
|
f.pos += int64(n)
|
|
return n, err
|
|
}
|
|
|
|
a := js.TypedArrayOf(b)
|
|
n, err := fsCall("write", fd, a, 0, len(b), nil)
|
|
a.Release()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
n2 := n.Int()
|
|
f.pos += int64(n2)
|
|
return n2, err
|
|
}
|
|
|
|
func Pread(fd int, b []byte, offset int64) (int, error) {
|
|
a := js.TypedArrayOf(b)
|
|
n, err := fsCall("read", fd, a, 0, len(b), offset)
|
|
a.Release()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return n.Int(), nil
|
|
}
|
|
|
|
func Pwrite(fd int, b []byte, offset int64) (int, error) {
|
|
a := js.TypedArrayOf(b)
|
|
n, err := fsCall("write", fd, a, 0, len(b), offset)
|
|
a.Release()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return n.Int(), nil
|
|
}
|
|
|
|
func Seek(fd int, offset int64, whence int) (int64, error) {
|
|
f, err := fdToFile(fd)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
var newPos int64
|
|
switch whence {
|
|
case io.SeekStart:
|
|
newPos = offset
|
|
case io.SeekCurrent:
|
|
newPos = f.pos + offset
|
|
case io.SeekEnd:
|
|
var st Stat_t
|
|
if err := Fstat(fd, &st); err != nil {
|
|
return 0, err
|
|
}
|
|
newPos = st.Size + offset
|
|
default:
|
|
return 0, errnoErr(EINVAL)
|
|
}
|
|
|
|
if newPos < 0 {
|
|
return 0, errnoErr(EINVAL)
|
|
}
|
|
|
|
f.seeked = true
|
|
f.pos = newPos
|
|
return newPos, nil
|
|
}
|
|
|
|
func Dup(fd int) (int, error) {
|
|
return 0, ENOSYS
|
|
}
|
|
|
|
func Dup2(fd, newfd int) error {
|
|
return ENOSYS
|
|
}
|
|
|
|
func Pipe(fd []int) error {
|
|
return ENOSYS
|
|
}
|
|
|
|
func fsCall(name string, args ...interface{}) (js.Value, error) {
|
|
type callResult struct {
|
|
val js.Value
|
|
err error
|
|
}
|
|
|
|
c := make(chan callResult, 1)
|
|
jsFS.Call(name, append(args, js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
var res callResult
|
|
|
|
if len(args) >= 1 { // on Node.js 8, fs.utimes calls the callback without any arguments
|
|
if jsErr := args[0]; jsErr != js.Null() {
|
|
res.err = mapJSError(jsErr)
|
|
}
|
|
}
|
|
|
|
res.val = js.Undefined()
|
|
if len(args) >= 2 {
|
|
res.val = args[1]
|
|
}
|
|
|
|
c <- res
|
|
return nil
|
|
}))...)
|
|
res := <-c
|
|
return res.val, res.err
|
|
}
|
|
|
|
// checkPath checks that the path is not empty and that it contains no null characters.
|
|
func checkPath(path string) error {
|
|
if path == "" {
|
|
return EINVAL
|
|
}
|
|
for i := 0; i < len(path); i++ {
|
|
if path[i] == '\x00' {
|
|
return EINVAL
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func recoverErr(errPtr *error) {
|
|
if err := recover(); err != nil {
|
|
jsErr, ok := err.(js.Error)
|
|
if !ok {
|
|
panic(err)
|
|
}
|
|
*errPtr = mapJSError(jsErr.Value)
|
|
}
|
|
}
|
|
|
|
// mapJSError maps an error given by Node.js to the appropriate Go error
|
|
func mapJSError(jsErr js.Value) error {
|
|
errno, ok := errnoByCode[jsErr.Get("code").String()]
|
|
if !ok {
|
|
panic(jsErr)
|
|
}
|
|
return errnoErr(Errno(errno))
|
|
}
|