-
Notifications
You must be signed in to change notification settings - Fork 229
P3 stdout implementation #718
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,11 @@ | |
| #include <sys/stat.h> | ||
| #include <netinet/in.h> | ||
|
|
||
| #ifdef __wasip3__ | ||
| // create an alias to distinguish the handle type in the API | ||
| typedef uint32_t waitable_t; | ||
| #endif | ||
|
|
||
| /** | ||
| * Operations that are required of all descriptors registered as file | ||
| * descriptors. | ||
|
|
@@ -37,6 +42,12 @@ typedef struct descriptor_vtable_t { | |
| /// Same as `get_read_stream`, but for output streams. | ||
| int (*get_write_stream)(void*, streams_borrow_output_stream_t*, off_t**, poll_own_pollable_t**); | ||
| #endif | ||
| #ifdef __wasip3__ | ||
| /// Start an asynchronous read or write, returns zero on success. | ||
| /// Stores the waitable, status and offset location. | ||
| int (*read3)(void*, void *buf, size_t nbyte, waitable_t *waitable, wasip3_waitable_status_t *out, off_t**); | ||
| int (*write3)(void*, void const *buf, size_t nbyte, waitable_t *waitable, wasip3_waitable_status_t *out, off_t**); | ||
|
Comment on lines
+46
to
+49
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be possible to make this look more like
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously I just returned a stream_u8_t, then I found that each subsystem has its own name for stream_write (filesystem_, sockets_, stdin_, ...) and moved the write call into the descriptor specific code as well. I don't think it makes a difference, but it just feels wrong to call filesystem_stream_u8_read on a tcp socket. And as this interface works for read, write and poll I thought that it might be the right level of abstraction.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah ok that makes sense. Given that those are wit-bindgen-isms I think it'd be reasonable to have something like |
||
| #endif | ||
|
|
||
| /// Sets the nonblocking flag for this object to the specified value. | ||
| int (*set_blocking)(void*, bool); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| #include <assert.h> | ||
| #include <common/errors.h> | ||
| #include <errno.h> | ||
| #include <stddef.h> | ||
| #include <stdlib.h> | ||
| #include <wasi/file_utils.h> | ||
|
|
||
| #ifdef __wasip3__ | ||
| ssize_t __wasilibc_write3(int fildes, void const *buf, size_t nbyte) { | ||
| descriptor_table_entry_t *entry = descriptor_table_get_ref(fildes); | ||
| if (!entry) | ||
| return -1; | ||
| if (!entry->vtable->write3) { | ||
| errno = EOPNOTSUPP; | ||
| return -1; | ||
| } | ||
| off_t *off; | ||
| waitable_t output_stream; | ||
| wasip3_waitable_status_t status; | ||
| if ((*entry->vtable->write3)(entry->data, buf, nbyte, &output_stream, &status, | ||
| &off) < 0) | ||
| return -1; | ||
| if (status == WASIP3_WAITABLE_STATUS_BLOCKED) { | ||
| wasip3_waitable_set_t set = wasip3_waitable_set_new(); | ||
| wasip3_waitable_join(output_stream, set); | ||
| wasip3_event_t event; | ||
| wasip3_waitable_set_wait(set, &event); | ||
| assert(event.event == WASIP3_EVENT_STREAM_WRITE); | ||
| assert(event.waitable == output_stream); | ||
| // remove from set | ||
| wasip3_waitable_join(output_stream, 0); | ||
| wasip3_waitable_set_drop(set); | ||
| ssize_t bytes_written = event.code; | ||
| if (off) | ||
| *off += bytes_written; | ||
| return bytes_written; | ||
| } else if (WASIP3_WAITABLE_STATE(status) == WASIP3_WAITABLE_COMPLETED) { | ||
| ssize_t bytes_written = WASIP3_WAITABLE_COUNT(status); | ||
| if (off) | ||
| *off += bytes_written; | ||
| return bytes_written; | ||
| } else { | ||
| abort(); | ||
| } | ||
| } | ||
|
|
||
| ssize_t __wasilibc_read3(int fildes, void *buf, size_t nbyte) { | ||
| descriptor_table_entry_t *entry = descriptor_table_get_ref(fildes); | ||
| if (!entry) | ||
| return -1; | ||
| if (!entry->vtable->read3) { | ||
| errno = EOPNOTSUPP; | ||
| return -1; | ||
| } | ||
| off_t *off; | ||
| waitable_t waitable; | ||
| wasip3_waitable_status_t status; | ||
| if ((*entry->vtable->read3)(entry->data, buf, nbyte, &waitable, &status, | ||
| &off) < 0) | ||
| return -1; | ||
| if (status == WASIP3_WAITABLE_STATUS_BLOCKED) { | ||
| wasip3_waitable_set_t set = wasip3_waitable_set_new(); | ||
| wasip3_waitable_join(waitable, set); | ||
| wasip3_event_t event; | ||
| wasip3_waitable_set_wait(set, &event); | ||
| assert(event.event == WASIP3_EVENT_STREAM_READ); | ||
| assert(event.waitable == waitable); | ||
| // remove from set | ||
| wasip3_waitable_join(waitable, 0); | ||
| wasip3_waitable_set_drop(set); | ||
| ssize_t bytes_read = event.code; | ||
| if (off) | ||
| *off += bytes_read; | ||
| return bytes_read; | ||
| } else if (WASIP3_WAITABLE_STATE(status) == WASIP3_WAITABLE_COMPLETED) { | ||
| ssize_t bytes_read = WASIP3_WAITABLE_COUNT(status); | ||
| if (off) | ||
| *off += bytes_read; | ||
| return bytes_read; | ||
| } else if (WASIP3_WAITABLE_STATE(status) == WASIP3_WAITABLE_DROPPED) { | ||
| return 0; | ||
| } else { | ||
| abort(); | ||
| } | ||
| } | ||
| #endif // __wasip3__ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,155 @@ | |
| #include <wasi/version.h> | ||
|
|
||
| #ifdef __wasip3__ | ||
| #include <fcntl.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <wasi/descriptor_table.h> | ||
| #include <wasi/wasip3.h> | ||
|
|
||
| typedef struct { | ||
| stdin_stream_u8_t input; | ||
| stdin_future_result_void_error_code_t future; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps |
||
| terminal_input_own_terminal_input_t terminal_in; | ||
|
|
||
| stdin_stream_u8_writer_t stdout; | ||
| // contents will be filled by host (once stdout has an error) | ||
| stdout_result_void_error_code_t stdout_result; | ||
| wasip3_subtask_t stdout_task; | ||
| terminal_output_own_terminal_output_t terminal_out; | ||
| } stdio3_t; | ||
|
|
||
| static void stdio3_free(void *data) { | ||
| stdio3_t *stdio = (stdio3_t *)data; | ||
| if (stdio->terminal_in.__handle) | ||
| terminal_input_terminal_input_drop_own(stdio->terminal_in); | ||
| if (stdio->future) | ||
| stdin_future_result_void_error_code_drop_readable(stdio->future); | ||
| if (stdio->input) | ||
| stdin_stream_u8_drop_readable(stdio->input); | ||
|
|
||
| if (stdio->terminal_out.__handle) | ||
| terminal_output_terminal_output_drop_own(stdio->terminal_out); | ||
| if (stdio->stdout_task) | ||
| wasip3_subtask_cancel(stdio->stdout_task); | ||
| if (stdio->stdout) | ||
| stdin_stream_u8_drop_writable(stdio->stdout); | ||
| free(stdio); | ||
| } | ||
|
|
||
| static int stdio3_write(void *data, void const *buf, size_t nbyte, | ||
| waitable_t *waitable, wasip3_waitable_status_t *out, | ||
| off_t **offs) { | ||
| stdio3_t *stdio = (stdio3_t *)data; | ||
| if (!stdio->stdout) { | ||
| errno = EBADF; | ||
| return -1; | ||
| } | ||
| *waitable = stdio->stdout; | ||
| *out = stdin_stream_u8_write(stdio->stdout, buf, nbyte); | ||
| *offs = NULL; | ||
| return 0; | ||
| } | ||
|
|
||
| static int stdio3_read(void *data, void *buf, size_t nbyte, | ||
| waitable_t *waitable, wasip3_waitable_status_t *out, | ||
| off_t **offs) { | ||
| stdio3_t *stdio = (stdio3_t *)data; | ||
| if (!stdio->input) { | ||
| errno = EBADF; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For wasip2 this returns
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I just checked with the Linux manual page for write which documented EBADF. EOPNOTSUPP makes sense to me as well. |
||
| return -1; | ||
| } | ||
| *waitable = stdio->input; | ||
| *out = stdin_stream_u8_read(stdio->input, buf, nbyte); | ||
| *offs = NULL; | ||
| return 0; | ||
| } | ||
|
|
||
| static int stdio3_fstat(void *data, struct stat *buf) { | ||
| memset(buf, 0, sizeof(*buf)); | ||
| return 0; | ||
| } | ||
|
|
||
| static int stdio3_fcntl_getfl(void *data) { | ||
| stdio3_t *stdio = (stdio3_t *)data; | ||
| if (stdio->stdout == 0) { | ||
| return O_RDONLY; | ||
| } else { | ||
| return O_WRONLY; | ||
| } | ||
| } | ||
|
|
||
| static int stdio3_isatty(void *data) { | ||
| stdio3_t *stdio = (stdio3_t *)data; | ||
| return stdio->terminal_in.__handle != 0 || stdio->terminal_out.__handle != 0; | ||
| } | ||
|
|
||
| static descriptor_vtable_t stdio3_vtable = { | ||
| .free = stdio3_free, | ||
| .read3 = stdio3_read, | ||
| .write3 = stdio3_write, | ||
| .fstat = stdio3_fstat, | ||
| .fcntl_getfl = stdio3_fcntl_getfl, | ||
| .isatty = stdio3_isatty, | ||
| }; | ||
|
|
||
| static int stdio_add_input() { | ||
| stdio3_t *stdio = calloc(1, sizeof(stdio3_t)); | ||
| if (!stdio) { | ||
| errno = ENOMEM; | ||
| return -1; | ||
| } | ||
| stdin_tuple2_stream_u8_future_result_void_error_code_t stdin; | ||
| stdin_read_via_stream(&stdin); | ||
|
|
||
| if (!terminal_stdin_get_terminal_stdin(&stdio->terminal_in)) | ||
| stdio->terminal_in.__handle = 0; | ||
|
Comment on lines
+103
to
+107
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this perhaps be more of a lazy initialization similar to how wasip2 works? I felt that worked out pretty well and helps reduce runtime dependencies to a bare minimum where possible |
||
|
|
||
| stdio->input = stdin.f0; | ||
| stdio->future = stdin.f1; | ||
|
|
||
| descriptor_table_entry_t entry; | ||
| entry.vtable = &stdio3_vtable; | ||
| entry.data = stdio; | ||
| return descriptor_table_insert(entry); | ||
| } | ||
|
|
||
| static int stdio3_add_output( | ||
| // int fd, | ||
| wasip3_subtask_status_t (*func)(stdin_stream_u8_t data, | ||
| stdout_result_void_error_code_t *result), | ||
| bool (*terminal)(terminal_stdout_own_terminal_output_t *ret)) { | ||
| stdio3_t *stdio = calloc(1, sizeof(stdio3_t)); | ||
| if (!stdio) { | ||
| errno = ENOMEM; | ||
| return -1; | ||
| } | ||
| stdin_stream_u8_t read_side = stdin_stream_u8_new(&stdio->stdout); | ||
| wasip3_subtask_status_t res = (*func)(read_side, &stdio->stdout_result); | ||
| stdio->stdout_task = WASIP3_SUBTASK_HANDLE(res); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This'll want to handle the case that
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The next write will pick up the error anyway, do you think the open should fail immediately? Wouldn't init returning -1 render the file descriptor infrastructure broken (at least the first open will fail in
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that returning -1 shouldn't happen, but handling it in this case (IMO) should effectively move stdout into a state of "it's closed, don't try again". I'd imagine that this would trigger writes to start returning EPIPE (or maybe 0) or something like that, but basically the case that the function returns immediately should be used to update the internal structure vs returning an error |
||
|
|
||
| if (!(*terminal)(&stdio->terminal_out)) | ||
| stdio->terminal_out.__handle = 0; | ||
|
|
||
| descriptor_table_entry_t entry; | ||
| entry.vtable = &stdio3_vtable; | ||
| entry.data = stdio; | ||
| return descriptor_table_insert(entry); | ||
| } | ||
|
|
||
| int __wasilibc_init_stdio() { | ||
| // TODO(wasip3) | ||
| errno = EOPNOTSUPP; | ||
| return -1; | ||
| if (stdio_add_input() < 0) | ||
| return -1; | ||
| if (stdio3_add_output(stdout_write_via_stream, | ||
| terminal_stdout_get_terminal_stdout) < 0) | ||
| return -1; | ||
| // assuming that stdout and stderr functions are compatible | ||
| if (stdio3_add_output( | ||
| (wasip3_subtask_status_t (*)( | ||
| stdin_stream_u8_t, | ||
| stdout_result_void_error_code_t *))stderr_write_via_stream, | ||
| terminal_stderr_get_terminal_stderr) < 0) | ||
| return -1; | ||
| return 0; | ||
| } | ||
| #endif // __wasip3__ | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently the general style in wasi-libc is to have the implementation of this function inlined here (since I think this will forever be the only caller, right?)
Some helpers I could see being reasonable to put elsewhere, but for the main body of the implementation that seems like it'll be
read-specific so I think it's reasonable to inline here (and inwritebelow)Although I'll be honest in terms of style I'm just winging it I don't have a strong rhyme or reason one way or the other. Keeping things as "one symbol per file and as few deps on other files as possible" seems to be the general ethos.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was hoping to factorize out common parts between read and write and having both in a single file (file_util) helps with that, previously I had considerable code inlined here and in write.c.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah if there's a large shared body of code between read/write definitely worth splitting out. I'd imagine though that ABI-wise there's details of read/write that aren't shared which would make sense to go here (vs having this function effectively just tail-call another function), but I'm also ok waiting to see how the dust settles in wasip3 to see what the best organization is