Skip to main content

priv/c_src/wamr/shared/platform/zephyr/zephyr_file.c

/*
 * 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 <string.h>
#include <stdlib.h>

#include <zephyr/fs/fs.h>
#include <zephyr/fs/fs_interface.h>
#include <zephyr/fs/fs_sys.h>
#include <zephyr/fs/littlefs.h>

/* 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->dir_index = 0;
            size_t path_len = strlen(path) + 1;
            ptr->path = BH_MALLOC(path_len);
            if (ptr->path == NULL) {
                ptr->used = false;
                k_mutex_unlock(&desc_array_mutex);
                return NULL;
            }
            strcpy(ptr->path, path);
            *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
            bytes_read being a non-negative value was already checked */
        if ((size_t)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
            bytes_read being a non-negative value was already checked */
        if ((size_t)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
            bytes_read being a non-negative value was already checked */
        if ((size_t)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
            bytes_read being a non-negative value was already checked */
        if ((size_t)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) {
            size_t new_path_len = strlen(abs_new_path) + 1;
            char *new_path_copy = BH_MALLOC(new_path_len);
            if (new_path_copy != NULL) {
                strcpy(new_path_copy, abs_new_path);
                BH_FREE(ptr->path);
                ptr->path = new_path_copy;
            }
            else {
                k_mutex_unlock(&desc_array_mutex);
                return __WASI_ENOMEM;
            }
            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);
}