From 939ac4319cb047a37ba46f84eff81948063f6954 Mon Sep 17 00:00:00 2001 From: Mitchell Riedstra Date: Wed, 24 Dec 2025 19:49:57 -0500 Subject: Add working webpage for unix v4 --- jslinux-2019-12-21/tinyemu-2019-12-21/fs_net.c | 2910 ++++++++++++++++++++++++ 1 file changed, 2910 insertions(+) create mode 100644 jslinux-2019-12-21/tinyemu-2019-12-21/fs_net.c (limited to 'jslinux-2019-12-21/tinyemu-2019-12-21/fs_net.c') 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 +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "list.h" +#include "fs.h" +#include "fs_utils.h" +#include "fs_wget.h" +#include "fbuf.h" + +#if defined(EMSCRIPTEN) +#include +#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 -- cgit v1.2.3