├── .github └── workflows │ ├── main.yml │ ├── publish.yml │ └── update.yml ├── LICENSE.md ├── README.md ├── imports.md ├── test └── README.md └── wit ├── error.wit ├── poll.wit ├── streams.wit └── world.wit /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | abi-up-to-date: 10 | name: Check ABI files are up-to-date 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: WebAssembly/wit-abi-up-to-date@v23 15 | with: 16 | wit-bindgen: '0.37.0' 17 | all-features: 'true' -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish a Wasm Component package to GitHub Artifacts 2 | 3 | # Run this action whenever a new release is tagged 4 | on: 5 | push: 6 | tags: 7 | - v* 8 | workflow_dispatch: 9 | 10 | env: 11 | IMAGE_NAME: ${{ github.repository }} 12 | 13 | jobs: 14 | publish: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | id-token: write 18 | packages: write 19 | contents: write 20 | 21 | steps: 22 | # Checkout the repo and install dependencies 23 | - name: Checkout repository 24 | uses: actions/checkout@v2 25 | - name: Install cargo-binstall 26 | uses: cargo-bins/cargo-binstall@v1.10.15 27 | - name: Install wkg 28 | shell: bash 29 | run: cargo binstall wkg 30 | - name: Install cosign 31 | uses: sigstore/cosign-installer@v3.7.0 32 | 33 | # To version our image we want to obtain the version from the tag 34 | - name: Get version 35 | id: meta 36 | uses: docker/metadata-action@v5 37 | with: 38 | images: ghcr.io/webassembly/wasi/io 39 | tags: | 40 | type=semver,pattern={{version}} 41 | 42 | # To upload our image to the GitHub registry, we first have to login 43 | - name: Login to the GitHub registry 44 | uses: docker/login-action@v3 45 | with: 46 | registry: ghcr.io 47 | username: ${{ github.actor }} 48 | password: ${{ secrets.ORG_PAT }} 49 | 50 | # Build our `.wit` files into a Wasm binary 51 | - name: Build 52 | shell: bash 53 | run: wkg wit build -o wasi-io.wasm 54 | 55 | # Upload the Wasm binary to the GitHub registry 56 | - name: Publish to GitHub Container Registry 57 | id: publish 58 | uses: bytecodealliance/wkg-github-action@v5 59 | with: 60 | oci-reference-without-tag: 'ghcr.io/webassembly/wasi/io' 61 | file: 'wasi-io.wasm' 62 | description: 'A WASI API providing I/O stream abstractions.' 63 | source: 'https://github.com/webassembly/wasi' 64 | homepage: 'https://wasi.dev' 65 | version: ${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 66 | licenses: 'Apache-2.0 WITH LLVM-exception' 67 | 68 | # Sign the output component 69 | - name: Sign the wasm component 70 | run: cosign sign --yes ghcr.io/webassembly/wasi/io@${{ steps.publish.outputs.digest }} 71 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: Create a PR to upgrade WIT to a new version 2 | 3 | # Manually dispatch this action from the Actions page 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | prev_version: 8 | description: 'Upgrading from version (without the v)' 9 | required: true 10 | type: string 11 | next_version: 12 | description: 'Upgrading to version (without the v)' 13 | required: true 14 | type: string 15 | 16 | permissions: 17 | pull-requests: write 18 | contents: write 19 | 20 | jobs: 21 | update-versions: 22 | runs-on: ubuntu-latest 23 | steps: 24 | # Checkout the repo and install dependencies 25 | - name: Checkout repository 26 | uses: actions/checkout@v4 27 | - name: Install cargo-binstall 28 | uses: cargo-bins/cargo-binstall@v1.10.15 29 | - name: Install wit-bindgen 30 | shell: bash 31 | run: cargo binstall wit-bindgen-cli 32 | - name: Install wit-deps 33 | shell: bash 34 | run: cargo binstall wit-deps-cli 35 | 36 | # echo input 37 | - name: Print the versions 38 | run: echo Upgrading from ${{ inputs.prev_version }} to ${{ inputs.next_version }} 39 | 40 | # upgrade 41 | - name: Upgrade tag 42 | run: find . -type f -name "*.wit" -exec sed -i "s/${{ inputs.prev_version }}/${{ inputs.next_version }}/g" {} + 43 | # - name: Upgrade wit deps 44 | # run: wit-deps update 45 | - name: Generate markdown 46 | run: wit-bindgen markdown wit --html-in-md --all-features 47 | 48 | # file PR 49 | - name: Create feature branch 50 | env: 51 | FEATURE_BRANCH: release-v${{ inputs.next_version }} 52 | run: | 53 | git config user.name "github-actions[bot]" 54 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 55 | git add . 56 | git checkout -b $FEATURE_BRANCH 57 | git push -u origin $FEATURE_BRANCH 58 | git commit -m "Update to v${{ inputs.next_version }}" 59 | git push 60 | 61 | - name: Create pull request 62 | run: gh pr create -B main -H release-v${{ inputs.next_version }} --title 'Release v${{ inputs.next_version }}' --body 'Updates the package version to v${{ inputs.next_version }}. Thanks!' 63 | env: 64 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 65 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2019-2023 the Contributors to the WASI Specification, published 2 | by the [WebAssembly Community Group][cg] under the 3 | [W3C Community Contributor License Agreement (CLA)][cla]. A human-readable 4 | [summary][summary] is available. 5 | 6 | [cg]: https://www.w3.org/community/webassembly/ 7 | [cla]: https://www.w3.org/community/about/agreements/cla/ 8 | [summary]: https://www.w3.org/community/about/agreements/cla-deed/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WASI I/O 2 | 3 | A proposed [WebAssembly System Interface](https://github.com/WebAssembly/WASI) API. 4 | 5 | ### Current Phase 6 | 7 | WASI I/O is currently in [Phase 3]. 8 | 9 | [Phase 3]: https://github.com/WebAssembly/WASI/blob/main/Proposals.md#phase-3---implementation-phase-cg--wg 10 | 11 | ### Champions 12 | 13 | - Dan Gohman 14 | 15 | ### Portability Criteria 16 | 17 | WASI I/O must have host implementations which can pass the testsuite on at least Windows, macOS, and Linux. 18 | 19 | WASI I/O must have at least two complete independent implementations. 20 | 21 | ## Table of Contents 22 | 23 | - [Introduction](#introduction) 24 | - [Goals [or Motivating Use Cases, or Scenarios]](#goals-or-motivating-use-cases-or-scenarios) 25 | - [Non-goals](#non-goals) 26 | - [API walk-through](#api-walk-through) 27 | - [Use case: copying from input to output using `read`/`write`](#use-case-copying-from-input-to-output-using-readwrite) 28 | - [Use case: copying from input to output using `splice`](#use-case-copying-from-input-to-output-using-splice) 29 | - [Use case: copying from input to output using `forward`](#use-case-copying-from-input-to-output-using-forward) 30 | - [Detailed design discussion](#detailed-design-discussion) 31 | - [Should we have support for non-blocking read/write?](#should-we-have-support-for-non-blocking-read-write) 32 | - [Why do read/write use u64 sizes?[Tricky design choice 2]](#why-do-read-write-use-u64-sizes) 33 | - [Why have a `forward` function when you can just `splice` in a loop?](#why-have-a-forward-function-when-you-can-just-splice-in-a-loop) 34 | - [Stakeholder Interest & Feedback](#stakeholder-interest--feedback) 35 | - [References & acknowledgements](#references--acknowledgements) 36 | 37 | ### Introduction 38 | 39 | Wasi I/O is an API providing I/O stream abstractions. There are two 40 | types, `input-stream`, and `output-stream`, which support `read` and 41 | `write`, respectively, as well as a number of utility functions. 42 | 43 | ### Goals 44 | 45 | - Be usable by wasi-libc to implement POSIX-like file and socket APIs. 46 | - Support many different kinds of host streams, including files, sockets, 47 | pipes, character devices, and more. 48 | 49 | ### Non-goals 50 | 51 | - Support for async. That will be addressed in the component-model async 52 | design, where we can have the benefit of tighter integration with language 53 | bindings. 54 | - Bidirectional streams. 55 | 56 | ### API walk-through 57 | 58 | #### Use Case: copying from input to output using `read`/`write` 59 | 60 | ```rust 61 | fn copy_data(input: InputStream, output: OutputStream) -> Result<(), StreamError> { 62 | const BUFFER_LEN: usize = 4096; 63 | 64 | let wait_input = [subscribe_to_input_stream(input)]; 65 | let wait_output = [subscribe_to_output_stream(output)]; 66 | 67 | loop { 68 | let (mut data, mut eos) = input.read(BUFFER_LEN)?; 69 | 70 | // If we didn't get any data promptly, wait for it. 71 | if data.len() == 0 { 72 | let _ = poll_list(&wait_input[..]); 73 | (data, eos) = input.read(BUFFER_LEN)?; 74 | } 75 | 76 | let mut remaining = &data[..]; 77 | while !remaining.is_empty() { 78 | let mut num_written = output.write(remaining)?; 79 | 80 | // If we didn't put any data promptly, wait for it. 81 | if num_written == 0 { 82 | let _ = poll_list(&wait_output[..]); 83 | num_written = output.write(remaining)?; 84 | } 85 | 86 | remaining = &remaining[num_written..]; 87 | } 88 | if eos { 89 | break; 90 | } 91 | } 92 | Ok(()) 93 | } 94 | ``` 95 | 96 | #### Use case: copying from input to output using `splice` 97 | 98 | ```rust 99 | fn copy_data(input: InputStream, output: OutputStream) -> Result<(), StreamError> { 100 | let wait_input = [subscribe_to_input_stream(input)]; 101 | 102 | loop { 103 | let (num_copied, eos) = output.splice(input, u64::MAX)?; 104 | if eos { 105 | break; 106 | } 107 | 108 | // If we didn't get any data promptly, wait for it. 109 | if num_copied == 0 { 110 | let _ = poll_list(&wait_input[..]); 111 | } 112 | } 113 | Ok(()) 114 | } 115 | ``` 116 | 117 | #### Use case: copying from input to output using `forward` 118 | 119 | ```rust 120 | fn copy_data(input: InputStream, output: OutputStream) -> Result<(), StreamError> { 121 | output.forward(input)?; 122 | Ok(()) 123 | } 124 | ``` 125 | 126 | ### Detailed design discussion 127 | 128 | #### Should we have support for non-blocking read/write? 129 | 130 | This may be something we'll need to revisit, but currently, the way 131 | non-blocking streams work is that they perform reads or writes that 132 | read or write fewer bytes than requested. 133 | 134 | #### Why do read/write use u64 sizes? 135 | 136 | This is to make the API independent of the address space size of 137 | the caller. Callees are still advised to avoid using sizes that 138 | are larger than their instances will be able to allocate. 139 | 140 | #### Why have a `forward` function when you can just `splice` in a loop? 141 | 142 | This seems like it'll be a common use case, and `forward` 143 | addresses it in a very simple way. 144 | 145 | ### Stakeholder Interest & Feedback 146 | 147 | Wasi-io is a dependency of wasi-filesystem, wasi-sockets, and wasi-http, and 148 | is a foundational piece of WASI Preview 2. 149 | 150 | ### References & acknowledgements 151 | 152 | Many thanks for valuable feedback and advice from: 153 | 154 | - Thanks to Luke Wagner for many design functions and the design of 155 | the component-model async streams which inform the design here. 156 | - Thanks to Calvin Prewitt for the idea to include a `forward` function 157 | in this API. 158 | -------------------------------------------------------------------------------- /imports.md: -------------------------------------------------------------------------------- 1 |

