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

View as plain text