summaryrefslogtreecommitdiffstats
path: root/src/syscall/fs_js.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/syscall/fs_js.go')
-rw-r--r--src/syscall/fs_js.go554
1 files changed, 554 insertions, 0 deletions
diff --git a/src/syscall/fs_js.go b/src/syscall/fs_js.go
new file mode 100644
index 0000000..673feea
--- /dev/null
+++ b/src/syscall/fs_js.go
@@ -0,0 +1,554 @@
+// 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"
+ "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 uint8Array = js.Global().Get("Uint8Array")
+
+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
+ dirIdx int // entries[:dirIdx] have already been returned in ReadDirent
+ pos int64
+ seeked bool
+}
+
+var filesMu sync.Mutex
+var files = map[int]*jsFile{
+ 0: {},
+ 1: {},
+ 2: {},
+}
+
+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()
+ }
+ }
+
+ if path[0] != '/' {
+ cwd := jsProcess.Call("cwd").String()
+ path = cwd + "/" + path
+ }
+ 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 f.dirIdx < len(f.entries) {
+ entry := f.entries[f.dirIdx]
+ 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.dirIdx++
+ }
+
+ 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
+ }
+ _, err := fsCall("chown", path, uint32(uid), uint32(gid))
+ return err
+}
+
+func Fchown(fd int, uid, gid int) error {
+ _, err := fsCall("fchown", fd, uint32(uid), uint32(gid))
+ return err
+}
+
+func Lchown(path string, uid, gid int) error {
+ if err := checkPath(path); err != nil {
+ return err
+ }
+ if jsFS.Get("lchown").IsUndefined() {
+ // fs.lchown is unavailable on Linux until Node.js 10.6.0
+ // TODO(neelance): remove when we require at least this Node.js version
+ return ENOSYS
+ }
+ _, err := fsCall("lchown", path, uint32(uid), uint32(gid))
+ return err
+}
+
+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
+ }
+
+ buf := uint8Array.New(len(b))
+ n, err := fsCall("read", fd, buf, 0, len(b), nil)
+ if err != nil {
+ return 0, err
+ }
+ js.CopyBytesToGo(b, buf)
+
+ 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
+ }
+
+ if faketime && (fd == 1 || fd == 2) {
+ n := faketimeWrite(fd, b)
+ if n < 0 {
+ return 0, errnoErr(Errno(-n))
+ }
+ return n, nil
+ }
+
+ buf := uint8Array.New(len(b))
+ js.CopyBytesToJS(buf, b)
+ n, err := fsCall("write", fd, buf, 0, len(b), nil)
+ 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) {
+ buf := uint8Array.New(len(b))
+ n, err := fsCall("read", fd, buf, 0, len(b), offset)
+ if err != nil {
+ return 0, err
+ }
+ js.CopyBytesToGo(b, buf)
+ return n.Int(), nil
+}
+
+func Pwrite(fd int, b []byte, offset int64) (int, error) {
+ buf := uint8Array.New(len(b))
+ js.CopyBytesToJS(buf, b)
+ n, err := fsCall("write", fd, buf, 0, len(b), offset)
+ 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 0:
+ newPos = offset
+ case 1:
+ newPos = f.pos + offset
+ case 2:
+ 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.dirIdx = 0 // Reset directory read position. See issue 35767.
+ 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)
+ f := 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.IsNull() {
+ res.err = mapJSError(jsErr)
+ }
+ }
+
+ res.val = js.Undefined()
+ if len(args) >= 2 {
+ res.val = args[1]
+ }
+
+ c <- res
+ return nil
+ })
+ defer f.Release()
+ jsFS.Call(name, append(args, f)...)
+ 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))
+}