World imports

2 | 11 |

Import interface wasi:io/error@0.2.5

12 |
13 |

Types

14 |

resource error

15 |

A resource which represents some error information.

16 |

The only method provided by this resource is to-debug-string, 17 | which provides some human-readable information about the error.

18 |

In the wasi:io package, this resource is returned through the 19 | wasi:io/streams/stream-error type.

20 |

To provide more specific error information, other interfaces may 21 | offer functions to "downcast" this error into more specific types. For example, 22 | errors returned from streams derived from filesystem types can be described using 23 | the filesystem's own error-code type. This is done using the function 24 | wasi:filesystem/types/filesystem-error-code, which takes a borrow<error> 25 | parameter and returns an option<wasi:filesystem/types/error-code>.

26 |

The set of functions which can "downcast" an error into a more 27 | concrete type is open.

28 |

Functions

29 |

[method]error.to-debug-string: func

30 |

Returns a string that is suitable to assist humans in debugging 31 | this error.

32 |

WARNING: The returned string should not be consumed mechanically! 33 | It may change across platforms, hosts, or other implementation 34 | details. Parsing this string is a major platform-compatibility 35 | hazard.

36 |
Params
37 | 40 |
Return values
41 | 44 |

Import interface wasi:io/poll@0.2.5

45 |

