componentized/directory

componentized:filesystem

interface dev

A collection of utility components that remix wasi:filesystem types and interfaces.

interface latch componentized:filesystem/latch@0.3.0-dev since 0.3.0-dev

Access control for wasi:filesystem.

A gate consults a latch before performing each filesystem operation. The latch either defers, raising no objection, or denies the operation. A latch never grants an operation, an operation proceeds when the latch defers. Several latches can be aggregated into one, the operation is denied when any of them denies it.

Deciding and acting on a decision are separate steps. authorize decides without side effects, then the gate reports the final decision to observe-decision, where a latch updates any state it keeps. A latch aggregated with others may not be asked to authorize an operation another latch already denied, but it always observes the final decision.

Types

use enum advicesince 0.3.0-dev

from wasi:filesystem/types@0.3.0

File or memory access pattern advisory information.

  • normal

    The application has no advice to give on its behavior with respect to the specified data.

  • sequential

    The application expects to access the specified data sequentially from lower offsets to higher offsets.

  • random

    The application expects to access the specified data in a random order.

  • will-need

    The application expects to access the specified data in the near future.

  • dont-need

    The application expects that it will not access the specified data in the near future.

  • no-reuse

    The application expects to access the specified data once and then not reuse it thereafter.

use resource descriptorsince 0.3.0-dev

