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 nodeDIRECTORY = constants.Get("O_DIRECTORY").Int()
33 )
34
35 type jsFile struct {
36 path string
37 entries []string
38 dirIdx int
39 pos int64
40 seeked bool
41 }
42
43 var filesMu sync.Mutex
44 var files = map[int]*jsFile{
45 0: {},
46 1: {},
47 2: {},
48 }
49
50 func fdToFile(fd int) (*jsFile, error) {
51 filesMu.Lock()
52 f, ok := files[fd]
53 filesMu.Unlock()
54 if !ok {
55 return nil, EBADF
56 }
57 return f, nil
58 }
59
60 func Open(path string, openmode int, perm uint32) (int, error) {
61 if err := checkPath(path); err != nil {
62 return 0, err
63 }
64
65 flags := 0
66 if openmode&O_WRONLY != 0 {
67 flags |= nodeWRONLY
68 }
69 if openmode&O_RDWR != 0 {
70 flags |= nodeRDWR
71 }
72 if openmode&O_CREATE != 0 {
73 flags |= nodeCREATE
74 }
75 if openmode&O_TRUNC != 0 {
76 flags |= nodeTRUNC
77 }
78 if openmode&O_APPEND != 0 {
79 flags |= nodeAPPEND
80 }
81 if openmode&O_EXCL != 0 {
82 flags |= nodeEXCL
83 }
84 if openmode&O_SYNC != 0 {
85 return 0, errors.New("syscall.Open: O_SYNC is not supported by js/wasm")
86 }
87 if openmode&O_DIRECTORY != 0 {
88 flags |= nodeDIRECTORY
89 }
90
91 jsFD, err := fsCall("open", path, flags, perm)
92 if err != nil {
93 return 0, err
94 }
95 fd := jsFD.Int()
96
97 var entries []string
98 if stat, err := fsCall("fstat", fd); err == nil && stat.Call("isDirectory").Bool() {
99 dir, err := fsCall("readdir", path)
100 if err != nil {
101 return 0, err
102 }
103 entries = make([]string, dir.Length())
104 for i := range entries {
105 entries[i] = dir.Index(i).String()
106 }
107 }
108
109 path = jsPath.Call("resolve", path).String()
110
111 f := &jsFile{
112 path: path,
113 entries: entries,
114 }
115 filesMu.Lock()
116 files[fd] = f
117 filesMu.Unlock()
118 return fd, nil
119 }
120
121 func Close(fd int) error {
122 filesMu.Lock()
123 delete(files, fd)
124 filesMu.Unlock()
125 _, err := fsCall("close", fd)
126 return err
127 }
128
129 func CloseOnExec(fd int) {
130
131 }
132
133 func Mkdir(path string, perm uint32) error {
134 if err := checkPath(path); err != nil {
135 return err
136 }
137 _, err := fsCall("mkdir", path, perm)
138 return err
139 }
140
141 func ReadDirent(fd int, buf []byte) (int, error) {
142 f, err := fdToFile(fd)
143 if err != nil {
144 return 0, err
145 }
146 if f.entries == nil {
147 return 0, EINVAL
148 }
149
150 n := 0
151 for f.dirIdx < len(f.entries) {
152 entry := f.entries[f.dirIdx]
153 l := 2 + len(entry)
154 if l > len(buf) {
155 break
156 }
157 buf[0] = byte(l)
158 buf[1] = byte(l >> 8)
159 copy(buf[2:], entry)
160 buf = buf[l:]
161 n += l
162 f.dirIdx++
163 }
164
165 return n, nil
166 }
167
168 func setStat(st *Stat_t, jsSt js.Value) {
169 st.Dev = int64(jsSt.Get("dev").Int())
170 st.Ino = uint64(jsSt.Get("ino").Int())
171 st.Mode = uint32(jsSt.Get("mode").Int())
172 st.Nlink = uint32(jsSt.Get("nlink").Int())
173 st.Uid = uint32(jsSt.Get("uid").Int())
174 st.Gid = uint32(jsSt.Get("gid").Int())
175 st.Rdev = int64(jsSt.Get("rdev").Int())
176 st.Size = int64(jsSt.Get("size").Int())
177 st.Blksize = int32(jsSt.Get("blksize").Int())
178 st.Blocks = int32(jsSt.Get("blocks").Int())
179 atime := int64(jsSt.Get("atimeMs").Int())
180 st.Atime = atime / 1000
181 st.AtimeNsec = (atime % 1000) * 1000000
182 mtime := int64(jsSt.Get("mtimeMs").Int())
183 st.Mtime = mtime / 1000
184 st.MtimeNsec = (mtime % 1000) * 1000000
185 ctime := int64(jsSt.Get("ctimeMs").Int())
186 st.Ctime = ctime / 1000
187 st.CtimeNsec = (ctime % 1000) * 1000000
188 }
189
190 func Stat(path string, st *Stat_t) error {
191 if err := checkPath(path); err != nil {
192 return err
193 }
194 jsSt, err := fsCall("stat", path)
195 if err != nil {
196 return err
197 }
198 setStat(st, jsSt)
199 return nil
200 }
201
202 func Lstat(path string, st *Stat_t) error {
203 if err := checkPath(path); err != nil {
204 return err
205 }
206 jsSt, err := fsCall("lstat", path)
207 if err != nil {
208 return err
209 }
210 setStat(st, jsSt)
211 return nil
212 }
213
214 func Fstat(fd int, st *Stat_t) error {
215 jsSt, err := fsCall("fstat", fd)
216 if err != nil {
217 return err
218 }
219 setStat(st, jsSt)
220 return nil
221 }
222
223 func Unlink(path string) error {
224 if err := checkPath(path); err != nil {
225 return err
226 }
227 _, err := fsCall("unlink", path)
228 return err
229 }
230
231 func Rmdir(path string) error {
232 if err := checkPath(path); err != nil {
233 return err
234 }
235 _, err := fsCall("rmdir", path)
236 return err
237 }
238
239 func Chmod(path string, mode uint32) error {
240 if err := checkPath(path); err != nil {
241 return err
242 }
243 _, err := fsCall("chmod", path, mode)
244 return err
245 }
246
247 func Fchmod(fd int, mode uint32) error {
248 _, err := fsCall("fchmod", fd, mode)
249 return err
250 }
251
252 func Chown(path string, uid, gid int) error {
253 if err := checkPath(path); err != nil {
254 return err
255 }
256 _, err := fsCall("chown", path, uint32(uid), uint32(gid))
257 return err
258 }
259
260 func Fchown(fd int, uid, gid int) error {
261 _, err := fsCall("fchown", fd, uint32(uid), uint32(gid))
262 return err
263 }
264
265 func Lchown(path string, uid, gid int) error {
266 if err := checkPath(path); err != nil {
267 return err
268 }
269 if jsFS.Get("lchown").IsUndefined() {
270
271
272 return ENOSYS
273 }
274 _, err := fsCall("lchown", path, uint32(uid), uint32(gid))
275 return err
276 }
277
278 func UtimesNano(path string, ts []Timespec) error {
279
280 const UTIME_OMIT = -0x2
281 if err := checkPath(path); err != nil {
282 return err
283 }
284 if len(ts) != 2 {
285 return EINVAL
286 }
287 atime := ts[0].Sec
288 mtime := ts[1].Sec
289 if atime == UTIME_OMIT || mtime == UTIME_OMIT {
290 var st Stat_t
291 if err := Stat(path, &st); err != nil {
292 return err
293 }
294 if atime == UTIME_OMIT {
295 atime = st.Atime
296 }
297 if mtime == UTIME_OMIT {
298 mtime = st.Mtime
299 }
300 }
301 _, err := fsCall("utimes", path, atime, mtime)
302 return err
303 }
304
305 func Rename(from, to string) error {
306 if err := checkPath(from); err != nil {
307 return err
308 }
309 if err := checkPath(to); err != nil {
310 return err
311 }
312 _, err := fsCall("rename", from, to)
313 return err
314 }
315
316 func Truncate(path string, length int64) error {
317 if err := checkPath(path); err != nil {
318 return err
319 }
320 _, err := fsCall("truncate", path, length)
321 return err
322 }
323
324 func Ftruncate(fd int, length int64) error {
325 _, err := fsCall("ftruncate", fd, length)
326 return err
327 }
328
329 func Getcwd(buf []byte) (n int, err error) {
330 defer recoverErr(&err)
331 cwd := jsProcess.Call("cwd").String()
332 n = copy(buf, cwd)
333 return
334 }
335
336 func Chdir(path string) (err error) {
337 if err := checkPath(path); err != nil {
338 return err
339 }
340 defer recoverErr(&err)
341 jsProcess.Call("chdir", path)
342 return
343 }
344
345 func Fchdir(fd int) error {
346 f, err := fdToFile(fd)
347 if err != nil {
348 return err
349 }
350 return Chdir(f.path)
351 }
352
353 func Readlink(path string, buf []byte) (n int, err error) {
354 if err := checkPath(path); err != nil {
355 return 0, err
356 }
357 dst, err := fsCall("readlink", path)
358 if err != nil {
359 return 0, err
360 }
361 n = copy(buf, dst.String())
362 return n, nil
363 }
364
365 func Link(path, link string) error {
366 if err := checkPath(path); err != nil {
367 return err
368 }
369 if err := checkPath(link); err != nil {
370 return err
371 }
372 _, err := fsCall("link", path, link)
373 return err
374 }
375
376 func Symlink(path, link string) error {
377 if err := checkPath(path); err != nil {
378 return err
379 }
380 if err := checkPath(link); err != nil {
381 return err
382 }
383 _, err := fsCall("symlink", path, link)
384 return err
385 }
386
387 func Fsync(fd int) error {
388 _, err := fsCall("fsync", fd)
389 return err
390 }
391
392 func Read(fd int, b []byte) (int, error) {
393 f, err := fdToFile(fd)
394 if err != nil {
395 return 0, err
396 }
397
398 if f.seeked {
399 n, err := Pread(fd, b, f.pos)
400 f.pos += int64(n)
401 return n, err
402 }
403
404 buf := uint8Array.New(len(b))
405 n, err := fsCall("read", fd, buf, 0, len(b), nil)
406 if err != nil {
407 return 0, err
408 }
409 js.CopyBytesToGo(b, buf)
410
411 n2 := n.Int()
412 f.pos += int64(n2)
413 return n2, err
414 }
415
416 func Write(fd int, b []byte) (int, error) {
417 f, err := fdToFile(fd)
418 if err != nil {
419 return 0, err
420 }
421
422 if f.seeked {
423 n, err := Pwrite(fd, b, f.pos)
424 f.pos += int64(n)
425 return n, err
426 }
427
428 if faketime && (fd == 1 || fd == 2) {
429 n := faketimeWrite(fd, b)
430 if n < 0 {
431 return 0, errnoErr(Errno(-n))
432 }
433 return n, nil
434 }
435
436 buf := uint8Array.New(len(b))
437 js.CopyBytesToJS(buf, b)
438 n, err := fsCall("write", fd, buf, 0, len(b), nil)
439 if err != nil {
440 return 0, err
441 }
442 n2 := n.Int()
443 f.pos += int64(n2)
444 return n2, err
445 }
446
447 func Pread(fd int, b []byte, offset int64) (int, error) {
448 buf := uint8Array.New(len(b))
449 n, err := fsCall("read", fd, buf, 0, len(b), offset)
450 if err != nil {
451 return 0, err
452 }
453 js.CopyBytesToGo(b, buf)
454 return n.Int(), nil
455 }
456
457 func Pwrite(fd int, b []byte, offset int64) (int, error) {
458 buf := uint8Array.New(len(b))
459 js.CopyBytesToJS(buf, b)
460 n, err := fsCall("write", fd, buf, 0, len(b), offset)
461 if err != nil {
462 return 0, err
463 }
464 return n.Int(), nil
465 }
466
467 func Seek(fd int, offset int64, whence int) (int64, error) {
468 f, err := fdToFile(fd)
469 if err != nil {
470 return 0, err
471 }
472
473 var newPos int64
474 switch whence {
475 case 0:
476 newPos = offset
477 case 1:
478 newPos = f.pos + offset
479 case 2:
480 var st Stat_t
481 if err := Fstat(fd, &st); err != nil {
482 return 0, err
483 }
484 newPos = st.Size + offset
485 default:
486 return 0, errnoErr(EINVAL)
487 }
488
489 if newPos < 0 {
490 return 0, errnoErr(EINVAL)
491 }
492
493 f.seeked = true
494 f.dirIdx = 0
495 f.pos = newPos
496 return newPos, nil
497 }
498
499 func Dup(fd int) (int, error) {
500 return 0, ENOSYS
501 }
502
503 func Dup2(fd, newfd int) error {
504 return ENOSYS
505 }
506
507 func Pipe(fd []int) error {
508 return ENOSYS
509 }
510
511 func fsCall(name string, args ...any) (js.Value, error) {
512 type callResult struct {
513 val js.Value
514 err error
515 }
516
517 c := make(chan callResult, 1)
518 f := js.FuncOf(func(this js.Value, args []js.Value) any {
519 var res callResult
520
521 if len(args) >= 1 {
522 if jsErr := args[0]; !jsErr.IsNull() {
523 res.err = mapJSError(jsErr)
524 }
525 }
526
527 res.val = js.Undefined()
528 if len(args) >= 2 {
529 res.val = args[1]
530 }
531
532 c <- res
533 return nil
534 })
535 defer f.Release()
536 jsFS.Call(name, append(args, f)...)
537 res := <-c
538 return res.val, res.err
539 }
540
541
542 func checkPath(path string) error {
543 if path == "" {
544 return EINVAL
545 }
546 for i := 0; i < len(path); i++ {
547 if path[i] == '\x00' {
548 return EINVAL
549 }
550 }
551 return nil
552 }
553
554 func recoverErr(errPtr *error) {
555 if err := recover(); err != nil {
556 jsErr, ok := err.(js.Error)
557 if !ok {
558 panic(err)
559 }
560 *errPtr = mapJSError(jsErr.Value)
561 }
562 }
563
564
565 func mapJSError(jsErr js.Value) error {
566 errno, ok := errnoByCode[jsErr.Get("code").String()]
567 if !ok {
568 panic(jsErr)
569 }
570 return errnoErr(Errno(errno))
571 }
572
View as plain text