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 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
34
35
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
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
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
286
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
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
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 {
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
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
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
View as plain text