from wasi:filesystem/types@0.3.0

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.

  • functionread-via-stream: func( offset: filesize, ) -> tuple<stream<u8>, future<result<_, error-code>>>since 0.3.0

    Return a stream for reading from a file.

    Multiple read, write, and append streams may be active on the same open file and they do not interfere with each other.

    This function returns a stream which provides the data received from the file, and a future providing additional error information in case an error is encountered.

    If no error is encountered, stream.read on the stream will return read-status::closed with no error-context and the future resolves to the value ok. If an error is encountered, stream.read on the stream returns read-status::closed with an error-context and the future resolves to err with an error-code.

    Note: This is similar to pread in POSIX.

  • functionwrite-via-stream: func( data: stream<u8>, offset: filesize, ) -> future<result<_, error-code>>since 0.3.0

    Return a stream for writing to a file, if available.

    May fail with an error-code describing why the file cannot be written.

    It is valid to write past the end of a file; the file is extended to the extent of the write, with bytes between the previous end and the start of the write set to zero.

    This function returns once either full contents of the stream are written or an error is encountered.

    Note: This is similar to pwrite in POSIX.

  • functionappend-via-stream: func( data: stream<u8>, ) -> future<result<_, error-code>>since 0.3.0

    Return a stream for appending to a file, if available.

    May fail with an error-code describing why the file cannot be appended.

    This function returns once either full contents of the stream are written or an error is encountered.

    Note: This is similar to write with O_APPEND in POSIX.

  • async functionadvise: async func( offset: filesize, length: filesize, advice: advice, ) -> result<_, error-code>since 0.3.0

    Provide file advisory information on a descriptor.

    This is similar to posix_fadvise in POSIX.

  • async functionsync-data: async func() -> result<_, error-code>since 0.3.0

    Synchronize the data of a file to disk.

    This function succeeds with no effect if the file descriptor is not opened for writing.

    Note: This is similar to fdatasync in POSIX.

  • async functionget-flags: async func() -> result<descriptor-flags, error-code>since 0.3.0

    Get flags associated with a descriptor.

    Note: This returns similar flags to fcntl(fd, F_GETFL) in POSIX.

    Note: This returns the value that was the fs_flags value returned from fdstat_get in earlier versions of WASI.

  • async functionget-type: async func() -> result<descriptor-type, error-code>since 0.3.0

    Get the dynamic type of a descriptor.

    Note: This returns the same value as the type field of the fd-stat returned by stat, stat-at and similar.

    Note: This returns similar flags to the st_mode & S_IFMT value provided by fstat in POSIX.

    Note: This returns the value that was the fs_filetype value returned from fdstat_get in earlier versions of WASI.

  • async functionset-size: async func(size: filesize) -> result<_, error-code>since 0.3.0

    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.

  • async functionset-times: async func( data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp, ) -> result<_, error-code>since 0.3.0

    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.

  • functionread-directory: func() -> tuple<stream<directory-entry>, future<result<_, error-code>>>since 0.3.0

    Read directory entries from a directory.

    On filesystems where directories contain entries referring to themselves and their parents, often named . and .. respectively, these entries are omitted.

    This always returns a new stream which starts at the beginning of the directory. Multiple streams may be active on the same directory, and they do not interfere with each other.

    This function returns a future, which will resolve to an error code if reading full contents of the directory fails.

  • async functionsync: async func() -> result<_, error-code>since 0.3.0

    Synchronize the data and metadata of a file to disk.

    This function succeeds with no effect if the file descriptor is not opened for writing.

    Note: This is similar to fsync in POSIX.

  • async functioncreate-directory-at: async func(path: string) -> result<_, error-code>since 0.3.0

    Create a directory.

    Note: This is similar to mkdirat in POSIX.

  • async functionstat: async func() -> result<descriptor-stat, error-code>since 0.3.0

    Return the attributes of an open file or directory.

    Note: This is similar to fstat in POSIX, except that it does not return device and inode information. For testing whether two descriptors refer to the same underlying filesystem object, use is-same-object. To obtain additional data that can be used do determine whether a file has been modified, use metadata-hash.

    Note: This was called fd_filestat_get in earlier versions of WASI.

  • async functionstat-at: async func( path-flags: path-flags, path: string, ) -> result<descriptor-stat, error-code>since 0.3.0

    Return the attributes of a file or directory.

    Note: This is similar to fstatat in POSIX, except that it does not return device and inode information. See the stat description for a discussion of alternatives.

    Note: This was called path_filestat_get in earlier versions of WASI.

  • async functionset-times-at: async func( path-flags: path-flags, path: string, data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp, ) -> result<_, error-code>since 0.3.0

    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.

  • async functionopen-at: async func( path-flags: path-flags, path: string, open-flags: open-flags, flags: descriptor-flags, ) -> result<descriptor, error-code>since 0.3.0

    Open a file or directory.

    If flags contains descriptor-flags::mutate-directory, and the base descriptor doesn't have descriptor-flags::mutate-directory set, open-at fails with error-code::read-only.

    If flags contains write or mutate-directory, or open-flags contains truncate or create, and the base descriptor doesn't have descriptor-flags::mutate-directory set, open-at fails with error-code::read-only.

    Note: This is similar to openat in POSIX.

  • async functionremove-directory-at: async func(path: string) -> result<_, error-code>since 0.3.0

    Remove a directory.

    Return error-code::not-empty if the directory is not empty.

    Note: This is similar to unlinkat(fd, path, AT_REMOVEDIR) in POSIX.

  • async functionrename-at: async func( old-path: string, new-descriptor: borrow<descriptor>, new-path: string, ) -> result<_, error-code>since 0.3.0

    Rename a filesystem object.

    Note: This is similar to renameat in POSIX.

  • async functionis-same-object: async func(other: borrow<descriptor>) -> boolsince 0.3.0

    Test whether two descriptors refer to the same filesystem object.

    In POSIX, this corresponds to testing whether the two descriptors have the same device (st_dev) and inode (st_ino or d_ino) numbers. wasi-filesystem does not expose device and inode numbers, so this function may be used instead.

  • async functionmetadata-hash: async func() -> result<metadata-hash-value, error-code>since 0.3.0

    Return a hash of the metadata associated with a filesystem object referred to by a descriptor.

    This returns a hash of the last-modification timestamp and file size, and may also include the inode number, device number, birth timestamp, and other metadata fields that may change when the file is modified or replaced. It may also include a secret value chosen by the implementation and not otherwise exposed.

    Implementations are encouraged to provide the following properties:

    • If the file is not modified or replaced, the computed hash value should usually not change.
    • If the object is modified or replaced, the computed hash value should usually change.
    • The inputs to the hash should not be easily computable from the computed hash.

    However, none of these is required.

  • async functionmetadata-hash-at: async func( path-flags: path-flags, path: string, ) -> result<metadata-hash-value, error-code>since 0.3.0

    Return a hash of the metadata associated with a filesystem object referred to by a directory descriptor and a relative path.

    This performs the same hash computation as metadata-hash.

