diff --git a/Makefile b/Makefile index da1d5dd..3698db8 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,15 @@ ifeq ($(call has, VIRTIORNG), 1) OBJS_EXTRA += virtio-rng.o endif +# virtio-fs +ENABLE_VIRTIOFS ?= 1 +$(call set-feature, VIRTIOFS) +SHARED_DIRECTORY ?= ./shared +ifeq ($(call has, VIRTIOFS), 1) + OBJS_EXTRA += virtio-fs.o + OPTS += -s $(SHARED_DIRECTORY) +endif + NETDEV ?= tap # virtio-net ENABLE_VIRTIONET ?= 1 @@ -197,7 +206,14 @@ ext4.img: $(Q)dd if=/dev/zero of=$@ bs=4k count=600 $(Q)$(MKFS_EXT4) -F $@ -check: $(BIN) minimal.dtb $(KERNEL_DATA) $(INITRD_DATA) $(DISKIMG_FILE) +.PHONY: $(DIRECTORY) +$(SHARED_DIRECTORY): + @if [ ! -d $@ ]; then \ + echo "Creating mount directory: $@"; \ + mkdir -p $@; \ + fi + +check: $(BIN) minimal.dtb $(KERNEL_DATA) $(INITRD_DATA) $(DISKIMG_FILE) $(SHARED_DIRECTORY) @$(call notice, Ready to launch Linux kernel. Please be patient.) $(Q)./$(BIN) -k $(KERNEL_DATA) -c $(SMP) -b minimal.dtb -i $(INITRD_DATA) -n $(NETDEV) $(OPTS) diff --git a/README.md b/README.md index cf10269..9c51022 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,33 @@ You can exit the emulator using: \. (press Ctrl+A, leave it, afterwar ## Usage ```shell -./semu -k linux-image [-b dtb-file] [-i initrd-image] [-d disk-image] +./semu -k linux-image [-b dtb-file] [-i initrd-image] [-d disk-image] [-s shared-directory] ``` * `linux-image` is the path to the Linux kernel `Image`. * `dtb-file` is optional, as it specifies the user-specified device tree blob. * `initrd-image` is optional, as it specifies the user-specified initial RAM disk image. * `disk-image` is optional, as it specifies the path of a disk image in ext4 file system for the virtio-blk device. +* `shared-directory` is optional, as it specifies the path of a directory you want to mount in host. + +## Mount and unmount a directory in semu + +To mount the directory in semu: + +```shell +$ mount -t virtiofs myfs [shared-directory] +``` + +* `shared-directory` is the path of a directory you want to mount in semu. + +To unmount the directory in semu: + +```shell +$ umount [shared-directory] +``` + +* `shared-directory` is the path of a directory you want to unmount in semu. + ## Build Linux kernel image and root file system diff --git a/configs/linux.config b/configs/linux.config index cf2a4e5..a8bb3fb 100644 --- a/configs/linux.config +++ b/configs/linux.config @@ -1257,7 +1257,9 @@ CONFIG_INOTIFY_USER=y # CONFIG_QUOTA is not set CONFIG_AUTOFS4_FS=y CONFIG_AUTOFS_FS=y -# CONFIG_FUSE_FS is not set +CONFIG_CUSE=y +CONFIG_FUSE_FS=y +CONFIG_VIRTIO_FS=y # CONFIG_OVERLAY_FS is not set # diff --git a/device.h b/device.h index c8b1d4b..5bf7f0e 100644 --- a/device.h +++ b/device.h @@ -3,6 +3,9 @@ #if SEMU_HAS(VIRTIONET) #include "netdev.h" #endif +#if SEMU_HAS(VIRTIOFS) +#include "uthash.h" +#endif #include "riscv.h" #include "virtio.h" @@ -357,6 +360,70 @@ void virtio_snd_write(hart_t *core, bool virtio_snd_init(virtio_snd_state_t *vsnd); #endif /* SEMU_HAS(VIRTIOSND) */ +/* VirtIO-File-System */ + +#if SEMU_HAS(VIRTIOFS) +#define IRQ_VFS 6 +#define IRQ_VFS_BIT (1 << IRQ_VFS) + +typedef struct { + uint64_t ino; + char *path; + UT_hash_handle hh; +} inode_map_entry; + +typedef struct { + uint32_t QueueNum; + uint32_t QueueDesc; + uint32_t QueueAvail; + uint32_t QueueUsed; + uint16_t last_avail; + bool ready; +} virtio_fs_queue_t; + +typedef struct { + /* feature negotiation */ + uint32_t DeviceFeaturesSel; + uint32_t DriverFeatures; + uint32_t DriverFeaturesSel; + + /* queue config */ + uint32_t QueueSel; + virtio_fs_queue_t queues[3]; + + /* status */ + uint32_t Status; + uint32_t InterruptStatus; + + /* guest memory base */ + uint32_t *ram; + + char *mount_tag; /* guest sees this tag */ + char *shared_dir; + + inode_map_entry *inode_map; + + /* optional implementation-specific */ + void *priv; +} virtio_fs_state_t; + +/* MMIO read/write */ +void virtio_fs_read(hart_t *core, + virtio_fs_state_t *vfs, + uint32_t addr, + uint8_t width, + uint32_t *value); + +void virtio_fs_write(hart_t *core, + virtio_fs_state_t *vfs, + uint32_t addr, + uint8_t width, + uint32_t value); + +bool virtio_fs_init(virtio_fs_state_t *vfs, char *mtag, char *dir); + +#endif /* SEMU_HAS(VIRTIOFS) */ + /* memory mapping */ typedef struct { bool debug; @@ -382,6 +449,9 @@ typedef struct { #if SEMU_HAS(VIRTIOSND) virtio_snd_state_t vsnd; #endif +#if SEMU_HAS(VIRTIOFS) + virtio_fs_state_t vfs; +#endif uint32_t peripheral_update_ctr; diff --git a/feature.h b/feature.h index d0adcdd..850ef89 100644 --- a/feature.h +++ b/feature.h @@ -17,5 +17,10 @@ #define SEMU_FEATURE_VIRTIOSND 1 #endif +/* virtio-fs */ +#ifndef SEMU_FEATURE_VIRTIOFS +#define SEMU_FEATURE_VIRTIOFS 1 +#endif + /* Feature test macro */ #define SEMU_HAS(x) SEMU_FEATURE_##x diff --git a/fuse.h b/fuse.h new file mode 100644 index 0000000..3558216 --- /dev/null +++ b/fuse.h @@ -0,0 +1,159 @@ +#include "riscv.h" +#include "virtio.h" + +#define FUSE_REC_ALIGN(x) \ + (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1)) +#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x) + +struct fuse_in_header { + uint32_t len; + uint32_t opcode; + uint64_t unique; + uint64_t nodeid; + uint32_t uid; + uint32_t gid; + uint32_t pid; + uint32_t padding; +}; + +struct fuse_out_header { + uint32_t len; + int32_t error; + uint64_t unique; +}; + +struct vfs_req_header { + struct fuse_in_header in; +}; + +struct vfs_resp_header { + struct fuse_out_header out; +}; + +struct fuse_init_in { + /* FUSE major version supported by the guest (typically 7) */ + uint32_t major; + /* FUSE minor version supported by the guest (e.g., 31, 26) */ + uint32_t minor; + uint32_t max_readahead; /* Maximum readahead size supported by the guest */ + uint32_t flags; /* Flags requested by the guest */ +}; + +struct fuse_init_out { + uint32_t major; /* FUSE major version supported by the device */ + uint32_t minor; /* FUSE minor version supported by the device */ + uint32_t max_readahead; /* Maximum readahead size accepted by the device */ + /* Flags supported by the device (negotiated with the guest) */ + uint32_t flags; + uint16_t max_background; /* Maximum number of background requests */ + uint16_t congestion_threshold; + uint32_t max_write; /* Maximum write size the device can handle */ + uint32_t time_gran; /* Time granularity (in nanoseconds) */ + uint32_t unused[11]; /* Reserved */ +}; + +struct fuse_getattr_in { + /* bitmask for valid fields (e.g. FUSE_GETATTR_FH) */ + uint32_t getattr_flags; + uint32_t padding; /* unused, reserved for alignment */ + uint64_t fh; /* optional: file handle (used when getattr_flags has */ +}; + +struct fuse_attr { + uint64_t ino; /* inode number */ + uint64_t size; /* file size in bytes */ + uint64_t blocks; /* number of 512B blocks allocated */ + uint64_t atime; /* last access time (UNIX time) */ + uint64_t mtime; /* last modification time */ + uint64_t ctime; /* last status change time */ + uint32_t atimensec; /* nanoseconds part */ + uint32_t mtimensec; + uint32_t ctimensec; + uint32_t mode; /* file mode (e.g. S_IFDIR | 0755) */ + uint32_t nlink; /* number of hard links */ + uint32_t uid; /* owner uid */ + uint32_t gid; /* owner gid */ + uint32_t rdev; /* device ID (if special file) */ + uint32_t blksize; /* block size */ + uint32_t flags; /* reserved */ +}; + +struct fuse_attr_out { + uint64_t attr_valid; /* seconds the attributes are valid */ + uint32_t attr_valid_nsec; /* nanoseconds part of attr_valid */ + uint32_t dummy; /* padding for alignment */ + struct fuse_attr attr; /* actual attributes */ +}; + +struct fuse_open_in { + uint32_t flags; + uint32_t open_flags; +}; + +struct fuse_open_out { + uint64_t fh; + uint32_t open_flags; + int32_t backing_id; +}; + +struct fuse_read_in { + uint64_t fh; + uint64_t offset; + uint32_t size; + uint32_t read_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_entry_out { + uint64_t nodeid; /* inode number */ + uint64_t generation; /* inode generation */ + uint64_t entry_valid; /* cache timeout (sec) */ + uint64_t attr_valid; /* attr cache timeout (sec) */ + uint32_t entry_valid_nsec; /* cache timeout (nsec) */ + uint32_t attr_valid_nsec; /* attr cache timeout (nsec) */ + struct fuse_attr attr; /* file attributes */ +}; + +struct fuse_dirent { + uint64_t ino; /* inode number */ + uint64_t off; /* offset to next entry */ + uint32_t namelen; /* length of the entry name */ + uint32_t type; /* file type (DT_REG, DT_DIR, etc.) */ + char name[]; /* name (not null-terminated) */ +}; + +struct fuse_direntplus { + struct fuse_entry_out entry_out; + struct fuse_dirent dirent; +}; + +struct fuse_lookup_in { + uint64_t parent; /* inode of parent dir */ +}; + +struct fuse_forget_in { + uint64_t nlookup; +}; + +struct fuse_create_in { + uint32_t flags; + uint32_t mode; + uint32_t umask; + uint32_t open_flags; +}; + +struct fuse_release_in { + uint64_t fh; + uint32_t flags; + uint32_t release_flags; + uint64_t lock_owner; +}; + +struct fuse_flush_in { + uint64_t fh; + uint32_t unused; + uint32_t padding; + uint64_t lock_owner; +}; \ No newline at end of file diff --git a/main.c b/main.c index 7c7d7b3..97968c0 100644 --- a/main.c +++ b/main.c @@ -113,6 +113,18 @@ static void emu_update_vsnd_interrupts(vm_t *vm) } #endif +#if SEMU_HAS(VIRTIOFS) +static void emu_update_vfs_interrupts(vm_t *vm) +{ + emu_state_t *data = PRIV(vm->hart[0]); + if (data->vfs.InterruptStatus) + data->plic.active |= IRQ_VFS_BIT; + else + data->plic.active &= ~IRQ_VFS_BIT; + plic_update_interrupts(vm, &data->plic); +} +#endif + static void mem_load(hart_t *hart, uint32_t addr, uint8_t width, @@ -175,6 +187,13 @@ static void mem_load(hart_t *hart, emu_update_vsnd_interrupts(hart->vm); return; #endif + +#if SEMU_HAS(VIRTIOFS) + case 0x48: /* virtio-fs */ + virtio_fs_read(hart, &data->vfs, addr & 0xFFFFF, width, value); + emu_update_vfs_interrupts(hart->vm); + return; +#endif } } vm_set_exception(hart, RV_EXC_LOAD_FAULT, hart->exc_val); @@ -243,6 +262,13 @@ static void mem_store(hart_t *hart, emu_update_vsnd_interrupts(hart->vm); return; #endif + +#if SEMU_HAS(VIRTIOFS) + case 0x48: /* virtio-fs */ + virtio_fs_write(hart, &data->vfs, addr & 0xFFFFF, width, value); + emu_update_vfs_interrupts(hart->vm); + return; +#endif } } vm_set_exception(hart, RV_EXC_STORE_FAULT, hart->exc_val); @@ -508,10 +534,10 @@ static void map_file(char **ram_loc, const char *name) static void usage(const char *execpath) { - fprintf( - stderr, - "Usage: %s -k linux-image [-b dtb] [-i initrd-image] [-d disk-image]\n", - execpath); + fprintf(stderr, + "Usage: %s -k linux-image [-b dtb] [-i initrd-image] [-d " + "disk-image] [-s shared-directory]\n", + execpath); } static void handle_options(int argc, @@ -522,20 +548,21 @@ static void handle_options(int argc, char **disk_file, char **net_dev, int *hart_count, - bool *debug) + bool *debug, + char **shared_dir) { - *kernel_file = *dtb_file = *initrd_file = *disk_file = *net_dev = NULL; + *kernel_file = *dtb_file = *initrd_file = *disk_file = *net_dev = + *shared_dir = NULL; int optidx = 0; - struct option opts[] = { - {"kernel", 1, NULL, 'k'}, {"dtb", 1, NULL, 'b'}, - {"initrd", 1, NULL, 'i'}, {"disk", 1, NULL, 'd'}, - {"netdev", 1, NULL, 'n'}, {"smp", 1, NULL, 'c'}, - {"gdbstub", 0, NULL, 'g'}, {"help", 0, NULL, 'h'}, - }; + struct option opts[] = {{"kernel", 1, NULL, 'k'}, {"dtb", 1, NULL, 'b'}, + {"initrd", 1, NULL, 'i'}, {"disk", 1, NULL, 'd'}, + {"netdev", 1, NULL, 'n'}, {"smp", 1, NULL, 'c'}, + {"gdbstub", 0, NULL, 'g'}, {"help", 0, NULL, 'h'}, + {"shared_dir", 1, NULL, 's'}}; int c; - while ((c = getopt_long(argc, argv, "k:b:i:d:n:c:gh", opts, &optidx)) != + while ((c = getopt_long(argc, argv, "k:b:i:d:n:c:s:gh", opts, &optidx)) != -1) { switch (c) { case 'k': @@ -556,6 +583,9 @@ static void handle_options(int argc, case 'c': *hart_count = atoi(optarg); break; + case 's': + *shared_dir = optarg; + break; case 'g': *debug = true; break; @@ -603,11 +633,12 @@ static int semu_init(emu_state_t *emu, int argc, char **argv) char *initrd_file; char *disk_file; char *netdev; + char *shared_dir; int hart_count = 1; bool debug = false; vm_t *vm = &emu->vm; handle_options(argc, argv, &kernel_file, &dtb_file, &initrd_file, - &disk_file, &netdev, &hart_count, &debug); + &disk_file, &netdev, &hart_count, &debug, &shared_dir); /* Initialize the emulator */ memset(emu, 0, sizeof(*emu)); @@ -691,6 +722,11 @@ static int semu_init(emu_state_t *emu, int argc, char **argv) fprintf(stderr, "No virtio-snd functioned\n"); emu->vsnd.ram = emu->ram; #endif +#if SEMU_HAS(VIRTIOFS) + emu->vfs.ram = emu->ram; + if (!virtio_fs_init(&(emu->vfs), "myfs", shared_dir)) + fprintf(stderr, "No virtio-fs functioned\n"); +#endif emu->peripheral_update_ctr = 0; emu->debug = debug; @@ -728,6 +764,11 @@ static int semu_step(emu_state_t *emu) if (emu->vsnd.InterruptStatus) emu_update_vsnd_interrupts(vm); #endif + +#if SEMU_HAS(VIRTIOFS) + if (emu->vfs.InterruptStatus) + emu_update_vfs_interrupts(vm); +#endif } emu_update_timer_interrupt(vm->hart[i]); diff --git a/minimal.dts b/minimal.dts index f5b57a3..c2d412c 100644 --- a/minimal.dts +++ b/minimal.dts @@ -79,5 +79,13 @@ interrupts = <5>; }; #endif + +#if SEMU_FEATURE_VIRTIOFS + fs0: virtio@4800000 { + compatible = "virtio,mmio"; + reg = <0x4800000 0x1000>; + interrupts = <6>; + }; +#endif }; }; diff --git a/uthash.h b/uthash.h new file mode 100644 index 0000000..7ef9dd3 --- /dev/null +++ b/uthash.h @@ -0,0 +1,1260 @@ +/* +Copyright (c) 2003-2025, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* ptrdiff_t */ +#include /* exit */ +#include /* memcmp, memset, strlen */ + +#if defined(HASH_NO_STDINT) && HASH_NO_STDINT +/* The user doesn't have , and must figure out their own way + to provide definitions for uint8_t and uint32_t. */ +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || \ + defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst, src) \ + do { \ + char **_da_dst = (char **) (&(dst)); \ + *_da_dst = (char *) (src); \ + } while (0) +#else +#define DECLTYPE_ASSIGN(dst, src) \ + do { \ + (dst) = DECLTYPE(dst)(src); \ + } while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr, sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a, n) memset(a, '\0', n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr, keylen, hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a, b, n) memcmp(a, b, n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) \ + do { \ + } while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) \ + do { \ + (oomed) = 1; \ + } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 \ + 5U /* lg2 of initial number of buckets \ + */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl, hhp) ((void *) (((char *) (hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl, elp) \ + ((UT_hash_handle *) (void *) (((char *) (elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ + do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ + } while (0) + +#define HASH_VALUE(keyptr, keylen, hashv) \ + do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ + } while (0) + +#define HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, hashval, out) \ + do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval)) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, \ + (head)->hh.tbl->buckets[_hf_bkt], keyptr, \ + keylen, hashval, out); \ + } \ + } \ + } while (0) + +#define HASH_FIND(hh, head, keyptr, keylen, out) \ + do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ + } while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN \ + (HASH_BLOOM_BITLEN / 8UL) + (((HASH_BLOOM_BITLEN % 8UL) != 0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl, oomed) \ + do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t *) uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ + } while (0) + +#define HASH_BLOOM_FREE(tbl) \ + do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + } while (0) + +#define HASH_BLOOM_BITSET(bv, idx) (bv[(idx) / 8U] |= (1U << ((idx) % 8U))) +#define HASH_BLOOM_BITTEST(bv, idx) \ + ((bv[(idx) / 8U] & (1U << ((idx) % 8U))) != 0) + +#define HASH_BLOOM_ADD(tbl, hashv) \ + HASH_BLOOM_BITSET( \ + (tbl)->bloom_bv, \ + ((hashv) & (uint32_t) ((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl, hashv) \ + HASH_BLOOM_BITTEST( \ + (tbl)->bloom_bv, \ + ((hashv) & (uint32_t) ((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl, oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl, hashv) +#define HASH_BLOOM_TEST(tbl, hashv) 1 +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh, head, oomed) \ + do { \ + (head)->hh.tbl = \ + (UT_hash_table *) uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char *) (&(head)->hh) - (char *) (head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket *) uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero( \ + (head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM(if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * \ + sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + }) \ + } \ + } \ + } while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, \ + hashval, add, replaced, cmpfcn) \ + do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, \ + hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), \ + keylen_in, hashval, add, cmpfcn); \ + } while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, hashval, add, \ + replaced) \ + do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, \ + hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, \ + hashval, add); \ + } while (0) + +#define HASH_REPLACE(hh, head, fieldname, keylen_in, add, replaced) \ + do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, \ + add, replaced); \ + } while (0) + +#define HASH_REPLACE_INORDER(hh, head, fieldname, keylen_in, add, replaced, \ + cmpfcn) \ + do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, \ + _hr_hashv, add, replaced, cmpfcn); \ + } while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ + do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ + } while (0) + +#define HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn) \ + do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ + } while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn) \ + do { \ + char *_hs_saved_head = (char *) (head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ + } while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, oomed) \ + do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, \ + oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ + } while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, oomed) \ + do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, \ + oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, \ + hashval, add, cmpfcn) \ + do { \ + IF_HASH_NONFATAL_OOM(int _ha_oomed = 0;) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char *) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM(if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( \ + }) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = \ + (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, \ + _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ + } while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh, head, keyptr, keylen_in, add, cmpfcn) \ + do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, \ + _hs_hashv, add, cmpfcn); \ + } while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, hashval, \ + add, cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), \ + keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh, head, fieldname, keylen_in, add, cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, \ + cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, hashval, add) \ + do { \ + IF_HASH_NONFATAL_OOM(int _ha_oomed = 0;) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void *) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM(if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( \ + }) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, \ + _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ + } while (0) + +#define HASH_ADD_KEYPTR(hh, head, keyptr, keylen_in, add) \ + do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, \ + add); \ + } while (0) + +#define HASH_ADD_BYHASHVALUE(hh, head, fieldname, keylen_in, hashval, add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, \ + hashval, add) + +#define HASH_ADD(hh, head, fieldname, keylen_in, add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv, num_bkts, bkt) \ + do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ + } while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh, head, delptr) HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh, head, delptrhh) \ + do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free( \ + (head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = \ + _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = \ + _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, \ + _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ + } while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head, findstr, out) \ + do { \ + unsigned _uthash_hfstr_keylen = (unsigned) uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ + } while (0) +#define HASH_ADD_STR(head, strfield, add) \ + do { \ + unsigned _uthash_hastr_keylen = \ + (unsigned) uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ + } while (0) +#define HASH_REPLACE_STR(head, strfield, add, replaced) \ + do { \ + unsigned _uthash_hrstr_keylen = \ + (unsigned) uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, \ + replaced); \ + } while (0) +#define HASH_FIND_INT(head, findint, out) \ + HASH_FIND(hh, head, findint, sizeof(int), out) +#define HASH_ADD_INT(head, intfield, add) \ + HASH_ADD(hh, head, intfield, sizeof(int), add) +#define HASH_REPLACE_INT(head, intfield, add, replaced) \ + HASH_REPLACE(hh, head, intfield, sizeof(int), add, replaced) +#define HASH_FIND_PTR(head, findptr, out) \ + HASH_FIND(hh, head, findptr, sizeof(void *), out) +#define HASH_ADD_PTR(head, ptrfield, add) \ + HASH_ADD(hh, head, ptrfield, sizeof(void *), add) +#define HASH_REPLACE_PTR(head, ptrfield, add, replaced) \ + HASH_REPLACE(hh, head, ptrfield, sizeof(void *), add, replaced) +#define HASH_DEL(head, delptr) HASH_DELETE(hh, head, delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is + * defined. This is for uthash developer only; it compiles away if HASH_DEBUG + * isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + exit(-1); \ + } while (0) +#define HASH_FSCK(hh, head, where) \ + do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char *) (_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void *) _thh->hh_prev, \ + (void *) _prev); \ + } \ + _bkt_count++; \ + _prev = (char *) (_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, \ + _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char *) _thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", (where), \ + (void *) _thh->prev, (void *) _prev); \ + } \ + _prev = (char *) ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) \ + : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ + } while (0) +#else +#define HASH_FSCK(hh, head, where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh, head, keyptr, fieldlen) \ + do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long) fieldlen); \ + } while (0) +#else +#define HASH_EMIT_KEY(hh, head, keyptr, fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. + */ +#define HASH_BER(key, keylen, hashv) \ + do { \ + unsigned _hb_keylen = (unsigned) keylen; \ + const unsigned char *_hb_key = (const unsigned char *) (key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ + } while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key, keylen, hashv) \ + do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char *) (key); \ + hashv = 0; \ + for (_sx_i = 0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ + } while (0) +/* FNV-1a variation */ +#define HASH_FNV(key, keylen, hashv) \ + do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char *) (key); \ + (hashv) = 2166136261U; \ + for (_fn_i = 0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ + } while (0) + +#define HASH_OAT(key, keylen, hashv) \ + do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key = (const unsigned char *) (key); \ + hashv = 0; \ + for (_ho_i = 0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ + } while (0) + +#define HASH_JEN_MIX(a, b, c) \ + do { \ + a -= b; \ + a -= c; \ + a ^= (c >> 13); \ + b -= c; \ + b -= a; \ + b ^= (a << 8); \ + c -= a; \ + c -= b; \ + c ^= (b >> 13); \ + a -= b; \ + a -= c; \ + a ^= (c >> 12); \ + b -= c; \ + b -= a; \ + b ^= (a << 16); \ + c -= a; \ + c -= b; \ + c ^= (b >> 5); \ + a -= b; \ + a -= c; \ + a ^= (c >> 3); \ + b -= c; \ + b -= a; \ + b ^= (a << 10); \ + c -= a; \ + c -= b; \ + c ^= (b >> 15); \ + } while (0) + +#define HASH_JEN(key, keylen, hashv) \ + do { \ + unsigned _hj_i, _hj_j, _hj_k; \ + unsigned const char *_hj_key = (unsigned const char *) (key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned) (keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ((unsigned) _hj_key[1] << 8) + \ + ((unsigned) _hj_key[2] << 16) + \ + ((unsigned) _hj_key[3] << 24)); \ + _hj_j += (_hj_key[4] + ((unsigned) _hj_key[5] << 8) + \ + ((unsigned) _hj_key[6] << 16) + \ + ((unsigned) _hj_key[7] << 24)); \ + hashv += (_hj_key[8] + ((unsigned) _hj_key[9] << 8) + \ + ((unsigned) _hj_key[10] << 16) + \ + ((unsigned) _hj_key[11] << 24)); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned) (keylen); \ + switch (_hj_k) { \ + case 11: \ + hashv += ((unsigned) _hj_key[10] << 24); /* FALLTHROUGH */ \ + case 10: \ + hashv += ((unsigned) _hj_key[9] << 16); /* FALLTHROUGH */ \ + case 9: \ + hashv += ((unsigned) _hj_key[8] << 8); /* FALLTHROUGH */ \ + case 8: \ + _hj_j += ((unsigned) _hj_key[7] << 24); /* FALLTHROUGH */ \ + case 7: \ + _hj_j += ((unsigned) _hj_key[6] << 16); /* FALLTHROUGH */ \ + case 6: \ + _hj_j += ((unsigned) _hj_key[5] << 8); /* FALLTHROUGH */ \ + case 5: \ + _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: \ + _hj_i += ((unsigned) _hj_key[3] << 24); /* FALLTHROUGH */ \ + case 3: \ + _hj_i += ((unsigned) _hj_key[2] << 16); /* FALLTHROUGH */ \ + case 2: \ + _hj_i += ((unsigned) _hj_key[1] << 8); /* FALLTHROUGH */ \ + case 1: \ + _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default:; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + } while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) || \ + defined(_MSC_VER) || defined(__BORLANDC__) || defined(__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined(get16bits) +#define get16bits(d) \ + ((((uint32_t) (((const uint8_t *) (d))[1])) << 8) + \ + (uint32_t) (((const uint8_t *) (d))[0])) +#endif +#define HASH_SFH(key, keylen, hashv) \ + do { \ + unsigned const char *_sfh_key = (unsigned const char *) (key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t) keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (; _sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits(_sfh_key); \ + _sfh_tmp = ((uint32_t) (get16bits(_sfh_key + 2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U * sizeof(uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: \ + hashv += get16bits(_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t) (_sfh_key[sizeof(uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: \ + hashv += get16bits(_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: \ + hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default:; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ + } while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl, hh, head, keyptr, keylen_in, hashval, out) \ + do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && \ + (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ + } while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head, hh, addhh, oomed) \ + do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= \ + ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) && \ + !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh, (addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM(if (oomed) { HASH_DEL_IN_BKT(head, addhh); }) \ + } \ + } while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head, delhh) \ + do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ + } while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh, tbl, oomed) \ + do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket *) uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, sizeof(struct UT_hash_bucket) * \ + (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets + 1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets * 2U) - 1U)) != 0U) \ + ? 1U \ + : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[_he_bkt_i].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, \ + _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > \ + _he_newbkt->expand_mult * \ + (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, \ + (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = \ + ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) \ + ? ((tbl)->ineff_expands + 1U) \ + : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ + } while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head, cmpfcn) HASH_SRT(hh, head, cmpfcn) +#define HASH_SRT(hh, head, cmpfcn) \ + do { \ + unsigned _hs_i; \ + unsigned _hs_looping, _hs_nmerges, _hs_insize, _hs_psize, _hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = \ + ((_hs_q->next != NULL) \ + ? HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) \ + : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || \ + ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) \ + ? HH_FROM_ELMT((head)->hh.tbl, \ + _hs_q->next) \ + : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) \ + ? HH_FROM_ELMT((head)->hh.tbl, \ + _hs_p->next) \ + : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn(DECLTYPE(head)(ELMT_FROM_HH( \ + (head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH( \ + (head)->hh.tbl, _hs_q)))) <= \ + 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) \ + ? HH_FROM_ELMT((head)->hh.tbl, \ + _hs_p->next) \ + : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) \ + ? HH_FROM_ELMT((head)->hh.tbl, \ + _hs_q->next) \ + : NULL); \ + _hs_qsize--; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = \ + ((_hs_e != NULL) \ + ? ELMT_FROM_HH((head)->hh.tbl, _hs_e) \ + : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = \ + ((_hs_tail != NULL) \ + ? ELMT_FROM_HH((head)->hh.tbl, _hs_tail) \ + : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, \ + ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ + } while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ + do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh = NULL; \ + ptrdiff_t _dst_hho = ((char *) (&(dst)->hh_dst) - (char *) (dst)); \ + if ((src) != NULL) { \ + for (_src_bkt = 0; _src_bkt < (src)->hh_src.tbl->num_buckets; \ + _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM(int _hs_oomed = 0;) \ + _dst_hh = \ + (UT_hash_handle *) (void *) (((char *) _elt) + \ + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM(if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + }) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, \ + _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], \ + hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM(if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + }) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ + } while (0) + +#define HASH_CLEAR(hh, head) \ + do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free( \ + (head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ + } while (0) + +#define HASH_OVERHEAD(hh, head) \ + (((head) != NULL) \ + ? ((size_t) (((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + (HASH_BLOOM_BYTELEN))) \ + : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh, head, el, tmp) \ + for (((el) = (head)), \ + ((*(char **) (&(tmp))) = \ + (char *) ((head != NULL) ? (head)->hh.next : NULL)); \ + (el) != NULL; ((el) = (tmp)), \ + ((*(char **) (&(tmp))) = \ + (char *) ((tmp != NULL) ? (tmp)->hh.next : NULL))) +#else +#define HASH_ITER(hh, head, el, tmp) \ + for (((el) = (head)), \ + ((tmp) = DECLTYPE(el)((head != NULL) ? (head)->hh.next : NULL)); \ + (el) != NULL; \ + ((el) = (tmp)), \ + ((tmp) = DECLTYPE(el)((tmp != NULL) ? (tmp)->hh.next : NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh, head) +#define HASH_CNT(hh, head) ((head != NULL) ? ((head)->hh.tbl->num_items) : 0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps + */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t + bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/virtio-fs.c b/virtio-fs.c new file mode 100644 index 0000000..851f4c8 --- /dev/null +++ b/virtio-fs.c @@ -0,0 +1,936 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "device.h" +#include "fuse.h" +#include "riscv.h" +#include "riscv_private.h" +#include "virtio.h" + +#define VFS_DEV_CNT_MAX 1 + +#define VFS_FEATURES_0 0 +#define VFS_FEATURES_1 1 /* VIRTIO_F_VERSION_1 */ +#define VFS_QUEUE_NUM_MAX 1024 +#define VFS_QUEUE (vfs->queues[vfs->QueueSel]) +#define NUM_REQUEST_QUEUES_ADDR 0x49 + +#define PRIV(x) ((struct virtio_fs_config *) x->priv) + +PACKED(struct virtio_fs_config { + char tag[36]; + uint32_t num_request_queues; + uint32_t notify_buf_size; /* ignored */ +}); + +typedef struct { + DIR *dir; + char *path; +} dir_handle_t; + +static struct virtio_fs_config vfs_configs[VFS_DEV_CNT_MAX]; +static int vfs_dev_cnt = 0; + +static void virtio_fs_set_fail(virtio_fs_state_t *vfs) +{ + vfs->Status |= VIRTIO_STATUS__DEVICE_NEEDS_RESET; + if (vfs->Status & VIRTIO_STATUS__DRIVER_OK) + vfs->InterruptStatus |= VIRTIO_INT__CONF_CHANGE; +} + +static inline uint32_t vfs_preprocess(virtio_fs_state_t *vfs, uint32_t addr) +{ + if ((addr >= RAM_SIZE) || (addr & 0b11)) + return virtio_fs_set_fail(vfs), 0; + + return addr >> 2; +} + +static void virtio_fs_update_status(virtio_fs_state_t *vfs, uint32_t status) +{ + vfs->Status |= status; + if (status) + return; + + /* Reset */ + uint32_t *ram = vfs->ram; + void *priv = vfs->priv; + char *mount_tag = vfs->mount_tag; + if (!vfs->shared_dir) + return; + size_t shared_dir_len = strlen(vfs->shared_dir) + 1; + char *shared_dir = (char *) malloc(shared_dir_len); + if (!shared_dir) { + shared_dir = NULL; + } else { + snprintf(shared_dir, shared_dir_len, "%s", vfs->shared_dir); + } + + inode_map_entry *inode_map = vfs->inode_map; + memset(vfs, 0, sizeof(*vfs)); + vfs->ram = ram; + vfs->priv = priv; + vfs->mount_tag = mount_tag; + + if (shared_dir) { + vfs->shared_dir = strdup(shared_dir); + free(shared_dir); + } else { + if (vfs->shared_dir) { + vfs->shared_dir[0] = '\0'; + } + } + + vfs->inode_map = inode_map; +} + +static void virtio_fs_init_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + struct fuse_init_out *init_out = + (struct fuse_init_out *) ((uintptr_t) vfs->ram + vq_desc[3].addr); + + header_resp->out.len = + sizeof(struct fuse_out_header) + sizeof(struct fuse_init_out); + header_resp->out.error = 0; + + /* Fill init_out with capabilities */ + init_out->major = 7; + init_out->minor = 41; + init_out->max_readahead = 0x10000; + init_out->flags = FUSE_ASYNC_READ | FUSE_BIG_WRITES | FUSE_DO_READDIRPLUS; + init_out->max_background = 64; + init_out->congestion_threshold = 32; + init_out->max_write = 0x131072; + init_out->time_gran = 1; + + *plen = header_resp->out.len; +} + +static void virtio_fs_getattr_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + const struct fuse_in_header *in_header = + (struct fuse_in_header *) ((uintptr_t) vfs->ram + vq_desc[0].addr); + uint64_t inode = in_header->nodeid; + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + struct fuse_attr_out *outattr = + (struct fuse_attr_out *) ((uintptr_t) vfs->ram + vq_desc[3].addr); + + header_resp->out.len = + sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out); + header_resp->out.error = 0; + + const char *target_path = NULL; + struct stat st; + + /* root entry (inode=1) */ + if (inode == 1) { + target_path = vfs->shared_dir; + } else { + inode_map_entry *entry = NULL; + HASH_FIND_INT(vfs->inode_map, &inode, entry); + if (!entry) { + header_resp->out.error = -ENOENT; + *plen = sizeof(struct fuse_out_header); + return; + } + target_path = entry->path; + } + + if (stat(target_path, &st) < 0) { + header_resp->out.error = -errno; + *plen = sizeof(struct fuse_out_header); + return; + } + + outattr->attr_valid = 60; + outattr->attr_valid_nsec = 0; + outattr->attr.ino = st.st_ino; + outattr->attr.size = st.st_size; + outattr->attr.blocks = st.st_blocks; + outattr->attr.atime = st.st_atime; + outattr->attr.mtime = st.st_mtime; + outattr->attr.ctime = st.st_ctime; + outattr->attr.mode = st.st_mode; + outattr->attr.nlink = st.st_nlink; + outattr->attr.uid = st.st_uid; + outattr->attr.gid = st.st_gid; + outattr->attr.blksize = st.st_blksize; + + *plen = header_resp->out.len; +} + +static void virtio_fs_opendir_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + struct fuse_in_header *in_header = + (struct fuse_in_header *) ((uintptr_t) vfs->ram + vq_desc[0].addr); + uint64_t nodeid = in_header->nodeid; + + inode_map_entry *entry = NULL; + HASH_FIND_INT(vfs->inode_map, &nodeid, entry); + if (!entry) { + return; + } + + DIR *dir = opendir(entry->path); + if (!dir) { + return; + } + + /* Allocate dir_handle_t structure */ + dir_handle_t *handle = malloc(sizeof(dir_handle_t)); + if (!handle) { + closedir(dir); + return; + } + handle->dir = dir; + + /* Dynamically allocate and copy the path string */ + size_t path_len = strlen(entry->path) + 1; + handle->path = malloc(path_len); + if (!handle->path) { + closedir(dir); + free(handle); + return; + } + memcpy(handle->path, entry->path, path_len); + + struct fuse_open_out *open_out = + (struct fuse_open_out *) ((uintptr_t) vfs->ram + vq_desc[3].addr); + memset(open_out, 0, sizeof(*open_out)); + open_out->fh = (uint64_t) handle; + open_out->open_flags = 0; + + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.len = + sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out); + header_resp->out.error = 0; + *plen = header_resp->out.len; +} + +static void virtio_fs_readdirplus_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + struct fuse_read_in *read_in = + (struct fuse_read_in *) ((uintptr_t) vfs->ram + vq_desc[1].addr); + dir_handle_t *handle = (dir_handle_t *) (uintptr_t) read_in->fh; + if (!handle || !handle->dir) { + return; + } + + DIR *dir = handle->dir; + const char *dir_path = handle->path; + + uintptr_t base = (uintptr_t) vfs->ram + vq_desc[3].addr; + size_t offset = 0; + + rewinddir(dir); + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + size_t dir_len = strlen(dir_path); + size_t name_len = strlen(entry->d_name); + size_t full_len = dir_len + 1 + name_len + 1; /* '/' + name + '\0' */ + + /* Dynamically allocate buffer for full_path */ + char *full_path = (char *) malloc(full_len); + if (!full_path) { + fprintf(stderr, "malloc failed for full_path\n"); + continue; + } + + /* Build the full path */ + memcpy(full_path, dir_path, dir_len); + full_path[dir_len] = '/'; + memcpy(full_path + dir_len + 1, entry->d_name, name_len); + full_path[dir_len + 1 + name_len] = '\0'; + + struct stat st; + if (stat(full_path, &st) < 0) { + printf("[READDIRPLUS] stat failed for: %s\n", full_path); + free(full_path); + continue; + } + + struct fuse_entry_out *entry_out = + (struct fuse_entry_out *) (base + offset); + memset(entry_out, 0, sizeof(*entry_out)); + entry_out->nodeid = st.st_ino; + entry_out->attr.ino = st.st_ino; + entry_out->attr.mode = st.st_mode; + entry_out->attr.nlink = st.st_nlink; + entry_out->attr.size = st.st_size; + entry_out->attr.atime = st.st_atime; + entry_out->attr.mtime = st.st_mtime; + entry_out->attr.ctime = st.st_ctime; + entry_out->attr.uid = st.st_uid; + entry_out->attr.gid = st.st_gid; + entry_out->attr.blksize = st.st_blksize; + entry_out->attr.blocks = st.st_blocks; + + struct fuse_direntplus *direntplus = + (struct fuse_direntplus *) (base + offset + + sizeof(struct fuse_entry_out)); + direntplus->dirent.ino = st.st_ino; + direntplus->dirent.namelen = name_len; + direntplus->dirent.type = S_ISDIR(st.st_mode) ? 4 : 8; + memcpy(direntplus->dirent.name, entry->d_name, name_len); + + size_t dirent_size = sizeof(struct fuse_direntplus) + name_len; + size_t dirent_aligned = (dirent_size + 7) & ~7; + offset += sizeof(struct fuse_entry_out) + dirent_aligned; + printf("%s ", entry->d_name); + + free(full_path); + } + printf("\n"); + + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.len = sizeof(struct fuse_out_header) + offset; + header_resp->out.error = 0; + *plen = header_resp->out.len; + if (header_resp->out.error) + printf("[READDIRPLUS] error: %d\n", header_resp->out.error); +} + +static void virtio_fs_releasedir_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + struct fuse_release_in *release_in = + (struct fuse_release_in *) ((uintptr_t) vfs->ram + vq_desc[1].addr); + dir_handle_t *handle = (dir_handle_t *) (uintptr_t) release_in->fh; + if (handle) { + if (handle->dir) + closedir(handle->dir); + if (handle->path) + free(handle->path); + free(handle); + } + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.len = sizeof(struct fuse_out_header); + header_resp->out.error = 0; + *plen = header_resp->out.len; +} + +static void virtio_fs_lookup_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + const struct fuse_in_header *in_header = + (struct fuse_in_header *) ((uintptr_t) vfs->ram + vq_desc[0].addr); + uint64_t parent_inode = in_header->nodeid; + + struct fuse_lookup_in *lookup_in = + (struct fuse_lookup_in *) ((uintptr_t) vfs->ram + vq_desc[1].addr); + char *name = (char *) (lookup_in); + size_t name_len = vq_desc[1].len; + + if (name_len == 0) { + return; + } + char *name_buf = malloc(name_len + 1); + if (!name_buf) { + fprintf(stderr, "malloc failed for name_buf\n"); + return; + } + memcpy(name_buf, name, name_len); + name_buf[name_len] = '\0'; + + inode_map_entry *parent_entry = NULL; + HASH_FIND_INT(vfs->inode_map, &parent_inode, parent_entry); + if (!parent_entry) { + free(name_buf); + return; + } + const char *parent_path = parent_entry->path; + + size_t parent_len = strlen(parent_path); + size_t name_len1 = strlen(name_buf); + size_t host_path_len = parent_len + 1 + name_len1 + 1; + + char *host_path = malloc(host_path_len); + if (!host_path) { + fprintf(stderr, "malloc failed for host_path\n"); + free(name_buf); + return; + } + memcpy(host_path, parent_path, parent_len); + host_path[parent_len] = '/'; + memcpy(host_path + parent_len + 1, name_buf, name_len1); + host_path[parent_len + 1 + name_len1] = '\0'; + + struct stat st; + if (stat(host_path, &st) < 0) { + free(name_buf); + free(host_path); + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.error = -ENOENT; + *plen = sizeof(struct fuse_out_header); + return; + } + + inode_map_entry *entry = NULL; + HASH_FIND_INT(vfs->inode_map, &st.st_ino, entry); + if (!entry) { + entry = malloc(sizeof(inode_map_entry)); + if (!entry) { + free(name_buf); + free(host_path); + fprintf(stderr, "malloc failed for inode_map_entry\n"); + return; + } + entry->ino = st.st_ino; + entry->path = strdup(host_path); + if (!entry->path) { + free(entry); + free(name_buf); + free(host_path); + fprintf(stderr, "strdup failed for entry->path\n"); + return; + } + HASH_ADD_INT(vfs->inode_map, ino, entry); + } + + free(name_buf); + free(host_path); + + struct fuse_entry_out *entry_out = + (struct fuse_entry_out *) ((uintptr_t) vfs->ram + vq_desc[3].addr); + memset(entry_out, 0, sizeof(*entry_out)); + entry_out->nodeid = st.st_ino; + entry_out->attr.ino = st.st_ino; + entry_out->attr.mode = st.st_mode; + entry_out->attr.nlink = st.st_nlink; + entry_out->attr.size = st.st_size; + entry_out->attr.atime = st.st_atime; + entry_out->attr.mtime = st.st_mtime; + entry_out->attr.ctime = st.st_ctime; + entry_out->attr.uid = st.st_uid; + entry_out->attr.gid = st.st_gid; + entry_out->attr.blksize = st.st_blksize; + entry_out->attr.blocks = st.st_blocks; + + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.len = + sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out); + header_resp->out.error = 0; + *plen = header_resp->out.len; +} + +static void virtio_fs_open_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + const struct fuse_in_header *in_header = + (struct fuse_in_header *) ((uintptr_t) vfs->ram + vq_desc[0].addr); + uint64_t inode = in_header->nodeid; + + const char *target_path = NULL; + if (inode == 1) { + target_path = vfs->shared_dir; + } else { + inode_map_entry *entry = NULL; + HASH_FIND_INT(vfs->inode_map, &inode, entry); + if (!entry) { + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + + vq_desc[2].addr); + header_resp->out.error = -ENOENT; + *plen = sizeof(struct fuse_out_header); + return; + } + target_path = entry->path; + } + + int fd = open(target_path, O_RDONLY); + if (fd < 0) { + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.error = -errno; + *plen = sizeof(struct fuse_out_header); + printf("[OPEN] failed: %s, error=%s\n", target_path, strerror(errno)); + return; + } + + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + struct fuse_open_out *open_out = + (struct fuse_open_out *) ((uintptr_t) vfs->ram + vq_desc[3].addr); + + open_out->fh = (uint64_t) fd; + open_out->open_flags = 0; + header_resp->out.len = + sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out); + header_resp->out.error = 0; + *plen = header_resp->out.len; +} + +static void virtio_fs_read_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + struct fuse_read_in *read_in = + (struct fuse_read_in *) ((uintptr_t) vfs->ram + vq_desc[1].addr); + int fd = (int) (uintptr_t) read_in->fh; + off_t offset = read_in->offset; + size_t size = read_in->size; + + char *buf = malloc(size); + if (!buf) { + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.error = -ENOMEM; + *plen = sizeof(struct fuse_out_header); + fprintf(stderr, "[READ] malloc failed, size=%zu\n", size); + return; + } + + ssize_t n = pread(fd, buf, size, offset); + if (n < 0) { + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.error = -errno; + *plen = sizeof(struct fuse_out_header); + fprintf(stderr, "[READ] failed: fd=%d, errno=%d\n", fd, errno); + free(buf); + return; + } + + memcpy((void *) ((uintptr_t) vfs->ram + vq_desc[3].addr), buf, n); + + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.len = sizeof(struct fuse_out_header) + n; + header_resp->out.error = 0; + *plen = header_resp->out.len; + + free(buf); +} + +static void virtio_fs_release_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + struct fuse_release_in *release_in = + (struct fuse_release_in *) ((uintptr_t) vfs->ram + vq_desc[1].addr); + int fd = (int) release_in->fh; + close(fd); + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.len = sizeof(struct fuse_out_header); + header_resp->out.error = 0; + *plen = header_resp->out.len; +} + +static void virtio_fs_flush_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.len = sizeof(struct fuse_out_header); + header_resp->out.error = 0; + *plen = header_resp->out.len; +} + + +static void virtio_fs_forget_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.len = sizeof(struct fuse_out_header); + header_resp->out.error = 0; + *plen = header_resp->out.len; +} + +static void virtio_fs_destroy_handler(virtio_fs_state_t *vfs, + struct virtq_desc vq_desc[4], + uint32_t *plen) +{ + struct vfs_resp_header *header_resp = + (struct vfs_resp_header *) ((uintptr_t) vfs->ram + vq_desc[2].addr); + header_resp->out.len = sizeof(struct fuse_out_header); + header_resp->out.error = 0; + *plen = header_resp->out.len; +} + +static int virtio_fs_desc_handler(virtio_fs_state_t *vfs, + const virtio_fs_queue_t *queue, + uint32_t desc_idx, + uint32_t *plen) +{ + struct virtq_desc vq_desc[4]; + for (int i = 0; i < 4; i++) { + /* The size of the `struct virtq_desc` is 4 words */ + const struct virtq_desc *desc = + (struct virtq_desc *) &vfs->ram[queue->QueueDesc + desc_idx * 4]; + vq_desc[i].addr = desc->addr; + vq_desc[i].len = desc->len; + vq_desc[i].flags = desc->flags; + desc_idx = desc->next; + } + + + const struct vfs_req_header *header_req = + (struct vfs_req_header *) ((uintptr_t) vfs->ram + vq_desc[0].addr); + uint32_t op = header_req->in.opcode; + switch (op) { + case FUSE_INIT: + virtio_fs_init_handler(vfs, vq_desc, plen); + break; + case FUSE_GETATTR: + virtio_fs_getattr_handler(vfs, vq_desc, plen); + break; + case FUSE_OPENDIR: + virtio_fs_opendir_handler(vfs, vq_desc, plen); + break; + case FUSE_READDIRPLUS: + virtio_fs_readdirplus_handler(vfs, vq_desc, plen); + break; + case FUSE_LOOKUP: + virtio_fs_lookup_handler(vfs, vq_desc, plen); + break; + case FUSE_FORGET: + virtio_fs_forget_handler(vfs, vq_desc, plen); + break; + case FUSE_RELEASEDIR: + virtio_fs_releasedir_handler(vfs, vq_desc, plen); + break; + case FUSE_OPEN: + virtio_fs_open_handler(vfs, vq_desc, plen); + break; + case FUSE_READ: + virtio_fs_read_handler(vfs, vq_desc, plen); + break; + case FUSE_RELEASE: + virtio_fs_release_handler(vfs, vq_desc, plen); + break; + case FUSE_FLUSH: + virtio_fs_flush_handler(vfs, vq_desc, plen); + break; + case FUSE_DESTROY: + virtio_fs_destroy_handler(vfs, vq_desc, plen); + break; + default: + fprintf(stderr, "unsupported virtio-fs operation!\n"); + return -1; + } + /* TODO: FUSE_WRITE, FUSE_MKDIR, FUSE_RMDIR, FUSE_CREATE */ + + return 0; +} + +static void virtio_queue_notify_handler(virtio_fs_state_t *vfs, int index) +{ + uint32_t *ram = vfs->ram; + virtio_fs_queue_t *queue = &vfs->queues[index]; + if (vfs->Status & VIRTIO_STATUS__DEVICE_NEEDS_RESET) + return; + + if (!((vfs->Status & VIRTIO_STATUS__DRIVER_OK) && queue->ready)) + return virtio_fs_set_fail(vfs); + + uint16_t new_avail = ram[queue->QueueAvail] >> 16; + if (new_avail - queue->last_avail > (uint16_t) queue->QueueNum) + return virtio_fs_set_fail(vfs); + + if (queue->last_avail == new_avail) + return; + + uint16_t new_used = ram[queue->QueueUsed] >> 16; + while (queue->last_avail != new_avail) { + uint16_t queue_idx = queue->last_avail % queue->QueueNum; + uint16_t buffer_idx = ram[queue->QueueAvail + 1 + queue_idx / 2] >> + (16 * (queue_idx % 2)); + + uint32_t len = 0; + int result = virtio_fs_desc_handler(vfs, queue, buffer_idx, &len); + if (result != 0) + return virtio_fs_set_fail(vfs); + + uint32_t vq_used_addr = + queue->QueueUsed + 1 + (new_used % queue->QueueNum) * 2; + ram[vq_used_addr] = buffer_idx; + ram[vq_used_addr + 1] = len; + queue->last_avail++; + new_used++; + } + + vfs->ram[queue->QueueUsed] &= MASK(16); + vfs->ram[queue->QueueUsed] |= ((uint32_t) new_used) << 16; + + if (!(ram[queue->QueueAvail] & 1)) + vfs->InterruptStatus |= VIRTIO_INT__USED_RING; +} + +static bool virtio_fs_reg_read(virtio_fs_state_t *vfs, + uint32_t addr, + uint32_t *value) +{ +#define _(reg) VIRTIO_##reg + switch (addr) { + case _(MagicValue): + *value = 0x74726976; /* "virt" */ + return true; + case _(Version): + *value = 2; + return true; + case _(DeviceID): + *value = 26; /* = virtio-fs */ + return true; + case _(VendorID): + *value = VIRTIO_VENDOR_ID; + return true; + case _(DeviceFeatures): + *value = vfs->DeviceFeaturesSel == 0 + ? VFS_FEATURES_0 + : (vfs->DeviceFeaturesSel == 1 ? VFS_FEATURES_1 : 0); + return true; + case _(QueueNumMax): + *value = VFS_QUEUE_NUM_MAX; + return true; + case _(QueueReady): + *value = VFS_QUEUE.ready ? 1 : 0; + return true; + case _(InterruptStatus): + *value = vfs->InterruptStatus; + return true; + case _(Status): + *value = vfs->Status; + return true; + case _(ConfigGeneration): + *value = 0; + return true; + case NUM_REQUEST_QUEUES_ADDR: + *value = ((uint32_t *) PRIV(vfs))[addr - _(Config)]; + return true; + default: + if (!RANGE_CHECK((addr >> 2), _(Config), + sizeof(struct virtio_fs_config))) + return false; + uint32_t cfg_offset = addr - ((_(Config)) << 2); + uint8_t *cfg_bytes = (uint8_t *) PRIV(vfs); + *value = cfg_bytes[cfg_offset]; + + return true; + } +#undef _ +} + +static bool virtio_fs_reg_write(virtio_fs_state_t *vfs, + uint32_t addr, + uint32_t value) +{ +#define _(reg) VIRTIO_##reg + switch (addr) { + case _(DeviceFeaturesSel): + vfs->DeviceFeaturesSel = value; + return true; + case _(DriverFeatures): + if (vfs->DriverFeaturesSel == 0) + vfs->DriverFeatures = value; + return true; + case _(DriverFeaturesSel): + vfs->DriverFeaturesSel = value; + return true; + case _(QueueSel): + if (value < ARRAY_SIZE(vfs->queues)) { + vfs->QueueSel = value; + } else + virtio_fs_set_fail(vfs); + return true; + case _(QueueNum): + if (value > 0 && value <= VFS_QUEUE_NUM_MAX) { + VFS_QUEUE.QueueNum = value; + } else + virtio_fs_set_fail(vfs); + return true; + case _(QueueReady): + VFS_QUEUE.ready = value & 1; + if (value & 1) + VFS_QUEUE.last_avail = vfs->ram[VFS_QUEUE.QueueAvail] >> 16; + return true; + case _(QueueDescLow): + VFS_QUEUE.QueueDesc = vfs_preprocess(vfs, value); + return true; + case _(QueueDescHigh): + if (value) { + virtio_fs_set_fail(vfs); + } + return true; + case _(QueueDriverLow): + VFS_QUEUE.QueueAvail = vfs_preprocess(vfs, value); + return true; + case _(QueueDriverHigh): + if (value) { + virtio_fs_set_fail(vfs); + } + return true; + case _(QueueDeviceLow): + VFS_QUEUE.QueueUsed = vfs_preprocess(vfs, value); + return true; + case _(QueueDeviceHigh): + if (value) { + virtio_fs_set_fail(vfs); + } + return true; + case _(QueueNotify): + if (value < ARRAY_SIZE(vfs->queues)) { + virtio_queue_notify_handler(vfs, value); + } else + virtio_fs_set_fail(vfs); + return true; + case _(InterruptACK): + vfs->InterruptStatus &= ~value; + return true; + case _(Status): + virtio_fs_update_status(vfs, value); + return true; + default: + /* Invalid address which exceeded the range */ + if (!RANGE_CHECK(addr, _(Config), sizeof(struct virtio_fs_config))) + return false; + + /* Write configuration to the corresponding register */ + ((uint32_t *) PRIV(vfs))[addr - _(Config)] = value; + + return true; + } +#undef _ +} + +void virtio_fs_read(hart_t *vm, + virtio_fs_state_t *vfs, + uint32_t addr, + uint8_t width, + uint32_t *value) +{ + switch (width) { + case RV_MEM_LW: + if (!virtio_fs_reg_read(vfs, addr >> 2, value)) { + vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val); + } + break; + case RV_MEM_LBU: + case RV_MEM_LB: + if (!virtio_fs_reg_read(vfs, addr, value)) { + vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val); + } + break; + case RV_MEM_LHU: + case RV_MEM_LH: + vm_set_exception(vm, RV_EXC_LOAD_MISALIGN, vm->exc_val); + break; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + break; + } +} + +void virtio_fs_write(hart_t *vm, + virtio_fs_state_t *vfs, + uint32_t addr, + uint8_t width, + uint32_t value) +{ + switch (width) { + case RV_MEM_SW: + if (!virtio_fs_reg_write(vfs, addr >> 2, value)) { + vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val); + } + break; + case RV_MEM_SB: + case RV_MEM_SH: + vm_set_exception(vm, RV_EXC_STORE_MISALIGN, vm->exc_val); + break; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + break; + } +} + +bool virtio_fs_init(virtio_fs_state_t *vfs, char *mtag, char *dir) +{ + if (vfs_dev_cnt >= VFS_DEV_CNT_MAX) { + fprintf(stderr, + "Exceeded the number of virtio-fs devices that can be " + "allocated.\n"); + exit(2); + } + + vfs->priv = &vfs_configs[vfs_dev_cnt++]; + + if (!dir) { + /* -s parameter is empty, virtio-fs is unused. */ + return false; + } + + int dir_fd = open(dir, O_RDONLY); + if (dir_fd < 0) { + fprintf(stderr, "Could not open directory: %s\n", dir); + exit(2); + } + + size_t dir_len = strlen(dir) + 1; + vfs->shared_dir = malloc(dir_len); + if (!vfs->shared_dir) { + fprintf(stderr, "Failed to allocate memory for shared_dir\n"); + exit(2); + } + memcpy(vfs->shared_dir, dir, dir_len); + + snprintf(PRIV(vfs)->tag, sizeof(PRIV(vfs)->tag), "%s", mtag); + PRIV(vfs)->num_request_queues = 3; + vfs->mount_tag = mtag; + + inode_map_entry *root_entry = malloc(sizeof(inode_map_entry)); + if (!root_entry) { + fprintf(stderr, "Failed to allocate memory for root_entry\n"); + return false; + } + root_entry->ino = 1; + + root_entry->path = strdup(vfs->shared_dir); + if (!root_entry->path) { + fprintf(stderr, "Failed to allocate memory for root_entry->path\n"); + free(root_entry); + return false; + } + + HASH_ADD_INT(vfs->inode_map, ino, root_entry); + + return true; +} diff --git a/virtio.h b/virtio.h index c142da5..95357d6 100644 --- a/virtio.h +++ b/virtio.h @@ -24,6 +24,53 @@ #define VIRTIO_BLK_S_IOERR 1 #define VIRTIO_BLK_S_UNSUPP 2 +/* The FUSE OP codes are from + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/fuse.h + */ +#define FUSE_INIT 26 +#define FUSE_GETATTR 3 +#define FUSE_OPENDIR 27 +#define FUSE_READDIRPLUS 44 +#define FUSE_LOOKUP 1 +#define FUSE_FORGET 2 +#define FUSE_RELEASEDIR 29 +#define FUSE_OPEN 14 +#define FUSE_READ 15 +#define FUSE_RELEASE 18 +#define FUSE_FLUSH 25 +#define FUSE_DESTROY 38 +/* TODO OP codes are below: */ +#define FUSE_MKDIR 9 +#define FUSE_WRITE 16 +#define FUSE_RMDIR 11 +#define FUSE_CREATE 35 + +#define FUSE_ASYNC_READ (1 << 0) +#define FUSE_POSIX_LOCKS (1 << 1) +#define FUSE_FILE_OPS (1 << 2) +#define FUSE_ATOMIC_O_TRUNC (1 << 3) +#define FUSE_EXPORT_SUPPORT (1 << 4) +#define FUSE_BIG_WRITES (1 << 5) +#define FUSE_DONT_MASK (1 << 6) +#define FUSE_SPLICE_WRITE (1 << 7) +#define FUSE_SPLICE_MOVE (1 << 8) +#define FUSE_SPLICE_READ (1 << 9) +#define FUSE_FLOCK_LOCKS (1 << 10) +#define FUSE_HAS_IOCTL_DIR (1 << 11) +#define FUSE_AUTO_INVAL_DATA (1 << 12) +#define FUSE_DO_READDIRPLUS (1 << 13) +#define FUSE_READDIRPLUS_AUTO (1 << 14) +#define FUSE_ASYNC_DIO (1 << 15) +#define FUSE_WRITEBACK_CACHE (1 << 16) +#define FUSE_NO_OPEN_SUPPORT (1 << 17) +#define FUSE_PARALLEL_DIROPS (1 << 18) +#define FUSE_HANDLE_KILLPRIV (1 << 19) +#define FUSE_POSIX_ACL (1 << 20) +#define FUSE_ABORT_ERROR (1 << 21) +#define FUSE_MAX_PAGES (1 << 22) +#define FUSE_CACHE_SYMLINKS (1 << 23) +#define FUSE_NO_OPENDIR_SUPPORT (1 << 24) + /* VirtIO MMIO registers */ #define VIRTIO_REG_LIST \ _(MagicValue, 0x000) /* R */ \