Source file
src/syscall/fs_js.go
1
2
3
4
5
6
7 package syscall
8
9 import (
10 "errors"
11 "sync"
12 "syscall/js"
13 )
14
15
16 func now() (sec int64, nsec int32)
17
18 var jsProcess = js.Global().Get("process")
19 var jsFS = js.Global().Get("fs")
20 var constants = jsFS.Get("constants")
21
22 var uint8Array = js.Global().Get("Uint8Array")
23
24 var (
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
33 type jsFile struct {
34 path string
35 entries []string
36 dirIdx int
37 pos int64
38 seeked bool
39 }
40
41 var filesMu sync.Mutex
42 var files = map[int]*jsFile{
43 0: {},
44 1: {},
45 2: {},
46 }
47
48 func 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
58 func 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
118 func Close(fd int) error {
119 filesMu.Lock()
120 delete(files, fd)
121 filesMu.Unlock()
122 _, err := fsCall("close", fd)
123 return err
124 }
125
126 func CloseOnExec(fd int) {
127
128 }
129
130 func 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
138 func 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
165 func 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
187 func 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
199 func 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
211 func 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
220 func 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
228 func 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
236 func 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
244 func Fchmod(fd int, mode uint32) error {
245 _, err := fsCall("fchmod", fd, mode)
246 return err
247 }
248
249 func 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
257 func Fchown(fd int, uid, gid int) error {
258 _, err := fsCall("fchown", fd, uint32(uid), uint32(gid))
259 return err
260 }
261
262 func 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
268
269 return ENOSYS
270 }
271 _, err := fsCall("lchown", path, uint32(uid), uint32(gid))
272 return err
273 }
274
275 func UtimesNano(path string, ts []Timespec) error {
276
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
302 func 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
313 func 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
321 func Ftruncate(fd int, length int64) error {
322 _, err := fsCall("ftruncate", fd, length)
323 return err
324 }
325
326 func 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
333 func 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
342 func Fchdir(fd int) error {
343 f, err := fdToFile(fd)
344 if err != nil {
345 return err
346 }
347 return Chdir(f.path)
348 }
349
350 func 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
362 func 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
373 func 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
384 func Fsync(fd int) error {
385 _, err := fsCall("fsync", fd)
386 return err
387 }
388
389 func 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
413 func 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
444 func 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
454 func 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
464 func 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
492 f.pos = newPos
493 return newPos, nil
494 }
495
496 func Dup(fd int) (int, error) {
497 return 0, ENOSYS
498 }
499
500 func Dup2(fd, newfd int) error {
501 return ENOSYS
502 }
503
504 func Pipe(fd []int) error {
505 return ENOSYS
506 }
507
508 func 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 {
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
539 func 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
551 func 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
562 func 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
View as plain text