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