Initial commit: Go 1.23 release state
This commit is contained in:
568
src/syscall/fs_js.go
Normal file
568
src/syscall/fs_js.go
Normal file
@@ -0,0 +1,568 @@
|
||||
// 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.
|
||||
|
||||
//go: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 {
|
||||
// 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
|
||||
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)
|
||||
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 ...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
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user