use flags descriptor-flagssince 0.3.0-dev

from wasi:filesystem/types@0.3.0

Descriptor flags.

Note: This was called fdflags in earlier versions of WASI.

  • read

    Read mode: Data can be read.

  • write

    Write mode: Data can be written to.

  • file-integrity-sync

    Request that writes be performed according to synchronized I/O file integrity completion. The data stored in the file and the file's metadata are synchronized. This is similar to O_SYNC in POSIX.

    The precise semantics of this operation have not yet been defined for WASI. At this time, it should be interpreted as a request, and not a requirement.

  • data-integrity-sync

    Request that writes be performed according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. This is similar to O_DSYNC in POSIX.

    The precise semantics of this operation have not yet been defined for WASI. At this time, it should be interpreted as a request, and not a requirement.

  • requested-write-sync

    Requests that reads be performed at the same level of integrity requested for writes. This is similar to O_RSYNC in POSIX.

    The precise semantics of this operation have not yet been defined for WASI. At this time, it should be interpreted as a request, and not a requirement.

  • mutate-directory

    Mutating directories mode: Directory contents may be mutated.

    When this flag is unset on a descriptor, operations using the descriptor which would create, rename, delete, modify the data or metadata of filesystem objects, or obtain another handle which would permit any of those, shall fail with error-code::read-only if they would otherwise succeed.

    This may only be set on directories.

use record directory-entrysince 0.3.0-dev

from wasi:filesystem/types@0.3.0

A directory entry.

  • type: descriptor-type

    The type of the file referred to by this directory entry.

  • name: string

    The name of the object.

use variant filesystem-error-codesince 0.3.0-dev

from wasi:filesystem/types@0.3.0

Error codes returned by functions, similar to errno in POSIX. 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.

  • access

    Permission denied, similar to EACCES in POSIX.

  • already

    Connection already in progress, similar to EALREADY in POSIX.

  • bad-descriptor

    Bad descriptor, similar to EBADF in POSIX.

  • busy

    Device or resource busy, similar to EBUSY in POSIX.

  • deadlock

    Resource deadlock would occur, similar to EDEADLK in POSIX.

  • quota

    Storage quota exceeded, similar to EDQUOT in POSIX.

  • exist

    File exists, similar to EEXIST in POSIX.

  • file-too-large

    File too large, similar to EFBIG in POSIX.

  • illegal-byte-sequence

    Illegal byte sequence, similar to EILSEQ in POSIX.

  • in-progress

    Operation in progress, similar to EINPROGRESS in POSIX.

  • interrupted

    Interrupted function, similar to EINTR in POSIX.

  • invalid

    Invalid argument, similar to EINVAL in POSIX.

  • io

    I/O error, similar to EIO in POSIX.

  • is-directory

    Is a directory, similar to EISDIR in POSIX.

  • loop

    Too many levels of symbolic links, similar to ELOOP in POSIX.

  • too-many-links

    Too many links, similar to EMLINK in POSIX.

  • message-size

    Message too large, similar to EMSGSIZE in POSIX.

  • name-too-long

    Filename too long, similar to ENAMETOOLONG in POSIX.

  • no-device

    No such device, similar to ENODEV in POSIX.

  • no-entry

    No such file or directory, similar to ENOENT in POSIX.

  • no-lock

    No locks available, similar to ENOLCK in POSIX.

  • insufficient-memory

    Not enough space, similar to ENOMEM in POSIX.

  • insufficient-space

    No space left on device, similar to ENOSPC in POSIX.

  • not-directory

    Not a directory or a symbolic link to a directory, similar to ENOTDIR in POSIX.

  • not-empty

    Directory not empty, similar to ENOTEMPTY in POSIX.

  • not-recoverable

    State not recoverable, similar to ENOTRECOVERABLE in POSIX.

  • unsupported

    Not supported, similar to ENOTSUP and ENOSYS in POSIX.

  • no-tty

    Inappropriate I/O control operation, similar to ENOTTY in POSIX.

  • no-such-device

    No such device or address, similar to ENXIO in POSIX.

  • overflow

    Value too large to be stored in data type, similar to EOVERFLOW in POSIX.

  • not-permitted

    Operation not permitted, similar to EPERM in POSIX.

  • pipe

    Broken pipe, similar to EPIPE in POSIX.

  • read-only

    Read-only file system, similar to EROFS in POSIX.

  • invalid-seek

    Invalid seek, similar to ESPIPE in POSIX.

  • text-file-busy

    Text file busy, similar to ETXTBSY in POSIX.

  • cross-device

    Cross-device link, similar to EXDEV in POSIX.

  • other: option<string>

    A catch-all for errors not captured by the existing variants. Implementations can use this to extend the error type without breaking existing code.

