github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/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 // UTIME_OMIT value must match internal/syscall/unix/at_js.go 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 // Reset directory read position. See issue 35767. 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 { // on Node.js 8, fs.utimes calls the callback without any arguments 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 // checkPath checks that the path is not empty and that it contains no null characters. 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 // mapJSError maps an error given by Node.js to the appropriate Go error. 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 }