blob: 9d8022f047d0a770cebea6cb283dc2304abf18d7 [file] [log] [blame] [raw]
/*
* 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=%zu\n",
n->u.reg.filename, 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 " %zu\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 %zu\n",
fname, n->u.reg.size);
fflush(fs->dump_preload_file);
} else {
fprintf(fs->dump_preload_archive_file, " %s %zu %" 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