use type filesizesince 0.3.0-dev

from wasi:filesystem/types@0.3.0

File size or length of a region within a file.

  • u64

use flags open-flagssince 0.3.0-dev

from wasi:filesystem/types@0.3.0

Open flags used by open-at.

  • create

    Create file if it does not exist, similar to O_CREAT in POSIX.

  • directory

    Fail if not a directory, similar to O_DIRECTORY in POSIX.

  • exclusive

    Fail if file already exists, similar to O_EXCL in POSIX.

  • truncate

    Truncate file to size 0, similar to O_TRUNC in POSIX.

use variant new-timestampsince 0.3.0-dev

from wasi:filesystem/types@0.3.0

When setting a timestamp, this gives the value to set it to.

  • no-change

    Leave the timestamp set to its previous value.

  • now

    Set the timestamp to the current time of the system clock associated with the filesystem.

  • timestamp: instant

    Set the timestamp to the given value.

use flags path-flagssince 0.3.0-dev

from wasi:filesystem/types@0.3.0

Flags determining the method of how paths are resolved.

  • symlink-follow

    As long as the resolved path corresponds to a symbolic link, it is expanded.

type pathsince 0.3.0-dev

The path of a descriptor, the directory or file an operation is performed on, and that the relative path arguments of the operation are resolved against.

A preopen is labeled with its name, e.g. / or .. A descriptor opened with open-at is labeled with the path it was opened at, joined to the path of the descriptor it was opened from: . and .. are applied, and symlinks are followed where the host follows them. Path arguments of an operation are passed as given by the caller, relative to the descriptor's path.

  • string

record descriptor-read-via-stream-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.read-via-stream.

  • offset: filesize

    The offset in the file to start reading at.

record descriptor-write-via-stream-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.write-via-stream.

  • offset: filesize

    The offset in the file to start writing at.

record descriptor-advise-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.advise.

  • offset: filesize

    The offset of the range the advice applies to.

  • length: filesize

    The length of the range the advice applies to.

  • advice: advice

    The advice for the range.

record descriptor-set-size-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.set-size.

  • size: filesize

    The new size of the file.

record descriptor-set-times-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.set-times.

record descriptor-create-directory-at-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.create-directory-at.

  • path: path

    The directory to create, relative to the descriptor's path.

record descriptor-stat-at-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.stat-at.

  • path-flags: path-flags

    Whether a symlink at the last component of the path is followed.

  • path: path

    The file or directory to stat, relative to the descriptor's path.

record descriptor-set-times-at-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.set-times-at.

  • path-flags: path-flags

    Whether a symlink at the last component of the path is followed.

  • path: path

    The file or directory to change, relative to the descriptor's path.

  • data-access-timestamp: new-timestamp

    The new access time.

  • data-modification-timestamp: new-timestamp

    The new modification time.

record descriptor-open-at-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.open-at.

  • path-flags: path-flags

    Whether a symlink at the last component of the path is followed.

  • path: path

    The file or directory to open, relative to the descriptor's path.

  • open-flags: open-flags

    How the file or directory is opened, e.g. whether it is created or truncated.

  • flags: descriptor-flags

    What the opened descriptor may be used for, e.g. reading, writing or changing a directory.

