github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/syscall/fs_js.go (about) 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 7 package syscall 8 9 import ( 10 "errors" 11 "sync" 12 "syscall/js" 13 ) 14 15 // Provided by package runtime. 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 // entries[:dirIdx] have already been returned in ReadDirent 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 // nothing to do - no exec 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 // 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 275 func UtimesNano(path string, ts []Timespec) error { 276 if err := checkPath(path); err != nil { 277 return err 278 } 279 if len(ts) != 2 { 280 return EINVAL 281 } 282 atime := ts[0].Sec 283 mtime := ts[1].Sec 284 _, err := fsCall("utimes", path, atime, mtime) 285 return err 286 } 287 288 func Rename(from, to string) error { 289 if err := checkPath(from); err != nil { 290 return err 291 } 292 if err := checkPath(to); err != nil { 293 return err 294 } 295 _, err := fsCall("rename", from, to) 296 return err 297 } 298 299 func Truncate(path string, length int64) error { 300 if err := checkPath(path); err != nil { 301 return err 302 } 303 _, err := fsCall("truncate", path, length) 304 return err 305 } 306 307 func Ftruncate(fd int, length int64) error { 308 _, err := fsCall("ftruncate", fd, length) 309 return err 310 } 311 312 func Getcwd(buf []byte) (n int, err error) { 313 defer recoverErr(&err) 314 cwd := jsProcess.Call("cwd").String() 315 n = copy(buf, cwd) 316 return 317 } 318 319 func Chdir(path string) (err error) { 320 if err := checkPath(path); err != nil { 321 return err 322 } 323 defer recoverErr(&err) 324 jsProcess.Call("chdir", path) 325 return 326 } 327 328 func Fchdir(fd int) error { 329 f, err := fdToFile(fd) 330 if err != nil { 331 return err 332 } 333 return Chdir(f.path) 334 } 335 336 func Readlink(path string, buf []byte) (n int, err error) { 337 if err := checkPath(path); err != nil { 338 return 0, err 339 } 340 dst, err := fsCall("readlink", path) 341 if err != nil { 342 return 0, err 343 } 344 n = copy(buf, dst.String()) 345 return n, nil 346 } 347 348 func Link(path, link string) error { 349 if err := checkPath(path); err != nil { 350 return err 351 } 352 if err := checkPath(link); err != nil { 353 return err 354 } 355 _, err := fsCall("link", path, link) 356 return err 357 } 358 359 func Symlink(path, link string) error { 360 if err := checkPath(path); err != nil { 361 return err 362 } 363 if err := checkPath(link); err != nil { 364 return err 365 } 366 _, err := fsCall("symlink", path, link) 367 return err 368 } 369 370 func Fsync(fd int) error { 371 _, err := fsCall("fsync", fd) 372 return err 373 } 374 375 func Read(fd int, b []byte) (int, error) { 376 f, err := fdToFile(fd) 377 if err != nil { 378 return 0, err 379 } 380 381 if f.seeked { 382 n, err := Pread(fd, b, f.pos) 383 f.pos += int64(n) 384 return n, err 385 } 386 387 buf := uint8Array.New(len(b)) 388 n, err := fsCall("read", fd, buf, 0, len(b), nil) 389 if err != nil { 390 return 0, err 391 } 392 js.CopyBytesToGo(b, buf) 393 394 n2 := n.Int() 395 f.pos += int64(n2) 396 return n2, err 397 } 398 399 func Write(fd int, b []byte) (int, error) { 400 f, err := fdToFile(fd) 401 if err != nil { 402 return 0, err 403 } 404 405 if f.seeked { 406 n, err := Pwrite(fd, b, f.pos) 407 f.pos += int64(n) 408 return n, err 409 } 410 411 if faketime && (fd == 1 || fd == 2) { 412 n := faketimeWrite(fd, b) 413 if n < 0 { 414 return 0, errnoErr(Errno(-n)) 415 } 416 return n, nil 417 } 418 419 buf := uint8Array.New(len(b)) 420 js.CopyBytesToJS(buf, b) 421 n, err := fsCall("write", fd, buf, 0, len(b), nil) 422 if err != nil { 423 return 0, err 424 } 425 n2 := n.Int() 426 f.pos += int64(n2) 427 return n2, err 428 } 429 430 func Pread(fd int, b []byte, offset int64) (int, error) { 431 buf := uint8Array.New(len(b)) 432 n, err := fsCall("read", fd, buf, 0, len(b), offset) 433 if err != nil { 434 return 0, err 435 } 436 js.CopyBytesToGo(b, buf) 437 return n.Int(), nil 438 } 439 440 func Pwrite(fd int, b []byte, offset int64) (int, error) { 441 buf := uint8Array.New(len(b)) 442 js.CopyBytesToJS(buf, b) 443 n, err := fsCall("write", fd, buf, 0, len(b), offset) 444 if err != nil { 445 return 0, err 446 } 447 return n.Int(), nil 448 } 449 450 func Seek(fd int, offset int64, whence int) (int64, error) { 451 f, err := fdToFile(fd) 452 if err != nil { 453 return 0, err 454 } 455 456 var newPos int64 457 switch whence { 458 case 0: 459 newPos = offset 460 case 1: 461 newPos = f.pos + offset 462 case 2: 463 var st Stat_t 464 if err := Fstat(fd, &st); err != nil { 465 return 0, err 466 } 467 newPos = st.Size + offset 468 default: 469 return 0, errnoErr(EINVAL) 470 } 471 472 if newPos < 0 { 473 return 0, errnoErr(EINVAL) 474 } 475 476 f.seeked = true 477 f.dirIdx = 0 // Reset directory read position. See issue 35767. 478 f.pos = newPos 479 return newPos, nil 480 } 481 482 func Dup(fd int) (int, error) { 483 return 0, ENOSYS 484 } 485 486 func Dup2(fd, newfd int) error { 487 return ENOSYS 488 } 489 490 func Pipe(fd []int) error { 491 return ENOSYS 492 } 493 494 func fsCall(name string, args ...any) (js.Value, error) { 495 type callResult struct { 496 val js.Value 497 err error 498 } 499 500 c := make(chan callResult, 1) 501 f := js.FuncOf(func(this js.Value, args []js.Value) any { 502 var res callResult 503 504 if len(args) >= 1 { // on Node.js 8, fs.utimes calls the callback without any arguments 505 if jsErr := args[0]; !jsErr.IsNull() { 506 res.err = mapJSError(jsErr) 507 } 508 } 509 510 res.val = js.Undefined() 511 if len(args) >= 2 { 512 res.val = args[1] 513 } 514 515 c <- res 516 return nil 517 }) 518 defer f.Release() 519 jsFS.Call(name, append(args, f)...) 520 res := <-c 521 return res.val, res.err 522 } 523 524 // checkPath checks that the path is not empty and that it contains no null characters. 525 func checkPath(path string) error { 526 if path == "" { 527 return EINVAL 528 } 529 for i := 0; i < len(path); i++ { 530 if path[i] == '\x00' { 531 return EINVAL 532 } 533 } 534 return nil 535 } 536 537 func recoverErr(errPtr *error) { 538 if err := recover(); err != nil { 539 jsErr, ok := err.(js.Error) 540 if !ok { 541 panic(err) 542 } 543 *errPtr = mapJSError(jsErr.Value) 544 } 545 } 546 547 // mapJSError maps an error given by Node.js to the appropriate Go error 548 func mapJSError(jsErr js.Value) error { 549 errno, ok := errnoByCode[jsErr.Get("code").String()] 550 if !ok { 551 panic(jsErr) 552 } 553 return errnoErr(Errno(errno)) 554 }