aboutsummaryrefslogtreecommitdiff
path: root/jslinux-2019-12-21/tinyemu-2019-12-21/fs_net.c
diff options
context:
space:
mode:
authorMitchell Riedstra <mitch@riedstra.dev>2025-12-24 19:49:57 -0500
committerMitchell Riedstra <mitch@riedstra.dev>2025-12-24 19:49:57 -0500
commit939ac4319cb047a37ba46f84eff81948063f6954 (patch)
tree5112cf8aad73125a13f5b52c0290a7f26f948b52 /jslinux-2019-12-21/tinyemu-2019-12-21/fs_net.c
parent3a1b5ba15b89c907f9bf66a0761ffdd73b32208b (diff)
downloadunixv4-939ac4319cb047a37ba46f84eff81948063f6954.tar.gz
unixv4-939ac4319cb047a37ba46f84eff81948063f6954.tar.xz
Add working webpage for unix v4
Diffstat (limited to 'jslinux-2019-12-21/tinyemu-2019-12-21/fs_net.c')
-rw-r--r--jslinux-2019-12-21/tinyemu-2019-12-21/fs_net.c2910
1 files changed, 2910 insertions, 0 deletions
diff --git a/jslinux-2019-12-21/tinyemu-2019-12-21/fs_net.c b/jslinux-2019-12-21/tinyemu-2019-12-21/fs_net.c
new file mode 100644
index 0000000..c7c7484
--- /dev/null
+++ b/jslinux-2019-12-21/tinyemu-2019-12-21/fs_net.c
@@ -0,0 +1,2910 @@
+/*
+ * Networked Filesystem using HTTP
+ *
+ * Copyright (c) 2016-2017 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <sys/time.h>
+#include <ctype.h>
+
+#include "cutils.h"
+#include "list.h"
+#include "fs.h"
+#include "fs_utils.h"
+#include "fs_wget.h"
+#include "fbuf.h"
+
+#if defined(EMSCRIPTEN)
+#include <emscripten.h>
+#endif
+
+/*
+ TODO:
+ - implement fs_lock/fs_getlock
+ - update fs_size with links ?
+ - limit fs_size in dirent creation
+ - limit filename length
+*/
+
+//#define DEBUG_CACHE
+#if !defined(EMSCRIPTEN)
+#define DUMP_CACHE_LOAD
+#endif
+
+#if defined(EMSCRIPTEN)
+#define DEFAULT_INODE_CACHE_SIZE (64 * 1024 * 1024)
+#else
+#define DEFAULT_INODE_CACHE_SIZE (256 * 1024 * 1024)
+#endif
+
+typedef enum {
+ FT_FIFO = 1,
+ FT_CHR = 2,
+ FT_DIR = 4,
+ FT_BLK = 6,
+ FT_REG = 8,
+ FT_LNK = 10,
+ FT_SOCK = 12,
+} FSINodeTypeEnum;
+
+typedef enum {
+ REG_STATE_LOCAL, /* local content */
+ REG_STATE_UNLOADED, /* content not loaded */
+ REG_STATE_LOADING, /* content is being loaded */
+ REG_STATE_LOADED, /* loaded, not modified, stored in cached_inode_list */
+} FSINodeRegStateEnum;
+
+typedef struct FSBaseURL {
+ struct list_head link;
+ int ref_count;
+ char *base_url_id;
+ char *url;
+ char *user;
+ char *password;
+ BOOL encrypted;
+ AES_KEY aes_state;
+} FSBaseURL;
+
+typedef struct FSINode {
+ struct list_head link;
+ uint64_t inode_num; /* inode number */
+ int32_t refcount;
+ int32_t open_count;
+ FSINodeTypeEnum type;
+ uint32_t mode;
+ uint32_t uid;
+ uint32_t gid;
+ uint32_t mtime_sec;
+ uint32_t ctime_sec;
+ uint32_t mtime_nsec;
+ uint32_t ctime_nsec;
+ union {
+ struct {
+ FSINodeRegStateEnum state;
+ size_t size; /* real file size */
+ FileBuffer fbuf;
+ FSBaseURL *base_url;
+ FSFileID file_id; /* network file ID */
+ struct list_head link;
+ struct FSOpenInfo *open_info; /* used in LOADING state */
+ BOOL is_fscmd;
+#ifdef DUMP_CACHE_LOAD
+ char *filename;
+#endif
+ } reg;
+ struct {
+ struct list_head de_list; /* list of FSDirEntry */
+ int size;
+ } dir;
+ struct {
+ uint32_t major;
+ uint32_t minor;
+ } dev;
+ struct {
+ char *name;
+ } symlink;
+ } u;
+} FSINode;
+
+typedef struct {
+ struct list_head link;
+ FSINode *inode;
+ uint8_t mark; /* temporary use only */
+ char name[0];
+} FSDirEntry;
+
+typedef enum {
+ FS_CMD_XHR,
+ FS_CMD_PBKDF2,
+} FSCMDRequestEnum;
+
+#define FS_CMD_REPLY_LEN_MAX 64
+
+typedef struct {
+ FSCMDRequestEnum type;
+ struct CmdXHRState *xhr_state;
+ int reply_len;
+ uint8_t reply_buf[FS_CMD_REPLY_LEN_MAX];
+} FSCMDRequest;
+
+struct FSFile {
+ uint32_t uid;
+ FSINode *inode;
+ BOOL is_opened;
+ uint32_t open_flags;
+ FSCMDRequest *req;
+};
+
+typedef struct {
+ struct list_head link;
+ BOOL is_archive;
+ const char *name;
+} PreloadFile;
+
+typedef struct {
+ struct list_head link;
+ FSFileID file_id;
+ struct list_head file_list; /* list of PreloadFile.link */
+} PreloadEntry;
+
+typedef struct {
+ struct list_head link;
+ FSFileID file_id;
+ uint64_t size;
+ const char *name;
+} PreloadArchiveFile;
+
+typedef struct {
+ struct list_head link;
+ const char *name;
+ struct list_head file_list; /* list of PreloadArchiveFile.link */
+} PreloadArchive;
+
+typedef struct FSDeviceMem {
+ FSDevice common;
+
+ struct list_head inode_list; /* list of FSINode */
+ int64_t inode_count; /* current number of inodes */
+ uint64_t inode_limit;
+ int64_t fs_blocks;
+ uint64_t fs_max_blocks;
+ uint64_t inode_num_alloc;
+ int block_size_log2;
+ uint32_t block_size; /* for stat/statfs */
+ FSINode *root_inode;
+ struct list_head inode_cache_list; /* list of FSINode.u.reg.link */
+ int64_t inode_cache_size;
+ int64_t inode_cache_size_limit;
+ struct list_head preload_list; /* list of PreloadEntry.link */
+ struct list_head preload_archive_list; /* list of PreloadArchive.link */
+ /* network */
+ struct list_head base_url_list; /* list of FSBaseURL.link */
+ char *import_dir;
+#ifdef DUMP_CACHE_LOAD
+ BOOL dump_cache_load;
+ BOOL dump_started;
+ char *dump_preload_dir;
+ FILE *dump_preload_file;
+ FILE *dump_preload_archive_file;
+
+ char *dump_archive_name;
+ uint64_t dump_archive_size;
+ FILE *dump_archive_file;
+
+ int dump_archive_num;
+ struct list_head dump_preload_list; /* list of PreloadFile.link */
+ struct list_head dump_exclude_list; /* list of PreloadFile.link */
+#endif
+} FSDeviceMem;
+
+typedef enum {
+ FS_OPEN_WGET_REG,
+ FS_OPEN_WGET_ARCHIVE,
+ FS_OPEN_WGET_ARCHIVE_FILE,
+} FSOpenWgetEnum;
+
+typedef struct FSOpenInfo {
+ FSDevice *fs;
+ FSOpenWgetEnum open_type;
+
+ /* used for FS_OPEN_WGET_REG, FS_OPEN_WGET_ARCHIVE */
+ XHRState *xhr;
+ FSINode *n;
+ DecryptFileState *dec_state;
+ size_t cur_pos;
+
+ struct list_head archive_link; /* FS_OPEN_WGET_ARCHIVE_FILE */
+ uint64_t archive_offset; /* FS_OPEN_WGET_ARCHIVE_FILE */
+ struct list_head archive_file_list; /* FS_OPEN_WGET_ARCHIVE */
+
+ /* the following is set in case there is a fs_open callback */
+ FSFile *f;
+ FSOpenCompletionFunc *cb;
+ void *opaque;
+} FSOpenInfo;
+
+static void fs_close(FSDevice *fs, FSFile *f);
+static void inode_decref(FSDevice *fs1, FSINode *n);
+static int fs_cmd_write(FSDevice *fs, FSFile *f, uint64_t offset,
+ const uint8_t *buf, int buf_len);
+static int fs_cmd_read(FSDevice *fs, FSFile *f, uint64_t offset,
+ uint8_t *buf, int buf_len);
+static int fs_truncate(FSDevice *fs1, FSINode *n, uint64_t size);
+static void fs_open_end(FSOpenInfo *oi);
+static void fs_base_url_decref(FSDevice *fs, FSBaseURL *bu);
+static FSBaseURL *fs_net_set_base_url(FSDevice *fs1,
+ const char *base_url_id,
+ const char *url,
+ const char *user, const char *password,
+ AES_KEY *aes_state);
+static void fs_cmd_close(FSDevice *fs, FSFile *f);
+static void fs_error_archive(FSOpenInfo *oi);
+#ifdef DUMP_CACHE_LOAD
+static void dump_loaded_file(FSDevice *fs1, FSINode *n);
+#endif
+
+#if !defined(EMSCRIPTEN)
+/* file buffer (the content of the buffer can be stored elsewhere) */
+void file_buffer_init(FileBuffer *bs)
+{
+ bs->data = NULL;
+ bs->allocated_size = 0;
+}
+
+void file_buffer_reset(FileBuffer *bs)
+{
+ free(bs->data);
+ file_buffer_init(bs);
+}
+
+int file_buffer_resize(FileBuffer *bs, size_t new_size)
+{
+ uint8_t *new_data;
+ new_data = realloc(bs->data, new_size);
+ if (!new_data && new_size != 0)
+ return -1;
+ bs->data = new_data;
+ bs->allocated_size = new_size;
+ return 0;
+}
+
+void file_buffer_write(FileBuffer *bs, size_t offset, const uint8_t *buf,
+ size_t size)
+{
+ memcpy(bs->data + offset, buf, size);
+}
+
+void file_buffer_set(FileBuffer *bs, size_t offset, int val, size_t size)
+{
+ memset(bs->data + offset, val, size);
+}
+
+void file_buffer_read(FileBuffer *bs, size_t offset, uint8_t *buf,
+ size_t size)
+{
+ memcpy(buf, bs->data + offset, size);
+}
+#endif
+
+static int64_t to_blocks(FSDeviceMem *fs, uint64_t size)
+{
+ return (size + fs->block_size - 1) >> fs->block_size_log2;
+}
+
+static FSINode *inode_incref(FSDevice *fs, FSINode *n)
+{
+ n->refcount++;
+ return n;
+}
+
+static FSINode *inode_inc_open(FSDevice *fs, FSINode *n)
+{
+ n->open_count++;
+ return n;
+}
+
+static void inode_free(FSDevice *fs1, FSINode *n)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+
+ // printf("inode_free=%" PRId64 "\n", n->inode_num);
+ assert(n->refcount == 0);
+ assert(n->open_count == 0);
+ switch(n->type) {
+ case FT_REG:
+ fs->fs_blocks -= to_blocks(fs, n->u.reg.size);
+ assert(fs->fs_blocks >= 0);
+ file_buffer_reset(&n->u.reg.fbuf);
+#ifdef DUMP_CACHE_LOAD
+ free(n->u.reg.filename);
+#endif
+ switch(n->u.reg.state) {
+ case REG_STATE_LOADED:
+ list_del(&n->u.reg.link);
+ fs->inode_cache_size -= n->u.reg.size;
+ assert(fs->inode_cache_size >= 0);
+ fs_base_url_decref(fs1, n->u.reg.base_url);
+ break;
+ case REG_STATE_LOADING:
+ {
+ FSOpenInfo *oi = n->u.reg.open_info;
+ if (oi->xhr)
+ fs_wget_free(oi->xhr);
+ if (oi->open_type == FS_OPEN_WGET_ARCHIVE) {
+ fs_error_archive(oi);
+ }
+ fs_open_end(oi);
+ fs_base_url_decref(fs1, n->u.reg.base_url);
+ }
+ break;
+ case REG_STATE_UNLOADED:
+ fs_base_url_decref(fs1, n->u.reg.base_url);
+ break;
+ case REG_STATE_LOCAL:
+ break;
+ default:
+ abort();
+ }
+ break;
+ case FT_LNK:
+ free(n->u.symlink.name);
+ break;
+ case FT_DIR:
+ assert(list_empty(&n->u.dir.de_list));
+ break;
+ default:
+ break;
+ }
+ list_del(&n->link);
+ free(n);
+ fs->inode_count--;
+ assert(fs->inode_count >= 0);
+}
+
+static void inode_decref(FSDevice *fs1, FSINode *n)
+{
+ assert(n->refcount >= 1);
+ if (--n->refcount <= 0 && n->open_count <= 0) {
+ inode_free(fs1, n);
+ }
+}
+
+static void inode_dec_open(FSDevice *fs1, FSINode *n)
+{
+ assert(n->open_count >= 1);
+ if (--n->open_count <= 0 && n->refcount <= 0) {
+ inode_free(fs1, n);
+ }
+}
+
+static void inode_update_mtime(FSDevice *fs, FSINode *n)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ n->mtime_sec = tv.tv_sec;
+ n->mtime_nsec = tv.tv_usec * 1000;
+}
+
+static FSINode *inode_new(FSDevice *fs1, FSINodeTypeEnum type,
+ uint32_t mode, uint32_t uid, uint32_t gid)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ FSINode *n;
+
+ n = mallocz(sizeof(*n));
+ n->refcount = 1;
+ n->open_count = 0;
+ n->inode_num = fs->inode_num_alloc;
+ fs->inode_num_alloc++;
+ n->type = type;
+ n->mode = mode & 0xfff;
+ n->uid = uid;
+ n->gid = gid;
+
+ switch(type) {
+ case FT_REG:
+ file_buffer_init(&n->u.reg.fbuf);
+ break;
+ case FT_DIR:
+ init_list_head(&n->u.dir.de_list);
+ break;
+ default:
+ break;
+ }
+
+ list_add(&n->link, &fs->inode_list);
+ fs->inode_count++;
+
+ inode_update_mtime(fs1, n);
+ n->ctime_sec = n->mtime_sec;
+ n->ctime_nsec = n->mtime_nsec;
+
+ return n;
+}
+
+/* warning: the refcount of 'n1' is not incremented by this function */
+/* XXX: test FS max size */
+static FSDirEntry *inode_dir_add(FSDevice *fs1, FSINode *n, const char *name,
+ FSINode *n1)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ FSDirEntry *de;
+ int name_len, dirent_size, new_size;
+ assert(n->type == FT_DIR);
+
+ name_len = strlen(name);
+ de = mallocz(sizeof(*de) + name_len + 1);
+ de->inode = n1;
+ memcpy(de->name, name, name_len + 1);
+ dirent_size = sizeof(*de) + name_len + 1;
+ new_size = n->u.dir.size + dirent_size;
+ fs->fs_blocks += to_blocks(fs, new_size) - to_blocks(fs, n->u.dir.size);
+ n->u.dir.size = new_size;
+ list_add_tail(&de->link, &n->u.dir.de_list);
+ return de;
+}
+
+static FSDirEntry *inode_search(FSINode *n, const char *name)
+{
+ struct list_head *el;
+ FSDirEntry *de;
+
+ if (n->type != FT_DIR)
+ return NULL;
+
+ list_for_each(el, &n->u.dir.de_list) {
+ de = list_entry(el, FSDirEntry, link);
+ if (!strcmp(de->name, name))
+ return de;
+ }
+ return NULL;
+}
+
+static FSINode *inode_search_path1(FSDevice *fs, FSINode *n, const char *path)
+{
+ char name[1024];
+ const char *p, *p1;
+ int len;
+ FSDirEntry *de;
+
+ p = path;
+ if (*p == '/')
+ p++;
+ if (*p == '\0')
+ return n;
+ for(;;) {
+ p1 = strchr(p, '/');
+ if (!p1) {
+ len = strlen(p);
+ } else {
+ len = p1 - p;
+ p1++;
+ }
+ if (len > sizeof(name) - 1)
+ return NULL;
+ memcpy(name, p, len);
+ name[len] = '\0';
+ if (n->type != FT_DIR)
+ return NULL;
+ de = inode_search(n, name);
+ if (!de)
+ return NULL;
+ n = de->inode;
+ p = p1;
+ if (!p)
+ break;
+ }
+ return n;
+}
+
+static FSINode *inode_search_path(FSDevice *fs1, const char *path)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ if (!fs1)
+ return NULL;
+ return inode_search_path1(fs1, fs->root_inode, path);
+}
+
+static BOOL is_empty_dir(FSDevice *fs, FSINode *n)
+{
+ struct list_head *el;
+ FSDirEntry *de;
+
+ list_for_each(el, &n->u.dir.de_list) {
+ de = list_entry(el, FSDirEntry, link);
+ if (strcmp(de->name, ".") != 0 &&
+ strcmp(de->name, "..") != 0)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void inode_dirent_delete_no_decref(FSDevice *fs1, FSINode *n, FSDirEntry *de)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ int dirent_size, new_size;
+ dirent_size = sizeof(*de) + strlen(de->name) + 1;
+
+ new_size = n->u.dir.size - dirent_size;
+ fs->fs_blocks += to_blocks(fs, new_size) - to_blocks(fs, n->u.dir.size);
+ n->u.dir.size = new_size;
+ assert(n->u.dir.size >= 0);
+ assert(fs->fs_blocks >= 0);
+ list_del(&de->link);
+ free(de);
+}
+
+static void inode_dirent_delete(FSDevice *fs, FSINode *n, FSDirEntry *de)
+{
+ FSINode *n1;
+ n1 = de->inode;
+ inode_dirent_delete_no_decref(fs, n, de);
+ inode_decref(fs, n1);
+}
+
+static void flush_dir(FSDevice *fs, FSINode *n)
+{
+ struct list_head *el, *el1;
+ FSDirEntry *de;
+ list_for_each_safe(el, el1, &n->u.dir.de_list) {
+ de = list_entry(el, FSDirEntry, link);
+ inode_dirent_delete(fs, n, de);
+ }
+ assert(n->u.dir.size == 0);
+}
+
+static void fs_delete(FSDevice *fs, FSFile *f)
+{
+ fs_close(fs, f);
+ inode_dec_open(fs, f->inode);
+ free(f);
+}
+
+static FSFile *fid_create(FSDevice *fs1, FSINode *n, uint32_t uid)
+{
+ FSFile *f;
+
+ f = mallocz(sizeof(*f));
+ f->inode = inode_inc_open(fs1, n);
+ f->uid = uid;
+ return f;
+}
+
+static void inode_to_qid(FSQID *qid, FSINode *n)
+{
+ if (n->type == FT_DIR)
+ qid->type = P9_QTDIR;
+ else if (n->type == FT_LNK)
+ qid->type = P9_QTSYMLINK;
+ else
+ qid->type = P9_QTFILE;
+ qid->version = 0; /* no caching on client */
+ qid->path = n->inode_num;
+}
+
+static void fs_statfs(FSDevice *fs1, FSStatFS *st)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ st->f_bsize = 1024;
+ st->f_blocks = fs->fs_max_blocks <<
+ (fs->block_size_log2 - 10);
+ st->f_bfree = (fs->fs_max_blocks - fs->fs_blocks) <<
+ (fs->block_size_log2 - 10);
+ st->f_bavail = st->f_bfree;
+ st->f_files = fs->inode_limit;
+ st->f_ffree = fs->inode_limit - fs->inode_count;
+}
+
+static int fs_attach(FSDevice *fs1, FSFile **pf, FSQID *qid, uint32_t uid,
+ const char *uname, const char *aname)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+
+ *pf = fid_create(fs1, fs->root_inode, uid);
+ inode_to_qid(qid, fs->root_inode);
+ return 0;
+}
+
+static int fs_walk(FSDevice *fs, FSFile **pf, FSQID *qids,
+ FSFile *f, int count, char **names)
+{
+ int i;
+ FSINode *n;
+ FSDirEntry *de;
+
+ n = f->inode;
+ for(i = 0; i < count; i++) {
+ de = inode_search(n, names[i]);
+ if (!de)
+ break;
+ n = de->inode;
+ inode_to_qid(&qids[i], n);
+ }
+ *pf = fid_create(fs, n, f->uid);
+ return i;
+}
+
+static int fs_mkdir(FSDevice *fs, FSQID *qid, FSFile *f,
+ const char *name, uint32_t mode, uint32_t gid)
+{
+ FSINode *n, *n1;
+
+ n = f->inode;
+ if (n->type != FT_DIR)
+ return -P9_ENOTDIR;
+ if (inode_search(n, name))
+ return -P9_EEXIST;
+ n1 = inode_new(fs, FT_DIR, mode, f->uid, gid);
+ inode_dir_add(fs, n1, ".", inode_incref(fs, n1));
+ inode_dir_add(fs, n1, "..", inode_incref(fs, n));
+ inode_dir_add(fs, n, name, n1);
+ inode_to_qid(qid, n1);
+ return 0;
+}
+
+/* remove elements in the cache considering that 'added_size' will be
+ added */
+static void fs_trim_cache(FSDevice *fs1, int64_t added_size)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ struct list_head *el, *el1;
+ FSINode *n;
+
+ if ((fs->inode_cache_size + added_size) <= fs->inode_cache_size_limit)
+ return;
+ list_for_each_prev_safe(el, el1, &fs->inode_cache_list) {
+ n = list_entry(el, FSINode, u.reg.link);
+ assert(n->u.reg.state == REG_STATE_LOADED);
+ /* cannot remove open files */
+ // printf("open_count=%d\n", n->open_count);
+ if (n->open_count != 0)
+ continue;
+#ifdef DEBUG_CACHE
+ printf("fs_trim_cache: remove '%s' size=%ld\n",
+ n->u.reg.filename, (long)n->u.reg.size);
+#endif
+ file_buffer_reset(&n->u.reg.fbuf);
+ n->u.reg.state = REG_STATE_UNLOADED;
+ list_del(&n->u.reg.link);
+ fs->inode_cache_size -= n->u.reg.size;
+ assert(fs->inode_cache_size >= 0);
+ if ((fs->inode_cache_size + added_size) <= fs->inode_cache_size_limit)
+ break;
+ }
+}
+
+static void fs_open_end(FSOpenInfo *oi)
+{
+ if (oi->open_type == FS_OPEN_WGET_ARCHIVE_FILE) {
+ list_del(&oi->archive_link);
+ }
+ if (oi->dec_state)
+ decrypt_file_end(oi->dec_state);
+ free(oi);
+}
+
+static int fs_open_write_cb(void *opaque, const uint8_t *data, size_t size)
+{
+ FSOpenInfo *oi = opaque;
+ size_t len;
+ FSINode *n = oi->n;
+
+ /* we ignore extraneous data */
+ len = n->u.reg.size - oi->cur_pos;
+ if (size < len)
+ len = size;
+ file_buffer_write(&n->u.reg.fbuf, oi->cur_pos, data, len);
+ oi->cur_pos += len;
+ return 0;
+}
+
+static void fs_wget_set_loaded(FSINode *n)
+{
+ FSOpenInfo *oi;
+ FSDeviceMem *fs;
+ FSFile *f;
+ FSQID qid;
+
+ assert(n->u.reg.state == REG_STATE_LOADING);
+ oi = n->u.reg.open_info;
+ fs = (FSDeviceMem *)oi->fs;
+ n->u.reg.state = REG_STATE_LOADED;
+ list_add(&n->u.reg.link, &fs->inode_cache_list);
+ fs->inode_cache_size += n->u.reg.size;
+
+ if (oi->cb) {
+ f = oi->f;
+ f->is_opened = TRUE;
+ inode_to_qid(&qid, n);
+ oi->cb(oi->fs, &qid, 0, oi->opaque);
+ }
+ fs_open_end(oi);
+}
+
+static void fs_wget_set_error(FSINode *n)
+{
+ FSOpenInfo *oi;
+ assert(n->u.reg.state == REG_STATE_LOADING);
+ oi = n->u.reg.open_info;
+ n->u.reg.state = REG_STATE_UNLOADED;
+ file_buffer_reset(&n->u.reg.fbuf);
+ if (oi->cb) {
+ oi->cb(oi->fs, NULL, -P9_EIO, oi->opaque);
+ }
+ fs_open_end(oi);
+}
+
+static void fs_read_archive(FSOpenInfo *oi)
+{
+ FSINode *n = oi->n;
+ uint64_t pos, pos1, l;
+ uint8_t buf[1024];
+ FSINode *n1;
+ FSOpenInfo *oi1;
+ struct list_head *el, *el1;
+
+ list_for_each_safe(el, el1, &oi->archive_file_list) {
+ oi1 = list_entry(el, FSOpenInfo, archive_link);
+ n1 = oi1->n;
+ /* copy the archive data to the file */
+ pos = oi1->archive_offset;
+ pos1 = 0;
+ while (pos1 < n1->u.reg.size) {
+ l = n1->u.reg.size - pos1;
+ if (l > sizeof(buf))
+ l = sizeof(buf);
+ file_buffer_read(&n->u.reg.fbuf, pos, buf, l);
+ file_buffer_write(&n1->u.reg.fbuf, pos1, buf, l);
+ pos += l;
+ pos1 += l;
+ }
+ fs_wget_set_loaded(n1);
+ }
+}
+
+static void fs_error_archive(FSOpenInfo *oi)
+{
+ FSOpenInfo *oi1;
+ struct list_head *el, *el1;
+
+ list_for_each_safe(el, el1, &oi->archive_file_list) {
+ oi1 = list_entry(el, FSOpenInfo, archive_link);
+ fs_wget_set_error(oi1->n);
+ }
+}
+
+static void fs_open_cb(void *opaque, int err, void *data, size_t size)
+{
+ FSOpenInfo *oi = opaque;
+ FSINode *n = oi->n;
+
+ // printf("open_cb: err=%d size=%ld\n", err, size);
+ if (err < 0) {
+ error:
+ if (oi->open_type == FS_OPEN_WGET_ARCHIVE)
+ fs_error_archive(oi);
+ fs_wget_set_error(n);
+ } else {
+ if (oi->dec_state) {
+ if (decrypt_file(oi->dec_state, data, size) < 0)
+ goto error;
+ if (err == 0) {
+ if (decrypt_file_flush(oi->dec_state) < 0)
+ goto error;
+ }
+ } else {
+ fs_open_write_cb(oi, data, size);
+ }
+
+ if (err == 0) {
+ /* end of transfer */
+ if (oi->cur_pos != n->u.reg.size)
+ goto error;
+#ifdef DUMP_CACHE_LOAD
+ dump_loaded_file(oi->fs, n);
+#endif
+ if (oi->open_type == FS_OPEN_WGET_ARCHIVE)
+ fs_read_archive(oi);
+ fs_wget_set_loaded(n);
+ }
+ }
+}
+
+
+static int fs_open_wget(FSDevice *fs1, FSINode *n, FSOpenWgetEnum open_type)
+{
+ char *url;
+ FSOpenInfo *oi;
+ char fname[FILEID_SIZE_MAX];
+ FSBaseURL *bu;
+
+ assert(n->u.reg.state == REG_STATE_UNLOADED);
+
+ fs_trim_cache(fs1, n->u.reg.size);
+
+ if (file_buffer_resize(&n->u.reg.fbuf, n->u.reg.size) < 0)
+ return -P9_EIO;
+ n->u.reg.state = REG_STATE_LOADING;
+ oi = mallocz(sizeof(*oi));
+ oi->cur_pos = 0;
+ oi->fs = fs1;
+ oi->n = n;
+ oi->open_type = open_type;
+ if (open_type != FS_OPEN_WGET_ARCHIVE_FILE) {
+ if (open_type == FS_OPEN_WGET_ARCHIVE)
+ init_list_head(&oi->archive_file_list);
+ file_id_to_filename(fname, n->u.reg.file_id);
+ bu = n->u.reg.base_url;
+ url = compose_path(bu->url, fname);
+ if (bu->encrypted) {
+ oi->dec_state = decrypt_file_init(&bu->aes_state, fs_open_write_cb, oi);
+ }
+ oi->xhr = fs_wget(url, bu->user, bu->password, oi, fs_open_cb, FALSE);
+ }
+ n->u.reg.open_info = oi;
+ return 0;
+}
+
+
+static void fs_preload_file(FSDevice *fs1, const char *filename)
+{
+ FSINode *n;
+
+ n = inode_search_path(fs1, filename);
+ if (n && n->type == FT_REG && n->u.reg.state == REG_STATE_UNLOADED) {
+#if defined(DEBUG_CACHE)
+ printf("preload: %s\n", filename);
+#endif
+ fs_open_wget(fs1, n, FS_OPEN_WGET_REG);
+ }
+}
+
+static PreloadArchive *find_preload_archive(FSDeviceMem *fs,
+ const char *filename)
+{
+ PreloadArchive *pa;
+ struct list_head *el;
+ list_for_each(el, &fs->preload_archive_list) {
+ pa = list_entry(el, PreloadArchive, link);
+ if (!strcmp(pa->name, filename))
+ return pa;
+ }
+ return NULL;
+}
+
+static void fs_preload_archive(FSDevice *fs1, const char *filename)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ PreloadArchive *pa;
+ PreloadArchiveFile *paf;
+ struct list_head *el;
+ FSINode *n, *n1;
+ uint64_t offset;
+ BOOL has_unloaded;
+
+ pa = find_preload_archive(fs, filename);
+ if (!pa)
+ return;
+#if defined(DEBUG_CACHE)
+ printf("preload archive: %s\n", filename);
+#endif
+ n = inode_search_path(fs1, filename);
+ if (n && n->type == FT_REG && n->u.reg.state == REG_STATE_UNLOADED) {
+ /* if all the files are loaded, no need to load the archive */
+ offset = 0;
+ has_unloaded = FALSE;
+ list_for_each(el, &pa->file_list) {
+ paf = list_entry(el, PreloadArchiveFile, link);
+ n1 = inode_search_path(fs1, paf->name);
+ if (n1 && n1->type == FT_REG &&
+ n1->u.reg.state == REG_STATE_UNLOADED) {
+ has_unloaded = TRUE;
+ }
+ offset += paf->size;
+ }
+ if (!has_unloaded) {
+#if defined(DEBUG_CACHE)
+ printf("archive files already loaded\n");
+#endif
+ return;
+ }
+ /* check archive size consistency */
+ if (offset != n->u.reg.size) {
+#if defined(DEBUG_CACHE)
+ printf(" inconsistent archive size: %" PRId64 " %" PRId64 "\n",
+ offset, n->u.reg.size);
+#endif
+ goto load_fallback;
+ }
+
+ /* start loading the archive */
+ fs_open_wget(fs1, n, FS_OPEN_WGET_ARCHIVE);
+
+ /* indicate that all the archive files are being loaded. Also
+ check consistency of size and file id */
+ offset = 0;
+ list_for_each(el, &pa->file_list) {
+ paf = list_entry(el, PreloadArchiveFile, link);
+ n1 = inode_search_path(fs1, paf->name);
+ if (n1 && n1->type == FT_REG &&
+ n1->u.reg.state == REG_STATE_UNLOADED) {
+ if (n1->u.reg.size == paf->size &&
+ n1->u.reg.file_id == paf->file_id) {
+ fs_open_wget(fs1, n1, FS_OPEN_WGET_ARCHIVE_FILE);
+ list_add_tail(&n1->u.reg.open_info->archive_link,
+ &n->u.reg.open_info->archive_file_list);
+ n1->u.reg.open_info->archive_offset = offset;
+ } else {
+#if defined(DEBUG_CACHE)
+ printf(" inconsistent archive file: %s\n", paf->name);
+#endif
+ /* fallback to file preload */
+ fs_preload_file(fs1, paf->name);
+ }
+ }
+ offset += paf->size;
+ }
+ } else {
+ load_fallback:
+ /* if the archive is already loaded or not loaded, we load the
+ files separately (XXX: not optimal if the archive is
+ already loaded, but it should not happen often) */
+ list_for_each(el, &pa->file_list) {
+ paf = list_entry(el, PreloadArchiveFile, link);
+ fs_preload_file(fs1, paf->name);
+ }
+ }
+}
+
+static void fs_preload_files(FSDevice *fs1, FSFileID file_id)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ struct list_head *el;
+ PreloadEntry *pe;
+ PreloadFile *pf;
+
+ list_for_each(el, &fs->preload_list) {
+ pe = list_entry(el, PreloadEntry, link);
+ if (pe->file_id == file_id)
+ goto found;
+ }
+ return;
+ found:
+ list_for_each(el, &pe->file_list) {
+ pf = list_entry(el, PreloadFile, link);
+ if (pf->is_archive)
+ fs_preload_archive(fs1, pf->name);
+ else
+ fs_preload_file(fs1, pf->name);
+ }
+}
+
+/* return < 0 if error, 0 if OK, 1 if asynchronous completion */
+/* XXX: we don't support several simultaneous asynchronous open on the
+ same inode */
+static int fs_open(FSDevice *fs1, FSQID *qid, FSFile *f, uint32_t flags,
+ FSOpenCompletionFunc *cb, void *opaque)
+{
+ FSINode *n = f->inode;
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ int ret;
+
+ fs_close(fs1, f);
+
+ if (flags & P9_O_DIRECTORY) {
+ if (n->type != FT_DIR)
+ return -P9_ENOTDIR;
+ } else {
+ if (n->type != FT_REG && n->type != FT_DIR)
+ return -P9_EINVAL; /* XXX */
+ }
+ f->open_flags = flags;
+ if (n->type == FT_REG) {
+ if ((flags & P9_O_TRUNC) && (flags & P9_O_NOACCESS) != P9_O_RDONLY) {
+ fs_truncate(fs1, n, 0);
+ }
+
+ switch(n->u.reg.state) {
+ case REG_STATE_UNLOADED:
+ {
+ FSOpenInfo *oi;
+ /* need to load the file */
+ fs_preload_files(fs1, n->u.reg.file_id);
+ /* The state can be modified by the fs_preload_files */
+ if (n->u.reg.state == REG_STATE_LOADING)
+ goto handle_loading;
+ ret = fs_open_wget(fs1, n, FS_OPEN_WGET_REG);
+ if (ret)
+ return ret;
+ oi = n->u.reg.open_info;
+ oi->f = f;
+ oi->cb = cb;
+ oi->opaque = opaque;
+ return 1; /* completion callback will be called later */
+ }
+ break;
+ case REG_STATE_LOADING:
+ handle_loading:
+ {
+ FSOpenInfo *oi;
+ /* we only handle the case where the file is being preloaded */
+ oi = n->u.reg.open_info;
+ if (oi->cb)
+ return -P9_EIO;
+ oi = n->u.reg.open_info;
+ oi->f = f;
+ oi->cb = cb;
+ oi->opaque = opaque;
+ return 1; /* completion callback will be called later */
+ }
+ break;
+ case REG_STATE_LOCAL:
+ goto do_open;
+ case REG_STATE_LOADED:
+ /* move to front */
+ list_del(&n->u.reg.link);
+ list_add(&n->u.reg.link, &fs->inode_cache_list);
+ goto do_open;
+ default:
+ abort();
+ }
+ } else {
+ do_open:
+ f->is_opened = TRUE;
+ inode_to_qid(qid, n);
+ return 0;
+ }
+}
+
+static int fs_create(FSDevice *fs, FSQID *qid, FSFile *f, const char *name,
+ uint32_t flags, uint32_t mode, uint32_t gid)
+{
+ FSINode *n1, *n = f->inode;
+
+ if (n->type != FT_DIR)
+ return -P9_ENOTDIR;
+ if (inode_search(n, name)) {
+ /* XXX: support it, but Linux does not seem to use this case */
+ return -P9_EEXIST;
+ } else {
+ fs_close(fs, f);
+
+ n1 = inode_new(fs, FT_REG, mode, f->uid, gid);
+ inode_dir_add(fs, n, name, n1);
+
+ inode_dec_open(fs, f->inode);
+ f->inode = inode_inc_open(fs, n1);
+ f->is_opened = TRUE;
+ f->open_flags = flags;
+ inode_to_qid(qid, n1);
+ return 0;
+ }
+}
+
+static int fs_readdir(FSDevice *fs, FSFile *f, uint64_t offset1,
+ uint8_t *buf, int count)
+{
+ FSINode *n1, *n = f->inode;
+ int len, pos, name_len, type;
+ struct list_head *el;
+ FSDirEntry *de;
+ uint64_t offset;
+
+ if (!f->is_opened || n->type != FT_DIR)
+ return -P9_EPROTO;
+
+ el = n->u.dir.de_list.next;
+ offset = 0;
+ while (offset < offset1) {
+ if (el == &n->u.dir.de_list)
+ return 0; /* no more entries */
+ offset++;
+ el = el->next;
+ }
+
+ pos = 0;
+ for(;;) {
+ if (el == &n->u.dir.de_list)
+ break;
+ de = list_entry(el, FSDirEntry, link);
+ name_len = strlen(de->name);
+ len = 13 + 8 + 1 + 2 + name_len;
+ if ((pos + len) > count)
+ break;
+ offset++;
+ n1 = de->inode;
+ if (n1->type == FT_DIR)
+ type = P9_QTDIR;
+ else if (n1->type == FT_LNK)
+ type = P9_QTSYMLINK;
+ else
+ type = P9_QTFILE;
+ buf[pos++] = type;
+ put_le32(buf + pos, 0); /* version */
+ pos += 4;
+ put_le64(buf + pos, n1->inode_num);
+ pos += 8;
+ put_le64(buf + pos, offset);
+ pos += 8;
+ buf[pos++] = n1->type;
+ put_le16(buf + pos, name_len);
+ pos += 2;
+ memcpy(buf + pos, de->name, name_len);
+ pos += name_len;
+ el = el->next;
+ }
+ return pos;
+}
+
+static int fs_read(FSDevice *fs, FSFile *f, uint64_t offset,
+ uint8_t *buf, int count)
+{
+ FSINode *n = f->inode;
+ uint64_t count1;
+
+ if (!f->is_opened)
+ return -P9_EPROTO;
+ if (n->type != FT_REG)
+ return -P9_EIO;
+ if ((f->open_flags & P9_O_NOACCESS) == P9_O_WRONLY)
+ return -P9_EIO;
+ if (n->u.reg.is_fscmd)
+ return fs_cmd_read(fs, f, offset, buf, count);
+ if (offset >= n->u.reg.size)
+ return 0;
+ count1 = n->u.reg.size - offset;
+ if (count1 < count)
+ count = count1;
+ file_buffer_read(&n->u.reg.fbuf, offset, buf, count);
+ return count;
+}
+
+static int fs_truncate(FSDevice *fs1, FSINode *n, uint64_t size)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ intptr_t diff, diff_blocks;
+ size_t new_allocated_size;
+
+ if (n->type != FT_REG)
+ return -P9_EINVAL;
+ if (size > UINTPTR_MAX)
+ return -P9_ENOSPC;
+ diff = size - n->u.reg.size;
+ if (diff == 0)
+ return 0;
+ diff_blocks = to_blocks(fs, size) - to_blocks(fs, n->u.reg.size);
+ /* currently cannot resize while loading */
+ switch(n->u.reg.state) {
+ case REG_STATE_LOADING:
+ return -P9_EIO;
+ case REG_STATE_UNLOADED:
+ if (size == 0) {
+ /* now local content */
+ n->u.reg.state = REG_STATE_LOCAL;
+ }
+ break;
+ case REG_STATE_LOADED:
+ case REG_STATE_LOCAL:
+ if (diff > 0) {
+ if ((fs->fs_blocks + diff_blocks) > fs->fs_max_blocks)
+ return -P9_ENOSPC;
+ if (size > n->u.reg.fbuf.allocated_size) {
+ new_allocated_size = n->u.reg.fbuf.allocated_size * 5 / 4;
+ if (size > new_allocated_size)
+ new_allocated_size = size;
+ if (file_buffer_resize(&n->u.reg.fbuf, new_allocated_size) < 0)
+ return -P9_ENOSPC;
+ }
+ file_buffer_set(&n->u.reg.fbuf, n->u.reg.size, 0, diff);
+ } else {
+ new_allocated_size = n->u.reg.fbuf.allocated_size * 4 / 5;
+ if (size <= new_allocated_size) {
+ if (file_buffer_resize(&n->u.reg.fbuf, new_allocated_size) < 0)
+ return -P9_ENOSPC;
+ }
+ }
+ /* file is modified, so it is now local */
+ if (n->u.reg.state == REG_STATE_LOADED) {
+ list_del(&n->u.reg.link);
+ fs->inode_cache_size -= n->u.reg.size;
+ assert(fs->inode_cache_size >= 0);
+ n->u.reg.state = REG_STATE_LOCAL;
+ }
+ break;
+ default:
+ abort();
+ }
+ fs->fs_blocks += diff_blocks;
+ assert(fs->fs_blocks >= 0);
+ n->u.reg.size = size;
+ return 0;
+}
+
+static int fs_write(FSDevice *fs1, FSFile *f, uint64_t offset,
+ const uint8_t *buf, int count)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ FSINode *n = f->inode;
+ uint64_t end;
+ int err;
+
+ if (!f->is_opened)
+ return -P9_EPROTO;
+ if (n->type != FT_REG)
+ return -P9_EIO;
+ if ((f->open_flags & P9_O_NOACCESS) == P9_O_RDONLY)
+ return -P9_EIO;
+ if (count == 0)
+ return 0;
+ if (n->u.reg.is_fscmd) {
+ return fs_cmd_write(fs1, f, offset, buf, count);
+ }
+ end = offset + count;
+ if (end > n->u.reg.size) {
+ err = fs_truncate(fs1, n, end);
+ if (err)
+ return err;
+ }
+ inode_update_mtime(fs1, n);
+ /* file is modified, so it is now local */
+ if (n->u.reg.state == REG_STATE_LOADED) {
+ list_del(&n->u.reg.link);
+ fs->inode_cache_size -= n->u.reg.size;
+ assert(fs->inode_cache_size >= 0);
+ n->u.reg.state = REG_STATE_LOCAL;
+ }
+ file_buffer_write(&n->u.reg.fbuf, offset, buf, count);
+ return count;
+}
+
+static void fs_close(FSDevice *fs, FSFile *f)
+{
+ if (f->is_opened) {
+ f->is_opened = FALSE;
+ }
+ if (f->req)
+ fs_cmd_close(fs, f);
+}
+
+static int fs_stat(FSDevice *fs1, FSFile *f, FSStat *st)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ FSINode *n = f->inode;
+
+ inode_to_qid(&st->qid, n);
+ st->st_mode = n->mode | (n->type << 12);
+ st->st_uid = n->uid;
+ st->st_gid = n->gid;
+ st->st_nlink = n->refcount;
+ if (n->type == FT_BLK || n->type == FT_CHR) {
+ /* XXX: check */
+ st->st_rdev = (n->u.dev.major << 8) | n->u.dev.minor;
+ } else {
+ st->st_rdev = 0;
+ }
+ st->st_blksize = fs->block_size;
+ if (n->type == FT_REG) {
+ st->st_size = n->u.reg.size;
+ } else if (n->type == FT_LNK) {
+ st->st_size = strlen(n->u.symlink.name);
+ } else if (n->type == FT_DIR) {
+ st->st_size = n->u.dir.size;
+ } else {
+ st->st_size = 0;
+ }
+ /* in 512 byte blocks */
+ st->st_blocks = to_blocks(fs, st->st_size) << (fs->block_size_log2 - 9);
+
+ /* Note: atime is not supported */
+ st->st_atime_sec = n->mtime_sec;
+ st->st_atime_nsec = n->mtime_nsec;
+ st->st_mtime_sec = n->mtime_sec;
+ st->st_mtime_nsec = n->mtime_nsec;
+ st->st_ctime_sec = n->ctime_sec;
+ st->st_ctime_nsec = n->ctime_nsec;
+ return 0;
+}
+
+static int fs_setattr(FSDevice *fs1, FSFile *f, uint32_t mask,
+ uint32_t mode, uint32_t uid, uint32_t gid,
+ uint64_t size, uint64_t atime_sec, uint64_t atime_nsec,
+ uint64_t mtime_sec, uint64_t mtime_nsec)
+{
+ FSINode *n = f->inode;
+ int ret;
+
+ if (mask & P9_SETATTR_MODE) {
+ n->mode = mode;
+ }
+ if (mask & P9_SETATTR_UID) {
+ n->uid = uid;
+ }
+ if (mask & P9_SETATTR_GID) {
+ n->gid = gid;
+ }
+ if (mask & P9_SETATTR_SIZE) {
+ ret = fs_truncate(fs1, n, size);
+ if (ret)
+ return ret;
+ }
+ if (mask & P9_SETATTR_MTIME) {
+ if (mask & P9_SETATTR_MTIME_SET) {
+ n->mtime_sec = mtime_sec;
+ n->mtime_nsec = mtime_nsec;
+ } else {
+ inode_update_mtime(fs1, n);
+ }
+ }
+ if (mask & P9_SETATTR_CTIME) {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ n->ctime_sec = tv.tv_sec;
+ n->ctime_nsec = tv.tv_usec * 1000;
+ }
+ return 0;
+}
+
+static int fs_link(FSDevice *fs, FSFile *df, FSFile *f, const char *name)
+{
+ FSINode *n = df->inode;
+
+ if (f->inode->type == FT_DIR)
+ return -P9_EPERM;
+ if (inode_search(n, name))
+ return -P9_EEXIST;
+ inode_dir_add(fs, n, name, inode_incref(fs, f->inode));
+ return 0;
+}
+
+static int fs_symlink(FSDevice *fs, FSQID *qid,
+ FSFile *f, const char *name, const char *symgt, uint32_t gid)
+{
+ FSINode *n1, *n = f->inode;
+
+ if (inode_search(n, name))
+ return -P9_EEXIST;
+
+ n1 = inode_new(fs, FT_LNK, 0777, f->uid, gid);
+ n1->u.symlink.name = strdup(symgt);
+ inode_dir_add(fs, n, name, n1);
+ inode_to_qid(qid, n1);
+ return 0;
+}
+
+static int fs_mknod(FSDevice *fs, FSQID *qid,
+ FSFile *f, const char *name, uint32_t mode, uint32_t major,
+ uint32_t minor, uint32_t gid)
+{
+ int type;
+ FSINode *n1, *n = f->inode;
+
+ type = (mode & P9_S_IFMT) >> 12;
+ /* XXX: add FT_DIR support */
+ if (type != FT_FIFO && type != FT_CHR && type != FT_BLK &&
+ type != FT_REG && type != FT_SOCK)
+ return -P9_EINVAL;
+ if (inode_search(n, name))
+ return -P9_EEXIST;
+ n1 = inode_new(fs, type, mode, f->uid, gid);
+ if (type == FT_CHR || type == FT_BLK) {
+ n1->u.dev.major = major;
+ n1->u.dev.minor = minor;
+ }
+ inode_dir_add(fs, n, name, n1);
+ inode_to_qid(qid, n1);
+ return 0;
+}
+
+static int fs_readlink(FSDevice *fs, char *buf, int buf_size, FSFile *f)
+{
+ FSINode *n = f->inode;
+ int len;
+ if (n->type != FT_LNK)
+ return -P9_EIO;
+ len = min_int(strlen(n->u.symlink.name), buf_size - 1);
+ memcpy(buf, n->u.symlink.name, len);
+ buf[len] = '\0';
+ return 0;
+}
+
+static int fs_renameat(FSDevice *fs, FSFile *f, const char *name,
+ FSFile *new_f, const char *new_name)
+{
+ FSDirEntry *de, *de1;
+ FSINode *n1;
+
+ de = inode_search(f->inode, name);
+ if (!de)
+ return -P9_ENOENT;
+ de1 = inode_search(new_f->inode, new_name);
+ n1 = NULL;
+ if (de1) {
+ n1 = de1->inode;
+ if (n1->type == FT_DIR)
+ return -P9_EEXIST; /* XXX: handle the case */
+ inode_dirent_delete_no_decref(fs, new_f->inode, de1);
+ }
+ inode_dir_add(fs, new_f->inode, new_name, inode_incref(fs, de->inode));
+ inode_dirent_delete(fs, f->inode, de);
+ if (n1)
+ inode_decref(fs, n1);
+ return 0;
+}
+
+static int fs_unlinkat(FSDevice *fs, FSFile *f, const char *name)
+{
+ FSDirEntry *de;
+ FSINode *n;
+
+ if (!strcmp(name, ".") || !strcmp(name, ".."))
+ return -P9_ENOENT;
+ de = inode_search(f->inode, name);
+ if (!de)
+ return -P9_ENOENT;
+ n = de->inode;
+ if (n->type == FT_DIR) {
+ if (!is_empty_dir(fs, n))
+ return -P9_ENOTEMPTY;
+ flush_dir(fs, n);
+ }
+ inode_dirent_delete(fs, f->inode, de);
+ return 0;
+}
+
+static int fs_lock(FSDevice *fs, FSFile *f, const FSLock *lock)
+{
+ FSINode *n = f->inode;
+ if (!f->is_opened)
+ return -P9_EPROTO;
+ if (n->type != FT_REG)
+ return -P9_EIO;
+ /* XXX: implement it */
+ return P9_LOCK_SUCCESS;
+}
+
+static int fs_getlock(FSDevice *fs, FSFile *f, FSLock *lock)
+{
+ FSINode *n = f->inode;
+ if (!f->is_opened)
+ return -P9_EPROTO;
+ if (n->type != FT_REG)
+ return -P9_EIO;
+ /* XXX: implement it */
+ return 0;
+}
+
+/* XXX: only used with file lists, so not all the data is released */
+static void fs_mem_end(FSDevice *fs1)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ struct list_head *el, *el1, *el2, *el3;
+ FSINode *n;
+ FSDirEntry *de;
+
+ list_for_each_safe(el, el1, &fs->inode_list) {
+ n = list_entry(el, FSINode, link);
+ n->refcount = 0;
+ if (n->type == FT_DIR) {
+ list_for_each_safe(el2, el3, &n->u.dir.de_list) {
+ de = list_entry(el2, FSDirEntry, link);
+ list_del(&de->link);
+ free(de);
+ }
+ init_list_head(&n->u.dir.de_list);
+ }
+ inode_free(fs1, n);
+ }
+ assert(list_empty(&fs->inode_cache_list));
+ free(fs->import_dir);
+}
+
+FSDevice *fs_mem_init(void)
+{
+ FSDeviceMem *fs;
+ FSDevice *fs1;
+ FSINode *n;
+
+ fs = mallocz(sizeof(*fs));
+ fs1 = &fs->common;
+
+ fs->common.fs_end = fs_mem_end;
+ fs->common.fs_delete = fs_delete;
+ fs->common.fs_statfs = fs_statfs;
+ fs->common.fs_attach = fs_attach;
+ fs->common.fs_walk = fs_walk;
+ fs->common.fs_mkdir = fs_mkdir;
+ fs->common.fs_open = fs_open;
+ fs->common.fs_create = fs_create;
+ fs->common.fs_stat = fs_stat;
+ fs->common.fs_setattr = fs_setattr;
+ fs->common.fs_close = fs_close;
+ fs->common.fs_readdir = fs_readdir;
+ fs->common.fs_read = fs_read;
+ fs->common.fs_write = fs_write;
+ fs->common.fs_link = fs_link;
+ fs->common.fs_symlink = fs_symlink;
+ fs->common.fs_mknod = fs_mknod;
+ fs->common.fs_readlink = fs_readlink;
+ fs->common.fs_renameat = fs_renameat;
+ fs->common.fs_unlinkat = fs_unlinkat;
+ fs->common.fs_lock = fs_lock;
+ fs->common.fs_getlock = fs_getlock;
+
+ init_list_head(&fs->inode_list);
+ fs->inode_num_alloc = 1;
+ fs->block_size_log2 = FS_BLOCK_SIZE_LOG2;
+ fs->block_size = 1 << fs->block_size_log2;
+ fs->inode_limit = 1 << 20; /* arbitrary */
+ fs->fs_max_blocks = 1 << (30 - fs->block_size_log2); /* arbitrary */
+
+ init_list_head(&fs->inode_cache_list);
+ fs->inode_cache_size_limit = DEFAULT_INODE_CACHE_SIZE;
+
+ init_list_head(&fs->preload_list);
+ init_list_head(&fs->preload_archive_list);
+
+ init_list_head(&fs->base_url_list);
+
+ /* create the root inode */
+ n = inode_new(fs1, FT_DIR, 0777, 0, 0);
+ inode_dir_add(fs1, n, ".", inode_incref(fs1, n));
+ inode_dir_add(fs1, n, "..", inode_incref(fs1, n));
+ fs->root_inode = n;
+
+ return (FSDevice *)fs;
+}
+
+static BOOL fs_is_net(FSDevice *fs)
+{
+ return (fs->fs_end == fs_mem_end);
+}
+
+static FSBaseURL *fs_find_base_url(FSDevice *fs1,
+ const char *base_url_id)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ struct list_head *el;
+ FSBaseURL *bu;
+
+ list_for_each(el, &fs->base_url_list) {
+ bu = list_entry(el, FSBaseURL, link);
+ if (!strcmp(bu->base_url_id, base_url_id))
+ return bu;
+ }
+ return NULL;
+}
+
+static void fs_base_url_decref(FSDevice *fs, FSBaseURL *bu)
+{
+ assert(bu->ref_count >= 1);
+ if (--bu->ref_count == 0) {
+ free(bu->base_url_id);
+ free(bu->url);
+ free(bu->user);
+ free(bu->password);
+ list_del(&bu->link);
+ free(bu);
+ }
+}
+
+static FSBaseURL *fs_net_set_base_url(FSDevice *fs1,
+ const char *base_url_id,
+ const char *url,
+ const char *user, const char *password,
+ AES_KEY *aes_state)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ FSBaseURL *bu;
+
+ assert(fs_is_net(fs1));
+ bu = fs_find_base_url(fs1, base_url_id);
+ if (!bu) {
+ bu = mallocz(sizeof(*bu));
+ bu->base_url_id = strdup(base_url_id);
+ bu->ref_count = 1;
+ list_add_tail(&bu->link, &fs->base_url_list);
+ } else {
+ free(bu->url);
+ free(bu->user);
+ free(bu->password);
+ }
+
+ bu->url = strdup(url);
+ if (user)
+ bu->user = strdup(user);
+ else
+ bu->user = NULL;
+ if (password)
+ bu->password = strdup(password);
+ else
+ bu->password = NULL;
+ if (aes_state) {
+ bu->encrypted = TRUE;
+ bu->aes_state = *aes_state;
+ } else {
+ bu->encrypted = FALSE;
+ }
+ return bu;
+}
+
+static int fs_net_reset_base_url(FSDevice *fs1,
+ const char *base_url_id)
+{
+ FSBaseURL *bu;
+
+ assert(fs_is_net(fs1));
+ bu = fs_find_base_url(fs1, base_url_id);
+ if (!bu)
+ return -P9_ENOENT;
+ fs_base_url_decref(fs1, bu);
+ return 0;
+}
+
+static void fs_net_set_fs_max_size(FSDevice *fs1, uint64_t fs_max_size)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+
+ assert(fs_is_net(fs1));
+ fs->fs_max_blocks = to_blocks(fs, fs_max_size);
+}
+
+static int fs_net_set_url(FSDevice *fs1, FSINode *n,
+ const char *base_url_id, FSFileID file_id, uint64_t size)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ FSBaseURL *bu;
+
+ assert(fs_is_net(fs1));
+
+ bu = fs_find_base_url(fs1, base_url_id);
+ if (!bu)
+ return -P9_ENOENT;
+
+ /* XXX: could accept more state */
+ if (n->type != FT_REG ||
+ n->u.reg.state != REG_STATE_LOCAL ||
+ n->u.reg.fbuf.allocated_size != 0)
+ return -P9_EIO;
+
+ if (size > 0) {
+ n->u.reg.state = REG_STATE_UNLOADED;
+ n->u.reg.base_url = bu;
+ bu->ref_count++;
+ n->u.reg.size = size;
+ fs->fs_blocks += to_blocks(fs, size);
+ n->u.reg.file_id = file_id;
+ }
+ return 0;
+}
+
+#ifdef DUMP_CACHE_LOAD
+
+#include "json.h"
+
+#define ARCHIVE_SIZE_MAX (4 << 20)
+
+static void fs_dump_add_file(struct list_head *head, const char *name)
+{
+ PreloadFile *pf;
+ pf = mallocz(sizeof(*pf));
+ pf->name = strdup(name);
+ list_add_tail(&pf->link, head);
+}
+
+static PreloadFile *fs_dump_find_file(struct list_head *head, const char *name)
+{
+ PreloadFile *pf;
+ struct list_head *el;
+ list_for_each(el, head) {
+ pf = list_entry(el, PreloadFile, link);
+ if (!strcmp(pf->name, name))
+ return pf;
+ }
+ return NULL;
+}
+
+static void dump_close_archive(FSDevice *fs1)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ if (fs->dump_archive_file) {
+ fclose(fs->dump_archive_file);
+ }
+ fs->dump_archive_file = NULL;
+ fs->dump_archive_size = 0;
+}
+
+static void dump_loaded_file(FSDevice *fs1, FSINode *n)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ char filename[1024];
+ const char *fname, *p;
+
+ if (!fs->dump_cache_load || !n->u.reg.filename)
+ return;
+ fname = n->u.reg.filename;
+
+ if (fs_dump_find_file(&fs->dump_preload_list, fname)) {
+ dump_close_archive(fs1);
+ p = strrchr(fname, '/');
+ if (!p)
+ p = fname;
+ else
+ p++;
+ free(fs->dump_archive_name);
+ fs->dump_archive_name = strdup(p);
+ fs->dump_started = TRUE;
+ fs->dump_archive_num = 0;
+
+ fprintf(fs->dump_preload_file, "\n%s :\n", fname);
+ }
+ if (!fs->dump_started)
+ return;
+
+ if (!fs->dump_archive_file) {
+ snprintf(filename, sizeof(filename), "%s/%s%d",
+ fs->dump_preload_dir, fs->dump_archive_name,
+ fs->dump_archive_num);
+ fs->dump_archive_file = fopen(filename, "wb");
+ if (!fs->dump_archive_file) {
+ perror(filename);
+ exit(1);
+ }
+ fprintf(fs->dump_preload_archive_file, "\n@.preload2/%s%d :\n",
+ fs->dump_archive_name, fs->dump_archive_num);
+ fprintf(fs->dump_preload_file, " @.preload2/%s%d\n",
+ fs->dump_archive_name, fs->dump_archive_num);
+ fflush(fs->dump_preload_file);
+ fs->dump_archive_num++;
+ }
+
+ if (n->u.reg.size >= ARCHIVE_SIZE_MAX) {
+ /* exclude large files from archive */
+ /* add indicative size */
+ fprintf(fs->dump_preload_file, " %s %" PRId64 "\n",
+ fname, n->u.reg.size);
+ fflush(fs->dump_preload_file);
+ } else {
+ fprintf(fs->dump_preload_archive_file, " %s %" PRId64 " %" PRIx64 "\n",
+ n->u.reg.filename, n->u.reg.size, n->u.reg.file_id);
+ fflush(fs->dump_preload_archive_file);
+ fwrite(n->u.reg.fbuf.data, 1, n->u.reg.size, fs->dump_archive_file);
+ fflush(fs->dump_archive_file);
+ fs->dump_archive_size += n->u.reg.size;
+ if (fs->dump_archive_size >= ARCHIVE_SIZE_MAX) {
+ dump_close_archive(fs1);
+ }
+ }
+}
+
+static JSONValue json_load(const char *filename)
+{
+ FILE *f;
+ JSONValue val;
+ size_t size;
+ char *buf;
+
+ f = fopen(filename, "rb");
+ if (!f) {
+ perror(filename);
+ exit(1);
+ }
+ fseek(f, 0, SEEK_END);
+ size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ buf = malloc(size + 1);
+ fread(buf, 1, size, f);
+ fclose(f);
+ val = json_parse_value_len(buf, size);
+ free(buf);
+ return val;
+}
+
+void fs_dump_cache_load(FSDevice *fs1, const char *cfg_filename)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ JSONValue cfg, val, array;
+ char *fname;
+ const char *preload_dir, *name;
+ int i;
+
+ if (!fs_is_net(fs1))
+ return;
+ cfg = json_load(cfg_filename);
+ if (json_is_error(cfg)) {
+ fprintf(stderr, "%s\n", json_get_error(cfg));
+ exit(1);
+ }
+
+ val = json_object_get(cfg, "preload_dir");
+ if (json_is_undefined(cfg)) {
+ config_error:
+ exit(1);
+ }
+ preload_dir = json_get_str(val);
+ if (!preload_dir) {
+ fprintf(stderr, "expecting preload_filename\n");
+ goto config_error;
+ }
+ fs->dump_preload_dir = strdup(preload_dir);
+
+ init_list_head(&fs->dump_preload_list);
+ init_list_head(&fs->dump_exclude_list);
+
+ array = json_object_get(cfg, "preload");
+ if (array.type != JSON_ARRAY) {
+ fprintf(stderr, "expecting preload array\n");
+ goto config_error;
+ }
+ for(i = 0; i < array.u.array->len; i++) {
+ val = json_array_get(array, i);
+ name = json_get_str(val);
+ if (!name) {
+ fprintf(stderr, "expecting a string\n");
+ goto config_error;
+ }
+ fs_dump_add_file(&fs->dump_preload_list, name);
+ }
+ json_free(cfg);
+
+ fname = compose_path(fs->dump_preload_dir, "preload.txt");
+ fs->dump_preload_file = fopen(fname, "w");
+ if (!fs->dump_preload_file) {
+ perror(fname);
+ exit(1);
+ }
+ free(fname);
+
+ fname = compose_path(fs->dump_preload_dir, "preload_archive.txt");
+ fs->dump_preload_archive_file = fopen(fname, "w");
+ if (!fs->dump_preload_archive_file) {
+ perror(fname);
+ exit(1);
+ }
+ free(fname);
+
+ fs->dump_cache_load = TRUE;
+}
+#else
+void fs_dump_cache_load(FSDevice *fs1, const char *cfg_filename)
+{
+}
+#endif
+
+/***********************************************/
+/* file list processing */
+
+static int filelist_load_rec(FSDevice *fs1, const char **pp, FSINode *dir,
+ const char *path)
+{
+ // FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ char fname[1024], lname[1024];
+ int ret;
+ const char *p;
+ FSINodeTypeEnum type;
+ uint32_t mode, uid, gid;
+ uint64_t size;
+ FSINode *n;
+
+ p = *pp;
+ for(;;) {
+ /* skip comments or empty lines */
+ if (*p == '\0')
+ break;
+ if (*p == '#') {
+ skip_line(&p);
+ continue;
+ }
+ /* end of directory */
+ if (*p == '.') {
+ p++;
+ skip_line(&p);
+ break;
+ }
+ if (parse_uint32_base(&mode, &p, 8) < 0) {
+ fprintf(stderr, "invalid mode\n");
+ return -1;
+ }
+ type = mode >> 12;
+ mode &= 0xfff;
+
+ if (parse_uint32(&uid, &p) < 0) {
+ fprintf(stderr, "invalid uid\n");
+ return -1;
+ }
+
+ if (parse_uint32(&gid, &p) < 0) {
+ fprintf(stderr, "invalid gid\n");
+ return -1;
+ }
+
+ n = inode_new(fs1, type, mode, uid, gid);
+
+ size = 0;
+ switch(type) {
+ case FT_CHR:
+ case FT_BLK:
+ if (parse_uint32(&n->u.dev.major, &p) < 0) {
+ fprintf(stderr, "invalid major\n");
+ return -1;
+ }
+ if (parse_uint32(&n->u.dev.minor, &p) < 0) {
+ fprintf(stderr, "invalid minor\n");
+ return -1;
+ }
+ break;
+ case FT_REG:
+ if (parse_uint64(&size, &p) < 0) {
+ fprintf(stderr, "invalid size\n");
+ return -1;
+ }
+ break;
+ case FT_DIR:
+ inode_dir_add(fs1, n, ".", inode_incref(fs1, n));
+ inode_dir_add(fs1, n, "..", inode_incref(fs1, dir));
+ break;
+ default:
+ break;
+ }
+
+ /* modification time */
+ if (parse_time(&n->mtime_sec, &n->mtime_nsec, &p) < 0) {
+ fprintf(stderr, "invalid mtime\n");
+ return -1;
+ }
+
+ if (parse_fname(fname, sizeof(fname), &p) < 0) {
+ fprintf(stderr, "invalid filename\n");
+ return -1;
+ }
+ inode_dir_add(fs1, dir, fname, n);
+
+ if (type == FT_LNK) {
+ if (parse_fname(lname, sizeof(lname), &p) < 0) {
+ fprintf(stderr, "invalid symlink name\n");
+ return -1;
+ }
+ n->u.symlink.name = strdup(lname);
+ } else if (type == FT_REG && size > 0) {
+ FSFileID file_id;
+ if (parse_file_id(&file_id, &p) < 0) {
+ fprintf(stderr, "invalid file id\n");
+ return -1;
+ }
+ fs_net_set_url(fs1, n, "/", file_id, size);
+#ifdef DUMP_CACHE_LOAD
+ {
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ if (fs->dump_cache_load
+#ifdef DEBUG_CACHE
+ || 1
+#endif
+ ) {
+ n->u.reg.filename = compose_path(path, fname);
+ } else {
+ n->u.reg.filename = NULL;
+ }
+ }
+#endif
+ }
+
+ skip_line(&p);
+
+ if (type == FT_DIR) {
+ char *path1;
+ path1 = compose_path(path, fname);
+ ret = filelist_load_rec(fs1, &p, n, path1);
+ free(path1);
+ if (ret)
+ return ret;
+ }
+ }
+ *pp = p;
+ return 0;
+}
+
+static int filelist_load(FSDevice *fs1, const char *str)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ int ret;
+ const char *p;
+
+ if (parse_tag_version(str) != 1)
+ return -1;
+ p = skip_header(str);
+ if (!p)
+ return -1;
+ ret = filelist_load_rec(fs1, &p, fs->root_inode, "");
+ return ret;
+}
+
+/************************************************************/
+/* FS init from network */
+
+static void __attribute__((format(printf, 1, 2))) fatal_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ fprintf(stderr, "Error: ");
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ exit(1);
+}
+
+static void fs_create_cmd(FSDevice *fs)
+{
+ FSFile *root_fd;
+ FSQID qid;
+ FSINode *n;
+
+ assert(!fs->fs_attach(fs, &root_fd, &qid, 0, "", ""));
+ assert(!fs->fs_create(fs, &qid, root_fd, FSCMD_NAME, P9_O_RDWR | P9_O_TRUNC,
+ 0666, 0));
+ n = root_fd->inode;
+ n->u.reg.is_fscmd = TRUE;
+ fs->fs_delete(fs, root_fd);
+}
+
+typedef struct {
+ FSDevice *fs;
+ char *url;
+ void (*start_cb)(void *opaque);
+ void *start_opaque;
+
+ FSFile *root_fd;
+ FSFile *fd;
+ int file_index;
+
+} FSNetInitState;
+
+static void fs_initial_sync(FSDevice *fs,
+ const char *url, void (*start_cb)(void *opaque),
+ void *start_opaque);
+static void head_loaded(FSDevice *fs, FSFile *f, int64_t size, void *opaque);
+static void filelist_loaded(FSDevice *fs, FSFile *f, int64_t size, void *opaque);
+static void kernel_load_cb(FSDevice *fs, FSQID *qid, int err,
+ void *opaque);
+static int preload_parse(FSDevice *fs, const char *fname, BOOL is_new);
+
+#ifdef EMSCRIPTEN
+static FSDevice *fs_import_fs;
+#endif
+
+#define DEFAULT_IMPORT_FILE_PATH "/tmp"
+
+FSDevice *fs_net_init(const char *url, void (*start_cb)(void *opaque),
+ void *start_opaque)
+{
+ FSDevice *fs;
+ FSDeviceMem *fs1;
+
+ fs_wget_init();
+
+ fs = fs_mem_init();
+#ifdef EMSCRIPTEN
+ if (!fs_import_fs)
+ fs_import_fs = fs;
+#endif
+ fs1 = (FSDeviceMem *)fs;
+ fs1->import_dir = strdup(DEFAULT_IMPORT_FILE_PATH);
+
+ fs_create_cmd(fs);
+
+ if (url) {
+ fs_initial_sync(fs, url, start_cb, start_opaque);
+ }
+ return fs;
+}
+
+static void fs_initial_sync(FSDevice *fs,
+ const char *url, void (*start_cb)(void *opaque),
+ void *start_opaque)
+{
+ FSNetInitState *s;
+ FSFile *head_fd;
+ FSQID qid;
+ char *head_url;
+ char buf[128];
+ struct timeval tv;
+
+ s = mallocz(sizeof(*s));
+ s->fs = fs;
+ s->url = strdup(url);
+ s->start_cb = start_cb;
+ s->start_opaque = start_opaque;
+ assert(!fs->fs_attach(fs, &s->root_fd, &qid, 0, "", ""));
+
+ /* avoid using cached version */
+ gettimeofday(&tv, NULL);
+ snprintf(buf, sizeof(buf), HEAD_FILENAME "?nocache=%" PRId64,
+ (int64_t)tv.tv_sec * 1000000 + tv.tv_usec);
+ head_url = compose_url(s->url, buf);
+ head_fd = fs_dup(fs, s->root_fd);
+ assert(!fs->fs_create(fs, &qid, head_fd, ".head",
+ P9_O_RDWR | P9_O_TRUNC, 0644, 0));
+ fs_wget_file2(fs, head_fd, head_url, NULL, NULL, NULL, 0,
+ head_loaded, s, NULL);
+ free(head_url);
+}
+
+static void head_loaded(FSDevice *fs, FSFile *f, int64_t size, void *opaque)
+{
+ FSNetInitState *s = opaque;
+ char *buf, *root_url, *url;
+ char fname[FILEID_SIZE_MAX];
+ FSFileID root_id;
+ FSFile *new_filelist_fd;
+ FSQID qid;
+ uint64_t fs_max_size;
+
+ if (size < 0)
+ fatal_error("could not load 'head' file (HTTP error=%d)", -(int)size);
+
+ buf = malloc(size + 1);
+ fs->fs_read(fs, f, 0, (uint8_t *)buf, size);
+ buf[size] = '\0';
+ fs->fs_delete(fs, f);
+ fs->fs_unlinkat(fs, s->root_fd, ".head");
+
+ if (parse_tag_version(buf) != 1)
+ fatal_error("invalid head version");
+
+ if (parse_tag_file_id(&root_id, buf, "RootID") < 0)
+ fatal_error("expected RootID tag");
+
+ if (parse_tag_uint64(&fs_max_size, buf, "FSMaxSize") == 0 &&
+ fs_max_size >= ((uint64_t)1 << 20)) {
+ fs_net_set_fs_max_size(fs, fs_max_size);
+ }
+
+ /* set the Root URL in the filesystem */
+ root_url = compose_url(s->url, ROOT_FILENAME);
+ fs_net_set_base_url(fs, "/", root_url, NULL, NULL, NULL);
+
+ new_filelist_fd = fs_dup(fs, s->root_fd);
+ assert(!fs->fs_create(fs, &qid, new_filelist_fd, ".filelist.txt",
+ P9_O_RDWR | P9_O_TRUNC, 0644, 0));
+
+ file_id_to_filename(fname, root_id);
+ url = compose_url(root_url, fname);
+ fs_wget_file2(fs, new_filelist_fd, url, NULL, NULL, NULL, 0,
+ filelist_loaded, s, NULL);
+ free(root_url);
+ free(url);
+}
+
+static void filelist_loaded(FSDevice *fs, FSFile *f, int64_t size, void *opaque)
+{
+ FSNetInitState *s = opaque;
+ uint8_t *buf;
+
+ if (size < 0)
+ fatal_error("could not load file list (HTTP error=%d)", -(int)size);
+
+ buf = malloc(size + 1);
+ fs->fs_read(fs, f, 0, buf, size);
+ buf[size] = '\0';
+ fs->fs_delete(fs, f);
+ fs->fs_unlinkat(fs, s->root_fd, ".filelist.txt");
+
+ if (filelist_load(fs, (char *)buf) != 0)
+ fatal_error("error while parsing file list");
+
+ /* try to load the kernel and the preload file */
+ s->file_index = 0;
+ kernel_load_cb(fs, NULL, 0, s);
+}
+
+
+#define FILE_LOAD_COUNT 2
+
+static const char *kernel_file_list[FILE_LOAD_COUNT] = {
+ ".preload",
+ ".preload2/preload.txt",
+};
+
+static void kernel_load_cb(FSDevice *fs, FSQID *qid1, int err,
+ void *opaque)
+{
+ FSNetInitState *s = opaque;
+ FSQID qid;
+
+#ifdef DUMP_CACHE_LOAD
+ /* disable preloading if dumping cache load */
+ if (((FSDeviceMem *)fs)->dump_cache_load)
+ return;
+#endif
+
+ if (s->fd) {
+ fs->fs_delete(fs, s->fd);
+ s->fd = NULL;
+ }
+
+ if (s->file_index >= FILE_LOAD_COUNT) {
+ /* all files are loaded */
+ if (preload_parse(fs, ".preload2/preload.txt", TRUE) < 0) {
+ preload_parse(fs, ".preload", FALSE);
+ }
+ fs->fs_delete(fs, s->root_fd);
+ if (s->start_cb)
+ s->start_cb(s->start_opaque);
+ free(s);
+ } else {
+ s->fd = fs_walk_path(fs, s->root_fd, kernel_file_list[s->file_index++]);
+ if (!s->fd)
+ goto done;
+ err = fs->fs_open(fs, &qid, s->fd, P9_O_RDONLY, kernel_load_cb, s);
+ if (err <= 0) {
+ done:
+ kernel_load_cb(fs, NULL, 0, s);
+ }
+ }
+}
+
+static void preload_parse_str_old(FSDevice *fs1, const char *p)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ char fname[1024];
+ PreloadEntry *pe;
+ PreloadFile *pf;
+ FSINode *n;
+
+ for(;;) {
+ while (isspace_nolf(*p))
+ p++;
+ if (*p == '\n') {
+ p++;
+ continue;
+ }
+ if (*p == '\0')
+ break;
+ if (parse_fname(fname, sizeof(fname), &p) < 0) {
+ fprintf(stderr, "invalid filename\n");
+ return;
+ }
+ // printf("preload file='%s\n", fname);
+ n = inode_search_path(fs1, fname);
+ if (!n || n->type != FT_REG || n->u.reg.state == REG_STATE_LOCAL) {
+ fprintf(stderr, "invalid preload file: '%s'\n", fname);
+ while (*p != '\n' && *p != '\0')
+ p++;
+ } else {
+ pe = mallocz(sizeof(*pe));
+ pe->file_id = n->u.reg.file_id;
+ init_list_head(&pe->file_list);
+ list_add_tail(&pe->link, &fs->preload_list);
+ for(;;) {
+ while (isspace_nolf(*p))
+ p++;
+ if (*p == '\0' || *p == '\n')
+ break;
+ if (parse_fname(fname, sizeof(fname), &p) < 0) {
+ fprintf(stderr, "invalid filename\n");
+ return;
+ }
+ // printf(" adding '%s'\n", fname);
+ pf = mallocz(sizeof(*pf));
+ pf->name = strdup(fname);
+ list_add_tail(&pf->link, &pe->file_list);
+ }
+ }
+ }
+}
+
+static void preload_parse_str(FSDevice *fs1, const char *p)
+{
+ FSDeviceMem *fs = (FSDeviceMem *)fs1;
+ PreloadEntry *pe;
+ PreloadArchive *pa;
+ FSINode *n;
+ BOOL is_archive;
+ char fname[1024];
+
+ pe = NULL;
+ pa = NULL;
+ for(;;) {
+ while (isspace_nolf(*p))
+ p++;
+ if (*p == '\n') {
+ pe = NULL;
+ p++;
+ continue;
+ }
+ if (*p == '#')
+ continue; /* comment */
+ if (*p == '\0')
+ break;
+
+ is_archive = FALSE;
+ if (*p == '@') {
+ is_archive = TRUE;
+ p++;
+ }
+ if (parse_fname(fname, sizeof(fname), &p) < 0) {
+ fprintf(stderr, "invalid filename\n");
+ return;
+ }
+ while (isspace_nolf(*p))
+ p++;
+ if (*p == ':') {
+ p++;
+ // printf("preload file='%s' archive=%d\n", fname, is_archive);
+ n = inode_search_path(fs1, fname);
+ pe = NULL;
+ pa = NULL;
+ if (!n || n->type != FT_REG || n->u.reg.state == REG_STATE_LOCAL) {
+ fprintf(stderr, "invalid preload file: '%s'\n", fname);
+ while (*p != '\n' && *p != '\0')
+ p++;
+ } else if (is_archive) {
+ pa = mallocz(sizeof(*pa));
+ pa->name = strdup(fname);
+ init_list_head(&pa->file_list);
+ list_add_tail(&pa->link, &fs->preload_archive_list);
+ } else {
+ pe = mallocz(sizeof(*pe));
+ pe->file_id = n->u.reg.file_id;
+ init_list_head(&pe->file_list);
+ list_add_tail(&pe->link, &fs->preload_list);
+ }
+ } else {
+ if (!pe && !pa) {
+ fprintf(stderr, "filename without target: %s\n", fname);
+ return;
+ }
+ if (pa) {
+ PreloadArchiveFile *paf;
+ FSFileID file_id;
+ uint64_t size;
+
+ if (parse_uint64(&size, &p) < 0) {
+ fprintf(stderr, "invalid size\n");
+ return;
+ }
+
+ if (parse_file_id(&file_id, &p) < 0) {
+ fprintf(stderr, "invalid file id\n");
+ return;
+ }
+
+ paf = mallocz(sizeof(*paf));
+ paf->name = strdup(fname);
+ paf->file_id = file_id;
+ paf->size = size;
+ list_add_tail(&paf->link, &pa->file_list);
+ } else {
+ PreloadFile *pf;
+ pf = mallocz(sizeof(*pf));
+ pf->name = strdup(fname);
+ pf->is_archive = is_archive;
+ list_add_tail(&pf->link, &pe->file_list);
+ }
+ }
+ /* skip the rest of the line */
+ while (*p != '\n' && *p != '\0')
+ p++;
+ if (*p == '\n')
+ p++;
+ }
+}
+
+static int preload_parse(FSDevice *fs, const char *fname, BOOL is_new)
+{
+ FSINode *n;
+ char *buf;
+ size_t size;
+
+ n = inode_search_path(fs, fname);
+ if (!n || n->type != FT_REG || n->u.reg.state != REG_STATE_LOADED)
+ return -1;
+ /* transform to zero terminated string */
+ size = n->u.reg.size;
+ buf = malloc(size + 1);
+ file_buffer_read(&n->u.reg.fbuf, 0, (uint8_t *)buf, size);
+ buf[size] = '\0';
+ if (is_new)
+ preload_parse_str(fs, buf);
+ else
+ preload_parse_str_old(fs, buf);
+ free(buf);
+ return 0;
+}
+
+
+
+/************************************************************/
+/* FS user interface */
+
+typedef struct CmdXHRState {
+ FSFile *req_fd;
+ FSFile *root_fd;
+ FSFile *fd;
+ FSFile *post_fd;
+ AES_KEY aes_state;
+} CmdXHRState;
+
+static void fs_cmd_xhr_on_load(FSDevice *fs, FSFile *f, int64_t size,
+ void *opaque);
+
+static int parse_hex_buf(uint8_t *buf, int buf_size, const char **pp)
+{
+ char buf1[1024];
+ int len;
+
+ if (parse_fname(buf1, sizeof(buf1), pp) < 0)
+ return -1;
+ len = strlen(buf1);
+ if ((len & 1) != 0)
+ return -1;
+ len >>= 1;
+ if (len > buf_size)
+ return -1;
+ if (decode_hex(buf, buf1, len) < 0)
+ return -1;
+ return len;
+}
+
+static int fs_cmd_xhr(FSDevice *fs, FSFile *f,
+ const char *p, uint32_t uid, uint32_t gid)
+{
+ char url[1024], post_filename[1024], filename[1024];
+ char user_buf[128], *user;
+ char password_buf[128], *password;
+ FSQID qid;
+ FSFile *fd, *root_fd, *post_fd;
+ uint64_t post_data_len;
+ int err, aes_key_len;
+ CmdXHRState *s;
+ char *name;
+ AES_KEY *paes_state;
+ uint8_t aes_key[FS_KEY_LEN];
+ uint32_t flags;
+ FSCMDRequest *req;
+
+ /* a request is already done or in progress */
+ if (f->req != NULL)
+ return -P9_EIO;
+
+ if (parse_fname(url, sizeof(url), &p) < 0)
+ goto fail;
+ if (parse_fname(user_buf, sizeof(user_buf), &p) < 0)
+ goto fail;
+ if (parse_fname(password_buf, sizeof(password_buf), &p) < 0)
+ goto fail;
+ if (parse_fname(post_filename, sizeof(post_filename), &p) < 0)
+ goto fail;
+ if (parse_fname(filename, sizeof(filename), &p) < 0)
+ goto fail;
+ aes_key_len = parse_hex_buf(aes_key, FS_KEY_LEN, &p);
+ if (aes_key_len < 0)
+ goto fail;
+ if (parse_uint32(&flags, &p) < 0)
+ goto fail;
+ if (aes_key_len != 0 && aes_key_len != FS_KEY_LEN)
+ goto fail;
+
+ if (user_buf[0] != '\0')
+ user = user_buf;
+ else
+ user = NULL;
+ if (password_buf[0] != '\0')
+ password = password_buf;
+ else
+ password = NULL;
+
+ // printf("url='%s' '%s' '%s' filename='%s'\n", url, user, password, filename);
+ assert(!fs->fs_attach(fs, &root_fd, &qid, uid, "", ""));
+ post_fd = NULL;
+
+ fd = fs_walk_path1(fs, root_fd, filename, &name);
+ if (!fd) {
+ err = -P9_ENOENT;
+ goto fail1;
+ }
+ /* XXX: until fs_create is fixed */
+ fs->fs_unlinkat(fs, fd, name);
+
+ err = fs->fs_create(fs, &qid, fd, name,
+ P9_O_RDWR | P9_O_TRUNC, 0600, gid);
+ if (err < 0) {
+ goto fail1;
+ }
+
+ if (post_filename[0] != '\0') {
+ FSINode *n;
+
+ post_fd = fs_walk_path(fs, root_fd, post_filename);
+ if (!post_fd) {
+ err = -P9_ENOENT;
+ goto fail1;
+ }
+ err = fs->fs_open(fs, &qid, post_fd, P9_O_RDONLY, NULL, NULL);
+ if (err < 0)
+ goto fail1;
+ n = post_fd->inode;
+ assert(n->type == FT_REG && n->u.reg.state == REG_STATE_LOCAL);
+ post_data_len = n->u.reg.size;
+ } else {
+ post_data_len = 0;
+ }
+
+ s = mallocz(sizeof(*s));
+ s->root_fd = root_fd;
+ s->fd = fd;
+ s->post_fd = post_fd;
+ if (aes_key_len != 0) {
+ AES_set_decrypt_key(aes_key, FS_KEY_LEN * 8, &s->aes_state);
+ paes_state = &s->aes_state;
+ } else {
+ paes_state = NULL;
+ }
+
+ req = mallocz(sizeof(*req));
+ req->type = FS_CMD_XHR;
+ req->reply_len = 0;
+ req->xhr_state = s;
+ s->req_fd = f;
+ f->req = req;
+
+ fs_wget_file2(fs, fd, url, user, password, post_fd, post_data_len,
+ fs_cmd_xhr_on_load, s, paes_state);
+ return 0;
+ fail1:
+ if (fd)
+ fs->fs_delete(fs, fd);
+ if (post_fd)
+ fs->fs_delete(fs, post_fd);
+ fs->fs_delete(fs, root_fd);
+ return err;
+ fail:
+ return -P9_EIO;
+}
+
+static void fs_cmd_xhr_on_load(FSDevice *fs, FSFile *f, int64_t size,
+ void *opaque)
+{
+ CmdXHRState *s = opaque;
+ FSCMDRequest *req;
+ int ret;
+
+ // printf("fs_cmd_xhr_on_load: size=%d\n", (int)size);
+
+ if (s->fd)
+ fs->fs_delete(fs, s->fd);
+ if (s->post_fd)
+ fs->fs_delete(fs, s->post_fd);
+ fs->fs_delete(fs, s->root_fd);
+
+ if (s->req_fd) {
+ req = s->req_fd->req;
+ if (size < 0) {
+ ret = size;
+ } else {
+ ret = 0;
+ }
+ put_le32(req->reply_buf, ret);
+ req->reply_len = sizeof(ret);
+ req->xhr_state = NULL;
+ }
+ free(s);
+}
+
+static int fs_cmd_set_base_url(FSDevice *fs, const char *p)
+{
+ // FSDeviceMem *fs1 = (FSDeviceMem *)fs;
+ char url[1024], base_url_id[1024];
+ char user_buf[128], *user;
+ char password_buf[128], *password;
+ AES_KEY aes_state, *paes_state;
+ uint8_t aes_key[FS_KEY_LEN];
+ int aes_key_len;
+
+ if (parse_fname(base_url_id, sizeof(base_url_id), &p) < 0)
+ goto fail;
+ if (parse_fname(url, sizeof(url), &p) < 0)
+ goto fail;
+ if (parse_fname(user_buf, sizeof(user_buf), &p) < 0)
+ goto fail;
+ if (parse_fname(password_buf, sizeof(password_buf), &p) < 0)
+ goto fail;
+ aes_key_len = parse_hex_buf(aes_key, FS_KEY_LEN, &p);
+ if (aes_key_len < 0)
+ goto fail;
+
+ if (user_buf[0] != '\0')
+ user = user_buf;
+ else
+ user = NULL;
+ if (password_buf[0] != '\0')
+ password = password_buf;
+ else
+ password = NULL;
+
+ if (aes_key_len != 0) {
+ if (aes_key_len != FS_KEY_LEN)
+ goto fail;
+ AES_set_decrypt_key(aes_key, FS_KEY_LEN * 8, &aes_state);
+ paes_state = &aes_state;
+ } else {
+ paes_state = NULL;
+ }
+
+ fs_net_set_base_url(fs, base_url_id, url, user, password,
+ paes_state);
+ return 0;
+ fail:
+ return -P9_EINVAL;
+}
+
+static int fs_cmd_reset_base_url(FSDevice *fs, const char *p)
+{
+ char base_url_id[1024];
+
+ if (parse_fname(base_url_id, sizeof(base_url_id), &p) < 0)
+ goto fail;
+ fs_net_reset_base_url(fs, base_url_id);
+ return 0;
+ fail:
+ return -P9_EINVAL;
+}
+
+static int fs_cmd_set_url(FSDevice *fs, const char *p)
+{
+ char base_url_id[1024];
+ char filename[1024];
+ FSFileID file_id;
+ uint64_t size;
+ FSINode *n;
+
+ if (parse_fname(filename, sizeof(filename), &p) < 0)
+ goto fail;
+ if (parse_fname(base_url_id, sizeof(base_url_id), &p) < 0)
+ goto fail;
+ if (parse_file_id(&file_id, &p) < 0)
+ goto fail;
+ if (parse_uint64(&size, &p) < 0)
+ goto fail;
+
+ n = inode_search_path(fs, filename);
+ if (!n) {
+ return -P9_ENOENT;
+ }
+ return fs_net_set_url(fs, n, base_url_id, file_id, size);
+ fail:
+ return -P9_EINVAL;
+}
+
+static int fs_cmd_export_file(FSDevice *fs, const char *p)
+{
+ char filename[1024];
+ FSINode *n;
+ const char *name;
+ uint8_t *buf;
+
+ if (parse_fname(filename, sizeof(filename), &p) < 0)
+ goto fail;
+ n = inode_search_path(fs, filename);
+ if (!n)
+ return -P9_ENOENT;
+ if (n->type != FT_REG ||
+ (n->u.reg.state != REG_STATE_LOCAL &&
+ n->u.reg.state != REG_STATE_LOADED))
+ goto fail;
+ name = strrchr(filename, '/');
+ if (name)
+ name++;
+ else
+ name = filename;
+ /* XXX: pass the buffer to JS to avoid the allocation */
+ buf = malloc(n->u.reg.size);
+ file_buffer_read(&n->u.reg.fbuf, 0, buf, n->u.reg.size);
+ fs_export_file(name, buf, n->u.reg.size);
+ free(buf);
+ return 0;
+ fail:
+ return -P9_EIO;
+}
+
+/* PBKDF2 crypto acceleration */
+static int fs_cmd_pbkdf2(FSDevice *fs, FSFile *f, const char *p)
+{
+ uint8_t pwd[1024];
+ uint8_t salt[128];
+ uint32_t iter, key_len;
+ int pwd_len, salt_len;
+ FSCMDRequest *req;
+
+ /* a request is already done or in progress */
+ if (f->req != NULL)
+ return -P9_EIO;
+
+ pwd_len = parse_hex_buf(pwd, sizeof(pwd), &p);
+ if (pwd_len < 0)
+ goto fail;
+ salt_len = parse_hex_buf(salt, sizeof(salt), &p);
+ if (pwd_len < 0)
+ goto fail;
+ if (parse_uint32(&iter, &p) < 0)
+ goto fail;
+ if (parse_uint32(&key_len, &p) < 0)
+ goto fail;
+ if (key_len > FS_CMD_REPLY_LEN_MAX ||
+ key_len == 0)
+ goto fail;
+ req = mallocz(sizeof(*req));
+ req->type = FS_CMD_PBKDF2;
+ req->reply_len = key_len;
+ pbkdf2_hmac_sha256(pwd, pwd_len, salt, salt_len, iter, key_len,
+ req->reply_buf);
+ f->req = req;
+ return 0;
+ fail:
+ return -P9_EINVAL;
+}
+
+static int fs_cmd_set_import_dir(FSDevice *fs, FSFile *f, const char *p)
+{
+ FSDeviceMem *fs1 = (FSDeviceMem *)fs;
+ char filename[1024];
+
+ if (parse_fname(filename, sizeof(filename), &p) < 0)
+ return -P9_EINVAL;
+ free(fs1->import_dir);
+ fs1->import_dir = strdup(filename);
+ return 0;
+}
+
+static int fs_cmd_write(FSDevice *fs, FSFile *f, uint64_t offset,
+ const uint8_t *buf, int buf_len)
+{
+ char *buf1;
+ const char *p;
+ char cmd[64];
+ int err;
+
+ /* transform into a string */
+ buf1 = malloc(buf_len + 1);
+ memcpy(buf1, buf, buf_len);
+ buf1[buf_len] = '\0';
+
+ err = 0;
+ p = buf1;
+ if (parse_fname(cmd, sizeof(cmd), &p) < 0)
+ goto fail;
+ if (!strcmp(cmd, "xhr")) {
+ err = fs_cmd_xhr(fs, f, p, f->uid, 0);
+ } else if (!strcmp(cmd, "set_base_url")) {
+ err = fs_cmd_set_base_url(fs, p);
+ } else if (!strcmp(cmd, "reset_base_url")) {
+ err = fs_cmd_reset_base_url(fs, p);
+ } else if (!strcmp(cmd, "set_url")) {
+ err = fs_cmd_set_url(fs, p);
+ } else if (!strcmp(cmd, "export_file")) {
+ err = fs_cmd_export_file(fs, p);
+ } else if (!strcmp(cmd, "pbkdf2")) {
+ err = fs_cmd_pbkdf2(fs, f, p);
+ } else if (!strcmp(cmd, "set_import_dir")) {
+ err = fs_cmd_set_import_dir(fs, f, p);
+ } else {
+ printf("unknown command: '%s'\n", cmd);
+ fail:
+ err = -P9_EIO;
+ }
+ free(buf1);
+ if (err == 0)
+ return buf_len;
+ else
+ return err;
+}
+
+static int fs_cmd_read(FSDevice *fs, FSFile *f, uint64_t offset,
+ uint8_t *buf, int buf_len)
+{
+ FSCMDRequest *req;
+ int l;
+
+ req = f->req;
+ if (!req)
+ return -P9_EIO;
+ l = min_int(req->reply_len, buf_len);
+ memcpy(buf, req->reply_buf, l);
+ return l;
+}
+
+static void fs_cmd_close(FSDevice *fs, FSFile *f)
+{
+ FSCMDRequest *req;
+ req = f->req;
+
+ if (req) {
+ if (req->xhr_state) {
+ req->xhr_state->req_fd = NULL;
+ }
+ free(req);
+ f->req = NULL;
+ }
+}
+
+/* Create a .fscmd_pwd file to avoid passing the password thru the
+ Linux command line */
+void fs_net_set_pwd(FSDevice *fs, const char *pwd)
+{
+ FSFile *root_fd;
+ FSQID qid;
+
+ assert(fs_is_net(fs));
+
+ assert(!fs->fs_attach(fs, &root_fd, &qid, 0, "", ""));
+ assert(!fs->fs_create(fs, &qid, root_fd, ".fscmd_pwd", P9_O_RDWR | P9_O_TRUNC,
+ 0600, 0));
+ fs->fs_write(fs, root_fd, 0, (uint8_t *)pwd, strlen(pwd));
+ fs->fs_delete(fs, root_fd);
+}
+
+/* external file import */
+
+#ifdef EMSCRIPTEN
+
+void fs_import_file(const char *filename, uint8_t *buf, int buf_len)
+{
+ FSDevice *fs;
+ FSDeviceMem *fs1;
+ FSFile *fd, *root_fd;
+ FSQID qid;
+
+ // printf("importing file: %s len=%d\n", filename, buf_len);
+ fs = fs_import_fs;
+ if (!fs) {
+ free(buf);
+ return;
+ }
+
+ assert(!fs->fs_attach(fs, &root_fd, &qid, 1000, "", ""));
+ fs1 = (FSDeviceMem *)fs;
+ fd = fs_walk_path(fs, root_fd, fs1->import_dir);
+ if (!fd)
+ goto fail;
+ fs_unlinkat(fs, root_fd, filename);
+ if (fs->fs_create(fs, &qid, fd, filename, P9_O_RDWR | P9_O_TRUNC,
+ 0600, 0) < 0)
+ goto fail;
+ fs->fs_write(fs, fd, 0, buf, buf_len);
+ fail:
+ if (fd)
+ fs->fs_delete(fs, fd);
+ if (root_fd)
+ fs->fs_delete(fs, root_fd);
+ free(buf);
+}
+
+#else
+
+void fs_export_file(const char *filename,
+ const uint8_t *buf, int buf_len)
+{
+}
+
+#endif