record descriptor-remove-directory-at-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.remove-directory-at.

  • path: path

    The directory to remove, relative to the descriptor's path.

record descriptor-rename-at-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.rename-at.

  • old-path: path

    The file or directory to rename, relative to the descriptor's path.

  • new-descriptor: borrow<descriptor>

    The directory the new path is relative to, a latch may query it.

  • new-path: path

    The new path, relative to the new descriptor.

record descriptor-metadata-hash-at-argssince 0.3.0-dev

Arguments to wasi:filesystem/types#descriptor.metadata-hash-at.

  • path-flags: path-flags

    Whether a symlink at the last component of the path is followed.

  • path: path

    The file or directory to hash, relative to the descriptor's path.

variant preopens-operationsince 0.3.0-dev

Operations of the wasi:filesystem/preopens interface.

  • get-directories

    Authorized before the preopens are returned. A denial returns no preopens.

  • get-directories-item: tuple<borrow<descriptor>, path>

    Authorized for each preopen, with its name. A denied preopen is left out of the preopens returned, the other preopens are still returned.

variant descriptor-operationsince 0.3.0-dev

Operations of the wasi:filesystem/types#descriptor resource.

The borrowed descriptor is the descriptor the operation is performed on, followed by its path, a latch may query the descriptor, for example for its type or flags. A denial fails the operation with the denial's error code, unless noted otherwise.

variant operationsince 0.3.0-dev

Operations of the wasi:filesystem package a gate asks a latch to authorize.

variant decisionsince 0.3.0-dev

Whether the operation is allowed to proceed.

  • deferred

    The latch raises no objection, the operation proceeds unless another latch denies it.

  • denied: filesystem-error-code

    The operation must not proceed. The error code is returned to the caller for operations that fail. For items that are left out instead, a preopen or a directory entry, it is only logged.

type latch-namesince 0.3.0-dev

The name of a latch component, identifying which latch reported an error.

  • string

variant error-codesince 0.3.0-dev

A latch was unable to decide or observe. The gate fails the operation, a directory entry fails the directory read. Getting the preopens can't fail, the gate traps instead.

  • invalid-config: latch-name

    The latch's configuration is invalid, the cause is logged by the latch.

  • observation-failed: latch-name

    The latch was unable to act on the final decision.

  • other: option<string>

    Any other error.

Functions

functionauthorize: func(operation: operation) -> result<decision, error-code>since 0.3.0-dev

Decide whether the operation may proceed.

Must not have side effects. When latches are aggregated, a latch is not asked about an operation another latch already denied, so state changed here could disagree with what actually happened. Act on decisions in observe-decision instead.

functionobserve-decision: func( final-decision: decision, operation: operation, ) -> result<_, error-code>since 0.3.0-dev

Observe the final decision for an operation, after authorize returned a decision.

Called for both deferred and denied operations, including operations this latch was not asked to authorize because an aggregated latch denied them first. The final decision is what the gate enforces, so this is where a latch updates any state it keeps. Not called when authorize returned an error.

An error fails the operation, since the latch could not act on the decision.

WIT

/// Access control for `wasi:filesystem`.
///
/// A gate consults a latch before performing each filesystem operation. The latch either defers,
/// raising no objection, or denies the operation. A latch never grants an operation, an
/// operation proceeds when the latch defers. Several latches can be aggregated into one, the
/// operation is denied when any of them denies it.
///
/// Deciding and acting on a decision are separate steps. `authorize` decides without side
/// effects, then the gate reports the final decision to `observe-decision`, where a latch updates
/// any state it keeps. A latch aggregated with others may not be asked to authorize an operation
/// another latch already denied, but it always observes the final decision.
@since(version = 0.3.0-dev)
interface latch {
  @since(version = 0.3.0-dev)
  use wasi:filesystem/types@0.3.0.{advice, descriptor, descriptor-flags, directory-entry, error-code as filesystem-error-code, filesize, open-flags, new-timestamp, path-flags};

