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