Black Lives Matter. Support the Equal Justice Initiative.

Source file src/syscall/fs_js.go

Documentation: syscall

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

View as plain text