  /// The path of a descriptor, the directory or file an operation is performed on, and that the
  /// relative path arguments of the operation are resolved against.
  ///
  /// A preopen is labeled with its name, e.g. `/` or `.`. A descriptor opened with `open-at` is
  /// labeled with the path it was opened at, joined to the path of the descriptor it was opened
  /// from: `.` and `..` are applied, and symlinks are followed where the host follows them.
  /// Path arguments of an operation are passed as given by the caller, relative to the
  /// descriptor's path.
  @since(version = 0.3.0-dev)
  type path = string;

  /// Arguments to wasi:filesystem/types#descriptor.read-via-stream.
  @since(version = 0.3.0-dev)
  record descriptor-read-via-stream-args {
    /// The offset in the file to start reading at.
    offset: filesize,
  }

  /// Arguments to wasi:filesystem/types#descriptor.write-via-stream.
  @since(version = 0.3.0-dev)
  record descriptor-write-via-stream-args {
    /// The offset in the file to start writing at.
    offset: filesize,
  }

  /// Arguments to wasi:filesystem/types#descriptor.advise.
  @since(version = 0.3.0-dev)
  record descriptor-advise-args {
    /// The offset of the range the advice applies to.
    offset: filesize,
    /// The length of the range the advice applies to.
    length: filesize,
    /// The advice for the range.
    advice: advice,
  }

  /// Arguments to wasi:filesystem/types#descriptor.set-size.
  @since(version = 0.3.0-dev)
  record descriptor-set-size-args {
    /// The new size of the file.
    size: filesize,
  }

  /// Arguments to wasi:filesystem/types#descriptor.set-times.
  @since(version = 0.3.0-dev)
  record descriptor-set-times-args {
    /// The new access time.
    data-access-timestamp: new-timestamp,
    /// The new modification time.
    data-modification-timestamp: new-timestamp,
  }

  /// Arguments to wasi:filesystem/types#descriptor.create-directory-at.
  @since(version = 0.3.0-dev)
  record descriptor-create-directory-at-args {
    /// The directory to create, relative to the descriptor's path.
    path: path,
  }

  /// Arguments to wasi:filesystem/types#descriptor.stat-at.
  @since(version = 0.3.0-dev)
  record descriptor-stat-at-args {
    /// Whether a symlink at the last component of the path is followed.
    path-flags: path-flags,
    /// The file or directory to stat, relative to the descriptor's path.
    path: path,
  }

  /// Arguments to wasi:filesystem/types#descriptor.set-times-at.
  @since(version = 0.3.0-dev)
  record descriptor-set-times-at-args {
    /// Whether a symlink at the last component of the path is followed.
    path-flags: path-flags,
    /// The file or directory to change, relative to the descriptor's path.
    path: path,
    /// The new access time.
    data-access-timestamp: new-timestamp,
    /// The new modification time.
    data-modification-timestamp: new-timestamp,
  }

  /// Arguments to wasi:filesystem/types#descriptor.link-at.
  @since(version = 0.3.0-dev)
  record descriptor-link-at-args {
    /// Whether a symlink at the last component of the old path is followed.
    old-path-flags: path-flags,
    /// The existing file to link to, relative to the descriptor's path.
    old-path: path,
    /// The directory the new path is relative to, a latch may query it.
    new-descriptor: borrow<descriptor>,
    /// The link to create, relative to the new descriptor.
    new-path: path,
  }

  /// Arguments to wasi:filesystem/types#descriptor.open-at.
  @since(version = 0.3.0-dev)
  record descriptor-open-at-args {
    /// Whether a symlink at the last component of the path is followed.
    path-flags: path-flags,
    /// The file or directory to open, relative to the descriptor's path.
    path: path,
    /// How the file or directory is opened, e.g. whether it is created or truncated.
    open-flags: open-flags,
    /// What the opened descriptor may be used for, e.g. reading, writing or changing a
    /// directory.
    %flags: descriptor-flags,
  }

  /// Arguments to wasi:filesystem/types#descriptor.readlink-at.
  @since(version = 0.3.0-dev)
  record descriptor-readlink-at-args {
    /// The symlink to read, relative to the descriptor's path.
    path: path,
  }

