fs_js.mx raw

   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