diff --git a/fastrpc/hexagonfs.c b/fastrpc/hexagonfs.c
new file mode 100644
index 0000000..7bc6133
--- /dev/null
+++ b/fastrpc/hexagonfs.c
@@ -0,0 +1,368 @@
+/*
+ * Virtual read-only filesystem for Hexagon processors
+ *
+ * Copyright (C) 2023 Richard Acayan
+ *
+ * This file is part of sensh.
+ *
+ * Sensh is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "hexagonfs.h"
+
+#define HEXAGONFS_MAX_FD 256
+
+#define DEFINE_VIRT_DIR(dirname, files...) \
+ (struct hexagonfs_dirent) { \
+ .name = dirname, \
+ .ops = &hexagonfs_virt_dir_ops, \
+ .u.dir = (struct hexagonfs_dirent *[]) { files }, \
+ }
+
+#define DEFINE_MAPPED(dirname, path) \
+ (struct hexagonfs_dirent) { \
+ .name = dirname, \
+ .ops = &hexagonfs_mapped_ops, \
+ .u.phys = path, \
+ }
+
+#define DEFINE_SYSFILE(dirname, func) \
+ (struct hexagonfs_dirent) { \
+ .name = dirname, \
+ .ops = &hexagonfs_sysfs_ops, \
+ .u.func = func, \
+ }
+
+static struct hexagonfs_dirent root_dir = DEFINE_VIRT_DIR("/",
+ &DEFINE_VIRT_DIR("mnt",
+ &DEFINE_VIRT_DIR("vendor",
+ &DEFINE_VIRT_DIR("persist",
+ &DEFINE_VIRT_DIR("sensors",
+ &DEFINE_MAPPED("registry",
+ "/var/lib/qcom/sensors"),
+ NULL,
+ ),
+ NULL,
+ ),
+ NULL,
+ ),
+ NULL,
+ ),
+ &DEFINE_VIRT_DIR("sys",
+ &DEFINE_VIRT_DIR("devices",
+ &DEFINE_VIRT_DIR("soc0",
+ &DEFINE_MAPPED("hw_platform",
+ "/sys/kernel/debug/qcom_socinfo/hardware_platform"),
+ &(struct hexagonfs_dirent) {
+ .name = "platform_subtype",
+ .ops = &hexagonfs_plat_subtype_name_ops,
+ .u.phys = "/sys/kernel/debug/qcom_socinfo/hardware_platform_subtype",
+ },
+ &DEFINE_MAPPED("platform_subtype_id",
+ "/sys/kernel/debug/qcom_socinfo/hardware_platform_subtype"),
+ &DEFINE_MAPPED("platform_version",
+ "/sys/kernel/debug/qcom_socinfo/platform_version"),
+ &DEFINE_MAPPED("revision",
+ "/sys/devices/soc0/revision"),
+ &DEFINE_MAPPED("soc_id",
+ "/sys/devices/soc0/soc_id"),
+ NULL,
+ ),
+ NULL,
+ ),
+ NULL,
+ ),
+ &DEFINE_VIRT_DIR("usr",
+ &DEFINE_VIRT_DIR("lib",
+ &DEFINE_VIRT_DIR("qcom",
+ &DEFINE_MAPPED("adsp",
+ "/usr/lib/qcom/adsp/"),
+ NULL,
+ ),
+ NULL,
+ ),
+ NULL,
+ ),
+ &DEFINE_VIRT_DIR("vendor",
+ &DEFINE_VIRT_DIR("etc",
+ &DEFINE_VIRT_DIR("sensors",
+ &DEFINE_MAPPED("config",
+ "/etc/qcom/sensors.d/"),
+ &DEFINE_MAPPED("sns_reg_config",
+ "/etc/qcom/sns_reg.conf"),
+ NULL,
+ ),
+ NULL,
+ ),
+ NULL,
+ ),
+ NULL,
+);
+
+static struct hexagonfs_fd *fds[HEXAGONFS_MAX_FD];
+
+static char *copy_segment_and_advance(const char *path,
+ bool *trailing_slash,
+ const char **next)
+{
+ const char *next_tmp;
+ char *segment;
+ size_t segment_len;
+
+ next_tmp = strchr(path, '/');
+ if (next_tmp == NULL) {
+ next_tmp = &path[strlen(path)];
+ *trailing_slash = false;
+ } else {
+ *trailing_slash = true;
+ }
+
+ segment_len = next_tmp - path;
+
+ while (*next_tmp == '/')
+ next_tmp++;
+
+ segment = malloc(segment_len + 1);
+ if (segment == NULL)
+ return NULL;
+
+ memcpy(segment, path, segment_len);
+ segment[segment_len] = 0;
+
+ *next = next_tmp;
+
+ return segment;
+}
+
+static struct hexagonfs_fd *pop_dir(struct hexagonfs_fd *dir,
+ struct hexagonfs_fd *root)
+{
+ struct hexagonfs_fd *up;
+
+ if (dir != root && dir->up != NULL) {
+ up = dir->up;
+ dir->ops->close(dir->data);
+ free(dir);
+ } else {
+ up = dir;
+ }
+
+ return up;
+}
+
+static int allocate_file_number(struct hexagonfs_fd *fd)
+{
+ size_t i;
+
+ for (i = 0; i < HEXAGONFS_MAX_FD; i++) {
+ if (fds[i] == NULL) {
+ fd->is_assigned = true;
+ fds[i] = fd;
+ return i;
+ }
+ }
+
+ return -EMFILE;
+}
+
+static void destroy_file_descriptor(struct hexagonfs_fd *fd)
+{
+ struct hexagonfs_fd *curr = fd;
+ struct hexagonfs_fd *next;
+
+ while (curr != NULL && !curr->is_assigned) {
+ next = curr->up;
+ curr->ops->close(curr->data);
+ free(curr);
+
+ curr = next;
+ }
+}
+
+int hexagonfs_open_root()
+{
+ struct hexagonfs_fd *fd;
+ int ret;
+
+ fd = malloc(sizeof(struct hexagonfs_fd));
+ if (fd == NULL)
+ return -ENOMEM;
+
+ fd->off = 0;
+ fd->is_assigned = false;
+ fd->up = NULL;
+ fd->ops = root_dir.ops;
+
+ ret = root_dir.ops->from_dirent(root_dir.u.ptr, true, &fd->data);
+ if (ret)
+ goto err;
+
+ ret = allocate_file_number(fd);
+ if (ret < 0)
+ goto err;
+
+ return ret;
+
+err:
+ destroy_file_descriptor(fd);
+ return ret;
+}
+
+int hexagonfs_openat(int rootfd, int dirfd, const char *name)
+{
+ struct hexagonfs_fd *fd;
+ const char *curr = name;
+ char *segment;
+ bool expect_dir;
+ int selected = dirfd;
+ int ret = 0;
+
+ if (*curr == '/') {
+ selected = rootfd;
+
+ while (*curr == '/')
+ curr++;
+ }
+
+ fd = fds[selected];
+
+ while (*curr != '\0' && !ret) {
+ segment = copy_segment_and_advance(curr, &expect_dir, &curr);
+ if (segment == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ if (!strcmp(segment, ".")) {
+ goto next;
+ } else if (!strcmp(segment, "..")) {
+ fd = pop_dir(fd, fds[rootfd]);
+ } else {
+ ret = fd->ops->openat(fd, segment, expect_dir, &fd);
+ }
+
+ next:
+ free(segment);
+ }
+
+ if (ret)
+ goto err;
+
+ ret = allocate_file_number(fd);
+ if (ret)
+ goto err;
+
+ return ret;
+
+err:
+ destroy_file_descriptor(fd);
+
+ return ret;
+}
+
+int hexagonfs_close(int fileno)
+{
+ struct hexagonfs_fd *fd;
+
+ if (fileno >= HEXAGONFS_MAX_FD)
+ return -EBADF;
+
+ fd = fds[fileno];
+ if (fd == NULL || fd->ops == NULL)
+ return -EBADF;
+
+ fd->is_assigned = false;
+ destroy_file_descriptor(fd);
+
+ fds[fileno] = NULL;
+
+ return 0;
+}
+
+int hexagonfs_lseek(int fileno, off_t off, int whence)
+{
+ struct hexagonfs_fd *fd;
+
+ if (fileno >= HEXAGONFS_MAX_FD)
+ return -EBADF;
+
+ fd = fds[fileno];
+ if (fd == NULL)
+ return -EBADF;
+
+ if (fd->ops->seek == NULL)
+ return -ENOSYS;
+
+ return fd->ops->seek(fd, off, whence);
+}
+
+ssize_t hexagonfs_read(int fileno, size_t size, void *ptr)
+{
+ struct hexagonfs_fd *fd;
+
+ if (fileno >= HEXAGONFS_MAX_FD)
+ return -EBADF;
+
+ fd = fds[fileno];
+ if (fd == NULL)
+ return -EBADF;
+
+ if (fd->ops->read == NULL)
+ return -ENOSYS;
+
+ return fd->ops->read(fd, size, ptr);
+}
+
+int hexagonfs_readdir(int fileno, size_t ent_size, char *ent)
+{
+ struct hexagonfs_fd *fd;
+ int ret;
+
+ if (fileno >= HEXAGONFS_MAX_FD)
+ return -EBADF;
+
+ fd = fds[fileno];
+ if (fd == NULL)
+ return -EBADF;
+
+ if (fd->ops->readdir == NULL)
+ return -ENOSYS;
+
+ return fd->ops->readdir(fd, ent_size, ent);
+}
+
+int hexagonfs_fstat(int fileno, struct stat *stats)
+{
+ struct hexagonfs_fd *fd;
+
+ if (fileno >= HEXAGONFS_MAX_FD)
+ return -EBADF;
+
+ fd = fds[fileno];
+ if (fd == NULL)
+ return -EBADF;
+
+ if (fd->ops->stat == NULL)
+ return -ENOSYS;
+
+ return fd->ops->stat(fd, stats);
+}
diff --git a/fastrpc/hexagonfs.h b/fastrpc/hexagonfs.h
new file mode 100644
index 0000000..0c0b368
--- /dev/null
+++ b/fastrpc/hexagonfs.h
@@ -0,0 +1,78 @@
+/*
+ * Virtual read-only filesystem for Hexagon processors
+ *
+ * Copyright (C) 2023 Richard Acayan
+ *
+ * This file is part of sensh.
+ *
+ * Sensh is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef HEXAGONFS_H
+#define HEXAGONFS_H
+
+#include
+#include
+#include
+#include
+
+struct hexagonfs_fd;
+
+struct hexagonfs_file_ops {
+ void (*close)(void *fd_data);
+ int (*from_dirent)(void *dirent_data, bool dir, void **fd_data);
+ int (*openat)(struct hexagonfs_fd *dir,
+ const char *segment,
+ bool expect_dir,
+ struct hexagonfs_fd **out);
+ int (*readdir)(struct hexagonfs_fd *fd, size_t size, char *out);
+ ssize_t (*read)(struct hexagonfs_fd *fd, size_t size, void *ptr);
+ int (*stat)(struct hexagonfs_fd *fd, struct stat *stats);
+ int (*seek)(struct hexagonfs_fd *fd, off_t off, int whence);
+};
+
+struct hexagonfs_dirent {
+ const char *name;
+
+ struct hexagonfs_file_ops *ops;
+ union hexagonfs_dirent_data {
+ void *ptr;
+ struct hexagonfs_dirent **dir;
+ const char *phys;
+ } u;
+};
+
+struct hexagonfs_fd {
+ off_t off;
+ bool is_assigned;
+ struct hexagonfs_fd *up;
+ void *data;
+
+ struct hexagonfs_file_ops *ops;
+};
+
+extern struct hexagonfs_file_ops hexagonfs_mapped_ops;
+extern struct hexagonfs_file_ops hexagonfs_plat_subtype_name_ops;
+extern struct hexagonfs_file_ops hexagonfs_virt_dir_ops;
+
+int hexagonfs_open_root();
+int hexagonfs_openat(int rootfd, int dirfd, const char *name);
+int hexagonfs_close(int fileno);
+
+int hexagonfs_fstat(int fileno, struct stat *stats);
+int hexagonfs_lseek(int fileno, off_t pos, int whence);
+int hexagonfs_readdir(int fileno, size_t size, char *name);
+ssize_t hexagonfs_read(int fileno, size_t size, void *ptr);
+
+#endif
diff --git a/fastrpc/hexagonfs_mapped.c b/fastrpc/hexagonfs_mapped.c
new file mode 100644
index 0000000..c4bbbba
--- /dev/null
+++ b/fastrpc/hexagonfs_mapped.c
@@ -0,0 +1,226 @@
+/*
+ * HexagonFS mapped file/directory operations
+ *
+ * Copyright (C) 2023 Richard Acayan
+ *
+ * This file is part of sensh.
+ *
+ * Sensh is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "hexagonfs.h"
+
+struct mapped_ctx {
+ int fd;
+ DIR *dir;
+};
+
+static void mapped_close(void *fd_data)
+{
+ struct mapped_ctx *ctx = fd_data;
+
+ if (ctx->dir != NULL)
+ closedir(ctx->dir);
+ else
+ close(ctx->fd);
+
+ free(ctx);
+}
+
+static int mapped_from_dirent(void *dirent_data, bool dir, void **fd_data)
+{
+ struct mapped_ctx *ctx;
+ const char *name = dirent_data;
+ int flags = O_RDONLY;
+ int fd;
+ int ret;
+
+ ctx = malloc(sizeof(struct mapped_ctx));
+ if (ctx == NULL)
+ return -ENOMEM;
+
+ if (dir)
+ flags |= O_DIRECTORY;
+
+ ctx->fd = open(name, flags);
+ if (ctx->fd == -1) {
+ ret = -errno;
+ goto err;
+ }
+
+ ctx->dir = NULL;
+
+ *fd_data = ctx;
+
+ return 0;
+
+err:
+ free(ctx);
+ return ret;
+}
+
+static int mapped_openat(struct hexagonfs_fd *dir,
+ const char *segment,
+ bool expect_dir,
+ struct hexagonfs_fd **out)
+{
+ struct mapped_ctx *dir_ctx = dir->data;
+ struct hexagonfs_fd *fd;
+ struct mapped_ctx *ctx;
+ int flags = O_RDONLY;
+ int ret;
+
+ ctx = malloc(sizeof(struct mapped_ctx));
+ if (ctx == NULL)
+ return -ENOMEM;
+
+ fd = malloc(sizeof(struct hexagonfs_fd));
+ if (fd == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ if (expect_dir)
+ flags |= O_DIRECTORY;
+
+ ctx->fd = openat(dir_ctx->fd, segment, flags);
+ if (ctx->fd == -1) {
+ ret = -errno;
+ goto err_free_fd;
+ }
+
+ ctx->dir = NULL;
+
+ fd->off = 0;
+ fd->up = dir;
+ fd->ops = &hexagonfs_mapped_ops;
+ fd->data = ctx;
+
+ *out = fd;
+
+ return 0;
+
+err_free_fd:
+ free(fd);
+err:
+ free(ctx);
+ return ret;
+}
+
+ssize_t mapped_read(struct hexagonfs_fd *fd, size_t size, void *out)
+{
+ struct mapped_ctx *ctx = fd->data;
+ ssize_t ret;
+
+ ret = read(ctx->fd, out, size);
+ if (ret < 0)
+ return -errno;
+
+ return ret;
+}
+
+int mapped_readdir(struct hexagonfs_fd *fd, size_t size, char *out)
+{
+ struct mapped_ctx *ctx = fd->data;
+ struct dirent *ent;
+
+ if (ctx->dir == NULL) {
+ ctx->dir = fdopendir(ctx->fd);
+ if (ctx->dir == NULL)
+ return -errno;
+
+ ctx->fd = dirfd(ctx->dir);
+ if (ctx->fd == -1)
+ return -errno;
+ }
+
+ errno = 0;
+
+ ent = readdir(ctx->dir);
+ if (ent == NULL)
+ return -errno;
+
+ strncpy(out, ent->d_name, size);
+ out[size - 1] = '\0';
+
+ return 0;
+}
+
+static int mapped_seek(struct hexagonfs_fd *fd, off_t off, int whence)
+{
+ struct mapped_ctx *ctx = fd->data;
+ int ret;
+
+ ret = lseek(ctx->fd, off, whence);
+ if (ret == -1)
+ return -errno;
+
+ return 0;
+}
+
+static int mapped_stat(struct hexagonfs_fd *fd, struct stat *stats)
+{
+ struct mapped_ctx *ctx = fd->data;
+ struct stat phys;
+ int ret;
+
+ ret = fstat(ctx->fd, &phys);
+ if (ret)
+ return -errno;
+
+ stats->st_size = phys.st_size;
+
+ stats->st_dev = 0;
+ stats->st_rdev = 0;
+
+ stats->st_ino = 0;
+ stats->st_nlink = 0;
+
+ if (phys.st_mode & S_IFDIR) {
+ stats->st_mode = S_IFDIR
+ | S_IRUSR | S_IXUSR
+ | S_IRGRP | S_IXGRP
+ | S_IROTH | S_IXOTH;
+ } else {
+ stats->st_mode = S_IRUSR | S_IRGRP | S_IROTH;
+ }
+
+ stats->st_atim.tv_sec = phys.st_atim.tv_sec;
+ stats->st_atim.tv_nsec = phys.st_atim.tv_nsec;
+ stats->st_ctim.tv_sec = phys.st_ctim.tv_sec;
+ stats->st_ctim.tv_nsec = phys.st_ctim.tv_nsec;
+ stats->st_mtim.tv_sec = phys.st_mtim.tv_sec;
+ stats->st_mtim.tv_nsec = phys.st_mtim.tv_nsec;
+
+ return 0;
+}
+
+struct hexagonfs_file_ops hexagonfs_mapped_ops = {
+ .close = mapped_close,
+ .from_dirent = mapped_from_dirent,
+ .openat = mapped_openat,
+ .read = mapped_read,
+ .readdir = mapped_readdir,
+ .seek = mapped_seek,
+ .stat = mapped_stat,
+};
diff --git a/fastrpc/hexagonfs_plat_subtype_name.c b/fastrpc/hexagonfs_plat_subtype_name.c
new file mode 100644
index 0000000..55ba4cc
--- /dev/null
+++ b/fastrpc/hexagonfs_plat_subtype_name.c
@@ -0,0 +1,100 @@
+/*
+ * HexagonFS operations for a missing sysfs file
+ *
+ * Copyright (C) 2023 Richard Acayan
+ *
+ * This file is part of sensh.
+ *
+ * Sensh is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+
+#include "hexagonfs.h"
+
+struct plat_subtype_ctx {
+ int fd;
+ char *name;
+};
+
+static void plat_subtype_name_close(void *fd_data)
+{
+ struct plat_subtype_ctx *ctx = fd_data;
+
+ close(ctx->fd);
+
+ if (ctx->name != NULL)
+ free(ctx->name);
+}
+
+static int plat_subtype_name_from_dirent(void *dirent_data,
+ bool dir,
+ void **fd_data)
+{
+ struct plat_subtype_ctx *ctx;
+
+ ctx = malloc(sizeof(struct plat_subtype_ctx));
+ if (ctx == NULL)
+ return -ENOMEM;
+
+ ctx->fd = open((const char *) dirent_data, O_RDONLY);
+ if (ctx->fd == -1)
+ return -errno;
+
+ ctx->name = NULL;
+
+ *fd_data = ctx;
+
+ return 0;
+}
+
+static int plat_subtype_name_openat(struct hexagonfs_fd *dir,
+ const char *segment,
+ bool expect_dir,
+ struct hexagonfs_fd **out)
+{
+ return -ENOTDIR;
+}
+
+static int plat_subtype_stat(struct hexagonfs_fd *fd, struct stat *stats)
+{
+ stats->st_size = 0;
+
+ stats->st_dev = 0;
+ stats->st_rdev = 0;
+
+ stats->st_ino = 0;
+ stats->st_nlink = 0;
+
+ stats->st_mode = S_IRUSR | S_IRGRP | S_IROTH;
+
+ stats->st_atim.tv_sec = 0;
+ stats->st_atim.tv_nsec = 0;
+ stats->st_ctim.tv_sec = 0;
+ stats->st_ctim.tv_nsec = 0;
+ stats->st_mtim.tv_sec = 0;
+ stats->st_mtim.tv_nsec = 0;
+
+ return 0;
+}
+
+struct hexagonfs_file_ops hexagonfs_plat_subtype_name_ops = {
+ .close = plat_subtype_name_close,
+ .from_dirent = plat_subtype_name_from_dirent,
+ .openat = plat_subtype_name_openat,
+ .stat = plat_subtype_stat,
+};
diff --git a/fastrpc/hexagonfs_virt_dir.c b/fastrpc/hexagonfs_virt_dir.c
new file mode 100644
index 0000000..8490ba8
--- /dev/null
+++ b/fastrpc/hexagonfs_virt_dir.c
@@ -0,0 +1,122 @@
+/*
+ * HexagonFS virtual directory operations
+ *
+ * Copyright (C) 2023 Richard Acayan
+ *
+ * This file is part of sensh.
+ *
+ * Sensh is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+
+#include "hexagonfs.h"
+
+/*
+ * This function searches for the relevant path segment in the path directory.
+ * It is not optimized because it is dealing with small directories.
+ */
+static struct hexagonfs_dirent *walk_dir(struct hexagonfs_dirent **dir,
+ const char *segment)
+{
+ struct hexagonfs_dirent **curr = dir;
+
+ while (*curr != NULL) {
+ if (!strcmp(segment, (*curr)->name))
+ break;
+
+ curr++;
+ }
+
+ return *curr;
+}
+
+static int virt_dir_from_dirent(void *dirent_data, bool dir, void **fd_data)
+{
+ *fd_data = dirent_data;
+ return 0;
+}
+
+static int virt_dir_openat(struct hexagonfs_fd *dir,
+ const char *segment,
+ bool expect_dir,
+ struct hexagonfs_fd **out)
+{
+ struct hexagonfs_fd *fd;
+ struct hexagonfs_dirent *ent;
+ struct hexagonfs_dirent **dirlist = dir->data;
+ int ret;
+
+ ent = walk_dir(dirlist, segment);
+ if (ent == NULL)
+ return -ENOENT;
+
+ fd = malloc(sizeof(struct hexagonfs_fd));
+ if (fd == NULL)
+ return -ENOMEM;
+
+ fd->off = 0;
+ fd->up = dir;
+ fd->ops = ent->ops;
+
+ ret = ent->ops->from_dirent(ent->u.ptr, expect_dir, &fd->data);
+ if (ret)
+ goto err;
+
+ *out = fd;
+
+ return 0;
+
+err:
+ free(fd);
+ return ret;
+}
+
+static void virt_dir_close(void *fd_data)
+{
+}
+
+static int virt_dir_stat(struct hexagonfs_fd *fd, struct stat *stats)
+{
+ stats->st_size = 0;
+
+ stats->st_dev = 0;
+ stats->st_rdev = 0;
+
+ stats->st_ino = 0;
+ stats->st_nlink = 0;
+
+ stats->st_mode = S_IRUSR | S_IXUSR
+ | S_IRGRP | S_IXGRP
+ | S_IROTH | S_IXOTH;
+
+ stats->st_atim.tv_sec = 0;
+ stats->st_atim.tv_nsec = 0;
+ stats->st_ctim.tv_sec = 0;
+ stats->st_ctim.tv_nsec = 0;
+ stats->st_mtim.tv_sec = 0;
+ stats->st_mtim.tv_nsec = 0;
+
+ return 0;
+}
+
+struct hexagonfs_file_ops hexagonfs_virt_dir_ops = {
+ .close = virt_dir_close,
+ .from_dirent = virt_dir_from_dirent,
+ .openat = virt_dir_openat,
+ .stat = virt_dir_stat,
+};