  /// Arguments to wasi:filesystem/types#descriptor.remove-directory-at.
  @since(version = 0.3.0-dev)
  record descriptor-remove-directory-at-args {
    /// The directory to remove, relative to the descriptor's path.
    path: path,
  }

  /// Arguments to wasi:filesystem/types#descriptor.rename-at.
  @since(version = 0.3.0-dev)
  record descriptor-rename-at-args {
    /// The file or directory to rename, relative to the descriptor's path.
    old-path: path,
    /// The directory the new path is relative to, a latch may query it.
    new-descriptor: borrow<descriptor>,
    /// The new path, relative to the new descriptor.
    new-path: path,
  }

  /// Arguments to wasi:filesystem/types#descriptor.symlink-at.
  @since(version = 0.3.0-dev)
  record descriptor-symlink-at-args {
    /// The contents of the symlink, the path it points to. It is not resolved by the gate.
    old-path: path,
    /// The symlink to create, relative to the descriptor's path.
    new-path: path,
  }

  /// Arguments to wasi:filesystem/types#descriptor.unlink-file-at.
  @since(version = 0.3.0-dev)
  record descriptor-unlink-file-at-args {
    /// The file to unlink, relative to the descriptor's path.
    path: path,
  }

  /// Arguments to wasi:filesystem/types#descriptor.metadata-hash-at.
  @since(version = 0.3.0-dev)
  record descriptor-metadata-hash-at-args {
    /// Whether a symlink at the last component of the path is followed.
    path-flags: path-flags,
    /// The file or directory to hash, relative to the descriptor's path.
    path: path,
  }

  /// Operations of the wasi:filesystem/preopens interface.
  @since(version = 0.3.0-dev)
  variant preopens-operation {
    /// Authorized before the preopens are returned. A denial returns no preopens.
    get-directories,
    /// Authorized for each preopen, with its name. A denied preopen is left out of the
    /// preopens returned, the other preopens are still returned.
    get-directories-item(tuple<borrow<descriptor>, path>),
  }

  /// Operations of the wasi:filesystem/types#descriptor resource.
  ///
  /// The borrowed descriptor is the descriptor the operation is performed on, followed by its
  /// path, a latch may query the descriptor, for example for its type or flags. A denial fails
  /// the operation with the denial's error code, unless noted otherwise.
  @since(version = 0.3.0-dev)
  variant descriptor-operation {
    /// Authorized before reading a file. A denial returns an empty stream, the future
    /// resolves to the error code.
    read-via-stream(tuple<borrow<descriptor>, path, descriptor-read-via-stream-args>),
    /// Authorized before writing a file. A denial reads nothing from the stream, the future
    /// resolves to the error code.
    write-via-stream(tuple<borrow<descriptor>, path, descriptor-write-via-stream-args>),
    /// Authorized before appending to a file. A denial reads nothing from the stream, the
    /// future resolves to the error code.
    append-via-stream(tuple<borrow<descriptor>, path>),
    /// Authorized before giving advice for a file.
    advise(tuple<borrow<descriptor>, path, descriptor-advise-args>),
    /// Authorized before syncing the data of a file.
    sync-data(tuple<borrow<descriptor>, path>),
    /// Authorized before reading the flags of a descriptor.
    get-flags(tuple<borrow<descriptor>, path>),
    /// Authorized before reading the type of a descriptor.
    get-type(tuple<borrow<descriptor>, path>),
    /// Authorized before changing the size of a file.
    set-size(tuple<borrow<descriptor>, path, descriptor-set-size-args>),
    /// Authorized before changing the times of a file or directory.
    set-times(tuple<borrow<descriptor>, path, descriptor-set-times-args>),
    /// Authorized before reading a directory. A denial returns an empty stream, the future
    /// resolves to the error code.
    read-directory(tuple<borrow<descriptor>, path>),
    /// Authorized for each entry of a directory being read. A denied entry is left out of the
    /// stream, the other entries are still returned.
    read-directory-entry(tuple<borrow<descriptor>, path, directory-entry>),
    /// Authorized before syncing the data and metadata of a file or directory.
    sync(tuple<borrow<descriptor>, path>),
    /// Authorized before creating a directory.
    create-directory-at(tuple<borrow<descriptor>, path, descriptor-create-directory-at-args>),
    /// Authorized before reading the attributes of a file or directory.
    stat(tuple<borrow<descriptor>, path>),
    /// Authorized before reading the attributes of a file or directory at a path.
    stat-at(tuple<borrow<descriptor>, path, descriptor-stat-at-args>),
    /// Authorized before changing the times of a file or directory at a path.
    set-times-at(tuple<borrow<descriptor>, path, descriptor-set-times-at-args>),
    /// Authorized before creating a hard link.
    link-at(tuple<borrow<descriptor>, path, descriptor-link-at-args>),
    /// Authorized before opening a file or directory.
    open-at(tuple<borrow<descriptor>, path, descriptor-open-at-args>),
    /// Authorized before reading the contents of a symlink.
    readlink-at(tuple<borrow<descriptor>, path, descriptor-readlink-at-args>),
    /// Authorized before removing a directory.
    remove-directory-at(tuple<borrow<descriptor>, path, descriptor-remove-directory-at-args>),
    /// Authorized before renaming a file or directory.
    rename-at(tuple<borrow<descriptor>, path, descriptor-rename-at-args>),
    /// Authorized before creating a symlink.
    symlink-at(tuple<borrow<descriptor>, path, descriptor-symlink-at-args>),
    /// Authorized before unlinking a file.
    unlink-file-at(tuple<borrow<descriptor>, path, descriptor-unlink-file-at-args>),
    /// Authorized before hashing the metadata of a file or directory.
    metadata-hash(tuple<borrow<descriptor>, path>),
    /// Authorized before hashing the metadata of a file or directory at a path.
    metadata-hash-at(tuple<borrow<descriptor>, path, descriptor-metadata-hash-at-args>),
  }

