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