Newer
Older
// 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.
"sync"
"syscall/js"
)
// Provided by package runtime.
func now() (sec int64, nsec int32)
var jsProcess = js.Global().Get("process")
var jsPath = js.Global().Get("path")
var jsFS = js.Global().Get("fs")
var uint8Array = js.Global().Get("Uint8Array")
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()
nodeDIRECTORY = constants.Get("O_DIRECTORY").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{
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
}
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")
if openmode&O_DIRECTORY != 0 {
flags |= nodeDIRECTORY
}
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()
}
}
path = jsPath.Call("resolve", path).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 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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
}
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 {
Constantin Konstantinidis
committed
// UTIME_OMIT value must match internal/syscall/unix/at_js.go
const UTIME_OMIT = -0x2
if err := checkPath(path); err != nil {
return err
}
if len(ts) != 2 {
return EINVAL
}
atime := ts[0].Sec
mtime := ts[1].Sec
Constantin Konstantinidis
committed
if atime == UTIME_OMIT || mtime == UTIME_OMIT {
var st Stat_t
if err := Stat(path, &st); err != nil {
return err
}
if atime == UTIME_OMIT {
atime = st.Atime
}
if mtime == UTIME_OMIT {
mtime = st.Mtime
}
}
_, 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)
}
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)
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)
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 {
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 ...any) (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) any {
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
})
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))