A poll API intended to let users wait for I/O events on multiple handles 46 | at once.

47 |
48 |

Types

49 |

resource pollable

50 |

pollable represents a single I/O event which may be ready, or not.

51 |

Functions

52 |

[method]pollable.ready: func

53 |

Return the readiness of a pollable. This function never blocks.

54 |

Returns true when the pollable is ready, and false otherwise.

55 |
Params
56 | 59 |
Return values
60 | 63 |

[method]pollable.block: func

64 |

block returns immediately if the pollable is ready, and otherwise 65 | blocks until ready.

66 |

This function is equivalent to calling poll.poll on a list 67 | containing only this pollable.

68 |
Params
69 | 72 |

poll: func

73 |

Poll for completion on a set of pollables.

74 |

This function takes a list of pollables, which identify I/O sources of 75 | interest, and waits until one or more of the events is ready for I/O.

76 |

The result list<u32> contains one or more indices of handles in the 77 | argument list that is ready for I/O.

78 |

This function traps if either:

79 | 83 |

A timeout can be implemented by adding a pollable from the 84 | wasi-clocks API to the list.

85 |

This function does not return a result; polling in itself does not 86 | do any I/O so it doesn't fail. If any of the I/O sources identified by 87 | the pollables has an error, it is indicated by marking the source as 88 | being ready for I/O.

89 |
Params
90 | 93 |
Return values
94 | 97 |

Import interface wasi:io/streams@0.2.5

98 |

WASI I/O is an I/O abstraction API which is currently focused on providing 99 | stream types.

100 |

In the future, the component model is expected to add built-in stream types; 101 | when it does, they are expected to subsume this API.

102 |
103 |

Types

104 |

type error

105 |

error

106 |

107 | #### `type pollable` 108 | [`pollable`](#pollable) 109 |

110 | #### `variant stream-error` 111 |

