wa-lang.org/wazero@v1.0.2/imports/wasi_snapshot_preview1/fs.go (about) 1 package wasi_snapshot_preview1 2 3 import ( 4 "context" 5 "encoding/binary" 6 "errors" 7 "io" 8 "io/fs" 9 "math" 10 "path" 11 "syscall" 12 13 "wa-lang.org/wazero/api" 14 internalsys "wa-lang.org/wazero/internal/sys" 15 "wa-lang.org/wazero/internal/wasm" 16 ) 17 18 const ( 19 functionFdAdvise = "fd_advise" 20 functionFdAllocate = "fd_allocate" 21 functionFdClose = "fd_close" 22 functionFdDatasync = "fd_datasync" 23 functionFdFdstatGet = "fd_fdstat_get" 24 functionFdFdstatSetFlags = "fd_fdstat_set_flags" 25 functionFdFdstatSetRights = "fd_fdstat_set_rights" 26 functionFdFilestatGet = "fd_filestat_get" 27 functionFdFilestatSetSize = "fd_filestat_set_size" 28 functionFdFilestatSetTimes = "fd_filestat_set_times" 29 functionFdPread = "fd_pread" 30 functionFdPrestatGet = "fd_prestat_get" 31 functionFdPrestatDirName = "fd_prestat_dir_name" 32 functionFdPwrite = "fd_pwrite" 33 functionFdRead = "fd_read" 34 functionFdReaddir = "fd_readdir" 35 functionFdRenumber = "fd_renumber" 36 functionFdSeek = "fd_seek" 37 functionFdSync = "fd_sync" 38 functionFdTell = "fd_tell" 39 functionFdWrite = "fd_write" 40 41 functionPathCreateDirectory = "path_create_directory" 42 functionPathFilestatGet = "path_filestat_get" 43 functionPathFilestatSetTimes = "path_filestat_set_times" 44 functionPathLink = "path_link" 45 functionPathOpen = "path_open" 46 functionPathReadlink = "path_readlink" 47 functionPathRemoveDirectory = "path_remove_directory" 48 functionPathRename = "path_rename" 49 functionPathSymlink = "path_symlink" 50 functionPathUnlinkFile = "path_unlink_file" 51 ) 52 53 // fdAdvise is the WASI function named functionFdAdvise which provides file 54 // advisory information on a file descriptor. 55 // 56 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_advisefd-fd-offset-filesize-len-filesize-advice-advice---errno 57 var fdAdvise = stubFunction( 58 functionFdAdvise, 59 []wasm.ValueType{i32, i64, i64, i32}, 60 []string{"fd", "offset", "len", "result.advice"}, 61 ) 62 63 // fdAllocate is the WASI function named functionFdAllocate which forces the 64 // allocation of space in a file. 65 // 66 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_allocatefd-fd-offset-filesize-len-filesize---errno 67 var fdAllocate = stubFunction( 68 functionFdAllocate, 69 []wasm.ValueType{i32, i64, i64}, 70 []string{"fd", "offset", "len"}, 71 ) 72 73 // fdClose is the WASI function named functionFdClose which closes a file 74 // descriptor. 75 // 76 // # Parameters 77 // 78 // - fd: file descriptor to close 79 // 80 // Result (Errno) 81 // 82 // The return value is ErrnoSuccess except the following error conditions: 83 // - ErrnoBadf: the fd was not open. 84 // 85 // Note: This is similar to `close` in POSIX. 86 // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_close 87 // and https://linux.die.net/man/3/close 88 var fdClose = &wasm.HostFunc{ 89 ExportNames: []string{functionFdClose}, 90 Name: functionFdClose, 91 ParamTypes: []api.ValueType{i32}, 92 ParamNames: []string{"fd"}, 93 ResultTypes: []api.ValueType{i32}, 94 Code: &wasm.Code{ 95 IsHostFunction: true, 96 GoFunc: wasiFunc(fdCloseFn), 97 }, 98 } 99 100 func fdCloseFn(ctx context.Context, mod api.Module, params []uint64) Errno { 101 sysCtx := mod.(*wasm.CallContext).Sys 102 fd := uint32(params[0]) 103 104 if ok := sysCtx.FS(ctx).CloseFile(ctx, fd); !ok { 105 return ErrnoBadf 106 } 107 return ErrnoSuccess 108 } 109 110 // fdDatasync is the WASI function named functionFdDatasync which synchronizes 111 // the data of a file to disk. 112 // 113 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_datasyncfd-fd---errno 114 var fdDatasync = stubFunction( 115 functionFdDatasync, 116 []wasm.ValueType{i32}, 117 []string{"fd"}, 118 ) 119 120 // fdFdstatGet is the WASI function named functionFdFdstatGet which returns the 121 // attributes of a file descriptor. 122 // 123 // # Parameters 124 // 125 // - fd: file descriptor to get the fdstat attributes data 126 // - resultFdstat: offset to write the result fdstat data 127 // 128 // Result (Errno) 129 // 130 // The return value is ErrnoSuccess except the following error conditions: 131 // - ErrnoBadf: `fd` is invalid 132 // - ErrnoFault: `resultFdstat` points to an offset out of memory 133 // 134 // fdstat byte layout is 24-byte size, with the following fields: 135 // - fs_filetype 1 byte: the file type 136 // - fs_flags 2 bytes: the file descriptor flag 137 // - 5 pad bytes 138 // - fs_right_base 8 bytes: ignored as rights were removed from WASI. 139 // - fs_right_inheriting 8 bytes: ignored as rights were removed from WASI. 140 // 141 // For example, with a file corresponding with `fd` was a directory (=3) opened 142 // with `fd_read` right (=1) and no fs_flags (=0), parameter resultFdstat=1, 143 // this function writes the below to api.Memory: 144 // 145 // uint16le padding uint64le uint64le 146 // uint8 --+ +--+ +-----------+ +--------------------+ +--------------------+ 147 // | | | | | | | | | 148 // []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} 149 // resultFdstat --^ ^-- fs_flags ^-- fs_right_base ^-- fs_right_inheriting 150 // | 151 // +-- fs_filetype 152 // 153 // Note: fdFdstatGet returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as 154 // well as additional fields. 155 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdstat 156 // and https://linux.die.net/man/3/fsync 157 var fdFdstatGet = &wasm.HostFunc{ 158 ExportNames: []string{functionFdFdstatGet}, 159 Name: functionFdFdstatGet, 160 ParamTypes: []api.ValueType{i32, i32}, 161 ParamNames: []string{"fd", "result.stat"}, 162 ResultTypes: []api.ValueType{i32}, 163 Code: &wasm.Code{ 164 IsHostFunction: true, 165 GoFunc: wasiFunc(fdFdstatGetFn), 166 }, 167 } 168 169 func fdFdstatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno { 170 sysCtx := mod.(*wasm.CallContext).Sys 171 // TODO: actually write the fdstat! 172 fd, _ := uint32(params[0]), uint32(params[1]) 173 174 if _, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok { 175 return ErrnoBadf 176 } 177 return ErrnoSuccess 178 } 179 180 // fdFdstatSetFlags is the WASI function named functionFdFdstatSetFlags which 181 // adjusts the flags associated with a file descriptor. 182 // 183 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_fdstat_set_flagsfd-fd-flags-fdflags---errnoand is stubbed for GrainLang per #271 184 var fdFdstatSetFlags = stubFunction( 185 functionFdFdstatSetFlags, 186 []wasm.ValueType{i32, i32}, 187 []string{"fd", "flags"}, 188 ) 189 190 // fdFdstatSetRights will not be implemented as rights were removed from WASI. 191 // 192 // See https://github.com/bytecodealliance/wasmtime/pull/4666 193 var fdFdstatSetRights = stubFunction( 194 functionFdFdstatSetRights, 195 []wasm.ValueType{i32, i64, i64}, 196 []string{"fd", "fs_rights_base", "fs_rights_inheriting"}, 197 ) 198 199 // fdFilestatGet is the WASI function named functionFdFilestatGet which returns 200 // the stat attributes of an open file. 201 // 202 // # Parameters 203 // 204 // - fd: file descriptor to get the filestat attributes data for 205 // - resultFilestat: offset to write the result filestat data 206 // 207 // Result (Errno) 208 // 209 // The return value is ErrnoSuccess except the following error conditions: 210 // - ErrnoBadf: `fd` is invalid 211 // - ErrnoIo: could not stat `fd` on filesystem 212 // - ErrnoFault: `resultFilestat` points to an offset out of memory 213 // 214 // filestat byte layout is 64-byte size, with the following fields: 215 // - dev 8 bytes: the device ID of device containing the file 216 // - ino 8 bytes: the file serial number 217 // - filetype 1 byte: the type of the file 218 // - 7 pad bytes 219 // - nlink 8 bytes: number of hard links to the file 220 // - 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 221 // - atim 8 bytes: ast data access timestamp 222 // - mtim 8 bytes: last data modification timestamp 223 // - ctim 8 bytes: ast file status change timestamp 224 // 225 // For example, with a regular file this function writes the below to api.Memory: 226 // 227 // uint8 --+ 228 // uint64le uint64le | padding uint64le uint64le uint64le uint64le uint64le 229 // +--------------------+ +--------------------+ | +-----------------+ +--------------------+ +-----------------------+ +----------------------------------+ +----------------------------------+ +----------------------------------+ 230 // | | | | | | | | | | | | | | | | | 231 // []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} 232 // resultFilestat ^-- dev ^-- ino ^ ^-- nlink ^-- size ^-- atim ^-- mtim ^-- ctim 233 // | 234 // +-- filetype 235 // 236 // The following properties of filestat are not implemented: 237 // - dev: not supported by Golang FS 238 // - ino: not supported by Golang FS 239 // - nlink: not supported by Golang FS 240 // - atime: not supported by Golang FS, we use mtim for this 241 // - ctim: not supported by Golang FS, we use mtim for this 242 // 243 // Note: This is similar to `fstat` in POSIX. 244 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_getfd-fd---errno-filestat 245 // and https://linux.die.net/man/3/fstat 246 var fdFilestatGet = &wasm.HostFunc{ 247 ExportNames: []string{functionFdFilestatGet}, 248 Name: functionFdFilestatGet, 249 ParamTypes: []api.ValueType{i32, i32}, 250 ParamNames: []string{"fd", "result.buf"}, 251 ResultTypes: []api.ValueType{i32}, 252 Code: &wasm.Code{ 253 IsHostFunction: true, 254 GoFunc: wasiFunc(fdFilestatGetFn), 255 }, 256 } 257 258 type wasiFiletype uint8 259 260 const ( 261 wasiFiletypeUnknown wasiFiletype = iota 262 wasiFiletypeBlockDevice 263 wasiFiletypeCharacterDevice 264 wasiFiletypeDirectory 265 wasiFiletypeRegularFile 266 wasiFiletypeSocketDgram 267 wasiFiletypeSocketStream 268 wasiFiletypeSymbolicLink 269 ) 270 271 func fdFilestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno { 272 return fdFilestatGetFunc(ctx, mod, uint32(params[0]), uint32(params[1])) 273 } 274 275 func fdFilestatGetFunc(ctx context.Context, mod api.Module, fd, resultBuf uint32) Errno { 276 sysCtx := mod.(*wasm.CallContext).Sys 277 file, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd) 278 if !ok { 279 return ErrnoBadf 280 } 281 282 fileStat, err := file.File.Stat() 283 if err != nil { 284 return ErrnoIo 285 } 286 287 fileMode := fileStat.Mode() 288 289 wasiFileMode := wasiFiletypeUnknown 290 if fileMode&fs.ModeDevice != 0 { 291 wasiFileMode = wasiFiletypeBlockDevice 292 } else if fileMode&fs.ModeCharDevice != 0 { 293 wasiFileMode = wasiFiletypeCharacterDevice 294 } else if fileMode&fs.ModeDir != 0 { 295 wasiFileMode = wasiFiletypeDirectory 296 } else if fileMode&fs.ModeType == 0 { 297 wasiFileMode = wasiFiletypeRegularFile 298 } else if fileMode&fs.ModeSymlink != 0 { 299 wasiFileMode = wasiFiletypeSymbolicLink 300 } 301 302 buf, ok := mod.Memory().Read(ctx, resultBuf, 64) 303 if !ok { 304 return ErrnoFault 305 } 306 307 buf[16] = uint8(wasiFileMode) 308 size := uint64(fileStat.Size()) 309 binary.LittleEndian.PutUint64(buf[32:], size) 310 mtim := uint64(fileStat.ModTime().UnixNano()) 311 binary.LittleEndian.PutUint64(buf[40:], mtim) 312 binary.LittleEndian.PutUint64(buf[48:], mtim) 313 binary.LittleEndian.PutUint64(buf[56:], mtim) 314 315 return ErrnoSuccess 316 } 317 318 // fdFilestatSetSize is the WASI function named functionFdFilestatSetSize which 319 // adjusts the size of an open file. 320 // 321 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---errno 322 var fdFilestatSetSize = stubFunction( 323 functionFdFilestatSetSize, 324 []wasm.ValueType{i32, i64}, 325 []string{"fd", "size"}, 326 ) 327 328 // fdFilestatSetTimes is the WASI function named functionFdFilestatSetTimes 329 // which adjusts the times of an open file. 330 // 331 // 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 332 var fdFilestatSetTimes = stubFunction( 333 functionFdFilestatSetTimes, 334 []wasm.ValueType{i32, i64, i64, i32}, 335 []string{"fd", "atim", "mtim", "fst_flags"}, 336 ) 337 338 // fdPread is the WASI function named functionFdPread which reads from a file 339 // descriptor, without using and updating the file descriptor's offset. 340 // 341 // Except for handling offset, this implementation is identical to fdRead. 342 // 343 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---errno-size 344 var fdPread = &wasm.HostFunc{ 345 ExportNames: []string{functionFdPread}, 346 Name: functionFdPread, 347 ParamTypes: []api.ValueType{i32, i32, i32, i64, i32}, 348 ParamNames: []string{"fd", "iovs", "iovs_len", "offset", "result.size"}, 349 ResultTypes: []api.ValueType{i32}, 350 Code: &wasm.Code{ 351 IsHostFunction: true, 352 GoFunc: wasiFunc(fdPreadFn), 353 }, 354 } 355 356 func fdPreadFn(ctx context.Context, mod api.Module, params []uint64) Errno { 357 return fdReadOrPread(ctx, mod, params, true) 358 } 359 360 // fdPrestatGet is the WASI function named functionFdPrestatGet which returns 361 // the prestat data of a file descriptor. 362 // 363 // # Parameters 364 // 365 // - fd: file descriptor to get the prestat 366 // - resultPrestat: offset to write the result prestat data 367 // 368 // Result (Errno) 369 // 370 // The return value is ErrnoSuccess except the following error conditions: 371 // - ErrnoBadf: `fd` is invalid or the `fd` is not a pre-opened directory 372 // - ErrnoFault: `resultPrestat` points to an offset out of memory 373 // 374 // prestat byte layout is 8 bytes, beginning with an 8-bit tag and 3 pad bytes. 375 // The only valid tag is `prestat_dir`, which is tag zero. This simplifies the 376 // byte layout to 4 empty bytes followed by the uint32le encoded path length. 377 // 378 // For example, the directory name corresponding with `fd` was "/tmp" and 379 // parameter resultPrestat=1, this function writes the below to api.Memory: 380 // 381 // padding uint32le 382 // uint8 --+ +-----+ +--------+ 383 // | | | | | 384 // []byte{?, 0, 0, 0, 0, 4, 0, 0, 0, ?} 385 // resultPrestat --^ ^ 386 // tag --+ | 387 // +-- size in bytes of the string "/tmp" 388 // 389 // See fdPrestatDirName and 390 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat 391 var fdPrestatGet = &wasm.HostFunc{ 392 ExportNames: []string{functionFdPrestatGet}, 393 Name: functionFdPrestatGet, 394 ParamTypes: []api.ValueType{i32, i32}, 395 ParamNames: []string{"fd", "result.prestat"}, 396 ResultTypes: []api.ValueType{i32}, 397 Code: &wasm.Code{ 398 IsHostFunction: true, 399 GoFunc: wasiFunc(fdPrestatGetFn), 400 }, 401 } 402 403 func fdPrestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno { 404 sysCtx := mod.(*wasm.CallContext).Sys 405 fd, resultPrestat := uint32(params[0]), uint32(params[1]) 406 407 entry, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd) 408 if !ok { 409 return ErrnoBadf 410 } 411 412 // Zero-value 8-bit tag, and 3-byte zero-value paddings, which is uint32le(0) in short. 413 if !mod.Memory().WriteUint32Le(ctx, resultPrestat, uint32(0)) { 414 return ErrnoFault 415 } 416 417 // Write the length of the directory name at offset 4. 418 if !mod.Memory().WriteUint32Le(ctx, resultPrestat+4, uint32(len(entry.Path))) { 419 return ErrnoFault 420 } 421 return ErrnoSuccess 422 } 423 424 // fdPrestatDirName is the WASI function named functionFdPrestatDirName which 425 // returns the path of the pre-opened directory of a file descriptor. 426 // 427 // # Parameters 428 // 429 // - fd: file descriptor to get the path of the pre-opened directory 430 // - path: offset in api.Memory to write the result path 431 // - pathLen: count of bytes to write to `path` 432 // - This should match the uint32le fdPrestatGet writes to offset 433 // `resultPrestat`+4 434 // 435 // Result (Errno) 436 // 437 // The return value is ErrnoSuccess except the following error conditions: 438 // - ErrnoBadf: `fd` is invalid 439 // - ErrnoFault: `path` points to an offset out of memory 440 // - ErrnoNametoolong: `pathLen` is longer than the actual length of the result 441 // 442 // For example, the directory name corresponding with `fd` was "/tmp" and 443 // # Parameters path=1 pathLen=4 (correct), this function will write the below to 444 // api.Memory: 445 // 446 // pathLen 447 // +--------------+ 448 // | | 449 // []byte{?, '/', 't', 'm', 'p', ?} 450 // path --^ 451 // 452 // See fdPrestatGet 453 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_dir_name 454 var fdPrestatDirName = &wasm.HostFunc{ 455 ExportNames: []string{functionFdPrestatDirName}, 456 Name: functionFdPrestatDirName, 457 ParamTypes: []api.ValueType{i32, i32, i32}, 458 ParamNames: []string{"fd", "path", "path_len"}, 459 ResultTypes: []api.ValueType{i32}, 460 Code: &wasm.Code{ 461 IsHostFunction: true, 462 GoFunc: wasiFunc(fdPrestatDirNameFn), 463 }, 464 } 465 466 func fdPrestatDirNameFn(ctx context.Context, mod api.Module, params []uint64) Errno { 467 sysCtx := mod.(*wasm.CallContext).Sys 468 fd, path, pathLen := uint32(params[0]), uint32(params[1]), uint32(params[2]) 469 470 f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd) 471 if !ok { 472 return ErrnoBadf 473 } 474 475 // Some runtimes may have another semantics. See /RATIONALE.md 476 if uint32(len(f.Path)) < pathLen { 477 return ErrnoNametoolong 478 } 479 480 // TODO: fdPrestatDirName may have to return ErrnoNotdir if the type of the 481 // prestat data of `fd` is not a PrestatDir. 482 if !mod.Memory().Write(ctx, path, []byte(f.Path)[:pathLen]) { 483 return ErrnoFault 484 } 485 return ErrnoSuccess 486 } 487 488 // fdPwrite is the WASI function named functionFdPwrite which writes to a file 489 // descriptor, without using and updating the file descriptor's offset. 490 // 491 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_pwritefd-fd-iovs-ciovec_array-offset-filesize---errno-size 492 var fdPwrite = stubFunction(functionFdPwrite, 493 []wasm.ValueType{i32, i32, i32, i64, i32}, 494 []string{"fd", "iovs", "iovs_len", "offset", "result.nwritten"}, 495 ) 496 497 // fdRead is the WASI function named functionFdRead which reads from a file 498 // descriptor. 499 // 500 // # Parameters 501 // 502 // - fd: an opened file descriptor to read data from 503 // - iovs: offset in api.Memory to read offset, size pairs representing where 504 // to write file data 505 // - Both offset and length are encoded as uint32le 506 // - iovsCount: count of memory offset, size pairs to read sequentially 507 // starting at iovs 508 // - resultSize: offset in api.Memory to write the number of bytes read 509 // 510 // Result (Errno) 511 // 512 // The return value is ErrnoSuccess except the following error conditions: 513 // - ErrnoBadf: `fd` is invalid 514 // - ErrnoFault: `iovs` or `resultSize` point to an offset out of memory 515 // - ErrnoIo: a file system error 516 // 517 // For example, this function needs to first read `iovs` to determine where 518 // to write contents. If parameters iovs=1 iovsCount=2, this function reads two 519 // offset/length pairs from api.Memory: 520 // 521 // iovs[0] iovs[1] 522 // +---------------------+ +--------------------+ 523 // | uint32le uint32le| |uint32le uint32le| 524 // +---------+ +--------+ +--------+ +--------+ 525 // | | | | | | | | 526 // []byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... } 527 // iovs --^ ^ ^ ^ 528 // | | | | 529 // offset --+ length --+ offset --+ length --+ 530 // 531 // If the contents of the `fd` parameter was "wazero" (6 bytes) and parameter 532 // resultSize=26, this function writes the below to api.Memory: 533 // 534 // iovs[0].length iovs[1].length 535 // +--------------+ +----+ uint32le 536 // | | | | +--------+ 537 // []byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ?, 6, 0, 0, 0 } 538 // iovs[0].offset --^ ^ ^ 539 // iovs[1].offset --+ | 540 // resultSize --+ 541 // 542 // Note: This is similar to `readv` in POSIX. https://linux.die.net/man/3/readv 543 // 544 // See fdWrite 545 // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read 546 var fdRead = &wasm.HostFunc{ 547 ExportNames: []string{functionFdRead}, 548 Name: functionFdRead, 549 ParamTypes: []api.ValueType{i32, i32, i32, i32}, 550 ParamNames: []string{"fd", "iovs", "iovs_len", "result.size"}, 551 ResultTypes: []api.ValueType{i32}, 552 Code: &wasm.Code{ 553 IsHostFunction: true, 554 GoFunc: wasiFunc(fdReadFn), 555 }, 556 } 557 558 func fdReadFn(ctx context.Context, mod api.Module, params []uint64) Errno { 559 return fdReadOrPread(ctx, mod, params, false) 560 } 561 562 func fdReadOrPread(ctx context.Context, mod api.Module, params []uint64, isPread bool) Errno { 563 sysCtx := mod.(*wasm.CallContext).Sys 564 mem := mod.Memory() 565 fd := uint32(params[0]) 566 iovs := uint32(params[1]) 567 iovsCount := uint32(params[2]) 568 569 var offset int64 570 var resultSize uint32 571 if isPread { 572 offset = int64(params[3]) 573 resultSize = uint32(params[4]) 574 } else { 575 resultSize = uint32(params[3]) 576 } 577 578 r := internalsys.FdReader(ctx, sysCtx, fd) 579 if r == nil { 580 return ErrnoBadf 581 } 582 583 if isPread { 584 if s, ok := r.(io.Seeker); ok { 585 if _, err := s.Seek(offset, io.SeekStart); err != nil { 586 return ErrnoFault 587 } 588 } else { 589 return ErrnoInval 590 } 591 } 592 593 var nread uint32 594 for i := uint32(0); i < iovsCount; i++ { 595 iov := iovs + i*8 596 offset, ok := mem.ReadUint32Le(ctx, iov) 597 if !ok { 598 return ErrnoFault 599 } 600 l, ok := mem.ReadUint32Le(ctx, iov+4) 601 if !ok { 602 return ErrnoFault 603 } 604 b, ok := mem.Read(ctx, offset, l) 605 if !ok { 606 return ErrnoFault 607 } 608 609 n, err := r.Read(b) 610 nread += uint32(n) 611 612 shouldContinue, errno := fdRead_shouldContinueRead(uint32(n), l, err) 613 if errno != ErrnoSuccess { 614 return errno 615 } else if !shouldContinue { 616 break 617 } 618 } 619 if !mem.WriteUint32Le(ctx, resultSize, nread) { 620 return ErrnoFault 621 } else { 622 return ErrnoSuccess 623 } 624 } 625 626 // fdRead_shouldContinueRead decides whether to continue reading the next iovec 627 // based on the amount read (n/l) and a possible error returned from io.Reader. 628 // 629 // Note: When there are both bytes read (n) and an error, this continues. 630 // See /RATIONALE.md "Why ignore the error returned by io.Reader when n > 1?" 631 func fdRead_shouldContinueRead(n, l uint32, err error) (bool, Errno) { 632 if errors.Is(err, io.EOF) { 633 return false, ErrnoSuccess // EOF isn't an error, and we shouldn't continue. 634 } else if err != nil && n == 0 { 635 return false, ErrnoIo 636 } else if err != nil { 637 return false, ErrnoSuccess // Allow the caller to process n bytes. 638 } 639 // Continue reading, unless there's a partial read or nothing to read. 640 return n == l && n != 0, ErrnoSuccess 641 } 642 643 // fdReaddir is the WASI function named functionFdReaddir which reads directory 644 // entries from a directory. 645 // 646 // 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 647 var fdReaddir = &wasm.HostFunc{ 648 ExportNames: []string{functionFdReaddir}, 649 Name: functionFdReaddir, 650 ParamTypes: []wasm.ValueType{i32, i32, i32, i64, i32}, 651 ParamNames: []string{"fd", "buf", "buf_len", "cookie", "result.bufused"}, 652 ResultTypes: []api.ValueType{i32}, 653 Code: &wasm.Code{ 654 IsHostFunction: true, 655 GoFunc: wasiFunc(fdReaddirFn), 656 }, 657 } 658 659 func fdReaddirFn(ctx context.Context, mod api.Module, params []uint64) Errno { 660 fd := uint32(params[0]) 661 buf := uint32(params[1]) 662 bufLen := uint32(params[2]) 663 // We control the value of the cookie, and it should never be negative. 664 // However, we coerce it to signed to ensure the caller doesn't manipulate 665 // it in such a way that becomes negative. 666 cookie := int64(params[3]) 667 resultBufused := uint32(params[4]) 668 669 // Validate the FD is a directory 670 rd, dir, errno := openedDir(ctx, mod, fd) 671 if errno != ErrnoSuccess { 672 return errno 673 } 674 675 // expect a cookie only if we are continuing a read. 676 if cookie == 0 && dir.CountRead > 0 { 677 return ErrnoInval // invalid as a cookie is minimally one. 678 } 679 680 // First, determine the maximum directory entries that can be encoded as 681 // dirents. The total size is direntSize(24) + nameSize, for each file. 682 // Since a zero-length file name is invalid, the minimum size entry is 683 // 25 (direntSize + 1 character). 684 maxDirEntries := int(bufLen/direntSize + 1) 685 686 // While unlikely maxDirEntries will fit into bufLen, add one more just in 687 // case, as we need to know if we hit the end of the directory or not to 688 // write the correct bufused (e.g. == bufLen unless EOF). 689 // >> If less than the size of the read buffer, the end of the 690 // >> directory has been reached. 691 maxDirEntries += 1 692 693 // The host keeps state for any unread entries from the prior call because 694 // we cannot seek to a previous directory position. Collect these entries. 695 entries, errno := lastDirEntries(dir, cookie) 696 if errno != ErrnoSuccess { 697 return errno 698 } 699 700 // Check if we have maxDirEntries, and read more from the FS as needed. 701 if entryCount := len(entries); entryCount < maxDirEntries { 702 if l, err := rd.ReadDir(maxDirEntries - entryCount); err != io.EOF { 703 if err != nil { 704 return ErrnoIo 705 } 706 dir.CountRead += uint64(len(l)) 707 entries = append(entries, l...) 708 // Replace the cache with up to maxDirEntries, starting at cookie. 709 dir.Entries = entries 710 } 711 } 712 713 mem := mod.Memory() 714 715 // Determine how many dirents we can write, excluding a potentially 716 // truncated entry. 717 bufused, direntCount, writeTruncatedEntry := maxDirents(entries, bufLen) 718 719 // Now, write entries to the underlying buffer. 720 if bufused > 0 { 721 722 // d_next is the index of the next file in the list, so it should 723 // always be one higher than the requested cookie. 724 d_next := uint64(cookie + 1) 725 // ^^ yes this can overflow to negative, which means our implementation 726 // doesn't support writing greater than max int64 entries. 727 728 dirents, ok := mem.Read(ctx, buf, bufused) 729 if !ok { 730 return ErrnoFault 731 } 732 733 writeDirents(entries, direntCount, writeTruncatedEntry, dirents, d_next) 734 } 735 736 if !mem.WriteUint32Le(ctx, resultBufused, bufused) { 737 return ErrnoFault 738 } 739 return ErrnoSuccess 740 } 741 742 const largestDirent = int64(math.MaxUint32 - direntSize) 743 744 // lastDirEntries is broken out from fdReaddirFn for testability. 745 func lastDirEntries(dir *internalsys.ReadDir, cookie int64) (entries []fs.DirEntry, errno Errno) { 746 if cookie < 0 { 747 errno = ErrnoInval // invalid as we will never send a negative cookie. 748 return 749 } 750 751 entryCount := int64(len(dir.Entries)) 752 if entryCount == 0 { // there was no prior call 753 if cookie != 0 { 754 errno = ErrnoInval // invalid as we haven't sent that cookie 755 } 756 return 757 } 758 759 // Get the first absolute position in our window of results 760 firstPos := int64(dir.CountRead) - entryCount 761 cookiePos := cookie - firstPos 762 763 switch { 764 case cookiePos < 0: // cookie is asking for results outside our window. 765 errno = ErrnoNosys // we can't implement directory seeking backwards. 766 case cookiePos == 0: // cookie is asking for the next page. 767 case cookiePos > entryCount: 768 errno = ErrnoInval // invalid as we read that far, yet. 769 case cookiePos > 0: // truncate so to avoid large lists. 770 entries = dir.Entries[cookiePos:] 771 default: 772 entries = dir.Entries 773 } 774 if len(entries) == 0 { 775 entries = nil 776 } 777 return 778 } 779 780 // direntSize is the size of the dirent struct, which should be followed by the 781 // length of a file name. 782 const direntSize = uint32(24) 783 784 // maxDirents returns the maximum count and total entries that can fit in 785 // maxLen bytes. 786 // 787 // truncatedEntryLen is the amount of bytes past bufLen needed to write the 788 // next entry. We have to return bufused == bufLen unless the directory is 789 // exhausted. 790 // 791 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir 792 // See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L44 793 func maxDirents(entries []fs.DirEntry, bufLen uint32) (bufused, direntCount uint32, writeTruncatedEntry bool) { 794 lenRemaining := bufLen 795 for _, e := range entries { 796 if lenRemaining < direntSize { 797 // We don't have enough space in bufLen for another struct, 798 // entry. A caller who wants more will retry. 799 800 // bufused == bufLen means more entries exist, which is the case 801 // when the dirent is larger than bytes remaining. 802 bufused = bufLen 803 break 804 } 805 806 // use int64 to guard against huge filenames 807 nameLen := int64(len(e.Name())) 808 var entryLen uint32 809 810 // Check to see if direntSize + nameLen overflows, or if it would be 811 // larger than possible to encode. 812 if el := int64(direntSize) + nameLen; el < 0 || el > largestDirent { 813 // panic, as testing is difficult. ex we would have to extract a 814 // function to get size of a string or allocate a 2^32 size one! 815 panic("invalid filename: too large") 816 } else { // we know this can fit into a uint32 817 entryLen = uint32(el) 818 } 819 820 if entryLen > lenRemaining { 821 // We haven't room to write the entry, and docs say to write the 822 // header. This helps especially when there is an entry with a very 823 // long filename. Ex if bufLen is 4096 and the filename is 4096, 824 // we need to write direntSize(24) + 4096 bytes to write the entry. 825 // In this case, we only write up to direntSize(24) to allow the 826 // caller to resize. 827 828 // bufused == bufLen means more entries exist, which is the case 829 // when the next entry is larger than bytes remaining. 830 bufused = bufLen 831 832 // We do have enough space to write the header, this value will be 833 // passed on to writeDirents to only write the header for this entry. 834 writeTruncatedEntry = true 835 break 836 } 837 838 // This won't go negative because we checked entryLen <= lenRemaining. 839 lenRemaining -= entryLen 840 bufused += entryLen 841 direntCount++ 842 } 843 return 844 } 845 846 // writeDirents writes the directory entries to the buffer, which is pre-sized 847 // based on maxDirents. truncatedEntryLen means write one past entryCount, 848 // without its name. See maxDirents for why 849 func writeDirents( 850 entries []fs.DirEntry, 851 entryCount uint32, 852 writeTruncatedEntry bool, 853 dirents []byte, 854 d_next uint64, 855 ) { 856 pos, i := uint32(0), uint32(0) 857 for ; i < entryCount; i++ { 858 e := entries[i] 859 nameLen := uint32(len(e.Name())) 860 861 writeDirent(dirents[pos:], d_next, nameLen, e.IsDir()) 862 pos += direntSize 863 864 copy(dirents[pos:], e.Name()) 865 pos += nameLen 866 d_next++ 867 } 868 869 if !writeTruncatedEntry { 870 return 871 } 872 873 // Write a dirent without its name 874 dirent := make([]byte, direntSize) 875 e := entries[i] 876 writeDirent(dirent, d_next, uint32(len(e.Name())), e.IsDir()) 877 878 // Potentially truncate it 879 copy(dirents[pos:], dirent) 880 } 881 882 // writeDirent writes direntSize bytes 883 func writeDirent(buf []byte, dNext uint64, dNamlen uint32, dType bool) { 884 binary.LittleEndian.PutUint64(buf, dNext) // d_next 885 binary.LittleEndian.PutUint64(buf[8:], 0) // no d_ino 886 binary.LittleEndian.PutUint32(buf[16:], dNamlen) // d_namlen 887 888 filetype := wasiFiletypeRegularFile 889 if dType { 890 filetype = wasiFiletypeDirectory 891 } 892 binary.LittleEndian.PutUint32(buf[20:], uint32(filetype)) // d_type 893 } 894 895 // openedDir returns the directory and ErrnoSuccess if the fd points to a readable directory. 896 func openedDir(ctx context.Context, mod api.Module, fd uint32) (fs.ReadDirFile, *internalsys.ReadDir, Errno) { 897 fsc := mod.(*wasm.CallContext).Sys.FS(ctx) 898 if f, ok := fsc.OpenedFile(ctx, fd); !ok { 899 return nil, nil, ErrnoBadf 900 } else if d, ok := f.File.(fs.ReadDirFile); !ok { 901 return nil, nil, ErrnoNotdir 902 } else { 903 if f.ReadDir == nil { 904 f.ReadDir = &internalsys.ReadDir{} 905 } 906 return d, f.ReadDir, ErrnoSuccess 907 } 908 } 909 910 // fdRenumber is the WASI function named functionFdRenumber which atomically 911 // replaces a file descriptor by renumbering another file descriptor. 912 // 913 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno 914 var fdRenumber = stubFunction( 915 functionFdRenumber, 916 []wasm.ValueType{i32, i32}, 917 []string{"fd", "to"}, 918 ) 919 920 // fdSeek is the WASI function named functionFdSeek which moves the offset of a 921 // file descriptor. 922 // 923 // # Parameters 924 // 925 // - fd: file descriptor to move the offset of 926 // - offset: signed int64, which is encoded as uint64, input argument to 927 // `whence`, which results in a new offset 928 // - whence: operator that creates the new offset, given `offset` bytes 929 // - If io.SeekStart, new offset == `offset`. 930 // - If io.SeekCurrent, new offset == existing offset + `offset`. 931 // - If io.SeekEnd, new offset == file size of `fd` + `offset`. 932 // - resultNewoffset: offset in api.Memory to write the new offset to, 933 // relative to start of the file 934 // 935 // Result (Errno) 936 // 937 // The return value is ErrnoSuccess except the following error conditions: 938 // - ErrnoBadf: `fd` is invalid 939 // - ErrnoFault: `resultNewoffset` points to an offset out of memory 940 // - ErrnoInval: `whence` is an invalid value 941 // - ErrnoIo: a file system error 942 // 943 // For example, if fd 3 is a file with offset 0, and parameters fd=3, offset=4, 944 // whence=0 (=io.SeekStart), resultNewOffset=1, this function writes the below 945 // to api.Memory: 946 // 947 // uint64le 948 // +--------------------+ 949 // | | 950 // []byte{?, 4, 0, 0, 0, 0, 0, 0, 0, ? } 951 // resultNewoffset --^ 952 // 953 // Note: This is similar to `lseek` in POSIX. https://linux.die.net/man/3/lseek 954 // 955 // See io.Seeker 956 // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_seek 957 var fdSeek = &wasm.HostFunc{ 958 ExportNames: []string{functionFdSeek}, 959 Name: functionFdSeek, 960 ParamTypes: []api.ValueType{i32, i64, i32, i32}, 961 ParamNames: []string{"fd", "offset", "whence", "result.newoffset"}, 962 ResultTypes: []api.ValueType{i32}, 963 Code: &wasm.Code{ 964 IsHostFunction: true, 965 GoFunc: wasiFunc(fdSeekFn), 966 }, 967 } 968 969 func fdSeekFn(ctx context.Context, mod api.Module, params []uint64) Errno { 970 sysCtx := mod.(*wasm.CallContext).Sys 971 fd := uint32(params[0]) 972 offset := params[1] 973 whence := uint32(params[2]) 974 resultNewoffset := uint32(params[3]) 975 976 var seeker io.Seeker 977 // Check to see if the file descriptor is available 978 if f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok || f.File == nil { 979 return ErrnoBadf 980 // fs.FS doesn't declare io.Seeker, but implementations such as os.File implement it. 981 } else if seeker, ok = f.File.(io.Seeker); !ok { 982 return ErrnoBadf 983 } 984 985 if whence > io.SeekEnd /* exceeds the largest valid whence */ { 986 return ErrnoInval 987 } 988 989 newOffset, err := seeker.Seek(int64(offset), int(whence)) 990 if err != nil { 991 return ErrnoIo 992 } 993 if !mod.Memory().WriteUint64Le(ctx, resultNewoffset, uint64(newOffset)) { 994 return ErrnoFault 995 } 996 return ErrnoSuccess 997 } 998 999 // fdSync is the WASI function named functionFdSync which synchronizes the data 1000 // and metadata of a file to disk. 1001 // 1002 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_syncfd-fd---errno 1003 var fdSync = stubFunction( 1004 functionFdSync, 1005 []wasm.ValueType{i32}, 1006 []string{"fd"}, 1007 ) 1008 1009 // fdTell is the WASI function named functionFdTell which returns the current 1010 // offset of a file descriptor. 1011 // 1012 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_tellfd-fd---errno-filesize 1013 var fdTell = stubFunction( 1014 functionFdTell, 1015 []wasm.ValueType{i32, i32}, 1016 []string{"fd", "result.offset"}, 1017 ) 1018 1019 // fdWrite is the WASI function named functionFdWrite which writes to a file 1020 // descriptor. 1021 // 1022 // # Parameters 1023 // 1024 // - fd: an opened file descriptor to write data to 1025 // - iovs: offset in api.Memory to read offset, size pairs representing the 1026 // data to write to `fd` 1027 // - Both offset and length are encoded as uint32le. 1028 // - iovsCount: count of memory offset, size pairs to read sequentially 1029 // starting at iovs 1030 // - resultSize: offset in api.Memory to write the number of bytes written 1031 // 1032 // Result (Errno) 1033 // 1034 // The return value is ErrnoSuccess except the following error conditions: 1035 // - ErrnoBadf: `fd` is invalid 1036 // - ErrnoFault: `iovs` or `resultSize` point to an offset out of memory 1037 // - ErrnoIo: a file system error 1038 // 1039 // For example, this function needs to first read `iovs` to determine what to 1040 // write to `fd`. If parameters iovs=1 iovsCount=2, this function reads two 1041 // offset/length pairs from api.Memory: 1042 // 1043 // iovs[0] iovs[1] 1044 // +---------------------+ +--------------------+ 1045 // | uint32le uint32le| |uint32le uint32le| 1046 // +---------+ +--------+ +--------+ +--------+ 1047 // | | | | | | | | 1048 // []byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... } 1049 // iovs --^ ^ ^ ^ 1050 // | | | | 1051 // offset --+ length --+ offset --+ length --+ 1052 // 1053 // This function reads those chunks api.Memory into the `fd` sequentially. 1054 // 1055 // iovs[0].length iovs[1].length 1056 // +--------------+ +----+ 1057 // | | | | 1058 // []byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ? } 1059 // iovs[0].offset --^ ^ 1060 // iovs[1].offset --+ 1061 // 1062 // Since "wazero" was written, if parameter resultSize=26, this function writes 1063 // the below to api.Memory: 1064 // 1065 // uint32le 1066 // +--------+ 1067 // | | 1068 // []byte{ 0..24, ?, 6, 0, 0, 0', ? } 1069 // resultSize --^ 1070 // 1071 // Note: This is similar to `writev` in POSIX. https://linux.die.net/man/3/writev 1072 // 1073 // See fdRead 1074 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#ciovec 1075 // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write 1076 var fdWrite = &wasm.HostFunc{ 1077 ExportNames: []string{functionFdWrite}, 1078 Name: functionFdWrite, 1079 ParamTypes: []api.ValueType{i32, i32, i32, i32}, 1080 ParamNames: []string{"fd", "iovs", "iovs_len", "result.size"}, 1081 ResultTypes: []api.ValueType{i32}, 1082 Code: &wasm.Code{ 1083 IsHostFunction: true, 1084 GoFunc: wasiFunc(fdWriteFn), 1085 }, 1086 } 1087 1088 func fdWriteFn(ctx context.Context, mod api.Module, params []uint64) Errno { 1089 fd := uint32(params[0]) 1090 iovs := uint32(params[1]) 1091 iovsCount := uint32(params[2]) 1092 resultSize := uint32(params[3]) 1093 1094 sysCtx := mod.(*wasm.CallContext).Sys 1095 writer := internalsys.FdWriter(ctx, sysCtx, fd) 1096 if writer == nil { 1097 return ErrnoBadf 1098 } 1099 1100 var err error 1101 var nwritten uint32 1102 for i := uint32(0); i < iovsCount; i++ { 1103 iov := iovs + i*8 1104 offset, ok := mod.Memory().ReadUint32Le(ctx, iov) 1105 if !ok { 1106 return ErrnoFault 1107 } 1108 // Note: emscripten has been known to write zero length iovec. However, 1109 // it is not common in other compilers, so we don't optimize for it. 1110 l, ok := mod.Memory().ReadUint32Le(ctx, iov+4) 1111 if !ok { 1112 return ErrnoFault 1113 } 1114 1115 var n int 1116 if writer == io.Discard { // special-case default 1117 n = int(l) 1118 } else { 1119 b, ok := mod.Memory().Read(ctx, offset, l) 1120 if !ok { 1121 return ErrnoFault 1122 } 1123 n, err = writer.Write(b) 1124 if err != nil { 1125 return ErrnoIo 1126 } 1127 } 1128 nwritten += uint32(n) 1129 } 1130 if !mod.Memory().WriteUint32Le(ctx, resultSize, nwritten) { 1131 return ErrnoFault 1132 } 1133 return ErrnoSuccess 1134 } 1135 1136 // pathCreateDirectory is the WASI function named functionPathCreateDirectory 1137 // which creates a directory. 1138 // 1139 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_create_directoryfd-fd-path-string---errno 1140 var pathCreateDirectory = stubFunction( 1141 functionPathCreateDirectory, 1142 []wasm.ValueType{i32, i32, i32}, 1143 []string{"fd", "path", "path_len"}, 1144 ) 1145 1146 // pathFilestatGet is the WASI function named functionPathFilestatGet which 1147 // returns the stat attributes of a file or directory. 1148 // 1149 // # Parameters 1150 // 1151 // - fd: file descriptor of the folder to look in for the path 1152 // - flags: flags determining the method of how paths are resolved 1153 // - path: path under fd to get the filestat attributes data for 1154 // - path_len: length of the path that was given 1155 // - resultFilestat: offset to write the result filestat data 1156 // 1157 // Result (Errno) 1158 // 1159 // The return value is ErrnoSuccess except the following error conditions: 1160 // - ErrnoBadf: `fd` is invalid 1161 // - ErrnoNotdir: `fd` points to a file not a directory 1162 // - ErrnoIo: could not stat `fd` on filesystem 1163 // - ErrnoInval: the path contained "../" 1164 // - ErrnoNametoolong: `path` + `path_len` is out of memory 1165 // - ErrnoFault: `resultFilestat` points to an offset out of memory 1166 // - ErrnoNoent: could not find the path 1167 // 1168 // The rest of this implementation matches that of fdFilestatGet, so is not 1169 // repeated here. 1170 // 1171 // Note: This is similar to `fstatat` in POSIX. 1172 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_getfd-fd-flags-lookupflags-path-string---errno-filestat 1173 // and https://linux.die.net/man/2/fstatat 1174 var pathFilestatGet = &wasm.HostFunc{ 1175 ExportNames: []string{functionPathFilestatGet}, 1176 Name: functionPathFilestatGet, 1177 ParamTypes: []api.ValueType{i32, i32, i32, i32, i32}, 1178 ParamNames: []string{"fd", "flags", "path", "path_len", "result.buf"}, 1179 ResultTypes: []api.ValueType{i32}, 1180 Code: &wasm.Code{ 1181 IsHostFunction: true, 1182 GoFunc: wasiFunc(pathFilestatGetFn), 1183 }, 1184 } 1185 1186 func pathFilestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno { 1187 sysCtx := mod.(*wasm.CallContext).Sys 1188 fsc := sysCtx.FS(ctx) 1189 1190 fd := uint32(params[0]) 1191 1192 // TODO: implement flags? 1193 // flags := uint32(params[1]) 1194 1195 pathOffset := uint32(params[2]) 1196 pathLen := uint32(params[3]) 1197 resultBuf := uint32(params[4]) 1198 1199 // open_at isn't supported in fs.FS, so we check the path can't escape, 1200 // then join it with its parent 1201 b, ok := mod.Memory().Read(ctx, pathOffset, pathLen) 1202 if !ok { 1203 return ErrnoNametoolong 1204 } 1205 pathName := string(b) 1206 1207 if dir, ok := fsc.OpenedFile(ctx, fd); !ok { 1208 return ErrnoBadf 1209 } else if dir.File == nil { // root 1210 } else if _, ok := dir.File.(fs.ReadDirFile); !ok { 1211 return ErrnoNotdir 1212 } else { 1213 pathName = path.Join(dir.Path, pathName) 1214 } 1215 1216 // Sadly, we need to open the file to stat it. 1217 pathFd, errnoResult := openFile(ctx, fsc, pathName) 1218 if errnoResult != ErrnoSuccess { 1219 return errnoResult 1220 } 1221 1222 // Close it when the function returns. 1223 defer fsc.CloseFile(ctx, pathFd) 1224 return fdFilestatGetFunc(ctx, mod, pathFd, resultBuf) 1225 } 1226 1227 // pathFilestatSetTimes is the WASI function named functionPathFilestatSetTimes 1228 // which adjusts the timestamps of a file or directory. 1229 // 1230 // 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 1231 var pathFilestatSetTimes = stubFunction( 1232 functionPathFilestatSetTimes, 1233 []wasm.ValueType{i32, i32, i32, i32, i64, i64, i32}, 1234 []string{"fd", "flags", "path", "path_len", "atim", "mtim", "fst_flags"}, 1235 ) 1236 1237 // pathLink is the WASI function named functionPathLink which adjusts the 1238 // timestamps of a file or directory. 1239 // 1240 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_link 1241 var pathLink = stubFunction( 1242 functionPathLink, 1243 []wasm.ValueType{i32, i32, i32, i32, i32, i32, i32}, 1244 []string{"old_fd", "old_flags", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len"}, 1245 ) 1246 1247 // pathOpen is the WASI function named functionPathOpen which opens a file or 1248 // directory. This returns ErrnoBadf if the fd is invalid. 1249 // 1250 // # Parameters 1251 // 1252 // - fd: file descriptor of a directory that `path` is relative to 1253 // - dirflags: flags to indicate how to resolve `path` 1254 // - path: offset in api.Memory to read the path string from 1255 // - pathLen: length of `path` 1256 // - oFlags: open flags to indicate the method by which to open the file 1257 // - fsRightsBase: ignored as rights were removed from WASI. 1258 // - fsRightsInheriting: ignored as rights were removed from WASI. 1259 // created file descriptor for `path` 1260 // - fdFlags: file descriptor flags 1261 // - resultOpenedFd: offset in api.Memory to write the newly created file 1262 // descriptor to. 1263 // - The result FD value is guaranteed to be less than 2**31 1264 // 1265 // Result (Errno) 1266 // 1267 // The return value is ErrnoSuccess except the following error conditions: 1268 // - ErrnoBadf: `fd` is invalid 1269 // - ErrnoFault: `resultOpenedFd` points to an offset out of memory 1270 // - ErrnoNoent: `path` does not exist. 1271 // - ErrnoExist: `path` exists, while `oFlags` requires that it must not. 1272 // - ErrnoNotdir: `path` is not a directory, while `oFlags` requires it. 1273 // - ErrnoIo: a file system error 1274 // 1275 // For example, this function needs to first read `path` to determine the file 1276 // to open. If parameters `path` = 1, `pathLen` = 6, and the path is "wazero", 1277 // pathOpen reads the path from api.Memory: 1278 // 1279 // pathLen 1280 // +------------------------+ 1281 // | | 1282 // []byte{ ?, 'w', 'a', 'z', 'e', 'r', 'o', ?... } 1283 // path --^ 1284 // 1285 // Then, if parameters resultOpenedFd = 8, and this function opened a new file 1286 // descriptor 5 with the given flags, this function writes the below to 1287 // api.Memory: 1288 // 1289 // uint32le 1290 // +--------+ 1291 // | | 1292 // []byte{ 0..6, ?, 5, 0, 0, 0, ?} 1293 // resultOpenedFd --^ 1294 // 1295 // # Notes 1296 // - This is similar to `openat` in POSIX. https://linux.die.net/man/3/openat 1297 // - The returned file descriptor is not guaranteed to be the lowest-number 1298 // 1299 // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#path_open 1300 var pathOpen = &wasm.HostFunc{ 1301 ExportNames: []string{functionPathOpen}, 1302 Name: functionPathOpen, 1303 ParamTypes: []api.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, 1304 ParamNames: []string{"fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags", "result.opened_fd"}, 1305 ResultTypes: []api.ValueType{i32}, 1306 Code: &wasm.Code{ 1307 IsHostFunction: true, 1308 GoFunc: wasiFunc(pathOpenFn), 1309 }, 1310 } 1311 1312 func pathOpenFn(ctx context.Context, mod api.Module, params []uint64) Errno { 1313 sysCtx := mod.(*wasm.CallContext).Sys 1314 fsc := sysCtx.FS(ctx) 1315 1316 fd := uint32(params[0]) 1317 _ /* dirflags */ = uint32(params[1]) 1318 path := uint32(params[2]) 1319 pathLen := uint32(params[3]) 1320 _ /* oflags */ = uint32(params[4]) 1321 // rights aren't used 1322 _, _ = params[5], params[6] 1323 _ /* fdflags */ = uint32(params[7]) 1324 resultOpenedFd := uint32(params[8]) 1325 1326 if _, ok := fsc.OpenedFile(ctx, fd); !ok { 1327 return ErrnoBadf 1328 } 1329 1330 b, ok := mod.Memory().Read(ctx, path, pathLen) 1331 if !ok { 1332 return ErrnoFault 1333 } 1334 1335 newFD, errnoResult := openFile(ctx, fsc, string(b)) 1336 if errnoResult != ErrnoSuccess { 1337 return errnoResult 1338 } 1339 1340 if !mod.Memory().WriteUint32Le(ctx, resultOpenedFd, newFD) { 1341 _ = fsc.CloseFile(ctx, newFD) 1342 return ErrnoFault 1343 } 1344 return ErrnoSuccess 1345 } 1346 1347 // pathReadlink is the WASI function named functionPathReadlink that reads the 1348 // contents of a symbolic link. 1349 // 1350 // 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 1351 var pathReadlink = stubFunction( 1352 functionPathReadlink, 1353 []wasm.ValueType{i32, i32, i32, i32, i32, i32}, 1354 []string{"fd", "path", "path_len", "buf", "buf_len", "result.bufused"}, 1355 ) 1356 1357 // pathRemoveDirectory is the WASI function named functionPathRemoveDirectory 1358 // which removes a directory. 1359 // 1360 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_remove_directoryfd-fd-path-string---errno 1361 var pathRemoveDirectory = stubFunction( 1362 functionPathRemoveDirectory, 1363 []wasm.ValueType{i32, i32, i32}, 1364 []string{"fd", "path", "path_len"}, 1365 ) 1366 1367 // pathRename is the WASI function named functionPathRename which renames a 1368 // file or directory. 1369 var pathRename = stubFunction( 1370 functionPathRename, 1371 []wasm.ValueType{i32, i32, i32, i32, i32, i32}, 1372 []string{"fd", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len"}, 1373 ) 1374 1375 // pathSymlink is the WASI function named functionPathSymlink which creates a 1376 // symbolic link. 1377 // 1378 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_symlink 1379 var pathSymlink = stubFunction( 1380 functionPathSymlink, 1381 []wasm.ValueType{i32, i32, i32, i32, i32}, 1382 []string{"old_path", "old_path_len", "fd", "new_path", "new_path_len"}, 1383 ) 1384 1385 // pathUnlinkFile is the WASI function named functionPathUnlinkFile which 1386 // unlinks a file. 1387 // 1388 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_unlink_filefd-fd-path-string---errno 1389 var pathUnlinkFile = stubFunction( 1390 functionPathUnlinkFile, 1391 []wasm.ValueType{i32, i32, i32}, 1392 []string{"fd", "path", "path_len"}, 1393 ) 1394 1395 // openFile attempts to open the file at the given path. Errors coerce to WASI 1396 // Errno, returned as a slice to avoid allocation per-error. 1397 // 1398 // Note: Coercion isn't centralized in internalsys.FSContext because ABI use 1399 // different error codes. For example, wasi-filesystem and GOOS=js don't map to 1400 // these Errno. 1401 func openFile(ctx context.Context, fsc *internalsys.FSContext, name string) (fd uint32, errno Errno) { 1402 newFD, err := fsc.OpenFile(ctx, name) 1403 if err == nil { 1404 fd = newFD 1405 errno = ErrnoSuccess 1406 return 1407 } 1408 // handle all the cases of FS.Open or internal to FSContext.OpenFile 1409 switch { 1410 case errors.Is(err, fs.ErrInvalid): 1411 errno = ErrnoInval 1412 case errors.Is(err, fs.ErrNotExist): 1413 // fs.FS is allowed to return this instead of ErrInvalid on an invalid path 1414 errno = ErrnoNoent 1415 case errors.Is(err, fs.ErrExist): 1416 errno = ErrnoExist 1417 case errors.Is(err, syscall.EBADF): 1418 // fsc.OpenFile currently returns this on out of file descriptors 1419 errno = ErrnoBadf 1420 default: 1421 errno = ErrnoIo 1422 } 1423 return 1424 }