  /// Operations of the wasi:filesystem package a gate asks a latch to authorize.
  @since(version = 0.3.0-dev)
  variant operation {
    /// An operation of wasi:filesystem/preopens.
    preopens(preopens-operation),
    /// An operation on a wasi:filesystem/types#descriptor.
    descriptor(descriptor-operation),
  }

  /// Whether the operation is allowed to proceed.
  @since(version = 0.3.0-dev)
  variant decision {
    /// The latch raises no objection, the operation proceeds unless another latch denies it.
    deferred,
    /// The operation must not proceed. The error code is returned to the caller for
    /// operations that fail. For items that are left out instead, a preopen or a directory
    /// entry, it is only logged.
    denied(filesystem-error-code),
  }

  /// The name of a latch component, identifying which latch reported an error.
  @since(version = 0.3.0-dev)
  type latch-name = string;

  /// A latch was unable to decide or observe. The gate fails the operation, a directory entry
  /// fails the directory read. Getting the preopens can't fail, the gate traps instead.
  @since(version = 0.3.0-dev)
  variant error-code {
    /// The latch's configuration is invalid, the cause is logged by the latch.
    invalid-config(latch-name),
    /// The latch was unable to act on the final decision.
    observation-failed(latch-name),
    /// Any other error.
    other(option<string>),
  }

  /// Decide whether the operation may proceed.
  ///
  /// Must not have side effects. When latches are aggregated, a latch is not asked about an
  /// operation another latch already denied, so state changed here could disagree with what
  /// actually happened. Act on decisions in `observe-decision` instead.
  @since(version = 0.3.0-dev)
  authorize: func(operation: operation) -> result<decision, error-code>;

  /// Observe the final decision for an operation, after `authorize` returned a decision.
  ///
  /// Called for both deferred and denied operations, including operations this latch was not
  /// asked to authorize because an aggregated latch denied them first. The final decision is
  /// what the gate enforces, so this is where a latch updates any state it keeps. Not called
  /// when `authorize` returned an error.
  ///
  /// An error fails the operation, since the latch could not act on the decision.
  @since(version = 0.3.0-dev)
  observe-decision: func(final-decision: decision, operation: operation) -> result<_, error-code>;
}