An error for input-stream and output-stream operations.

112 |
Variant Cases
113 | 128 |

resource input-stream

129 |

An input bytestream.

130 |

input-streams are non-blocking to the extent practical on underlying 131 | platforms. I/O operations always return promptly; if fewer bytes are 132 | promptly available than requested, they return the number of bytes promptly 133 | available, which could even be zero. To wait for data to be available, 134 | use the subscribe function to obtain a pollable which can be polled 135 | for using wasi:io/poll.

136 |

resource output-stream

137 |

An output bytestream.

138 |

output-streams are non-blocking to the extent practical on 139 | underlying platforms. Except where specified otherwise, I/O operations also 140 | always return promptly, after the number of bytes that can be written 141 | promptly, which could even be zero. To wait for the stream to be ready to 142 | accept data, the subscribe function to obtain a pollable which can be 143 | polled for using wasi:io/poll.

144 |

Dropping an output-stream while there's still an active write in 145 | progress may result in the data being lost. Before dropping the stream, 146 | be sure to fully flush your writes.

147 |

Functions

148 |

[method]input-stream.read: func

149 |

Perform a non-blocking read from the stream.

150 |

When the source of a read is binary data, the bytes from the source 151 | are returned verbatim. When the source of a read is known to the 152 | implementation to be text, bytes containing the UTF-8 encoding of the 153 | text are returned.

154 |

This function returns a list of bytes containing the read data, 155 | when successful. The returned list will contain up to len bytes; 156 | it may return fewer than requested, but not more. The list is 157 | empty when no bytes are available for reading at this time. The 158 | pollable given by subscribe will be ready when more bytes are 159 | available.

160 |

This function fails with a stream-error when the operation 161 | encounters an error, giving last-operation-failed, or when the 162 | stream is closed, giving closed.

163 |

When the caller gives a len of 0, it represents a request to 164 | read 0 bytes. If the stream is still open, this call should 165 | succeed and return an empty list, or otherwise fail with closed.

166 |

The len parameter is a u64, which could represent a list of u8 which 167 | is not possible to allocate in wasm32, or not desirable to allocate as 168 | as a return value by the callee. The callee may return a list of bytes 169 | less than len in size while more bytes are available for reading.

170 |
Params
171 | 175 |
Return values
176 | 179 |

[method]input-stream.blocking-read: func

180 |

Read bytes from a stream, after blocking until at least one byte can 181 | be read. Except for blocking, behavior is identical to read.

182 |
Params
183 | 187 |
Return values
188 | 191 |

[method]input-stream.skip: func

192 |

Skip bytes from a stream. Returns number of bytes skipped.

193 |

Behaves identical to read, except instead of returning a list 194 | of bytes, returns the number of bytes consumed from the stream.

195 |
Params
196 | 200 |
Return values
201 | 204 |

[method]input-stream.blocking-skip: func

205 |

Skip bytes from a stream, after blocking until at least one byte 206 | can be skipped. Except for blocking behavior, identical to skip.

207 |
Params
208 | 212 |
Return values
213 | 216 |

[method]input-stream.subscribe: func

217 |

Create a pollable which will resolve once either the specified stream 218 | has bytes available to read or the other end of the stream has been 219 | closed. 220 | The created pollable is a child resource of the input-stream. 221 | Implementations may trap if the input-stream is dropped before 222 | all derived pollables created with this function are dropped.

223 |
Params
224 | 227 |
Return values
228 | 231 |

[method]output-stream.check-write: func

232 |

Check readiness for writing. This function never blocks.

233 |

Returns the number of bytes permitted for the next call to write, 234 | or an error. Calling write with more bytes than this function has 235 | permitted will trap.

236 |

When this function returns 0 bytes, the subscribe pollable will 237 | become ready when this function will report at least 1 byte, or an 238 | error.

239 |
Params
240 | 243 |
Return values
244 | 247 |

[method]output-stream.write: func

248 |

Perform a write. This function never blocks.

249 |

When the destination of a write is binary data, the bytes from 250 | contents are written verbatim. When the destination of a write is 251 | known to the implementation to be text, the bytes of contents are 252 | transcoded from UTF-8 into the encoding of the destination and then 253 | written.

254 |

Precondition: check-write gave permit of Ok(n) and contents has a 255 | length of less than or equal to n. Otherwise, this function will trap.

256 |

returns Err(closed) without writing if the stream has closed since 257 | the last call to check-write provided a permit.

258 |
Params
259 | 263 |
Return values
264 | 267 |

[method]output-stream.blocking-write-and-flush: func

268 |

Perform a write of up to 4096 bytes, and then flush the stream. Block 269 | until all of these operations are complete, or an error occurs.

270 |

This is a convenience wrapper around the use of check-write, 271 | subscribe, write, and flush, and is implemented with the 272 | following pseudo-code:

