1// Copyright 2023 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 wasip1 6 7package syscall 8 9import ( 10 "internal/stringslite" 11 "runtime" 12 "unsafe" 13) 14 15func init() { 16 // Try to set stdio to non-blocking mode before the os package 17 // calls NewFile for each fd. NewFile queries the non-blocking flag 18 // but doesn't change it, even if the runtime supports non-blocking 19 // stdio. Since WebAssembly modules are single-threaded, blocking 20 // system calls temporarily halt execution of the module. If the 21 // runtime supports non-blocking stdio, the Go runtime is able to 22 // use the WASI net poller to poll for read/write readiness and is 23 // able to schedule goroutines while waiting. 24 SetNonblock(0, true) 25 SetNonblock(1, true) 26 SetNonblock(2, true) 27} 28 29type uintptr32 = uint32 30type size = uint32 31type fdflags = uint32 32type filesize = uint64 33type filetype = uint8 34type lookupflags = uint32 35type oflags = uint32 36type rights = uint64 37type timestamp = uint64 38type dircookie = uint64 39type filedelta = int64 40type fstflags = uint32 41 42type iovec struct { 43 buf uintptr32 44 bufLen size 45} 46 47const ( 48 LOOKUP_SYMLINK_FOLLOW = 0x00000001 49) 50 51const ( 52 OFLAG_CREATE = 0x0001 53 OFLAG_DIRECTORY = 0x0002 54 OFLAG_EXCL = 0x0004 55 OFLAG_TRUNC = 0x0008 56) 57 58const ( 59 FDFLAG_APPEND = 0x0001 60 FDFLAG_DSYNC = 0x0002 61 FDFLAG_NONBLOCK = 0x0004 62 FDFLAG_RSYNC = 0x0008 63 FDFLAG_SYNC = 0x0010 64) 65 66const ( 67 RIGHT_FD_DATASYNC = 1 << iota 68 RIGHT_FD_READ 69 RIGHT_FD_SEEK 70 RIGHT_FDSTAT_SET_FLAGS 71 RIGHT_FD_SYNC 72 RIGHT_FD_TELL 73 RIGHT_FD_WRITE 74 RIGHT_FD_ADVISE 75 RIGHT_FD_ALLOCATE 76 RIGHT_PATH_CREATE_DIRECTORY 77 RIGHT_PATH_CREATE_FILE 78 RIGHT_PATH_LINK_SOURCE 79 RIGHT_PATH_LINK_TARGET 80 RIGHT_PATH_OPEN 81 RIGHT_FD_READDIR 82 RIGHT_PATH_READLINK 83 RIGHT_PATH_RENAME_SOURCE 84 RIGHT_PATH_RENAME_TARGET 85 RIGHT_PATH_FILESTAT_GET 86 RIGHT_PATH_FILESTAT_SET_SIZE 87 RIGHT_PATH_FILESTAT_SET_TIMES 88 RIGHT_FD_FILESTAT_GET 89 RIGHT_FD_FILESTAT_SET_SIZE 90 RIGHT_FD_FILESTAT_SET_TIMES 91 RIGHT_PATH_SYMLINK 92 RIGHT_PATH_REMOVE_DIRECTORY 93 RIGHT_PATH_UNLINK_FILE 94 RIGHT_POLL_FD_READWRITE 95 RIGHT_SOCK_SHUTDOWN 96 RIGHT_SOCK_ACCEPT 97) 98 99const ( 100 WHENCE_SET = 0 101 WHENCE_CUR = 1 102 WHENCE_END = 2 103) 104 105const ( 106 FILESTAT_SET_ATIM = 0x0001 107 FILESTAT_SET_ATIM_NOW = 0x0002 108 FILESTAT_SET_MTIM = 0x0004 109 FILESTAT_SET_MTIM_NOW = 0x0008 110) 111 112const ( 113 // Despite the rights being defined as a 64 bits integer in the spec, 114 // wasmtime crashes the program if we set any of the upper 32 bits. 115 fullRights = rights(^uint32(0)) 116 readRights = rights(RIGHT_FD_READ | RIGHT_FD_READDIR) 117 writeRights = rights(RIGHT_FD_DATASYNC | RIGHT_FD_WRITE | RIGHT_FD_ALLOCATE | RIGHT_PATH_FILESTAT_SET_SIZE) 118 119 // Some runtimes have very strict expectations when it comes to which 120 // rights can be enabled on files opened by path_open. The fileRights 121 // constant is used as a mask to retain only bits for operations that 122 // are supported on files. 123 fileRights rights = RIGHT_FD_DATASYNC | 124 RIGHT_FD_READ | 125 RIGHT_FD_SEEK | 126 RIGHT_FDSTAT_SET_FLAGS | 127 RIGHT_FD_SYNC | 128 RIGHT_FD_TELL | 129 RIGHT_FD_WRITE | 130 RIGHT_FD_ADVISE | 131 RIGHT_FD_ALLOCATE | 132 RIGHT_PATH_CREATE_DIRECTORY | 133 RIGHT_PATH_CREATE_FILE | 134 RIGHT_PATH_LINK_SOURCE | 135 RIGHT_PATH_LINK_TARGET | 136 RIGHT_PATH_OPEN | 137 RIGHT_FD_READDIR | 138 RIGHT_PATH_READLINK | 139 RIGHT_PATH_RENAME_SOURCE | 140 RIGHT_PATH_RENAME_TARGET | 141 RIGHT_PATH_FILESTAT_GET | 142 RIGHT_PATH_FILESTAT_SET_SIZE | 143 RIGHT_PATH_FILESTAT_SET_TIMES | 144 RIGHT_FD_FILESTAT_GET | 145 RIGHT_FD_FILESTAT_SET_SIZE | 146 RIGHT_FD_FILESTAT_SET_TIMES | 147 RIGHT_PATH_SYMLINK | 148 RIGHT_PATH_REMOVE_DIRECTORY | 149 RIGHT_PATH_UNLINK_FILE | 150 RIGHT_POLL_FD_READWRITE 151 152 // Runtimes like wasmtime and wasmedge will refuse to open directories 153 // if the rights requested by the application exceed the operations that 154 // can be performed on a directory. 155 dirRights rights = RIGHT_FD_SEEK | 156 RIGHT_FDSTAT_SET_FLAGS | 157 RIGHT_FD_SYNC | 158 RIGHT_PATH_CREATE_DIRECTORY | 159 RIGHT_PATH_CREATE_FILE | 160 RIGHT_PATH_LINK_SOURCE | 161 RIGHT_PATH_LINK_TARGET | 162 RIGHT_PATH_OPEN | 163 RIGHT_FD_READDIR | 164 RIGHT_PATH_READLINK | 165 RIGHT_PATH_RENAME_SOURCE | 166 RIGHT_PATH_RENAME_TARGET | 167 RIGHT_PATH_FILESTAT_GET | 168 RIGHT_PATH_FILESTAT_SET_SIZE | 169 RIGHT_PATH_FILESTAT_SET_TIMES | 170 RIGHT_FD_FILESTAT_GET | 171 RIGHT_FD_FILESTAT_SET_TIMES | 172 RIGHT_PATH_SYMLINK | 173 RIGHT_PATH_REMOVE_DIRECTORY | 174 RIGHT_PATH_UNLINK_FILE 175) 176 177// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_closefd-fd---result-errno 178// 179//go:wasmimport wasi_snapshot_preview1 fd_close 180//go:noescape 181func fd_close(fd int32) Errno 182 183// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---result-errno 184// 185//go:wasmimport wasi_snapshot_preview1 fd_filestat_set_size 186//go:noescape 187func fd_filestat_set_size(fd int32, set_size filesize) Errno 188 189// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---resultsize-errno 190// 191//go:wasmimport wasi_snapshot_preview1 fd_pread 192//go:noescape 193func fd_pread(fd int32, iovs unsafe.Pointer, iovsLen size, offset filesize, nread unsafe.Pointer) Errno 194 195//go:wasmimport wasi_snapshot_preview1 fd_pwrite 196//go:noescape 197func fd_pwrite(fd int32, iovs unsafe.Pointer, iovsLen size, offset filesize, nwritten unsafe.Pointer) Errno 198 199//go:wasmimport wasi_snapshot_preview1 fd_read 200//go:noescape 201func fd_read(fd int32, iovs unsafe.Pointer, iovsLen size, nread unsafe.Pointer) Errno 202 203//go:wasmimport wasi_snapshot_preview1 fd_readdir 204//go:noescape 205func fd_readdir(fd int32, buf unsafe.Pointer, bufLen size, cookie dircookie, nwritten unsafe.Pointer) Errno 206 207//go:wasmimport wasi_snapshot_preview1 fd_seek 208//go:noescape 209func fd_seek(fd int32, offset filedelta, whence uint32, newoffset unsafe.Pointer) Errno 210 211// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_fdstat_set_rightsfd-fd-fs_rights_base-rights-fs_rights_inheriting-rights---result-errno 212// 213//go:wasmimport wasi_snapshot_preview1 fd_fdstat_set_rights 214//go:noescape 215func fd_fdstat_set_rights(fd int32, rightsBase rights, rightsInheriting rights) Errno 216 217//go:wasmimport wasi_snapshot_preview1 fd_filestat_get 218//go:noescape 219func fd_filestat_get(fd int32, buf unsafe.Pointer) Errno 220 221//go:wasmimport wasi_snapshot_preview1 fd_write 222//go:noescape 223func fd_write(fd int32, iovs unsafe.Pointer, iovsLen size, nwritten unsafe.Pointer) Errno 224 225//go:wasmimport wasi_snapshot_preview1 fd_sync 226//go:noescape 227func fd_sync(fd int32) Errno 228 229//go:wasmimport wasi_snapshot_preview1 path_create_directory 230//go:noescape 231func path_create_directory(fd int32, path unsafe.Pointer, pathLen size) Errno 232 233//go:wasmimport wasi_snapshot_preview1 path_filestat_get 234//go:noescape 235func path_filestat_get(fd int32, flags lookupflags, path unsafe.Pointer, pathLen size, buf unsafe.Pointer) Errno 236 237//go:wasmimport wasi_snapshot_preview1 path_filestat_set_times 238//go:noescape 239func path_filestat_set_times(fd int32, flags lookupflags, path unsafe.Pointer, pathLen size, atim timestamp, mtim timestamp, fstflags fstflags) Errno 240 241//go:wasmimport wasi_snapshot_preview1 path_link 242//go:noescape 243func path_link(oldFd int32, oldFlags lookupflags, oldPath unsafe.Pointer, oldPathLen size, newFd int32, newPath unsafe.Pointer, newPathLen size) Errno 244 245//go:wasmimport wasi_snapshot_preview1 path_readlink 246//go:noescape 247func path_readlink(fd int32, path unsafe.Pointer, pathLen size, buf unsafe.Pointer, bufLen size, nwritten unsafe.Pointer) Errno 248 249//go:wasmimport wasi_snapshot_preview1 path_remove_directory 250//go:noescape 251func path_remove_directory(fd int32, path unsafe.Pointer, pathLen size) Errno 252 253//go:wasmimport wasi_snapshot_preview1 path_rename 254//go:noescape 255func path_rename(oldFd int32, oldPath unsafe.Pointer, oldPathLen size, newFd int32, newPath unsafe.Pointer, newPathLen size) Errno 256 257//go:wasmimport wasi_snapshot_preview1 path_symlink 258//go:noescape 259func path_symlink(oldPath unsafe.Pointer, oldPathLen size, fd int32, newPath unsafe.Pointer, newPathLen size) Errno 260 261//go:wasmimport wasi_snapshot_preview1 path_unlink_file 262//go:noescape 263func path_unlink_file(fd int32, path unsafe.Pointer, pathLen size) Errno 264 265//go:wasmimport wasi_snapshot_preview1 path_open 266//go:noescape 267func path_open(rootFD int32, dirflags lookupflags, path unsafe.Pointer, pathLen size, oflags oflags, fsRightsBase rights, fsRightsInheriting rights, fsFlags fdflags, fd unsafe.Pointer) Errno 268 269//go:wasmimport wasi_snapshot_preview1 random_get 270//go:noescape 271func random_get(buf unsafe.Pointer, bufLen size) Errno 272 273// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fdstat-record 274// fdflags must be at offset 2, hence the uint16 type rather than the 275// fdflags (uint32) type. 276type fdstat struct { 277 filetype filetype 278 fdflags uint16 279 rightsBase rights 280 rightsInheriting rights 281} 282 283//go:wasmimport wasi_snapshot_preview1 fd_fdstat_get 284//go:noescape 285func fd_fdstat_get(fd int32, buf unsafe.Pointer) Errno 286 287//go:wasmimport wasi_snapshot_preview1 fd_fdstat_set_flags 288//go:noescape 289func fd_fdstat_set_flags(fd int32, flags fdflags) Errno 290 291// fd_fdstat_get_flags is accessed from internal/syscall/unix 292//go:linkname fd_fdstat_get_flags 293 294func fd_fdstat_get_flags(fd int) (uint32, error) { 295 var stat fdstat 296 errno := fd_fdstat_get(int32(fd), unsafe.Pointer(&stat)) 297 return uint32(stat.fdflags), errnoErr(errno) 298} 299 300// fd_fdstat_get_type is accessed from net 301//go:linkname fd_fdstat_get_type 302 303func fd_fdstat_get_type(fd int) (uint8, error) { 304 var stat fdstat 305 errno := fd_fdstat_get(int32(fd), unsafe.Pointer(&stat)) 306 return stat.filetype, errnoErr(errno) 307} 308 309type preopentype = uint8 310 311const ( 312 preopentypeDir preopentype = iota 313) 314 315type prestatDir struct { 316 prNameLen size 317} 318 319type prestat struct { 320 typ preopentype 321 dir prestatDir 322} 323 324//go:wasmimport wasi_snapshot_preview1 fd_prestat_get 325//go:noescape 326func fd_prestat_get(fd int32, prestat unsafe.Pointer) Errno 327 328//go:wasmimport wasi_snapshot_preview1 fd_prestat_dir_name 329//go:noescape 330func fd_prestat_dir_name(fd int32, path unsafe.Pointer, pathLen size) Errno 331 332type opendir struct { 333 fd int32 334 name string 335} 336 337// List of preopen directories that were exposed by the runtime. The first one 338// is assumed to the be root directory of the file system, and others are seen 339// as mount points at sub paths of the root. 340var preopens []opendir 341 342// Current working directory. We maintain this as a string and resolve paths in 343// the code because wasmtime does not allow relative path lookups outside of the 344// scope of a directory; a previous approach we tried consisted in maintaining 345// open a file descriptor to the current directory so we could perform relative 346// path lookups from that location, but it resulted in breaking path resolution 347// from the current directory to its parent. 348var cwd string 349 350func init() { 351 dirNameBuf := make([]byte, 256) 352 // We start looking for preopens at fd=3 because 0, 1, and 2 are reserved 353 // for standard input and outputs. 354 for preopenFd := int32(3); ; preopenFd++ { 355 var prestat prestat 356 357 errno := fd_prestat_get(preopenFd, unsafe.Pointer(&prestat)) 358 if errno == EBADF { 359 break 360 } 361 if errno == ENOTDIR || prestat.typ != preopentypeDir { 362 continue 363 } 364 if errno != 0 { 365 panic("fd_prestat: " + errno.Error()) 366 } 367 if int(prestat.dir.prNameLen) > len(dirNameBuf) { 368 dirNameBuf = make([]byte, prestat.dir.prNameLen) 369 } 370 371 errno = fd_prestat_dir_name(preopenFd, unsafe.Pointer(&dirNameBuf[0]), prestat.dir.prNameLen) 372 if errno != 0 { 373 panic("fd_prestat_dir_name: " + errno.Error()) 374 } 375 376 preopens = append(preopens, opendir{ 377 fd: preopenFd, 378 name: string(dirNameBuf[:prestat.dir.prNameLen]), 379 }) 380 } 381 382 if cwd, _ = Getenv("PWD"); cwd != "" { 383 cwd = joinPath("/", cwd) 384 } else if len(preopens) > 0 { 385 cwd = preopens[0].name 386 } 387} 388 389// Provided by package runtime. 390func now() (sec int64, nsec int32) 391 392//go:nosplit 393func appendCleanPath(buf []byte, path string, lookupParent bool) ([]byte, bool) { 394 i := 0 395 for i < len(path) { 396 for i < len(path) && path[i] == '/' { 397 i++ 398 } 399 400 j := i 401 for j < len(path) && path[j] != '/' { 402 j++ 403 } 404 405 s := path[i:j] 406 i = j 407 408 switch s { 409 case "": 410 continue 411 case ".": 412 continue 413 case "..": 414 if !lookupParent { 415 k := len(buf) 416 for k > 0 && buf[k-1] != '/' { 417 k-- 418 } 419 for k > 1 && buf[k-1] == '/' { 420 k-- 421 } 422 buf = buf[:k] 423 if k == 0 { 424 lookupParent = true 425 } else { 426 s = "" 427 continue 428 } 429 } 430 default: 431 lookupParent = false 432 } 433 434 if len(buf) > 0 && buf[len(buf)-1] != '/' { 435 buf = append(buf, '/') 436 } 437 buf = append(buf, s...) 438 } 439 return buf, lookupParent 440} 441 442// joinPath concatenates dir and file paths, producing a cleaned path where 443// "." and ".." have been removed, unless dir is relative and the references 444// to parent directories in file represented a location relative to a parent 445// of dir. 446// 447// This function is used for path resolution of all wasi functions expecting 448// a path argument; the returned string is heap allocated, which we may want 449// to optimize in the future. Instead of returning a string, the function 450// could append the result to an output buffer that the functions in this 451// file can manage to have allocated on the stack (e.g. initializing to a 452// fixed capacity). Since it will significantly increase code complexity, 453// we prefer to optimize for readability and maintainability at this time. 454func joinPath(dir, file string) string { 455 buf := make([]byte, 0, len(dir)+len(file)+1) 456 if isAbs(dir) { 457 buf = append(buf, '/') 458 } 459 buf, lookupParent := appendCleanPath(buf, dir, false) 460 buf, _ = appendCleanPath(buf, file, lookupParent) 461 // The appendCleanPath function cleans the path so it does not inject 462 // references to the current directory. If both the dir and file args 463 // were ".", this results in the output buffer being empty so we handle 464 // this condition here. 465 if len(buf) == 0 { 466 buf = append(buf, '.') 467 } 468 // If the file ended with a '/' we make sure that the output also ends 469 // with a '/'. This is needed to ensure that programs have a mechanism 470 // to represent dereferencing symbolic links pointing to directories. 471 if buf[len(buf)-1] != '/' && isDir(file) { 472 buf = append(buf, '/') 473 } 474 return unsafe.String(&buf[0], len(buf)) 475} 476 477func isAbs(path string) bool { 478 return stringslite.HasPrefix(path, "/") 479} 480 481func isDir(path string) bool { 482 return stringslite.HasSuffix(path, "/") 483} 484 485// preparePath returns the preopen file descriptor of the directory to perform 486// path resolution from, along with the pair of pointer and length for the 487// relative expression of path from the directory. 488// 489// If the path argument is not absolute, it is first appended to the current 490// working directory before resolution. 491func preparePath(path string) (int32, unsafe.Pointer, size) { 492 var dirFd = int32(-1) 493 var dirName string 494 495 dir := "/" 496 if !isAbs(path) { 497 dir = cwd 498 } 499 path = joinPath(dir, path) 500 501 for _, p := range preopens { 502 if len(p.name) > len(dirName) && stringslite.HasPrefix(path, p.name) { 503 dirFd, dirName = p.fd, p.name 504 } 505 } 506 507 path = path[len(dirName):] 508 for isAbs(path) { 509 path = path[1:] 510 } 511 if len(path) == 0 { 512 path = "." 513 } 514 515 return dirFd, stringPointer(path), size(len(path)) 516} 517 518func Open(path string, openmode int, perm uint32) (int, error) { 519 if path == "" { 520 return -1, EINVAL 521 } 522 dirFd, pathPtr, pathLen := preparePath(path) 523 524 var oflags oflags 525 if (openmode & O_CREATE) != 0 { 526 oflags |= OFLAG_CREATE 527 } 528 if (openmode & O_TRUNC) != 0 { 529 oflags |= OFLAG_TRUNC 530 } 531 if (openmode & O_EXCL) != 0 { 532 oflags |= OFLAG_EXCL 533 } 534 535 var rights rights 536 switch openmode & (O_RDONLY | O_WRONLY | O_RDWR) { 537 case O_RDONLY: 538 rights = fileRights & ^writeRights 539 case O_WRONLY: 540 rights = fileRights & ^readRights 541 case O_RDWR: 542 rights = fileRights 543 } 544 545 var fdflags fdflags 546 if (openmode & O_APPEND) != 0 { 547 fdflags |= FDFLAG_APPEND 548 } 549 if (openmode & O_SYNC) != 0 { 550 fdflags |= FDFLAG_SYNC 551 } 552 553 var fd int32 554 errno := path_open( 555 dirFd, 556 LOOKUP_SYMLINK_FOLLOW, 557 pathPtr, 558 pathLen, 559 oflags, 560 rights, 561 fileRights, 562 fdflags, 563 unsafe.Pointer(&fd), 564 ) 565 if errno == EISDIR && oflags == 0 && fdflags == 0 && ((rights & writeRights) == 0) { 566 // wasmtime and wasmedge will error if attempting to open a directory 567 // because we are asking for too many rights. However, we cannot 568 // determine ahead of time if the path we are about to open is a 569 // directory, so instead we fallback to a second call to path_open with 570 // a more limited set of rights. 571 // 572 // This approach is subject to a race if the file system is modified 573 // concurrently, so we also inject OFLAG_DIRECTORY to ensure that we do 574 // not accidentally open a file which is not a directory. 575 errno = path_open( 576 dirFd, 577 LOOKUP_SYMLINK_FOLLOW, 578 pathPtr, 579 pathLen, 580 oflags|OFLAG_DIRECTORY, 581 rights&dirRights, 582 fileRights, 583 fdflags, 584 unsafe.Pointer(&fd), 585 ) 586 } 587 return int(fd), errnoErr(errno) 588} 589 590func Close(fd int) error { 591 errno := fd_close(int32(fd)) 592 return errnoErr(errno) 593} 594 595func CloseOnExec(fd int) { 596 // nothing to do - no exec 597} 598 599func Mkdir(path string, perm uint32) error { 600 if path == "" { 601 return EINVAL 602 } 603 dirFd, pathPtr, pathLen := preparePath(path) 604 errno := path_create_directory(dirFd, pathPtr, pathLen) 605 return errnoErr(errno) 606} 607 608func ReadDir(fd int, buf []byte, cookie dircookie) (int, error) { 609 var nwritten size 610 errno := fd_readdir(int32(fd), unsafe.Pointer(&buf[0]), size(len(buf)), cookie, unsafe.Pointer(&nwritten)) 611 return int(nwritten), errnoErr(errno) 612} 613 614type Stat_t struct { 615 Dev uint64 616 Ino uint64 617 Filetype uint8 618 Nlink uint64 619 Size uint64 620 Atime uint64 621 Mtime uint64 622 Ctime uint64 623 624 Mode int 625 626 // Uid and Gid are always zero on wasip1 platforms 627 Uid uint32 628 Gid uint32 629} 630 631func Stat(path string, st *Stat_t) error { 632 if path == "" { 633 return EINVAL 634 } 635 dirFd, pathPtr, pathLen := preparePath(path) 636 errno := path_filestat_get(dirFd, LOOKUP_SYMLINK_FOLLOW, pathPtr, pathLen, unsafe.Pointer(st)) 637 setDefaultMode(st) 638 return errnoErr(errno) 639} 640 641func Lstat(path string, st *Stat_t) error { 642 if path == "" { 643 return EINVAL 644 } 645 dirFd, pathPtr, pathLen := preparePath(path) 646 errno := path_filestat_get(dirFd, 0, pathPtr, pathLen, unsafe.Pointer(st)) 647 setDefaultMode(st) 648 return errnoErr(errno) 649} 650 651func Fstat(fd int, st *Stat_t) error { 652 errno := fd_filestat_get(int32(fd), unsafe.Pointer(st)) 653 setDefaultMode(st) 654 return errnoErr(errno) 655} 656 657func setDefaultMode(st *Stat_t) { 658 // WASI does not support unix-like permissions, but Go programs are likely 659 // to expect the permission bits to not be zero so we set defaults to help 660 // avoid breaking applications that are migrating to WASM. 661 if st.Filetype == FILETYPE_DIRECTORY { 662 st.Mode = 0700 663 } else { 664 st.Mode = 0600 665 } 666} 667 668func Unlink(path string) error { 669 if path == "" { 670 return EINVAL 671 } 672 dirFd, pathPtr, pathLen := preparePath(path) 673 errno := path_unlink_file(dirFd, pathPtr, pathLen) 674 return errnoErr(errno) 675} 676 677func Rmdir(path string) error { 678 if path == "" { 679 return EINVAL 680 } 681 dirFd, pathPtr, pathLen := preparePath(path) 682 errno := path_remove_directory(dirFd, pathPtr, pathLen) 683 return errnoErr(errno) 684} 685 686func Chmod(path string, mode uint32) error { 687 var stat Stat_t 688 return Stat(path, &stat) 689} 690 691func Fchmod(fd int, mode uint32) error { 692 var stat Stat_t 693 return Fstat(fd, &stat) 694} 695 696func Chown(path string, uid, gid int) error { 697 return ENOSYS 698} 699 700func Fchown(fd int, uid, gid int) error { 701 return ENOSYS 702} 703 704func Lchown(path string, uid, gid int) error { 705 return ENOSYS 706} 707 708func UtimesNano(path string, ts []Timespec) error { 709 // UTIME_OMIT value must match internal/syscall/unix/at_wasip1.go 710 const UTIME_OMIT = -0x2 711 if path == "" { 712 return EINVAL 713 } 714 dirFd, pathPtr, pathLen := preparePath(path) 715 atime := TimespecToNsec(ts[0]) 716 mtime := TimespecToNsec(ts[1]) 717 if ts[0].Nsec == UTIME_OMIT || ts[1].Nsec == UTIME_OMIT { 718 var st Stat_t 719 if err := Stat(path, &st); err != nil { 720 return err 721 } 722 if ts[0].Nsec == UTIME_OMIT { 723 atime = int64(st.Atime) 724 } 725 if ts[1].Nsec == UTIME_OMIT { 726 mtime = int64(st.Mtime) 727 } 728 } 729 errno := path_filestat_set_times( 730 dirFd, 731 LOOKUP_SYMLINK_FOLLOW, 732 pathPtr, 733 pathLen, 734 timestamp(atime), 735 timestamp(mtime), 736 FILESTAT_SET_ATIM|FILESTAT_SET_MTIM, 737 ) 738 return errnoErr(errno) 739} 740 741func Rename(from, to string) error { 742 if from == "" || to == "" { 743 return EINVAL 744 } 745 oldDirFd, oldPathPtr, oldPathLen := preparePath(from) 746 newDirFd, newPathPtr, newPathLen := preparePath(to) 747 errno := path_rename( 748 oldDirFd, 749 oldPathPtr, 750 oldPathLen, 751 newDirFd, 752 newPathPtr, 753 newPathLen, 754 ) 755 return errnoErr(errno) 756} 757 758func Truncate(path string, length int64) error { 759 if path == "" { 760 return EINVAL 761 } 762 fd, err := Open(path, O_WRONLY, 0) 763 if err != nil { 764 return err 765 } 766 defer Close(fd) 767 return Ftruncate(fd, length) 768} 769 770func Ftruncate(fd int, length int64) error { 771 errno := fd_filestat_set_size(int32(fd), filesize(length)) 772 return errnoErr(errno) 773} 774 775const ImplementsGetwd = true 776 777func Getwd() (string, error) { 778 return cwd, nil 779} 780 781func Chdir(path string) error { 782 if path == "" { 783 return EINVAL 784 } 785 786 dir := "/" 787 if !isAbs(path) { 788 dir = cwd 789 } 790 path = joinPath(dir, path) 791 792 var stat Stat_t 793 dirFd, pathPtr, pathLen := preparePath(path) 794 errno := path_filestat_get(dirFd, LOOKUP_SYMLINK_FOLLOW, pathPtr, pathLen, unsafe.Pointer(&stat)) 795 if errno != 0 { 796 return errnoErr(errno) 797 } 798 if stat.Filetype != FILETYPE_DIRECTORY { 799 return ENOTDIR 800 } 801 cwd = path 802 return nil 803} 804 805func Readlink(path string, buf []byte) (n int, err error) { 806 if path == "" { 807 return 0, EINVAL 808 } 809 if len(buf) == 0 { 810 return 0, nil 811 } 812 dirFd, pathPtr, pathLen := preparePath(path) 813 var nwritten size 814 errno := path_readlink( 815 dirFd, 816 pathPtr, 817 pathLen, 818 unsafe.Pointer(&buf[0]), 819 size(len(buf)), 820 unsafe.Pointer(&nwritten), 821 ) 822 // For some reason wasmtime returns ERANGE when the output buffer is 823 // shorter than the symbolic link value. os.Readlink expects a nil 824 // error and uses the fact that n is greater or equal to the buffer 825 // length to assume that it needs to try again with a larger size. 826 // This condition is handled in os.Readlink. 827 return int(nwritten), errnoErr(errno) 828} 829 830func Link(path, link string) error { 831 if path == "" || link == "" { 832 return EINVAL 833 } 834 oldDirFd, oldPathPtr, oldPathLen := preparePath(path) 835 newDirFd, newPathPtr, newPathLen := preparePath(link) 836 errno := path_link( 837 oldDirFd, 838 0, 839 oldPathPtr, 840 oldPathLen, 841 newDirFd, 842 newPathPtr, 843 newPathLen, 844 ) 845 return errnoErr(errno) 846} 847 848func Symlink(path, link string) error { 849 if path == "" || link == "" { 850 return EINVAL 851 } 852 dirFd, pathPtr, pathlen := preparePath(link) 853 errno := path_symlink( 854 stringPointer(path), 855 size(len(path)), 856 dirFd, 857 pathPtr, 858 pathlen, 859 ) 860 return errnoErr(errno) 861} 862 863func Fsync(fd int) error { 864 errno := fd_sync(int32(fd)) 865 return errnoErr(errno) 866} 867 868func bytesPointer(b []byte) unsafe.Pointer { 869 return unsafe.Pointer(unsafe.SliceData(b)) 870} 871 872func stringPointer(s string) unsafe.Pointer { 873 return unsafe.Pointer(unsafe.StringData(s)) 874} 875 876func makeIOVec(b []byte) unsafe.Pointer { 877 return unsafe.Pointer(&iovec{ 878 buf: uintptr32(uintptr(bytesPointer(b))), 879 bufLen: size(len(b)), 880 }) 881} 882 883func Read(fd int, b []byte) (int, error) { 884 var nread size 885 errno := fd_read(int32(fd), makeIOVec(b), 1, unsafe.Pointer(&nread)) 886 runtime.KeepAlive(b) 887 return int(nread), errnoErr(errno) 888} 889 890func Write(fd int, b []byte) (int, error) { 891 var nwritten size 892 errno := fd_write(int32(fd), makeIOVec(b), 1, unsafe.Pointer(&nwritten)) 893 runtime.KeepAlive(b) 894 return int(nwritten), errnoErr(errno) 895} 896 897func Pread(fd int, b []byte, offset int64) (int, error) { 898 var nread size 899 errno := fd_pread(int32(fd), makeIOVec(b), 1, filesize(offset), unsafe.Pointer(&nread)) 900 runtime.KeepAlive(b) 901 return int(nread), errnoErr(errno) 902} 903 904func Pwrite(fd int, b []byte, offset int64) (int, error) { 905 var nwritten size 906 errno := fd_pwrite(int32(fd), makeIOVec(b), 1, filesize(offset), unsafe.Pointer(&nwritten)) 907 runtime.KeepAlive(b) 908 return int(nwritten), errnoErr(errno) 909} 910 911func Seek(fd int, offset int64, whence int) (int64, error) { 912 var newoffset filesize 913 errno := fd_seek(int32(fd), filedelta(offset), uint32(whence), unsafe.Pointer(&newoffset)) 914 return int64(newoffset), errnoErr(errno) 915} 916 917func Dup(fd int) (int, error) { 918 return 0, ENOSYS 919} 920 921func Dup2(fd, newfd int) error { 922 return ENOSYS 923} 924 925func Pipe(fd []int) error { 926 return ENOSYS 927} 928 929func RandomGet(b []byte) error { 930 errno := random_get(bytesPointer(b), size(len(b))) 931 return errnoErr(errno) 932} 933