• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7package syscall
8
9import (
10	"errors"
11	"sync"
12	"syscall/js"
13)
14
15// Provided by package runtime.
16func now() (sec int64, nsec int32)
17
18var jsProcess = js.Global().Get("process")
19var jsFS = js.Global().Get("fs")
20var constants = jsFS.Get("constants")
21
22var uint8Array = js.Global().Get("Uint8Array")
23
24var (
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
33type 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
41var filesMu sync.Mutex
42var files = map[int]*jsFile{
43	0: {},
44	1: {},
45	2: {},
46}
47
48func 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
58func 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
118func Close(fd int) error {
119	filesMu.Lock()
120	delete(files, fd)
121	filesMu.Unlock()
122	_, err := fsCall("close", fd)
123	return err
124}
125
126func CloseOnExec(fd int) {
127	// nothing to do - no exec
128}
129
130func 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
138func 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
165func 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
187func 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
199func 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
211func 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
220func 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
228func 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
236func 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
244func Fchmod(fd int, mode uint32) error {
245	_, err := fsCall("fchmod", fd, mode)
246	return err
247}
248
249func 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
257func Fchown(fd int, uid, gid int) error {
258	_, err := fsCall("fchown", fd, uint32(uid), uint32(gid))
259	return err
260}
261
262func 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
275func 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
302func 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
313func 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
321func Ftruncate(fd int, length int64) error {
322	_, err := fsCall("ftruncate", fd, length)
323	return err
324}
325
326func 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
333func 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
342func Fchdir(fd int) error {
343	f, err := fdToFile(fd)
344	if err != nil {
345		return err
346	}
347	return Chdir(f.path)
348}
349
350func 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
362func 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
373func 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
384func Fsync(fd int) error {
385	_, err := fsCall("fsync", fd)
386	return err
387}
388
389func 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
413func 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
444func 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
454func 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
464func 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
496func Dup(fd int) (int, error) {
497	return 0, ENOSYS
498}
499
500func Dup2(fd, newfd int) error {
501	return ENOSYS
502}
503
504func Pipe(fd []int) error {
505	return ENOSYS
506}
507
508func 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.
539func 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
551func 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.
562func 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