mirror of
https://git.proxmox.com/git/wasi-libc
synced 2025-10-05 08:25:51 +00:00

* Add basic emulation of getcwd/chdir This commit adds basic emulation of a current working directory to wasi-libc. The `getcwd` and `chdir` symbols are now implemented and available for use. The `getcwd` implementation is pretty simple in that it just copies out of a new global, `__wasilibc_cwd`, which defaults to `"/"`. The `chdir` implementation is much more involved and has more ramification, however. A new function, `make_absolute`, was added to the preopens object. Paths stored in the preopen table are now always stored as absolute paths instead of relative paths, and initial relative paths are interpreted as being relative to `/`. Looking up a path to preopen now always turns it into an absolute path, relative to the current working directory, and an appropriate path is then returned. The signature of `__wasilibc_find_relpath` has changed as well. It now returns two path components, one for the absolute part and one for the relative part. Additionally the relative part is always dynamically allocated since it may no longer be a substring of the original input path. This has been tested lightly against the Rust standard library so far, but I'm not a regular C developer so there's likely a few things to improve! * Amortize mallocs made in syscalls * Avoid size bloat on programs that don't use `chdir` * Add threading compat * Collect `link`/`renameat` second path lookup * Update comments about chdir.c in makefile * Move definition of `__wasilibc_find_relpath_alloc` to header * Expand comments * Document the format of strings a bit more * Fixup a few issues in path logic * Fix GitHub Actions
284 lines
8.2 KiB
C
284 lines
8.2 KiB
C
//! POSIX-like functions supporting absolute path arguments, implemented in
|
|
//! terms of `__wasilibc_find_relpath` and `*at`-style functions.
|
|
|
|
#include <errno.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <utime.h>
|
|
#include <wasi/libc.h>
|
|
#include <wasi/libc-find-relpath.h>
|
|
|
|
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;
|
|
if (__wasilibc_find_relpath_alloc)
|
|
return __wasilibc_find_relpath_alloc(path, &abs, relative, relative_len, 1);
|
|
return __wasilibc_find_relpath(path, &abs, relative, *relative_len);
|
|
}
|
|
|
|
// Helper to call `__wasilibc_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;
|
|
*relative = relative_buf;
|
|
return find_relpath2(path, relative, &relative_buf_len);
|
|
}
|
|
|
|
// same as `find_relpath`, but uses another set of static variables to cache
|
|
static int find_relpath_alt(const char *path, char **relative) {
|
|
static __thread char *relative_buf = NULL;
|
|
static __thread size_t relative_buf_len = 0;
|
|
*relative = relative_buf;
|
|
return find_relpath2(path, relative, &relative_buf_len);
|
|
}
|
|
|
|
int open(const char *path, int oflag, ...) {
|
|
// WASI libc's `openat` ignores the mode argument, so call a special
|
|
// entrypoint which avoids the varargs calling convention.
|
|
return __wasilibc_open_nomode(path, oflag);
|
|
}
|
|
|
|
// See the documentation in libc.h
|
|
int __wasilibc_open_nomode(const char *path, int oflag) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(path, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
return __wasilibc_openat_nomode(dirfd, relative_path, oflag);
|
|
}
|
|
|
|
int access(const char *path, int amode) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(path, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
return faccessat(dirfd, relative_path, amode, 0);
|
|
}
|
|
|
|
ssize_t readlink(
|
|
const char *restrict path,
|
|
char *restrict buf,
|
|
size_t bufsize)
|
|
{
|
|
char *relative_path;
|
|
int dirfd = find_relpath(path, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
return readlinkat(dirfd, relative_path, buf, bufsize);
|
|
}
|
|
|
|
int stat(const char *restrict path, struct stat *restrict buf) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(path, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
return fstatat(dirfd, relative_path, buf, 0);
|
|
}
|
|
|
|
int lstat(const char *restrict path, struct stat *restrict buf) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(path, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
return fstatat(dirfd, relative_path, buf, AT_SYMLINK_NOFOLLOW);
|
|
}
|
|
|
|
int utime(const char *path, const struct utimbuf *times) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(path, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
return utimensat(dirfd, relative_path,
|
|
times ? ((struct timespec [2]) {
|
|
{ .tv_sec = times->actime },
|
|
{ .tv_sec = times->modtime }
|
|
})
|
|
: NULL,
|
|
0);
|
|
}
|
|
|
|
int unlink(const char *path) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(path, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
// `unlinkat` imports `__wasi_path_remove_directory` even when
|
|
// `AT_REMOVEDIR` isn't passed. Instead, use a specialized function which
|
|
// just imports `__wasi_path_unlink_file`.
|
|
return __wasilibc_unlinkat(dirfd, relative_path);
|
|
}
|
|
|
|
int rmdir(const char *path) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(path, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
return __wasilibc_rmdirat(dirfd, relative_path);
|
|
}
|
|
|
|
int remove(const char *path) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(path, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
// First try to remove it as a file.
|
|
int r = __wasilibc_unlinkat(dirfd, relative_path);
|
|
if (r != 0 && (errno == EISDIR || errno == ENOTCAPABLE)) {
|
|
// That failed, but it might be a directory.
|
|
r = __wasilibc_rmdirat(dirfd, relative_path);
|
|
|
|
// If it isn't a directory, we lack capabilities to remove it as a file.
|
|
if (errno == ENOTDIR)
|
|
errno = ENOTCAPABLE;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int mkdir(const char *path, mode_t mode) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(path, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
return mkdirat(dirfd, relative_path, mode);
|
|
}
|
|
|
|
DIR *opendir(const char *dirname) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(dirname, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return NULL;
|
|
}
|
|
|
|
return opendirat(dirfd, relative_path);
|
|
}
|
|
|
|
int scandir(
|
|
const char *restrict dir,
|
|
struct dirent ***restrict namelist,
|
|
int (*filter)(const struct dirent *),
|
|
int (*compar)(const struct dirent **, const struct dirent **)
|
|
) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(dir, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
return scandirat(dirfd, relative_path, namelist, filter, compar);
|
|
}
|
|
|
|
int symlink(const char *target, const char *linkpath) {
|
|
char *relative_path;
|
|
int dirfd = find_relpath(linkpath, &relative_path);
|
|
|
|
// If we can't find a preopen for it, indicate that we lack capabilities.
|
|
if (dirfd == -1) {
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
return symlinkat(target, dirfd, relative_path);
|
|
}
|
|
|
|
int link(const char *old, const char *new) {
|
|
char *old_relative_path;
|
|
int old_dirfd = find_relpath_alt(old, &old_relative_path);
|
|
|
|
if (old_dirfd != -1) {
|
|
char *new_relative_path;
|
|
int new_dirfd = find_relpath(new, &new_relative_path);
|
|
|
|
if (new_dirfd != -1)
|
|
return linkat(old_dirfd, old_relative_path,
|
|
new_dirfd, new_relative_path, 0);
|
|
}
|
|
|
|
// We couldn't find a preopen for it; indicate that we lack capabilities.
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|
|
|
|
int rename(const char *old, const char *new) {
|
|
char *old_relative_path;
|
|
int old_dirfd = find_relpath_alt(old, &old_relative_path);
|
|
|
|
if (old_dirfd != -1) {
|
|
char *new_relative_path;
|
|
int new_dirfd = find_relpath(new, &new_relative_path);
|
|
|
|
if (new_dirfd != -1)
|
|
return renameat(old_dirfd, old_relative_path,
|
|
new_dirfd, new_relative_path);
|
|
}
|
|
|
|
// We couldn't find a preopen for it; indicate that we lack capabilities.
|
|
errno = ENOTCAPABLE;
|
|
return -1;
|
|
}
|