diff options
Diffstat (limited to 'src/syscall/fs_js.go')
-rw-r--r-- | src/syscall/fs_js.go | 554 |
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)) +} |