Source file src/syscall/fs_js.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build js && wasm
     6  
     7  package syscall
     8  
     9  import (
    10  	"errors"
    11  	"sync"
    12  	"syscall/js"
    13  )
    14  
    15  // Provided by package runtime.
    16  func now() (sec int64, nsec int32)
    17  
    18  var jsProcess = js.Global().Get("process")
    19  var jsFS = js.Global().Get("fs")
    20  var constants = jsFS.Get("constants")
    21  
    22  var uint8Array = js.Global().Get("Uint8Array")
    23  
    24  var (
    25  	nodeWRONLY = constants.Get("O_WRONLY").Int()
    26  	nodeRDWR   = constants.Get("O_RDWR").Int()
    27  	nodeCREATE = constants.Get("O_CREAT").Int()
    28  	nodeTRUNC  = constants.Get("O_TRUNC").Int()
    29  	nodeAPPEND = constants.Get("O_APPEND").Int()
    30  	nodeEXCL   = constants.Get("O_EXCL").Int()
    31  )
    32  
    33  type jsFile struct {
    34  	path    string
    35  	entries []string
    36  	dirIdx  int // entries[:dirIdx] have already been returned in ReadDirent
    37  	pos     int64
    38  	seeked  bool
    39  }
    40  
    41  var filesMu sync.Mutex
    42  var files = map[int]*jsFile{
    43  	0: {},
    44  	1: {},
    45  	2: {},
    46  }
    47  
    48  func fdToFile(fd int) (*jsFile, error) {
    49  	filesMu.Lock()
    50  	f, ok := files[fd]
    51  	filesMu.Unlock()
    52  	if !ok {
    53  		return nil, EBADF
    54  	}
    55  	return f, nil
    56  }
    57  
    58  func Open(path string, openmode int, perm uint32) (int, error) {
    59  	if err := checkPath(path); err != nil {
    60  		return 0, err
    61  	}
    62  
    63  	flags := 0
    64  	if openmode&O_WRONLY != 0 {
    65  		flags |= nodeWRONLY
    66  	}
    67  	if openmode&O_RDWR != 0 {
    68  		flags |= nodeRDWR
    69  	}
    70  	if openmode&O_CREATE != 0 {
    71  		flags |= nodeCREATE
    72  	}
    73  	if openmode&O_TRUNC != 0 {
    74  		flags |= nodeTRUNC
    75  	}
    76  	if openmode&O_APPEND != 0 {
    77  		flags |= nodeAPPEND
    78  	}
    79  	if openmode&O_EXCL != 0 {
    80  		flags |= nodeEXCL
    81  	}
    82  	if openmode&O_SYNC != 0 {
    83  		return 0, errors.New("syscall.Open: O_SYNC is not supported by js/wasm")
    84  	}
    85  
    86  	jsFD, err := fsCall("open", path, flags, perm)
    87  	if err != nil {
    88  		return 0, err
    89  	}
    90  	fd := jsFD.Int()
    91  
    92  	var entries []string
    93  	if stat, err := fsCall("fstat", fd); err == nil && stat.Call("isDirectory").Bool() {
    94  		dir, err := fsCall("readdir", path)
    95  		if err != nil {
    96  			return 0, err
    97  		}
    98  		entries = make([]string, dir.Length())
    99  		for i := range entries {
   100  			entries[i] = dir.Index(i).String()
   101  		}
   102  	}
   103  
   104  	if path[0] != '/' {
   105  		cwd := jsProcess.Call("cwd").String()
   106  		path = cwd + "/" + path
   107  	}
   108  	f := &jsFile{
   109  		path:    path,
   110  		entries: entries,
   111  	}
   112  	filesMu.Lock()
   113  	files[fd] = f
   114  	filesMu.Unlock()
   115  	return fd, nil
   116  }
   117  
   118  func Close(fd int) error {
   119  	filesMu.Lock()
   120  	delete(files, fd)
   121  	filesMu.Unlock()
   122  	_, err := fsCall("close", fd)
   123  	return err
   124  }
   125  
   126  func CloseOnExec(fd int) {
   127  	// nothing to do - no exec
   128  }
   129  
   130  func Mkdir(path string, perm uint32) error {
   131  	if err := checkPath(path); err != nil {
   132  		return err
   133  	}
   134  	_, err := fsCall("mkdir", path, perm)
   135  	return err
   136  }
   137  
   138  func ReadDirent(fd int, buf []byte) (int, error) {
   139  	f, err := fdToFile(fd)
   140  	if err != nil {
   141  		return 0, err
   142  	}
   143  	if f.entries == nil {
   144  		return 0, EINVAL
   145  	}
   146  
   147  	n := 0
   148  	for f.dirIdx < len(f.entries) {
   149  		entry := f.entries[f.dirIdx]
   150  		l := 2 + len(entry)
   151  		if l > len(buf) {
   152  			break
   153  		}
   154  		buf[0] = byte(l)
   155  		buf[1] = byte(l >> 8)
   156  		copy(buf[2:], entry)
   157  		buf = buf[l:]
   158  		n += l
   159  		f.dirIdx++
   160  	}
   161  
   162  	return n, nil
   163  }
   164  
   165  func setStat(st *Stat_t, jsSt js.Value) {
   166  	st.Dev = int64(jsSt.Get("dev").Int())
   167  	st.Ino = uint64(jsSt.Get("ino").Int())
   168  	st.Mode = uint32(jsSt.Get("mode").Int())
   169  	st.Nlink = uint32(jsSt.Get("nlink").Int())
   170  	st.Uid = uint32(jsSt.Get("uid").Int())
   171  	st.Gid = uint32(jsSt.Get("gid").Int())
   172  	st.Rdev = int64(jsSt.Get("rdev").Int())
   173  	st.Size = int64(jsSt.Get("size").Int())
   174  	st.Blksize = int32(jsSt.Get("blksize").Int())
   175  	st.Blocks = int32(jsSt.Get("blocks").Int())
   176  	atime := int64(jsSt.Get("atimeMs").Int())
   177  	st.Atime = atime / 1000
   178  	st.AtimeNsec = (atime % 1000) * 1000000
   179  	mtime := int64(jsSt.Get("mtimeMs").Int())
   180  	st.Mtime = mtime / 1000
   181  	st.MtimeNsec = (mtime % 1000) * 1000000
   182  	ctime := int64(jsSt.Get("ctimeMs").Int())
   183  	st.Ctime = ctime / 1000
   184  	st.CtimeNsec = (ctime % 1000) * 1000000
   185  }
   186  
   187  func Stat(path string, st *Stat_t) error {
   188  	if err := checkPath(path); err != nil {
   189  		return err
   190  	}
   191  	jsSt, err := fsCall("stat", path)
   192  	if err != nil {
   193  		return err
   194  	}
   195  	setStat(st, jsSt)
   196  	return nil
   197  }
   198  
   199  func Lstat(path string, st *Stat_t) error {
   200  	if err := checkPath(path); err != nil {
   201  		return err
   202  	}
   203  	jsSt, err := fsCall("lstat", path)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	setStat(st, jsSt)
   208  	return nil
   209  }
   210  
   211  func Fstat(fd int, st *Stat_t) error {
   212  	jsSt, err := fsCall("fstat", fd)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	setStat(st, jsSt)
   217  	return nil
   218  }
   219  
   220  func Unlink(path string) error {
   221  	if err := checkPath(path); err != nil {
   222  		return err
   223  	}
   224  	_, err := fsCall("unlink", path)
   225  	return err
   226  }
   227  
   228  func Rmdir(path string) error {
   229  	if err := checkPath(path); err != nil {
   230  		return err
   231  	}
   232  	_, err := fsCall("rmdir", path)
   233  	return err
   234  }
   235  
   236  func Chmod(path string, mode uint32) error {
   237  	if err := checkPath(path); err != nil {
   238  		return err
   239  	}
   240  	_, err := fsCall("chmod", path, mode)
   241  	return err
   242  }
   243  
   244  func Fchmod(fd int, mode uint32) error {
   245  	_, err := fsCall("fchmod", fd, mode)
   246  	return err
   247  }
   248  
   249  func Chown(path string, uid, gid int) error {
   250  	if err := checkPath(path); err != nil {
   251  		return err
   252  	}
   253  	_, err := fsCall("chown", path, uint32(uid), uint32(gid))
   254  	return err
   255  }
   256  
   257  func Fchown(fd int, uid, gid int) error {
   258  	_, err := fsCall("fchown", fd, uint32(uid), uint32(gid))
   259  	return err
   260  }
   261  
   262  func Lchown(path string, uid, gid int) error {
   263  	if err := checkPath(path); err != nil {
   264  		return err
   265  	}
   266  	if jsFS.Get("lchown").IsUndefined() {
   267  		// fs.lchown is unavailable on Linux until Node.js 10.6.0
   268  		// TODO(neelance): remove when we require at least this Node.js version
   269  		return ENOSYS
   270  	}
   271  	_, err := fsCall("lchown", path, uint32(uid), uint32(gid))
   272  	return err
   273  }
   274  
   275  func UtimesNano(path string, ts []Timespec) error {
   276  	// UTIME_OMIT value must match internal/syscall/unix/at_js.go
   277  	const UTIME_OMIT = -0x2
   278  	if err := checkPath(path); err != nil {
   279  		return err
   280  	}
   281  	if len(ts) != 2 {
   282  		return EINVAL
   283  	}
   284  	atime := ts[0].Sec
   285  	mtime := ts[1].Sec
   286  	if atime == UTIME_OMIT || mtime == UTIME_OMIT {
   287  		var st Stat_t
   288  		if err := Stat(path, &st); err != nil {
   289  			return err
   290  		}
   291  		if atime == UTIME_OMIT {
   292  			atime = st.Atime
   293  		}
   294  		if mtime == UTIME_OMIT {
   295  			mtime = st.Mtime
   296  		}
   297  	}
   298  	_, err := fsCall("utimes", path, atime, mtime)
   299  	return err
   300  }
   301  
   302  func Rename(from, to string) error {
   303  	if err := checkPath(from); err != nil {
   304  		return err
   305  	}
   306  	if err := checkPath(to); err != nil {
   307  		return err
   308  	}
   309  	_, err := fsCall("rename", from, to)
   310  	return err
   311  }
   312  
   313  func Truncate(path string, length int64) error {
   314  	if err := checkPath(path); err != nil {
   315  		return err
   316  	}
   317  	_, err := fsCall("truncate", path, length)
   318  	return err
   319  }
   320  
   321  func Ftruncate(fd int, length int64) error {
   322  	_, err := fsCall("ftruncate", fd, length)
   323  	return err
   324  }
   325  
   326  func Getcwd(buf []byte) (n int, err error) {
   327  	defer recoverErr(&err)
   328  	cwd := jsProcess.Call("cwd").String()
   329  	n = copy(buf, cwd)
   330  	return
   331  }
   332  
   333  func Chdir(path string) (err error) {
   334  	if err := checkPath(path); err != nil {
   335  		return err
   336  	}
   337  	defer recoverErr(&err)
   338  	jsProcess.Call("chdir", path)
   339  	return
   340  }
   341  
   342  func Fchdir(fd int) error {
   343  	f, err := fdToFile(fd)
   344  	if err != nil {
   345  		return err
   346  	}
   347  	return Chdir(f.path)
   348  }
   349  
   350  func Readlink(path string, buf []byte) (n int, err error) {
   351  	if err := checkPath(path); err != nil {
   352  		return 0, err
   353  	}
   354  	dst, err := fsCall("readlink", path)
   355  	if err != nil {
   356  		return 0, err
   357  	}
   358  	n = copy(buf, dst.String())
   359  	return n, nil
   360  }
   361  
   362  func Link(path, link string) error {
   363  	if err := checkPath(path); err != nil {
   364  		return err
   365  	}
   366  	if err := checkPath(link); err != nil {
   367  		return err
   368  	}
   369  	_, err := fsCall("link", path, link)
   370  	return err
   371  }
   372  
   373  func Symlink(path, link string) error {
   374  	if err := checkPath(path); err != nil {
   375  		return err
   376  	}
   377  	if err := checkPath(link); err != nil {
   378  		return err
   379  	}
   380  	_, err := fsCall("symlink", path, link)
   381  	return err
   382  }
   383  
   384  func Fsync(fd int) error {
   385  	_, err := fsCall("fsync", fd)
   386  	return err
   387  }
   388  
   389  func Read(fd int, b []byte) (int, error) {
   390  	f, err := fdToFile(fd)
   391  	if err != nil {
   392  		return 0, err
   393  	}
   394  
   395  	if f.seeked {
   396  		n, err := Pread(fd, b, f.pos)
   397  		f.pos += int64(n)
   398  		return n, err
   399  	}
   400  
   401  	buf := uint8Array.New(len(b))
   402  	n, err := fsCall("read", fd, buf, 0, len(b), nil)
   403  	if err != nil {
   404  		return 0, err
   405  	}
   406  	js.CopyBytesToGo(b, buf)
   407  
   408  	n2 := n.Int()
   409  	f.pos += int64(n2)
   410  	return n2, err
   411  }
   412  
   413  func Write(fd int, b []byte) (int, error) {
   414  	f, err := fdToFile(fd)
   415  	if err != nil {
   416  		return 0, err
   417  	}
   418  
   419  	if f.seeked {
   420  		n, err := Pwrite(fd, b, f.pos)
   421  		f.pos += int64(n)
   422  		return n, err
   423  	}
   424  
   425  	if faketime && (fd == 1 || fd == 2) {
   426  		n := faketimeWrite(fd, b)
   427  		if n < 0 {
   428  			return 0, errnoErr(Errno(-n))
   429  		}
   430  		return n, nil
   431  	}
   432  
   433  	buf := uint8Array.New(len(b))
   434  	js.CopyBytesToJS(buf, b)
   435  	n, err := fsCall("write", fd, buf, 0, len(b), nil)
   436  	if err != nil {
   437  		return 0, err
   438  	}
   439  	n2 := n.Int()
   440  	f.pos += int64(n2)
   441  	return n2, err
   442  }
   443  
   444  func Pread(fd int, b []byte, offset int64) (int, error) {
   445  	buf := uint8Array.New(len(b))
   446  	n, err := fsCall("read", fd, buf, 0, len(b), offset)
   447  	if err != nil {
   448  		return 0, err
   449  	}
   450  	js.CopyBytesToGo(b, buf)
   451  	return n.Int(), nil
   452  }
   453  
   454  func Pwrite(fd int, b []byte, offset int64) (int, error) {
   455  	buf := uint8Array.New(len(b))
   456  	js.CopyBytesToJS(buf, b)
   457  	n, err := fsCall("write", fd, buf, 0, len(b), offset)
   458  	if err != nil {
   459  		return 0, err
   460  	}
   461  	return n.Int(), nil
   462  }
   463  
   464  func Seek(fd int, offset int64, whence int) (int64, error) {
   465  	f, err := fdToFile(fd)
   466  	if err != nil {
   467  		return 0, err
   468  	}
   469  
   470  	var newPos int64
   471  	switch whence {
   472  	case 0:
   473  		newPos = offset
   474  	case 1:
   475  		newPos = f.pos + offset
   476  	case 2:
   477  		var st Stat_t
   478  		if err := Fstat(fd, &st); err != nil {
   479  			return 0, err
   480  		}
   481  		newPos = st.Size + offset
   482  	default:
   483  		return 0, errnoErr(EINVAL)
   484  	}
   485  
   486  	if newPos < 0 {
   487  		return 0, errnoErr(EINVAL)
   488  	}
   489  
   490  	f.seeked = true
   491  	f.dirIdx = 0 // Reset directory read position. See issue 35767.
   492  	f.pos = newPos
   493  	return newPos, nil
   494  }
   495  
   496  func Dup(fd int) (int, error) {
   497  	return 0, ENOSYS
   498  }
   499  
   500  func Dup2(fd, newfd int) error {
   501  	return ENOSYS
   502  }
   503  
   504  func Pipe(fd []int) error {
   505  	return ENOSYS
   506  }
   507  
   508  func fsCall(name string, args ...any) (js.Value, error) {
   509  	type callResult struct {
   510  		val js.Value
   511  		err error
   512  	}
   513  
   514  	c := make(chan callResult, 1)
   515  	f := js.FuncOf(func(this js.Value, args []js.Value) any {
   516  		var res callResult
   517  
   518  		if len(args) >= 1 { // on Node.js 8, fs.utimes calls the callback without any arguments
   519  			if jsErr := args[0]; !jsErr.IsNull() {
   520  				res.err = mapJSError(jsErr)
   521  			}
   522  		}
   523  
   524  		res.val = js.Undefined()
   525  		if len(args) >= 2 {
   526  			res.val = args[1]
   527  		}
   528  
   529  		c <- res
   530  		return nil
   531  	})
   532  	defer f.Release()
   533  	jsFS.Call(name, append(args, f)...)
   534  	res := <-c
   535  	return res.val, res.err
   536  }
   537  
   538  // checkPath checks that the path is not empty and that it contains no null characters.
   539  func checkPath(path string) error {
   540  	if path == "" {
   541  		return EINVAL
   542  	}
   543  	for i := 0; i < len(path); i++ {
   544  		if path[i] == '\x00' {
   545  			return EINVAL
   546  		}
   547  	}
   548  	return nil
   549  }
   550  
   551  func recoverErr(errPtr *error) {
   552  	if err := recover(); err != nil {
   553  		jsErr, ok := err.(js.Error)
   554  		if !ok {
   555  			panic(err)
   556  		}
   557  		*errPtr = mapJSError(jsErr.Value)
   558  	}
   559  }
   560  
   561  // mapJSError maps an error given by Node.js to the appropriate Go error.
   562  func mapJSError(jsErr js.Value) error {
   563  	errno, ok := errnoByCode[jsErr.Get("code").String()]
   564  	if !ok {
   565  		panic(jsErr)
   566  	}
   567  	return errnoErr(Errno(errno))
   568  }
   569  

View as plain text