diff --git a/Makefile b/Makefile index ad5428759..3de98ed69 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,11 @@ NM ?= $(patsubst %clang,%llvm-nm,$(filter-out ccache sccache,$(CC))) ifeq ($(origin AR), default) AR = $(patsubst %clang,%llvm-ar,$(filter-out ccache sccache,$(CC))) endif +ifeq ($(DEBUG), true) +EXTRA_CFLAGS ?= -O0 -g +else EXTRA_CFLAGS ?= -O2 -DNDEBUG +endif # The directory where we build the sysroot. SYSROOT ?= $(CURDIR)/sysroot # A directory to install to for "make install". @@ -924,8 +928,10 @@ check-symbols: $(STARTUP_FILES) libc |grep ' U ' |sed 's/.* U //' |LC_ALL=C sort |uniq); do \ grep -q '\<'$$undef_sym'\>' "$(DEFINED_SYMBOLS)" || echo $$undef_sym; \ done | grep -E -v "^__mul|__memory_base|__indirect_function_table|__tls_base" > "$(UNDEFINED_SYMBOLS)" +ifneq ($(WASI_SNAPSHOT), p2) grep '^_*imported_wasi_' "$(UNDEFINED_SYMBOLS)" \ > "$(SYSROOT_LIB)/libc.imports" +endif # # Generate a test file that includes all public C header files. @@ -1056,7 +1062,7 @@ bindings: $(BINDING_WORK_DIR)/wasi-cli $(BINDING_WORK_DIR)/wit-bindgen --autodrop-borrows yes \ --rename-world wasip2 \ --type-section-suffix __wasi_libc \ - --world wasi:cli/imports@0.2.0 \ + --world wasi:cli/command@0.2.0 \ --rename wasi:clocks/monotonic-clock@0.2.0=monotonic_clock \ --rename wasi:clocks/wall-clock@0.2.0=wall_clock \ --rename wasi:filesystem/preopens@0.2.0=filesystem_preopens \ diff --git a/expected/wasm32-wasip2/defined-symbols.txt b/expected/wasm32-wasip2/defined-symbols.txt index 10e55033c..ab2760a11 100644 --- a/expected/wasm32-wasip2/defined-symbols.txt +++ b/expected/wasm32-wasip2/defined-symbols.txt @@ -287,51 +287,6 @@ __uflow __unlist_locked_file __uselocale __utc -__wasi_args_get -__wasi_args_sizes_get -__wasi_clock_res_get -__wasi_clock_time_get -__wasi_environ_get -__wasi_environ_sizes_get -__wasi_fd_advise -__wasi_fd_allocate -__wasi_fd_close -__wasi_fd_datasync -__wasi_fd_fdstat_get -__wasi_fd_fdstat_set_flags -__wasi_fd_fdstat_set_rights -__wasi_fd_filestat_get -__wasi_fd_filestat_set_size -__wasi_fd_filestat_set_times -__wasi_fd_pread -__wasi_fd_prestat_dir_name -__wasi_fd_prestat_get -__wasi_fd_pwrite -__wasi_fd_read -__wasi_fd_readdir -__wasi_fd_renumber -__wasi_fd_seek -__wasi_fd_sync -__wasi_fd_tell -__wasi_fd_write -__wasi_path_create_directory -__wasi_path_filestat_get -__wasi_path_filestat_set_times -__wasi_path_link -__wasi_path_open -__wasi_path_readlink -__wasi_path_remove_directory -__wasi_path_rename -__wasi_path_symlink -__wasi_path_unlink_file -__wasi_poll_oneoff -__wasi_proc_exit -__wasi_random_get -__wasi_sched_yield -__wasi_sock_accept -__wasi_sock_recv -__wasi_sock_send -__wasi_sock_shutdown __wasi_sockets_services_db __wasi_sockets_utils__any_addr __wasi_sockets_utils__borrow_network @@ -392,6 +347,7 @@ __wasilibc_tell __wasilibc_unlinkat __wasilibc_utimens __wasm_call_dtors +__wasm_export_exports_wasi_cli_run_run __wcscoll_l __wcsftime_l __wcsxfrm_l @@ -567,6 +523,7 @@ ctime_r descriptor_table_get_ref descriptor_table_insert descriptor_table_remove +descriptor_table_update difftime dirfd dirname @@ -575,6 +532,8 @@ dprintf drand48 drem dremf +drop_directory_stream +drop_file_handle drop_tcp_socket drop_udp_socket drop_udp_socket_streams @@ -609,6 +568,8 @@ explicit_bzero expm1 expm1f expm1l +exports_wasi_cli_run_result_void_void_free +exports_wasi_cli_run_run fabs fabsf fabsl @@ -1247,7 +1208,6 @@ scalbnl scandir scandirat scanf -sched_yield seed48 seekdir select diff --git a/expected/wasm32-wasip2/undefined-symbols.txt b/expected/wasm32-wasip2/undefined-symbols.txt index b98dc7113..f3aca6b32 100644 --- a/expected/wasm32-wasip2/undefined-symbols.txt +++ b/expected/wasm32-wasip2/undefined-symbols.txt @@ -12,51 +12,6 @@ __getf2 __gttf2 __heap_base __heap_end -__imported_wasi_snapshot_preview1_args_get -__imported_wasi_snapshot_preview1_args_sizes_get -__imported_wasi_snapshot_preview1_clock_res_get -__imported_wasi_snapshot_preview1_clock_time_get -__imported_wasi_snapshot_preview1_environ_get -__imported_wasi_snapshot_preview1_environ_sizes_get -__imported_wasi_snapshot_preview1_fd_advise -__imported_wasi_snapshot_preview1_fd_allocate -__imported_wasi_snapshot_preview1_fd_close -__imported_wasi_snapshot_preview1_fd_datasync -__imported_wasi_snapshot_preview1_fd_fdstat_get -__imported_wasi_snapshot_preview1_fd_fdstat_set_flags -__imported_wasi_snapshot_preview1_fd_fdstat_set_rights -__imported_wasi_snapshot_preview1_fd_filestat_get -__imported_wasi_snapshot_preview1_fd_filestat_set_size -__imported_wasi_snapshot_preview1_fd_filestat_set_times -__imported_wasi_snapshot_preview1_fd_pread -__imported_wasi_snapshot_preview1_fd_prestat_dir_name -__imported_wasi_snapshot_preview1_fd_prestat_get -__imported_wasi_snapshot_preview1_fd_pwrite -__imported_wasi_snapshot_preview1_fd_read -__imported_wasi_snapshot_preview1_fd_readdir -__imported_wasi_snapshot_preview1_fd_renumber -__imported_wasi_snapshot_preview1_fd_seek -__imported_wasi_snapshot_preview1_fd_sync -__imported_wasi_snapshot_preview1_fd_tell -__imported_wasi_snapshot_preview1_fd_write -__imported_wasi_snapshot_preview1_path_create_directory -__imported_wasi_snapshot_preview1_path_filestat_get -__imported_wasi_snapshot_preview1_path_filestat_set_times -__imported_wasi_snapshot_preview1_path_link -__imported_wasi_snapshot_preview1_path_open -__imported_wasi_snapshot_preview1_path_readlink -__imported_wasi_snapshot_preview1_path_remove_directory -__imported_wasi_snapshot_preview1_path_rename -__imported_wasi_snapshot_preview1_path_symlink -__imported_wasi_snapshot_preview1_path_unlink_file -__imported_wasi_snapshot_preview1_poll_oneoff -__imported_wasi_snapshot_preview1_proc_exit -__imported_wasi_snapshot_preview1_random_get -__imported_wasi_snapshot_preview1_sched_yield -__imported_wasi_snapshot_preview1_sock_accept -__imported_wasi_snapshot_preview1_sock_recv -__imported_wasi_snapshot_preview1_sock_send -__imported_wasi_snapshot_preview1_sock_shutdown __letf2 __lttf2 __netf2 @@ -65,8 +20,6 @@ __subtf3 __trunctfdf2 __trunctfsf2 __unordtf2 -__wasi_preview1_adapter_close_badfd -__wasi_preview1_adapter_open_badfd __wasm_call_ctors __wasm_import_environment_get_arguments __wasm_import_environment_get_environment diff --git a/libc-bottom-half/clocks/clock.c b/libc-bottom-half/clocks/clock.c index 6767d7393..2d14c2260 100644 --- a/libc-bottom-half/clocks/clock.c +++ b/libc-bottom-half/clocks/clock.c @@ -1,6 +1,10 @@ #define _WASI_EMULATED_PROCESS_CLOCKS #include +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif #include _Static_assert( @@ -8,6 +12,32 @@ _Static_assert( "This implementation assumes that `clock` is in nanoseconds" ); +#ifdef __wasilibc_use_wasip2 +// Snapshot of the monotonic clock at the start of the program. +static wall_clock_datetime_t start; + +// Use a priority of 10 to run fairly early in the implementation-reserved +// constructor priority range. +__attribute__((constructor(10))) +static void init(void) { + wall_clock_now(&start); +} + +// Define the libc symbol as `__clock` so that we can reliably call it +// from elsewhere in libc. +clock_t __clock(void) { + // Use `MONOTONIC` instead of `PROCESS_CPUTIME_ID` since WASI doesn't have + // an inherent concept of a process. Note that this means we'll incorrectly + // include time from other processes, so this function is only declared by + // the headers if `_WASI_EMULATED_PROCESS_CLOCKS` is defined. + wall_clock_datetime_t now; + wall_clock_now(&now); + now.seconds -= start.seconds; + now.nanoseconds -= start.nanoseconds; + return (now.seconds + (now.nanoseconds * NSEC_PER_SEC)); +} +#else + // Snapshot of the monotonic clock at the start of the program. static __wasi_timestamp_t start; @@ -29,6 +59,7 @@ clock_t __clock(void) { (void)__wasi_clock_time_get(__WASI_CLOCKID_MONOTONIC, 0, &now); return now - start; } +#endif // Define a user-visible alias as a weak symbol. __attribute__((__weak__, __alias__("__clock"))) diff --git a/libc-bottom-half/clocks/getrusage.c b/libc-bottom-half/clocks/getrusage.c index d0113c513..b5046226a 100644 --- a/libc-bottom-half/clocks/getrusage.c +++ b/libc-bottom-half/clocks/getrusage.c @@ -2,7 +2,11 @@ #include #include #include +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif #include // `clock` is a weak symbol so that application code can override it. @@ -12,10 +16,17 @@ clock_t __clock(void); int getrusage(int who, struct rusage *r_usage) { switch (who) { case RUSAGE_SELF: { +#ifdef __wasilibc_use_wasip2 + clock_t usertime = __clock(); + *r_usage = (struct rusage) { + .ru_utime = instant_to_timeval(usertime) + }; +#else __wasi_timestamp_t usertime = __clock(); *r_usage = (struct rusage) { .ru_utime = timestamp_to_timeval(usertime) }; +#endif return 0; } case RUSAGE_CHILDREN: diff --git a/libc-bottom-half/clocks/times.c b/libc-bottom-half/clocks/times.c index 48fa07baa..814a57fcb 100644 --- a/libc-bottom-half/clocks/times.c +++ b/libc-bottom-half/clocks/times.c @@ -1,7 +1,11 @@ #define _WASI_EMULATED_PROCESS_CLOCKS #include #include +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif #include _Static_assert( @@ -14,6 +18,17 @@ _Static_assert( clock_t __clock(void); clock_t times(struct tms *buffer) { +#ifdef __wasilibc_use_wasip2 + clock_t user = __clock(); + *buffer = (struct tms){ + .tms_utime = user, + // WASI doesn't provide a way to spawn a new process, so always 0. + .tms_cutime = 0 + }; + + monotonic_clock_instant_t realtime = monotonic_clock_now(); +#else + __wasi_timestamp_t user = __clock(); *buffer = (struct tms){ .tms_utime = user, @@ -23,5 +38,6 @@ clock_t times(struct tms *buffer) { __wasi_timestamp_t realtime = 0; (void)__wasi_clock_time_get(__WASI_CLOCKID_MONOTONIC, 0, &realtime); +#endif return realtime; } diff --git a/libc-bottom-half/cloudlibc/src/common/errors.h b/libc-bottom-half/cloudlibc/src/common/errors.h new file mode 100644 index 000000000..5c0602265 --- /dev/null +++ b/libc-bottom-half/cloudlibc/src/common/errors.h @@ -0,0 +1,122 @@ +#ifdef __wasilibc_use_wasip2 +#include +#include + +static void translate_error(filesystem_error_code_t error) { + switch (error) { + case FILESYSTEM_ERROR_CODE_ACCESS: + errno = __WASI_ERRNO_ACCES; + break; + case FILESYSTEM_ERROR_CODE_WOULD_BLOCK: + errno = __WASI_ERRNO_AGAIN; + break; + case FILESYSTEM_ERROR_CODE_ALREADY: + errno = __WASI_ERRNO_ALREADY; + break; + case FILESYSTEM_ERROR_CODE_BAD_DESCRIPTOR: + errno = __WASI_ERRNO_BADF; + break; + case FILESYSTEM_ERROR_CODE_BUSY: + errno = __WASI_ERRNO_BUSY; + break; + case FILESYSTEM_ERROR_CODE_DEADLOCK: + errno = __WASI_ERRNO_DEADLK; + break; + case FILESYSTEM_ERROR_CODE_QUOTA: + errno = __WASI_ERRNO_DQUOT; + break; + case FILESYSTEM_ERROR_CODE_EXIST: + errno = __WASI_ERRNO_EXIST; + break; + case FILESYSTEM_ERROR_CODE_FILE_TOO_LARGE: + errno = __WASI_ERRNO_FBIG; + break; + case FILESYSTEM_ERROR_CODE_ILLEGAL_BYTE_SEQUENCE: + errno = __WASI_ERRNO_ILSEQ; + break; + case FILESYSTEM_ERROR_CODE_IN_PROGRESS: + errno = __WASI_ERRNO_INPROGRESS; + break; + case FILESYSTEM_ERROR_CODE_INTERRUPTED: + errno = __WASI_ERRNO_INTR; + break; + case FILESYSTEM_ERROR_CODE_INVALID: + errno = __WASI_ERRNO_INVAL; + break; + case FILESYSTEM_ERROR_CODE_IO: + errno = __WASI_ERRNO_IO; + break; + case FILESYSTEM_ERROR_CODE_IS_DIRECTORY: + errno = __WASI_ERRNO_ISDIR; + break; + case FILESYSTEM_ERROR_CODE_LOOP: + errno = __WASI_ERRNO_LOOP; + break; + case FILESYSTEM_ERROR_CODE_TOO_MANY_LINKS: + errno = __WASI_ERRNO_MLINK; + break; + case FILESYSTEM_ERROR_CODE_MESSAGE_SIZE: + errno = __WASI_ERRNO_MSGSIZE; + break; + case FILESYSTEM_ERROR_CODE_NAME_TOO_LONG: + errno = __WASI_ERRNO_NAMETOOLONG; + break; + case FILESYSTEM_ERROR_CODE_NO_DEVICE: + errno = __WASI_ERRNO_NODEV; + break; + case FILESYSTEM_ERROR_CODE_NO_ENTRY: + errno = __WASI_ERRNO_NOENT; + break; + case FILESYSTEM_ERROR_CODE_NO_LOCK: + errno = __WASI_ERRNO_NOLCK; + break; + case FILESYSTEM_ERROR_CODE_INSUFFICIENT_MEMORY: + errno = __WASI_ERRNO_NOMEM; + break; + case FILESYSTEM_ERROR_CODE_INSUFFICIENT_SPACE: + errno = __WASI_ERRNO_NOSPC; + break; + case FILESYSTEM_ERROR_CODE_NOT_DIRECTORY: + errno = __WASI_ERRNO_NOTDIR; + break; + case FILESYSTEM_ERROR_CODE_NOT_EMPTY: + errno = __WASI_ERRNO_NOTEMPTY; + break; + case FILESYSTEM_ERROR_CODE_NOT_RECOVERABLE: + errno = __WASI_ERRNO_NOTRECOVERABLE; + break; + case FILESYSTEM_ERROR_CODE_UNSUPPORTED: + errno = __WASI_ERRNO_NOTSUP; + break; + case FILESYSTEM_ERROR_CODE_NO_TTY: + errno = __WASI_ERRNO_NOTTY; + break; + case FILESYSTEM_ERROR_CODE_NO_SUCH_DEVICE: + errno = __WASI_ERRNO_NXIO; + break; + case FILESYSTEM_ERROR_CODE_OVERFLOW: + errno = __WASI_ERRNO_OVERFLOW; + break; + case FILESYSTEM_ERROR_CODE_NOT_PERMITTED: + errno = __WASI_ERRNO_PERM; + break; + case FILESYSTEM_ERROR_CODE_PIPE: + errno = __WASI_ERRNO_PIPE; + break; + case FILESYSTEM_ERROR_CODE_READ_ONLY: + errno = __WASI_ERRNO_ROFS; + break; + case FILESYSTEM_ERROR_CODE_INVALID_SEEK: + errno = __WASI_ERRNO_SPIPE; + break; + case FILESYSTEM_ERROR_CODE_TEXT_FILE_BUSY: + errno = __WASI_ERRNO_TXTBSY; + break; + case FILESYSTEM_ERROR_CODE_CROSS_DEVICE: + errno = __WASI_ERRNO_XDEV; + break; + default: + abort(); // Unreachable + } +} +#endif diff --git a/libc-bottom-half/cloudlibc/src/common/time.h b/libc-bottom-half/cloudlibc/src/common/time.h index 08e285269..891c623c1 100644 --- a/libc-bottom-half/cloudlibc/src/common/time.h +++ b/libc-bottom-half/cloudlibc/src/common/time.h @@ -9,14 +9,22 @@ #include +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif #include #include #define NSEC_PER_SEC 1000000000 static inline bool timespec_to_timestamp_exact( - const struct timespec *timespec, __wasi_timestamp_t *timestamp) { +#ifdef __wasilibc_use_wasip2 + const struct timespec *timespec, wall_clock_datetime_t *timestamp) { +#else + const struct timespec *timespec, __wasi_timestamp_t *timestamp) { +#endif // Invalid nanoseconds field. if (timespec->tv_nsec < 0 || timespec->tv_nsec >= NSEC_PER_SEC) return false; @@ -25,39 +33,86 @@ static inline bool timespec_to_timestamp_exact( if (timespec->tv_sec < 0) return false; +#ifdef __wasilibc_use_wasip2 + timestamp->seconds = timespec->tv_sec; + timestamp->nanoseconds = timespec->tv_nsec; + return true; +#else // Make sure our timestamp does not overflow. return !__builtin_mul_overflow(timespec->tv_sec, NSEC_PER_SEC, timestamp) && !__builtin_add_overflow(*timestamp, timespec->tv_nsec, timestamp); +#endif } static inline bool timespec_to_timestamp_clamp( - const struct timespec *timespec, __wasi_timestamp_t *timestamp) { +#ifdef __wasilibc_use_wasip2 + const struct timespec *timespec, wall_clock_datetime_t *timestamp) { +#else + const struct timespec *timespec, __wasi_timestamp_t *timestamp) { +#endif // Invalid nanoseconds field. if (timespec->tv_nsec < 0 || timespec->tv_nsec >= NSEC_PER_SEC) return false; if (timespec->tv_sec < 0) { // Timestamps before the Epoch are not supported. - *timestamp = 0; +#if __wasilibc_use_wasip2 + timestamp->seconds = 0; + timestamp->nanoseconds = 0; + } else { + timestamp->seconds = timespec->tv_sec; + timestamp->nanoseconds = timespec->tv_nsec; +#else + *timestamp = 0; } else if (__builtin_mul_overflow(timespec->tv_sec, NSEC_PER_SEC, timestamp) || __builtin_add_overflow(*timestamp, timespec->tv_nsec, timestamp)) { // Make sure our timestamp does not overflow. *timestamp = NUMERIC_MAX(__wasi_timestamp_t); +#endif } return true; } +#ifdef __wasilibc_use_wasip2 +static inline struct timespec timestamp_to_timespec( + wall_clock_datetime_t *timestamp) { + return (struct timespec){.tv_sec = timestamp->seconds, + .tv_nsec = timestamp->nanoseconds}; +} +#else static inline struct timespec timestamp_to_timespec( - __wasi_timestamp_t timestamp) { + __wasi_timestamp_t timestamp) { // Decompose timestamp into seconds and nanoseconds. return (struct timespec){.tv_sec = timestamp / NSEC_PER_SEC, .tv_nsec = timestamp % NSEC_PER_SEC}; } +#endif + +#ifdef __wasilibc_use_wasip2 +static inline struct timespec instant_to_timespec( + monotonic_clock_instant_t ns) { + // Decompose instant into seconds and nanoseconds + return (struct timespec){.tv_sec = ns / NSEC_PER_SEC, + .tv_nsec = ns % NSEC_PER_SEC}; +} + +static inline struct timeval instant_to_timeval( + monotonic_clock_instant_t ns) { + // Decompose instant into seconds and microoseconds + return (struct timeval){.tv_sec = ns / 1000, + .tv_usec = ns % 1000}; +} static inline struct timeval timestamp_to_timeval( - __wasi_timestamp_t timestamp) { + wall_clock_datetime_t *timestamp) { + return (struct timeval){.tv_sec = timestamp->seconds, + .tv_usec = timestamp->nanoseconds / 1000}; +} +#else +static inline struct timeval timestamp_to_timeval( + __wasi_timestamp_t timestamp) { struct timespec ts = timestamp_to_timespec(timestamp); return (struct timeval){.tv_sec = ts.tv_sec, ts.tv_nsec / 1000}; } - +#endif #endif diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/fdopendir.c b/libc-bottom-half/cloudlibc/src/libc/dirent/fdopendir.c index 4a2136af5..984b35e88 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/fdopendir.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/fdopendir.c @@ -2,7 +2,13 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include #include @@ -20,6 +26,46 @@ DIR *fdopendir(int fd) { return NULL; } +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle(fd, &file_handle)) { + errno = EBADF; + return NULL; + } + + // Read the directory + filesystem_own_directory_entry_stream_t result; + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_read_directory(file_handle, + &result, + &error_code); + if (!ok) { + free(dirp->buffer); + free(dirp); + translate_error(error_code); + return NULL; + } + + dirp->fd = fd; + // Add an internal handle for the buffer + descriptor_table_entry_t new_entry; + new_entry.tag = DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM; + directory_stream_entry_t stream_info; + stream_info.directory_stream = result; + stream_info.directory_file_handle = file_handle; + new_entry.directory_stream_info = stream_info; + int new_fd = -1; + descriptor_table_update(dirp->fd, new_entry); + + dirp->cookie = __WASI_DIRCOOKIE_START; + dirp->buffer_processed = 0; + dirp->buffer_size = DIRENT_DEFAULT_BUFFER_SIZE; + dirp->dirent = NULL; + dirp->dirent_size = 1; + return dirp; + +#else // Ensure that this is really a directory by already loading the first // chunk of data. __wasi_errno_t error = @@ -41,4 +87,5 @@ DIR *fdopendir(int fd) { dirp->dirent = NULL; dirp->dirent_size = 1; return dirp; +#endif } diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c b/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c index bfd19213c..010e32c96 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c @@ -8,7 +8,13 @@ #include #include +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include #include @@ -40,6 +46,59 @@ static_assert(DT_UNKNOWN == __WASI_FILETYPE_UNKNOWN, "Value mismatch"); } \ } while (0) +#ifdef __wasilibc_use_wasip2 +struct dirent *readdir(DIR *dirp) { + // Translate the file descriptor to an internal handle + filesystem_borrow_directory_entry_stream_t stream; + filesystem_borrow_descriptor_t parent_handle; + if (!fd_to_directory_stream(dirp->fd, &stream, &parent_handle)) { + errno = EBADF; + return NULL; + } + + filesystem_option_directory_entry_t dir_entry_optional; + filesystem_error_code_t error_code; + bool ok = filesystem_method_directory_entry_stream_read_directory_entry(stream, + &dir_entry_optional, + &error_code); + if (!ok) { + translate_error(error_code); + return NULL; + } + + if (!dir_entry_optional.is_some) { + // End-of-file + return NULL; + } + + filesystem_directory_entry_t dir_entry = dir_entry_optional.val; + + // Ensure that the dirent is large enough to fit the filename + size_t the_size = offsetof(struct dirent, d_name); + GROW(dirp->dirent, dirp->dirent_size, + the_size + dir_entry.name.len + 1); + struct dirent *dirent = dirp->dirent; + + // Get the inode number + filesystem_path_flags_t path_flags = 0; // Don't follow symlinks + filesystem_metadata_hash_value_t metadata; + ok = filesystem_method_descriptor_metadata_hash_at(parent_handle, + path_flags, + &dir_entry.name, + &metadata, + &error_code); + if (!ok) { + translate_error(error_code); + return NULL; + } + dirent->d_ino = metadata.lower; + dirent->d_type = dir_entry.type; + memcpy(dirent->d_name, dir_entry.name.ptr, dir_entry.name.len); + dirent->d_name[dir_entry.name.len] = '\0'; + + return dirent; +} +#else struct dirent *readdir(DIR *dirp) { for (;;) { // Extract the next dirent header. @@ -130,3 +189,4 @@ struct dirent *readdir(DIR *dirp) { dirp->buffer_processed = 0; } } +#endif diff --git a/libc-bottom-half/cloudlibc/src/libc/dirent/scandirat.c b/libc-bottom-half/cloudlibc/src/libc/dirent/scandirat.c index 079b3b3b2..8057c8133 100644 --- a/libc-bottom-half/cloudlibc/src/libc/dirent/scandirat.c +++ b/libc-bottom-half/cloudlibc/src/libc/dirent/scandirat.c @@ -2,7 +2,13 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include #include @@ -28,6 +34,96 @@ int __wasilibc_nocwd_scandirat(int dirfd, const char *dir, struct dirent ***name if (sel == NULL) sel = sel_true; +#ifdef __wasilibc_use_wasip2 + // Open the directory. + int fd = __wasilibc_nocwd_openat_nomode(dirfd, dir, O_RDONLY | O_NONBLOCK | O_DIRECTORY); + if (fd == -1) + return -1; + DIR *dirp = fdopendir(fd); + if (!dirp) + return -1; + fd = dirp->fd; + + // Translate the file descriptor to an internal handle + filesystem_borrow_directory_entry_stream_t stream; + filesystem_borrow_descriptor_t parent_file_handle; + if (!fd_to_directory_stream(fd, &stream, &parent_file_handle)) { + errno = EBADF; + return -1; + } + + // Space for the array to return to the caller. + struct dirent **dirents = NULL; + size_t dirents_size = 0; + size_t dirents_used = 0; + + bool ok = true; + filesystem_option_directory_entry_t dir_entry_optional; + filesystem_error_code_t error_code; + while (true) { + ok = filesystem_method_directory_entry_stream_read_directory_entry(stream, + &dir_entry_optional, + &error_code); + if (!ok) { + translate_error(error_code); + return -1; + } + if (!dir_entry_optional.is_some) { + // All directory entries have been read + break; + } + + // Create the new directory entry + struct dirent *dirent = + malloc(offsetof(struct dirent, d_name) + dir_entry_optional.val.name.len + 1); + if (dirent == NULL) { + errno = EINVAL; + return -1; + } + dirent->d_type = dir_entry_optional.val.type; + memcpy(dirent->d_name, dir_entry_optional.val.name.ptr, dir_entry_optional.val.name.len); + dirent->d_name[dir_entry_optional.val.name.len] = '\0'; + + // Do an `fstatat` to get the inode number. + if (fstatat(fd, dirent->d_name, &statbuf, AT_SYMLINK_NOFOLLOW) != 0) { + errno = EBADF; + return -1; + } + // Fill in the inode. + dirent->d_ino = statbuf.st_ino; + + // In case someone raced with us and replaced the object with this name + // with another of a different type, update the type too. + dirent->d_type = __wasilibc_iftodt(statbuf.st_mode & S_IFMT); + + if (sel(dirent)) { + // Add the entry to the results + if (dirents_used == dirents_size) { + dirents_size = dirents_size < 8 ? 8 : dirents_size * 2; + struct dirent **new_dirents = + realloc(dirents, dirents_size * sizeof(*dirents)); + if (new_dirents == NULL) { + free(dirent); + free(dirents); + errno = EBADF; + return -1; + } + dirents = new_dirents; + } + dirents[dirents_used++] = dirent; + } else { + // Discard the entry. + free(dirent); + } + } + + // Sort results and return them + filesystem_directory_entry_stream_drop_borrow(stream); + (qsort)(dirents, dirents_used, sizeof(*dirents), + (int (*)(const void *, const void *))compar); + *namelist = dirents; + return dirents_used; +#else // Open the directory. int fd = __wasilibc_nocwd_openat_nomode(dirfd, dir, O_RDONLY | O_NONBLOCK | O_DIRECTORY); if (fd == -1) @@ -165,4 +261,6 @@ int __wasilibc_nocwd_scandirat(int dirfd, const char *dir, struct dirent ***name free(buffer); close(fd); return -1; +#endif + } diff --git a/libc-bottom-half/cloudlibc/src/libc/fcntl/fcntl.c b/libc-bottom-half/cloudlibc/src/libc/fcntl/fcntl.c index 5d4055dd5..50c57cb4f 100644 --- a/libc-bottom-half/cloudlibc/src/libc/fcntl/fcntl.c +++ b/libc-bottom-half/cloudlibc/src/libc/fcntl/fcntl.c @@ -2,7 +2,13 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include #include @@ -16,6 +22,45 @@ int fcntl(int fildes, int cmd, ...) { // The close-on-exec flag is ignored. return 0; case F_GETFL: { +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + descriptor_table_entry_t *entry; + bool ref_exists = descriptor_table_get_ref(fildes, &entry); + filesystem_borrow_descriptor_t file_handle; + if (!ref_exists) { + errno = EBADF; + return -1; + } + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_STREAM) + file_handle = entry->stream.file_info.file_handle; + else if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE) + file_handle = entry->file.file_handle; + else { + errno = EINVAL; + return -1; + } + + // Get the flags of the descriptor + filesystem_descriptor_flags_t flags; + filesystem_error_code_t error_code; + if (!filesystem_method_descriptor_get_flags(file_handle, &flags, &error_code)) { + translate_error(error_code); + return -1; + } + + int oflags = 0; + if (flags & FILESYSTEM_DESCRIPTOR_FLAGS_READ) { + if (flags & FILESYSTEM_DESCRIPTOR_FLAGS_WRITE) + oflags |= O_RDWR; + else + oflags |= O_RDONLY; + } else if (flags & FILESYSTEM_DESCRIPTOR_FLAGS_WRITE) + oflags |= O_WRONLY; + else + oflags |= O_SEARCH; + + return oflags; +#else // Obtain the flags and the rights of the descriptor. __wasi_fdstat_t fds; __wasi_errno_t error = __wasi_fd_fdstat_get(fildes, &fds); @@ -38,6 +83,7 @@ int fcntl(int fildes, int cmd, ...) { oflags |= O_SEARCH; } return oflags; +#endif } case F_SETFL: { // Set new file descriptor flags. @@ -46,6 +92,16 @@ int fcntl(int fildes, int cmd, ...) { int flags = va_arg(ap, int); va_end(ap); +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fildes, &file_handle)) { + errno = EBADF; + return -1; + } + + // This is a no-op -- not supported in wasip2 +#else __wasi_fdflags_t fs_flags = flags & 0xfff; __wasi_errno_t error = __wasi_fd_fdstat_set_flags(fildes, fs_flags); @@ -53,6 +109,7 @@ int fcntl(int fildes, int cmd, ...) { errno = error; return -1; } +#endif return 0; } default: diff --git a/libc-bottom-half/cloudlibc/src/libc/fcntl/openat.c b/libc-bottom-half/cloudlibc/src/libc/fcntl/openat.c index 09cbbf800..f9c96c98a 100644 --- a/libc-bottom-half/cloudlibc/src/libc/fcntl/openat.c +++ b/libc-bottom-half/cloudlibc/src/libc/fcntl/openat.c @@ -3,7 +3,14 @@ // SPDX-License-Identifier: BSD-2-Clause #include +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#include +#else #include +#endif #include #include #include @@ -21,6 +28,87 @@ static_assert(O_EXCL >> 12 == __WASI_OFLAGS_EXCL, "Value mismatch"); static_assert(O_TRUNC >> 12 == __WASI_OFLAGS_TRUNC, "Value mismatch"); int __wasilibc_nocwd_openat_nomode(int fd, const char *path, int oflag) { + +#ifdef __wasilibc_use_wasip2 + // Set up path flags + filesystem_path_flags_t lookup_flags = 0; + if ((oflag & O_NOFOLLOW) == 0) + lookup_flags |= FILESYSTEM_PATH_FLAGS_SYMLINK_FOLLOW; + + // Set up open flags + filesystem_open_flags_t open_flags = 0; + if ((oflag & O_CREAT) != 0) + open_flags |= FILESYSTEM_OPEN_FLAGS_CREATE; + if ((oflag & O_DIRECTORY) != 0) + open_flags |= FILESYSTEM_OPEN_FLAGS_DIRECTORY; + if ((oflag & O_EXCL) != 0) + open_flags |= FILESYSTEM_OPEN_FLAGS_EXCLUSIVE; + if ((oflag & O_TRUNC) != 0) + open_flags |= FILESYSTEM_OPEN_FLAGS_TRUNCATE; + + // Set up descriptor flags + filesystem_descriptor_flags_t fs_flags = 0; + + switch (oflag & O_ACCMODE) { + case O_RDONLY: + case O_RDWR: + case O_WRONLY: + if ((oflag & O_RDONLY) != 0) { + fs_flags |= FILESYSTEM_DESCRIPTOR_FLAGS_READ; + } + if ((oflag & O_WRONLY) != 0) { + // Sync flags are not supported yet + fs_flags |= FILESYSTEM_DESCRIPTOR_FLAGS_WRITE | FILESYSTEM_DESCRIPTOR_FLAGS_MUTATE_DIRECTORY; + } + break; + case O_EXEC: + break; + case O_SEARCH: + break; + default: + errno = EINVAL; + return -1; + } + + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle(fd, &file_handle)) { + errno = EBADF; + return -1; + } + + // Construct a WASI string for the path + wasip2_string_t path2; + wasip2_string_dup(&path2, path); + + // Open the file, yielding a new handle + filesystem_own_descriptor_t new_handle; + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_open_at(file_handle, + lookup_flags, + &path2, + open_flags, + fs_flags, + &new_handle, + &error_code); + wasip2_string_free(&path2); + if (!ok) { + translate_error(error_code); + return -1; + } + + // Update the descriptor table with the new handle + int new_fd = -1; + descriptor_table_entry_t new_entry; + new_entry.tag = DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE; + new_entry.file.readable = ((oflag & O_RDONLY) != 0); + new_entry.file.writable = ((oflag & O_WRONLY) != 0); + new_entry.file.file_handle = filesystem_borrow_descriptor(new_handle); + descriptor_table_insert(new_entry, &new_fd); + + // Return the new file descriptor from the table + return new_fd; +#else // Compute rights corresponding with the access modes provided. // Attempt to obtain all rights, except the ones that contradict the // access mode provided to openat(). @@ -77,4 +165,6 @@ int __wasilibc_nocwd_openat_nomode(int fd, const char *path, int oflag) { return -1; } return newfd; +#endif + } diff --git a/libc-bottom-half/cloudlibc/src/libc/fcntl/posix_fadvise.c b/libc-bottom-half/cloudlibc/src/libc/fcntl/posix_fadvise.c index d683d399f..a14c9c50a 100644 --- a/libc-bottom-half/cloudlibc/src/libc/fcntl/posix_fadvise.c +++ b/libc-bottom-half/cloudlibc/src/libc/fcntl/posix_fadvise.c @@ -3,7 +3,13 @@ // SPDX-License-Identifier: BSD-2-Clause #include +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include @@ -20,5 +26,47 @@ static_assert(POSIX_FADV_WILLNEED == __WASI_ADVICE_WILLNEED, int posix_fadvise(int fd, off_t offset, off_t len, int advice) { if (offset < 0 || len < 0) return EINVAL; + +#ifdef __wasilibc_use_wasip2 + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fd, &file_handle)) { + errno = EBADF; + return EBADF; + } + + filesystem_error_code_t error_code; + filesystem_advice_t fs_advice; + switch (advice) { + case POSIX_FADV_NORMAL: + fs_advice = FILESYSTEM_ADVICE_NORMAL; + break; + case POSIX_FADV_SEQUENTIAL: + fs_advice = FILESYSTEM_ADVICE_SEQUENTIAL; + break; + case POSIX_FADV_RANDOM: + fs_advice = FILESYSTEM_ADVICE_RANDOM; + break; + case POSIX_FADV_NOREUSE: + fs_advice = FILESYSTEM_ADVICE_NO_REUSE; + break; + case POSIX_FADV_WILLNEED: + fs_advice = FILESYSTEM_ADVICE_WILL_NEED; + break; + case POSIX_FADV_DONTNEED: + fs_advice = FILESYSTEM_ADVICE_DONT_NEED; + break; + default: { + errno = EINVAL; + return EINVAL; + } + } + bool ok = filesystem_method_descriptor_advise(file_handle, offset, len, fs_advice, &error_code); + if (!ok) { + translate_error(error_code); + return errno; + } + return 0; +#else return __wasi_fd_advise(fd, offset, len, advice); +#endif } diff --git a/libc-bottom-half/cloudlibc/src/libc/fcntl/posix_fallocate.c b/libc-bottom-half/cloudlibc/src/libc/fcntl/posix_fallocate.c index 4b41c4b65..e9124518d 100644 --- a/libc-bottom-half/cloudlibc/src/libc/fcntl/posix_fallocate.c +++ b/libc-bottom-half/cloudlibc/src/libc/fcntl/posix_fallocate.c @@ -2,12 +2,19 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifndef __wasilibc_use_wasip2 #include +#endif #include #include int posix_fallocate(int fd, off_t offset, off_t len) { +// Note: this operation isn't supported in wasip2 +#ifdef __wasilibc_use_wasip2 + return ENOTSUP; +#else if (offset < 0 || len < 0) return EINVAL; return __wasi_fd_allocate(fd, offset, len); +#endif } diff --git a/libc-bottom-half/cloudlibc/src/libc/poll/poll.c b/libc-bottom-half/cloudlibc/src/libc/poll/poll.c index 0ee14791d..98ee1f746 100644 --- a/libc-bottom-half/cloudlibc/src/libc/poll/poll.c +++ b/libc-bottom-half/cloudlibc/src/libc/poll/poll.c @@ -140,7 +140,11 @@ int poll(struct pollfd* fds, nfds_t nfds, int timeout) for (size_t i = 0; i < nfds; ++i) { descriptor_table_entry_t* entry; if (descriptor_table_get_ref(fds[i].fd, &entry)) { - found_socket = true; + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET + || entry->tag == DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET) + found_socket = true; + else + found_non_socket = true; } else { found_non_socket = true; } @@ -155,16 +159,8 @@ int poll(struct pollfd* fds, nfds_t nfds, int timeout) errno = ENOTSUP; return -1; } - - return poll_wasip2(fds, nfds, timeout); - } else if (found_non_socket) { - return poll_wasip1(fds, nfds, timeout); - } else if (timeout >= 0) { - return poll_wasip2(fds, nfds, timeout); - } else { - errno = ENOTSUP; - return -1; } + return poll_wasip2(fds, nfds, timeout); } #else // not __wasilibc_use_wasip2 int poll(struct pollfd* fds, nfds_t nfds, int timeout) diff --git a/libc-bottom-half/cloudlibc/src/libc/sched/sched_yield.c b/libc-bottom-half/cloudlibc/src/libc/sched/sched_yield.c index 6100ea561..cd5ec14b9 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sched/sched_yield.c +++ b/libc-bottom-half/cloudlibc/src/libc/sched/sched_yield.c @@ -2,10 +2,13 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifndef __wasilibc_use_wasip2 #include +#endif #include #include +#ifndef __wasilibc_use_wasip2 int sched_yield(void) { __wasi_errno_t error = __wasi_sched_yield(); if (error != 0) { @@ -14,3 +17,4 @@ int sched_yield(void) { } return 0; } +#endif diff --git a/libc-bottom-half/cloudlibc/src/libc/stdio/renameat.c b/libc-bottom-half/cloudlibc/src/libc/stdio/renameat.c index c1706db48..72e0cfb74 100644 --- a/libc-bottom-half/cloudlibc/src/libc/stdio/renameat.c +++ b/libc-bottom-half/cloudlibc/src/libc/stdio/renameat.c @@ -2,16 +2,56 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include #include int __wasilibc_nocwd_renameat(int oldfd, const char *old, int newfd, const char *new) { +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptors to internal handles + filesystem_borrow_descriptor_t old_file_handle; + if (!fd_to_file_handle_allow_open(oldfd, &old_file_handle)) { + errno = EBADF; + return -1; + } + + filesystem_borrow_descriptor_t new_file_handle; + if (!fd_to_file_handle_allow_open(newfd, &new_file_handle)) { + errno = EBADF; + return -1; + } + + // Copy the strings into WASI strings + wasip2_string_t old_path, new_path; + wasip2_string_dup(&old_path, old); + wasip2_string_dup(&new_path, new); + + // Rename the file + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_rename_at(old_file_handle, + &old_path, + new_file_handle, + &new_path, + &error_code); + wasip2_string_free(&old_path); + wasip2_string_free(&new_path); + if (!ok) { + translate_error(error_code); + return -1; + } +#else __wasi_errno_t error = __wasi_path_rename(oldfd, old, newfd, new); if (error != 0) { errno = error; return -1; } +#endif return 0; } diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/ioctl/ioctl.c b/libc-bottom-half/cloudlibc/src/libc/sys/ioctl/ioctl.c index 2c968b2a0..f506b4499 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/ioctl/ioctl.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/ioctl/ioctl.c @@ -7,9 +7,10 @@ #include #include -#include #ifdef __wasilibc_use_wasip2 #include +#else +#include #endif int ioctl(int fildes, int request, ...) { @@ -56,7 +57,8 @@ int ioctl(int fildes, int request, ...) { return -1; } } - + // TODO: In particular, this doesn't support using FIONREAD + // with file descriptors default: errno = ENOPROTOOPT; return -1; @@ -66,6 +68,11 @@ int ioctl(int fildes, int request, ...) { switch (request) { case FIONREAD: { +#ifdef __wasilibc_use_wasip2 + // wasip2 doesn't support this operation + errno = ENOTSUP; + return -1; +#else // Poll the file descriptor to determine how many bytes can be read. __wasi_subscription_t subscriptions[2] = { { @@ -108,8 +115,14 @@ int ioctl(int fildes, int request, ...) { // No data available for reading. *result = 0; return 0; +#endif } case FIONBIO: { +#ifdef __wasilibc_use_wasip2 + // wasip2 doesn't support setting the non-blocking flag + errno = ENOTSUP; + return -1; +#else // Obtain the current file descriptor flags. __wasi_fdstat_t fds; __wasi_errno_t error = __wasi_fd_fdstat_get(fildes, &fds); @@ -134,6 +147,7 @@ int ioctl(int fildes, int request, ...) { return -1; } return 0; +#endif } default: // Invalid request. diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/select/pselect.c b/libc-bottom-half/cloudlibc/src/libc/sys/select/pselect.c index 3b479edfb..ea5eb8daf 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/select/pselect.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/select/pselect.c @@ -61,14 +61,24 @@ int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, int poll_timeout; if (timeout) { +#ifdef __wasilibc_use_wasip2 + wall_clock_datetime_t timestamp; + if (!timespec_to_timestamp_clamp(timeout, ×tamp) ) { +#else uint64_t timeout_u64; if (!timespec_to_timestamp_clamp(timeout, &timeout_u64) ) { +#endif errno = EINVAL; return -1; } // Convert nanoseconds to milliseconds: +#ifdef __wasilibc_use_wasip2 + uint64_t timeout_u64 = timestamp.nanoseconds /= 1000000; + timeout_u64 += timestamp.seconds * 1000; +#else timeout_u64 /= 1000000; +#endif if (timeout_u64 > INT_MAX) { timeout_u64 = INT_MAX; diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstat.c b/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstat.c index b8ffdb59c..4edc35017 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstat.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstat.c @@ -4,11 +4,51 @@ #include +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include "stat_impl.h" +#ifdef __wasilibc_use_wasip2 +int fstat(int fildes, struct stat *buf) { + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fildes, &file_handle)) { + errno = EBADF; + return -1; + } + + // Get the metadata hash for the file descriptor + filesystem_metadata_hash_value_t metadata; + filesystem_error_code_t error; + if (!filesystem_method_descriptor_metadata_hash(file_handle, + &metadata, + &error)) { + translate_error(error); + return -1; + } + + // Get the file metadata + filesystem_descriptor_stat_t internal_stat; + bool stat_result = filesystem_method_descriptor_stat(file_handle, + &internal_stat, + &error); + if (!stat_result) { + translate_error(error); + return -1; + } + + // Convert the internal data to an external struct + to_public_stat(&metadata, &internal_stat, buf); + return 0; +} +#else int fstat(int fildes, struct stat *buf) { __wasi_filestat_t internal_stat; __wasi_errno_t error = __wasi_fd_filestat_get(fildes, &internal_stat); @@ -19,3 +59,4 @@ int fstat(int fildes, struct stat *buf) { to_public_stat(&internal_stat, buf); return 0; } +#endif diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstatat.c b/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstatat.c index 25b29ac98..9e7a169c4 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstatat.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstatat.c @@ -4,28 +4,84 @@ #include +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include #include +#include #include "stat_impl.h" int __wasilibc_nocwd_fstatat(int fd, const char *restrict path, struct stat *restrict buf, int flag) { +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fd, &file_handle)) { + errno = EBADF; + return -1; + } + + // Copy the string into a Wasm string + wasip2_string_t path_wasm_string; + wasip2_string_dup(&path_wasm_string, path); + + // Get the metadata hash for this file + filesystem_metadata_hash_value_t metadata; + filesystem_error_code_t error; + filesystem_path_flags_t path_flags = (flag | AT_SYMLINK_NOFOLLOW) ? 0 + : FILESYSTEM_PATH_FLAGS_SYMLINK_FOLLOW; + if (!filesystem_method_descriptor_metadata_hash_at(file_handle, + path_flags, + &path_wasm_string, + &metadata, + &error)) { + translate_error(error); + return -1; + } + // Create lookup properties. __wasi_lookupflags_t lookup_flags = 0; if ((flag & AT_SYMLINK_NOFOLLOW) == 0) lookup_flags |= __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; + // Perform system call. + filesystem_descriptor_stat_t internal_stat; + bool stat_result = filesystem_method_descriptor_stat_at(file_handle, + lookup_flags, + &path_wasm_string, + &internal_stat, + &error); + wasip2_string_free(&path_wasm_string); + if (!stat_result) { + translate_error(error); + return -1; + } + + // Convert the internal data to an external struct + to_public_stat(&metadata, &internal_stat, buf); + return 0; +#else + __wasi_lookupflags_t lookup_flags = 0; + if ((flag & AT_SYMLINK_NOFOLLOW) == 0) + lookup_flags |= __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; + // Perform system call. __wasi_filestat_t internal_stat; __wasi_errno_t error = __wasi_path_filestat_get(fd, lookup_flags, path, &internal_stat); if (error != 0) { - errno = error; - return -1; + errno = error; + return -1; } to_public_stat(&internal_stat, buf); + return 0; +#endif } diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/stat/futimens.c b/libc-bottom-half/cloudlibc/src/libc/sys/stat/futimens.c index 13357fcf0..aadaf869a 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/stat/futimens.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/stat/futimens.c @@ -4,11 +4,77 @@ #include +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include "stat_impl.h" +#ifdef __wasilibc_use_wasip2 +static void set_atim_tag_from_flags(__wasi_fstflags_t flags, filesystem_new_timestamp_t* timestamp) { + if (flags & __WASI_FSTFLAGS_ATIM) { + timestamp->tag = FILESYSTEM_NEW_TIMESTAMP_TIMESTAMP; + } else if (flags & __WASI_FSTFLAGS_ATIM_NOW) { + timestamp->tag = FILESYSTEM_NEW_TIMESTAMP_NOW; + } else { + timestamp->tag = FILESYSTEM_NEW_TIMESTAMP_NO_CHANGE; + } +} + +static void set_mtim_tag_from_flags(__wasi_fstflags_t flags, filesystem_new_timestamp_t* timestamp) { + if (flags & __WASI_FSTFLAGS_MTIM) { + timestamp->tag = FILESYSTEM_NEW_TIMESTAMP_TIMESTAMP; + } else if (flags & __WASI_FSTFLAGS_MTIM_NOW) { + timestamp->tag = FILESYSTEM_NEW_TIMESTAMP_NOW; + } else { + timestamp->tag = FILESYSTEM_NEW_TIMESTAMP_NO_CHANGE; + } +} + +int futimens(int fd, const struct timespec *times) { + // Translate the file descriptor to an internal handle + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fd, &file_handle)) { + errno = EBADF; + return -1; + } + + // Convert timestamps and extract NOW/OMIT flags. + filesystem_datetime_t st_atim; + filesystem_datetime_t st_mtim; + __wasi_fstflags_t flags; + if (!utimens_get_timestamps(times, &st_atim, &st_mtim, &flags)) { + errno = EINVAL; + return -1; + } + + // Set up filesystem_new_timestamps + filesystem_new_timestamp_t new_timestamp_atim; + set_atim_tag_from_flags(flags, &new_timestamp_atim); + new_timestamp_atim.val.timestamp = st_atim; + filesystem_new_timestamp_t new_timestamp_mtim; + set_mtim_tag_from_flags(flags, &new_timestamp_mtim); + new_timestamp_mtim.val.timestamp = st_mtim; + + // Perform system call. + filesystem_error_code_t error; + if (!filesystem_method_descriptor_set_times(file_handle, + &new_timestamp_atim, + &new_timestamp_mtim, + &error)) { + translate_error(error); + return -1; + } + + return 0; +} +#else int futimens(int fd, const struct timespec *times) { // Convert timestamps and extract NOW/OMIT flags. __wasi_timestamp_t st_atim; @@ -27,3 +93,4 @@ int futimens(int fd, const struct timespec *times) { } return 0; } +#endif diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/stat/mkdirat.c b/libc-bottom-half/cloudlibc/src/libc/sys/stat/mkdirat.c index fd27d5e17..55bb9372c 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/stat/mkdirat.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/stat/mkdirat.c @@ -4,15 +4,44 @@ #include +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include int __wasilibc_nocwd_mkdirat_nomode(int fd, const char *path) { + +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle(fd, &file_handle)) + return EBADF; + + // Create the directory + filesystem_error_code_t error; + wasip2_string_t path2; + wasip2_string_dup(&path2, path); + bool ok = filesystem_method_descriptor_create_directory_at(file_handle, + &path2, + &error); + wasip2_string_free(&path2); + if (!ok) { + translate_error(error); + return -1; + } + return 0; +#else __wasi_errno_t error = __wasi_path_create_directory(fd, path); + if (error != 0) { errno = error; return -1; } return 0; +#endif } diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/stat/stat_impl.h b/libc-bottom-half/cloudlibc/src/libc/sys/stat/stat_impl.h index 9726b5165..162e2c61e 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/stat/stat_impl.h +++ b/libc-bottom-half/cloudlibc/src/libc/sys/stat/stat_impl.h @@ -10,7 +10,11 @@ #include #include +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif #include static_assert(S_ISBLK(S_IFBLK), "Value mismatch"); @@ -21,53 +25,159 @@ static_assert(S_ISLNK(S_IFLNK), "Value mismatch"); static_assert(S_ISREG(S_IFREG), "Value mismatch"); static_assert(S_ISSOCK(S_IFSOCK), "Value mismatch"); -static inline void to_public_stat(const __wasi_filestat_t *in, +#ifdef __wasilibc_use_wasip2 +static inline void to_public_stat(const filesystem_metadata_hash_value_t *metadata, + const filesystem_descriptor_stat_t *in, struct stat *out) { - // Ensure that we don't truncate any values. - static_assert(sizeof(in->dev) == sizeof(out->st_dev), "Size mismatch"); - static_assert(sizeof(in->ino) == sizeof(out->st_ino), "Size mismatch"); /* * The non-standard __st_filetype field appears to only be used for shared * memory, which we don't currently support. */ /* nlink_t is 64-bit on wasm32, following the x32 ABI. */ - static_assert(sizeof(in->nlink) <= sizeof(out->st_nlink), "Size shortfall"); + static_assert(sizeof(in->link_count) <= sizeof(out->st_nlink), "Size shortfall"); static_assert(sizeof(in->size) == sizeof(out->st_size), "Size mismatch"); + filesystem_datetime_t zero = { 0, 0 }; + filesystem_datetime_t accessTime = + in->data_access_timestamp.is_some ? in->data_access_timestamp.val : zero; + filesystem_datetime_t modificationTime = + in->data_modification_timestamp.is_some ? in->data_modification_timestamp.val : zero; + filesystem_datetime_t statusChangeTime = + in->status_change_timestamp.is_some ? in->status_change_timestamp.val : zero; + *out = (struct stat){ - .st_dev = in->dev, - .st_ino = in->ino, - .st_nlink = in->nlink, - .st_size = in->size, - .st_atim = timestamp_to_timespec(in->atim), - .st_mtim = timestamp_to_timespec(in->mtim), - .st_ctim = timestamp_to_timespec(in->ctim), + // wasip2's filesystem_descriptor_stat_t does not track the device number. + .st_dev = 1, + .st_ino = metadata->lower, + .st_nlink = in->link_count, + .st_size = in->size, + .st_atim = timestamp_to_timespec(&accessTime), + .st_mtim = timestamp_to_timespec(&modificationTime), + .st_ctim = timestamp_to_timespec(&statusChangeTime) }; // Convert file type to legacy types encoded in st_mode. - switch (in->filetype) { - case __WASI_FILETYPE_BLOCK_DEVICE: + switch (in->type) { + case FILESYSTEM_DESCRIPTOR_TYPE_BLOCK_DEVICE: out->st_mode |= S_IFBLK; break; - case __WASI_FILETYPE_CHARACTER_DEVICE: + case FILESYSTEM_DESCRIPTOR_TYPE_CHARACTER_DEVICE: out->st_mode |= S_IFCHR; break; - case __WASI_FILETYPE_DIRECTORY: + case FILESYSTEM_DESCRIPTOR_TYPE_DIRECTORY: out->st_mode |= S_IFDIR; break; - case __WASI_FILETYPE_REGULAR_FILE: + case FILESYSTEM_DESCRIPTOR_TYPE_REGULAR_FILE: out->st_mode |= S_IFREG; break; - case __WASI_FILETYPE_SOCKET_DGRAM: - case __WASI_FILETYPE_SOCKET_STREAM: + case FILESYSTEM_DESCRIPTOR_TYPE_SOCKET: out->st_mode |= S_IFSOCK; break; - case __WASI_FILETYPE_SYMBOLIC_LINK: + case FILESYSTEM_DESCRIPTOR_TYPE_SYMBOLIC_LINK: out->st_mode |= S_IFLNK; break; } } +#else +static inline void to_public_stat(const __wasi_filestat_t *in, + struct stat *out) { + // Ensure that we don't truncate any values. + static_assert(sizeof(in->dev) == sizeof(out->st_dev), "Size mismatch"); + static_assert(sizeof(in->ino) == sizeof(out->st_ino), "Size mismatch"); + /* + * The non-standard __st_filetype field appears to only be used for shared + * memory, which we don't currently support. + */ + /* nlink_t is 64-bit on wasm32, following the x32 ABI. */ + static_assert(sizeof(in->nlink) <= sizeof(out->st_nlink), "Size shortfall"); + + static_assert(sizeof(in->size) == sizeof(out->st_size), "Size mismatch"); + + *out = (struct stat){ + .st_dev = in->dev, + .st_ino = in->ino, + .st_nlink = in->nlink, + .st_size = in->size, + .st_atim = timestamp_to_timespec(in->atim), + .st_mtim = timestamp_to_timespec(in->mtim), + .st_ctim = timestamp_to_timespec(in->ctim), + }; + // Convert file type to legacy types encoded in st_mode. + switch (in->filetype) { + case __WASI_FILETYPE_BLOCK_DEVICE: + break; + case __WASI_FILETYPE_CHARACTER_DEVICE: + out->st_mode |= S_IFCHR; + break; + case __WASI_FILETYPE_DIRECTORY: + out->st_mode |= S_IFDIR; + break; + case __WASI_FILETYPE_REGULAR_FILE: + out->st_mode |= S_IFREG; + break; + case __WASI_FILETYPE_SOCKET_DGRAM: + case __WASI_FILETYPE_SOCKET_STREAM: + out->st_mode |= S_IFSOCK; + break; + case __WASI_FILETYPE_SYMBOLIC_LINK: + out->st_mode |= S_IFLNK; + break; + } +} +#endif +#ifdef __wasilibc_use_wasip2 +static inline bool utimens_get_timestamps(const struct timespec *times, + filesystem_datetime_t *st_atim, + filesystem_datetime_t *st_mtim, + __wasi_fstflags_t *flags) { + if (times == NULL) { + // Update both timestamps. + *flags = __WASI_FSTFLAGS_ATIM_NOW | __WASI_FSTFLAGS_MTIM_NOW; + st_atim->seconds = 0; + st_atim->nanoseconds = 0; + st_mtim->seconds = 0; + st_mtim->nanoseconds = 0; + } else { + // Set individual timestamps. + *flags = 0; + switch (times[0].tv_nsec) { + case UTIME_NOW: + *flags |= __WASI_FSTFLAGS_ATIM_NOW; + st_atim->seconds = 0; + st_atim->nanoseconds = 0; + break; + case UTIME_OMIT: + st_atim->seconds = 0; + st_atim->nanoseconds = 0; + break; + default: + *flags |= __WASI_FSTFLAGS_ATIM; + if (!timespec_to_timestamp_exact(×[0], st_atim)) + return false; + break; + } + + switch (times[1].tv_nsec) { + case UTIME_NOW: + *flags |= __WASI_FSTFLAGS_MTIM_NOW; + st_mtim->seconds = 0; + st_mtim->nanoseconds = 0; + break; + case UTIME_OMIT: + st_mtim->seconds = 0; + st_mtim->nanoseconds = 0; + break; + default: + *flags |= __WASI_FSTFLAGS_MTIM; + if (!timespec_to_timestamp_exact(×[1], st_mtim)) + return false; + break; + } + } + return true; +} +#else static inline bool utimens_get_timestamps(const struct timespec *times, __wasi_timestamp_t *st_atim, __wasi_timestamp_t *st_mtim, @@ -112,5 +222,5 @@ static inline bool utimens_get_timestamps(const struct timespec *times, } return true; } - +#endif #endif diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/stat/utimensat.c b/libc-bottom-half/cloudlibc/src/libc/sys/stat/utimensat.c index 19508a136..49a27b5fa 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/stat/utimensat.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/stat/utimensat.c @@ -4,13 +4,73 @@ #include +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include #include #include "stat_impl.h" +#ifdef __wasilibc_use_wasip2 +int __wasilibc_nocwd_utimensat(int fd, const char *path, const struct timespec times[2], + int flag) { + // Translate the file descriptor to an internal handle + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fd, &file_handle)) { + errno = EBADF; + return -1; + } + + // Convert timestamps and extract NOW/OMIT flags. + filesystem_datetime_t st_atim; + filesystem_datetime_t st_mtim; + __wasi_fstflags_t flags; + if (!utimens_get_timestamps(times, &st_atim, &st_mtim, &flags)) { + errno = EINVAL; + return -1; + } + + // Set up filesystem_new_timestamps + filesystem_new_timestamp_t new_timestamp_atim; + new_timestamp_atim.tag = FILESYSTEM_NEW_TIMESTAMP_TIMESTAMP; + new_timestamp_atim.val.timestamp = st_atim; + filesystem_new_timestamp_t new_timestamp_mtim; + new_timestamp_mtim.tag = FILESYSTEM_NEW_TIMESTAMP_TIMESTAMP; + new_timestamp_mtim.val.timestamp = st_mtim; + + // Create lookup properties. + __wasi_lookupflags_t lookup_flags = 0; + if ((flag & AT_SYMLINK_NOFOLLOW) == 0) + lookup_flags |= __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; + + // Copy the string into a Wasm string + wasip2_string_t path_wasm_string; + wasip2_string_dup(&path_wasm_string, path); + + // Perform system call. + filesystem_error_code_t error; + bool set_times_result = filesystem_method_descriptor_set_times_at(file_handle, + lookup_flags, + &path_wasm_string, + &new_timestamp_atim, + &new_timestamp_mtim, + &error); + wasip2_string_free(&path_wasm_string); + if (!set_times_result) { + translate_error(error); + return -1; + } + + return 0; +} +#else int __wasilibc_nocwd_utimensat(int fd, const char *path, const struct timespec times[2], int flag) { // Convert timestamps and extract NOW/OMIT flags. @@ -36,3 +96,4 @@ int __wasilibc_nocwd_utimensat(int fd, const char *path, const struct timespec t } return 0; } +#endif diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/time/gettimeofday.c b/libc-bottom-half/cloudlibc/src/libc/sys/time/gettimeofday.c index 596bdfff2..6702e246f 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/time/gettimeofday.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/time/gettimeofday.c @@ -6,13 +6,23 @@ #include +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif int gettimeofday(struct timeval *restrict tp, void *tz) { if (tp != NULL) { +#ifdef __wasilibc_use_wasip2 + wall_clock_datetime_t time_result; + wall_clock_now(&time_result); + *tp = timestamp_to_timeval(&time_result); +#else __wasi_timestamp_t ts = 0; (void)__wasi_clock_time_get(__WASI_CLOCKID_REALTIME, 1000, &ts); *tp = timestamp_to_timeval(ts); +#endif } return 0; } diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/uio/preadv.c b/libc-bottom-half/cloudlibc/src/libc/sys/uio/preadv.c index 36f882dd9..210956829 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/uio/preadv.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/uio/preadv.c @@ -4,8 +4,13 @@ #include #include - +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include ssize_t preadv(int fildes, const struct iovec *iov, int iovcnt, off_t offset) { @@ -14,11 +19,59 @@ ssize_t preadv(int fildes, const struct iovec *iov, int iovcnt, off_t offset) { return -1; } size_t bytes_read; +#ifdef __wasilibc_use_wasip2 + // Find the first non-empty iov + int32_t i = 0; + while (i < iovcnt) { + if (iov[i].iov_len != 0) + break; + i++; + } + + // If there is none, return + if (i >= iovcnt) { + return 0; + } + + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fildes, &file_handle)) { + errno = EBADF; + return -1; + } + + // Create a WASI buffer to receive the contents + wasip2_tuple2_list_u8_bool_t buffer; + + // Read the data + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_read(file_handle, + iov[i].iov_len, + offset, + &buffer, + &error_code); + if (buffer.f0.len > INT32_MAX) { + // In this case, the number of bytes read can't + // be represented by an ssize_t + errno = EINVAL; + return -1; + } + bytes_read = buffer.f0.len; + // Copy the contents of the buffer into the iov + memcpy(iov[i].iov_base, buffer.f0.ptr, buffer.f0.len); + wasip2_list_u8_free(&buffer.f0); + + if (!ok) { + translate_error(error_code); + return -1; + } +#else __wasi_errno_t error = __wasi_fd_pread( fildes, (const __wasi_iovec_t *)iov, iovcnt, offset, &bytes_read); if (error != 0) { errno = error; return -1; } +#endif return bytes_read; } diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/uio/pwritev.c b/libc-bottom-half/cloudlibc/src/libc/sys/uio/pwritev.c index d6f885187..b72524b94 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/uio/pwritev.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/uio/pwritev.c @@ -4,8 +4,13 @@ #include #include - +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include ssize_t pwritev(int fildes, const struct iovec *iov, int iovcnt, off_t offset) { @@ -14,11 +19,63 @@ ssize_t pwritev(int fildes, const struct iovec *iov, int iovcnt, off_t offset) { return -1; } size_t bytes_written; +#ifdef __wasilibc_use_wasip2 + // Note: following the preview1 adapter's behavior, only the first non-empty + // iov is written. + + // Find the first non-empty iov + int32_t i = 0; + while (i < iovcnt) { + if (iov[i].iov_len != 0) + break; + i++; + } + + // If there is none, return + if (i >= iovcnt) { + return 0; + } + + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fildes, &file_handle)) { + errno = EBADF; + return -1; + } + + // Create a WASI buffer to copy the contents into + wasip2_list_u8_t buffer; + buffer.len = iov[i].iov_len; + buffer.ptr = malloc(buffer.len); + memcpy(buffer.ptr, iov[i].iov_base, buffer.len); + + // Write the data + filesystem_error_code_t error_code; + filesystem_filesize_t num_bytes; + bool ok = filesystem_method_descriptor_write(file_handle, + &buffer, + offset, + &num_bytes, + &error_code); + wasip2_list_u8_free(&buffer); + if (!ok) { + translate_error(error_code); + return -1; + } + if (num_bytes > INT32_MAX) { + // In this case, the number of bytes written can't + // be represented by an ssize_t + errno = EINVAL; + return -1; + } + bytes_written = (ssize_t) num_bytes; +#else __wasi_errno_t error = __wasi_fd_pwrite( fildes, (const __wasi_ciovec_t *)iov, iovcnt, offset, &bytes_written); if (error != 0) { errno = error; return -1; } +#endif return bytes_written; } diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/uio/readv.c b/libc-bottom-half/cloudlibc/src/libc/sys/uio/readv.c index e3eaebe46..c2e8104bb 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/uio/readv.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/uio/readv.c @@ -30,11 +30,15 @@ ssize_t readv(int fildes, const struct iovec *iov, int iovcnt) { return -1; } size_t bytes_read; +#ifdef __wasilibc_use_wasip2 + return preadv(fildes, iov, iovcnt, 0); +#else __wasi_errno_t error = __wasi_fd_read( fildes, (const __wasi_iovec_t *)iov, iovcnt, &bytes_read); if (error != 0) { errno = error; return -1; } +#endif return bytes_read; } diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/uio/writev.c b/libc-bottom-half/cloudlibc/src/libc/sys/uio/writev.c index 459b15264..2781b5477 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/uio/writev.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/uio/writev.c @@ -5,9 +5,16 @@ #include #include +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include +#include static_assert(offsetof(struct iovec, iov_base) == offsetof(__wasi_ciovec_t, buf), @@ -29,12 +36,25 @@ ssize_t writev(int fildes, const struct iovec *iov, int iovcnt) { errno = EINVAL; return -1; } - size_t bytes_written; + size_t bytes_written = 0; + +#ifdef __wasilibc_use_wasip2 + // Following the behavior of the preview1 component adapter, + // only write the first non-empty iov + + for (size_t i = 0; i < iovcnt; i++) { + if (iov[i].iov_len == 0) + continue; + bytes_written = write(fildes, iov[i].iov_base, iov[i].iov_len); + break; + } +#else __wasi_errno_t error = __wasi_fd_write( fildes, (const __wasi_ciovec_t *)iov, iovcnt, &bytes_written); if (error != 0) { errno = error; return -1; } +#endif return bytes_written; } diff --git a/libc-bottom-half/cloudlibc/src/libc/time/clock_getres.c b/libc-bottom-half/cloudlibc/src/libc/time/clock_getres.c index 8030d4bb3..24a1b15b3 100644 --- a/libc-bottom-half/cloudlibc/src/libc/time/clock_getres.c +++ b/libc-bottom-half/cloudlibc/src/libc/time/clock_getres.c @@ -5,11 +5,22 @@ #include #include +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif #include #include int clock_getres(clockid_t clock_id, struct timespec *res) { +#ifdef __wasilibc_use_wasip2 + if (res != NULL) { + wall_clock_datetime_t time_result; + wall_clock_resolution(&time_result); + *res = timestamp_to_timespec(&time_result); + } +#else __wasi_timestamp_t ts; __wasi_errno_t error = __wasi_clock_res_get(clock_id->id, &ts); if (error != 0) { @@ -17,5 +28,6 @@ int clock_getres(clockid_t clock_id, struct timespec *res) { return -1; } *res = timestamp_to_timespec(ts); +#endif return 0; } diff --git a/libc-bottom-half/cloudlibc/src/libc/time/clock_gettime.c b/libc-bottom-half/cloudlibc/src/libc/time/clock_gettime.c index c7e1a609e..0fa598308 100644 --- a/libc-bottom-half/cloudlibc/src/libc/time/clock_gettime.c +++ b/libc-bottom-half/cloudlibc/src/libc/time/clock_gettime.c @@ -5,11 +5,31 @@ #include #include +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif #include #include int __clock_gettime(clockid_t clock_id, struct timespec *tp) { +#ifdef __wasilibc_use_wasip2 + if (tp == NULL) + return 0; + + if (clock_id == CLOCK_MONOTONIC) { + monotonic_clock_instant_t ns = monotonic_clock_now(); + *tp = instant_to_timespec(ns); + } else if (clock_id == CLOCK_REALTIME) { + wall_clock_datetime_t time_result; + wall_clock_now(&time_result); + *tp = timestamp_to_timespec(&time_result); + } else { + errno = __WASI_ERRNO_BADF; + return -1; + } +#else __wasi_timestamp_t ts; __wasi_errno_t error = __wasi_clock_time_get(clock_id->id, 1, &ts); if (error != 0) { @@ -17,6 +37,7 @@ int __clock_gettime(clockid_t clock_id, struct timespec *tp) { return -1; } *tp = timestamp_to_timespec(ts); +#endif return 0; } weak_alias(__clock_gettime, clock_gettime); diff --git a/libc-bottom-half/cloudlibc/src/libc/time/clock_nanosleep.c b/libc-bottom-half/cloudlibc/src/libc/time/clock_nanosleep.c index d375056ff..42a131ce1 100644 --- a/libc-bottom-half/cloudlibc/src/libc/time/clock_nanosleep.c +++ b/libc-bottom-half/cloudlibc/src/libc/time/clock_nanosleep.c @@ -6,13 +6,40 @@ #include #include +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif #include #include static_assert(TIMER_ABSTIME == __WASI_SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME, "Value mismatch"); +#ifdef __wasilibc_use_wasip2 +int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp, + struct timespec *rmtp) { + if ((flags & ~TIMER_ABSTIME) != 0) + return EINVAL; + + // Note: rmtp is ignored + + if (clock_id != CLOCK_MONOTONIC) { + // wasip2 only provides a pollable for monotonic clocks + return ENOTSUP; + } + + // Prepare pollable + int64_t duration = (rqtp->tv_sec * NSEC_PER_SEC) + rqtp->tv_nsec; + monotonic_clock_own_pollable_t pollable = monotonic_clock_subscribe_duration(duration); + + // Block until polling event is triggered. + poll_method_pollable_block(poll_borrow_pollable(pollable)); + + return 0; +} +#else int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp, struct timespec *rmtp) { if ((flags & ~TIMER_ABSTIME) != 0) @@ -33,5 +60,6 @@ int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp, __wasi_errno_t error = __wasi_poll_oneoff(&sub, &ev, 1, &nevents); return error == 0 && ev.error == 0 ? 0 : ENOTSUP; } +#endif weak_alias(clock_nanosleep, __clock_nanosleep); diff --git a/libc-bottom-half/cloudlibc/src/libc/time/time.c b/libc-bottom-half/cloudlibc/src/libc/time/time.c index 52bc0e484..0482e63e9 100644 --- a/libc-bottom-half/cloudlibc/src/libc/time/time.c +++ b/libc-bottom-half/cloudlibc/src/libc/time/time.c @@ -4,12 +4,22 @@ #include +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif #include time_t time(time_t *tloc) { +#ifdef __wasilibc_use_wasip2 + wall_clock_datetime_t res; + wall_clock_now(&res); + uint64_t ts = (res.seconds * NSEC_PER_SEC) + res.nanoseconds; +#else __wasi_timestamp_t ts = 0; (void)__wasi_clock_time_get(__WASI_CLOCKID_REALTIME, NSEC_PER_SEC, &ts); +#endif if (tloc != NULL) *tloc = ts / NSEC_PER_SEC; return ts / NSEC_PER_SEC; diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/faccessat.c b/libc-bottom-half/cloudlibc/src/libc/unistd/faccessat.c index ffaef6ed6..7d22a5175 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/faccessat.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/faccessat.c @@ -2,7 +2,13 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include #include @@ -16,6 +22,63 @@ int __wasilibc_nocwd_faccessat(int fd, const char *path, int amode, int flag) { return -1; } +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fd, &file_handle)) { + errno = EBADF; + return -1; + } + + // Convert the path to a WASI string + wasip2_string_t wasi_path; + wasip2_string_dup(&wasi_path, path); + + // Call stat() to check if the file exists + filesystem_descriptor_stat_t stat_result; + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_stat_at(file_handle, + FILESYSTEM_PATH_FLAGS_SYMLINK_FOLLOW, + &wasi_path, + &stat_result, + &error_code); + wasip2_string_free(&wasi_path); + if (!ok) { + translate_error(error_code); + return -1; + } + + // If amode == F_OK, we can return true since we already checked + // that the file exists + if (amode != 0) { + // Get the permissions on the directory + filesystem_descriptor_flags_t directory_flags; + ok = filesystem_method_descriptor_get_flags(file_handle, + &directory_flags, + &error_code); + if (!ok) { + translate_error(error_code); + return -1; + } + + bool has_rights = true; + + // Readable if the directory is readable; writable if the directory is writable + if ((amode & R_OK) != 0) { + if ((directory_flags & FILESYSTEM_DESCRIPTOR_FLAGS_READ) == 0) + has_rights = false; + } + if ((amode & W_OK) != 0) { + if ((directory_flags & FILESYSTEM_DESCRIPTOR_FLAGS_MUTATE_DIRECTORY) == 0) + has_rights = false; + } + if (!has_rights) { + errno = EACCES; + return -1; + } + } +#else // Check for target file existence and obtain the file type. __wasi_lookupflags_t lookup_flags = __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; __wasi_filestat_t file; @@ -49,5 +112,6 @@ int __wasilibc_nocwd_faccessat(int fd, const char *path, int amode, int flag) { return -1; } } +#endif return 0; } diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/fdatasync.c b/libc-bottom-half/cloudlibc/src/libc/unistd/fdatasync.c index b821b0668..8d8e6ec37 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/fdatasync.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/fdatasync.c @@ -2,15 +2,38 @@ // // SPDX-License-Identifier: BSD-2-Clause +#include +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include int fdatasync(int fildes) { +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fildes, &file_handle)) { + errno = EBADF; + return -1; + } + + // Sync the data + filesystem_error_code_t error_code; + if (!filesystem_method_descriptor_sync_data(file_handle, &error_code)) { + translate_error(error_code); + return -1; + } +#else __wasi_errno_t error = __wasi_fd_datasync(fildes); if (error != 0) { errno = error == ENOTCAPABLE ? EBADF : error; return -1; } +#endif return 0; } diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/fsync.c b/libc-bottom-half/cloudlibc/src/libc/unistd/fsync.c index 733e29479..71b3f6352 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/fsync.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/fsync.c @@ -2,15 +2,37 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include int fsync(int fildes) { +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fildes, &file_handle)) { + errno = EBADF; + return -1; + } + + // Sync the file + filesystem_error_code_t error_code; + if (!filesystem_method_descriptor_sync(file_handle, &error_code)) { + translate_error(error_code); + return -1; + } +#else __wasi_errno_t error = __wasi_fd_sync(fildes); if (error != 0) { errno = error == ENOTCAPABLE ? EINVAL : error; return -1; } +#endif return 0; } diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/ftruncate.c b/libc-bottom-half/cloudlibc/src/libc/unistd/ftruncate.c index 7792597e2..92bfcfc80 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/ftruncate.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/ftruncate.c @@ -2,7 +2,13 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include @@ -11,6 +17,20 @@ int ftruncate(int fildes, off_t length) { errno = EINVAL; return -1; } +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal file handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fildes, &file_handle)) { + errno = EBADF; + return -1; + } + + filesystem_error_code_t error_code; + if (!filesystem_method_descriptor_set_size(file_handle, length, &error_code)) { + translate_error(error_code); + return -1; + } +#else __wasi_filesize_t st_size = length; __wasi_errno_t error = __wasi_fd_filestat_set_size(fildes, st_size); @@ -18,5 +38,6 @@ int ftruncate(int fildes, off_t length) { errno = error; return -1; } +#endif return 0; } diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/linkat.c b/libc-bottom-half/cloudlibc/src/libc/unistd/linkat.c index d57f5621d..33da03e28 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/linkat.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/linkat.c @@ -2,13 +2,52 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include #include #include int __wasilibc_nocwd_linkat(int fd1, const char *path1, int fd2, const char *path2, int flag) { +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptors to internal handles + filesystem_borrow_descriptor_t file_handle1, file_handle2; + if (!fd_to_file_handle_allow_open(fd1, &file_handle1)) { + errno = EBADF; + return -1; + } + if (!fd_to_file_handle_allow_open(fd2, &file_handle2)) { + errno = EBADF; + return -1; + } + + // Create WASI strings for the paths + wasip2_string_t path1_wasi, path2_wasi; + wasip2_string_dup(&path1_wasi, path1); + wasip2_string_dup(&path2_wasi, path2); + + // Create the link + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_link_at(file_handle1, + 0, + &path1_wasi, + file_handle2, + &path2_wasi, + &error_code); + wasip2_string_free(&path1_wasi); + wasip2_string_free(&path2_wasi); + + if (!ok) { + translate_error(error_code); + return -1; + } +#else // Create lookup properties. __wasi_lookupflags_t lookup1_flags = 0; if ((flag & AT_SYMLINK_FOLLOW) != 0) @@ -20,5 +59,6 @@ int __wasilibc_nocwd_linkat(int fd1, const char *path1, int fd2, const char *pat errno = error; return -1; } +#endif return 0; } diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/lseek.c b/libc-bottom-half/cloudlibc/src/libc/unistd/lseek.c index 3e0429f10..66c362337 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/lseek.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/lseek.c @@ -3,7 +3,13 @@ // SPDX-License-Identifier: BSD-2-Clause #include +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include @@ -11,7 +17,118 @@ static_assert(SEEK_CUR == __WASI_WHENCE_CUR, "Value mismatch"); static_assert(SEEK_END == __WASI_WHENCE_END, "Value mismatch"); static_assert(SEEK_SET == __WASI_WHENCE_SET, "Value mismatch"); +#ifdef __wasilibc_use_wasip2 +static off_t last_offset(streams_borrow_input_stream_t input_stream) { + // Not ideal, but I'm not sure how else to find the last offset + // in the current stream + uint64_t total_bytes_skipped = 0; + uint64_t bytes_skipped = 0; + streams_stream_error_t stream_error; + bool ok = false; + while (true) { + ok = streams_method_input_stream_blocking_skip(input_stream, 1024, &bytes_skipped, &stream_error); + if (!ok) { + break; + } + total_bytes_skipped += bytes_skipped; + if (bytes_skipped < 1024) { + break; + } + } + if (!ok) { + if (stream_error.tag != STREAMS_STREAM_ERROR_CLOSED) { + errno = EIO; + return -1; + } + } + return bytes_skipped; +} +#endif + off_t __lseek(int fildes, off_t offset, int whence) { + +#ifdef __wasilibc_use_wasip2 + off_t offset_to_use = 0; + // Look up a stream for fildes + descriptor_table_entry_t *entry; + descriptor_table_get_ref(fildes, &entry); + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_STREAM) { + // Find the offset relative to the beginning of the file + // The offset is always *added*: either to 0, the current + // offset, or the offset of the end of the file. + switch (whence) { + case SEEK_SET: + offset_to_use = offset; + break; + case SEEK_CUR: + offset_to_use = offset + entry->stream.offset; + break; + case SEEK_END: { + // Find the end of the stream (is there a better way to do this?) + if (entry->stream.file_info.readable) { + off_t eof_offset = last_offset(entry->stream.read_stream); + if (eof_offset < 0) { + errno = EINVAL; + return -1; + } + offset_to_use = (eof_offset + entry->stream.offset) + offset; + } else { + offset_to_use = entry->stream.offset + offset; + } + break; + } + default: { + errno = EINVAL; + return -1; + } + } + // Drop the existing streams + if (entry->stream.file_info.readable) + streams_input_stream_drop_borrow(entry->stream.read_stream); + if (entry->stream.file_info.writable) + streams_output_stream_drop_borrow(entry->stream.write_stream); + + // Open a new stream with the right offset + if (entry->stream.file_info.readable) { + filesystem_own_input_stream_t new_stream; + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_read_via_stream(entry->stream.file_info.file_handle, + offset_to_use, + &new_stream, + &error_code); + if (!ok) { + translate_error(error_code); + return -1; + } + // Update input_stream.stream with the new stream + entry->stream.read_stream = streams_borrow_input_stream(new_stream); + } + if (entry->stream.file_info.writable) { + filesystem_own_output_stream_t new_stream; + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_write_via_stream(entry->stream.file_info.file_handle, + offset_to_use, + &new_stream, + &error_code); + if (!ok) { + translate_error(error_code); + return -1; + } + + // Update output_stream.stream with the new stream + entry->stream.write_stream = streams_borrow_output_stream(new_stream); + } + + // Update offset + entry->stream.offset = offset_to_use; + } else { + errno = EINVAL; + return -1; + } + + // Return the computed offset + return offset_to_use; +#else __wasi_filesize_t new_offset; __wasi_errno_t error = __wasi_fd_seek(fildes, offset, whence, &new_offset); @@ -20,6 +137,7 @@ off_t __lseek(int fildes, off_t offset, int whence) { return -1; } return new_offset; +#endif } weak_alias(__lseek, lseek); diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/pread.c b/libc-bottom-half/cloudlibc/src/libc/unistd/pread.c index c9944bc4e..a9ddfb3f7 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/pread.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/pread.c @@ -2,7 +2,13 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include @@ -11,6 +17,44 @@ ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset) { errno = EINVAL; return -1; } + +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fildes, &file_handle)) { + errno = EBADF; + return -1; + } + + // Set up a WASI tuple to receive the results + wasip2_tuple2_list_u8_bool_t contents; + + // Read the bytes + filesystem_error_code_t error_code; + size_t bytes_read; + bool ok = filesystem_method_descriptor_read(file_handle, + nbyte, + offset, + &contents, + &error_code); + bytes_read = contents.f0.len; + // Copy the result into the buffer + if (!memcpy(buf, contents.f0.ptr, bytes_read)) { + errno = EINVAL; + return -1; + } + + // Free the list in the tuple + wasip2_list_u8_free(&contents.f0); + + // Check for errors + if (!ok) { + translate_error(error_code); + return -1; + } + + return bytes_read; +#else __wasi_iovec_t iov = {.buf = buf, .buf_len = nbyte}; size_t bytes_read; __wasi_errno_t error = @@ -28,4 +72,6 @@ ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset) { return -1; } return bytes_read; +#endif } + diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/pwrite.c b/libc-bottom-half/cloudlibc/src/libc/unistd/pwrite.c index f80bc406b..bb8a5f5be 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/pwrite.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/pwrite.c @@ -2,7 +2,13 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include @@ -11,6 +17,43 @@ ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset) { errno = EINVAL; return -1; } + +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fildes, &file_handle)) { + errno = EBADF; + return -1; + } + + // Convert `buf` to a WASI byte list + wasip2_list_u8_t contents; + contents.len = nbyte; + contents.ptr = malloc(nbyte); + if (!memcpy(contents.ptr, buf, nbyte)) { + errno = EINVAL; + return -1; + } + + // Write the bytes + filesystem_filesize_t bytes_written; + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_write(file_handle, + &contents, + offset, + &bytes_written, + &error_code); + // Free the byte list + wasip2_list_u8_free(&contents); + + // Check for errors + if (!ok) { + translate_error(error_code); + return -1; + } + + return bytes_written; +#else __wasi_ciovec_t iov = {.buf = buf, .buf_len = nbyte}; size_t bytes_written; __wasi_errno_t error = @@ -28,4 +71,5 @@ ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset) { return -1; } return bytes_written; +#endif } diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/read.c b/libc-bottom-half/cloudlibc/src/libc/unistd/read.c index 1582126e1..f6ef7caae 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/read.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/read.c @@ -2,11 +2,113 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include ssize_t read(int fildes, void *buf, size_t nbyte) { +#ifdef __wasilibc_use_wasip2 + bool ok = false; + + // Translate the file descriptor to an internal handle + descriptor_table_entry_t* entry = 0; + descriptor_table_get_ref(fildes, &entry); + streams_borrow_input_stream_t input_stream; + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE) { + // File's input stream hasn't been opened yet + + // Get the input stream + filesystem_error_code_t error_code; + streams_own_input_stream_t input_stream_own; + ok = filesystem_method_descriptor_read_via_stream(entry->file.file_handle, + 0, + &input_stream_own, + &error_code); + if (!ok) { + translate_error(error_code); + return -1; + } + input_stream = streams_borrow_input_stream(input_stream_own); + + descriptor_table_entry_t new_entry; + + // If the file is writable, also need an output stream + if (entry->file.writable) { + streams_own_output_stream_t write_stream; + ok = filesystem_method_descriptor_write_via_stream(entry->file.file_handle, + 0, + &write_stream, + &error_code); + if (!ok) { + translate_error(error_code); + return -1; + } + + new_entry.stream.write_stream = streams_borrow_output_stream(write_stream); + } + + // Update the descriptor table with the newly opened stream + // for this file + new_entry.tag = DESCRIPTOR_TABLE_ENTRY_FILE_STREAM; + new_entry.stream.read_stream = input_stream; + new_entry.stream.offset = 0; + new_entry.stream.file_info.readable = entry->file.readable; + new_entry.stream.file_info.writable = entry->file.writable; + new_entry.stream.file_info.file_handle = entry->file.file_handle; + descriptor_table_update(fildes, new_entry); + } else if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_STREAM) { + if (!entry->stream.file_info.readable) { + errno = EBADF; + return -1; + } + input_stream = entry->stream.read_stream; + } else { + errno = EBADF; + return -1; + } + + // Set up a WASI list of bytes to receive the results + wasip2_list_u8_t contents; + + // Read the bytes + streams_stream_error_t stream_error; + ok = streams_method_input_stream_blocking_read(input_stream, + nbyte, + &contents, + &stream_error); + + if (!ok) { + if (stream_error.tag == STREAMS_STREAM_ERROR_CLOSED) + return 0; + else { + errno = EIO; + return -1; + } + } + + // Copy the bytes to `buf` so we can free the list + if (!memcpy(buf, contents.ptr, contents.len)) { + errno = EINVAL; + return -1; + } + wasip2_list_u8_free(&contents); + + // Update the offset + descriptor_table_get_ref(fildes, &entry); + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_STREAM) { + entry->stream.offset += contents.len; + } else { + abort(); // unreachable + } + return contents.len; + +#else __wasi_iovec_t iov = {.buf = buf, .buf_len = nbyte}; size_t bytes_read; __wasi_errno_t error = __wasi_fd_read(fildes, &iov, 1, &bytes_read); @@ -15,4 +117,5 @@ ssize_t read(int fildes, void *buf, size_t nbyte) { return -1; } return bytes_read; +#endif } diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/readlinkat.c b/libc-bottom-half/cloudlibc/src/libc/unistd/readlinkat.c index 7a3bce27b..92cf86d5d 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/readlinkat.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/readlinkat.c @@ -2,7 +2,13 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include #include @@ -10,6 +16,35 @@ ssize_t __wasilibc_nocwd_readlinkat(int fd, const char *restrict path, char *restrict buf, size_t bufsize) { size_t bufused; +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fd, &file_handle)) { + errno = EBADF; + return -1; + } + + // Create wasi strings for the input and output paths + wasip2_string_t wasi_path, link_source; + wasip2_string_dup(&wasi_path, path); + + // Read the link + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_readlink_at(file_handle, + &wasi_path, + &link_source, + &error_code); + wasip2_string_free(&wasi_path); + if (!ok) { + wasip2_string_free(&link_source); + translate_error(error_code); + return -1; + } + + // Copy the contents of the output path back into the buffer provided + bufused = bufsize < link_source.len ? bufsize : link_source.len; + memcpy(buf, link_source.ptr, bufused); +#else // TODO: Remove the cast on `buf` once the witx is updated with char8 support. __wasi_errno_t error = __wasi_path_readlink(fd, path, (uint8_t*)buf, bufsize, &bufused); @@ -17,5 +52,6 @@ ssize_t __wasilibc_nocwd_readlinkat(int fd, const char *restrict path, char *res errno = error; return -1; } +#endif return bufused; } diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/symlinkat.c b/libc-bottom-half/cloudlibc/src/libc/unistd/symlinkat.c index 0aa38be2c..3c47189eb 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/symlinkat.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/symlinkat.c @@ -2,16 +2,56 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include #include int __wasilibc_nocwd_symlinkat(const char *path1, int fd, const char *path2) { + +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + filesystem_borrow_descriptor_t file_handle; + if (!fd_to_file_handle_allow_open(fd, &file_handle)) { + errno = EBADF; + return -1; + } + + // Construct WASI string for the paths + wasip2_string_t path1_wasi; + wasip2_string_dup(&path1_wasi, path1); + wasip2_string_t path2_wasi; + wasip2_string_dup(&path2_wasi, path2); + + // Construct the link + filesystem_error_code_t error_code; + // path1 is the path for the existing file; path2 is the path for the new link + bool ok = filesystem_method_descriptor_symlink_at(file_handle, + &path1_wasi, + &path2_wasi, + &error_code); + wasip2_string_free(&path1_wasi); + wasip2_string_free(&path2_wasi); + + // Check for errors + if (!ok) { + translate_error(error_code); + return -1; + } + +#else + __wasi_errno_t error = __wasi_path_symlink(path1, fd, path2); if (error != 0) { errno = error; return -1; } +#endif return 0; } diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/write.c b/libc-bottom-half/cloudlibc/src/libc/unistd/write.c index a6567e809..3b88eea76 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/write.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/write.c @@ -2,11 +2,131 @@ // // SPDX-License-Identifier: BSD-2-Clause +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#include +#else #include +#endif #include #include ssize_t write(int fildes, const void *buf, size_t nbyte) { +#ifdef __wasilibc_use_wasip2 + streams_borrow_output_stream_t output_stream; + filesystem_own_input_stream_t input_stream; + bool ok = false; + bool create_new_stream = false; + filesystem_error_code_t error_code; + descriptor_table_entry_t* entry = 0; + + // Check for stdin/stdout/stderr + if (fildes == 1) + output_stream = streams_borrow_output_stream(stdout_get_stdout()); + else if (fildes == 2) + output_stream = streams_borrow_output_stream(stderr_get_stderr()); + else { + // Translate the file descriptor to an internal handle + descriptor_table_get_ref(fildes, &entry); + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE) { + create_new_stream = true; + + // Get the output stream + filesystem_own_output_stream_t stream_owned; + ok = filesystem_method_descriptor_write_via_stream(entry->file.file_handle, + 0, + &stream_owned, + &error_code); + if (!ok) { + translate_error(error_code); + return -1; + } + output_stream = streams_borrow_output_stream(stream_owned); + } else if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_STREAM) { + if (!entry->stream.file_info.writable) { + errno = EBADF; + return -1; + } + output_stream = entry->stream.write_stream; + } else { + errno = EBADF; + return -1; + } + } + + // Check readiness for writing + uint64_t num_bytes_permitted = 0; + streams_stream_error_t stream_error; + ok = streams_method_output_stream_check_write(output_stream, + &num_bytes_permitted, + &stream_error); + if (!ok) { + errno = EIO; + return -1; + } + + if (num_bytes_permitted < nbyte) { + streams_own_pollable_t pollable = streams_method_output_stream_subscribe(output_stream); + poll_method_pollable_block(poll_borrow_pollable(pollable)); + } + + // Convert the buffer to a WASI list of bytes + wasip2_list_u8_t contents; + contents.len = nbyte; + contents.ptr = malloc(nbyte); + if (!memcpy(contents.ptr, buf, nbyte)) { + errno = EINVAL; + return -1; + } + + // Write the bytes to the stream + ok = streams_method_output_stream_write(output_stream, + &contents, + &stream_error); + wasip2_list_u8_free(&contents); + + if (!ok) { + errno = EIO; + return -1; + } + + // Update the descriptor table with the stream + if (create_new_stream) { + descriptor_table_entry_t new_entry; + + // If the file is readable, also need an input stream + if (entry->file.readable) { + streams_own_input_stream_t read_stream; + ok = filesystem_method_descriptor_read_via_stream(entry->file.file_handle, + 0, + &read_stream, + &error_code); + if (!ok) { + translate_error(error_code); + return -1; + } + new_entry.stream.read_stream = streams_borrow_input_stream(read_stream); + } + new_entry.stream.write_stream = output_stream; + new_entry.stream.offset = contents.len; + new_entry.stream.file_info.readable = entry->file.readable; + new_entry.stream.file_info.writable = entry->file.writable; + new_entry.stream.file_info.file_handle = entry->file.file_handle; + new_entry.tag = DESCRIPTOR_TABLE_ENTRY_FILE_STREAM; + descriptor_table_update(fildes, new_entry); + } + + ok = streams_method_output_stream_blocking_flush(output_stream, + &stream_error); + if (!ok) { + errno = EIO; + return -1; + } + + return nbyte; +#else __wasi_ciovec_t iov = {.buf = buf, .buf_len = nbyte}; size_t bytes_written; __wasi_errno_t error = @@ -16,4 +136,5 @@ ssize_t write(int fildes, const void *buf, size_t nbyte) { return -1; } return bytes_written; +#endif } diff --git a/libc-bottom-half/headers/private/wasi/descriptor_table.h b/libc-bottom-half/headers/private/wasi/descriptor_table.h index 8e9a90d9d..b2756cf9e 100644 --- a/libc-bottom-half/headers/private/wasi/descriptor_table.h +++ b/libc-bottom-half/headers/private/wasi/descriptor_table.h @@ -106,18 +106,53 @@ typedef struct { udp_socket_state_t state; } udp_socket_t; +typedef struct { +// Stream for contents of directory + filesystem_own_directory_entry_stream_t directory_stream; +// File handle for the directory being listed +// (so that metadata hashes can be accessed). + filesystem_borrow_descriptor_t directory_file_handle; +} directory_stream_entry_t; + +// Stream representing an open file. +typedef struct { +// File was opened for reading + bool readable; +// File was opened for writing + bool writable; + filesystem_borrow_descriptor_t file_handle; +} file_t; + +typedef struct { + streams_borrow_input_stream_t read_stream; + streams_borrow_output_stream_t write_stream; + // Current position in stream, relative to the beginning of the *file*, measured in bytes + off_t offset; + // When the stream is closed, the caller should + // replace this entry in the table with the file handle + file_t file_info; +} file_stream_t; + // This is a tagged union. When adding/removing/renaming cases, be sure to keep the tag and union definitions in sync. typedef struct { enum { DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET, DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET, + DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE, + DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM, + DESCRIPTOR_TABLE_ENTRY_FILE_STREAM } tag; union { tcp_socket_t tcp_socket; udp_socket_t udp_socket; + file_t file; + directory_stream_entry_t directory_stream_info; + file_stream_t stream; }; } descriptor_table_entry_t; +bool descriptor_table_update(int fd, descriptor_table_entry_t entry); + bool descriptor_table_insert(descriptor_table_entry_t entry, int *fd); bool descriptor_table_get_ref(int fd, descriptor_table_entry_t **entry); diff --git a/libc-bottom-half/headers/private/wasi/file_utils.h b/libc-bottom-half/headers/private/wasi/file_utils.h new file mode 100644 index 000000000..ec71be3bb --- /dev/null +++ b/libc-bottom-half/headers/private/wasi/file_utils.h @@ -0,0 +1,52 @@ +#ifndef __wasi_file_utils_h +#define __wasi_file_utils_h + +#ifdef __wasilibc_use_wasip2 +#include + +// Succeed only if fd is bound to a file handle in the descriptor table +static bool fd_to_file_handle(int fd, filesystem_borrow_descriptor_t* result) { + descriptor_table_entry_t* entry = 0; + if (!descriptor_table_get_ref(fd, &entry)) + return false; + if (entry->tag != DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE) + return false; + *result = entry->file.file_handle; + return true; +} + +// Succeed if fd is bound to a file handle or a file input/output stream in the descriptor table +static bool fd_to_file_handle_allow_open(int fd, filesystem_borrow_descriptor_t* result) { + descriptor_table_entry_t *entry = 0; + bool ref_exists = descriptor_table_get_ref(fd, &entry); + filesystem_borrow_descriptor_t file_handle; + if (!ref_exists) + return false; + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_STREAM) + *result = entry->stream.file_info.file_handle; + else if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE) + *result = entry->file.file_handle; + else if (entry->tag == DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM) + *result = entry->directory_stream_info.directory_file_handle; + else + return false; + return true; +} + +// Succeed only if fd is bound to a directory stream in the descriptor table +static bool fd_to_directory_stream(int fd, filesystem_borrow_directory_entry_stream_t* result_stream, + filesystem_borrow_descriptor_t* result_fd) { + descriptor_table_entry_t *entry = 0; + if (!descriptor_table_get_ref(fd, &entry)) + return false; + if (entry->tag != DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM) { + return false; + } + *result_stream = filesystem_borrow_directory_entry_stream(entry->directory_stream_info.directory_stream); + *result_fd = entry->directory_stream_info.directory_file_handle; + return true; +} + +#endif + +#endif diff --git a/libc-bottom-half/headers/public/__header_time.h b/libc-bottom-half/headers/public/__header_time.h index 2a2ff91d8..74399b70a 100644 --- a/libc-bottom-half/headers/public/__header_time.h +++ b/libc-bottom-half/headers/public/__header_time.h @@ -10,7 +10,11 @@ #include <__struct_tm.h> #include <__typedef_clockid_t.h> +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif #define TIMER_ABSTIME __WASI_SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME diff --git a/libc-bottom-half/headers/public/wasi/libc.h b/libc-bottom-half/headers/public/wasi/libc.h index e280f0e42..abd2040cd 100644 --- a/libc-bottom-half/headers/public/wasi/libc.h +++ b/libc-bottom-half/headers/public/wasi/libc.h @@ -4,6 +4,9 @@ #include <__typedef_off_t.h> #include <__struct_timespec.h> #include +#ifdef __wasilibc_use_wasip2 +#include +#endif #ifdef __cplusplus extern "C" { @@ -19,9 +22,13 @@ struct timespec; void __wasilibc_populate_preopens(void); /// Register the given pre-opened file descriptor under the given path. -/// -/// This function does not take ownership of `prefix` (it makes its own copy). -int __wasilibc_register_preopened_fd(int fd, const char *prefix); +#ifdef __wasilibc_use_wasip2 +int __wasilibc_register_preopened_fd(filesystem_preopens_own_descriptor_t fd, + wasip2_string_t relprefix); +#else +int __wasilibc_register_preopened_fd(int fd, + const char* prefix); +#endif /// Renumber `fd` to `newfd`; similar to `dup2` but does a move rather than a /// copy. diff --git a/libc-bottom-half/headers/public/wasi/wasip2.h b/libc-bottom-half/headers/public/wasi/wasip2.h index df21eab4d..2428f7e79 100644 --- a/libc-bottom-half/headers/public/wasi/wasip2.h +++ b/libc-bottom-half/headers/public/wasi/wasip2.h @@ -1066,6 +1066,10 @@ typedef struct { uint64_t f1; } wasip2_tuple2_u64_u64_t; +typedef struct { + bool is_err; +} exports_wasi_cli_run_result_void_void_t; + // Imported Functions from `wasi:cli/environment@0.2.0` // Get the POSIX-style environment variables. // @@ -2254,6 +2258,9 @@ extern uint64_t random_insecure_get_insecure_random_u64(void); // protection. extern void random_insecure_seed_insecure_seed(wasip2_tuple2_u64_u64_t *ret); +// Exported Functions from `wasi:cli/run@0.2.0` +bool exports_wasi_cli_run_run(void); + // Helper Functions void wasip2_tuple2_string_string_free(wasip2_tuple2_string_string_t *ptr); @@ -2472,6 +2479,8 @@ void ip_name_lookup_option_ip_address_free(ip_name_lookup_option_ip_address_t *p void ip_name_lookup_result_option_ip_address_error_code_free(ip_name_lookup_result_option_ip_address_error_code_t *ptr); +void exports_wasi_cli_run_result_void_void_free(exports_wasi_cli_run_result_void_void_t *ptr); + // Transfers ownership of `s` into the string `ret` void wasip2_string_set(wasip2_string_t *ret, char*s); diff --git a/libc-bottom-half/sources/__main_void.c b/libc-bottom-half/sources/__main_void.c index c40241fa3..9b6e829c5 100644 --- a/libc-bottom-half/sources/__main_void.c +++ b/libc-bottom-half/sources/__main_void.c @@ -1,5 +1,6 @@ #ifdef __wasilibc_use_wasip2 #include +#include #else #include #endif @@ -108,3 +109,11 @@ int __main_void(void) { return __main_argc_argv(argc, argv); #endif } + +#ifdef __wasilibc_use_wasip2 +bool exports_wasi_cli_run_run(void) { + // TODO: this is supposed to be unnecessary, but functional/env.c fails without it + __wasilibc_initialize_environ(); + return __main_void() == 0; +} +#endif diff --git a/libc-bottom-half/sources/__wasilibc_fd_renumber.c b/libc-bottom-half/sources/__wasilibc_fd_renumber.c index 7690d1359..ef3143086 100644 --- a/libc-bottom-half/sources/__wasilibc_fd_renumber.c +++ b/libc-bottom-half/sources/__wasilibc_fd_renumber.c @@ -1,4 +1,8 @@ +#ifdef __wasilibc_use_wasip2 +#include +#else #include +#endif #include #include #include @@ -7,16 +11,27 @@ int __wasilibc_fd_renumber(int fd, int newfd) { // Scan the preopen fds before making any changes. __wasilibc_populate_preopens(); +#ifdef __wasilibc_use_wasip2 + descriptor_table_entry_t* entry; + if (!descriptor_table_get_ref(fd, &entry)) { + errno = EBADF; + return -1; + } + if (!descriptor_table_update(newfd, *entry)) { + errno = EBADF; + return -1; + } +#else __wasi_errno_t error = __wasi_fd_renumber(fd, newfd); if (error != 0) { errno = error; return -1; } +#endif return 0; } #ifdef __wasilibc_use_wasip2 -#include void drop_tcp_socket(tcp_socket_t socket) { switch (socket.state.tag) { @@ -69,6 +84,15 @@ void drop_udp_socket(udp_socket_t socket) { poll_pollable_drop_own(socket.socket_pollable); udp_udp_socket_drop_own(socket.socket); } + +void drop_file_handle(filesystem_borrow_descriptor_t handle) { + filesystem_descriptor_drop_borrow(handle); +} + +void drop_directory_stream(filesystem_own_directory_entry_stream_t directory_stream) { + filesystem_directory_entry_stream_drop_own(directory_stream); +} + #endif // __wasilibc_use_wasip2 int close(int fd) { @@ -87,18 +111,31 @@ int close(int fd) { case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET: drop_udp_socket(entry.udp_socket); break; + case DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE: + drop_file_handle(entry.file.file_handle); + break; + case DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM: + drop_directory_stream(entry.directory_stream_info.directory_stream); + break; + case DESCRIPTOR_TABLE_ENTRY_FILE_STREAM: + if (entry.stream.file_info.readable) + streams_input_stream_drop_borrow(entry.stream.read_stream); + if (entry.stream.file_info.writable) + streams_output_stream_drop_borrow(entry.stream.write_stream); + drop_file_handle(entry.stream.file_info.file_handle); + break; default: /* unreachable */ abort(); } - + return 0; } -#endif // __wasilibc_use_wasip2 - +#else __wasi_errno_t error = __wasi_fd_close(fd); if (error != 0) { errno = error; return -1; } +#endif // __wasilibc_use_wasip2 return 0; } diff --git a/libc-bottom-half/sources/__wasilibc_real.c b/libc-bottom-half/sources/__wasilibc_real.c index 186de0183..d2f5dbbd2 100644 --- a/libc-bottom-half/sources/__wasilibc_real.c +++ b/libc-bottom-half/sources/__wasilibc_real.c @@ -10,6 +10,8 @@ * must be modified to change this file. */ +#ifndef __wasilibc_use_wasip2 + #include #include @@ -669,3 +671,4 @@ int32_t __wasi_thread_spawn(void* start_arg) { return __imported_wasi_thread_spawn((int32_t) start_arg); } #endif +#endif // __wasilibc_use_wasip2 diff --git a/libc-bottom-half/sources/__wasilibc_rmdirat.c b/libc-bottom-half/sources/__wasilibc_rmdirat.c index b2b906aa6..b60d5707b 100644 --- a/libc-bottom-half/sources/__wasilibc_rmdirat.c +++ b/libc-bottom-half/sources/__wasilibc_rmdirat.c @@ -1,12 +1,50 @@ +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include int __wasilibc_nocwd___wasilibc_rmdirat(int fd, const char *path) { +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + descriptor_table_entry_t *entry; + bool ref_exists = descriptor_table_get_ref(fd, &entry); + filesystem_borrow_descriptor_t file_handle; + if (!ref_exists) { + errno = EBADF; + return EBADF; + } + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_DIRECTORY_STREAM) + file_handle = entry->directory_stream_info.directory_file_handle; + else if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE) + file_handle = entry->file.file_handle; + else { + errno = EINVAL; + return EINVAL; + } + + // Create a WASI string for the path + wasip2_string_t wasi_path; + wasip2_string_dup(&wasi_path, path); + filesystem_error_code_t error_code; + + // Remove the directory + bool ok = filesystem_method_descriptor_remove_directory_at(file_handle, &wasi_path, &error_code); + wasip2_string_free(&wasi_path); + if (!ok) { + translate_error(error_code); + return -1; + } +#else __wasi_errno_t error = __wasi_path_remove_directory(fd, path); if (error != 0) { errno = error; return -1; } +#endif return 0; } diff --git a/libc-bottom-half/sources/__wasilibc_tell.c b/libc-bottom-half/sources/__wasilibc_tell.c index 358d0ca37..79e658189 100644 --- a/libc-bottom-half/sources/__wasilibc_tell.c +++ b/libc-bottom-half/sources/__wasilibc_tell.c @@ -1,7 +1,25 @@ +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include off_t __wasilibc_tell(int fildes) { +#ifdef __wasilibc_use_wasip2 + // Look up a stream for fildes + descriptor_table_entry_t *entry; + descriptor_table_get_ref(fildes, &entry); + + // Return the current offset in the stream + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_STREAM) { + return entry->stream.offset; + } + errno = EINVAL; + return -1; +#else __wasi_filesize_t offset; __wasi_errno_t error = __wasi_fd_tell(fildes, &offset); if (error != 0) { @@ -11,4 +29,5 @@ off_t __wasilibc_tell(int fildes) { return -1; } return offset; +#endif } diff --git a/libc-bottom-half/sources/__wasilibc_unlinkat.c b/libc-bottom-half/sources/__wasilibc_unlinkat.c index 8b4f6b5ce..deb67a3b6 100644 --- a/libc-bottom-half/sources/__wasilibc_unlinkat.c +++ b/libc-bottom-half/sources/__wasilibc_unlinkat.c @@ -1,12 +1,55 @@ +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include #include int __wasilibc_nocwd___wasilibc_unlinkat(int fd, const char *path) { +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor to an internal handle + descriptor_table_entry_t* entry = 0; + descriptor_table_get_ref(fd, &entry); + filesystem_borrow_descriptor_t file_handle; + + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE) { + file_handle = entry->file.file_handle; + } else if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_STREAM) { + file_handle = entry->stream.file_info.file_handle; + if (entry->stream.file_info.readable) + streams_input_stream_drop_borrow(entry->stream.read_stream); + if (entry->stream.file_info.writable) + streams_output_stream_drop_borrow(entry->stream.write_stream); + } else { + errno = __WASI_ERRNO_BADF; + return -1; + } + + // Create a Wasm string from the path + wasip2_string_t wasi_path; + wasip2_string_dup(&wasi_path, path); + + // Unlink the file + filesystem_error_code_t error_code; + bool ok = filesystem_method_descriptor_unlink_file_at(entry->file.file_handle, + &wasi_path, + &error_code); + wasip2_string_free(&wasi_path); + if (!ok) { + translate_error(error_code); + return -1; + } + + return 0; +#else __wasi_errno_t error = __wasi_path_unlink_file(fd, path); if (error != 0) { errno = error; return -1; } return 0; +#endif } diff --git a/libc-bottom-half/sources/descriptor_table.c b/libc-bottom-half/sources/descriptor_table.c index d45e7ce58..ce95194e3 100644 --- a/libc-bottom-half/sources/descriptor_table.c +++ b/libc-bottom-half/sources/descriptor_table.c @@ -24,24 +24,6 @@ #include -__attribute__((__import_module__("wasi_snapshot_preview1"), - __import_name__("adapter_open_badfd"))) extern int32_t - __wasi_preview1_adapter_open_badfd(int32_t); - -static bool wasi_preview1_adapter_open_badfd(int *fd) -{ - return __wasi_preview1_adapter_open_badfd((int32_t)fd) == 0; -} - -__attribute__((__import_module__("wasi_snapshot_preview1"), - __import_name__("adapter_close_badfd"))) extern int32_t - __wasi_preview1_adapter_close_badfd(int32_t); - -static bool wasi_preview1_adapter_close_badfd(int fd) -{ - return __wasi_preview1_adapter_close_badfd(fd) == 0; -} - /* * This hash table is based on the one in musl/src/search/hsearch.c, but uses * integer keys and supports a `remove` operation. Note that I've switched from @@ -69,6 +51,8 @@ static descriptor_table_t global_table = { .entries = NULL, .mask = 0, .used = 0 }; +static int next_fd = 3; + static size_t keyhash(int key) { // TODO: use a hash function here @@ -123,7 +107,8 @@ static descriptor_table_item_t *lookup(int key, size_t hash, } static bool insert(descriptor_table_entry_t entry, int fd, - descriptor_table_t *table) + descriptor_table_t *table, + bool overwrite) { if (!table->entries) { if (!resize(MINSIZE, table)) { @@ -135,7 +120,7 @@ static bool insert(descriptor_table_entry_t entry, int fd, descriptor_table_item_t *e = lookup(fd, hash, table); e->entry = entry; - if (!e->occupied) { + if (!e->occupied || overwrite) { e->key = fd; e->occupied = true; if (++table->used > table->mask - table->mask / 4) { @@ -222,19 +207,8 @@ static bool remove(int fd, descriptor_table_entry_t *entry, bool descriptor_table_insert(descriptor_table_entry_t entry, int *fd) { - if (wasi_preview1_adapter_open_badfd(fd)) { - if (insert(entry, *fd, &global_table)) { - return true; - } else { - if (!wasi_preview1_adapter_close_badfd(*fd)) { - abort(); - } - *fd = -1; - return false; - } - } else { - return false; - } + *fd = ++next_fd; + return insert(entry, *fd, &global_table, false); } bool descriptor_table_get_ref(int fd, descriptor_table_entry_t **entry) @@ -242,14 +216,14 @@ bool descriptor_table_get_ref(int fd, descriptor_table_entry_t **entry) return get(fd, entry, &global_table); } +bool descriptor_table_update(int fd, descriptor_table_entry_t entry) { + if (!global_table.entries) + return false; + + return insert(entry, fd, &global_table, true); +} + bool descriptor_table_remove(int fd, descriptor_table_entry_t *entry) { - if (remove(fd, entry, &global_table)) { - if (!wasi_preview1_adapter_close_badfd(fd)) { - abort(); - } - return true; - } else { - return false; - } + return remove(fd, entry, &global_table); } diff --git a/libc-bottom-half/sources/isatty.c b/libc-bottom-half/sources/isatty.c index c6f866281..a7f14b8fb 100644 --- a/libc-bottom-half/sources/isatty.c +++ b/libc-bottom-half/sources/isatty.c @@ -1,8 +1,92 @@ +#ifdef __wasilibc_use_wasip2 +#include +#include +#include +#else #include +#endif #include <__errno.h> #include <__function___isatty.h> int __isatty(int fd) { +#ifdef __wasilibc_use_wasip2 + // Translate the file descriptor into an internal handle + filesystem_borrow_descriptor_t file_handle; + bool ok = fd_to_file_handle_allow_open(fd, &file_handle); + if (!ok) { + errno = EBADF; + return 0; + } + + // Stat the file to determine if it's a tty + filesystem_descriptor_stat_t statbuf; + filesystem_error_code_t error_code; + ok = filesystem_method_descriptor_stat(file_handle, &statbuf, &error_code); + if (!ok) { + translate_error(error_code); + return 0; + } + + if (statbuf.type != FILESYSTEM_DESCRIPTOR_TYPE_CHARACTER_DEVICE) { + errno = ENOTTY; + return 0; + } + + // To check if seeking/telling is allowed, try to open a stream. + // If a stream is already open, then seeking/telling is allowed. + descriptor_table_entry_t *entry = 0; + descriptor_table_get_ref(fd, &entry); + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_STREAM) { + errno = ENOTTY; + return 0; + } + + if (entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE) { + // Try opening an input stream at a non-zero offset + filesystem_own_input_stream_t input_stream; + bool ok = filesystem_method_descriptor_read_via_stream(file_handle, + 1, + &input_stream, + &error_code); + // If this succeeded, the file descriptor is not a tty + if (ok) { + streams_input_stream_drop_own(input_stream); + errno = ENOTTY; + return 0; + } + + // Check the error code + if (error_code == FILESYSTEM_ERROR_CODE_NO_TTY) + return 1; + + // Otherwise, the file might be write-only, so try opening an output stream + filesystem_own_output_stream_t output_stream; + ok = filesystem_method_descriptor_write_via_stream(file_handle, + 1, + &output_stream, + &error_code); + streams_input_stream_drop_own(input_stream); + streams_output_stream_drop_own(output_stream); + + // If that succeeded, the file descriptor is not a tty + if (ok) { + errno = ENOTTY; + return 0; + } + + // Check the error code + if (error_code == FILESYSTEM_ERROR_CODE_NO_TTY) + return 1; + + // The error code was something else; assume it's not a tty + translate_error(error_code); + return 0; + } + + errno = EBADF; + return 0; +#else + __wasi_fdstat_t statbuf; int r = __wasi_fd_fdstat_get(fd, &statbuf); if (r != 0) { @@ -18,5 +102,11 @@ int __isatty(int fd) { } return 1; +#endif } +#ifdef __wasilibc_use_wasip2 +weak_alias(__isatty, isatty); +#else extern __typeof(__isatty) isatty __attribute__((weak, alias("__isatty"))); +#endif + diff --git a/libc-bottom-half/sources/poll-wasip2.c b/libc-bottom-half/sources/poll-wasip2.c index be7809c66..644091722 100644 --- a/libc-bottom-half/sources/poll-wasip2.c +++ b/libc-bottom-half/sources/poll-wasip2.c @@ -141,7 +141,42 @@ int poll_wasip2(struct pollfd *fds, size_t nfds, int timeout) } break; } - + case DESCRIPTOR_TABLE_ENTRY_FILE_STREAM: { + file_stream_t stream = entry->stream; + if ((pollfd->events & POLLRDNORM) != 0) { + if (!stream.file_info.readable) { + errno = EBADF; + return -1; + } + streams_own_pollable_t input_stream_pollable = + streams_method_input_stream_subscribe(stream.read_stream); + states[state_index++] = (state_t) { + .pollable = input_stream_pollable, + .pollfd = pollfd, + .entry = entry, + .events = pollfd->events + }; + } + if ((pollfd->events & POLLWRNORM) != 0) { + if (!stream.file_info.writable) { + errno = EBADF; + return -1; + } + streams_own_pollable_t output_stream_pollable = + streams_method_output_stream_subscribe(stream.write_stream); + states[state_index++] = (state_t){ + .pollable = output_stream_pollable, + .pollfd = pollfd, + .entry = entry, + .events = pollfd->events + }; + } + break; + } + // File must be open + case DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE: + errno = EBADF; + return -1; default: errno = ENOTSUP; return -1; @@ -238,7 +273,14 @@ int poll_wasip2(struct pollfd *fds, size_t nfds, int timeout) } state->pollfd->revents |= state->events; } - } else { + } else if (state->entry->tag == DESCRIPTOR_TABLE_ENTRY_FILE_STREAM) { + poll_pollable_drop_own(state->pollable); + if (state->pollfd->revents == 0) { + ++event_count; + } + state->pollfd->revents |= state->events; + } + else { if (state->pollfd->revents == 0) { ++event_count; } diff --git a/libc-bottom-half/sources/preopens.c b/libc-bottom-half/sources/preopens.c index ebc7e4070..f237cb44c 100644 --- a/libc-bottom-half/sources/preopens.c +++ b/libc-bottom-half/sources/preopens.c @@ -11,10 +11,287 @@ #include #include #include +#ifdef __wasilibc_use_wasip2 +#include +#include +#else #include +#endif #include #include +#ifdef __wasilibc_use_wasip2 +/// A name and file descriptor pair. +typedef struct preopen { + /// The path prefix associated with the file descriptor. + wasip2_string_t prefix; + + /// The file descriptor. + filesystem_preopens_own_descriptor_t fd; +} preopen; + +/// A simple growable array of `preopen`. +static _Atomic _Bool preopens_populated = false; +static preopen *preopens; +static size_t num_preopens; +static size_t preopen_capacity; + +/// Access to the the above preopen must be protected in the presence of +/// threads. +#ifdef _REENTRANT +static volatile int lock[1]; +#endif + +#ifdef NDEBUG +#define assert_invariants() // assertions disabled +#else +static void assert_invariants(void) { + assert(num_preopens <= preopen_capacity); + assert(preopen_capacity == 0 || preopens != NULL); + assert(preopen_capacity == 0 || + preopen_capacity * sizeof(preopen) > preopen_capacity); + + for (size_t i = 0; i < num_preopens; ++i) { + const preopen *pre = &preopens[i]; + } +} +#endif + +/// Allocate space for more preopens. Returns 0 on success and -1 on failure. +static int resize(void) { + size_t start_capacity = 4; + size_t old_capacity = preopen_capacity; + size_t new_capacity = old_capacity == 0 ? start_capacity : old_capacity * 2; + + preopen *old_preopens = preopens; + preopen *new_preopens = calloc(sizeof(preopen), new_capacity); + if (new_preopens == NULL) { + return -1; + } + + memcpy(new_preopens, old_preopens, num_preopens * sizeof(preopen)); + preopens = new_preopens; + preopen_capacity = new_capacity; + free(old_preopens); + + assert_invariants(); + return 0; +} + +// Normalize an absolute path. Removes leading `/` and leading `./`, so the +// first character is the start of a directory name. This works because our +// process always starts with a working directory of `/`. Additionally translate +// `.` to the empty string. +static const char *strip_prefixes(const char *path) { + while (1) { + if (path[0] == '/') { + path++; + } else if (path[0] == '.' && path[1] == '/') { + path += 2; + } else if (path[0] == '.' && path[1] == 0) { + path++; + } else { + break; + } + } + + return path; +} + +/// Similar to `internal_register_preopened_fd` but does not take a lock. +static int internal_register_preopened_fd_unlocked(filesystem_preopens_own_descriptor_t fd, + wasip2_string_t relprefix) { + // Check preconditions. + assert_invariants(); + + if (num_preopens == preopen_capacity && resize() != 0) { + return -1; + } + + preopens[num_preopens++] = (preopen) { relprefix, fd, }; + + assert_invariants(); + return 0; +} + +/// Register the given preopened file descriptor under the given path. +/// +/// This function takes ownership of `prefix`. +static int internal_register_preopened_fd(filesystem_preopens_own_descriptor_t fd, + wasip2_string_t relprefix) { + LOCK(lock); + + int r = internal_register_preopened_fd_unlocked(fd, relprefix); + + UNLOCK(lock); + + return r; +} + +/// Are the `prefix_len` bytes pointed to by `prefix` a prefix of `path`? +static bool prefix_matches(const uint8_t *prefix, size_t prefix_len, const char *path) { + // Allow an empty string as a prefix of any relative path. + if (path[0] != '/' /* && prefix_len == 0 */) + return true; + + // Check whether any bytes of the prefix differ. + if (memcmp(path, prefix, prefix_len) != 0) + return false; + + // Ignore trailing slashes in directory names. + size_t i = prefix_len; + while (i > 0 && prefix[i - 1] == '/') { + --i; + } + + // Match only complete path components. + char last = path[i]; + return last == '/' || last == '\0'; +} + +// See the documentation in libc.h +int __wasilibc_register_preopened_fd(filesystem_preopens_own_descriptor_t fd, + wasip2_string_t prefix) { + __wasilibc_populate_preopens(); + + return internal_register_preopened_fd(fd, prefix); +} + +// See the documentation in libc-find-relpath.h. +int __wasilibc_find_relpath(const char *path, + const char **abs_prefix, + char **relative_path, + size_t relative_path_len) { + // If `chdir` is linked, whose object file defines this symbol, then we + // call that. Otherwise if the program can't `chdir` then `path` is + // absolute (or relative to the root dir), so we delegate to `find_abspath` + if (__wasilibc_find_relpath_alloc) + return __wasilibc_find_relpath_alloc(path, abs_prefix, relative_path, &relative_path_len, 0); + return __wasilibc_find_abspath(path, abs_prefix, (const char**) relative_path); +} + +// See the documentation in libc-find-relpath.h. +int __wasilibc_find_abspath(const char *path, + const char **abs_prefix, + const char **relative_path) { + __wasilibc_populate_preopens(); + + // Strip leading `/` characters, the prefixes we're matching won't have + // them. + while (*path == '/') + path++; + // Search through the preopens table. Iterate in reverse so that more + // recently added preopens take precedence over less recently addded ones. + size_t match_len = 0; + bool found_handle = false; + filesystem_preopens_own_descriptor_t handle; + LOCK(lock); + for (size_t i = num_preopens; i > 0; --i) { + const preopen *pre = &preopens[i - 1]; + const uint8_t *prefix = pre->prefix.ptr; + size_t len = pre->prefix.len; + + // If we haven't had a match yet, or the candidate path is longer than + // our current best match's path, and the candidate path is a prefix of + // the requested path, take that as the new best path. + if ((!found_handle || len > match_len) && + prefix_matches(prefix, len, path)) + { + found_handle = true; + handle = pre->fd; + match_len = len; + *abs_prefix = (const char*) prefix; + } + } + UNLOCK(lock); + + if (!found_handle) { + errno = ENOENT; + return -1; + } + + // The relative path is the substring after the portion that was matched. + const char *computed = path; + if (path[0] == '/') + computed = path + match_len; + + // Omit leading slashes in the relative path. + while (*computed == '/') + ++computed; + + // *at syscalls don't accept empty relative paths, so use "." instead. + if (*computed == '\0') + computed = "."; + + *relative_path = computed; + int fd = -1; + descriptor_table_entry_t entry; + entry.tag = DESCRIPTOR_TABLE_ENTRY_FILE_HANDLE; + entry.file.readable = true; + entry.file.writable = true; + entry.file.file_handle = filesystem_borrow_descriptor(handle); + if (!descriptor_table_insert(entry, &fd)) + return -1; + return fd; +} + +void __wasilibc_populate_preopens(void) { + // Fast path: If the preopens are already initialized, do nothing. + if (preopens_populated) { + return; + } + + LOCK(lock); + + // Check whether another thread initialized the preopens already. + if (preopens_populated) { + UNLOCK(lock); + return; + } + + filesystem_preopens_list_tuple2_own_descriptor_string_t preopens; + filesystem_preopens_get_directories(&preopens); + + for (size_t i = 0; i < preopens.len; ++i) { + filesystem_preopens_tuple2_own_descriptor_string_t name_and_descriptor = preopens.ptr[i]; + if (internal_register_preopened_fd_unlocked(name_and_descriptor.f0, + name_and_descriptor.f1)) { + goto software; + } + } + + // Preopens are now initialized. + preopens_populated = true; + + UNLOCK(lock); + + return; +// oserr: +// _Exit(EX_OSERR); +software: + _Exit(EX_SOFTWARE); +} + +void __wasilibc_reset_preopens(void) { + LOCK(lock); + + if (num_preopens) { + for (int i = 0; i < num_preopens; ++i) { + wasip2_string_free(&preopens[i].prefix); + } + free(preopens); + } + + preopens_populated = false; + preopens = NULL; + num_preopens = 0; + preopen_capacity = 0; + + assert_invariants(); + + UNLOCK(lock); +} +#else /// A name and file descriptor pair. typedef struct preopen { /// The path prefix associated with the file descriptor. @@ -306,3 +583,4 @@ void __wasilibc_reset_preopens(void) { UNLOCK(lock); } +#endif diff --git a/libc-bottom-half/sources/wasip2.c b/libc-bottom-half/sources/wasip2.c index b81d58134..cd8e2d28b 100644 --- a/libc-bottom-half/sources/wasip2.c +++ b/libc-bottom-half/sources/wasip2.c @@ -1013,6 +1013,11 @@ void ip_name_lookup_result_option_ip_address_error_code_free(ip_name_lookup_resu } } +void exports_wasi_cli_run_result_void_void_free(exports_wasi_cli_run_result_void_void_t *ptr) { + if (!ptr->is_err) { + } +} + void wasip2_string_set(wasip2_string_t *ret, char*s) { ret->ptr = (uint8_t*) s; ret->len = strlen(s); @@ -1071,7 +1076,7 @@ bool environment_initial_cwd(wasip2_string_t *ret) { return option.is_some; } -_Noreturn void exit_exit(exit_result_void_void_t *status) { +void exit_exit(exit_result_void_void_t *status) { int32_t result; if ((*status).is_err) { result = 1; @@ -4323,6 +4328,19 @@ void random_insecure_seed_insecure_seed(wasip2_tuple2_u64_u64_t *ret) { }; } +__attribute__((__export_name__("wasi:cli/run@0.2.0#run"))) +int32_t __wasm_export_exports_wasi_cli_run_run(void) { + exports_wasi_cli_run_result_void_void_t ret; + ret.is_err = !exports_wasi_cli_run_run(); + int32_t result; + if ((ret).is_err) { + result = 1; + } else { + result = 0; + } + return result; +} + extern void __component_type_object_force_link_wasip2(void); void __component_type_object_force_link_wasip2_public_use_in_this_compilation_unit(void) { __component_type_object_force_link_wasip2(); diff --git a/libc-bottom-half/sources/wasip2_component_type.o b/libc-bottom-half/sources/wasip2_component_type.o index 0548e3e97..ec0b2468b 100644 Binary files a/libc-bottom-half/sources/wasip2_component_type.o and b/libc-bottom-half/sources/wasip2_component_type.o differ diff --git a/libc-top-half/musl/include/fcntl.h b/libc-top-half/musl/include/fcntl.h index eef21db1d..d390d18ce 100644 --- a/libc-top-half/musl/include/fcntl.h +++ b/libc-top-half/musl/include/fcntl.h @@ -3,6 +3,7 @@ #ifdef __wasilibc_unmodified_upstream /* Use alternate WASI libc headers */ #else +#include <__wasi_snapshot.h> #include <__header_fcntl.h> #endif #ifdef __cplusplus diff --git a/libc-top-half/musl/include/stdio.h b/libc-top-half/musl/include/stdio.h index d63d739f0..2a2fe0d5d 100644 --- a/libc-top-half/musl/include/stdio.h +++ b/libc-top-half/musl/include/stdio.h @@ -5,6 +5,7 @@ extern "C" { #endif +#include <__wasi_snapshot.h> #include #define __NEED_FILE diff --git a/libc-top-half/musl/src/stdio/fputs.c b/libc-top-half/musl/src/stdio/fputs.c index 1cf344f28..7b092534b 100644 --- a/libc-top-half/musl/src/stdio/fputs.c +++ b/libc-top-half/musl/src/stdio/fputs.c @@ -4,7 +4,7 @@ int fputs(const char *restrict s, FILE *restrict f) { size_t l = strlen(s); - return (fwrite(s, 1, l, f)==l) - 1; + return (fwrite(s, 1, l, f)==l) - 1; } weak_alias(fputs, fputs_unlocked); diff --git a/libc-top-half/musl/src/stdio/fseek.c b/libc-top-half/musl/src/stdio/fseek.c index c07f7e952..2794b7a96 100644 --- a/libc-top-half/musl/src/stdio/fseek.c +++ b/libc-top-half/musl/src/stdio/fseek.c @@ -14,7 +14,7 @@ int __fseeko_unlocked(FILE *f, off_t off, int whence) /* Flush write buffer, and report error on failure. */ if (f->wpos != f->wbase) { - f->write(f, 0, 0); + f->write(f, 0, 0); if (!f->wpos) return -1; } @@ -27,7 +27,7 @@ int __fseeko_unlocked(FILE *f, off_t off, int whence) /* If seek succeeded, file is seekable and we discard read buffer. */ f->rpos = f->rend = 0; f->flags &= ~F_EOF; - + return 0; } diff --git a/test/Makefile b/test/Makefile index bc35da774..40f7191fb 100644 --- a/test/Makefile +++ b/test/Makefile @@ -49,8 +49,6 @@ WASMTIME_URL ?= https://github.com/bytecodealliance/wasmtime/releases/download/v WASMTIME = $(abspath $(DOWNDIR)/$(shell basename $(WASMTIME_URL) .tar.xz)/wasmtime) WASM_TOOLS_URL ?= https://github.com/bytecodealliance/wasm-tools/releases/download/v1.224.0/wasm-tools-1.224.0-$(ARCH)-linux.tar.gz WASM_TOOLS = $(DOWNDIR)/$(shell basename $(WASM_TOOLS_URL) .tar.gz)/wasm-tools -ADAPTER_URL ?= https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasi_snapshot_preview1.command.wasm -ADAPTER = $(DOWNDIR)/wasi_snapshot_preview1.command.wasm $(DOWNDIR): @mkdir -p $@ @@ -66,13 +64,10 @@ $(WASM_TOOLS): | $(DOWNDIR) wget --no-clobber --directory-prefix=$(DOWNDIR) $(WASM_TOOLS_URL) tar --extract --file=$(DOWNDIR)/$(shell basename $(WASM_TOOLS_URL)) --directory=$(DOWNDIR)/ -$(ADAPTER): | $(DOWNDIR) - wget --no-clobber --directory-prefix=$(DOWNDIR) $(ADAPTER_URL) - # Target to download all necessary dependencies. TO_DOWNLOAD = $(LIBC_TEST) $(WASMTIME) ifeq ($(TARGET_TRIPLE), wasm32-wasip2) -TO_DOWNLOAD += $(ADAPTER) $(WASM_TOOLS) +TO_DOWNLOAD += $(WASM_TOOLS) endif DOWNLOADED := $(DOWNDIR)/downloaded.stamp $(DOWNLOADED): $(TO_DOWNLOAD) @@ -134,6 +129,10 @@ ifneq ($(findstring -threads,$(TARGET_TRIPLE)),) CFLAGS += -pthread endif +ifeq ($(DEBUG), true) +CFLAGS += -g -O0 +endif + # Handle compiler-rt which is required for tests. This is done by requesting # that the parent directory, the main wasi-libc directory, fetch its compiler-rt # which will create a `resource-dir` argument which we can then add to LDFLAGS @@ -167,7 +166,7 @@ $(OBJDIR)/%.core.wasm: $(OBJDIR)/%.wasm.o $(INFRA_WASM_OBJS) | $(BUILTINS_STAMP) # For wasip2, we include an additional step to wrap up the core module into # a component. $(OBJDIR)/%.component.wasm: $(OBJDIR)/%.core.wasm - $(WASM_TOOLS) component new --adapt $(ADAPTER) $< -o $@ + $(WASM_TOOLS) component new $< -o $@ # Compile each selected test using Clang. Note that failures here are likely # due to a missing `libclang_rt.builtins-wasm32.a` in the Clang lib directory. diff --git a/test/scripts/generate-stubs.sh b/test/scripts/generate-stubs.sh index 6c87a8e3a..09ddee30c 100755 --- a/test/scripts/generate-stubs.sh +++ b/test/scripts/generate-stubs.sh @@ -8,7 +8,7 @@ FROM_DIR="${FROM_DIR:-build/download/libc-test}" TO_DIR="${TO_DIR:-src/libc-test}" # For now, only retrieve the functional tests. -FUNCTIONAL_TESTS=$(find $FROM_DIR/src/math -name '*.c') +FUNCTIONAL_TESTS=$(find $FROM_DIR/src/functional -name '*.c') for from in $FUNCTIONAL_TESTS; do to="${from/"$FROM_DIR/src"/"$TO_DIR"}" diff --git a/test/src/misc/access.c b/test/src/misc/access.c new file mode 100644 index 000000000..da85bdc84 --- /dev/null +++ b/test/src/misc/access.c @@ -0,0 +1,53 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + int fd, dirfd; + int flags = 0; + flags |= O_WRONLY | O_CREAT | O_EXCL; + + TEST((fd = open(tmp, flags, 0200)) > 2); + TEST(write(fd, "hello", 6)==6); + close(fd); + + TEST(access(tmp, F_OK)==0); + TEST(access(tmp, W_OK)==0); + // Even though the file was created as write-only, + // WASI returns 0 because the parent directory + // has read/write permissions + TEST(access(tmp, R_OK)==0); + + TEST((dirfd = open(".", O_RDONLY, 0600)) > 2); + + TEST(faccessat(dirfd, tmp, F_OK, 0)==0); + TEST(faccessat(dirfd, tmp, W_OK, 0)==-1); + TEST(faccessat(dirfd, tmp, R_OK, 0)==0); + + close(dirfd); + + // Test with a closed file descriptor + TEST(faccessat(dirfd, tmp, F_OK, 0)==-1); + + // Test with a nonexistent file + TEST(access("bogus", F_OK)==-1); + + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/clock_nanosleep.c b/test/src/misc/clock_nanosleep.c new file mode 100644 index 000000000..442fe5152 --- /dev/null +++ b/test/src/misc/clock_nanosleep.c @@ -0,0 +1,50 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + // Get the clock resolution + struct timespec clock_resolution; + clock_getres(CLOCK_MONOTONIC, &clock_resolution); + // Not much point running the test if the resolution is >= 1 sec + TEST(clock_resolution.tv_sec==0); + + // Adjust this number if necessary + // In wasip2 we don't get accurate results for sleeping < 1 ms + + struct timespec time_to_sleep = { .tv_sec = 0, + .tv_nsec = 1E6 * clock_resolution.tv_nsec }; + + struct timespec start_time, end_time; + clock_gettime(CLOCK_MONOTONIC, &start_time); + + TEST(clock_nanosleep(CLOCK_MONOTONIC, 0, &time_to_sleep, NULL)==0); + clock_gettime(CLOCK_MONOTONIC, &end_time); + TEST(end_time.tv_sec - start_time.tv_sec <= 1); + + long nanoseconds_elapsed = (end_time.tv_sec - start_time.tv_sec) + - start_time.tv_nsec + + end_time.tv_nsec; + + // Test that the difference between the requested amount of sleep + // and the actual elapsed time is within an acceptable margin + double difference = abs(nanoseconds_elapsed - time_to_sleep.tv_nsec) + / time_to_sleep.tv_nsec; + + // Allow the actual sleep time to be twice as much as the requested time + TEST(difference <= 1); + + return t_status; +} diff --git a/test/src/misc/fadvise.c b/test/src/misc/fadvise.c new file mode 100644 index 000000000..805f7e9df --- /dev/null +++ b/test/src/misc/fadvise.c @@ -0,0 +1,54 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + int fd; + int flags = 0; + + TEST((fd = open(tmp, flags | O_WRONLY | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, "hello", 6)==6); +// Test each possible value + TEST(posix_fadvise(fd, 1, 3, POSIX_FADV_NORMAL)==0); + TEST(posix_fadvise(fd, 2, 4, POSIX_FADV_SEQUENTIAL)==0); + TEST(posix_fadvise(fd, 3, 6, POSIX_FADV_RANDOM)==0); + TEST(posix_fadvise(fd, 0, 1, POSIX_FADV_NOREUSE)==0); + TEST(posix_fadvise(fd, 5, 6, POSIX_FADV_WILLNEED)==0); + TEST(posix_fadvise(fd, 0, 3, POSIX_FADV_DONTNEED)==0); +// Test bad file descriptor + int badfd = fd + 1; + TEST(posix_fadvise(badfd, 0, 0, POSIX_FADV_RANDOM)==EBADF); +// Test out-of-bounds offset (not an error) + TEST(posix_fadvise(fd, 42, 3, POSIX_FADV_NOREUSE)==0); +// Test in-bounds offset with out-of-bounds len (not an error) + TEST(posix_fadvise(fd, 1, 42, POSIX_FADV_WILLNEED)==0); + close(fd); +// Test negative offset and len (error) + TEST(posix_fadvise(fd, -1, 0, POSIX_FADV_WILLNEED)==EINVAL); + TEST(posix_fadvise(fd, 0, -2, POSIX_FADV_WILLNEED)==EINVAL); +// Test that it fails on a directory + TEST(mkdir("t", 0755)==0); + TEST((fd = open("t", flags | O_RDONLY) > 2)); + TEST(posix_fadvise(fd, 1, 42, POSIX_FADV_WILLNEED)==EBADF); + + close(fd); + TEST(unlink(tmp) != -1); + TEST(rmdir("t") != -1); + + return t_status; +} diff --git a/test/src/misc/fallocate.c b/test/src/misc/fallocate.c new file mode 100644 index 000000000..aed15fbbb --- /dev/null +++ b/test/src/misc/fallocate.c @@ -0,0 +1,40 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int fd_size(const char* path) { + struct stat st; + stat(path, &st); + return st.st_size; +} + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + int fd; + int flags = 0; + + TEST((fd = open(tmp, flags | O_WRONLY | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, "hello", 6)==6); + + // This operation is not supported + TEST(posix_fallocate(fd, 6, 5)==ENOTSUP); + + close(fd); + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/fcntl.c b/test/src/misc/fcntl.c new file mode 100644 index 000000000..f640dff3a --- /dev/null +++ b/test/src/misc/fcntl.c @@ -0,0 +1,50 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + int fd; + int flags = 0; + flags |= O_WRONLY | O_CREAT | O_EXCL; + + TEST((fd = open(tmp, flags, 0600)) > 2); + + int returned_flags = fcntl(fd, F_GETFL); + TEST(returned_flags==O_WRONLY); + + // This can only change some GNU-specific flags, + // so for now, just test that setting the same flags + // doesn't change anything + TEST(fcntl(fd, F_SETFL, returned_flags)==0); + returned_flags = fcntl(fd, F_GETFL); + TEST(returned_flags==O_WRONLY); + + // Test with a nonexistent file descriptor + int badfd = fd + 1; + TEST(fcntl(badfd, F_GETFL)==-1); + TEST(fcntl(badfd, F_SETFL, returned_flags)==-1); + + close(fd); + + // Test with a closed file descriptor + TEST(fcntl(fd, F_GETFL)==-1); + + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/fdatasync.c b/test/src/misc/fdatasync.c new file mode 100644 index 000000000..e8f58cf25 --- /dev/null +++ b/test/src/misc/fdatasync.c @@ -0,0 +1,38 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + int fd; + int flags = 0; + + TEST((fd = open(tmp, flags | O_WRONLY | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, "hello", 6)==6); + + // Test for success + TEST(fdatasync(fd)==0); + + // Test with a nonexistent file descriptor + int badfd = fd + 1; + TEST(fdatasync(badfd)==-1); + + close(fd); + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/fdopen.c b/test/src/misc/fdopen.c new file mode 100644 index 000000000..c7e6513b8 --- /dev/null +++ b/test/src/misc/fdopen.c @@ -0,0 +1,41 @@ +//! add-flags.py(RUN): --dir fs::/ + +// Modified from libc to not use mkstemp and to use a relative path + +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + char foo[6]; + int fd; + int flags = 0; + FILE *f; + + TEST((fd = open(tmp, flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, "hello", 6)==6); + TEST(f = fdopen(fd, "rb")); + if (f) { + TEST(ftello(f) == 6); + TEST(fseeko(f, 0, SEEK_SET)==0); + TEST(fgets(foo, sizeof foo, f)); + if (strcmp(foo,"hello") != 0) + t_error("fgets read back: \"%s\"; wanted: \"hello\"\n", foo); + fclose(f); + } + if (fd > 2) + TEST(unlink(tmp) != -1); + return t_status; +} diff --git a/test/src/misc/feof.c b/test/src/misc/feof.c new file mode 100644 index 000000000..35ac07501 --- /dev/null +++ b/test/src/misc/feof.c @@ -0,0 +1,67 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(r, f, x, m) ( \ + errno=0, ((r) = (f)) == (x) || \ + (t_error("%s failed (" m ")\n", #f, r, x, strerror(errno)), 0) ) + +#define TEST_S(s, x, m) ( \ + !strcmp((s),(x)) || \ + (t_error("[%s] != [%s] (%s)\n", s, x, m), 0) ) + +static FILE *make_temp_file() { + const char* path = "temp_file"; + FILE *f = fopen(path, "w+"); + if (f == NULL) { + printf("Error: fopen(%s) failed: %s\n", path, strerror(errno)); + exit(1); + } + return f; +} + +static FILE *writetemp(const char *data) +{ + FILE *f = make_temp_file(); + size_t n = strlen(data); + if (!f) return 0; + if (write(fileno(f), data, n) != n) { + t_error("write: %s\n", strerror(errno)); + fclose(f); + return 0; + } + if (lseek(fileno(f), 0, (SEEK_SET)) != 0) { + t_error("lseek: %s\n", strerror(errno)); + fclose(f); + return 0; + } + return f; +} + +int main(void) +{ + FILE *f; + int i; + + TEST(i, !!(f=writetemp("ab")), 1, "failed to make temp file"); + if (f) { + char c; + c = getc(f); + TEST(i, c, 'a', "%c != %c"); + c = getc(f); + TEST(i, c, 'b', "%c != %c"); + c = getc(f); + TEST(i, c, EOF, "%c != %c"); + TEST(i, !!feof(f), 1, "%d != %d"); + fclose(f); + } + + return t_status; +} diff --git a/test/src/misc/file_permissions.c b/test/src/misc/file_permissions.c new file mode 100644 index 000000000..36b83d192 --- /dev/null +++ b/test/src/misc/file_permissions.c @@ -0,0 +1,40 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + char foo[6]; + int fd; + int flags = 0; + FILE *f; + + TEST((fd = open(tmp, flags | O_WRONLY | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, "hello", 6)==6); + TEST(read(fd, foo, 6)==-1); + errno = 0; + close(fd); + + TEST(f = fdopen(fd, "r")); + if (f) { + TEST(write(fd, "hello", 6)==-1); + errno = 0; + close(fd); + } + if (fd > 2) + TEST(unlink(tmp) != -1); + return t_status; +} diff --git a/test/src/misc/fseek.c b/test/src/misc/fseek.c new file mode 100644 index 000000000..45751d6ed --- /dev/null +++ b/test/src/misc/fseek.c @@ -0,0 +1,96 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(r, f, x, m) ( \ + errno=0, ((r) = (f)) == (x) || \ + (t_error("%s failed (" m ")\n", #f, r, x, strerror(errno)), 0) ) + +#define TEST_S(s, x, m) ( \ + !strcmp((s),(x)) || \ + (t_error("[%s] != [%s] (%s)\n", s, x, m), 0) ) + +static FILE *make_temp_file() { + const char* path = "temp_file"; + FILE *f = fopen(path, "w+"); + if (f == NULL) { + printf("Error: fopen(%s) failed: %s\n", path, strerror(errno)); + exit(1); + } + return f; +} + +static FILE *writetemp(const char *data) +{ + FILE *f = make_temp_file(); + size_t n = strlen(data); + if (!f) return 0; + if (write(fileno(f), data, n) != n) { + t_error("write: %s\n", strerror(errno)); + fclose(f); + return 0; + } + if (lseek(fileno(f), 0, (SEEK_SET)) != 0) { + t_error("lseek: %s\n", strerror(errno)); + fclose(f); + return 0; + } + return f; +} + +int main(void) +{ + FILE *f; + int i; + + TEST(i, !!(f=writetemp("abc")), 1, "failed to make temp file"); + // Test interleaved reads and fseeks + if (f) { + char c; + c = getc(f); + TEST(i, c, 'a', "%c != %c"); + c = getc(f); + TEST(i, c, 'b', "%c != %c"); + c = getc(f); + TEST(i, c, 'c', "%c != %c"); + TEST(i, fseek(f, 1, SEEK_SET), 0, "%d != %d"); + TEST(i, ftell(f), 1, "%d != %d"); + c = getc(f); + // we're at offset 1; should be 'b' + TEST(i, c, 'b', "%c != %c"); + TEST(i, fseek(f, 1, SEEK_SET), 0, "%d != %d"); + TEST(i, ftell(f), 1, "%d != %d"); + TEST(i, fseek(f, 1, SEEK_CUR), 0, "%d != %d"); + TEST(i, ftell(f), 2, "%d != %d"); + c = getc(f); + // we're now at offset 2; should be 'c' + TEST(i, c, 'c', "%c != %c"); + TEST(i, fseek(f, -2, SEEK_END), 0, "%d != %d"); + TEST(i, ftell(f), 1, "%d != %d"); + c = getc(f); + // we're now at offset 1; should be 'b' + TEST(i, c, 'b', "%c != %c"); + + putc('x', f); + TEST(i, ftell(f), 4, "%d != %d"); + putc('y', f); + TEST(i, ftell(f), 5, "%d != %d"); + TEST(i, fseek(f, -1, SEEK_END), 0, "%d != %d"); + TEST(i, ftell(f), 4, "%d != %d"); + c = getc(f); + TEST(i, c, 'y', "%c != %c"); + + fclose(f); + } + + TEST(i, unlink("temp_file"), 0, "%d != %d"); + + return t_status; +} diff --git a/test/src/misc/fsync.c b/test/src/misc/fsync.c new file mode 100644 index 000000000..c9421b47b --- /dev/null +++ b/test/src/misc/fsync.c @@ -0,0 +1,38 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + int fd; + int flags = 0; + + TEST((fd = open(tmp, flags | O_WRONLY | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, "hello", 6)==6); + + // Test for success + TEST(fsync(fd)==0); + + // Test with a nonexistent file descriptor + int badfd = fd + 1; + TEST(fsync(badfd)==-1); + + close(fd); + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/ftruncate.c b/test/src/misc/ftruncate.c new file mode 100644 index 000000000..a7639d2cb --- /dev/null +++ b/test/src/misc/ftruncate.c @@ -0,0 +1,52 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + struct stat st; + int fd; + int flags = 0; + flags |= O_RDWR | O_CREAT | O_EXCL; + + TEST((fd = open(tmp, flags, 0200)) > 2); + TEST(write(fd, "hello world", 11)==11); +// Truncate to a smaller size + TEST(ftruncate(fd, 5)==0); + close(fd); + + TEST(stat(tmp, &st)==0); + TEST(st.st_size==5); + + TEST((fd = open(tmp, O_RDWR, 0200)) > 2); +// Truncate to a larger size + TEST(ftruncate(fd, 11)==0); +// length < 0 is an error + TEST(ftruncate(fd, -1)==-1); + close(fd); + + TEST(stat(tmp, &st)==0); + TEST(st.st_size==11); + +// Nonexistent file descriptor is an error + int badfd = fd + 1; + TEST(ftruncate(badfd, 1)==-1); + + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/fwscanf.c b/test/src/misc/fwscanf.c new file mode 100644 index 000000000..81bc18341 --- /dev/null +++ b/test/src/misc/fwscanf.c @@ -0,0 +1,131 @@ +//! add-flags.py(RUN): --dir fs::/ + +// Modified from libc-test to not use tmpfile + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(r, f, x, m) ( \ + errno=0, ((r) = (f)) == (x) || \ + (t_error("%s failed (" m ")\n", #f, r, x, strerror(errno)), 0) ) + +#define TEST_S(s, x, m) ( \ + !strcmp((s),(x)) || \ + (t_error("[%s] != [%s] (%s)\n", s, x, m), 0) ) + +static FILE *make_temp_file() { + const char* path = "temp_file"; + FILE *f = fopen(path, "w+"); + if (f == NULL) { + printf("Error: fopen(%s) failed: %s\n", path, strerror(errno)); + exit(1); + } + return f; +} + +static FILE *writetemp(const char *data) +{ + FILE *f = make_temp_file(); + size_t n = strlen(data); + if (!f) return 0; + if (write(fileno(f), data, n) != n) { + t_error("write: %s\n", strerror(errno)); + fclose(f); + return 0; + } + if (lseek(fileno(f), 0, SEEK_SET) != 0) { + t_error("lseek: %s\n", strerror(errno)); + fclose(f); + return 0; + } + return f; +} + +int main(void) +{ + int i, x, y; + double u; + char a[100], b[100]; + FILE *f; + + TEST(i, !!(f=writetemp(" 42")), 1, "failed to make temp file"); + if (f) { + x=y=-1; + TEST(i, fwscanf(f, L" %n%*d%n", &x, &y), 0, "%d != %d"); + TEST(i, x, 6, "%d != %d"); + TEST(i, y, 8, "%d != %d"); + TEST(i, ftell(f), 8, "%d != %d"); + TEST(i, !!feof(f), 1, "%d != %d"); + fclose(f); + } + + TEST(i, !!(f=writetemp("[abc123]....x")), 1, "failed to make temp file"); + if (f) { + x=y=-1; + TEST(i, fwscanf(f, L"%10[^]]%n%10[].]%n", a, &x, b, &y), 2, "%d != %d"); + TEST_S(a, "[abc123", "wrong result for %[^]]"); + TEST_S(b, "]....", "wrong result for %[].]"); + TEST(i, x, 7, "%d != %d"); + TEST(i, y, 12, "%d != %d"); + TEST(i, ftell(f), 12, "%d != %d"); + TEST(i, feof(f), 0, "%d != %d"); + TEST(i, fgetwc(f), 'x', "%d != %d"); + fclose(f); + } + + TEST(i, !!(f=writetemp("0x1p 12")), 1, "failed to make temp file"); + if (f) { + x=y=-1; + u=-1; + TEST(i, fwscanf(f, L"%lf%n %d", &u, &x, &y), 0, "%d != %d"); + TEST(u, u, -1.0, "%g != %g"); + TEST(i, x, -1, "%d != %d"); + TEST(i, y, -1, "%d != %d"); + TEST(i, ftell(f), 4, "%d != %d"); + TEST(i, feof(f), 0, "%d != %d"); + TEST(i, fgetwc(f), ' ', "%d != %d"); + rewind(f); + TEST(i, fgetwc(f), '0', "%d != %d"); + TEST(i, fgetwc(f), 'x', "%d != %d"); + TEST(i, fwscanf(f, L"%lf%n%c %d", &u, &x, a, &y), 3, "%d != %d"); + TEST(u, u, 1.0, "%g != %g"); + TEST(i, x, 1, "%d != %d"); + TEST(i, a[0], 'p', "%d != %d"); + TEST(i, y, 12, "%d != %d"); + TEST(i, ftell(f), 7, "%d != %d"); + TEST(i, !!feof(f), 1, "%d != %d"); + fclose(f); + } + + TEST(i, !!(f=writetemp("0x.1p4 012")), 1, "failed to make temp file"); + if (f) { + x=y=-1; + u=-1; + TEST(i, fwscanf(f, L"%lf%n %i", &u, &x, &y), 2, "%d != %d"); + TEST(u, u, 1.0, "%g != %g"); + TEST(i, x, 6, "%d != %d"); + TEST(i, y, 10, "%d != %d"); + TEST(i, ftell(f), 13, "%d != %d"); + TEST(i, !!feof(f), 1, "%d != %d"); + fclose(f); + } + + TEST(i, !!(f=writetemp("0xx")), 1, "failed to make temp file"); + if (f) { + x=y=-1; + TEST(i, fwscanf(f, L"%x%n", &x, &y), 0, "%d != %d"); + TEST(i, x, -1, "%d != %d"); + TEST(i, y, -1, "%d != %d"); + TEST(i, ftell(f), 2, "%d != %d"); + TEST(i, feof(f), 0, "%d != %d"); + fclose(f); + } + + return t_status; +} diff --git a/test/src/misc/ioctl.c b/test/src/misc/ioctl.c new file mode 100644 index 000000000..457a77fd2 --- /dev/null +++ b/test/src/misc/ioctl.c @@ -0,0 +1,41 @@ +//! add-flags.py(RUN): --dir fs::/ --wasi=inherit-network=y + +#include +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + int fd; + int nbytes = 0; + int flags = 0; + flags |= O_RDWR | O_CREAT | O_EXCL; + + TEST((fd = open(tmp, flags, 0600)) > 2); + TEST(write(fd, "hello", 6)==6); + +#ifdef __wasilibc_use_wasip2 + // Not supported on wasip2 + TEST(ioctl(fd, FIONREAD, &nbytes)==-1); +#else + // Always returns 0? + TEST(ioctl(fd, FIONREAD, &nbytes)==0); +#endif + close(fd); + TEST(unlink(tmp)==0); + + return t_status; +} diff --git a/test/src/misc/isatty.c b/test/src/misc/isatty.c new file mode 100644 index 000000000..7ddb69378 --- /dev/null +++ b/test/src/misc/isatty.c @@ -0,0 +1,32 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + int fd; + int flags = 0; + flags |= O_RDWR | O_CREAT | O_EXCL; + + TEST((fd = open(tmp, flags, 0200)) > 2); + TEST(!isatty(fd)); + close(fd); + + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/link.c b/test/src/misc/link.c new file mode 100644 index 000000000..a9dfed377 --- /dev/null +++ b/test/src/misc/link.c @@ -0,0 +1,35 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + char tmp1[] = "testsuite-link"; + int fd; + int flags = 0; + + TEST((fd = open(tmp, flags | O_WRONLY | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, "hello", 6)==6); + close(fd); + + TEST(link(tmp, tmp1)==0); + + TEST(unlink(tmp1) != -1); + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/poll.c b/test/src/misc/poll.c new file mode 100644 index 000000000..17857ca50 --- /dev/null +++ b/test/src/misc/poll.c @@ -0,0 +1,36 @@ +//! add-flags.py(RUN): --dir fs::/ + +// Modified from libc-test to not use tmpfile or /dev/null + +#include +#include +#include +#include +#include "test.h" + +#define TEST(c, ...) ((c) ? 1 : (t_error(#c" failed: " __VA_ARGS__),0)) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + int fd; + int flags = 0; + + // Not a very useful test, but it's hard to test poll() without threads + + TEST((fd = open(tmp, flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, "hello", 6)==6); + + struct pollfd poll_fd = { .fd = fd, .events = POLLRDNORM, .revents = 0 }; + TEST(poll(&poll_fd, 1, 1)==1); + + poll_fd.events = POLLWRNORM; + TEST(poll(&poll_fd, 1, 1)==1); + + close(fd); + + if (fd > 2) + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/preadvwritev.c b/test/src/misc/preadvwritev.c new file mode 100644 index 000000000..c246985f1 --- /dev/null +++ b/test/src/misc/preadvwritev.c @@ -0,0 +1,58 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + int fd; + int flags = 0; + flags |= O_RDWR | O_CREAT | O_EXCL; + + // NOTE: wasip1 pwritev() only writes the first non-empty iov + struct iovec iov; + iov.iov_base = malloc(5); + iov.iov_len = 5; + memcpy(iov.iov_base, "hello", 5); + + TEST((fd = open(tmp, flags, 0600)) > 2); + TEST(pwritev(fd, &iov, 1, 1)==5); + + free(iov.iov_base); + + iov.iov_base = malloc(5); + iov.iov_len = 5; + + TEST(preadv(fd, &iov, 1, 1)==5); + + // Negative offset is an error + TEST(pwritev(fd, &iov, 1, -1)==-1); + TEST(preadv(fd, &iov, 1, -1)==-1); + + // 0-length vector should work + free(iov.iov_base); + iov.iov_len = 0; + iov.iov_base = NULL; + TEST(pwritev(fd, &iov, 1, 0)==0); + TEST(preadv(fd, &iov, 1, 0)==0); + + close(fd); + + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/preadwrite.c b/test/src/misc/preadwrite.c new file mode 100644 index 000000000..f5b4e18f1 --- /dev/null +++ b/test/src/misc/preadwrite.c @@ -0,0 +1,41 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + char foo[6]; + int fd; + int flags = 0; + flags |= O_RDWR | O_CREAT | O_EXCL; + + TEST((fd = open(tmp, flags, 0200)) > 2); + TEST(write(fd, "hello", 6)==6); + TEST(pwrite(fd, "42", 2, 2)==2); + TEST(pread(fd, &foo, 3, 2)==3); + TEST(strcmp(foo, "42o")==0); + + // Negative offset is an error + TEST(pwrite(fd, "q", 1, -1)==-1); + TEST(pread(fd, &foo, 2, -1)==-1); + + close(fd); + + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/readlink.c b/test/src/misc/readlink.c new file mode 100644 index 000000000..5132e40cc --- /dev/null +++ b/test/src/misc/readlink.c @@ -0,0 +1,37 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + char tmp1[] = "testsuite-link"; + int fd; + int flags = 0; + + TEST((fd = open(tmp, flags | O_WRONLY | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, "hello", 6)==6); + close(fd); + + TEST(symlink(tmp, tmp1)==0); + + TEST(readlink(tmp1, tmp, 16)==16); + + TEST(unlink(tmp1) != -1); + TEST(unlink(tmp) != -1); + + return t_status; +} diff --git a/test/src/misc/rename.c b/test/src/misc/rename.c new file mode 100644 index 000000000..cdfb02d72 --- /dev/null +++ b/test/src/misc/rename.c @@ -0,0 +1,49 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "test"; + char tmp1[] = "test1"; + char nonexistent[] = "bogus"; + struct stat st; + int fd; + int flags = 0; + + // Create a directory + TEST(mkdir(tmp, 0755)==0); + // Rename + TEST(rename(tmp, tmp1)==0); + TEST(stat(tmp, &st)!=0); + TEST(stat(tmp1, &st)==0); + TEST(rmdir(tmp1)==0); + + // Create a file + TEST((fd = open(tmp, flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, 0, 1)==1); + close(fd); + // Rename + TEST(rename(tmp, tmp1)==0); + TEST(stat(tmp, &st)!=0); + TEST(stat(tmp1, &st)==0); + TEST(unlink(tmp1)==0); + + // Nonexistent source should fail + TEST(rename(nonexistent, tmp1)==-1); + + return t_status; +} diff --git a/test/src/misc/rmdir.c b/test/src/misc/rmdir.c new file mode 100644 index 000000000..7387fc1dc --- /dev/null +++ b/test/src/misc/rmdir.c @@ -0,0 +1,42 @@ +//! add-flags.py(RUN): --dir fs::/ + +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int main(void) +{ + char tmp[] = "test"; + char nonexistent[] = "bogus"; + + // Create a directory + TEST(mkdir(tmp, 0755)==0); + // Remove with rmdir() + TEST(rmdir(tmp) == 0); + + // Re-create + TEST(mkdir(tmp, 0755)==0); + // Remove with unlink() -- this fails for a directory + TEST(unlink(tmp)==-1); + + // Remove with remove() + TEST(remove(tmp) == 0); + + // Test that it fails with a non-existent directory + TEST(rmdir(nonexistent)==-1); + TEST(unlink(nonexistent)==-1); + TEST(remove(nonexistent)==-1); + + return t_status; +} diff --git a/test/src/misc/scandir.c b/test/src/misc/scandir.c new file mode 100644 index 000000000..bb9daa024 --- /dev/null +++ b/test/src/misc/scandir.c @@ -0,0 +1,59 @@ +//! add-flags.py(RUN): --dir fs::/ + +// Modified from libc to not use mkstemp and to use a relative path + +#include +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c) do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ +} while(0) + +int filter(const struct dirent* dirent) { + const char* name = dirent->d_name; + int is_b = strcmp(name, "b") == 0; + int is_dot = name[0] == '.'; + return !(is_b || is_dot); +} + +int compare(const struct dirent** dirent1, const struct dirent** dirent2) { + return strcmp((*dirent1)->d_name, (*dirent2)->d_name); +} + +int main(void) +{ + char tmp[] = "testsuite-XXXXXX"; + int fd; + int flags = 0; + + // Create the directory + TEST(mkdir("test", 0755)==0); + TEST((fd = open("test/a", flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, 0, 1)==1); + close(fd); + TEST((fd = open("test/b", flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, 0, 1)==1); + close(fd); + TEST((fd = open("test/c", flags | O_RDWR | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(fd, 0, 1)==1); + close(fd); + + struct dirent **namelist; + TEST(scandir("test", &namelist, &filter, &compare)==2); + + TEST(unlink("test/a") != -1); + TEST(unlink("test/b") != -1); + TEST(unlink("test/c") != -1); + TEST(rmdir("test") == 0); + + return t_status; +} diff --git a/test/src/misc/stat.c b/test/src/misc/stat.c new file mode 100644 index 000000000..14c2fc27e --- /dev/null +++ b/test/src/misc/stat.c @@ -0,0 +1,54 @@ +//! add-flags.py(RUN): --dir fs::/ + +// Modified from libc-test to not use tmpfile or /dev/null + +#include +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c, ...) ((c) ? 1 : (t_error(#c" failed: " __VA_ARGS__),0)) + +static FILE *make_temp_file() { + const char* path = "temp_file"; + FILE *f = fopen(path, "w"); + if (f == NULL) { + printf("Error: fopen(%s) failed: %s\n", path, strerror(errno)); + exit(1); + } + return f; +} + +int main(void) +{ + struct stat st; + FILE *f; + time_t t; + + if (TEST(stat(".",&st)==0, "errno = %s\n", strerror(errno))) { + TEST(S_ISDIR(st.st_mode), "\n"); + TEST(st.st_nlink>0, "%ju\n", (uintmax_t)st.st_nlink); + t = time(0); + TEST(st.st_ctime<=t, "%jd > %jd\n", (intmax_t)st.st_ctime, (intmax_t)t); + TEST(st.st_mtime<=t, "%jd > %jd\n", (intmax_t)st.st_mtime, (intmax_t)t); + TEST(st.st_atime<=t, "%jd > %jd\n", (intmax_t)st.st_atime, (intmax_t)t); + } + + if ((f = make_temp_file())) { + size_t fputs_result = fputs("hello", f); + TEST(fputs_result>=0, "fputs_result=%jd\n", fputs_result); + int flush_result = fflush(f); + TEST(flush_result==0, "%jd vs 0, errno=%d errnp = %s\n", flush_result, errno, strerror(errno)); + if (TEST(fstat(fileno(f),&st)==0, "errnp = %s\n", strerror(errno))) { + TEST(st.st_size==5, "%jd vs 5\n", (intmax_t)st.st_size); + } + fclose(f); + } + + return t_status; +} diff --git a/test/src/misc/strptime.c b/test/src/misc/strptime.c new file mode 100644 index 000000000..1e5d54c65 --- /dev/null +++ b/test/src/misc/strptime.c @@ -0,0 +1,124 @@ +// Modified from libc-test (see comments in main() + +// SPDX-License-Identifier: MIT + +#define _GNU_SOURCE /* For tm_gmtoff */ +#include +#include +#include +#include +#include "test.h" + +/** + * checkStrptime - parse time and check if it matches expected value + * + * This function compares time and date fields of tm structure only. + * It's because tm_wday and tm_yday may - but don't have to - be set + * while parsing a date. + */ +static void checkStrptime(const char *s, const char *format, const struct tm *expected) { + struct tm tm = { }; + const char *ret; + + ret = strptime(s, format, &tm); + if (!ret || *ret != '\0') { + t_error("\"%s\": failed to parse \"%s\"\n", format, s); + } else if (tm.tm_sec != expected->tm_sec || + tm.tm_min != expected->tm_min || + tm.tm_hour != expected->tm_hour || + tm.tm_mday != expected->tm_mday || + tm.tm_mon != expected->tm_mon || + tm.tm_year != expected->tm_year) { + char buf1[64]; + char buf2[64]; + + strftime(buf1, sizeof(buf1), "%FT%H:%M:%S%Z", expected); + strftime(buf2, sizeof(buf2), "%FT%H:%M:%S%Z", &tm); + + t_error("\"%s\": for \"%s\" expected %s but got %s\n", format, s, buf1, buf2); + } +} + +static void checkStrptimeTz(const char *s, int h, int m) { + long int expected = h * 3600 + m * 60; + struct tm tm = { }; + const char *ret; + + ret = strptime(s, "%z", &tm); + if (!ret || *ret != '\0') { + t_error("\"%%z\": failed to parse \"%s\"\n", s); + } else if (tm.tm_gmtoff != expected) { + t_error("\"%%z\": for \"%s\" expected tm_gmtoff %ld but got %ld\n", s, tm.tm_gmtoff, expected); + } +} + +static struct tm tm1 = { + .tm_sec = 8, + .tm_min = 57, + .tm_hour = 20, + .tm_mday = 0, + .tm_mon = 0, + .tm_year = 0, +}; + +static struct tm tm2 = { + .tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 25, + .tm_mon = 8 - 1, + .tm_year = 1991 - 1900, +}; + +static struct tm tm3 = { + .tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 21, + .tm_mon = 10 - 1, + .tm_year = 2015 - 1900, +}; + +static struct tm tm4 = { + .tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 10, + .tm_mon = 7 - 1, + .tm_year = 1856 - 1900, +}; + +int main() { + + /* Time */ + checkStrptime("20:57:08", "%H:%M:%S", &tm1); + checkStrptime("20:57:8", "%R:%S", &tm1); + checkStrptime("20:57:08", "%T", &tm1); + + /* Format */ + checkStrptime("20:57:08", "%H : %M : %S", &tm1); + checkStrptime("20 57 08", "%H %M %S", &tm1); + checkStrptime("20%57%08", "%H %% %M%%%S", &tm1); + checkStrptime("foo20bar57qux08 ", "foo %Hbar %M qux%S ", &tm1); + + /* Date */ + checkStrptime("1991-08-25", "%Y-%m-%d", &tm2); + checkStrptime("25.08.91", "%d.%m.%y", &tm2); + checkStrptime("08/25/91", "%D", &tm2); + checkStrptime("21.10.15", "%d.%m.%y", &tm3); + checkStrptime("10.7.56 in 18th", "%d.%m.%y in %C th", &tm4); + + /* Glibc */ +// Non-standard? +/* + checkStrptime("1856-07-10", "%F", &tm4); + checkStrptime("683078400", "%s", &tm2); +*/ +// Time zones not supported in wasip2 +/* + checkStrptimeTz("+0200", 2, 0); + checkStrptimeTz("-0530", -5, -30); + checkStrptimeTz("-06", -6, 0); +*/ + return t_status; +} diff --git a/test/src/misc/time.c b/test/src/misc/time.c new file mode 100644 index 000000000..642c116e1 --- /dev/null +++ b/test/src/misc/time.c @@ -0,0 +1,112 @@ +// Modified to not use tzset() + +#define _XOPEN_SOURCE 700 +#include +#include +#include +#include +#include +#include +#include "test.h" + +/* We use this instead of memcmp because some broken C libraries + * add additional nonstandard fields to struct tm... */ + +int tm_cmp(struct tm tm1, struct tm tm2) +{ + return tm1.tm_sec != tm2.tm_sec || + tm1.tm_min != tm2.tm_min || + tm1.tm_hour != tm2.tm_hour || + tm1.tm_mday != tm2.tm_mday || + tm1.tm_mon != tm2.tm_mon || + tm1.tm_year != tm2.tm_year || + tm1.tm_wday != tm2.tm_wday || + tm1.tm_yday != tm2.tm_yday || + tm1.tm_isdst!= tm2.tm_isdst; +} + +char *tm_str(struct tm tm) +{ + static int i; + static char b[4][64]; + i = (i+1)%4; + snprintf(b[i], sizeof b[i], + "s=%02d m=%02d h=%02d mday=%02d mon=%02d year=%04d wday=%d yday=%d isdst=%d", + tm.tm_sec, tm.tm_min, tm.tm_hour, + tm.tm_mday, tm.tm_mon, tm.tm_year, + tm.tm_wday, tm.tm_yday, tm.tm_isdst); + return b[i]; +} + +#define TM(ss,mm,hh,md,mo,yr,wd,yd,dst) (struct tm){ \ + .tm_sec = ss, .tm_min = mm, .tm_hour = hh, \ + .tm_mday = md, .tm_mon = mo, .tm_year = yr, \ + .tm_wday = wd, .tm_yday = yd, .tm_isdst = dst } + +#define TM_EPOCH TM(0,0,0,1,0,70,4,0,0) +#define TM_Y2038_1S TM(7,14,3,19,0,138,2,18,0) +#define TM_Y2038 TM(8,14,3,19,0,138,2,18,0) + +static void sec2tm(time_t t, char *m) +{ + struct tm *tm; + time_t r; + + errno = 0; + tm = gmtime(&t); + if (errno != 0) + t_error("%s: gmtime((time_t)%lld) should not set errno, got %s\n", + m, (long long)t, strerror(errno)); + errno = 0; + r = mktime(tm); + if (errno != 0) + t_error("%s: mktime(%s) should not set errno, got %s\n", + m, tm_str(*tm), strerror(errno)); + if (t != r) + t_error("%s: mktime(gmtime(%lld)) roundtrip failed: got %lld (gmtime is %s)\n", + m, (long long)t, (long long)r, tm_str(*tm)); +} + +static void tm2sec(struct tm *tm, int big, char *m) +{ + struct tm *r; + time_t t; + int overflow = big && (time_t)LLONG_MAX!=LLONG_MAX; + + errno = 0; + t = mktime(tm); + if (overflow && t != -1) + t_error("%s: mktime(%s) expected -1, got (time_t)%ld\n", + m, tm_str(*tm), (long)t); + if (overflow && errno != EOVERFLOW) + t_error("%s: mktime(%s) expected EOVERFLOW (%s), got (%s)\n", + m, tm_str(*tm), strerror(EOVERFLOW), strerror(errno)); + if (!overflow && t == -1) + t_error("%s: mktime(%s) expected success, got (time_t)-1\n", + m, tm_str(*tm)); + if (!overflow && errno) + t_error("%s: mktime(%s) expected no error, got (%s)\n", + m, tm_str(*tm), strerror(errno)); + r = gmtime(&t); + if (!overflow && tm_cmp(*r, *tm)) + t_error("%s: gmtime(mktime(%s)) roundtrip failed: got %s\n", + m, tm_str(*tm), tm_str(*r)); +} + +int main(void) +{ + time_t t; + + putenv("TZ=GMT"); + // tzset(); + tm2sec(&TM_EPOCH, 0, "gmtime(0)"); + tm2sec(&TM_Y2038_1S, 0, "2038-1s"); + tm2sec(&TM_Y2038, 1, "2038"); + + sec2tm(0, "EPOCH"); + for (t = 1; t < 1000; t++) + sec2tm(t*100003, "EPOCH+eps"); + + /* FIXME: set a TZ var and check DST boundary conditions */ + return t_status; +} diff --git a/test/src/misc/time_and_times.c b/test/src/misc/time_and_times.c new file mode 100644 index 000000000..145cd9088 --- /dev/null +++ b/test/src/misc/time_and_times.c @@ -0,0 +1,28 @@ +//! add-flags.py(CFLAGS): -D_WASI_EMULATED_PROCESS_CLOCKS +//! add-flags.py(LDFLAGS): -lwasi-emulated-process-clocks +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c, ...) ((c) ? 1 : (t_error(#c" failed: " __VA_ARGS__),0)) + +int main(void) +{ + time_t t; + struct tms buffer; + + clock_t result = times(&buffer); + TEST(result > 0); + TEST(buffer.tms_utime > 0); + TEST(buffer.tms_cutime == 0); // always 0 under WASI + + time_t t1 = time(&t); + TEST(t==t1); + TEST(t > 0); + + return t_status; +} diff --git a/test/src/misc/utime.c b/test/src/misc/utime.c new file mode 100644 index 000000000..aefa2cb14 --- /dev/null +++ b/test/src/misc/utime.c @@ -0,0 +1,86 @@ +//! add-flags.py(RUN): --dir fs::/ + +// Modified from libc-test in order to not use tmpfile + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +#define TEST(c, ...) ((c) ? 1 : (t_error(#c" failed: " __VA_ARGS__),0)) +#define TESTVAL(v,op,x) TEST(v op x, "%jd\n", (intmax_t)(v)) + +static FILE *make_temp_file() { + const char* path = "temp_file"; + FILE *f = fopen(path, "w"); + if (f == NULL) { + printf("Error: fopen(%s) failed: %s\n", path, strerror(errno)); + exit(1); + } + return f; +} + +int main(void) +{ + struct stat st; + FILE *f; + int fd; + time_t t; + + TEST(futimens(-1, ((struct timespec[2]){{.tv_nsec=UTIME_OMIT},{.tv_nsec=UTIME_OMIT}}))==0 || errno==EBADF, + "%s\n", strerror(errno)); + + if (!TEST(f = make_temp_file())) return t_status; + fd = fileno(f); + + TEST(futimens(fd, (struct timespec[2]){0}) == 0, "\n"); + TEST(fstat(fd, &st) == 0, "\n"); + TESTVAL(st.st_atim.tv_sec,==,0); + TESTVAL(st.st_atim.tv_nsec,==,0); + TESTVAL(st.st_mtim.tv_sec,==,0); + TESTVAL(st.st_mtim.tv_nsec,==,0); + + TEST(futimens(fd, ((struct timespec[2]){{.tv_sec=1,.tv_nsec=UTIME_OMIT},{.tv_sec=1,.tv_nsec=UTIME_OMIT}})) == 0, "\n"); + TEST(fstat(fd, &st) == 0, "\n"); + TESTVAL(st.st_atim.tv_sec,==,0); + TESTVAL(st.st_atim.tv_nsec,==,0); + TESTVAL(st.st_mtim.tv_sec,==,0); + TESTVAL(st.st_mtim.tv_nsec,==,0); + + t = time(0); + + TEST(futimens(fd, ((struct timespec[2]){{.tv_nsec=UTIME_NOW},{.tv_nsec=UTIME_OMIT}})) == 0, "\n"); + TEST(fstat(fd, &st) == 0, "\n"); + TESTVAL(st.st_atim.tv_sec,>=,t); + TESTVAL(st.st_mtim.tv_sec,==,0); + TESTVAL(st.st_mtim.tv_nsec,==,0); + + TEST(futimens(fd, (struct timespec[2]){0}) == 0, "\n"); + TEST(futimens(fd, ((struct timespec[2]){{.tv_nsec=UTIME_OMIT},{.tv_nsec=UTIME_NOW}})) == 0, "\n"); + TEST(fstat(fd, &st) == 0, "\n"); + TESTVAL(st.st_atim.tv_sec,==,0); + TESTVAL(st.st_mtim.tv_sec,>=,t); + + TEST(futimens(fd, ((struct timespec[2]){{.tv_nsec=UTIME_NOW},{.tv_nsec=UTIME_OMIT}})) == 0, "\n"); + TEST(fstat(fd, &st) == 0, "\n"); + TESTVAL(st.st_atim.tv_sec,>=,t); + TESTVAL(st.st_mtim.tv_sec,>=,t); + + if (TEST((time_t)(1LL<<32) == (1LL<<32), "implementation has Y2038 EOL\n")) { + if (TEST(futimens(fd, ((struct timespec[2]){{.tv_sec=1LL<<32},{.tv_sec=1LL<<32}})) == 0, "%s\n", strerror(errno))) { + TEST(fstat(fd, &st) == 0, "\n"); + TESTVAL(st.st_atim.tv_sec, ==, 1LL<<32); + TESTVAL(st.st_mtim.tv_sec, ==, 1LL<<32); + } + } + + fclose(f); + + return t_status; +}