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

View as plain text