github.com/tetratelabs/wazero@v1.2.1/imports/wasi_snapshot_preview1/fs.go (about) 1 package wasi_snapshot_preview1 2 3 import ( 4 "context" 5 "io" 6 "io/fs" 7 "math" 8 "path" 9 "strings" 10 "syscall" 11 "unsafe" 12 13 "github.com/tetratelabs/wazero/api" 14 "github.com/tetratelabs/wazero/internal/fsapi" 15 socketapi "github.com/tetratelabs/wazero/internal/sock" 16 "github.com/tetratelabs/wazero/internal/sys" 17 "github.com/tetratelabs/wazero/internal/sysfs" 18 "github.com/tetratelabs/wazero/internal/wasip1" 19 "github.com/tetratelabs/wazero/internal/wasm" 20 ) 21 22 // fdAdvise is the WASI function named FdAdviseName which provides file 23 // advisory information on a file descriptor. 24 // 25 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_advisefd-fd-offset-filesize-len-filesize-advice-advice---errno 26 var fdAdvise = newHostFunc( 27 wasip1.FdAdviseName, fdAdviseFn, 28 []wasm.ValueType{i32, i64, i64, i32}, 29 "fd", "offset", "len", "advice", 30 ) 31 32 func fdAdviseFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 33 fd := int32(params[0]) 34 _ = params[1] 35 _ = params[2] 36 advice := byte(params[3]) 37 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 38 39 _, ok := fsc.LookupFile(fd) 40 if !ok { 41 return syscall.EBADF 42 } 43 44 switch advice { 45 case wasip1.FdAdviceNormal, 46 wasip1.FdAdviceSequential, 47 wasip1.FdAdviceRandom, 48 wasip1.FdAdviceWillNeed, 49 wasip1.FdAdviceDontNeed, 50 wasip1.FdAdviceNoReuse: 51 default: 52 return syscall.EINVAL 53 } 54 55 // FdAdvice corresponds to posix_fadvise, but it can only be supported on linux. 56 // However, the purpose of the call is just to do best-effort optimization on OS kernels, 57 // so just making this noop rather than returning NoSup error makes sense and doesn't affect 58 // the semantics of Wasm applications. 59 // TODO: invoke posix_fadvise on linux, and partially on darwin. 60 // - https://gitlab.com/cznic/fileutil/-/blob/v1.1.2/fileutil_linux.go#L87-95 61 // - https://github.com/bytecodealliance/system-interface/blob/62b97f9776b86235f318c3a6e308395a1187439b/src/fs/file_io_ext.rs#L430-L442 62 return 0 63 } 64 65 // fdAllocate is the WASI function named FdAllocateName which forces the 66 // allocation of space in a file. 67 // 68 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_allocatefd-fd-offset-filesize-len-filesize---errno 69 var fdAllocate = newHostFunc( 70 wasip1.FdAllocateName, fdAllocateFn, 71 []wasm.ValueType{i32, i64, i64}, 72 "fd", "offset", "len", 73 ) 74 75 func fdAllocateFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 76 fd := int32(params[0]) 77 offset := params[1] 78 length := params[2] 79 80 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 81 f, ok := fsc.LookupFile(fd) 82 if !ok { 83 return syscall.EBADF 84 } 85 86 tail := int64(offset + length) 87 if tail < 0 { 88 return syscall.EINVAL 89 } 90 91 st, errno := f.File.Stat() 92 if errno != 0 { 93 return errno 94 } 95 96 if st.Size >= tail { 97 return 0 // We already have enough space. 98 } 99 100 return f.File.Truncate(tail) 101 } 102 103 // fdClose is the WASI function named FdCloseName which closes a file 104 // descriptor. 105 // 106 // # Parameters 107 // 108 // - fd: file descriptor to close 109 // 110 // Result (Errno) 111 // 112 // The return value is 0 except the following error conditions: 113 // - syscall.EBADF: the fd was not open. 114 // - syscall.ENOTSUP: the fs was a pre-open 115 // 116 // Note: This is similar to `close` in POSIX. 117 // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_close 118 // and https://linux.die.net/man/3/close 119 var fdClose = newHostFunc(wasip1.FdCloseName, fdCloseFn, []api.ValueType{i32}, "fd") 120 121 func fdCloseFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 122 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 123 fd := int32(params[0]) 124 125 return fsc.CloseFile(fd) 126 } 127 128 // fdDatasync is the WASI function named FdDatasyncName which synchronizes 129 // the data of a file to disk. 130 // 131 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_datasyncfd-fd---errno 132 var fdDatasync = newHostFunc(wasip1.FdDatasyncName, fdDatasyncFn, []api.ValueType{i32}, "fd") 133 134 func fdDatasyncFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 135 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 136 fd := int32(params[0]) 137 138 // Check to see if the file descriptor is available 139 if f, ok := fsc.LookupFile(fd); !ok { 140 return syscall.EBADF 141 } else { 142 return f.File.Datasync() 143 } 144 } 145 146 // fdFdstatGet is the WASI function named FdFdstatGetName which returns the 147 // attributes of a file descriptor. 148 // 149 // # Parameters 150 // 151 // - fd: file descriptor to get the fdstat attributes data 152 // - resultFdstat: offset to write the result fdstat data 153 // 154 // Result (Errno) 155 // 156 // The return value is 0 except the following error conditions: 157 // - syscall.EBADF: `fd` is invalid 158 // - syscall.EFAULT: `resultFdstat` points to an offset out of memory 159 // 160 // fdstat byte layout is 24-byte size, with the following fields: 161 // - fs_filetype 1 byte: the file type 162 // - fs_flags 2 bytes: the file descriptor flag 163 // - 5 pad bytes 164 // - fs_right_base 8 bytes: ignored as rights were removed from WASI. 165 // - fs_right_inheriting 8 bytes: ignored as rights were removed from WASI. 166 // 167 // For example, with a file corresponding with `fd` was a directory (=3) opened 168 // with `fd_read` right (=1) and no fs_flags (=0), parameter resultFdstat=1, 169 // this function writes the below to api.Memory: 170 // 171 // uint16le padding uint64le uint64le 172 // uint8 --+ +--+ +-----------+ +--------------------+ +--------------------+ 173 // | | | | | | | | | 174 // []byte{?, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0} 175 // resultFdstat --^ ^-- fs_flags ^-- fs_right_base ^-- fs_right_inheriting 176 // | 177 // +-- fs_filetype 178 // 179 // Note: fdFdstatGet returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as 180 // well as additional fields. 181 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdstat 182 // and https://linux.die.net/man/3/fsync 183 var fdFdstatGet = newHostFunc(wasip1.FdFdstatGetName, fdFdstatGetFn, []api.ValueType{i32, i32}, "fd", "result.stat") 184 185 // fdFdstatGetFn cannot currently use proxyResultParams because fdstat is larger 186 // than api.ValueTypeI64 (i64 == 8 bytes, but fdstat is 24). 187 func fdFdstatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 188 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 189 190 fd, resultFdstat := int32(params[0]), uint32(params[1]) 191 192 // Ensure we can write the fdstat 193 buf, ok := mod.Memory().Read(resultFdstat, 24) 194 if !ok { 195 return syscall.EFAULT 196 } 197 198 var fdflags uint16 199 var st fsapi.Stat_t 200 var errno syscall.Errno 201 f, ok := fsc.LookupFile(fd) 202 if !ok { 203 return syscall.EBADF 204 } else if st, errno = f.File.Stat(); errno != 0 { 205 return errno 206 } else if f.File.IsAppend() { 207 fdflags |= wasip1.FD_APPEND 208 } 209 210 if f.File.IsNonblock() { 211 fdflags |= wasip1.FD_NONBLOCK 212 } 213 214 var fsRightsBase uint32 215 var fsRightsInheriting uint32 216 fileType := getExtendedWasiFiletype(f.File, st.Mode) 217 218 switch fileType { 219 case wasip1.FILETYPE_DIRECTORY: 220 // To satisfy wasi-testsuite, we must advertise that directories cannot 221 // be given seek permission (RIGHT_FD_SEEK). 222 fsRightsBase = dirRightsBase 223 fsRightsInheriting = fileRightsBase | dirRightsBase 224 case wasip1.FILETYPE_CHARACTER_DEVICE: 225 // According to wasi-libc, 226 // > A tty is a character device that we can't seek or tell on. 227 // See https://github.com/WebAssembly/wasi-libc/blob/a6f871343313220b76009827ed0153586361c0d5/libc-bottom-half/sources/isatty.c#L13-L18 228 fsRightsBase = fileRightsBase &^ wasip1.RIGHT_FD_SEEK &^ wasip1.RIGHT_FD_TELL 229 default: 230 fsRightsBase = fileRightsBase 231 } 232 233 writeFdstat(buf, fileType, fdflags, fsRightsBase, fsRightsInheriting) 234 return 0 235 } 236 237 // isPreopenedStdio returns true if the FD is sys.FdStdin, sys.FdStdout or 238 // sys.FdStderr and pre-opened. This double check is needed in case the guest 239 // closes stdin and re-opens it with a random alternative file. 240 // 241 // Currently, we only support non-blocking mode for standard I/O streams. 242 // Non-blocking mode is rarely supported for regular files, and we don't 243 // yet have support for sockets, so we make a special case. 244 // 245 // Note: this to get or set FD_NONBLOCK, but skip FD_APPEND. Our current 246 // implementation can't set FD_APPEND, without re-opening files. As stdio are 247 // pre-opened, we don't know how to re-open them, neither should we close the 248 // underlying file. Later, we could add support for setting FD_APPEND, similar 249 // to SetNonblock. 250 func isPreopenedStdio(fd int32, f *sys.FileEntry) bool { 251 return fd <= sys.FdStderr && f.IsPreopen 252 } 253 254 const fileRightsBase = wasip1.RIGHT_FD_DATASYNC | 255 wasip1.RIGHT_FD_READ | 256 wasip1.RIGHT_FD_SEEK | 257 wasip1.RIGHT_FDSTAT_SET_FLAGS | 258 wasip1.RIGHT_FD_SYNC | 259 wasip1.RIGHT_FD_TELL | 260 wasip1.RIGHT_FD_WRITE | 261 wasip1.RIGHT_FD_ADVISE | 262 wasip1.RIGHT_FD_ALLOCATE | 263 wasip1.RIGHT_FD_FILESTAT_GET | 264 wasip1.RIGHT_FD_FILESTAT_SET_SIZE | 265 wasip1.RIGHT_FD_FILESTAT_SET_TIMES | 266 wasip1.RIGHT_POLL_FD_READWRITE 267 268 const dirRightsBase = wasip1.RIGHT_FD_DATASYNC | 269 wasip1.RIGHT_FDSTAT_SET_FLAGS | 270 wasip1.RIGHT_FD_SYNC | 271 wasip1.RIGHT_PATH_CREATE_DIRECTORY | 272 wasip1.RIGHT_PATH_CREATE_FILE | 273 wasip1.RIGHT_PATH_LINK_SOURCE | 274 wasip1.RIGHT_PATH_LINK_TARGET | 275 wasip1.RIGHT_PATH_OPEN | 276 wasip1.RIGHT_FD_READDIR | 277 wasip1.RIGHT_PATH_READLINK | 278 wasip1.RIGHT_PATH_RENAME_SOURCE | 279 wasip1.RIGHT_PATH_RENAME_TARGET | 280 wasip1.RIGHT_PATH_FILESTAT_GET | 281 wasip1.RIGHT_PATH_FILESTAT_SET_SIZE | 282 wasip1.RIGHT_PATH_FILESTAT_SET_TIMES | 283 wasip1.RIGHT_FD_FILESTAT_GET | 284 wasip1.RIGHT_FD_FILESTAT_SET_TIMES | 285 wasip1.RIGHT_PATH_SYMLINK | 286 wasip1.RIGHT_PATH_REMOVE_DIRECTORY | 287 wasip1.RIGHT_PATH_UNLINK_FILE 288 289 func writeFdstat(buf []byte, fileType uint8, fdflags uint16, fsRightsBase, fsRightsInheriting uint32) { 290 b := (*[24]byte)(buf) 291 le.PutUint16(b[0:], uint16(fileType)) 292 le.PutUint16(b[2:], fdflags) 293 le.PutUint32(b[4:], 0) 294 le.PutUint64(b[8:], uint64(fsRightsBase)) 295 le.PutUint64(b[16:], uint64(fsRightsInheriting)) 296 } 297 298 // fdFdstatSetFlags is the WASI function named FdFdstatSetFlagsName which 299 // adjusts the flags associated with a file descriptor. 300 var fdFdstatSetFlags = newHostFunc(wasip1.FdFdstatSetFlagsName, fdFdstatSetFlagsFn, []wasm.ValueType{i32, i32}, "fd", "flags") 301 302 func fdFdstatSetFlagsFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 303 fd, wasiFlag := int32(params[0]), uint16(params[1]) 304 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 305 306 // Currently we only support APPEND and NONBLOCK. 307 if wasip1.FD_DSYNC&wasiFlag != 0 || wasip1.FD_RSYNC&wasiFlag != 0 || wasip1.FD_SYNC&wasiFlag != 0 { 308 return syscall.EINVAL 309 } 310 311 if f, ok := fsc.LookupFile(fd); !ok { 312 return syscall.EBADF 313 } else { 314 nonblock := wasip1.FD_NONBLOCK&wasiFlag != 0 315 errno := f.File.SetNonblock(nonblock) 316 if errno != 0 { 317 return errno 318 } 319 if stat, err := f.File.Stat(); err == 0 && stat.Mode.IsRegular() { 320 // For normal files, proceed to apply an append flag. 321 append := wasip1.FD_APPEND&wasiFlag != 0 322 return f.File.SetAppend(append) 323 } 324 } 325 326 return 0 327 } 328 329 // fdFdstatSetRights will not be implemented as rights were removed from WASI. 330 // 331 // See https://github.com/bytecodealliance/wasmtime/pull/4666 332 var fdFdstatSetRights = stubFunction( 333 wasip1.FdFdstatSetRightsName, 334 []wasm.ValueType{i32, i64, i64}, 335 "fd", "fs_rights_base", "fs_rights_inheriting", 336 ) 337 338 // fdFilestatGet is the WASI function named FdFilestatGetName which returns 339 // the stat attributes of an open file. 340 // 341 // # Parameters 342 // 343 // - fd: file descriptor to get the filestat attributes data for 344 // - resultFilestat: offset to write the result filestat data 345 // 346 // Result (Errno) 347 // 348 // The return value is 0 except the following error conditions: 349 // - syscall.EBADF: `fd` is invalid 350 // - syscall.EIO: could not stat `fd` on filesystem 351 // - syscall.EFAULT: `resultFilestat` points to an offset out of memory 352 // 353 // filestat byte layout is 64-byte size, with the following fields: 354 // - dev 8 bytes: the device ID of device containing the file 355 // - ino 8 bytes: the file serial number 356 // - filetype 1 byte: the type of the file 357 // - 7 pad bytes 358 // - nlink 8 bytes: number of hard links to the file 359 // - size 8 bytes: for regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link 360 // - atim 8 bytes: ast data access timestamp 361 // - mtim 8 bytes: last data modification timestamp 362 // - ctim 8 bytes: ast file status change timestamp 363 // 364 // For example, with a regular file this function writes the below to api.Memory: 365 // 366 // uint8 --+ 367 // uint64le uint64le | padding uint64le uint64le uint64le uint64le uint64le 368 // +--------------------+ +--------------------+ | +-----------------+ +--------------------+ +-----------------------+ +----------------------------------+ +----------------------------------+ +----------------------------------+ 369 // | | | | | | | | | | | | | | | | | 370 // []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 80, 0, 0, 0, 0, 0, 0, 160, 153, 212, 128, 110, 221, 35, 23, 160, 153, 212, 128, 110, 221, 35, 23, 160, 153, 212, 128, 110, 221, 35, 23} 371 // resultFilestat ^-- dev ^-- ino ^ ^-- nlink ^-- size ^-- atim ^-- mtim ^-- ctim 372 // | 373 // +-- filetype 374 // 375 // The following properties of filestat are not implemented: 376 // - dev: not supported by Golang FS 377 // - ino: not supported by Golang FS 378 // - nlink: not supported by Golang FS, we use 1 379 // - atime: not supported by Golang FS, we use mtim for this 380 // - ctim: not supported by Golang FS, we use mtim for this 381 // 382 // Note: This is similar to `fstat` in POSIX. 383 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_getfd-fd---errno-filestat 384 // and https://linux.die.net/man/3/fstat 385 var fdFilestatGet = newHostFunc(wasip1.FdFilestatGetName, fdFilestatGetFn, []api.ValueType{i32, i32}, "fd", "result.filestat") 386 387 // fdFilestatGetFn cannot currently use proxyResultParams because filestat is 388 // larger than api.ValueTypeI64 (i64 == 8 bytes, but filestat is 64). 389 func fdFilestatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 390 return fdFilestatGetFunc(mod, int32(params[0]), uint32(params[1])) 391 } 392 393 func fdFilestatGetFunc(mod api.Module, fd int32, resultBuf uint32) syscall.Errno { 394 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 395 396 // Ensure we can write the filestat 397 buf, ok := mod.Memory().Read(resultBuf, 64) 398 if !ok { 399 return syscall.EFAULT 400 } 401 402 f, ok := fsc.LookupFile(fd) 403 if !ok { 404 return syscall.EBADF 405 } 406 407 st, errno := f.File.Stat() 408 if errno != 0 { 409 return errno 410 } 411 412 filetype := getExtendedWasiFiletype(f.File, st.Mode) 413 return writeFilestat(buf, &st, filetype) 414 } 415 416 func getExtendedWasiFiletype(file fsapi.File, fm fs.FileMode) (ftype uint8) { 417 ftype = getWasiFiletype(fm) 418 if ftype == wasip1.FILETYPE_UNKNOWN { 419 if _, ok := file.(socketapi.TCPSock); ok { 420 ftype = wasip1.FILETYPE_SOCKET_STREAM 421 } else if _, ok = file.(socketapi.TCPConn); ok { 422 ftype = wasip1.FILETYPE_SOCKET_STREAM 423 } 424 } 425 return 426 } 427 428 func getWasiFiletype(fm fs.FileMode) uint8 { 429 switch { 430 case fm.IsRegular(): 431 return wasip1.FILETYPE_REGULAR_FILE 432 case fm.IsDir(): 433 return wasip1.FILETYPE_DIRECTORY 434 case fm&fs.ModeSymlink != 0: 435 return wasip1.FILETYPE_SYMBOLIC_LINK 436 case fm&fs.ModeDevice != 0: 437 // Unlike ModeDevice and ModeCharDevice, FILETYPE_CHARACTER_DEVICE and 438 // FILETYPE_BLOCK_DEVICE are set mutually exclusively. 439 if fm&fs.ModeCharDevice != 0 { 440 return wasip1.FILETYPE_CHARACTER_DEVICE 441 } 442 return wasip1.FILETYPE_BLOCK_DEVICE 443 default: // unknown 444 return wasip1.FILETYPE_UNKNOWN 445 } 446 } 447 448 func writeFilestat(buf []byte, st *fsapi.Stat_t, ftype uint8) (errno syscall.Errno) { 449 le.PutUint64(buf, st.Dev) 450 le.PutUint64(buf[8:], st.Ino) 451 le.PutUint64(buf[16:], uint64(ftype)) 452 le.PutUint64(buf[24:], st.Nlink) 453 le.PutUint64(buf[32:], uint64(st.Size)) 454 le.PutUint64(buf[40:], uint64(st.Atim)) 455 le.PutUint64(buf[48:], uint64(st.Mtim)) 456 le.PutUint64(buf[56:], uint64(st.Ctim)) 457 return 458 } 459 460 // fdFilestatSetSize is the WASI function named FdFilestatSetSizeName which 461 // adjusts the size of an open file. 462 // 463 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---errno 464 var fdFilestatSetSize = newHostFunc(wasip1.FdFilestatSetSizeName, fdFilestatSetSizeFn, []wasm.ValueType{i32, i64}, "fd", "size") 465 466 func fdFilestatSetSizeFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 467 fd := int32(params[0]) 468 size := uint32(params[1]) 469 470 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 471 472 // Check to see if the file descriptor is available 473 if f, ok := fsc.LookupFile(fd); !ok { 474 return syscall.EBADF 475 } else { 476 return f.File.Truncate(int64(size)) 477 } 478 } 479 480 // fdFilestatSetTimes is the WASI function named functionFdFilestatSetTimes 481 // which adjusts the times of an open file. 482 // 483 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_timesfd-fd-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno 484 var fdFilestatSetTimes = newHostFunc( 485 wasip1.FdFilestatSetTimesName, fdFilestatSetTimesFn, 486 []wasm.ValueType{i32, i64, i64, i32}, 487 "fd", "atim", "mtim", "fst_flags", 488 ) 489 490 func fdFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 491 fd := int32(params[0]) 492 atim := int64(params[1]) 493 mtim := int64(params[2]) 494 fstFlags := uint16(params[3]) 495 496 sys := mod.(*wasm.ModuleInstance).Sys 497 fsc := sys.FS() 498 499 f, ok := fsc.LookupFile(fd) 500 if !ok { 501 return syscall.EBADF 502 } 503 504 times, errno := toTimes(atim, mtim, fstFlags) 505 if errno != 0 { 506 return errno 507 } 508 509 // Try to update the file timestamps by file-descriptor. 510 errno = f.File.Utimens(×) 511 512 // Fall back to path based, despite it being less precise. 513 switch errno { 514 case syscall.EPERM, syscall.ENOSYS: 515 errno = f.FS.Utimens(f.Name, ×, true) 516 } 517 518 return errno 519 } 520 521 func toTimes(atim, mtime int64, fstFlags uint16) (times [2]syscall.Timespec, errno syscall.Errno) { 522 // times[0] == atim, times[1] == mtim 523 524 // coerce atim into a timespec 525 if set, now := fstFlags&wasip1.FstflagsAtim != 0, fstFlags&wasip1.FstflagsAtimNow != 0; set && now { 526 errno = syscall.EINVAL 527 return 528 } else if set { 529 times[0] = syscall.NsecToTimespec(atim) 530 } else if now { 531 times[0].Nsec = sysfs.UTIME_NOW 532 } else { 533 times[0].Nsec = sysfs.UTIME_OMIT 534 } 535 536 // coerce mtim into a timespec 537 if set, now := fstFlags&wasip1.FstflagsMtim != 0, fstFlags&wasip1.FstflagsMtimNow != 0; set && now { 538 errno = syscall.EINVAL 539 return 540 } else if set { 541 times[1] = syscall.NsecToTimespec(mtime) 542 } else if now { 543 times[1].Nsec = sysfs.UTIME_NOW 544 } else { 545 times[1].Nsec = sysfs.UTIME_OMIT 546 } 547 return 548 } 549 550 // fdPread is the WASI function named FdPreadName which reads from a file 551 // descriptor, without using and updating the file descriptor's offset. 552 // 553 // Except for handling offset, this implementation is identical to fdRead. 554 // 555 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---errno-size 556 var fdPread = newHostFunc( 557 wasip1.FdPreadName, fdPreadFn, 558 []api.ValueType{i32, i32, i32, i64, i32}, 559 "fd", "iovs", "iovs_len", "offset", "result.nread", 560 ) 561 562 func fdPreadFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 563 return fdReadOrPread(mod, params, true) 564 } 565 566 // fdPrestatGet is the WASI function named FdPrestatGetName which returns 567 // the prestat data of a file descriptor. 568 // 569 // # Parameters 570 // 571 // - fd: file descriptor to get the prestat 572 // - resultPrestat: offset to write the result prestat data 573 // 574 // Result (Errno) 575 // 576 // The return value is 0 except the following error conditions: 577 // - syscall.EBADF: `fd` is invalid or the `fd` is not a pre-opened directory 578 // - syscall.EFAULT: `resultPrestat` points to an offset out of memory 579 // 580 // prestat byte layout is 8 bytes, beginning with an 8-bit tag and 3 pad bytes. 581 // The only valid tag is `prestat_dir`, which is tag zero. This simplifies the 582 // byte layout to 4 empty bytes followed by the uint32le encoded path length. 583 // 584 // For example, the directory name corresponding with `fd` was "/tmp" and 585 // parameter resultPrestat=1, this function writes the below to api.Memory: 586 // 587 // padding uint32le 588 // uint8 --+ +-----+ +--------+ 589 // | | | | | 590 // []byte{?, 0, 0, 0, 0, 4, 0, 0, 0, ?} 591 // resultPrestat --^ ^ 592 // tag --+ | 593 // +-- size in bytes of the string "/tmp" 594 // 595 // See fdPrestatDirName and 596 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat 597 var fdPrestatGet = newHostFunc(wasip1.FdPrestatGetName, fdPrestatGetFn, []api.ValueType{i32, i32}, "fd", "result.prestat") 598 599 func fdPrestatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 600 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 601 fd, resultPrestat := int32(params[0]), uint32(params[1]) 602 603 name, errno := preopenPath(fsc, fd) 604 if errno != 0 { 605 return errno 606 } 607 608 // Upper 32-bits are zero because... 609 // * Zero-value 8-bit tag, and 3-byte zero-value padding 610 prestat := uint64(len(name) << 32) 611 if !mod.Memory().WriteUint64Le(resultPrestat, prestat) { 612 return syscall.EFAULT 613 } 614 return 0 615 } 616 617 // fdPrestatDirName is the WASI function named FdPrestatDirNameName which 618 // returns the path of the pre-opened directory of a file descriptor. 619 // 620 // # Parameters 621 // 622 // - fd: file descriptor to get the path of the pre-opened directory 623 // - path: offset in api.Memory to write the result path 624 // - pathLen: count of bytes to write to `path` 625 // - This should match the uint32le fdPrestatGet writes to offset 626 // `resultPrestat`+4 627 // 628 // Result (Errno) 629 // 630 // The return value is 0 except the following error conditions: 631 // - syscall.EBADF: `fd` is invalid 632 // - syscall.EFAULT: `path` points to an offset out of memory 633 // - syscall.ENAMETOOLONG: `pathLen` is longer than the actual length of the result 634 // 635 // For example, the directory name corresponding with `fd` was "/tmp" and 636 // # Parameters path=1 pathLen=4 (correct), this function will write the below to 637 // api.Memory: 638 // 639 // pathLen 640 // +--------------+ 641 // | | 642 // []byte{?, '/', 't', 'm', 'p', ?} 643 // path --^ 644 // 645 // See fdPrestatGet 646 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_dir_name 647 var fdPrestatDirName = newHostFunc( 648 wasip1.FdPrestatDirNameName, fdPrestatDirNameFn, 649 []api.ValueType{i32, i32, i32}, 650 "fd", "result.path", "result.path_len", 651 ) 652 653 func fdPrestatDirNameFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 654 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 655 fd, path, pathLen := int32(params[0]), uint32(params[1]), uint32(params[2]) 656 657 name, errno := preopenPath(fsc, fd) 658 if errno != 0 { 659 return errno 660 } 661 662 // Some runtimes may have another semantics. See /RATIONALE.md 663 if uint32(len(name)) < pathLen { 664 return syscall.ENAMETOOLONG 665 } 666 667 if !mod.Memory().Write(path, []byte(name)[:pathLen]) { 668 return syscall.EFAULT 669 } 670 return 0 671 } 672 673 // fdPwrite is the WASI function named FdPwriteName which writes to a file 674 // descriptor, without using and updating the file descriptor's offset. 675 // 676 // Except for handling offset, this implementation is identical to fdWrite. 677 // 678 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_pwritefd-fd-iovs-ciovec_array-offset-filesize---errno-size 679 var fdPwrite = newHostFunc( 680 wasip1.FdPwriteName, fdPwriteFn, 681 []api.ValueType{i32, i32, i32, i64, i32}, 682 "fd", "iovs", "iovs_len", "offset", "result.nwritten", 683 ) 684 685 func fdPwriteFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 686 return fdWriteOrPwrite(mod, params, true) 687 } 688 689 // fdRead is the WASI function named FdReadName which reads from a file 690 // descriptor. 691 // 692 // # Parameters 693 // 694 // - fd: an opened file descriptor to read data from 695 // - iovs: offset in api.Memory to read offset, size pairs representing where 696 // to write file data 697 // - Both offset and length are encoded as uint32le 698 // - iovsCount: count of memory offset, size pairs to read sequentially 699 // starting at iovs 700 // - resultNread: offset in api.Memory to write the number of bytes read 701 // 702 // Result (Errno) 703 // 704 // The return value is 0 except the following error conditions: 705 // - syscall.EBADF: `fd` is invalid 706 // - syscall.EFAULT: `iovs` or `resultNread` point to an offset out of memory 707 // - syscall.EIO: a file system error 708 // 709 // For example, this function needs to first read `iovs` to determine where 710 // to write contents. If parameters iovs=1 iovsCount=2, this function reads two 711 // offset/length pairs from api.Memory: 712 // 713 // iovs[0] iovs[1] 714 // +---------------------+ +--------------------+ 715 // | uint32le uint32le| |uint32le uint32le| 716 // +---------+ +--------+ +--------+ +--------+ 717 // | | | | | | | | 718 // []byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... } 719 // iovs --^ ^ ^ ^ 720 // | | | | 721 // offset --+ length --+ offset --+ length --+ 722 // 723 // If the contents of the `fd` parameter was "wazero" (6 bytes) and parameter 724 // resultNread=26, this function writes the below to api.Memory: 725 // 726 // iovs[0].length iovs[1].length 727 // +--------------+ +----+ uint32le 728 // | | | | +--------+ 729 // []byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ?, 6, 0, 0, 0 } 730 // iovs[0].offset --^ ^ ^ 731 // iovs[1].offset --+ | 732 // resultNread --+ 733 // 734 // Note: This is similar to `readv` in POSIX. https://linux.die.net/man/3/readv 735 // 736 // See fdWrite 737 // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read 738 var fdRead = newHostFunc( 739 wasip1.FdReadName, fdReadFn, 740 []api.ValueType{i32, i32, i32, i32}, 741 "fd", "iovs", "iovs_len", "result.nread", 742 ) 743 744 // preader tracks an offset across multiple reads. 745 type preader struct { 746 f fsapi.File 747 offset int64 748 } 749 750 // Read implements the same function as documented on internalapi.File. 751 func (w *preader) Read(buf []byte) (n int, errno syscall.Errno) { 752 if len(buf) == 0 { 753 return 0, 0 // less overhead on zero-length reads. 754 } 755 756 n, err := w.f.Pread(buf, w.offset) 757 w.offset += int64(n) 758 return n, err 759 } 760 761 func fdReadFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 762 return fdReadOrPread(mod, params, false) 763 } 764 765 func fdReadOrPread(mod api.Module, params []uint64, isPread bool) syscall.Errno { 766 mem := mod.Memory() 767 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 768 769 fd := int32(params[0]) 770 iovs := uint32(params[1]) 771 iovsCount := uint32(params[2]) 772 773 var resultNread uint32 774 var reader func(buf []byte) (n int, errno syscall.Errno) 775 if f, ok := fsc.LookupFile(fd); !ok { 776 return syscall.EBADF 777 } else if isPread { 778 offset := int64(params[3]) 779 reader = (&preader{f: f.File, offset: offset}).Read 780 resultNread = uint32(params[4]) 781 } else { 782 reader = f.File.Read 783 resultNread = uint32(params[3]) 784 } 785 786 nread, errno := readv(mem, iovs, iovsCount, reader) 787 if errno != 0 { 788 return errno 789 } 790 if !mem.WriteUint32Le(resultNread, nread) { 791 return syscall.EFAULT 792 } else { 793 return 0 794 } 795 } 796 797 func readv(mem api.Memory, iovs uint32, iovsCount uint32, reader func(buf []byte) (nread int, errno syscall.Errno)) (uint32, syscall.Errno) { 798 var nread uint32 799 iovsStop := iovsCount << 3 // iovsCount * 8 800 iovsBuf, ok := mem.Read(iovs, iovsStop) 801 if !ok { 802 return 0, syscall.EFAULT 803 } 804 805 for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 { 806 offset := le.Uint32(iovsBuf[iovsPos:]) 807 l := le.Uint32(iovsBuf[iovsPos+4:]) 808 809 if l == 0 { // A zero length iovec could be ahead of another. 810 continue 811 } 812 813 b, ok := mem.Read(offset, l) 814 if !ok { 815 return 0, syscall.EFAULT 816 } 817 818 n, errno := reader(b) 819 nread += uint32(n) 820 821 if errno == syscall.ENOSYS { 822 return 0, syscall.EBADF // e.g. unimplemented for read 823 } else if errno != 0 { 824 return 0, errno 825 } else if n < int(l) { 826 break // stop when we read less than capacity. 827 } 828 } 829 return nread, 0 830 } 831 832 // fdReaddir is the WASI function named FdReaddirName which reads directory 833 // entries from a directory. 834 // 835 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_readdirfd-fd-buf-pointeru8-buf_len-size-cookie-dircookie---errno-size 836 var fdReaddir = newHostFunc( 837 wasip1.FdReaddirName, fdReaddirFn, 838 []wasm.ValueType{i32, i32, i32, i64, i32}, 839 "fd", "buf", "buf_len", "cookie", "result.bufused", 840 ) 841 842 func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 843 mem := mod.Memory() 844 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 845 846 fd := int32(params[0]) 847 buf := uint32(params[1]) 848 bufLen := uint32(params[2]) 849 // We control the value of the cookie, and it should never be negative. 850 // However, we coerce it to signed to ensure the caller doesn't manipulate 851 // it in such a way that becomes negative. 852 cookie := int64(params[3]) 853 resultBufused := uint32(params[4]) 854 855 // The bufLen must be enough to write a dirent. Otherwise, the caller can't 856 // read what the next cookie is. 857 if bufLen < wasip1.DirentSize { 858 return syscall.EINVAL 859 } 860 861 // Validate the FD is a directory 862 f, errno := openedDir(fsc, fd) 863 if errno != 0 { 864 return errno 865 } 866 // Discard the bool value because we validated the fd already. 867 dir, errno := fsc.LookupReaddir(fd, f) 868 if errno != 0 { 869 return errno 870 } 871 // Validate the cookie and possibly sync the internal state to the one the cookie represents. 872 if errno = dir.Rewind(cookie); errno != 0 { 873 return errno 874 } 875 876 // Determine how many dirents we can write, excluding a potentially 877 // truncated entry. 878 dirents, bufused, direntCount, writeTruncatedEntry := maxDirents(dir, bufLen) 879 880 // Now, write entries to the underlying buffer. 881 if bufused > 0 { 882 buf, ok := mem.Read(buf, bufused) 883 if !ok { 884 return syscall.EFAULT 885 } 886 887 // Iterate again on dir, this time writing the entries. 888 writeDirents(dirents, direntCount, writeTruncatedEntry, buf, uint64(cookie+1)) 889 } 890 891 if !mem.WriteUint32Le(resultBufused, bufused) { 892 return syscall.EFAULT 893 } 894 return 0 895 } 896 897 const largestDirent = int64(math.MaxUint32 - wasip1.DirentSize) 898 899 // maxDirents returns the maximum count and total entries that can fit in 900 // maxLen bytes. 901 // 902 // truncatedEntryLen is the amount of bytes past bufLen needed to write the 903 // next entry. We have to return bufused == bufLen unless the directory is 904 // exhausted. 905 // 906 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir 907 // See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L44 908 func maxDirents(dir *sys.Readdir, bufLen uint32) (dirents []fsapi.Dirent, bufused, direntCount uint32, writeTruncatedEntry bool) { 909 lenRemaining := bufLen 910 for { 911 d, errno := dir.Peek() 912 if errno != 0 { 913 return 914 } 915 dirents = append(dirents, *d) 916 917 if lenRemaining < wasip1.DirentSize { 918 // We don't have enough space in bufLen for another struct, 919 // entry. A caller who wants more will retry. 920 921 // bufused == bufLen means more dirents exist, which is the case 922 // when the dirent is larger than bytes remaining. 923 bufused = bufLen 924 break 925 } 926 927 // use int64 to guard against huge filenames 928 nameLen := int64(len(d.Name)) 929 var entryLen uint32 930 931 // Check to see if DirentSize + nameLen overflows, or if it would be 932 // larger than possible to encode. 933 if el := int64(wasip1.DirentSize) + nameLen; el < 0 || el > largestDirent { 934 // panic, as testing is difficult. ex we would have to extract a 935 // function to get size of a string or allocate a 2^32 size one! 936 panic("invalid filename: too large") 937 } else { // we know this can fit into a uint32 938 entryLen = uint32(el) 939 } 940 941 if entryLen > lenRemaining { 942 // We haven't room to write the entry, and docs say to write the 943 // header. This helps especially when there is an entry with a very 944 // long filename. Ex if bufLen is 4096 and the filename is 4096, 945 // we need to write DirentSize(24) + 4096 bytes to write the entry. 946 // In this case, we only write up to DirentSize(24) to allow the 947 // caller to resize. 948 949 // bufused == bufLen means more dirents exist, which is the case 950 // when the next entry is larger than bytes remaining. 951 bufused = bufLen 952 953 // We do have enough space to write the header, this value will be 954 // passed on to writeDirents to only write the header for this entry. 955 writeTruncatedEntry = true 956 break 957 } 958 959 // This won't go negative because we checked entryLen <= lenRemaining. 960 lenRemaining -= entryLen 961 bufused += entryLen 962 direntCount++ 963 _ = dir.Advance() 964 } 965 return 966 } 967 968 // writeDirents writes the directory entries to the buffer, which is pre-sized 969 // based on maxDirents. truncatedEntryLen means write one past entryCount, 970 // without its name. See maxDirents for why 971 func writeDirents( 972 dirents []fsapi.Dirent, 973 direntCount uint32, 974 writeTruncatedEntry bool, 975 buf []byte, 976 d_next uint64, 977 ) { 978 pos, i := uint32(0), uint32(0) 979 for ; i < direntCount; i++ { 980 e := dirents[i] 981 nameLen := uint32(len(e.Name)) 982 983 writeDirent(buf[pos:], d_next, e.Ino, nameLen, e.Type) 984 pos += wasip1.DirentSize 985 986 copy(buf[pos:], e.Name) 987 pos += nameLen 988 d_next++ 989 } 990 991 if !writeTruncatedEntry { 992 return 993 } 994 995 // Write a dirent without its name 996 dirent := make([]byte, wasip1.DirentSize) 997 e := dirents[i] 998 writeDirent(dirent, d_next, e.Ino, uint32(len(e.Name)), e.Type) 999 1000 // Potentially truncate it 1001 copy(buf[pos:], dirent) 1002 } 1003 1004 // writeDirent writes DirentSize bytes 1005 func writeDirent(buf []byte, dNext uint64, ino uint64, dNamlen uint32, dType fs.FileMode) { 1006 le.PutUint64(buf, dNext) // d_next 1007 le.PutUint64(buf[8:], ino) // d_ino 1008 le.PutUint32(buf[16:], dNamlen) // d_namlen 1009 filetype := getWasiFiletype(dType) 1010 le.PutUint32(buf[20:], uint32(filetype)) // d_type 1011 } 1012 1013 // openedDir returns the sys.FileEntry for the directory and 0 if the fd points to a readable directory. 1014 func openedDir(fsc *sys.FSContext, fd int32) (*sys.FileEntry, syscall.Errno) { 1015 if f, ok := fsc.LookupFile(fd); !ok { 1016 return nil, syscall.EBADF 1017 } else if isDir, errno := f.File.IsDir(); errno != 0 { 1018 return nil, errno 1019 } else if !isDir { 1020 // fd_readdir docs don't indicate whether to return syscall.ENOTDIR or 1021 // syscall.EBADF. It has been noticed that rust will crash on syscall.ENOTDIR, 1022 // and POSIX C ref seems to not return this, so we don't either. 1023 // 1024 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir 1025 // and https://en.wikibooks.org/wiki/C_Programming/POSIX_Reference/dirent.h 1026 return nil, syscall.EBADF 1027 } else { 1028 return f, 0 1029 } 1030 } 1031 1032 // fdRenumber is the WASI function named FdRenumberName which atomically 1033 // replaces a file descriptor by renumbering another file descriptor. 1034 // 1035 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno 1036 var fdRenumber = newHostFunc(wasip1.FdRenumberName, fdRenumberFn, []wasm.ValueType{i32, i32}, "fd", "to") 1037 1038 func fdRenumberFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1039 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1040 1041 from := int32(params[0]) 1042 to := int32(params[1]) 1043 1044 if errno := fsc.Renumber(from, to); errno != 0 { 1045 return errno 1046 } 1047 return 0 1048 } 1049 1050 // fdSeek is the WASI function named FdSeekName which moves the offset of a 1051 // file descriptor. 1052 // 1053 // # Parameters 1054 // 1055 // - fd: file descriptor to move the offset of 1056 // - offset: signed int64, which is encoded as uint64, input argument to 1057 // `whence`, which results in a new offset 1058 // - whence: operator that creates the new offset, given `offset` bytes 1059 // - If io.SeekStart, new offset == `offset`. 1060 // - If io.SeekCurrent, new offset == existing offset + `offset`. 1061 // - If io.SeekEnd, new offset == file size of `fd` + `offset`. 1062 // - resultNewoffset: offset in api.Memory to write the new offset to, 1063 // relative to start of the file 1064 // 1065 // Result (Errno) 1066 // 1067 // The return value is 0 except the following error conditions: 1068 // - syscall.EBADF: `fd` is invalid 1069 // - syscall.EFAULT: `resultNewoffset` points to an offset out of memory 1070 // - syscall.EINVAL: `whence` is an invalid value 1071 // - syscall.EIO: a file system error 1072 // - syscall.EISDIR: the file was a directory. 1073 // 1074 // For example, if fd 3 is a file with offset 0, and parameters fd=3, offset=4, 1075 // whence=0 (=io.SeekStart), resultNewOffset=1, this function writes the below 1076 // to api.Memory: 1077 // 1078 // uint64le 1079 // +--------------------+ 1080 // | | 1081 // []byte{?, 4, 0, 0, 0, 0, 0, 0, 0, ? } 1082 // resultNewoffset --^ 1083 // 1084 // Note: This is similar to `lseek` in POSIX. https://linux.die.net/man/3/lseek 1085 // 1086 // See io.Seeker 1087 // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_seek 1088 var fdSeek = newHostFunc( 1089 wasip1.FdSeekName, fdSeekFn, 1090 []api.ValueType{i32, i64, i32, i32}, 1091 "fd", "offset", "whence", "result.newoffset", 1092 ) 1093 1094 func fdSeekFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1095 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1096 fd := int32(params[0]) 1097 offset := params[1] 1098 whence := uint32(params[2]) 1099 resultNewoffset := uint32(params[3]) 1100 1101 if f, ok := fsc.LookupFile(fd); !ok { 1102 return syscall.EBADF 1103 } else if isDir, _ := f.File.IsDir(); isDir { 1104 return syscall.EISDIR // POSIX doesn't forbid seeking a directory, but wasi-testsuite does. 1105 } else if newOffset, errno := f.File.Seek(int64(offset), int(whence)); errno != 0 { 1106 return errno 1107 } else if !mod.Memory().WriteUint64Le(resultNewoffset, uint64(newOffset)) { 1108 return syscall.EFAULT 1109 } 1110 return 0 1111 } 1112 1113 // fdSync is the WASI function named FdSyncName which synchronizes the data 1114 // and metadata of a file to disk. 1115 // 1116 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_syncfd-fd---errno 1117 var fdSync = newHostFunc(wasip1.FdSyncName, fdSyncFn, []api.ValueType{i32}, "fd") 1118 1119 func fdSyncFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1120 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1121 fd := int32(params[0]) 1122 1123 // Check to see if the file descriptor is available 1124 if f, ok := fsc.LookupFile(fd); !ok { 1125 return syscall.EBADF 1126 } else { 1127 return f.File.Sync() 1128 } 1129 } 1130 1131 // fdTell is the WASI function named FdTellName which returns the current 1132 // offset of a file descriptor. 1133 // 1134 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_tellfd-fd---errno-filesize 1135 var fdTell = newHostFunc(wasip1.FdTellName, fdTellFn, []api.ValueType{i32, i32}, "fd", "result.offset") 1136 1137 func fdTellFn(ctx context.Context, mod api.Module, params []uint64) syscall.Errno { 1138 fd := params[0] 1139 offset := uint64(0) 1140 whence := uint64(io.SeekCurrent) 1141 resultNewoffset := params[1] 1142 1143 fdSeekParams := []uint64{fd, offset, whence, resultNewoffset} 1144 return fdSeekFn(ctx, mod, fdSeekParams) 1145 } 1146 1147 // fdWrite is the WASI function named FdWriteName which writes to a file 1148 // descriptor. 1149 // 1150 // # Parameters 1151 // 1152 // - fd: an opened file descriptor to write data to 1153 // - iovs: offset in api.Memory to read offset, size pairs representing the 1154 // data to write to `fd` 1155 // - Both offset and length are encoded as uint32le. 1156 // - iovsCount: count of memory offset, size pairs to read sequentially 1157 // starting at iovs 1158 // - resultNwritten: offset in api.Memory to write the number of bytes 1159 // written 1160 // 1161 // Result (Errno) 1162 // 1163 // The return value is 0 except the following error conditions: 1164 // - syscall.EBADF: `fd` is invalid 1165 // - syscall.EFAULT: `iovs` or `resultNwritten` point to an offset out of memory 1166 // - syscall.EIO: a file system error 1167 // 1168 // For example, this function needs to first read `iovs` to determine what to 1169 // write to `fd`. If parameters iovs=1 iovsCount=2, this function reads two 1170 // offset/length pairs from api.Memory: 1171 // 1172 // iovs[0] iovs[1] 1173 // +---------------------+ +--------------------+ 1174 // | uint32le uint32le| |uint32le uint32le| 1175 // +---------+ +--------+ +--------+ +--------+ 1176 // | | | | | | | | 1177 // []byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... } 1178 // iovs --^ ^ ^ ^ 1179 // | | | | 1180 // offset --+ length --+ offset --+ length --+ 1181 // 1182 // This function reads those chunks api.Memory into the `fd` sequentially. 1183 // 1184 // iovs[0].length iovs[1].length 1185 // +--------------+ +----+ 1186 // | | | | 1187 // []byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ? } 1188 // iovs[0].offset --^ ^ 1189 // iovs[1].offset --+ 1190 // 1191 // Since "wazero" was written, if parameter resultNwritten=26, this function 1192 // writes the below to api.Memory: 1193 // 1194 // uint32le 1195 // +--------+ 1196 // | | 1197 // []byte{ 0..24, ?, 6, 0, 0, 0', ? } 1198 // resultNwritten --^ 1199 // 1200 // Note: This is similar to `writev` in POSIX. https://linux.die.net/man/3/writev 1201 // 1202 // See fdRead 1203 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#ciovec 1204 // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write 1205 var fdWrite = newHostFunc( 1206 wasip1.FdWriteName, fdWriteFn, 1207 []api.ValueType{i32, i32, i32, i32}, 1208 "fd", "iovs", "iovs_len", "result.nwritten", 1209 ) 1210 1211 func fdWriteFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1212 return fdWriteOrPwrite(mod, params, false) 1213 } 1214 1215 // pwriter tracks an offset across multiple writes. 1216 type pwriter struct { 1217 f fsapi.File 1218 offset int64 1219 } 1220 1221 // Write implements the same function as documented on internalapi.File. 1222 func (w *pwriter) Write(buf []byte) (n int, errno syscall.Errno) { 1223 if len(buf) == 0 { 1224 return 0, 0 // less overhead on zero-length writes. 1225 } 1226 1227 n, err := w.f.Pwrite(buf, w.offset) 1228 w.offset += int64(n) 1229 return n, err 1230 } 1231 1232 func fdWriteOrPwrite(mod api.Module, params []uint64, isPwrite bool) syscall.Errno { 1233 mem := mod.Memory() 1234 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1235 1236 fd := int32(params[0]) 1237 iovs := uint32(params[1]) 1238 iovsCount := uint32(params[2]) 1239 1240 var resultNwritten uint32 1241 var writer func(buf []byte) (n int, errno syscall.Errno) 1242 if f, ok := fsc.LookupFile(fd); !ok { 1243 return syscall.EBADF 1244 } else if isPwrite { 1245 offset := int64(params[3]) 1246 writer = (&pwriter{f: f.File, offset: offset}).Write 1247 resultNwritten = uint32(params[4]) 1248 } else { 1249 writer = f.File.Write 1250 resultNwritten = uint32(params[3]) 1251 } 1252 1253 nwritten, errno := writev(mem, iovs, iovsCount, writer) 1254 if errno != 0 { 1255 return errno 1256 } 1257 1258 if !mod.Memory().WriteUint32Le(resultNwritten, nwritten) { 1259 return syscall.EFAULT 1260 } 1261 return 0 1262 } 1263 1264 func writev(mem api.Memory, iovs uint32, iovsCount uint32, writer func(buf []byte) (n int, errno syscall.Errno)) (uint32, syscall.Errno) { 1265 var nwritten uint32 1266 iovsStop := iovsCount << 3 // iovsCount * 8 1267 iovsBuf, ok := mem.Read(iovs, iovsStop) 1268 if !ok { 1269 return 0, syscall.EFAULT 1270 } 1271 1272 for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 { 1273 offset := le.Uint32(iovsBuf[iovsPos:]) 1274 l := le.Uint32(iovsBuf[iovsPos+4:]) 1275 1276 b, ok := mem.Read(offset, l) 1277 if !ok { 1278 return 0, syscall.EFAULT 1279 } 1280 n, errno := writer(b) 1281 nwritten += uint32(n) 1282 if errno == syscall.ENOSYS { 1283 return 0, syscall.EBADF // e.g. unimplemented for write 1284 } else if errno != 0 { 1285 return 0, errno 1286 } 1287 } 1288 return nwritten, 0 1289 } 1290 1291 // pathCreateDirectory is the WASI function named PathCreateDirectoryName which 1292 // creates a directory. 1293 // 1294 // # Parameters 1295 // 1296 // - fd: file descriptor of a directory that `path` is relative to 1297 // - path: offset in api.Memory to read the path string from 1298 // - pathLen: length of `path` 1299 // 1300 // # Result (Errno) 1301 // 1302 // The return value is 0 except the following error conditions: 1303 // - syscall.EBADF: `fd` is invalid 1304 // - syscall.ENOENT: `path` does not exist. 1305 // - syscall.ENOTDIR: `path` is a file 1306 // 1307 // # Notes 1308 // - This is similar to mkdirat in POSIX. 1309 // See https://linux.die.net/man/2/mkdirat 1310 // 1311 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_create_directoryfd-fd-path-string---errno 1312 var pathCreateDirectory = newHostFunc( 1313 wasip1.PathCreateDirectoryName, pathCreateDirectoryFn, 1314 []wasm.ValueType{i32, i32, i32}, 1315 "fd", "path", "path_len", 1316 ) 1317 1318 func pathCreateDirectoryFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1319 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1320 1321 fd := int32(params[0]) 1322 path := uint32(params[1]) 1323 pathLen := uint32(params[2]) 1324 1325 preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen) 1326 if errno != 0 { 1327 return errno 1328 } 1329 1330 if errno = preopen.Mkdir(pathName, 0o700); errno != 0 { 1331 return errno 1332 } 1333 1334 return 0 1335 } 1336 1337 // pathFilestatGet is the WASI function named PathFilestatGetName which 1338 // returns the stat attributes of a file or directory. 1339 // 1340 // # Parameters 1341 // 1342 // - fd: file descriptor of the folder to look in for the path 1343 // - flags: flags determining the method of how paths are resolved 1344 // - path: path under fd to get the filestat attributes data for 1345 // - path_len: length of the path that was given 1346 // - resultFilestat: offset to write the result filestat data 1347 // 1348 // Result (Errno) 1349 // 1350 // The return value is 0 except the following error conditions: 1351 // - syscall.EBADF: `fd` is invalid 1352 // - syscall.ENOTDIR: `fd` points to a file not a directory 1353 // - syscall.EIO: could not stat `fd` on filesystem 1354 // - syscall.EINVAL: the path contained "../" 1355 // - syscall.ENAMETOOLONG: `path` + `path_len` is out of memory 1356 // - syscall.EFAULT: `resultFilestat` points to an offset out of memory 1357 // - syscall.ENOENT: could not find the path 1358 // 1359 // The rest of this implementation matches that of fdFilestatGet, so is not 1360 // repeated here. 1361 // 1362 // Note: This is similar to `fstatat` in POSIX. 1363 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_getfd-fd-flags-lookupflags-path-string---errno-filestat 1364 // and https://linux.die.net/man/2/fstatat 1365 var pathFilestatGet = newHostFunc( 1366 wasip1.PathFilestatGetName, pathFilestatGetFn, 1367 []api.ValueType{i32, i32, i32, i32, i32}, 1368 "fd", "flags", "path", "path_len", "result.filestat", 1369 ) 1370 1371 func pathFilestatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1372 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1373 1374 fd := int32(params[0]) 1375 flags := uint16(params[1]) 1376 path := uint32(params[2]) 1377 pathLen := uint32(params[3]) 1378 1379 preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen) 1380 if errno != 0 { 1381 return errno 1382 } 1383 1384 // Stat the file without allocating a file descriptor. 1385 var st fsapi.Stat_t 1386 1387 if (flags & wasip1.LOOKUP_SYMLINK_FOLLOW) == 0 { 1388 st, errno = preopen.Lstat(pathName) 1389 } else { 1390 st, errno = preopen.Stat(pathName) 1391 } 1392 if errno != 0 { 1393 return errno 1394 } 1395 1396 // Write the stat result to memory 1397 resultBuf := uint32(params[4]) 1398 buf, ok := mod.Memory().Read(resultBuf, 64) 1399 if !ok { 1400 return syscall.EFAULT 1401 } 1402 1403 filetype := getWasiFiletype(st.Mode) 1404 return writeFilestat(buf, &st, filetype) 1405 } 1406 1407 // pathFilestatSetTimes is the WASI function named PathFilestatSetTimesName 1408 // which adjusts the timestamps of a file or directory. 1409 // 1410 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_set_timesfd-fd-flags-lookupflags-path-string-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno 1411 var pathFilestatSetTimes = newHostFunc( 1412 wasip1.PathFilestatSetTimesName, pathFilestatSetTimesFn, 1413 []wasm.ValueType{i32, i32, i32, i32, i64, i64, i32}, 1414 "fd", "flags", "path", "path_len", "atim", "mtim", "fst_flags", 1415 ) 1416 1417 func pathFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1418 fd := int32(params[0]) 1419 flags := uint16(params[1]) 1420 path := uint32(params[2]) 1421 pathLen := uint32(params[3]) 1422 atim := int64(params[4]) 1423 mtim := int64(params[5]) 1424 fstFlags := uint16(params[6]) 1425 1426 sys := mod.(*wasm.ModuleInstance).Sys 1427 fsc := sys.FS() 1428 1429 times, errno := toTimes(atim, mtim, fstFlags) 1430 if errno != 0 { 1431 return errno 1432 } 1433 1434 preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen) 1435 if errno != 0 { 1436 return errno 1437 } 1438 1439 symlinkFollow := flags&wasip1.LOOKUP_SYMLINK_FOLLOW != 0 1440 return preopen.Utimens(pathName, ×, symlinkFollow) 1441 } 1442 1443 // pathLink is the WASI function named PathLinkName which adjusts the 1444 // timestamps of a file or directory. 1445 // 1446 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_link 1447 var pathLink = newHostFunc( 1448 wasip1.PathLinkName, pathLinkFn, 1449 []wasm.ValueType{i32, i32, i32, i32, i32, i32, i32}, 1450 "old_fd", "old_flags", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len", 1451 ) 1452 1453 func pathLinkFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1454 mem := mod.Memory() 1455 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1456 1457 oldFD := int32(params[0]) 1458 // TODO: use old_flags? 1459 _ = uint32(params[1]) 1460 oldPath := uint32(params[2]) 1461 oldPathLen := uint32(params[3]) 1462 1463 oldFS, oldName, errno := atPath(fsc, mem, oldFD, oldPath, oldPathLen) 1464 if errno != 0 { 1465 return errno 1466 } 1467 1468 newFD := int32(params[4]) 1469 newPath := uint32(params[5]) 1470 newPathLen := uint32(params[6]) 1471 1472 newFS, newName, errno := atPath(fsc, mem, newFD, newPath, newPathLen) 1473 if errno != 0 { 1474 return errno 1475 } 1476 1477 if oldFS != newFS { // TODO: handle link across filesystems 1478 return syscall.ENOSYS 1479 } 1480 1481 return oldFS.Link(oldName, newName) 1482 } 1483 1484 // pathOpen is the WASI function named PathOpenName which opens a file or 1485 // directory. This returns syscall.EBADF if the fd is invalid. 1486 // 1487 // # Parameters 1488 // 1489 // - fd: file descriptor of a directory that `path` is relative to 1490 // - dirflags: flags to indicate how to resolve `path` 1491 // - path: offset in api.Memory to read the path string from 1492 // - pathLen: length of `path` 1493 // - oFlags: open flags to indicate the method by which to open the file 1494 // - fsRightsBase: interpret RIGHT_FD_WRITE to set O_RDWR 1495 // - fsRightsInheriting: ignored as rights were removed from WASI. 1496 // created file descriptor for `path` 1497 // - fdFlags: file descriptor flags 1498 // - resultOpenedFD: offset in api.Memory to write the newly created file 1499 // descriptor to. 1500 // - The result FD value is guaranteed to be less than 2**31 1501 // 1502 // Result (Errno) 1503 // 1504 // The return value is 0 except the following error conditions: 1505 // - syscall.EBADF: `fd` is invalid 1506 // - syscall.EFAULT: `resultOpenedFD` points to an offset out of memory 1507 // - syscall.ENOENT: `path` does not exist. 1508 // - syscall.EEXIST: `path` exists, while `oFlags` requires that it must not. 1509 // - syscall.ENOTDIR: `path` is not a directory, while `oFlags` requires it. 1510 // - syscall.EIO: a file system error 1511 // 1512 // For example, this function needs to first read `path` to determine the file 1513 // to open. If parameters `path` = 1, `pathLen` = 6, and the path is "wazero", 1514 // pathOpen reads the path from api.Memory: 1515 // 1516 // pathLen 1517 // +------------------------+ 1518 // | | 1519 // []byte{ ?, 'w', 'a', 'z', 'e', 'r', 'o', ?... } 1520 // path --^ 1521 // 1522 // Then, if parameters resultOpenedFD = 8, and this function opened a new file 1523 // descriptor 5 with the given flags, this function writes the below to 1524 // api.Memory: 1525 // 1526 // uint32le 1527 // +--------+ 1528 // | | 1529 // []byte{ 0..6, ?, 5, 0, 0, 0, ?} 1530 // resultOpenedFD --^ 1531 // 1532 // # Notes 1533 // - This is similar to `openat` in POSIX. https://linux.die.net/man/3/openat 1534 // - The returned file descriptor is not guaranteed to be the lowest-number 1535 // 1536 // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#path_open 1537 var pathOpen = newHostFunc( 1538 wasip1.PathOpenName, pathOpenFn, 1539 []api.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, 1540 "fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags", "result.opened_fd", 1541 ) 1542 1543 func pathOpenFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1544 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1545 1546 preopenFD := int32(params[0]) 1547 1548 // TODO: dirflags is a lookupflags, and it only has one bit: symlink_follow 1549 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#lookupflags 1550 dirflags := uint16(params[1]) 1551 1552 path := uint32(params[2]) 1553 pathLen := uint32(params[3]) 1554 1555 oflags := uint16(params[4]) 1556 1557 rights := uint32(params[5]) 1558 // inherited rights aren't used 1559 _ = params[6] 1560 1561 fdflags := uint16(params[7]) 1562 resultOpenedFD := uint32(params[8]) 1563 1564 preopen, pathName, errno := atPath(fsc, mod.Memory(), preopenFD, path, pathLen) 1565 if errno != 0 { 1566 return errno 1567 } 1568 1569 fileOpenFlags := openFlags(dirflags, oflags, fdflags, rights) 1570 isDir := fileOpenFlags&fsapi.O_DIRECTORY != 0 1571 1572 if isDir && oflags&wasip1.O_CREAT != 0 { 1573 return syscall.EINVAL // use pathCreateDirectory! 1574 } 1575 1576 newFD, errno := fsc.OpenFile(preopen, pathName, fileOpenFlags, 0o600) 1577 if errno != 0 { 1578 return errno 1579 } 1580 1581 // Check any flags that require the file to evaluate. 1582 if isDir { 1583 if f, ok := fsc.LookupFile(newFD); !ok { 1584 return syscall.EBADF // unexpected 1585 } else if isDir, errno := f.File.IsDir(); errno != 0 { 1586 _ = fsc.CloseFile(newFD) 1587 return errno 1588 } else if !isDir { 1589 _ = fsc.CloseFile(newFD) 1590 return syscall.ENOTDIR 1591 } 1592 } 1593 1594 if !mod.Memory().WriteUint32Le(resultOpenedFD, uint32(newFD)) { 1595 _ = fsc.CloseFile(newFD) 1596 return syscall.EFAULT 1597 } 1598 return 0 1599 } 1600 1601 // atPath returns the pre-open specific path after verifying it is a directory. 1602 // 1603 // # Notes 1604 // 1605 // Languages including Zig and Rust use only pre-opens for the FD because 1606 // wasi-libc `__wasilibc_find_relpath` will only return a preopen. That said, 1607 // our wasi.c example shows other languages act differently and can use a non 1608 // pre-opened file descriptor. 1609 // 1610 // We don't handle `AT_FDCWD`, as that's resolved in the compiler. There's no 1611 // working directory function in WASI, so most assume CWD is "/". Notably, Zig 1612 // has different behavior which assumes it is whatever the first pre-open name 1613 // is. 1614 // 1615 // See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/at_fdcwd.c 1616 // See https://linux.die.net/man/2/openat 1617 func atPath(fsc *sys.FSContext, mem api.Memory, fd int32, p, pathLen uint32) (fsapi.FS, string, syscall.Errno) { 1618 b, ok := mem.Read(p, pathLen) 1619 if !ok { 1620 return nil, "", syscall.EFAULT 1621 } 1622 pathName := string(b) 1623 1624 // interesting_paths wants us to break on trailing slash if the input ends 1625 // up a file, not a directory! 1626 hasTrailingSlash := strings.HasSuffix(pathName, "/") 1627 1628 // interesting_paths includes paths that include relative links but end up 1629 // not escaping 1630 pathName = path.Clean(pathName) 1631 1632 // interesting_paths wants to break on root paths or anything that escapes. 1633 // This part is the same as fs.FS.Open() 1634 if !fs.ValidPath(pathName) { 1635 return nil, "", syscall.EPERM 1636 } 1637 1638 // add the trailing slash back 1639 if hasTrailingSlash { 1640 pathName = pathName + "/" 1641 } 1642 1643 if f, ok := fsc.LookupFile(fd); !ok { 1644 return nil, "", syscall.EBADF // closed or invalid 1645 } else if isDir, errno := f.File.IsDir(); errno != 0 { 1646 return nil, "", errno 1647 } else if !isDir { 1648 return nil, "", syscall.ENOTDIR 1649 } else if f.IsPreopen { // don't append the pre-open name 1650 return f.FS, pathName, 0 1651 } else { 1652 // Join via concat to avoid name conflict on path.Join 1653 return f.FS, f.Name + "/" + pathName, 0 1654 } 1655 } 1656 1657 func preopenPath(fsc *sys.FSContext, fd int32) (string, syscall.Errno) { 1658 if f, ok := fsc.LookupFile(fd); !ok { 1659 return "", syscall.EBADF // closed 1660 } else if !f.IsPreopen { 1661 return "", syscall.EBADF 1662 } else if isDir, errno := f.File.IsDir(); errno != 0 || !isDir { 1663 // In wasip1, only directories can be returned by fd_prestat_get as 1664 // there are no prestat types defined for files or sockets. 1665 return "", errno 1666 } else { 1667 return f.Name, 0 1668 } 1669 } 1670 1671 func openFlags(dirflags, oflags, fdflags uint16, rights uint32) (openFlags int) { 1672 if dirflags&wasip1.LOOKUP_SYMLINK_FOLLOW == 0 { 1673 openFlags |= fsapi.O_NOFOLLOW 1674 } 1675 if oflags&wasip1.O_DIRECTORY != 0 { 1676 openFlags |= fsapi.O_DIRECTORY 1677 return // Early return for directories as the rest of flags doesn't make sense for it. 1678 } else if oflags&wasip1.O_EXCL != 0 { 1679 openFlags |= syscall.O_EXCL 1680 } 1681 // Because we don't implement rights, we paritally rely on the open flags 1682 // to determine the mode in which the file will be opened. This will create 1683 // divergent behavior compared to WASI runtimes which have a more strict 1684 // interpretation of the WASI capabilities model; for example, a program 1685 // which sets O_CREAT but does not give read or write permissions will 1686 // successfully create a file when running with wazero, but might get a 1687 // permission denied error on other runtimes. 1688 defaultMode := syscall.O_RDONLY 1689 if oflags&wasip1.O_TRUNC != 0 { 1690 openFlags |= syscall.O_TRUNC 1691 defaultMode = syscall.O_RDWR 1692 } 1693 if oflags&wasip1.O_CREAT != 0 { 1694 openFlags |= syscall.O_CREAT 1695 defaultMode = syscall.O_RDWR 1696 } 1697 if fdflags&wasip1.FD_NONBLOCK != 0 { 1698 openFlags |= syscall.O_NONBLOCK 1699 } 1700 if fdflags&wasip1.FD_APPEND != 0 { 1701 openFlags |= syscall.O_APPEND 1702 defaultMode = syscall.O_RDWR 1703 } 1704 // Since rights were discontinued in wasi, we only interpret RIGHT_FD_WRITE 1705 // because it is the only way to know that we need to set write permissions 1706 // on a file if the application did not pass any of O_CREATE, O_APPEND, nor 1707 // O_TRUNC. 1708 const r = wasip1.RIGHT_FD_READ 1709 const w = wasip1.RIGHT_FD_WRITE 1710 const rw = r | w 1711 switch { 1712 case (rights & rw) == rw: 1713 openFlags |= syscall.O_RDWR 1714 case (rights & w) == w: 1715 openFlags |= syscall.O_WRONLY 1716 case (rights & r) == r: 1717 openFlags |= syscall.O_RDONLY 1718 default: 1719 openFlags |= defaultMode 1720 } 1721 return 1722 } 1723 1724 // pathReadlink is the WASI function named PathReadlinkName that reads the 1725 // contents of a symbolic link. 1726 // 1727 // See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_readlinkfd-fd-path-string-buf-pointeru8-buf_len-size---errno-size 1728 var pathReadlink = newHostFunc( 1729 wasip1.PathReadlinkName, pathReadlinkFn, 1730 []wasm.ValueType{i32, i32, i32, i32, i32, i32}, 1731 "fd", "path", "path_len", "buf", "buf_len", "result.bufused", 1732 ) 1733 1734 func pathReadlinkFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1735 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1736 1737 fd := int32(params[0]) 1738 path := uint32(params[1]) 1739 pathLen := uint32(params[2]) 1740 buf := uint32(params[3]) 1741 bufLen := uint32(params[4]) 1742 resultBufused := uint32(params[5]) 1743 1744 if pathLen == 0 || bufLen == 0 { 1745 return syscall.EINVAL 1746 } 1747 1748 mem := mod.Memory() 1749 preopen, p, errno := atPath(fsc, mem, fd, path, pathLen) 1750 if errno != 0 { 1751 return errno 1752 } 1753 1754 dst, errno := preopen.Readlink(p) 1755 if errno != 0 { 1756 return errno 1757 } 1758 1759 if ok := mem.WriteString(buf, dst); !ok { 1760 return syscall.EFAULT 1761 } 1762 1763 if !mem.WriteUint32Le(resultBufused, uint32(len(dst))) { 1764 return syscall.EFAULT 1765 } 1766 return 0 1767 } 1768 1769 // pathRemoveDirectory is the WASI function named PathRemoveDirectoryName which 1770 // removes a directory. 1771 // 1772 // # Parameters 1773 // 1774 // - fd: file descriptor of a directory that `path` is relative to 1775 // - path: offset in api.Memory to read the path string from 1776 // - pathLen: length of `path` 1777 // 1778 // # Result (Errno) 1779 // 1780 // The return value is 0 except the following error conditions: 1781 // - syscall.EBADF: `fd` is invalid 1782 // - syscall.ENOENT: `path` does not exist. 1783 // - syscall.ENOTEMPTY: `path` is not empty 1784 // - syscall.ENOTDIR: `path` is a file 1785 // 1786 // # Notes 1787 // - This is similar to unlinkat with AT_REMOVEDIR in POSIX. 1788 // See https://linux.die.net/man/2/unlinkat 1789 // 1790 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_remove_directoryfd-fd-path-string---errno 1791 var pathRemoveDirectory = newHostFunc( 1792 wasip1.PathRemoveDirectoryName, pathRemoveDirectoryFn, 1793 []wasm.ValueType{i32, i32, i32}, 1794 "fd", "path", "path_len", 1795 ) 1796 1797 func pathRemoveDirectoryFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1798 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1799 1800 fd := int32(params[0]) 1801 path := uint32(params[1]) 1802 pathLen := uint32(params[2]) 1803 1804 preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen) 1805 if errno != 0 { 1806 return errno 1807 } 1808 1809 return preopen.Rmdir(pathName) 1810 } 1811 1812 // pathRename is the WASI function named PathRenameName which renames a file or 1813 // directory. 1814 // 1815 // # Parameters 1816 // 1817 // - fd: file descriptor of a directory that `old_path` is relative to 1818 // - old_path: offset in api.Memory to read the old path string from 1819 // - old_path_len: length of `old_path` 1820 // - new_fd: file descriptor of a directory that `new_path` is relative to 1821 // - new_path: offset in api.Memory to read the new path string from 1822 // - new_path_len: length of `new_path` 1823 // 1824 // # Result (Errno) 1825 // 1826 // The return value is 0 except the following error conditions: 1827 // - syscall.EBADF: `fd` or `new_fd` are invalid 1828 // - syscall.ENOENT: `old_path` does not exist. 1829 // - syscall.ENOTDIR: `old` is a directory and `new` exists, but is a file. 1830 // - syscall.EISDIR: `old` is a file and `new` exists, but is a directory. 1831 // 1832 // # Notes 1833 // - This is similar to unlinkat in POSIX. 1834 // See https://linux.die.net/man/2/renameat 1835 // 1836 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_renamefd-fd-old_path-string-new_fd-fd-new_path-string---errno 1837 var pathRename = newHostFunc( 1838 wasip1.PathRenameName, pathRenameFn, 1839 []wasm.ValueType{i32, i32, i32, i32, i32, i32}, 1840 "fd", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len", 1841 ) 1842 1843 func pathRenameFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1844 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1845 1846 fd := int32(params[0]) 1847 oldPath := uint32(params[1]) 1848 oldPathLen := uint32(params[2]) 1849 1850 newFD := int32(params[3]) 1851 newPath := uint32(params[4]) 1852 newPathLen := uint32(params[5]) 1853 1854 oldFS, oldPathName, errno := atPath(fsc, mod.Memory(), fd, oldPath, oldPathLen) 1855 if errno != 0 { 1856 return errno 1857 } 1858 1859 newFS, newPathName, errno := atPath(fsc, mod.Memory(), newFD, newPath, newPathLen) 1860 if errno != 0 { 1861 return errno 1862 } 1863 1864 if oldFS != newFS { // TODO: handle renames across filesystems 1865 return syscall.ENOSYS 1866 } 1867 1868 return oldFS.Rename(oldPathName, newPathName) 1869 } 1870 1871 // pathSymlink is the WASI function named PathSymlinkName which creates a 1872 // symbolic link. 1873 // 1874 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_symlink 1875 var pathSymlink = newHostFunc( 1876 wasip1.PathSymlinkName, pathSymlinkFn, 1877 []wasm.ValueType{i32, i32, i32, i32, i32}, 1878 "old_path", "old_path_len", "fd", "new_path", "new_path_len", 1879 ) 1880 1881 func pathSymlinkFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1882 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1883 1884 oldPath := uint32(params[0]) 1885 oldPathLen := uint32(params[1]) 1886 fd := int32(params[2]) 1887 newPath := uint32(params[3]) 1888 newPathLen := uint32(params[4]) 1889 1890 mem := mod.Memory() 1891 1892 dir, ok := fsc.LookupFile(fd) 1893 if !ok { 1894 return syscall.EBADF // closed 1895 } else if isDir, errno := dir.File.IsDir(); errno != 0 { 1896 return errno 1897 } else if !isDir { 1898 return syscall.ENOTDIR 1899 } 1900 1901 if oldPathLen == 0 || newPathLen == 0 { 1902 return syscall.EINVAL 1903 } 1904 1905 oldPathBuf, ok := mem.Read(oldPath, oldPathLen) 1906 if !ok { 1907 return syscall.EFAULT 1908 } 1909 1910 newPathBuf, ok := mem.Read(newPath, newPathLen) 1911 if !ok { 1912 return syscall.EFAULT 1913 } 1914 1915 return dir.FS.Symlink( 1916 // Do not join old path since it's only resolved when dereference the link created here. 1917 // And the dereference result depends on the opening directory's file descriptor at that point. 1918 bufToStr(oldPathBuf), 1919 path.Join(dir.Name, bufToStr(newPathBuf)), 1920 ) 1921 } 1922 1923 // bufToStr converts the given byte slice as string unsafely. 1924 func bufToStr(buf []byte) string { 1925 // TODO: use unsafe.String after flooring Go 1.20. 1926 return *(*string)(unsafe.Pointer(&buf)) 1927 } 1928 1929 // pathUnlinkFile is the WASI function named PathUnlinkFileName which unlinks a 1930 // file. 1931 // 1932 // # Parameters 1933 // 1934 // - fd: file descriptor of a directory that `path` is relative to 1935 // - path: offset in api.Memory to read the path string from 1936 // - pathLen: length of `path` 1937 // 1938 // # Result (Errno) 1939 // 1940 // The return value is 0 except the following error conditions: 1941 // - syscall.EBADF: `fd` is invalid 1942 // - syscall.ENOENT: `path` does not exist. 1943 // - syscall.EISDIR: `path` is a directory 1944 // 1945 // # Notes 1946 // - This is similar to unlinkat without AT_REMOVEDIR in POSIX. 1947 // See https://linux.die.net/man/2/unlinkat 1948 // 1949 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_unlink_filefd-fd-path-string---errno 1950 var pathUnlinkFile = newHostFunc( 1951 wasip1.PathUnlinkFileName, pathUnlinkFileFn, 1952 []wasm.ValueType{i32, i32, i32}, 1953 "fd", "path", "path_len", 1954 ) 1955 1956 func pathUnlinkFileFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno { 1957 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 1958 1959 fd := int32(params[0]) 1960 path := uint32(params[1]) 1961 pathLen := uint32(params[2]) 1962 1963 preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen) 1964 if errno != 0 { 1965 return errno 1966 } 1967 1968 return preopen.Unlink(pathName) 1969 }