From bd324228f861d88460d0e20d98b0debe84fe377d Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Wed, 3 May 2023 08:46:36 +0200 Subject: [PATCH 01/11] Reapply changes to 3.1.37 --- system/lib/libc/musl/src/stdio/freopen.c | 105 ++- system/lib/standalone/standalone.c | 1047 +++++++++++++++++++++- 2 files changed, 1106 insertions(+), 46 deletions(-) diff --git a/system/lib/libc/musl/src/stdio/freopen.c b/system/lib/libc/musl/src/stdio/freopen.c index b636a87484efc..1a571af1dcb09 100644 --- a/system/lib/libc/musl/src/stdio/freopen.c +++ b/system/lib/libc/musl/src/stdio/freopen.c @@ -1,6 +1,17 @@ +#ifdef __wasilibc_unmodified_upstream // WASI has no syscall, dup #include "stdio_impl.h" +#else +#include +#endif #include #include +#ifdef __wasilibc_unmodified_upstream // WASI has no syscall, dup +#else +// Move this below fcntl.h and unistd.h so that the __syscall macro doesn't +// cause trouble. +#include "stdio_impl.h" +#include +#endif /* The basic idea of this implementation is to open a new FILE, * hack the necessary parts of the new FILE into the old one, then @@ -10,46 +21,70 @@ * lock, via flockfile or otherwise, when freopen is called, and in that * case, freopen cannot act until the lock is released. */ +int __wasilibc_fd_renumber(int fd, int newfd) { + __wasi_errno_t error = __wasi_fd_renumber(fd, newfd); + if (error != 0) { + errno = error; + return -1; + } + return 0; +} + FILE *freopen(const char *restrict filename, const char *restrict mode, FILE *restrict f) { - int fl = __fmodeflags(mode); - FILE *f2; + int fl = __fmodeflags(mode); + FILE *f2; - FLOCK(f); + FLOCK(f); - fflush(f); + fflush(f); - if (!filename) { -#ifndef __EMSCRIPTEN__ // CLOEXEC makes no sense for a single process - if (fl&O_CLOEXEC) - __syscall(SYS_fcntl, f->fd, F_SETFD, FD_CLOEXEC); + if (!filename) { + if (fl&O_CLOEXEC) +#ifdef __wasilibc_unmodified_upstream // WASI has no syscall + __syscall(SYS_fcntl, f->fd, F_SETFD, FD_CLOEXEC); +#else + fcntl(f->fd, F_SETFD, FD_CLOEXEC); +#endif + fl &= ~(O_CREAT|O_EXCL|O_CLOEXEC); +#ifdef __wasilibc_unmodified_upstream // WASI has no syscall + if (syscall(SYS_fcntl, f->fd, F_SETFL, fl) < 0) +#else + if (fcntl(f->fd, F_SETFL, fl) < 0) #endif - fl &= ~(O_CREAT|O_EXCL|O_CLOEXEC); - if (syscall(SYS_fcntl, f->fd, F_SETFL, fl) < 0) - goto fail; - } else { - f2 = fopen(filename, mode); - if (!f2) goto fail; - if (f2->fd == f->fd) f2->fd = -1; /* avoid closing in fclose */ - else if (__dup3(f2->fd, f->fd, fl&O_CLOEXEC)<0) goto fail2; - - f->flags = (f->flags & F_PERM) | f2->flags; - f->read = f2->read; - f->write = f2->write; - f->seek = f2->seek; - f->close = f2->close; - - fclose(f2); - } - - FUNLOCK(f); - return f; - -fail2: - fclose(f2); -fail: - fclose(f); - return NULL; + goto fail; + } else { + f2 = fopen(filename, mode); + if (!f2) goto fail; + if (f2->fd == f->fd) f2->fd = -1; /* avoid closing in fclose */ +#ifdef __wasilibc_unmodified_upstream // WASI has no dup + else if (__dup3(f2->fd, f->fd, fl&O_CLOEXEC)<0) goto fail2; +#else + // WASI doesn't have dup3, but does have a way to renumber + // an existing file descriptor. + else { + if (__wasilibc_fd_renumber(f2->fd, f->fd)<0) goto fail2; + f2->fd = -1; /* avoid closing in fclose */ + } +#endif + + f->flags = (f->flags & F_PERM) | f2->flags; + f->read = f2->read; + f->write = f2->write; + f->seek = f2->seek; + f->close = f2->close; + + fclose(f2); + } + + FUNLOCK(f); + return f; + + fail2: + fclose(f2); + fail: + fclose(f); + return NULL; } -weak_alias(freopen, freopen64); +weak_alias(freopen, freopen64); \ No newline at end of file diff --git a/system/lib/standalone/standalone.c b/system/lib/standalone/standalone.c index dcb0149bc63cd..da0d4779eeedd 100644 --- a/system/lib/standalone/standalone.c +++ b/system/lib/standalone/standalone.c @@ -6,6 +6,7 @@ */ #define _GNU_SOURCE +#include #include #include #include @@ -16,6 +17,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -34,6 +42,13 @@ // libc +#ifdef NDEBUG +#define REPORT_UNSUPPORTED(action) +#else +#define REPORT_UNSUPPORTED(action) \ + emscripten_console_error("the program tried to " #action ", this is not supported in standalone mode"); +#endif + void abort() { _Exit(1); } @@ -85,41 +100,1021 @@ __attribute__((__weak__)) int _munmap_js( return -ENOSYS; } +/// A name and file descriptor pair. +typedef struct preopen { + /// The path prefix associated with the file descriptor. + const char *prefix; + + /// The file descriptor. + __wasi_fd_t fd; +} preopen; + +/// A simple growable array of `preopen`. +static preopen *preopens; +static size_t num_preopens; +static size_t preopen_capacity; + +/// Access to the the above preopen must be protected. +static volatile int lock[1]; + +#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]; + assert(pre->prefix != NULL); + assert(pre->fd != (__wasi_fd_t)-1); +#ifdef __wasm__ + assert((uintptr_t)pre->prefix < + (__uint128_t)__builtin_wasm_memory_size(0) * PAGESIZE); +#endif + } +} +#endif + +/// Allocate space for more preopens. Returns 0 on success and -1 on failure. +static int resize(void) { + LOCK(lock); + 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) { + UNLOCK(lock); + return -1; + } + + memcpy(new_preopens, old_preopens, num_preopens * sizeof(preopen)); + preopens = new_preopens; + preopen_capacity = new_capacity; + free(old_preopens); + + assert_invariants(); + UNLOCK(lock); + 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; +} + +/// Register the given preopened file descriptor under the given path. +/// +/// This function takes ownership of `prefix`. +static int internal_register_preopened_fd(__wasi_fd_t fd, const char *relprefix) { + LOCK(lock); + + // Check preconditions. + assert_invariants(); + assert(fd != AT_FDCWD); + assert(fd != -1); + assert(relprefix != NULL); + + if (num_preopens == preopen_capacity && resize() != 0) { + UNLOCK(lock); + return -1; + } + + char *prefix = strdup(strip_prefixes(relprefix)); + if (prefix == NULL) { + UNLOCK(lock); + return -1; + } + preopens[num_preopens++] = (preopen) { prefix, fd, }; + + assert_invariants(); + UNLOCK(lock); + return 0; +} + +/// Are the `prefix_len` bytes pointed to by `prefix` a prefix of `path`? +static bool prefix_matches(const char *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'; +} + +int __standalone_find_abspath(const char *path, + const char **abs_prefix, + const char **relative_path) { + // Strip leading `/` characters, the prefixes we're mataching 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; + int fd = -1; + LOCK(lock); + for (size_t i = num_preopens; i > 0; --i) { + const preopen *pre = &preopens[i - 1]; + const char *prefix = pre->prefix; + size_t len = strlen(prefix); + + // 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 ((fd == -1 || len > match_len) && + prefix_matches(prefix, len, path)) + { + fd = pre->fd; + match_len = len; + *abs_prefix = prefix; + } + } + UNLOCK(lock); + + if (fd == -1) { + errno = ENOENT; + return -1; + } + + // The relative path is the substring after the portion that was matched. + const char *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; + + return fd; +} + +// See the documentation in libc.h +int __standalone_register_preopened_fd(int fd, const char *prefix) { + return internal_register_preopened_fd((__wasi_fd_t)fd, prefix); +} + +int __standalone_find_relpath(const char *path, + const char **abs_prefix, + char **relative_path, + size_t relative_path_len) { + return __standalone_find_abspath(path, abs_prefix, (const char**) relative_path); +} + +static int find_relpath2( + const char *path, + char **relative, + size_t *relative_len +) { + // See comments in `preopens.c` for what this trick is doing. + const char *abs; + return __standalone_find_relpath(path, &abs, relative, *relative_len); +} + +// Helper to call `__standalone_find_relpath` and return an already-managed +// pointer for the `relative` path. This function is not reentrant since the +// `relative` pointer will point to static data that cannot be reused until +// `relative` is no longer used. +static int find_relpath(const char *path, char **relative) { + static __thread char *relative_buf = NULL; + static __thread size_t relative_buf_len = 0; + int fd = find_relpath2(path, &relative_buf, &relative_buf_len); + // find_relpath2 can update relative_buf, so assign it after the call + *relative = relative_buf; + return fd; +} + +// Populate WASI preopens. +__attribute__((constructor(100))) // construct this before user code +static void _standalone_populate_preopens(void) { + // Skip stdin, stdout, and stderr, and count up until we reach an invalid + // file descriptor. + for (__wasi_fd_t fd = 3; fd != 0; ++fd) { + __wasi_prestat_t prestat; + __wasi_errno_t ret = __wasi_fd_prestat_get(fd, &prestat); + if (ret == __WASI_ERRNO_BADF) + break; + if (ret != __WASI_ERRNO_SUCCESS) + goto oserr; + switch (prestat.pr_type) { + case __WASI_PREOPENTYPE_DIR: { + char *prefix = malloc(prestat.u.dir.pr_name_len + 1); + if (prefix == NULL) + goto software; + + // TODO: Remove the cast on `path` once the witx is updated with + // char8 support. + ret = __wasi_fd_prestat_dir_name(fd, (uint8_t *)prefix, + prestat.u.dir.pr_name_len); + if (ret != __WASI_ERRNO_SUCCESS) + goto oserr; + prefix[prestat.u.dir.pr_name_len] = '\0'; + + if (internal_register_preopened_fd(fd, prefix) != 0) + goto software; + free(prefix); + + break; + } + default: + break; + } + } + + return; + oserr: + _Exit(EX_OSERR); + software: + _Exit(EX_SOFTWARE); +} + // open(), etc. - we just support the standard streams, with no // corner case error checking; everything else is not permitted. // TODO: full file support for WASI, or an option for it // open() __attribute__((__weak__)) int __syscall_openat(int dirfd, intptr_t path, int flags, ...) { - if (!strcmp((const char*)path, "/dev/stdin")) { + const char* resolved_path = (const char*)path; + if (!strcmp(resolved_path, "/dev/stdin")) { return STDIN_FILENO; } - if (!strcmp((const char*)path, "/dev/stdout")) { + if (!strcmp(resolved_path, "/dev/stdout")) { return STDOUT_FILENO; } - if (!strcmp((const char*)path, "/dev/stderr")) { + if (!strcmp(resolved_path, "/dev/stderr")) { return STDERR_FILENO; } - return -EPERM; + + // Resolve path if fd is AT_FDCWD. + if (dirfd == AT_FDCWD) { + char *relative_path; + dirfd = find_relpath(resolved_path, &relative_path); + + // If we can't find a preopen for it, fail as if we can't find the path. + if (dirfd == -1) { + errno = ENOENT; + return -1; + } + + resolved_path = relative_path; + } + + // Compute rights corresponding with the access modes provided. + // Attempt to obtain all rights, except the ones that contradict the + // access mode provided to openat(). + __wasi_rights_t max = + ~(__WASI_RIGHTS_FD_DATASYNC | __WASI_RIGHTS_FD_READ | + __WASI_RIGHTS_FD_WRITE | __WASI_RIGHTS_FD_ALLOCATE | + __WASI_RIGHTS_FD_READDIR | __WASI_RIGHTS_FD_FILESTAT_SET_SIZE); + switch (flags & O_ACCMODE) { + case O_RDONLY: + case O_RDWR: + case O_WRONLY: + if ((flags & O_RDONLY) != 0) { + max |= __WASI_RIGHTS_FD_READ | __WASI_RIGHTS_FD_READDIR; + } + if ((flags & O_WRONLY) != 0) { + max |= __WASI_RIGHTS_FD_DATASYNC | __WASI_RIGHTS_FD_WRITE | + __WASI_RIGHTS_FD_ALLOCATE | + __WASI_RIGHTS_FD_FILESTAT_SET_SIZE; + } + break; + case O_EXEC: // O_EXEC => O_PATH => 010000000 + //case O_SEARCH: O_SEARCH => O_PATH => 010000000, both are the same, so causes errors. + break; + default: + errno = EINVAL; + return -1; + } + + // Ensure that we can actually obtain the minimal rights needed. + __wasi_fdstat_t fsb_cur; + __wasi_errno_t error = __wasi_fd_fdstat_get(dirfd, &fsb_cur); + if (error != __WASI_ERRNO_SUCCESS) { + return __wasi_syscall_ret(error); + } + + // Path lookup properties. + __wasi_lookupflags_t lookup_flags = 0; + if ((flags & O_NOFOLLOW) == 0) { + lookup_flags |= __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; + } + + // Open file with appropriate rights. + __wasi_fdflags_t fs_flags = 0; + if (flags & O_APPEND) { + fs_flags |= __WASI_FDFLAGS_APPEND; + } + if (flags & O_DSYNC) { + fs_flags |= __WASI_FDFLAGS_DSYNC; + } + if (flags & O_NONBLOCK) { + fs_flags |= __WASI_FDFLAGS_NONBLOCK; + } + if (flags & O_RSYNC) { + fs_flags |= __WASI_FDFLAGS_RSYNC; + } + if (flags & O_SYNC) { + fs_flags |= __WASI_FDFLAGS_SYNC; + } + + __wasi_oflags_t oflags = 0; + if (flags & O_CREAT) { + oflags |= __WASI_OFLAGS_CREAT; + } + if (flags & O_DIRECTORY) { + oflags |= __WASI_OFLAGS_DIRECTORY; + } + if (flags & O_EXCL) { + oflags |= __WASI_OFLAGS_EXCL; + } + if (flags & O_TRUNC) { + oflags |= __WASI_OFLAGS_TRUNC; + } + + __wasi_rights_t fs_rights_base = max & fsb_cur.fs_rights_inheriting; + __wasi_rights_t fs_rights_inheriting = fsb_cur.fs_rights_inheriting; + __wasi_fd_t newfd; + + error = __wasi_path_open(dirfd, lookup_flags, resolved_path, strlen(resolved_path), + oflags, + fs_rights_base, fs_rights_inheriting, fs_flags, + &newfd); + if (error != __WASI_ERRNO_SUCCESS) { + return __wasi_syscall_ret(error); + } + + return newfd; } -__attribute__((__weak__)) int __syscall_ioctl(int fd, int op, ...) { - return -ENOSYS; +#define __arraycount(x) (sizeof(x) / sizeof((x)[0])) + +__attribute__((__weak__)) int __syscall_ioctl(int fildes, int request, ...) { + switch (request) { + case FIONREAD: { + // Poll the file descriptor to determine how many bytes can be read. + __wasi_subscription_t subscriptions[2] = { + { + .type = __WASI_EVENTTYPE_FD_READ, + .u.fd_readwrite.file_descriptor = fildes, + }, + { + .type = __WASI_EVENTTYPE_CLOCK, + .u.clock.id = __WASI_CLOCKID_MONOTONIC, + }, + }; + __wasi_event_t events[__arraycount(subscriptions)]; + size_t nevents; + __wasi_errno_t error = __wasi_poll_oneoff( + subscriptions, events, __arraycount(subscriptions), &nevents); + if (error != 0) { + errno = error; + return -1; + } + + // Location where result should be written. + va_list ap; + va_start(ap, request); + int *result = va_arg(ap, int *); + va_end(ap); + + // Extract number of bytes for reading from poll results. + for (size_t i = 0; i < nevents; ++i) { + __wasi_event_t *event = &events[i]; + if (event->error != 0) { + errno = event->error; + return -1; + } + if (event->type == __WASI_EVENTTYPE_FD_READ) { + *result = event->u.fd_readwrite.nbytes; + return 0; + } + } + + // No data available for reading. + *result = 0; + return 0; + } + case FIONBIO: { + // Obtain the current file descriptor flags. + __wasi_fdstat_t fds; + __wasi_errno_t error = __wasi_fd_fdstat_get(fildes, &fds); + if (error != 0) { + errno = error; + return -1; + } + + // Toggle the non-blocking flag based on the argument. + va_list ap; + va_start(ap, request); + if (*va_arg(ap, const int *) != 0) + fds.fs_flags |= __WASI_FDFLAGS_NONBLOCK; + else + fds.fs_flags &= ~__WASI_FDFLAGS_NONBLOCK; + va_end(ap); + + // Update the file descriptor flags. + error = __wasi_fd_fdstat_set_flags(fildes, fds.fs_flags); + if (error != 0) { + errno = error; + return -1; + } + return 0; + } + default: + // Invalid request. + errno = EINVAL; + return -1; + } } __attribute__((__weak__)) int __syscall_fcntl64(int fd, int cmd, ...) { + switch (cmd) { + case F_GETFD: + // Act as if the close-on-exec flag is always set. + return FD_CLOEXEC; + case F_SETFD: + // The close-on-exec flag is ignored. + return 0; + case F_GETFL: { + // Obtain the flags and the rights of the descriptor. + __wasi_fdstat_t fds; + __wasi_errno_t error = __wasi_fd_fdstat_get(fd, &fds); + if (error != 0) { + errno = error; + return -1; + } + + // Roughly approximate the access mode by converting the rights. + int oflags = fds.fs_flags; + if ((fds.fs_rights_base & + (__WASI_RIGHTS_FD_READ | __WASI_RIGHTS_FD_READDIR)) != 0) { + if ((fds.fs_rights_base & __WASI_RIGHTS_FD_WRITE) != 0) + oflags |= O_RDWR; + else + oflags |= O_RDONLY; + } else if ((fds.fs_rights_base & __WASI_RIGHTS_FD_WRITE) != 0) { + oflags |= O_WRONLY; + } else { + oflags |= O_SEARCH; + } + return oflags; + } + case F_SETFL: { + // Set new file descriptor flags. + va_list ap; + va_start(ap, cmd); + int flags = va_arg(ap, int); + va_end(ap); + + __wasi_fdflags_t fs_flags = flags & 0xfff; + __wasi_errno_t error = + __wasi_fd_fdstat_set_flags(fd, fs_flags); + if (error != 0) { + errno = error; + return -1; + } + return 0; + } + default: + errno = EINVAL; + return -1; + } +} + +__attribute__((__weak__)) int __syscall_ftruncate64(int fd, uint64_t size) { + if (size < 0) { + errno = EINVAL; + return -1; + } + __wasi_filesize_t st_size = size; + __wasi_errno_t error = + __wasi_fd_filestat_set_size(fd, st_size); + if (error != 0) { + errno = error; + return -1; + } + return 0; +} + +int rmdirat(int dirfd, intptr_t path) { + const char* resolved_path = (const char*)path; + + // Resolve path if fd is AT_FDCWD. + if (dirfd == AT_FDCWD) { + char *relative_path; + dirfd = find_relpath(resolved_path, &relative_path); + + // If we can't find a preopen for it, fail as if we can't find the path. + if (dirfd == -1) { + errno = ENOENT; + return -1; + } + + resolved_path = relative_path; + } + + __wasi_errno_t error = __wasi_path_remove_directory(dirfd, resolved_path, strlen(resolved_path)); + if (error != 0) { + errno = error; + return -1; + } + + return 0; +} + +__attribute__((__weak__)) int __syscall_rmdir(intptr_t path) { + return rmdirat(AT_FDCWD, path); +} + +__attribute__((__weak__)) int __syscall_unlinkat(int dirfd, intptr_t path, int flags) { + // unlinkat with AT_REMOVEDIR flag is acutally rmdir. + if ((flags & AT_REMOVEDIR) != 0) { + return rmdirat(dirfd, path); + } + + const char* resolved_path = (const char*)path; + + // Resolve path if fd is AT_FDCWD. + if (dirfd == AT_FDCWD) { + char *relative_path; + dirfd = find_relpath(resolved_path, &relative_path); + + // If we can't find a preopen for it, fail as if we can't find the path. + if (dirfd == -1) { + errno = ENOENT; + return -1; + } + + resolved_path = relative_path; + } + + __wasi_errno_t error = __wasi_path_unlink_file(dirfd, resolved_path, strlen(resolved_path)); + if (error != 0) { + errno = error; + return -1; + } + return 0; +} + +__attribute__((__weak__)) int __syscall_pipe(intptr_t fd) { + REPORT_UNSUPPORTED(__syscall_pipe); + abort(); return -ENOSYS; } -__attribute__((__weak__)) int __syscall_fstat64(int fd, intptr_t buf) { +__attribute__((__weak__)) int __syscall_renameat(int olddirfd, intptr_t oldpath, int newdirfd, intptr_t newpath) { + const char* oldpathresolved_path = (const char*)oldpath; + + // Resolve path if fd is AT_FDCWD. + if (olddirfd == AT_FDCWD) { + char *oldpathrelative_path; + olddirfd = find_relpath(oldpathresolved_path, &oldpathrelative_path); + + // If we can't find a preopen for it, fail as if we can't find the path. + if (olddirfd == -1) { + errno = ENOENT; + return -1; + } + + oldpathresolved_path = oldpathrelative_path; + } + + const char* newpathresolved_path = (const char*)newpath; + + // Resolve path if fd is AT_FDCWD. + if (newdirfd == AT_FDCWD) { + char *newpathrelative_path; + newdirfd = find_relpath(newpathresolved_path, &newpathrelative_path); + + // If we can't find a preopen for it, fail as if we can't find the path. + if (newdirfd == -1) { + errno = ENOENT; + return -1; + } + + newpathresolved_path = newpathrelative_path; + } + + __wasi_errno_t error = __wasi_path_rename(olddirfd, oldpathresolved_path, strlen(oldpathresolved_path), newdirfd, newpathresolved_path, strlen(newpathresolved_path)); + if (error != 0) { + errno = error; + return -1; + } + return 0; +} + +__attribute__((__weak__)) int __syscall_dup(int fd) { + REPORT_UNSUPPORTED(__syscall_dup); + abort(); return -ENOSYS; } -// There is no good source of entropy without an import. Make this weak so that -// it can be replaced with a pRNG or a proper import. -__attribute__((__weak__)) -int getentropy(void* buffer, size_t length) { +__attribute__((__weak__)) int __syscall_dup3(int fd, int suggestfd, int flags) { + REPORT_UNSUPPORTED(__syscall_dup3); abort(); + return -ENOSYS; +} + +__attribute__((__weak__)) int __syscall_faccessat(int dirfd, intptr_t path, int amode, int flags) { + const char* resolved_path = (const char*)path; + + // Resolve path if fd is AT_FDCWD. + if (dirfd == AT_FDCWD) { + char *relative_path; + dirfd = find_relpath(resolved_path, &relative_path); + + // If we can't find a preopen for it, fail as if we can't find the path. + if (dirfd == -1) { + errno = ENOENT; + return -1; + } + + resolved_path = relative_path; + } + + // Validate function parameters. + if ((amode & ~(F_OK | R_OK | W_OK | X_OK)) != 0 || + (flags & ~AT_EACCESS) != 0) { + errno = EINVAL; + return -1; + } + + // Check for target file existence and obtain the file type. + __wasi_lookupflags_t lookup_flags = __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; + __wasi_filestat_t file; + __wasi_errno_t error = + __wasi_path_filestat_get(dirfd, lookup_flags, resolved_path, strlen(resolved_path), &file); + if (error != 0) { + errno = error; + return -1; + } + + // Test whether the requested access rights are present on the + // directory file descriptor. + if (amode != 0) { + __wasi_fdstat_t directory; + error = __wasi_fd_fdstat_get(dirfd, &directory); + if (error != 0) { + errno = error; + return -1; + } + + __wasi_rights_t min = 0; + if ((amode & R_OK) != 0) + min |= file.filetype == __WASI_FILETYPE_DIRECTORY + ? __WASI_RIGHTS_FD_READDIR + : __WASI_RIGHTS_FD_READ; + if ((amode & W_OK) != 0) + min |= __WASI_RIGHTS_FD_WRITE; + + if ((min & directory.fs_rights_inheriting) != min) { + errno = EACCES; + return -1; + } + } + return 0; +} + +static void __wasi_filestat_to_stat(const __wasi_filestat_t *in, + struct stat *out) { + *out = (struct stat){ + .st_dev = in->dev, + .st_ino = in->ino, + .st_nlink = in->nlink, + .st_size = in->size, + .st_atim = __wasi_timestamp_to_timespec(in->atim), + .st_mtim = __wasi_timestamp_to_timespec(in->mtim), + .st_ctim = __wasi_timestamp_to_timespec(in->ctim), + }; + + // Convert file type to legacy types encoded in st_mode. + switch (in->filetype) { + case __WASI_FILETYPE_BLOCK_DEVICE: + out->st_mode |= S_IFBLK; + 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; + } +} + +__attribute__((__weak__)) +int __syscall_fstat64(int fd, intptr_t buf) { + __wasi_filestat_t internal_stat; + __wasi_errno_t error = __wasi_fd_filestat_get(fd, &internal_stat); + if (error != __WASI_ERRNO_SUCCESS) { + return __wasi_syscall_ret(error); + } + __wasi_filestat_to_stat(&internal_stat, (struct stat *) buf); + return 0; +} + +__attribute__((__weak__)) +int __syscall_getdents64(int fd, intptr_t dirp, size_t count) { + intptr_t dirpointer = dirp; + struct dirent *de; + de = (void *)(dirpointer); + + // Check if the result buffer is too small. + if (count / sizeof(struct dirent) == 0) { + return -EINVAL; + } + + __wasi_dirent_t entry; + + // Create new buffer size to save same amount of __wasi_dirent_t as dirp records. + size_t buffer_size = (count / sizeof(struct dirent)) * (sizeof(entry) + 256); + char *buffer = malloc(buffer_size); + if (buffer == NULL) { + return -1; + } + + size_t buffer_processed = buffer_size; + size_t buffer_used = buffer_size; + size_t dirent_processed = 0; + + // Use the cookie of the previous entries, readdir reuses the buffer so + // a nonzero de->d_off is the cookie of the last readdir call. + int i; + struct dirent *checkde; + __wasi_dircookie_t cookie = 0; + for (i = 0; i < (count / sizeof(struct dirent)); ++i) { + checkde = (void *)(dirpointer + (sizeof(struct dirent) * i)); + + // Store cookie if it's bigger than the last known. + if (checkde->d_off > cookie) { + cookie = checkde->d_off; + } + + // Reset cookie to 0 so that this offset isn't going to hunt us in later calls. + checkde->d_off = 0; + } + + for (;;) { + // Extract the next dirent header. + size_t buffer_left = buffer_used - buffer_processed; + if (buffer_left < sizeof(__wasi_dirent_t)) { + // End-of-file. + if (buffer_used < buffer_size) { + break; + } + + goto read_entries; + } + __wasi_dirent_t entry; + memcpy(&entry, buffer + buffer_processed, sizeof(entry)); + + size_t entry_size = sizeof(__wasi_dirent_t) + entry.d_namlen; + if (entry.d_namlen == 0) { + // Invalid pathname length. Skip the entry. + buffer_processed += entry_size; + continue; + } + + // The entire entry must be present in buffer space. If not, read + // the entry another time. Ensure that the read buffer is large + // enough to fit at least this single entry. + if (buffer_left < entry_size) { + while (buffer_size < entry_size) { + buffer_size *= 2; + } + char *new_buffer = realloc(buffer, buffer_size); + if (new_buffer == NULL) { + return -1; + } + buffer = new_buffer; + goto read_entries; + } + + const char *name = buffer + buffer_processed + sizeof(entry); + buffer_processed += entry_size; + + // Skip entries that do not fit in the dirent name buffer. + if (entry.d_namlen > sizeof de->d_name) { + continue; + } + + // Skip entries having null bytes in the filename. + if (memchr(name, '\0', entry.d_namlen) != NULL) { + continue; + } + + de->d_ino = entry.d_ino; + + // Map the right WASI type to dirent type. + // I could not get the dirent.h import to work to use defines. + switch (entry.d_type) { + case __WASI_FILETYPE_UNKNOWN: + de->d_type = 0; + break; + case __WASI_FILETYPE_BLOCK_DEVICE: + de->d_type = 6; + break; + case __WASI_FILETYPE_CHARACTER_DEVICE: + de->d_type = 2; + break; + case __WASI_FILETYPE_DIRECTORY: + de->d_type = 4; + break; + case __WASI_FILETYPE_REGULAR_FILE: + de->d_type = 8; + break; + case __WASI_FILETYPE_SOCKET_DGRAM: + de->d_type = 12; + break; + case __WASI_FILETYPE_SOCKET_STREAM: + de->d_type = 12; + break; + case __WASI_FILETYPE_SYMBOLIC_LINK: + de->d_type = 10; + break; + default: + de->d_type = 0; + break; + } + + de->d_off = entry.d_next; + de->d_reclen = sizeof(struct dirent); + memcpy(de->d_name, name, entry.d_namlen); + de->d_name[entry.d_namlen] = '\0'; + cookie = entry.d_next; + dirent_processed = dirent_processed + sizeof(struct dirent); + + // Can't fit more in my buffer. + if (dirent_processed + sizeof(struct dirent) > count) { + break; + } + + // Set entry to next entry in memory. + dirpointer = dirpointer + sizeof(struct dirent); + de = (void *)(dirpointer); + + continue; + + read_entries:; + // Load more directory entries and continue. + // TODO: Remove the cast on `buffer` once the witx is updated with char8 support. + __wasi_errno_t error = __wasi_fd_readdir(fd, (uint8_t *)buffer, buffer_size, + cookie, &buffer_used); + if (error != 0) { + errno = error; + return -1; + } + buffer_processed = 0; + } + + return dirent_processed; +} + +int __syscall_newfstatat(int dirfd, intptr_t path, intptr_t buf, int flags) { + // Convert flags to WASI. + __wasi_lookupflags_t lookup_flags = 0; + if ((flags & AT_SYMLINK_NOFOLLOW) == 0) { + lookup_flags |= __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; + } + + const char* resolved_path = (const char*)path; + + // Resolve path if fd is AT_FDCWD. + if (dirfd == AT_FDCWD) { + char *relative_path; + dirfd = find_relpath(resolved_path, &relative_path); + + // If we can't find a preopen for it, fail as if we can't find the path. + if (dirfd == -1) { + errno = ENOENT; + return -1; + } + + resolved_path = relative_path; + } + + __wasi_filestat_t fsb_cur; + __wasi_errno_t error = __wasi_path_filestat_get(dirfd, lookup_flags, resolved_path, strlen(resolved_path), &fsb_cur); + if (error != __WASI_ERRNO_SUCCESS) { + return __wasi_syscall_ret(error); + } + + __wasi_filestat_to_stat(&fsb_cur, (struct stat *) buf); + + return 0; +} + +__attribute__((__weak__)) +int __syscall_stat64(intptr_t path, intptr_t buf) { + return __syscall_newfstatat(AT_FDCWD, path, buf, 0); +} + +__attribute__((__weak__)) +int __syscall_lstat64(intptr_t path, intptr_t buf) { + return __syscall_newfstatat(AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW); +} + +__attribute__((__weak__)) +int getentropy(void *buffer, size_t length) { + return __wasi_syscall_ret(__wasi_random_get(buffer, length)); +} + +__attribute__((__weak__)) +int __syscall_getcwd(intptr_t buf, size_t size) { + // Check if buf points to a bad address. + if (!buf && size > 0) { + return -EFAULT; + } + + // Check if the size argument is zero and buf is not a null pointer. + if (buf && size == 0) { + return -EINVAL; + } + + // We force our CWD to always be /. + char res[1]="/"; + int len = 2; + + // Check if the size argument is less than the length of the absolute + // pathname of the working directory, including null terminator. + if (len >= size) { + return -ERANGE; + } + + // Return value is a null-terminated c string. + strcpy((char*)buf, res); + + return len; +} + +__attribute__((__weak__)) +int __syscall_mkdirat(int dirfd, intptr_t path, int mode) { + const char* resolved_path = (const char*)path; + + // Resolve path if fd is AT_FDCWD. + if (dirfd == AT_FDCWD) { + char *relative_path; + dirfd = find_relpath(resolved_path, &relative_path); + + // If we can't find a preopen for it, fail as if we can't find the path. + if (dirfd == -1) { + errno = ENOENT; + return -1; + } + + resolved_path = relative_path; + } + + __wasi_errno_t error = __wasi_path_create_directory(dirfd, resolved_path, strlen(resolved_path)); + if (error != 0) { + errno = error; + return -1; + } + return 0; } // Emscripten additions @@ -158,12 +1153,25 @@ double emscripten_get_now(void) { return (1000ll * clock()) / (double)CLOCKS_PER_SEC; } +__attribute__((__weak__)) +void _emscripten_throw_longjmp() { + REPORT_UNSUPPORTED(call longjmp); + abort(); +} + +__attribute__((__weak__)) +int _setitimer_js(int which, double timeout) { + REPORT_UNSUPPORTED(set itimer); + abort(); +} + // C++ ABI #if EMSCRIPTEN_NOCATCH // When exception catching is disabled, we stub out calls to `__cxa_throw`. // Otherwise, `__cxa_throw` will be imported from the host. void __cxa_throw(void* ptr, void* type, void* destructor) { + REPORT_UNSUPPORTED(throw an exception); abort(); } #endif @@ -227,3 +1235,20 @@ int _setitimer_js(int which, double timeout) { errno = ENOTSUP; return -1; } +__attribute__((__weak__)) +void *dlopen(const char *file, int mode) { + REPORT_UNSUPPORTED(dlopen); + abort(); +} + +__attribute__((__weak__)) +void* __dlsym(void* restrict p, const char* restrict s, void* restrict ra) { + REPORT_UNSUPPORTED(dlsym); + abort(); +} + +__attribute__((__weak__)) +int system(const char *cmd) { + REPORT_UNSUPPORTED(system); + abort(); +} From ba74d3817ea8e3d0a96a023252f9e1aa3b800a44 Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Wed, 3 May 2023 08:55:51 +0200 Subject: [PATCH 02/11] Fix duplicate standalone --- system/lib/standalone/standalone.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/system/lib/standalone/standalone.c b/system/lib/standalone/standalone.c index da0d4779eeedd..35b97d1908942 100644 --- a/system/lib/standalone/standalone.c +++ b/system/lib/standalone/standalone.c @@ -1159,12 +1159,6 @@ void _emscripten_throw_longjmp() { abort(); } -__attribute__((__weak__)) -int _setitimer_js(int which, double timeout) { - REPORT_UNSUPPORTED(set itimer); - abort(); -} - // C++ ABI #if EMSCRIPTEN_NOCATCH From f9d1d2ae4605201b95e85cccc8bfd9cd67a68778 Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Wed, 28 Jun 2023 17:53:51 +0200 Subject: [PATCH 03/11] Fix func signature --- system/lib/standalone/standalone.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/lib/standalone/standalone.c b/system/lib/standalone/standalone.c index 4e96b66a5de4c..058d5739459af 100644 --- a/system/lib/standalone/standalone.c +++ b/system/lib/standalone/standalone.c @@ -615,12 +615,12 @@ weak int __syscall_fcntl64(int fd, int cmd, ...) { } } -weak int __syscall_ftruncate64(int fd, uint64_t size) { - if (size < 0) { +weak int __syscall_ftruncate64(int fd, off_t length) { + if (length < 0) { errno = EINVAL; return -1; } - __wasi_filesize_t st_size = size; + __wasi_filesize_t st_size = length; __wasi_errno_t error = __wasi_fd_filestat_set_size(fd, st_size); if (error != 0) { From b0d72c583173803c2082c289fe39216cfa2058ed Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Tue, 25 Jul 2023 21:40:49 +0200 Subject: [PATCH 04/11] Remove .idea --- .idea/.gitignore | 8 -------- .idea/misc.xml | 6 ------ .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 4 files changed, 28 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b81b018a..0000000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 830d3c82f5ae1..0000000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index d602af7da5e3d..0000000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfbbc02..0000000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 5ac7b86c86665d0893cf48cfa2f6963ca8c9219e Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Tue, 25 Jul 2023 21:41:02 +0200 Subject: [PATCH 05/11] Remove .idea --- .idea/.gitignore | 8 ++++++++ .idea/misc.xml | 6 ++++++ .idea/modules.xml | 8 ++++++++ .idea/vcs.xml | 6 ++++++ 4 files changed, 28 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000..13566b81b018a --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000000..830d3c82f5ae1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000000..d602af7da5e3d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000..35eb1ddfbbc02 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 134f3388669f06e71c8c26bde0fffddb0c43f697 Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Tue, 25 Jul 2023 21:41:26 +0200 Subject: [PATCH 06/11] Remove .idea --- .idea/.gitignore | 8 -------- .idea/misc.xml | 6 ------ .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 4 files changed, 28 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b81b018a..0000000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 830d3c82f5ae1..0000000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index d602af7da5e3d..0000000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfbbc02..0000000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 90e2bd7cd684209de1064103e9574c20d3f0fb9d Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Mon, 7 Aug 2023 09:35:14 +0200 Subject: [PATCH 07/11] Throw out _emscripten_throw_longjmp stub --- system/lib/standalone/standalone.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/system/lib/standalone/standalone.c b/system/lib/standalone/standalone.c index a1da3755aa0aa..3b52f7a70c5c1 100644 --- a/system/lib/standalone/standalone.c +++ b/system/lib/standalone/standalone.c @@ -1146,11 +1146,6 @@ weak double emscripten_get_now(void) { return (double)ts.tv_sec * 1000 + (double)ts.tv_nsec / 1000000; } -weak void _emscripten_throw_longjmp() { - REPORT_UNSUPPORTED(call longjmp); - abort(); -} - // C++ ABI #if EMSCRIPTEN_NOCATCH From e4f51891732b08742fb246bae425ff8bdbfa95a7 Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Fri, 19 Jul 2024 13:57:20 +0200 Subject: [PATCH 08/11] Add missing functions in WASI --- system/lib/standalone/standalone.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/system/lib/standalone/standalone.c b/system/lib/standalone/standalone.c index 19afd7984e09e..a5335906daddc 100644 --- a/system/lib/standalone/standalone.c +++ b/system/lib/standalone/standalone.c @@ -1151,6 +1151,14 @@ weak double emscripten_date_now(void) { return clock_gettime_ms(CLOCK_REALTIME); } +weak void __secs_to_zone(long long t, int local, int *isdst, long *offset, long *oppoff, const char **zonename) { + REPORT_UNSUPPORTED(__secs_to_zone); +} + +weak void _tzset_js(long* timezone, int* daylight, char* std_name, char* dst_name) { + REPORT_UNSUPPORTED(_tzset_js); +} + // C++ ABI #if EMSCRIPTEN_NOCATCH From 08f2aafb6ad32cb3ade3cc42fa9b9b15d383becb Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Wed, 23 Apr 2025 16:25:12 +0200 Subject: [PATCH 09/11] Add time related libc --- system/lib/standalone/standalone.c | 69 ++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/system/lib/standalone/standalone.c b/system/lib/standalone/standalone.c index 52683a3bfff5c..f71fd6dce9332 100644 --- a/system/lib/standalone/standalone.c +++ b/system/lib/standalone/standalone.c @@ -1303,3 +1303,72 @@ weak void _emscripten_get_progname(char* buf, int length) { } weak void _emscripten_runtime_keepalive_clear() {} + +weak time_t timegm(struct tm *tm) +{ + struct tm new; + long long t = __tm_to_secs(tm); + if (__secs_to_tm(t, &new) < 0) { + errno = EOVERFLOW; + return -1; + } + *tm = new; + tm->tm_isdst = 0; + tm->__tm_gmtoff = 0; + tm->__tm_zone = __utc; + return t; +} + +weak time_t mktime(struct tm *tm) +{ + struct tm new; + long opp; + long long t = __tm_to_secs(tm); + + __secs_to_zone(t, 1, &new.tm_isdst, &new.__tm_gmtoff, &opp, &new.__tm_zone); + + if (tm->tm_isdst>=0 && new.tm_isdst!=tm->tm_isdst) + t -= opp - new.__tm_gmtoff; + + t -= new.__tm_gmtoff; + if ((time_t)t != t) goto error; + + __secs_to_zone(t, 0, &new.tm_isdst, &new.__tm_gmtoff, &opp, &new.__tm_zone); + + if (__secs_to_tm(t + new.__tm_gmtoff, &new) < 0) goto error; + + *tm = new; + return t; + +error: + errno = EOVERFLOW; + return -1; +} + +weak struct tm *__localtime_r(const time_t *restrict t, struct tm *restrict tm) +{ + /* Reject time_t values whose year would overflow int because + * __secs_to_zone cannot safely handle them. */ + if (*t < INT_MIN * 31622400LL || *t > INT_MAX * 31622400LL) { + errno = EOVERFLOW; + return 0; + } + __secs_to_zone(*t, 0, &tm->tm_isdst, &tm->__tm_gmtoff, 0, &tm->__tm_zone); + if (__secs_to_tm((long long)*t + tm->__tm_gmtoff, tm) < 0) { + errno = EOVERFLOW; + return 0; + } + return tm; +} + +weak struct tm *__gmtime_r(const time_t *restrict t, struct tm *restrict tm) +{ + if (__secs_to_tm(*t, tm) < 0) { + errno = EOVERFLOW; + return 0; + } + tm->tm_isdst = 0; + tm->__tm_gmtoff = 0; + tm->__tm_zone = __utc; + return tm; +} From 8fd95dd56a62c2afa946067f275be3dfc680db63 Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Wed, 23 Apr 2025 17:20:29 +0200 Subject: [PATCH 10/11] Update time implementations for patch --- system/lib/standalone/standalone.c | 191 ++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 6 deletions(-) diff --git a/system/lib/standalone/standalone.c b/system/lib/standalone/standalone.c index f71fd6dce9332..1cb01d42b1c40 100644 --- a/system/lib/standalone/standalone.c +++ b/system/lib/standalone/standalone.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -1304,11 +1305,173 @@ weak void _emscripten_get_progname(char* buf, int length) { weak void _emscripten_runtime_keepalive_clear() {} +/* 2000-03-01 (mod 400 year, immediately after feb29 */ +#define LEAPOCH (946684800LL + 86400*(31+29)) +#define DAYS_PER_400Y (365*400 + 97) +#define DAYS_PER_100Y (365*100 + 24) +#define DAYS_PER_4Y (365*4 + 1) +const char __utc[] = "UTC"; + +int __standalone_month_to_secs(int month, int is_leap) +{ + static const int secs_through_month[] = { + 0, 31*86400, 59*86400, 90*86400, + 120*86400, 151*86400, 181*86400, 212*86400, + 243*86400, 273*86400, 304*86400, 334*86400 }; + int t = secs_through_month[month]; + if (is_leap && month >= 2) t+=86400; + return t; +} + +long long __standalone_year_to_secs(long long year, int *is_leap) +{ + if (year-2ULL <= 136) { + int y = year; + int leaps = (y-68)>>2; + if (!((y-68)&3)) { + leaps--; + if (is_leap) *is_leap = 1; + } else if (is_leap) *is_leap = 0; + return 31536000*(y-70) + 86400*leaps; + } + + int cycles, centuries, leaps, rem; + + if (!is_leap) is_leap = &(int){0}; + cycles = (year-100) / 400; + rem = (year-100) % 400; + if (rem < 0) { + cycles--; + rem += 400; + } + if (!rem) { + *is_leap = 1; + centuries = 0; + leaps = 0; + } else { + if (rem >= 200) { + if (rem >= 300) centuries = 3, rem -= 300; + else centuries = 2, rem -= 200; + } else { + if (rem >= 100) centuries = 1, rem -= 100; + else centuries = 0; + } + if (!rem) { + *is_leap = 0; + leaps = 0; + } else { + leaps = rem / 4U; + rem %= 4U; + *is_leap = !rem; + } + } + + leaps += 97*cycles + 24*centuries - *is_leap; + + return (year-100) * 31536000LL + leaps * 86400LL + 946684800 + 86400; +} + +long long __standalone_tm_to_secs(const struct tm *tm) +{ + int is_leap; + long long year = tm->tm_year; + int month = tm->tm_mon; + if (month >= 12 || month < 0) { + int adj = month / 12; + month %= 12; + if (month < 0) { + adj--; + month += 12; + } + year += adj; + } + long long t = __standalone_year_to_secs(year, &is_leap); + t += __standalone_month_to_secs(month, is_leap); + t += 86400LL * (tm->tm_mday-1); + t += 3600LL * tm->tm_hour; + t += 60LL * tm->tm_min; + t += tm->tm_sec; + return t; +} + +int __standalone_secs_to_tm(long long t, struct tm *tm) +{ + long long days, secs, years; + int remdays, remsecs, remyears; + int qc_cycles, c_cycles, q_cycles; + int months; + int wday, yday, leap; + static const char days_in_month[] = {31,30,31,30,31,31,30,31,30,31,31,29}; + + /* Reject time_t values whose year would overflow int */ + if (t < INT_MIN * 31622400LL || t > INT_MAX * 31622400LL) + return -1; + + secs = t - LEAPOCH; + days = secs / 86400; + remsecs = secs % 86400; + if (remsecs < 0) { + remsecs += 86400; + days--; + } + + wday = (3+days)%7; + if (wday < 0) wday += 7; + + qc_cycles = days / DAYS_PER_400Y; + remdays = days % DAYS_PER_400Y; + if (remdays < 0) { + remdays += DAYS_PER_400Y; + qc_cycles--; + } + + c_cycles = remdays / DAYS_PER_100Y; + if (c_cycles == 4) c_cycles--; + remdays -= c_cycles * DAYS_PER_100Y; + + q_cycles = remdays / DAYS_PER_4Y; + if (q_cycles == 25) q_cycles--; + remdays -= q_cycles * DAYS_PER_4Y; + + remyears = remdays / 365; + if (remyears == 4) remyears--; + remdays -= remyears * 365; + + leap = !remyears && (q_cycles || !c_cycles); + yday = remdays + 31 + 28 + leap; + if (yday >= 365+leap) yday -= 365+leap; + + years = remyears + 4*q_cycles + 100*c_cycles + 400LL*qc_cycles; + + for (months=0; days_in_month[months] <= remdays; months++) + remdays -= days_in_month[months]; + + if (months >= 10) { + months -= 12; + years++; + } + + if (years+100 > INT_MAX || years+100 < INT_MIN) + return -1; + + tm->tm_year = years + 100; + tm->tm_mon = months + 2; + tm->tm_mday = remdays + 1; + tm->tm_wday = wday; + tm->tm_yday = yday; + + tm->tm_hour = remsecs / 3600; + tm->tm_min = remsecs / 60 % 60; + tm->tm_sec = remsecs % 60; + + return 0; +} + weak time_t timegm(struct tm *tm) { struct tm new; - long long t = __tm_to_secs(tm); - if (__secs_to_tm(t, &new) < 0) { + long long t = __standalone_tm_to_secs(tm); + if (__standalone_secs_to_tm(t, &new) < 0) { errno = EOVERFLOW; return -1; } @@ -1323,7 +1486,7 @@ weak time_t mktime(struct tm *tm) { struct tm new; long opp; - long long t = __tm_to_secs(tm); + long long t = __standalone_tm_to_secs(tm); __secs_to_zone(t, 1, &new.tm_isdst, &new.__tm_gmtoff, &opp, &new.__tm_zone); @@ -1335,7 +1498,7 @@ weak time_t mktime(struct tm *tm) __secs_to_zone(t, 0, &new.tm_isdst, &new.__tm_gmtoff, &opp, &new.__tm_zone); - if (__secs_to_tm(t + new.__tm_gmtoff, &new) < 0) goto error; + if (__standalone_secs_to_tm(t + new.__tm_gmtoff, &new) < 0) goto error; *tm = new; return t; @@ -1354,7 +1517,7 @@ weak struct tm *__localtime_r(const time_t *restrict t, struct tm *restrict tm) return 0; } __secs_to_zone(*t, 0, &tm->tm_isdst, &tm->__tm_gmtoff, 0, &tm->__tm_zone); - if (__secs_to_tm((long long)*t + tm->__tm_gmtoff, tm) < 0) { + if (__standalone_secs_to_tm((long long)*t + tm->__tm_gmtoff, tm) < 0) { errno = EOVERFLOW; return 0; } @@ -1363,7 +1526,7 @@ weak struct tm *__localtime_r(const time_t *restrict t, struct tm *restrict tm) weak struct tm *__gmtime_r(const time_t *restrict t, struct tm *restrict tm) { - if (__secs_to_tm(*t, tm) < 0) { + if (__standalone_secs_to_tm(*t, tm) < 0) { errno = EOVERFLOW; return 0; } @@ -1372,3 +1535,19 @@ weak struct tm *__gmtime_r(const time_t *restrict t, struct tm *restrict tm) tm->__tm_zone = __utc; return tm; } + +weak_alias(__gmtime_r, gmtime_r); +weak_alias(__localtime_r, localtime_r); + +weak time_t _timegm_js(struct tm* tm) { + REPORT_UNSUPPORTED(_timegm_js); + errno = ENOTSUP; + return -1; +} + +weak time_t _mktime_js(struct tm* tm) { + REPORT_UNSUPPORTED(_mktime_js); + errno = ENOTSUP; + return -1; +} + From 2d359d3b133ef4efa645c4448e8e181019f44c55 Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Tue, 25 Nov 2025 11:41:19 +0100 Subject: [PATCH 11/11] Remove idea --- .idea/.gitignore | 8 - .idea/codeStyles/Project.xml | 158 --------- .idea/codeStyles/codeStyleConfig.xml | 5 - .idea/editor.xml | 344 ------------------- .idea/inspectionProfiles/Project_Default.xml | 6 - .idea/misc.xml | 13 - .idea/prettier.xml | 6 - .idea/vcs.xml | 6 - 8 files changed, 546 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/editor.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/prettier.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b81b018a..0000000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 8e92cca025ca6..0000000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123c2b23e..0000000000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml deleted file mode 100644 index 0093ef66d749b..0000000000000 --- a/.idea/editor.xml +++ /dev/null @@ -1,344 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 03d9549ea8e4a..0000000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index bedf918366a75..0000000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/prettier.xml b/.idea/prettier.xml deleted file mode 100644 index b0c1c68fbbad6..0000000000000 --- a/.idea/prettier.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfbbc02..0000000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file