From 54203dec4e88fc249ee82b8fa5d798fb7b20fe51 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 22 Nov 2021 13:32:41 -0800 Subject: [PATCH] Port the wasi-filesystem API to the new wai format. #35 This makes a number of changes, to make use of interface-types features such as expected, variant types, and resources. The change to use resources in particular means that filesystem functions are now methods of the descriptor resource. Since this means renaming everything, take this opportunity to introduce a new naming conventions, with _at being used for functions that take dirfd+path arguments. This also eliminates the rights concept what was present in earlier versions of WASI, has has discussed in #31. This required adding new flags to open_at, so while here, this also adds basic chmod-like support, as discussed in #33. And, this removes support for readdir seeking (seekdir/telldir), as discussed in #7. And it adds a fifo file type and a more general socket type, as discussed in #4. --- README.md | 110 ++++ test/README.md | 11 + wasi-filesystem.abi.md | 1121 ++++++++++++++++++++++++++++++++++++++++ wasi-filesystem.wai.md | 815 +++++++++++++++++++++++++++++ 4 files changed, 2057 insertions(+) create mode 100644 README.md create mode 100644 test/README.md create mode 100644 wasi-filesystem.abi.md create mode 100644 wasi-filesystem.wai.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa56877 --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# [Example WASI proposal] + +This template can be used to start a new proposal, which can then be proposed in the WASI Subgroup meetings. + +The sections below are recommended. However, every proposal is different, and the community can help you flesh out the proposal, so don't block on having something filled in for each one of them. + +Thank you to the W3C Privacy CG for the [inspiration](https://github.com/privacycg/template)! + +# [Title] + +A proposed [WebAssembly System Interface](https://github.com/WebAssembly/WASI) API. + +### Current Phase + +[Fill in the current phase, e.g. Phase 1] + +### Champions + +- [Champion 1] +- [Champion 2] +- [etc.] + +### Phase 4 Advancement Criteria + +TODO before entering Phase 2. + +## Table of Contents [if the explainer is longer than one printed page] + +- [Introduction](#introduction) +- [Goals [or Motivating Use Cases, or Scenarios]](#goals-or-motivating-use-cases-or-scenarios) +- [Non-goals](#non-goals) +- [API walk-through](#api-walk-through) + - [Use case 1](#use-case-1) + - [Use case 2](#use-case-2) +- [Detailed design discussion](#detailed-design-discussion) + - [[Tricky design choice 1]](#tricky-design-choice-1) + - [[Tricky design choice 2]](#tricky-design-choice-2) +- [Considered alternatives](#considered-alternatives) + - [[Alternative 1]](#alternative-1) + - [[Alternative 2]](#alternative-2) +- [Stakeholder Interest & Feedback](#stakeholder-interest--feedback) +- [References & acknowledgements](#references--acknowledgements) + +### Introduction + +[The "executive summary" or "abstract". Explain in a few sentences what the goals of the project are, and a brief overview of how the solution works. This should be no more than 1-2 paragraphs.] + +### Goals [or Motivating Use Cases, or Scenarios] + +[What is the end-user need which this project aims to address?] + +### Non-goals + +[If there are "adjacent" goals which may appear to be in scope but aren't, enumerate them here. This section may be fleshed out as your design progresses and you encounter necessary technical and other trade-offs.] + +### API walk-through + +[Walk through of how someone would use this API.] + +#### [Use case 1] + +[Provide example code snippets and diagrams explaining how the API would be used to solve the given problem] + +#### [Use case 2] + +[etc.] + +### Detailed design discussion + +[This section should mostly refer to the .wai.md file that specifies the API. This section is for any discussion of the choices made in the API which don't make sense to document in the spec file itself.] + +#### [Tricky design choice #1] + +[Talk through the tradeoffs in coming to the specific design point you want to make.] + +``` +// Illustrated with example code. +``` + +[This may be an open question, in which case you should link to any active discussion threads.] + +#### [Tricky design choice 2] + +[etc.] + +### Considered alternatives + +[This section is not required if you already covered considered alternatives in the design discussion above.] + +#### [Alternative 1] + +[Describe an alternative which was considered, and why you decided against it.] + +#### [Alternative 2] + +[etc.] + +### Stakeholder Interest & Feedback + +TODO before entering Phase 3. + +[This should include a list of implementers who have expressed interest in implementing the proposal] + +### References & acknowledgements + +Many thanks for valuable feedback and advice from: + +- [Person 1] +- [Person 2] +- [etc.] diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..c274acd --- /dev/null +++ b/test/README.md @@ -0,0 +1,11 @@ +# Testing guidelines + +TK fill in testing guidelines + +## Installing the tools + +TK fill in instructions + +## Running the tests + +TK fill in instructions diff --git a/wasi-filesystem.abi.md b/wasi-filesystem.abi.md new file mode 100644 index 0000000..80a6f38 --- /dev/null +++ b/wasi-filesystem.abi.md @@ -0,0 +1,1121 @@ +# Types + +## `size`: `u32` + + Size of a range of bytes in memory. + +Size: 4, Alignment: 4 + +## `filesize`: `u64` + + Non-negative file size or length of a region within a file. + +Size: 8, Alignment: 8 + +## `filedelta`: `s64` + + Relative offset within a file. + +Size: 8, Alignment: 8 + +## `timestamp`: `u64` + + Timestamp in nanoseconds. + + TODO: wasi-clocks is moving to seconds+nanoseconds. + +Size: 8, Alignment: 8 + +## `info`: record + + Information associated with a descriptor. + + Note: This was called `fdstat` in earlier versions of WASI. + +Size: 2, Alignment: 1 + +### Record Fields + +- [`type`](#info.type): [`type`](#type) + + The type of filesystem object referenced by a descriptor. + +- [`flags`](#info.flags): [`flags`](#flags) + + Flags associated with a descriptor. + +## `type`: variant + + The type of a filesystem object referenced by a descriptor. + + Note: This was called `filetype` in earlier versions of WASI. + +Size: 1, Alignment: 1 + +### Variant Cases + +- [`unknown`](#type.unknown) + + The type of the descriptor or file is unknown or is different from + any of the other types specified. + +- [`block_device`](#type.block_device) + + The descriptor refers to a block device inode. + +- [`character_device`](#type.character_device) + + The descriptor refers to a character device inode. + +- [`directory`](#type.directory) + + The descriptor refers to a directory inode. + +- [`fifo`](#type.fifo) + + The descriptor refers to a named pipe. + +- [`symbolic_link`](#type.symbolic_link) + + The file refers to a symbolic link inode. + +- [`regular_file`](#type.regular_file) + + The descriptor refers to a regular file inode. + +- [`socket`](#type.socket) + + The descriptor refers to a socket. + +## `flags`: record + + Descriptor flags. + + Note: This was called `fdflags` in earlier versions of WASI. + +Size: 1, Alignment: 1 + +### Record Fields + +- [`read`](#flags.read): `bool` + + Read mode: Data can be read. +Bit: 0 + +- [`write`](#flags.write): `bool` + + Write mode: Data can be written to. +Bit: 1 + +- [`append`](#flags.append): `bool` + + Append mode: Data written to the file is always appended to the file's + end. +Bit: 2 + +- [`dsync`](#flags.dsync): `bool` + + Write according to synchronized I/O data integrity completion. Only the + data stored in the file is synchronized. +Bit: 3 + +- [`nonblock`](#flags.nonblock): `bool` + + Non-blocking mode. +Bit: 4 + +- [`rsync`](#flags.rsync): `bool` + + Synchronized read I/O operations. +Bit: 5 + +- [`sync`](#flags.sync): `bool` + + Write according to synchronized I/O file integrity completion. In + addition to synchronizing the data stored in the file, the + implementation may also synchronously update the file's metadata. +Bit: 6 + +## `stat`: record + + File attributes. + + Note: This was called `filestat` in earlier versions of WASI. + +Size: 64, Alignment: 8 + +### Record Fields + +- [`dev`](#stat.dev): [`device`](#device) + + Device ID of device containing the file. + +- [`ino`](#stat.ino): [`inode`](#inode) + + File serial number. + +- [`type`](#stat.type): [`type`](#type) + + File type. + +- [`nlink`](#stat.nlink): [`linkcount`](#linkcount) + + Number of hard links to the file. + +- [`size`](#stat.size): [`filesize`](#filesize) + + For regular files, the file size in bytes. For symbolic links, the length + in bytes of the pathname contained in the symbolic link. + +- [`atim`](#stat.atim): [`timestamp`](#timestamp) + + Last data access timestamp. + +- [`mtim`](#stat.mtim): [`timestamp`](#timestamp) + + Last data modification timestamp. + +- [`ctim`](#stat.ctim): [`timestamp`](#timestamp) + + Last file status change timestamp. + +## `lookupflags`: record + + Flags determining the method of how paths are resolved. + +Size: 1, Alignment: 1 + +### Record Fields + +- [`symlink_follow`](#lookupflags.symlink_follow): `bool` + + As long as the resolved path corresponds to a symbolic link, it is expanded. +Bit: 0 + +## `oflags`: record + + Open flags used by `open_at`. + +Size: 1, Alignment: 1 + +### Record Fields + +- [`nofollow`](#oflags.nofollow): `bool` + + Fail if path names an existing symbolic link. +Bit: 0 + +- [`creat`](#oflags.creat): `bool` + + Create file if it does not exist. +Bit: 1 + +- [`directory`](#oflags.directory): `bool` + + Fail if not a directory. +Bit: 2 + +- [`excl`](#oflags.excl): `bool` + + Fail if file already exists. +Bit: 3 + +- [`trunc`](#oflags.trunc): `bool` + + Truncate file to size 0. +Bit: 4 + +## `mode`: record + + Permissions mode used by `open_at`, `change_permissions_at`, and similar. + +Size: 1, Alignment: 1 + +### Record Fields + +- [`readable`](#mode.readable): `bool` + + True if the resource is considered readable by the containing + filesystem. +Bit: 0 + +- [`writeable`](#mode.writeable): `bool` + + True if the resource is considered writeable by the containing + filesystem. +Bit: 1 + +- [`executable`](#mode.executable): `bool` + + True if the resource is considered executable by the containing + filesystem. This does not apply to directories. +Bit: 2 + +## `linkcount`: `u64` + + Number of hard links to an inode. + +Size: 8, Alignment: 8 + +## `device`: `u64` + + Identifier for a device containing a file system. Can be used in combination + with `inode` to uniquely identify a file or directory in the filesystem. + +Size: 8, Alignment: 8 + +## `inode`: `u64` + + +Size: 8, Alignment: 8 + +## `new_timestamp`: variant + + When setting a timestamp, this gives the value to set it to. + +Size: 16, Alignment: 8 + +### Variant Cases + +- [`no_change`](#new_timestamp.no_change) + + Leave the timestamp set to its previous value. + +- [`now`](#new_timestamp.now) + + Set the timestamp to the current time of the system clock associated + with the filesystem. + +- [`timestamp`](#new_timestamp.timestamp): [`timestamp`](#timestamp) + + Set the timestamp to the given value. + +## `dirent`: record + + A directory entry. + +Size: 16, Alignment: 8 + +### Record Fields + +- [`ino`](#dirent.ino): [`inode`](#inode) + + The serial number of the file referred to by this directory entry. + +- [`namelen`](#dirent.namelen): [`size`](#size) + + The length of the name of the directory entry. + +- [`type`](#dirent.type): [`type`](#type) + + The type of the file referred to by this directory entry. + +## `errno`: variant + + Error codes returned by functions. + Not all of these error codes are returned by the functions provided by this + API; some are used in higher-level library layers, and others are provided + merely for alignment with POSIX. + +Size: 1, Alignment: 1 + +### Variant Cases + +- [`success`](#errno.success) + + No error occurred. System call completed successfully. + +- [`toobig`](#errno.toobig) + + Argument list too long. This is similar to `E2BIG` in POSIX. + +- [`access`](#errno.access) + + Permission denied. + +- [`addrinuse`](#errno.addrinuse) + + Address in use. + +- [`addrnotavail`](#errno.addrnotavail) + + Address not available. + +- [`afnosupport`](#errno.afnosupport) + + Address family not supported. + +- [`again`](#errno.again) + + Resource unavailable, or operation would block. + +- [`already`](#errno.already) + + Connection already in progress. + +- [`badmsg`](#errno.badmsg) + + Bad message. + +- [`busy`](#errno.busy) + + Device or resource busy. + +- [`canceled`](#errno.canceled) + + Operation canceled. + +- [`child`](#errno.child) + + No child processes. + +- [`connaborted`](#errno.connaborted) + + Connection aborted. + +- [`connrefused`](#errno.connrefused) + + Connection refused. + +- [`connreset`](#errno.connreset) + + Connection reset. + +- [`deadlk`](#errno.deadlk) + + Resource deadlock would occur. + +- [`destaddrreq`](#errno.destaddrreq) + + Destination address required. + +- [`dom`](#errno.dom) + + Mathematics argument out of domain of function. + +- [`dquot`](#errno.dquot) + + Reserved. + +- [`exist`](#errno.exist) + + File exists. + +- [`fault`](#errno.fault) + + Bad address. + +- [`fbig`](#errno.fbig) + + File too large. + +- [`hostunreach`](#errno.hostunreach) + + Host is unreachable. + +- [`idrm`](#errno.idrm) + + Identifier removed. + +- [`ilseq`](#errno.ilseq) + + Illegal byte sequence. + +- [`inprogress`](#errno.inprogress) + + Operation in progress. + +- [`intr`](#errno.intr) + + Interrupted function. + +- [`inval`](#errno.inval) + + Invalid argument. + +- [`io`](#errno.io) + + I/O error. + +- [`isconn`](#errno.isconn) + + Socket is connected. + +- [`isdir`](#errno.isdir) + + Is a directory. + +- [`loop`](#errno.loop) + + Too many levels of symbolic links. + +- [`mfile`](#errno.mfile) + + File descriptor value too large. + +- [`mlink`](#errno.mlink) + + Too many links. + +- [`msgsize`](#errno.msgsize) + + Message too large. + +- [`multihop`](#errno.multihop) + + Reserved. + +- [`nametoolong`](#errno.nametoolong) + + Filename too long. + +- [`netdown`](#errno.netdown) + + Network is down. + +- [`netreset`](#errno.netreset) + + Connection aborted by network. + +- [`netunreach`](#errno.netunreach) + + Network unreachable. + +- [`nfile`](#errno.nfile) + + Too many files open in system. + +- [`nobufs`](#errno.nobufs) + + No buffer space available. + +- [`nodev`](#errno.nodev) + + No such device. + +- [`noent`](#errno.noent) + + No such file or directory. + +- [`noexec`](#errno.noexec) + + Executable file format error. + +- [`nolck`](#errno.nolck) + + No locks available. + +- [`nolink`](#errno.nolink) + + Reserved. + +- [`nomem`](#errno.nomem) + + Not enough space. + +- [`nomsg`](#errno.nomsg) + + No message of the desired type. + +- [`noprotoopt`](#errno.noprotoopt) + + Protocol not available. + +- [`nospc`](#errno.nospc) + + No space left on device. + +- [`nosys`](#errno.nosys) + + Function not supported. + +- [`notconn`](#errno.notconn) + + The socket is not connected. + +- [`notdir`](#errno.notdir) + + Not a directory or a symbolic link to a directory. + +- [`notempty`](#errno.notempty) + + Directory not empty. + +- [`notrecoverable`](#errno.notrecoverable) + + State not recoverable. + +- [`notsock`](#errno.notsock) + + Not a socket. + +- [`notsup`](#errno.notsup) + + Not supported, or operation not supported on socket. + +- [`notty`](#errno.notty) + + Inappropriate I/O control operation. + +- [`nxio`](#errno.nxio) + + No such device or address. + +- [`overflow`](#errno.overflow) + + Value too large to be stored in data type. + +- [`ownerdead`](#errno.ownerdead) + + Previous owner died. + +- [`perm`](#errno.perm) + + Operation not permitted. + +- [`pipe`](#errno.pipe) + + Broken pipe. + +- [`proto`](#errno.proto) + + Protocol error. + +- [`protonosupport`](#errno.protonosupport) + + Protocol not supported. + +- [`prototype`](#errno.prototype) + + Protocol wrong type for socket. + +- [`range`](#errno.range) + + Result too large. + +- [`rofs`](#errno.rofs) + + Read-only file system. + +- [`spipe`](#errno.spipe) + + Invalid seek. + +- [`srch`](#errno.srch) + + No such process. + +- [`stale`](#errno.stale) + + Reserved. + +- [`timedout`](#errno.timedout) + + Connection timed out. + +- [`txtbsy`](#errno.txtbsy) + + Text file busy. + +- [`xdev`](#errno.xdev) + + Cross-device link. + +- [`notcapable`](#errno.notcapable) + + Extension: Capabilities insufficient. + +## `advice`: variant + + File or memory access pattern advisory information. + +Size: 1, Alignment: 1 + +### Variant Cases + +- [`normal`](#advice.normal) + + The application has no advice to give on its behavior with respect to the specified data. + +- [`sequential`](#advice.sequential) + + The application expects to access the specified data sequentially from lower offsets to higher offsets. + +- [`random`](#advice.random) + + The application expects to access the specified data in a random order. + +- [`willneed`](#advice.willneed) + + The application expects to access the specified data in the near future. + +- [`dontneed`](#advice.dontneed) + + The application expects that it will not access the specified data in the near future. + +- [`noreuse`](#advice.noreuse) + + The application expects to access the specified data once and then not reuse it thereafter. + +## `seek_from`: variant + + The position relative to which to set the offset of the descriptor. + +Size: 16, Alignment: 8 + +### Variant Cases + +- [`set`](#seek_from.set): [`filesize`](#filesize) + + Seek relative to start-of-file. + +- [`cur`](#seek_from.cur): [`filedelta`](#filedelta) + + Seek relative to current position. + +- [`end`](#seek_from.end): [`filesize`](#filesize) + + Seek relative to end-of-file. + +# Functions + +---- + +#### `descriptor::fadvise` + + Provide file advisory information on a descriptor. + + This is similar to `posix_fadvise` in POSIX. +##### Params + +- `self`: handle +- `offset`: `u64` +- `len`: `u64` +- `advice`: [`advice`](#advice) +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::fallocate` + + Force the allocation of space in a file. + + Note: This is similar to `posix_fallocate` in POSIX. +##### Params + +- `self`: handle +- `offset`: [`filesize`](#filesize) +- `len`: [`filesize`](#filesize) +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::datasync` + + Synchronize the data of a file to disk. + + Note: This is similar to `fdatasync` in POSIX. +##### Params + +- `self`: handle +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::info` + + Get information associated with a descriptor. + + Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well + as additional fields. + + Note: This was called `fdstat_get` in earlier versions of WASI. +##### Params + +- `self`: handle +##### Results + +- ``: expected<[`info`](#info), [`errno`](#errno)> + +---- + +#### `descriptor::set_size` + + Adjust the size of an open file. If this increases the file's size, the + extra bytes are filled with zeros. + + Note: This was called `fd_filestat_set_size` in earlier versions of WASI. +##### Params + +- `self`: handle +- `size`: [`filesize`](#filesize) +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::set_times` + + Adjust the timestamps of an open file or directory. + + Note: This is similar to `futimens` in POSIX. + + Note: This was called `fd_filestat_set_times` in earlier versions of WASI. +##### Params + +- `self`: handle +- `atim`: [`new_timestamp`](#new_timestamp) +- `mtim`: [`new_timestamp`](#new_timestamp) +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::pread` + + Read from a descriptor, without using and updating the descriptor's offset. + + Note: This is similar to `pread` in POSIX. +##### Params + +- `self`: handle +- `buf`: push-buffer<`u8`> +- `offset`: [`filesize`](#filesize) +##### Results + +- ``: expected<[`size`](#size), [`errno`](#errno)> + +---- + +#### `descriptor::pwrite` + + Write to a descriptor, without using and updating the descriptor's offset. + + Note: This is similar to `pwrite` in POSIX. +##### Params + +- `self`: handle +- `buf`: pull-buffer<`u8`> +- `offset`: [`filesize`](#filesize) +##### Results + +- ``: expected<[`size`](#size), [`errno`](#errno)> + +---- + +#### `descriptor::read` + + Read from a descriptor. + + The meaning of `read` on a directory is unspecified. + + Note: This is similar to `read` in POSIX. +##### Params + +- `self`: handle +- `buf`: push-buffer<`u8`> +##### Results + +- ``: expected<[`size`](#size), [`errno`](#errno)> + +---- + +#### `descriptor::readdir` + + Read directory entries from a directory. + + When successful, the contents of the output buffer consist of a sequence of + directory entries. Each directory entry consists of a `dirent` object, + followed by `dirent::d_namlen` bytes holding the name of the directory + entry. + + This function fills the output buffer as much as possible, potentially + truncating the last directory entry. This allows the caller to grow its + read buffer size in case it's too small to fit a single large directory + entry, or skip the oversized directory entry. +##### Params + +- `self`: handle +- `buf`: push-buffer<`u8`> +- `rewind`: `bool` +##### Results + +- ``: expected<[`size`](#size), [`errno`](#errno)> + +---- + +#### `descriptor::seek` + + Move the offset of a descriptor. + + The meaning of `seek` on a directory is unspecified. + + Note: This is similar to `lseek` in POSIX. +##### Params + +- `self`: handle +- `from`: [`seek_from`](#seek_from) +##### Results + +- ``: expected<[`filesize`](#filesize), [`errno`](#errno)> + +---- + +#### `descriptor::sync` + + Synchronize the data and metadata of a file to disk. + + Note: This is similar to `fsync` in POSIX. +##### Params + +- `self`: handle +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::tell` + + Return the current offset of a descriptor. + + Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. +##### Params + +- `self`: handle +##### Results + +- ``: expected<[`filesize`](#filesize), [`errno`](#errno)> + +---- + +#### `descriptor::write` + + Write to a descriptor. + + Note: This is similar to `write` in POSIX. +##### Params + +- `self`: handle +- `buf`: pull-buffer<`u8`> +##### Results + +- ``: expected<[`size`](#size), [`errno`](#errno)> + +---- + +#### `descriptor::create_directory_at` + + Create a directory. + + Note: This is similar to `mkdirat` in POSIX. +##### Params + +- `self`: handle +- `path`: `string` +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::stat_at` + + Return the attributes of a file or directory. + + Note: This is similar to `fstatat` in POSIX. + + Note: This was called `fd_filestat_get` in earlier versions of WASI. +##### Params + +- `self`: handle +- `lookupflags`: [`lookupflags`](#lookupflags) +- `path`: `string` +##### Results + +- ``: expected<[`stat`](#stat), [`errno`](#errno)> + +---- + +#### `descriptor::set_times_at` + + Adjust the timestamps of a file or directory. + + Note: This is similar to `utimensat` in POSIX. + + Note: This was called `path_filestat_set_times` in earlier versions of WASI. +##### Params + +- `self`: handle +- `lookupflags`: [`lookupflags`](#lookupflags) +- `path`: `string` +- `atim`: [`new_timestamp`](#new_timestamp) +- `mtim`: [`new_timestamp`](#new_timestamp) +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::link_at` + + Create a hard link. + + Note: This is similar to `linkat` in POSIX. +##### Params + +- `self`: handle +- `old_lookupflags`: [`lookupflags`](#lookupflags) +- `old_path`: `string` +- `new_descriptor`: handle +- `new_path`: `string` +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::open_at` + + Open a file or directory. + + The returned descriptor is not guaranteed to be the lowest-numbered + descriptor not currently open/ it is randomized to prevent applications + from depending on making assumptions about indexes, since this is + error-prone in multi-threaded contexts. The returned descriptor is + guaranteed to be less than 2**31. + + Note: This is similar to `openat` in POSIX. + + TODO: Re-evaluate `direflags` here. + TODO: Add a permissions mode. +##### Params + +- `self`: handle +- `lookupflags`: [`lookupflags`](#lookupflags) +- `path`: `string` +- `oflags`: [`oflags`](#oflags) +- `fdflags`: [`flags`](#flags) +- `mode`: [`mode`](#mode) +##### Results + +- ``: expected, [`errno`](#errno)> + +---- + +#### `descriptor::readlink_at` + + Read the contents of a symbolic link. + + Note: This is similar to `readlinkat` in POSIX. +##### Params + +- `self`: handle +- `path`: `string` +- `buf`: push-buffer<`u8`> +##### Results + +- ``: expected<[`size`](#size), [`errno`](#errno)> + +---- + +#### `descriptor::remove_directory_at` + + Remove a directory. + + Return `errno::notempty` if the directory is not empty. + + Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. +##### Params + +- `self`: handle +- `path`: `string` +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::rename_at` + + Rename a filesystem object. + + Note: This is similar to `renameat` in POSIX. +##### Params + +- `self`: handle +- `old_path`: `string` +- `new_descriptor`: handle +- `new_path`: `string` +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::symlink_at` + + Create a symbolic link. + + Note: This is similar to `symlinkat` in POSIX. +##### Params + +- `self`: handle +- `old_path`: `string` +- `new_path`: `string` +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::unlink_file_at` + + Unlink a filesystem object that is not a directory. + + Return `errno::isdir` if the path refers to a directory. + Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. +##### Params + +- `self`: handle +- `path`: `string` +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::change_file_permissions_at` + +##### Params + +- `self`: handle +- `lookupflags`: [`lookupflags`](#lookupflags) +- `path`: `string` +- `mode`: [`mode`](#mode) +##### Results + +- ``: expected<_, [`errno`](#errno)> + +---- + +#### `descriptor::change_directory_permissions_at` + +##### Params + +- `self`: handle +- `lookupflags`: [`lookupflags`](#lookupflags) +- `path`: `string` +- `mode`: [`mode`](#mode) +##### Results + +- ``: expected<_, [`errno`](#errno)> + diff --git a/wasi-filesystem.wai.md b/wasi-filesystem.wai.md new file mode 100644 index 0000000..5ce49a4 --- /dev/null +++ b/wasi-filesystem.wai.md @@ -0,0 +1,815 @@ +# WASI Filesystem API + +WASI filesystem is a filesystem API primarily intended to let users run WASI +programs that access their files on their existing filesystems, without +significant overhead. + +It is intended to be roughly portable between Unix-family platforms and +Windows, though it does not hide many of the major differences. + +Paths are passed as interface-type `string`s, meaning they must consist of +a sequence of Unicode Scalar Values (USVs). Some filesystems may contain paths +which are not accessible by this API. + +Some of the content and ideas here are derived from +[CloudABI](https://github.com/NuxiNL/cloudabi). + +## `size` +```wai +/// Size of a range of bytes in memory. +type size = u32 +``` + +## `filesize` +```wai +/// Non-negative file size or length of a region within a file. +type filesize = u64 +``` + +## `filedelta` +```wai +/// Relative offset within a file. +type filedelta = s64 +``` + +## `timestamp` +```wai +/// Timestamp in nanoseconds. +/// +/// TODO: wasi-clocks is moving to seconds+nanoseconds. +type timestamp = u64 +``` + +## `info` +```wai +/// Information associated with a descriptor. +/// +/// Note: This was called `fdstat` in earlier versions of WASI. +record info { + /// The type of filesystem object referenced by a descriptor. + "type": "type", + /// Flags associated with a descriptor. + "flags": "flags", +} +``` + +## `type` +```wai +/// The type of a filesystem object referenced by a descriptor. +/// +/// Note: This was called `filetype` in earlier versions of WASI. +enum "type" { + /// The type of the descriptor or file is unknown or is different from + /// any of the other types specified. + unknown, + /// The descriptor refers to a block device inode. + block_device, + /// The descriptor refers to a character device inode. + character_device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic_link, + /// The descriptor refers to a regular file inode. + regular_file, + /// The descriptor refers to a socket. + socket, +} +``` + +## `flags` +```wai +/// Descriptor flags. +/// +/// Note: This was called `fdflags` in earlier versions of WASI. +flags "flags" { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Append mode: Data written to the file is always appended to the file's + /// end. + append, + /// Write according to synchronized I/O data integrity completion. Only the + /// data stored in the file is synchronized. + dsync, + /// Non-blocking mode. + nonblock, + /// Synchronized read I/O operations. + rsync, + /// Write according to synchronized I/O file integrity completion. In + /// addition to synchronizing the data stored in the file, the + /// implementation may also synchronously update the file's metadata. + sync, +} +``` + +## `stat` +```wai +/// File attributes. +/// +/// Note: This was called `filestat` in earlier versions of WASI. +record stat { + /// Device ID of device containing the file. + dev: device, + /// File serial number. + ino: inode, + /// File type. + "type": "type", + /// Number of hard links to the file. + nlink: linkcount, + /// For regular files, the file size in bytes. For symbolic links, the length + /// in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + atim: timestamp, + /// Last data modification timestamp. + mtim: timestamp, + /// Last file status change timestamp. + ctim: timestamp, +} +``` + +## `lookupflags` +```wai +/// Flags determining the method of how paths are resolved. +flags lookupflags { + /// As long as the resolved path corresponds to a symbolic link, it is expanded. + symlink_follow, +} +``` + +## `oflags` +```wai +/// Open flags used by `open_at`. +flags oflags { + /// Fail if path names an existing symbolic link. + nofollow, + /// Create file if it does not exist. + creat, + /// Fail if not a directory. + directory, + /// Fail if file already exists. + excl, + /// Truncate file to size 0. + trunc, +} +``` + +## `mode` +```wai +/// Permissions mode used by `open_at`, `change_permissions_at`, and similar. +flags mode { + /// True if the resource is considered readable by the containing + /// filesystem. + readable, + /// True if the resource is considered writeable by the containing + /// filesystem. + writeable, + /// True if the resource is considered executable by the containing + /// filesystem. This does not apply to directories. + executable, +} +``` + +## `linkcount` +```wai +/// Number of hard links to an inode. +type linkcount = u64 +``` + +## `device` +```wai +/// Identifier for a device containing a file system. Can be used in combination +/// with `inode` to uniquely identify a file or directory in the filesystem. +type device = u64 +``` + +## `inode` +/// Filesystem object serial number that is unique within its file system. +```wai +type inode = u64 +``` + +## `new_timestamp` +```wai +/// When setting a timestamp, this gives the value to set it to. +variant new_timestamp { + /// Leave the timestamp set to its previous value. + no_change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(timestamp), +} +``` + +## `dirent` +```wai +/// A directory entry. +record dirent { + /// The serial number of the file referred to by this directory entry. + ino: inode, + /// The length of the name of the directory entry. + namelen: size, + /// The type of the file referred to by this directory entry. + "type": "type", +} +``` + +## `errno` +```wai +/// Error codes returned by functions. +/// Not all of these error codes are returned by the functions provided by this +/// API; some are used in higher-level library layers, and others are provided +/// merely for alignment with POSIX. +enum errno { + /// No error occurred. System call completed successfully. + success, + /// Argument list too long. This is similar to `E2BIG` in POSIX. + toobig, + /// Permission denied. + access, + /// Address in use. + addrinuse, + /// Address not available. + addrnotavail, + /// Address family not supported. + afnosupport, + /// Resource unavailable, or operation would block. + again, + /// Connection already in progress. + already, + /// Bad message. + badmsg, + /// Device or resource busy. + busy, + /// Operation canceled. + canceled, + /// No child processes. + child, + /// Connection aborted. + connaborted, + /// Connection refused. + connrefused, + /// Connection reset. + connreset, + /// Resource deadlock would occur. + deadlk, + /// Destination address required. + destaddrreq, + /// Mathematics argument out of domain of function. + dom, + /// Reserved. + dquot, + /// File exists. + exist, + /// Bad address. + fault, + /// File too large. + fbig, + /// Host is unreachable. + hostunreach, + /// Identifier removed. + idrm, + /// Illegal byte sequence. + ilseq, + /// Operation in progress. + inprogress, + /// Interrupted function. + intr, + /// Invalid argument. + inval, + /// I/O error. + io, + /// Socket is connected. + isconn, + /// Is a directory. + isdir, + /// Too many levels of symbolic links. + loop, + /// File descriptor value too large. + mfile, + /// Too many links. + mlink, + /// Message too large. + msgsize, + /// Reserved. + multihop, + /// Filename too long. + nametoolong, + /// Network is down. + netdown, + /// Connection aborted by network. + netreset, + /// Network unreachable. + netunreach, + /// Too many files open in system. + nfile, + /// No buffer space available. + nobufs, + /// No such device. + nodev, + /// No such file or directory. + noent, + /// Executable file format error. + noexec, + /// No locks available. + nolck, + /// Reserved. + nolink, + /// Not enough space. + nomem, + /// No message of the desired type. + nomsg, + /// Protocol not available. + noprotoopt, + /// No space left on device. + nospc, + /// Function not supported. + nosys, + /// The socket is not connected. + notconn, + /// Not a directory or a symbolic link to a directory. + notdir, + /// Directory not empty. + notempty, + /// State not recoverable. + notrecoverable, + /// Not a socket. + notsock, + /// Not supported, or operation not supported on socket. + notsup, + /// Inappropriate I/O control operation. + notty, + /// No such device or address. + nxio, + /// Value too large to be stored in data type. + overflow, + /// Previous owner died. + ownerdead, + /// Operation not permitted. + perm, + /// Broken pipe. + pipe, + /// Protocol error. + proto, + /// Protocol not supported. + protonosupport, + /// Protocol wrong type for socket. + prototype, + /// Result too large. + range, + /// Read-only file system. + rofs, + /// Invalid seek. + spipe, + /// No such process. + srch, + /// Reserved. + stale, + /// Connection timed out. + timedout, + /// Text file busy. + txtbsy, + /// Cross-device link. + xdev, + /// Extension: Capabilities insufficient. + notcapable, +} +``` + +## `advice` +```wai +/// File or memory access pattern advisory information. +enum advice { + /// The application has no advice to give on its behavior with respect to the specified data. + normal, + /// The application expects to access the specified data sequentially from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random order. + random, + /// The application expects to access the specified data in the near future. + willneed, + /// The application expects that it will not access the specified data in the near future. + dontneed, + /// The application expects to access the specified data once and then not reuse it thereafter. + noreuse, +} +``` + +## `seek_from` +```wai +/// The position relative to which to set the offset of the descriptor. +variant seek_from { + /// Seek relative to start-of-file. + set(filesize), + /// Seek relative to current position. + cur(filedelta), + /// Seek relative to end-of-file. + end(filesize), +} +``` + +## `descriptor` +```wai +/// A descriptor is a reference to a filesystem object, which may be a file, +/// directory, named pipe, special file, or other object on which filesystem +/// calls may be made. +resource descriptor { +``` + +## `fadvise` +```wai +/// Provide file advisory information on a descriptor. +/// +/// This is similar to `posix_fadvise` in POSIX. +fadvise: function( + /// The offset within the file to which the advisory applies. + offset: u64, + /// The length of the region to which the advisory applies. + len: u64, + /// The advice. + advice: advice +) -> expected<_, errno> +``` + +## `fallocate` +```wai +/// Force the allocation of space in a file. +/// +/// Note: This is similar to `posix_fallocate` in POSIX. +fallocate: function( + /// The offset at which to start the allocation. + offset: filesize, + /// The length of the area that is allocated. + len: filesize +) -> expected<_, errno> +``` + +## `fdatasync` +```wai +/// Synchronize the data of a file to disk. +/// +/// Note: This is similar to `fdatasync` in POSIX. +datasync: function() -> expected<_, errno> +``` + +## `info` +```wai +/// Get information associated with a descriptor. +/// +/// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well +/// as additional fields. +/// +/// Note: This was called `fdstat_get` in earlier versions of WASI. +info: function() -> expected +``` + +## `set_size` +```wai +/// Adjust the size of an open file. If this increases the file's size, the +/// extra bytes are filled with zeros. +/// +/// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. +set_size: function(size: filesize) -> expected<_, errno> +``` + +## `set_times` +```wai +/// Adjust the timestamps of an open file or directory. +/// +/// Note: This is similar to `futimens` in POSIX. +/// +/// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. +set_times: function( + /// The desired values of the data access timestamp. + atim: new_timestamp, + /// The desired values of the data modification timestamp. + mtim: new_timestamp, +) -> expected<_, errno> +``` + +## `pread` +```wai +/// Read from a descriptor, without using and updating the descriptor's offset. +/// +/// Note: This is similar to `pread` in POSIX. +pread: function( + /// Buffer to read into + buf: push-buffer, + /// The offset within the file at which to read. + offset: filesize, +) -> expected +``` + +## `pwrite` +```wai +/// Write to a descriptor, without using and updating the descriptor's offset. +/// +/// Note: This is similar to `pwrite` in POSIX. +pwrite: function( + /// Data to write + buf: pull-buffer, + /// The offset within the file at which to write. + offset: filesize, +) -> expected +``` + +## `read` +```wai +/// Read from a descriptor. +/// +/// The meaning of `read` on a directory is unspecified. +/// +/// Note: This is similar to `read` in POSIX. +read: function( + /// Where to read into + buf: push-buffer, +) -> expected +``` + +## `readdir` +```wai +/// Read directory entries from a directory. +/// +/// When successful, the contents of the output buffer consist of a sequence of +/// directory entries. Each directory entry consists of a `dirent` object, +/// followed by `dirent::d_namlen` bytes holding the name of the directory +/// entry. +// +/// This function fills the output buffer as much as possible, potentially +/// truncating the last directory entry. This allows the caller to grow its +/// read buffer size in case it's too small to fit a single large directory +/// entry, or skip the oversized directory entry. +readdir: function( + /// The buffer where directory entries are stored + /// + /// TODO: Ideally we should return directory entries as typed records. + buf: push-buffer, + /// If true, rewind the current position to the beginning before reading. + rewind: bool, +) -> ( + /// The number of bytes stored in the read buffer. If less than the size of + /// the read buffer, the end of the directory has been reached. + expected +) +``` + +## `seek` +```wai +/// Move the offset of a descriptor. +/// +/// The meaning of `seek` on a directory is unspecified. +/// +/// Note: This is similar to `lseek` in POSIX. +seek: function( + /// The method to compute the new offset. + "from": seek_from, +) -> ( + /// The new offset of the descriptor, relative to the start of the file. + expected +) +``` + +## `sync` +```wai +/// Synchronize the data and metadata of a file to disk. +/// +/// Note: This is similar to `fsync` in POSIX. +sync: function() -> expected<_, errno> +``` + +## `tell` +```wai +/// Return the current offset of a descriptor. +/// +/// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. +tell: function() -> ( + /// The current offset of the descriptor, relative to the start of the file. + expected +) +``` + +## `write` +```wai +/// Write to a descriptor. +/// +/// Note: This is similar to `write` in POSIX. +write: function( + /// Data to write + buf: pull-buffer, +) -> expected +``` + +## `create_directory_at` +```wai +/// Create a directory. +/// +/// Note: This is similar to `mkdirat` in POSIX. +create_directory_at: function( + /// The relative path at which to create the directory. + path: string, +) -> expected<_, errno> +``` + +## `stat_at` +```wai +/// Return the attributes of a file or directory. +/// +/// Note: This is similar to `fstatat` in POSIX. +/// +/// Note: This was called `fd_filestat_get` in earlier versions of WASI. +stat_at: function( + /// Flags determining the method of how the path is resolved. + lookupflags: lookupflags, + /// The relative path of the file or directory to inspect. + path: string, +) -> ( + /// The buffer where the file's attributes are stored. + expected +) +``` + +## `set_times_at` +```wai +/// Adjust the timestamps of a file or directory. +/// +/// Note: This is similar to `utimensat` in POSIX. +/// +/// Note: This was called `path_filestat_set_times` in earlier versions of WASI. +set_times_at: function( + /// Flags determining the method of how the path is resolved. + lookupflags: lookupflags, + /// The relative path of the file or directory to operate on. + path: string, + /// The desired values of the data access timestamp. + atim: new_timestamp, + /// The desired values of the data modification timestamp. + mtim: new_timestamp, +) -> expected<_, errno> +``` + +## `link_at` +```wai +/// Create a hard link. +/// +/// Note: This is similar to `linkat` in POSIX. +link_at: function( + /// Flags determining the method of how the path is resolved. + old_lookupflags: lookupflags, + /// The relative source path from which to link. + old_path: string, + /// The base directory for `new_path`. + new_descriptor: handle descriptor, + /// The relative destination path at which to create the hard link. + new_path: string, +) -> expected<_, errno> +``` + +## `open_at` +```wai +/// Open a file or directory. +/// +/// The returned descriptor is not guaranteed to be the lowest-numbered +/// descriptor not currently open/ it is randomized to prevent applications +/// from depending on making assumptions about indexes, since this is +/// error-prone in multi-threaded contexts. The returned descriptor is +/// guaranteed to be less than 2**31. +/// +/// Note: This is similar to `openat` in POSIX. +/// +/// TODO: Re-evaluate `direflags` here. +/// TODO: Add a permissions mode. +open_at: function( + /// Flags determining the method of how the path is resolved. + lookupflags: lookupflags, + /// The relative path of the object to open. + path: string, + /// The method by which to open the file. + oflags: oflags, + /// Flags to use for the resulting descriptor. + fdflags: "flags", + /// Permissions to use when creating a new file. + mode: mode +) -> ( + /// The descriptor of the file that has been opened. + expected +) +``` + +## `readlink_at` +```wai +/// Read the contents of a symbolic link. +/// +/// Note: This is similar to `readlinkat` in POSIX. +readlink_at: function( + /// The relative path of the symbolic link from which to read. + path: string, + /// The buffer to which to write the contents of the symbolic link. + buf: push-buffer, +) -> ( + /// The number of bytes placed in the buffer. + expected +) +``` + +## `remove_directory_at` +```wai +/// Remove a directory. +/// +/// Return `errno::notempty` if the directory is not empty. +/// +/// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. +remove_directory_at: function( + /// The relative path to a directory to remove. + path: string, +) -> expected<_, errno> +``` + +## `rename_at` +```wai +/// Rename a filesystem object. +/// +/// Note: This is similar to `renameat` in POSIX. +rename_at: function( + /// The relative source path of the file or directory to rename. + old_path: string, + /// The base directory for `new_path`. + new_descriptor: handle descriptor, + /// The relative destination path to which to rename the file or directory. + new_path: string, +) -> expected<_, errno> +``` + +## `symlink_at` +```wai +/// Create a symbolic link. +/// +/// Note: This is similar to `symlinkat` in POSIX. +symlink_at: function( + /// The contents of the symbolic link. + old_path: string, + /// The relative destination path at which to create the symbolic link. + new_path: string, +) -> expected<_, errno> +``` + +## `unlink_file_at` +```wai +/// Unlink a filesystem object that is not a directory. +/// +/// Return `errno::isdir` if the path refers to a directory. +/// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. +unlink_file_at: function( + /// The relative path to a file to unlink. + path: string, +) -> expected<_, errno> +``` + +## `change_file_permissions_at` +/// Change the permissions of a filesystem object that is not a directory. +/// +/// Note that the ultimate meanings of these permissions is +/// filesystem-specific. +/// +/// Note: This is similar to `fchmodat` in POSIX. +```wai +change_file_permissions_at: function( + /// Flags determining the method of how the path is resolved. + lookupflags: lookupflags, + /// The relative path to operate on. + path: string, + /// The new permissions for the filesystem object. + mode: mode, +) -> expected<_, errno> +``` + +## `change_dir_permissions_at` +/// Change the permissions of a directory. +/// +/// Note that the ultimate meanings of these permissions is +/// filesystem-specific. +/// +/// Unlike in POSIX, the `executable` flag is not reinterpreted as a "search" +/// flag. `read` on a directory implies readability and searchability, and +/// `execute` is not valid for directories. +/// +/// Note: This is similar to `fchmodat` in POSIX. +```wai +change_directory_permissions_at: function( + /// Flags determining the method of how the path is resolved. + lookupflags: lookupflags, + /// The relative path to operate on. + path: string, + /// The new permissions for the directory. + mode: mode, +) -> expected<_, errno> +``` + +```wai +} +```