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