/* * Copyright (C) 2024 Grenoble INP - ESISAR. All rights reserved. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ #include "platform_api_vmcore.h" #include "platform_api_extension.h" #include "libc_errno.h" #include #include #include #include #include #include /* Notes: * This is the implementation of a POSIX-like file system interface for Zephyr. * To manage our file descriptors, we created a struct `zephyr_fs_desc` that * represent a zephyr file descriptor and hold useful informations. * We also created a file descriptor table to keep track of all the file * descriptors. * * To pass the file descriptor reference to the higher level abstraction, we * pass the index of the fd table to an `os_file_handle` struct. * Then in the WASI implementation layer we can retrieve the file descriptor * reference. * * We also fake the stdin, stdout and stderr file descriptors. * We do not handle write on stdin and read on stdin, stdout and stderr. */ // No OS API wrapper (Zephyr): // file: // off_t fs_tell(struct fs_file_t *zfp) // file system: // int fs_mount(struct fs_mount_t *mp) // int fs_unmount(struct fs_mount_t *mp // int fs_readmount(int *index, const char **name) // int fs_statvfs(const char *path, struct fs_statvfs *stat) // int fs_mkfs(int fs_type, uintptr_t dev_id, void *cfg, int flags) // int fs_register(int type, const struct fs_file_system_t *fs) // int fs_unregister(int type, const struct fs_file_system_t *fs) // We will take the maximum number of open files // from the Zephyr POSIX configuration #define CONFIG_WASI_MAX_OPEN_FILES CONFIG_ZVFS_OPEN_MAX static inline bool os_is_virtual_fd(int fd) { switch (fd) { case STDIN_FILENO: case STDOUT_FILENO: case STDERR_FILENO: return true; default: return false; }; } // Macro to retrieve a file system descriptor and check it's validity. // fd's 0-2 are reserved for standard streams, hence the by-3 offsets. #define GET_FILE_SYSTEM_DESCRIPTOR(fd, ptr) \ do { \ if (os_is_virtual_fd(fd)) { \ ptr = NULL; \ break; \ } \ if (fd < 3 || fd >= CONFIG_WASI_MAX_OPEN_FILES + 3) { \ return __WASI_EBADF; \ } \ k_mutex_lock(&desc_array_mutex, K_FOREVER); \ ptr = &desc_array[(int)fd - 3]; \ if (!ptr->used) { \ k_mutex_unlock(&desc_array_mutex); \ return __WASI_EBADF; \ } \ k_mutex_unlock(&desc_array_mutex); \ } while (0) // Array to keep track of file system descriptors. static struct zephyr_fs_desc desc_array[CONFIG_WASI_MAX_OPEN_FILES]; // mutex to protect the file descriptor array K_MUTEX_DEFINE(desc_array_mutex); static char prestat_dir[MAX_FILE_NAME + 1]; bool build_absolute_path(char *abs_path, size_t abs_path_len, const char *path) { if (!path) { abs_path[0] = '\0'; return false; } size_t len1 = strlen(prestat_dir); size_t len2 = strlen(path); if (len1 + 1 + len2 + 1 > abs_path_len) { abs_path[0] = '\0'; // Empty string on error return false; // Truncation would occur } snprintf(abs_path, abs_path_len, "%s/%s", prestat_dir, path); return true; } static struct zephyr_fs_desc * zephyr_fs_alloc_obj(bool is_dir, const char *path, int *index) { struct zephyr_fs_desc *ptr = NULL; *index = -1; // give a default value to index in case table is full k_mutex_lock(&desc_array_mutex, K_FOREVER); for (int i = 0; i < CONFIG_WASI_MAX_OPEN_FILES; i++) { if (desc_array[i].used == false) { ptr = &desc_array[i]; ptr->used = true; ptr->is_dir = is_dir; ptr->path = bh_strdup(path); ptr->dir_index = 0; if (ptr->path == NULL) { ptr->used = false; k_mutex_unlock(&desc_array_mutex); return NULL; } *index = i + 3; break; } } k_mutex_unlock(&desc_array_mutex); if (ptr == NULL) { printk("Error: all file descriptor slots are in use (max = %d)\n", CONFIG_WASI_MAX_OPEN_FILES); } return ptr; } static inline void zephyr_fs_free_obj(struct zephyr_fs_desc *ptr) { BH_FREE(ptr->path); ptr->path = NULL; ptr->used = false; } /* /!\ Needed for socket to work */ __wasi_errno_t os_fstat(os_file_handle handle, struct __wasi_filestat_t *buf) { struct zephyr_fs_desc *ptr = NULL; int socktype, rc; if (!handle->is_sock) { if (os_is_virtual_fd(handle->fd)) { buf->st_filetype = __WASI_FILETYPE_CHARACTER_DEVICE; buf->st_size = 0; buf->st_atim = 0; buf->st_mtim = 0; buf->st_ctim = 0; return __WASI_ESUCCESS; } GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); // Get file information using Zephyr's fs_stat function struct fs_dirent entry; rc = fs_stat(ptr->path, &entry); if (rc < 0) { return convert_errno(-rc); } // Fill in the __wasi_filestat_t structure buf->st_dev = 0; // Zephyr's fs_stat doesn't provide a device ID buf->st_ino = 0; // Zephyr's fs_stat doesn't provide an inode number buf->st_filetype = entry.type == FS_DIR_ENTRY_DIR ? __WASI_FILETYPE_DIRECTORY : __WASI_FILETYPE_REGULAR_FILE; buf->st_nlink = 1; // Zephyr's fs_stat doesn't provide a link count buf->st_size = entry.size; buf->st_atim = 0; // Zephyr's fs_stat doesn't provide timestamps buf->st_mtim = 0; buf->st_ctim = 0; return __WASI_ESUCCESS; // return os_fstatat(handle, ptr->path, buf, 0); } else { // socklen_t socktypelen = sizeof(socktype); // rc = zsock_getsockopt(handle->fd, SOL_SOCKET, SO_TYPE, &socktype, // &socktypelen); Using `zsock_getsockopt` will add a dependency on the // network stack // TODO: may add a type to the `zephyr_handle`. rc = 1; socktype = SOCK_STREAM; if (rc < 0) { return convert_errno(-rc); } switch (socktype) { case SOCK_DGRAM: buf->st_filetype = __WASI_FILETYPE_SOCKET_DGRAM; break; case SOCK_STREAM: buf->st_filetype = __WASI_FILETYPE_SOCKET_STREAM; break; default: buf->st_filetype = __WASI_FILETYPE_UNKNOWN; break; } buf->st_size = 0; buf->st_atim = 0; buf->st_mtim = 0; buf->st_ctim = 0; return __WASI_ESUCCESS; } } __wasi_errno_t os_fstatat(os_file_handle handle, const char *path, struct __wasi_filestat_t *buf, __wasi_lookupflags_t lookup_flags) { struct fs_dirent entry; int rc; if (handle->fd < 0) { return __WASI_EBADF; } char abs_path[MAX_FILE_NAME + 1]; if (handle == NULL) { return __WASI_EINVAL; // Or another appropriate error code } if (!build_absolute_path(abs_path, sizeof(abs_path), path)) { return __WASI_ENOMEM; } // Get file information using Zephyr's fs_stat function rc = fs_stat(abs_path, &entry); if (rc < 0) { return convert_errno(-rc); } // Fill in the __wasi_filestat_t structure buf->st_dev = 0; // Zephyr's fs_stat doesn't provide a device ID // DSK: setting this to 0, in addition to d_ino = 1 causes failures with // readdir() So, here's a hack to to avoid this. buf->st_ino = 1; // Zephyr's fs_stat doesn't provide an inode number. buf->st_filetype = entry.type == FS_DIR_ENTRY_DIR ? __WASI_FILETYPE_DIRECTORY : __WASI_FILETYPE_REGULAR_FILE; buf->st_nlink = 1; // Zephyr's fs_stat doesn't provide a link count buf->st_size = entry.size; buf->st_atim = 0; // Zephyr's fs_stat doesn't provide timestamps buf->st_mtim = 0; buf->st_ctim = 0; return __WASI_ESUCCESS; } __wasi_errno_t os_file_get_fdflags(os_file_handle handle, __wasi_fdflags_t *flags) { struct zephyr_fs_desc *ptr = NULL; if (os_is_virtual_fd(handle->fd)) { *flags = 0; return __WASI_ESUCCESS; } GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); if ((ptr->file.flags & FS_O_APPEND) != 0) { *flags |= __WASI_FDFLAG_APPEND; } /* Others flags: * - __WASI_FDFLAG_DSYNC * - __WASI_FDFLAG_RSYNC * - __WASI_FDFLAG_SYNC * - __WASI_FDFLAG_NONBLOCK * Have no equivalent in Zephyr. */ return __WASI_ESUCCESS; } __wasi_errno_t os_file_set_fdflags(os_file_handle handle, __wasi_fdflags_t flags) { if (os_is_virtual_fd(handle->fd)) { return __WASI_ESUCCESS; } struct zephyr_fs_desc *ptr = NULL; GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); if ((flags & __WASI_FDFLAG_APPEND) != 0) { ptr->file.flags |= FS_O_APPEND; } /* Same as above */ return __WASI_ESUCCESS; } __wasi_errno_t os_fdatasync(os_file_handle handle) { return os_fsync(handle); } __wasi_errno_t os_fsync(os_file_handle handle) { if (os_is_virtual_fd(handle->fd)) { return __WASI_ESUCCESS; } struct zephyr_fs_desc *ptr = NULL; int rc = 0; GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); if (ptr->is_dir) { return __WASI_EISDIR; } rc = fs_sync(&ptr->file); if (rc < 0) { return convert_errno(-rc); } return __WASI_ESUCCESS; } __wasi_errno_t os_open_preopendir(const char *path, os_file_handle *out) { int rc, index; struct zephyr_fs_desc *ptr; *out = BH_MALLOC(sizeof(struct zephyr_handle)); if (*out == NULL) { return __WASI_ENOMEM; } ptr = zephyr_fs_alloc_obj(true, path, &index); if (ptr == NULL) { BH_FREE(*out); return __WASI_EMFILE; } fs_dir_t_init(&ptr->dir); rc = fs_opendir(&ptr->dir, path); if (rc < 0) { zephyr_fs_free_obj(ptr); BH_FREE(*out); return convert_errno(-rc); } (*out)->fd = index; (*out)->is_sock = false; strncpy(prestat_dir, path, MAX_FILE_NAME + 1); prestat_dir[MAX_FILE_NAME] = '\0'; return __WASI_ESUCCESS; } static int wasi_flags_to_zephyr(__wasi_oflags_t oflags, __wasi_fdflags_t fd_flags, __wasi_lookupflags_t lookup_flags, wasi_libc_file_access_mode access_mode) { int mode = 0; // Convert open flags. if ((oflags & __WASI_O_CREAT) != 0) { mode |= FS_O_CREATE; } if (((oflags & __WASI_O_EXCL) != 0) || ((oflags & __WASI_O_TRUNC) != 0) || ((oflags & __WASI_O_DIRECTORY) != 0)) { /* Zephyr is not POSIX no equivalent for these flags */ /* __WASI_O_DIRECTORY: Open shouldn't handle directories */ // TODO: log warning } // Convert file descriptor flags. if ((fd_flags & __WASI_FDFLAG_APPEND) != 0) { mode |= FS_O_APPEND; } if (((fd_flags & __WASI_FDFLAG_DSYNC) != 0) || ((fd_flags & __WASI_FDFLAG_RSYNC) != 0) || ((fd_flags & __WASI_FDFLAG_SYNC) != 0) || ((fd_flags & __WASI_FDFLAG_NONBLOCK) != 0)) { /* Zephyr is not POSIX no equivalent for these flags */ // TODO: log warning } // Convert lookup flag. if ((lookup_flags & __WASI_LOOKUP_SYMLINK_FOLLOW) == 0) { /* Zephyr is not POSIX no equivalent for these flags */ // TODO: log warning return __WASI_ENOTSUP; } // Convert access mode. switch (access_mode) { case WASI_LIBC_ACCESS_MODE_READ_WRITE: mode |= FS_O_RDWR; break; case WASI_LIBC_ACCESS_MODE_READ_ONLY: mode |= FS_O_READ; break; case WASI_LIBC_ACCESS_MODE_WRITE_ONLY: mode |= FS_O_WRITE; break; default: // TODO: log warning break; } return mode; } __wasi_errno_t os_openat(os_file_handle handle, const char *path, __wasi_oflags_t oflags, __wasi_fdflags_t fd_flags, __wasi_lookupflags_t lookup_flags, wasi_libc_file_access_mode access_mode, os_file_handle *out) { /* * `handle` will be unused because zephyr doesn't expose an openat * function and don't seem to have the concept of relative path. * We fill `out` with a new file descriptor. */ int rc, index; struct zephyr_fs_desc *ptr = NULL; char abs_path[MAX_FILE_NAME + 1]; *out = BH_MALLOC(sizeof(struct zephyr_handle)); if (*out == NULL) { return __WASI_ENOMEM; } if (!build_absolute_path(abs_path, sizeof(abs_path), path)) { BH_FREE(*out); return __WASI_ENOMEM; } // Treat directories as a special case bool is_dir = oflags & __WASI_O_DIRECTORY; ptr = zephyr_fs_alloc_obj(is_dir, abs_path, &index); if (!ptr && (index < 0)) { BH_FREE(*out); return __WASI_EMFILE; } if (is_dir) { fs_dir_t_init(&ptr->dir); // fs_opendir() is called in libc later - don't call here } else { // Is a file int zmode = wasi_flags_to_zephyr(oflags, fd_flags, lookup_flags, access_mode); fs_file_t_init(&ptr->file); rc = fs_open(&ptr->file, abs_path, zmode); if (rc < 0) { zephyr_fs_free_obj(ptr); BH_FREE(*out); return convert_errno(-rc); } } (*out)->fd = index; (*out)->is_sock = false; return __WASI_ESUCCESS; } __wasi_errno_t os_file_get_access_mode(os_file_handle handle, wasi_libc_file_access_mode *access_mode) { if (handle->fd == STDIN_FILENO) { *access_mode = WASI_LIBC_ACCESS_MODE_READ_ONLY; return __WASI_ESUCCESS; } else if (handle->fd == STDOUT_FILENO || handle->fd == STDERR_FILENO) { *access_mode = WASI_LIBC_ACCESS_MODE_WRITE_ONLY; return __WASI_ESUCCESS; } struct zephyr_fs_desc *ptr = NULL; if (handle->is_sock) { // for socket we can use the following code // TODO: Need to determine better logic *access_mode = WASI_LIBC_ACCESS_MODE_READ_WRITE; return __WASI_ESUCCESS; } GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); if (ptr->is_dir) { // DSK: is this actually the correct mode for a dir? *access_mode = WASI_LIBC_ACCESS_MODE_READ_WRITE; return __WASI_ESUCCESS; } if ((ptr->file.flags & FS_O_RDWR) != 0) { *access_mode = WASI_LIBC_ACCESS_MODE_READ_WRITE; } else if ((ptr->file.flags & FS_O_READ) != 0) { *access_mode = WASI_LIBC_ACCESS_MODE_READ_ONLY; } else if ((ptr->file.flags & FS_O_WRITE) != 0) { *access_mode = WASI_LIBC_ACCESS_MODE_WRITE_ONLY; } else { // we return read/write by default *access_mode = WASI_LIBC_ACCESS_MODE_READ_WRITE; } return __WASI_ESUCCESS; } __wasi_errno_t os_close(os_file_handle handle, bool is_stdio) { int rc; struct zephyr_fs_desc *ptr = NULL; if (is_stdio) return __WASI_ESUCCESS; if (handle->is_sock) { rc = zsock_close(handle->fd); } // Handle is assumed to be a file descriptor else { GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); rc = ptr->is_dir ? fs_closedir(&ptr->dir) : fs_close(&ptr->file); zephyr_fs_free_obj(ptr); // free in any case. } BH_FREE(handle); if (rc < 0) { return convert_errno(-rc); } return __WASI_ESUCCESS; } __wasi_errno_t os_preadv(os_file_handle handle, const struct __wasi_iovec_t *iov, int iovcnt, __wasi_filesize_t offset, size_t *nread) { if (handle->fd == STDIN_FILENO) { return __WASI_ENOSYS; } struct zephyr_fs_desc *ptr = NULL; int rc; ssize_t total_read = 0; GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); // Seek to the offset rc = fs_seek(&ptr->file, offset, FS_SEEK_SET); if (rc < 0) { return convert_errno(-rc); } // Read data into each buffer for (int i = 0; i < iovcnt; i++) { ssize_t bytes_read = fs_read(&ptr->file, iov[i].buf, iov[i].buf_len); if (bytes_read < 0) { return convert_errno(-bytes_read); } total_read += bytes_read; // If we read less than we asked for, stop reading if (bytes_read < iov[i].buf_len) { break; } } *nread = total_read; return __WASI_ESUCCESS; } __wasi_errno_t os_pwritev(os_file_handle handle, const struct __wasi_ciovec_t *iov, int iovcnt, __wasi_filesize_t offset, size_t *nwritten) { struct zephyr_fs_desc *ptr = NULL; ssize_t total_written = 0; GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); // Seek to the offset int rc = fs_seek(&ptr->file, offset, FS_SEEK_SET); if (rc < 0) { return convert_errno(-rc); } // Write data from each buffer for (int i = 0; i < iovcnt; i++) { ssize_t bytes_written = fs_write(&ptr->file, iov[i].buf, iov[i].buf_len); if (bytes_written < 0) { return convert_errno(-bytes_written); } total_written += bytes_written; // If we wrote less than we asked for, stop writing if (bytes_written < iov[i].buf_len) { break; } } *nwritten = total_written; return __WASI_ESUCCESS; } __wasi_errno_t os_readv(os_file_handle handle, const struct __wasi_iovec_t *iov, int iovcnt, size_t *nread) { struct zephyr_fs_desc *ptr = NULL; ssize_t total_read = 0; GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); // Read data into each buffer for (int i = 0; i < iovcnt; i++) { ssize_t bytes_read = fs_read(&ptr->file, iov[i].buf, iov[i].buf_len); if (bytes_read < 0) { // If an error occurred, return it return convert_errno(-bytes_read); } total_read += bytes_read; // If we read less than we asked for, stop reading if (bytes_read < iov[i].buf_len) { break; } } *nread = total_read; return __WASI_ESUCCESS; } /* With wasi-libc we need to redirect write on stdout/err to printf */ // TODO: handle write on stdin __wasi_errno_t os_writev(os_file_handle handle, const struct __wasi_ciovec_t *iov, int iovcnt, size_t *nwritten) { ssize_t total_written = 0; if (os_is_virtual_fd(handle->fd)) { FILE *fd = (handle->fd == STDERR_FILENO) ? stderr : stdout; for (int i = 0; i < iovcnt; i++) { ssize_t bytes_written = fwrite(iov[i].buf, 1, iov[i].buf_len, fd); if (bytes_written < 0) return convert_errno(-bytes_written); total_written += bytes_written; } *nwritten = total_written; return __WASI_ESUCCESS; } struct zephyr_fs_desc *ptr = NULL; GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); // Write data from each buffer for (int i = 0; i < iovcnt; i++) { ssize_t bytes_written = fs_write(&ptr->file, iov[i].buf, iov[i].buf_len); if (bytes_written < 0) { return convert_errno(-bytes_written); } total_written += bytes_written; // If we wrote less than we asked for, stop writing if (bytes_written < iov[i].buf_len) { break; } } *nwritten = total_written; return __WASI_ESUCCESS; } __wasi_errno_t os_fallocate(os_file_handle handle, __wasi_filesize_t offset, __wasi_filesize_t length) { return __WASI_ENOSYS; } __wasi_errno_t os_ftruncate(os_file_handle handle, __wasi_filesize_t size) { if (os_is_virtual_fd(handle->fd)) { return __WASI_EINVAL; } struct zephyr_fs_desc *ptr = NULL; GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); int rc = fs_truncate(&ptr->file, (off_t)size); if (rc < 0) { return convert_errno(-rc); } return __WASI_ESUCCESS; } __wasi_errno_t os_futimens(os_file_handle handle, __wasi_timestamp_t access_time, __wasi_timestamp_t modification_time, __wasi_fstflags_t fstflags) { return __WASI_ENOSYS; } __wasi_errno_t os_utimensat(os_file_handle handle, const char *path, __wasi_timestamp_t access_time, __wasi_timestamp_t modification_time, __wasi_fstflags_t fstflags, __wasi_lookupflags_t lookup_flags) { return __WASI_ENOSYS; } __wasi_errno_t os_readlinkat(os_file_handle handle, const char *path, char *buf, size_t bufsize, size_t *nread) { return __WASI_ENOSYS; } __wasi_errno_t os_linkat(os_file_handle from_handle, const char *from_path, os_file_handle to_handle, const char *to_path, __wasi_lookupflags_t lookup_flags) { return __WASI_ENOSYS; } __wasi_errno_t os_symlinkat(const char *old_path, os_file_handle handle, const char *new_path) { return __WASI_ENOSYS; } __wasi_errno_t os_mkdirat(os_file_handle handle, const char *path) { struct zephyr_fs_desc *ptr = NULL; int index, rc; char abs_path[MAX_FILE_NAME + 1]; if (handle == NULL) { return __WASI_EINVAL; // Or another appropriate error code } if (!build_absolute_path(abs_path, sizeof(abs_path), path)) { return __WASI_ENOMEM; } rc = fs_mkdir(abs_path); if (rc < 0) { return convert_errno(-rc); } ptr = zephyr_fs_alloc_obj(true, abs_path, &index); if (!ptr) { fs_unlink(abs_path); return __WASI_EMFILE; } fs_dir_t_init(&ptr->dir); handle->fd = index; handle->is_sock = false; return __WASI_ESUCCESS; } // DSK: Somewhere along the WASI libc implementation path, the knowledge // was lost that `old_handle` and `new_handle` refer to directories that // contain the files to be renamed, rather than the file fds themselves: // // __wasilibc_nocwd_renameat(old_dirfd, old_relative_path, // new_dirfd, new_relative_path); // // Therefore we won't mess with the supplied fd's, and work only off // of the supplied paths. Note: this will change when more than one // pre-opened dir is supported in the future. __wasi_errno_t os_renameat(os_file_handle old_handle, const char *old_path, os_file_handle new_handle, const char *new_path) { // directories, safe to ignore (void)old_handle; (void)new_handle; char abs_old_path[MAX_FILE_NAME + 1]; char abs_new_path[MAX_FILE_NAME + 1]; if (!build_absolute_path(abs_old_path, sizeof(abs_old_path), old_path)) { return __WASI_ENOMEM; } if (!build_absolute_path(abs_new_path, sizeof(abs_new_path), new_path)) { return __WASI_ENOMEM; } // Attempt to perform the rename int rc = fs_rename(abs_old_path, abs_new_path); if (rc < 0) { return convert_errno(-rc); } // If there is an allocated fd in our table, update the descriptor table // entry DSK: better approach here? k_mutex_lock(&desc_array_mutex, K_FOREVER); for (int i = 0; i < CONFIG_WASI_MAX_OPEN_FILES; i++) { struct zephyr_fs_desc *ptr = &desc_array[i]; if (ptr->used && ptr->path && strcmp(ptr->path, abs_old_path) == 0) { char *new_path_copy = bh_strdup(new_path); if (new_path_copy != NULL) { BH_FREE(ptr->path); ptr->path = new_path_copy; } break; // Only one descriptor should match } } k_mutex_unlock(&desc_array_mutex); return __WASI_ESUCCESS; } // DSK: Same thing as renameat: `handle` refers to the containing directory, // not the file handle to unlink. We ignore the handle and use the path // exclusively. // // TODO: is there anything we need to do in case is_dir=true? __wasi_errno_t os_unlinkat(os_file_handle handle, const char *path, bool is_dir) { (void)handle; char abs_path[MAX_FILE_NAME + 1]; if (!build_absolute_path(abs_path, sizeof(abs_path), path)) { return __WASI_ENOMEM; } int rc = fs_unlink(abs_path); if (rc < 0) { return convert_errno(-rc); } // Search for any active descriptor referencing this path and free it. k_mutex_lock(&desc_array_mutex, K_FOREVER); for (int i = 0; i < CONFIG_WASI_MAX_OPEN_FILES; i++) { struct zephyr_fs_desc *ptr = &desc_array[i]; if (ptr->used && ptr->path && strcmp(ptr->path, abs_path) == 0) { zephyr_fs_free_obj(ptr); break; } } k_mutex_unlock(&desc_array_mutex); return __WASI_ESUCCESS; } __wasi_errno_t os_lseek(os_file_handle handle, __wasi_filedelta_t offset, __wasi_whence_t whence, __wasi_filesize_t *new_offset) { if (os_is_virtual_fd(handle->fd)) { return __WASI_ESPIPE; // Seeking not supported on character streams } struct zephyr_fs_desc *ptr = NULL; int zwhence; GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); // They have the same value but this is more explicit switch (whence) { case __WASI_WHENCE_SET: zwhence = FS_SEEK_SET; break; case __WASI_WHENCE_CUR: zwhence = FS_SEEK_CUR; break; case __WASI_WHENCE_END: zwhence = FS_SEEK_END; break; default: return __WASI_EINVAL; } off_t rc = fs_seek(&ptr->file, (off_t)offset, zwhence); if (rc < 0) { return convert_errno(-rc); } *new_offset = (__wasi_filesize_t)rc; return __WASI_ESUCCESS; } __wasi_errno_t os_fadvise(os_file_handle handle, __wasi_filesize_t offset, __wasi_filesize_t length, __wasi_advice_t advice) { return __WASI_ENOSYS; } __wasi_errno_t os_isatty(os_file_handle handle) { if (os_is_virtual_fd(handle->fd)) { return __WASI_ESUCCESS; } return __WASI_ENOTTY; } os_file_handle os_convert_stdin_handle(os_raw_file_handle raw_stdin) { os_file_handle handle = BH_MALLOC(sizeof(struct zephyr_handle)); if (!handle) return NULL; handle->fd = STDIN_FILENO; handle->is_sock = false; return handle; } os_file_handle os_convert_stdout_handle(os_raw_file_handle raw_stdout) { os_file_handle handle = BH_MALLOC(sizeof(struct zephyr_handle)); if (!handle) return NULL; handle->fd = STDOUT_FILENO; handle->is_sock = false; return handle; } os_file_handle os_convert_stderr_handle(os_raw_file_handle raw_stderr) { os_file_handle handle = BH_MALLOC(sizeof(struct zephyr_handle)); if (!handle) return NULL; handle->fd = STDERR_FILENO; handle->is_sock = false; return handle; } __wasi_errno_t os_fdopendir(os_file_handle handle, os_dir_stream *dir_stream) { /* Here we assume that either mdkdir or preopendir was called * before otherwise function will fail. */ struct zephyr_fs_desc *ptr = NULL; GET_FILE_SYSTEM_DESCRIPTOR(handle->fd, ptr); if (!ptr->is_dir) { return __WASI_ENOTDIR; } int rc = fs_opendir(&ptr->dir, ptr->path); if (rc < 0) { return convert_errno(-rc); } /* we store the fd in the `os_dir_stream` to use other function */ *dir_stream = handle->fd; return __WASI_ESUCCESS; } // DSK: simple open and close to rewind index. __wasi_errno_t os_rewinddir(os_dir_stream dir_stream) { struct zephyr_fs_desc *ptr = NULL; GET_FILE_SYSTEM_DESCRIPTOR(dir_stream, ptr); if (!ptr->is_dir) return __WASI_ENOTDIR; int rc = fs_closedir(&ptr->dir); // Close current stream if (rc < 0) return convert_errno(-rc); rc = fs_opendir(&ptr->dir, ptr->path); // Reopen from start if (rc < 0) return convert_errno(-rc); ptr->dir_index = 0; // Reset virtual position tracker return __WASI_ESUCCESS; } // DSK: start from 0 and linear seek since there's no cookies in the zephyr fs // TODO: duplicated code with rewinddir __wasi_errno_t os_seekdir(os_dir_stream dir_stream, __wasi_dircookie_t position) { struct zephyr_fs_desc *ptr = NULL; GET_FILE_SYSTEM_DESCRIPTOR(dir_stream, ptr); if (!ptr->is_dir) return __WASI_ENOTDIR; int rc = fs_closedir(&ptr->dir); if (rc < 0) return convert_errno(-rc); rc = fs_opendir(&ptr->dir, ptr->path); if (rc < 0) return convert_errno(-rc); // Emulate seek by re-reading entries up to 'position' struct fs_dirent tmp; for (__wasi_dircookie_t i = 0; i < position; i++) { rc = fs_readdir(&ptr->dir, &tmp); if (rc < 0) return convert_errno(-rc); if (tmp.name[0] == '\0') break; // End of directory } ptr->dir_index = position; return __WASI_ESUCCESS; } __wasi_errno_t os_readdir(os_dir_stream dir_stream, __wasi_dirent_t *entry, const char **d_name) { struct fs_dirent fs_entry; struct zephyr_fs_desc *ptr = NULL; GET_FILE_SYSTEM_DESCRIPTOR(dir_stream, ptr); if (!ptr->is_dir) { return __WASI_ENOTDIR; } int rc = fs_readdir(&ptr->dir, &fs_entry); if (rc < 0) { return convert_errno(-rc); } if (fs_entry.name[0] == '\0') { // DSK: the caller expects the name buffer to be null // when we've reached the end of the directory. *d_name = NULL; return __WASI_ESUCCESS; } // DSK: emulated increasing value for rewinddir and seekdir entry->d_next = ++ptr->dir_index; // DSK: A hack to get readdir working. This needs to be non-zero along with // st_ino for the libc side of readdir to work correctly. entry->d_ino = 1 + ptr->dir_index; entry->d_namlen = strlen(fs_entry.name); entry->d_type = fs_entry.type == FS_DIR_ENTRY_DIR ? __WASI_FILETYPE_DIRECTORY : __WASI_FILETYPE_REGULAR_FILE; // DSK: name exists in fs_entry and we need to return it static char name_buf[MAX_FILE_NAME + 1]; strncpy(name_buf, fs_entry.name, MAX_FILE_NAME); name_buf[MAX_FILE_NAME] = '\0'; *d_name = name_buf; return __WASI_ESUCCESS; } __wasi_errno_t os_closedir(os_dir_stream dir_stream) { struct zephyr_fs_desc *ptr = NULL; GET_FILE_SYSTEM_DESCRIPTOR(dir_stream, ptr); if (!ptr->is_dir) { return __WASI_ENOTDIR; } int rc = fs_closedir(&ptr->dir); zephyr_fs_free_obj(ptr); // free in any case. if (rc < 0) { return convert_errno(-rc); } return __WASI_ESUCCESS; } os_dir_stream os_get_invalid_dir_stream() { return OS_DIR_STREAM_INVALID; } bool os_is_dir_stream_valid(os_dir_stream *dir_stream) { // DSK: this probably needs a check... return false; } bool os_is_handle_valid(os_file_handle *handle) { if (handle == NULL || *handle == NULL) { return false; } return (*handle)->fd > -1; } char * os_realpath(const char *path, char *resolved_path) { /* In fact we could implement a path resolving method, because every paths * are at one point put into memory. * We could then maintain a 'tree' to represent the file system. * --> The file system root is easily accessable with: * * (fs_dir_t) dir.mp->mnt_point * * (fs_file_t) file.mp->mnt_point * But we will just use absolute path for now. */ if ((!path) || (strlen(path) > PATH_MAX)) { // Invalid input, path has to be valid and less than PATH_MAX return NULL; } return strncpy(resolved_path, path, PATH_MAX); } bool os_compare_file_handle(os_file_handle handle1, os_file_handle handle2) { return handle1->fd == handle2->fd && handle1->is_sock == handle2->is_sock; } bool os_is_stdin_handle(os_file_handle handle) { return (handle == (os_file_handle)stdin); } bool os_is_stdout_handle(os_file_handle handle) { return (handle == (os_file_handle)stdout); } bool os_is_stderr_handle(os_file_handle handle) { return (handle == (os_file_handle)stderr); }