273 |
let pollable = this.subscribe();
274 | while !contents.is_empty() {
275 |   // Wait for the stream to become writable
276 |   pollable.block();
277 |   let Ok(n) = this.check-write(); // eliding error handling
278 |   let len = min(n, contents.len());
279 |   let (chunk, rest) = contents.split_at(len);
280 |   this.write(chunk  );            // eliding error handling
281 |   contents = rest;
282 | }
283 | this.flush();
284 | // Wait for completion of `flush`
285 | pollable.block();
286 | // Check for any errors that arose during `flush`
287 | let _ = this.check-write();         // eliding error handling
288 | 
289 |
Params
290 | 294 |
Return values
295 | 298 |

[method]output-stream.flush: func

299 |

Request to flush buffered output. This function never blocks.

300 |

This tells the output-stream that the caller intends any buffered 301 | output to be flushed. the output which is expected to be flushed 302 | is all that has been passed to write prior to this call.

303 |

Upon calling this function, the output-stream will not accept any 304 | writes (check-write will return ok(0)) until the flush has 305 | completed. The subscribe pollable will become ready when the 306 | flush has completed and the stream can accept more writes.

307 |
Params
308 | 311 |
Return values
312 | 315 |

[method]output-stream.blocking-flush: func

316 |

Request to flush buffered output, and block until flush completes 317 | and stream is ready for writing again.

318 |
Params
319 | 322 |
Return values
323 | 326 |

[method]output-stream.subscribe: func

327 |

Create a pollable which will resolve once the output-stream 328 | is ready for more writing, or an error has occurred. When this 329 | pollable is ready, check-write will return ok(n) with n>0, or an 330 | error.

331 |

If the stream is closed, this pollable is always ready immediately.

332 |

The created pollable is a child resource of the output-stream. 333 | Implementations may trap if the output-stream is dropped before 334 | all derived pollables created with this function are dropped.

335 |
Params
336 | 339 |
Return values
340 | 343 |

[method]output-stream.write-zeroes: func

344 |

Write zeroes to a stream.

345 |

This should be used precisely like write with the exact same 346 | preconditions (must use check-write first), but instead of 347 | passing a list of bytes, you simply pass the number of zero-bytes 348 | that should be written.

349 |
Params
350 | 354 |
Return values
355 | 358 |

[method]output-stream.blocking-write-zeroes-and-flush: func

359 |

Perform a write of up to 4096 zeroes, and then flush the stream. 360 | Block until all of these operations are complete, or an error 361 | occurs.

362 |

This is a convenience wrapper around the use of check-write, 363 | subscribe, write-zeroes, and flush, and is implemented with 364 | the following pseudo-code:

365 |
let pollable = this.subscribe();
366 | while num_zeroes != 0 {
367 |   // Wait for the stream to become writable
368 |   pollable.block();
369 |   let Ok(n) = this.check-write(); // eliding error handling
370 |   let len = min(n, num_zeroes);
371 |   this.write-zeroes(len);         // eliding error handling
372 |   num_zeroes -= len;
373 | }
374 | this.flush();
375 | // Wait for completion of `flush`
376 | pollable.block();
377 | // Check for any errors that arose during `flush`
378 | let _ = this.check-write();         // eliding error handling
379 | 
380 |
Params
381 | 385 |
Return values
386 | 389 |

[method]output-stream.splice: func

390 |

Read from one stream and write to another.

391 |

The behavior of splice is equivalent to:

392 |
    393 |
  1. calling check-write on the output-stream
  2. 394 |
  3. calling read on the input-stream with the smaller of the 395 | check-write permitted length and the len provided to splice
  4. 396 |
  5. calling write on the output-stream with that read data.
  6. 397 |
398 |

Any error reported by the call to check-write, read, or 399 | write ends the splice and reports that error.

400 |

This function returns the number of bytes transferred; it may be less 401 | than len.

402 |
Params
403 | 408 |
Return values
409 | 412 |

[method]output-stream.blocking-splice: func

413 |

Read from one stream and write to another, with blocking.

414 |

This is similar to splice, except that it blocks until the 415 | output-stream is ready for writing, and the input-stream 416 | is ready for reading, before performing the splice.

417 |
Params
418 | 423 |
Return values
424 | 427 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Testing guidelines 2 | 3 | TK fill in testing guidelines 4 | 5 | ## Installing the tools 6 | 7 | TK fill in instructions 8 | 9 | ## Running the tests 10 | 11 | TK fill in instructions 12 | -------------------------------------------------------------------------------- /wit/error.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.5; 2 | 3 | @since(version = 0.2.0) 4 | interface error { 5 | /// A resource which represents some error information. 6 | /// 7 | /// The only method provided by this resource is `to-debug-string`, 8 | /// which provides some human-readable information about the error. 9 | /// 10 | /// In the `wasi:io` package, this resource is returned through the 11 | /// `wasi:io/streams/stream-error` type. 12 | /// 13 | /// To provide more specific error information, other interfaces may 14 | /// offer functions to "downcast" this error into more specific types. For example, 15 | /// errors returned from streams derived from filesystem types can be described using 16 | /// the filesystem's own error-code type. This is done using the function 17 | /// `wasi:filesystem/types/filesystem-error-code`, which takes a `borrow` 18 | /// parameter and returns an `option`. 19 | /// 20 | /// The set of functions which can "downcast" an `error` into a more 21 | /// concrete type is open. 22 | @since(version = 0.2.0) 23 | resource error { 24 | /// Returns a string that is suitable to assist humans in debugging 25 | /// this error. 26 | /// 27 | /// WARNING: The returned string should not be consumed mechanically! 28 | /// It may change across platforms, hosts, or other implementation 29 | /// details. Parsing this string is a major platform-compatibility 30 | /// hazard. 31 | @since(version = 0.2.0) 32 | to-debug-string: func() -> string; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /wit/poll.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.5; 2 | 3 | /// A poll API intended to let users wait for I/O events on multiple handles 4 | /// at once. 5 | @since(version = 0.2.0) 6 | interface poll { 7 | /// `pollable` represents a single I/O event which may be ready, or not. 8 | @since(version = 0.2.0) 9 | resource pollable { 10 | 11 | /// Return the readiness of a pollable. This function never blocks. 12 | /// 13 | /// Returns `true` when the pollable is ready, and `false` otherwise. 14 | @since(version = 0.2.0) 15 | ready: func() -> bool; 16 | 17 | /// `block` returns immediately if the pollable is ready, and otherwise 18 | /// blocks until ready. 19 | /// 20 | /// This function is equivalent to calling `poll.poll` on a list 21 | /// containing only this pollable. 22 | @since(version = 0.2.0) 23 | block: func(); 24 | } 25 | 26 | /// Poll for completion on a set of pollables. 27 | /// 28 | /// This function takes a list of pollables, which identify I/O sources of 29 | /// interest, and waits until one or more of the events is ready for I/O. 30 | /// 31 | /// The result `list` contains one or more indices of handles in the 32 | /// argument list that is ready for I/O. 33 | /// 34 | /// This function traps if either: 35 | /// - the list is empty, or: 36 | /// - the list contains more elements than can be indexed with a `u32` value. 37 | /// 38 | /// A timeout can be implemented by adding a pollable from the 39 | /// wasi-clocks API to the list. 40 | /// 41 | /// This function does not return a `result`; polling in itself does not 42 | /// do any I/O so it doesn't fail. If any of the I/O sources identified by 43 | /// the pollables has an error, it is indicated by marking the source as 44 | /// being ready for I/O. 45 | @since(version = 0.2.0) 46 | poll: func(in: list>) -> list; 47 | } 48 | -------------------------------------------------------------------------------- /wit/streams.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.5; 2 | 3 | /// WASI I/O is an I/O abstraction API which is currently focused on providing 4 | /// stream types. 5 | /// 6 | /// In the future, the component model is expected to add built-in stream types; 7 | /// when it does, they are expected to subsume this API. 8 | @since(version = 0.2.0) 9 | interface streams { 10 | @since(version = 0.2.0) 11 | use error.{error}; 12 | @since(version = 0.2.0) 13 | use poll.{pollable}; 14 | 15 | /// An error for input-stream and output-stream operations. 16 | @since(version = 0.2.0) 17 | variant stream-error { 18 | /// The last operation (a write or flush) failed before completion. 19 | /// 20 | /// More information is available in the `error` payload. 21 | /// 22 | /// After this, the stream will be closed. All future operations return 23 | /// `stream-error::closed`. 24 | last-operation-failed(error), 25 | /// The stream is closed: no more input will be accepted by the 26 | /// stream. A closed output-stream will return this error on all 27 | /// future operations. 28 | closed 29 | } 30 | 31 | /// An input bytestream. 32 | /// 33 | /// `input-stream`s are *non-blocking* to the extent practical on underlying 34 | /// platforms. I/O operations always return promptly; if fewer bytes are 35 | /// promptly available than requested, they return the number of bytes promptly 36 | /// available, which could even be zero. To wait for data to be available, 37 | /// use the `subscribe` function to obtain a `pollable` which can be polled 38 | /// for using `wasi:io/poll`. 39 | @since(version = 0.2.0) 40 | resource input-stream { 41 | /// Perform a non-blocking read from the stream. 42 | /// 43 | /// When the source of a `read` is binary data, the bytes from the source 44 | /// are returned verbatim. When the source of a `read` is known to the 45 | /// implementation to be text, bytes containing the UTF-8 encoding of the 46 | /// text are returned. 47 | /// 48 | /// This function returns a list of bytes containing the read data, 49 | /// when successful. The returned list will contain up to `len` bytes; 50 | /// it may return fewer than requested, but not more. The list is 51 | /// empty when no bytes are available for reading at this time. The 52 | /// pollable given by `subscribe` will be ready when more bytes are 53 | /// available. 54 | /// 55 | /// This function fails with a `stream-error` when the operation 56 | /// encounters an error, giving `last-operation-failed`, or when the 57 | /// stream is closed, giving `closed`. 58 | /// 59 | /// When the caller gives a `len` of 0, it represents a request to 60 | /// read 0 bytes. If the stream is still open, this call should 61 | /// succeed and return an empty list, or otherwise fail with `closed`. 62 | /// 63 | /// The `len` parameter is a `u64`, which could represent a list of u8 which 64 | /// is not possible to allocate in wasm32, or not desirable to allocate as 65 | /// as a return value by the callee. The callee may return a list of bytes 66 | /// less than `len` in size while more bytes are available for reading. 67 | @since(version = 0.2.0) 68 | read: func( 69 | /// The maximum number of bytes to read 70 | len: u64 71 | ) -> result, stream-error>; 72 | 73 | /// Read bytes from a stream, after blocking until at least one byte can 74 | /// be read. Except for blocking, behavior is identical to `read`. 75 | @since(version = 0.2.0) 76 | blocking-read: func( 77 | /// The maximum number of bytes to read 78 | len: u64 79 | ) -> result, stream-error>; 80 | 81 | /// Skip bytes from a stream. Returns number of bytes skipped. 82 | /// 83 | /// Behaves identical to `read`, except instead of returning a list 84 | /// of bytes, returns the number of bytes consumed from the stream. 85 | @since(version = 0.2.0) 86 | skip: func( 87 | /// The maximum number of bytes to skip. 88 | len: u64, 89 | ) -> result; 90 | 91 | /// Skip bytes from a stream, after blocking until at least one byte 92 | /// can be skipped. Except for blocking behavior, identical to `skip`. 93 | @since(version = 0.2.0) 94 | blocking-skip: func( 95 | /// The maximum number of bytes to skip. 96 | len: u64, 97 | ) -> result; 98 | 99 | /// Create a `pollable` which will resolve once either the specified stream 100 | /// has bytes available to read or the other end of the stream has been 101 | /// closed. 102 | /// The created `pollable` is a child resource of the `input-stream`. 103 | /// Implementations may trap if the `input-stream` is dropped before 104 | /// all derived `pollable`s created with this function are dropped. 105 | @since(version = 0.2.0) 106 | subscribe: func() -> pollable; 107 | } 108 | 109 | 110 | /// An output bytestream. 111 | /// 112 | /// `output-stream`s are *non-blocking* to the extent practical on 113 | /// underlying platforms. Except where specified otherwise, I/O operations also 114 | /// always return promptly, after the number of bytes that can be written 115 | /// promptly, which could even be zero. To wait for the stream to be ready to 116 | /// accept data, the `subscribe` function to obtain a `pollable` which can be 117 | /// polled for using `wasi:io/poll`. 118 | /// 119 | /// Dropping an `output-stream` while there's still an active write in 120 | /// progress may result in the data being lost. Before dropping the stream, 121 | /// be sure to fully flush your writes. 122 | @since(version = 0.2.0) 123 | resource output-stream { 124 | /// Check readiness for writing. This function never blocks. 125 | /// 126 | /// Returns the number of bytes permitted for the next call to `write`, 127 | /// or an error. Calling `write` with more bytes than this function has 128 | /// permitted will trap. 129 | /// 130 | /// When this function returns 0 bytes, the `subscribe` pollable will 131 | /// become ready when this function will report at least 1 byte, or an 132 | /// error. 133 | @since(version = 0.2.0) 134 | check-write: func() -> result; 135 | 136 | /// Perform a write. This function never blocks. 137 | /// 138 | /// When the destination of a `write` is binary data, the bytes from 139 | /// `contents` are written verbatim. When the destination of a `write` is 140 | /// known to the implementation to be text, the bytes of `contents` are 141 | /// transcoded from UTF-8 into the encoding of the destination and then 142 | /// written. 143 | /// 144 | /// Precondition: check-write gave permit of Ok(n) and contents has a 145 | /// length of less than or equal to n. Otherwise, this function will trap. 146 | /// 147 | /// returns Err(closed) without writing if the stream has closed since 148 | /// the last call to check-write provided a permit. 149 | @since(version = 0.2.0) 150 | write: func( 151 | contents: list 152 | ) -> result<_, stream-error>; 153 | 154 | /// Perform a write of up to 4096 bytes, and then flush the stream. Block 155 | /// until all of these operations are complete, or an error occurs. 156 | /// 157 | /// This is a convenience wrapper around the use of `check-write`, 158 | /// `subscribe`, `write`, and `flush`, and is implemented with the 159 | /// following pseudo-code: 160 | /// 161 | /// ```text 162 | /// let pollable = this.subscribe(); 163 | /// while !contents.is_empty() { 164 | /// // Wait for the stream to become writable 165 | /// pollable.block(); 166 | /// let Ok(n) = this.check-write(); // eliding error handling 167 | /// let len = min(n, contents.len()); 168 | /// let (chunk, rest) = contents.split_at(len); 169 | /// this.write(chunk ); // eliding error handling 170 | /// contents = rest; 171 | /// } 172 | /// this.flush(); 173 | /// // Wait for completion of `flush` 174 | /// pollable.block(); 175 | /// // Check for any errors that arose during `flush` 176 | /// let _ = this.check-write(); // eliding error handling 177 | /// ``` 178 | @since(version = 0.2.0) 179 | blocking-write-and-flush: func( 180 | contents: list 181 | ) -> result<_, stream-error>; 182 | 183 | /// Request to flush buffered output. This function never blocks. 184 | /// 185 | /// This tells the output-stream that the caller intends any buffered 186 | /// output to be flushed. the output which is expected to be flushed 187 | /// is all that has been passed to `write` prior to this call. 188 | /// 189 | /// Upon calling this function, the `output-stream` will not accept any 190 | /// writes (`check-write` will return `ok(0)`) until the flush has 191 | /// completed. The `subscribe` pollable will become ready when the 192 | /// flush has completed and the stream can accept more writes. 193 | @since(version = 0.2.0) 194 | flush: func() -> result<_, stream-error>; 195 | 196 | /// Request to flush buffered output, and block until flush completes 197 | /// and stream is ready for writing again. 198 | @since(version = 0.2.0) 199 | blocking-flush: func() -> result<_, stream-error>; 200 | 201 | /// Create a `pollable` which will resolve once the output-stream 202 | /// is ready for more writing, or an error has occurred. When this 203 | /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an 204 | /// error. 205 | /// 206 | /// If the stream is closed, this pollable is always ready immediately. 207 | /// 208 | /// The created `pollable` is a child resource of the `output-stream`. 209 | /// Implementations may trap if the `output-stream` is dropped before 210 | /// all derived `pollable`s created with this function are dropped. 211 | @since(version = 0.2.0) 212 | subscribe: func() -> pollable; 213 | 214 | /// Write zeroes to a stream. 215 | /// 216 | /// This should be used precisely like `write` with the exact same 217 | /// preconditions (must use check-write first), but instead of 218 | /// passing a list of bytes, you simply pass the number of zero-bytes 219 | /// that should be written. 220 | @since(version = 0.2.0) 221 | write-zeroes: func( 222 | /// The number of zero-bytes to write 223 | len: u64 224 | ) -> result<_, stream-error>; 225 | 226 | /// Perform a write of up to 4096 zeroes, and then flush the stream. 227 | /// Block until all of these operations are complete, or an error 228 | /// occurs. 229 | /// 230 | /// This is a convenience wrapper around the use of `check-write`, 231 | /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with 232 | /// the following pseudo-code: 233 | /// 234 | /// ```text 235 | /// let pollable = this.subscribe(); 236 | /// while num_zeroes != 0 { 237 | /// // Wait for the stream to become writable 238 | /// pollable.block(); 239 | /// let Ok(n) = this.check-write(); // eliding error handling 240 | /// let len = min(n, num_zeroes); 241 | /// this.write-zeroes(len); // eliding error handling 242 | /// num_zeroes -= len; 243 | /// } 244 | /// this.flush(); 245 | /// // Wait for completion of `flush` 246 | /// pollable.block(); 247 | /// // Check for any errors that arose during `flush` 248 | /// let _ = this.check-write(); // eliding error handling 249 | /// ``` 250 | @since(version = 0.2.0) 251 | blocking-write-zeroes-and-flush: func( 252 | /// The number of zero-bytes to write 253 | len: u64 254 | ) -> result<_, stream-error>; 255 | 256 | /// Read from one stream and write to another. 257 | /// 258 | /// The behavior of splice is equivalent to: 259 | /// 1. calling `check-write` on the `output-stream` 260 | /// 2. calling `read` on the `input-stream` with the smaller of the 261 | /// `check-write` permitted length and the `len` provided to `splice` 262 | /// 3. calling `write` on the `output-stream` with that read data. 263 | /// 264 | /// Any error reported by the call to `check-write`, `read`, or 265 | /// `write` ends the splice and reports that error. 266 | /// 267 | /// This function returns the number of bytes transferred; it may be less 268 | /// than `len`. 269 | @since(version = 0.2.0) 270 | splice: func( 271 | /// The stream to read from 272 | src: borrow, 273 | /// The number of bytes to splice 274 | len: u64, 275 | ) -> result; 276 | 277 | /// Read from one stream and write to another, with blocking. 278 | /// 279 | /// This is similar to `splice`, except that it blocks until the 280 | /// `output-stream` is ready for writing, and the `input-stream` 281 | /// is ready for reading, before performing the `splice`. 282 | @since(version = 0.2.0) 283 | blocking-splice: func( 284 | /// The stream to read from 285 | src: borrow, 286 | /// The number of bytes to splice 287 | len: u64, 288 | ) -> result; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /wit/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.5; 2 | 3 | @since(version = 0.2.0) 4 | world imports { 5 | @since(version = 0.2.0) 6 | import streams; 7 | 8 | @since(version = 0.2.0) 9 | import poll; 10 | } 11 | --------------------------------------------------------------------------------