#include <hashtbl.h>
#include <mutex.h>
#include <string.h>
#include <debug.h>
#include <nullfs.h>
// nullfs driver
static bool nullfs_fs_make(fs_handle_t *source, const char* opts, fs_t *d);
// nullfs fs_t
static void nullfs_fs_shutdown(fs_ptr fs);
// nullfs directory node
static bool nullfs_d_open(fs_node_ptr n, int mode, fs_handle_t *s);
static bool nullfs_d_stat(fs_node_ptr n, stat_t *st);
static bool nullfs_d_walk(fs_node_ptr n, const char* file, struct fs_node *node_d);
static bool nullfs_d_delete(fs_node_ptr n, const char* file);
static bool nullfs_d_move(fs_node_ptr n, const char* old_name, fs_node_t *new_parent, const char *new_name);
static bool nullfs_d_create(fs_node_ptr n, const char* file, int type);
static void nullfs_d_dispose(fs_node_ptr n);
// nullfs directory handle
static bool nullfs_dh_readdir(fs_handle_ptr f, dirent_t *d);
static void nullfs_dh_close(fs_handle_ptr f);
// nullfs ram file node
static bool nullfs_f_open(fs_node_ptr n, int mode, fs_handle_t *s);
static bool nullfs_f_stat(fs_node_ptr n, stat_t *st);
static void nullfs_f_dispose(fs_node_ptr n);
// nullfs ram file handle
static size_t nullfs_fh_read(fs_handle_ptr f, size_t offset, size_t len, char* buf);
static size_t nullfs_fh_write(fs_handle_ptr f, size_t offset, size_t len, const char* buf);
static void nullfs_fh_close(fs_handle_ptr f);
// VTables that go with it
static fs_driver_ops_t nullfs_driver_ops = {
.make = nullfs_fs_make,
.detect = 0,
};
static fs_ops_t nullfs_ops = {
.shutdown = nullfs_fs_shutdown,
.add_source = 0,
};
static fs_node_ops_t nullfs_d_ops = {
.open = nullfs_d_open,
.stat = nullfs_d_stat,
.walk = nullfs_d_walk,
.delete = nullfs_d_delete,
.move = nullfs_d_move,
.create = nullfs_d_create,
.dispose = nullfs_d_dispose,
.ioctl = 0,
};
static fs_handle_ops_t nullfs_dh_ops = {
.readdir = nullfs_dh_readdir,
.close = nullfs_dh_close,
.read = 0,
.write = 0,
};
static fs_node_ops_t nullfs_f_ops = {
.open = nullfs_f_open,
.stat = nullfs_f_stat,
.dispose = nullfs_f_dispose,
.walk = 0,
.create = 0,
.delete = 0,
.move = 0,
.ioctl =0,
};
static fs_handle_ops_t nullfs_fh_ops = {
.read = nullfs_fh_read,
.write = nullfs_fh_write,
.close = nullfs_fh_close,
.readdir = 0,
};
// ====================== //
// NULLFS DATA STRUCTURES //
// ====================== //
typedef struct {
bool can_create, can_move, can_delete;
} nullfs_t;
typedef struct nullfs_item {
char* name;
fs_node_ptr data;
fs_node_ops_t *ops;
struct nullfs_item *next;
} nullfs_item_t;
typedef struct {
nullfs_item_t *items_list;
hashtbl_t *items_idx;
mutex_t lock;
nullfs_t *fs;
} nullfs_dir_t;
typedef struct {
nullfs_dir_t *d;
int nitems, i;
dirent_t *items;
} nullfs_dh_t;
typedef struct {
char* data;
size_t size;
bool own_data;
int ok_modes;
mutex_t lock; // locked on open
} nullfs_file_t;
// No nullfs_file_handle_t struct, we don't need it. The handle's data
// pointer is simply a pointer to the file node.
// ===================== //
// NULLFS IMPLEMENTATION //
// ===================== //
void register_nullfs_driver() {
register_fs_driver("nullfs", &nullfs_driver_ops);
}
bool nullfs_fs_make(fs_handle_t *source, const char* opts, fs_t *fs_s) {
nullfs_t *fs = (nullfs_t*)malloc(sizeof(nullfs_t));
if (fs == 0) return false;
fs->can_create = (strchr(opts, 'c') != 0);
fs->can_move = (strchr(opts, 'm') != 0);
fs->can_delete = (strchr(opts, 'd') != 0);
nullfs_dir_t *root = (nullfs_dir_t*)malloc(sizeof(nullfs_dir_t));
if (root == 0) {
free(fs);
return false;
}
root->fs = fs;
root->items_list = 0;
root->lock = MUTEX_UNLOCKED;
root->items_idx = create_hashtbl(str_key_eq_fun, str_hash_fun, 0);
if (root->items_idx == 0) {
free(root);
free(fs);
return false;
}
fs_s->ops = &nullfs_ops;
fs_s->data = fs;
fs_s->root.ops = &nullfs_d_ops;
fs_s->root.data = root;
return true;
}
void nullfs_fs_shutdown(fs_ptr fs) {
dbg_printf("Not implemented: nullfs_fs_shutdown. Memory is leaking.\n");
// TODO free all
}
bool nullfs_add_node(fs_t *fs, const char* name, fs_node_ptr data, fs_node_ops_t *ops) {
char file_name[DIR_MAX];
fs_node_t *n = fs_walk_path_except_last(&fs->root, name, file_name);
if (n == 0) return false;
if (n->ops != &nullfs_d_ops) return false;
nullfs_dir_t *d = (nullfs_dir_t*)n->data;
mutex_lock(&d->lock);
nullfs_item_t *i = (nullfs_item_t*)malloc(sizeof(nullfs_item_t));
if (i == 0) goto error;
i->name = strdup(file_name);
if (i->name == 0) goto error;
i->data = data;
i->ops = ops;
bool add_ok = hashtbl_add(d->items_idx, i->name, i);
if (!add_ok) goto error;
i->next = d->items_list;
d->items_list = i;
mutex_unlock(&d->lock);
return true;
error:
if (i && i->name) free(i->name);
if (i) free(i);
mutex_unlock(&d->lock);
return false;
}
bool nullfs_add_ram_file(fs_t *fs, const char* name, char* data, size_t init_sz, bool copy, int ok_modes) {
nullfs_file_t *f = (nullfs_file_t*)malloc(sizeof(nullfs_file_t));
if (f == 0) return false;
f->size = init_sz;
if (copy) {
f->data = malloc(init_sz);
memcpy(f->data, data, init_sz);
f->own_data = true;
} else {
f->data = data;
f->own_data = false;
}
f->ok_modes = ok_modes;
f->lock = MUTEX_UNLOCKED;
bool add_ok = nullfs_add_node(fs, name, f, &nullfs_f_ops);
if (!add_ok) {
if (f->own_data) free(f->data);
free(f);
return false;
}
return true;
}
// -- Directory node --
bool nullfs_d_open(fs_node_ptr n, int mode, fs_handle_t *s) {
if (mode != FM_READDIR) return false;
nullfs_dir_t* d = (nullfs_dir_t*)n;
bool got_lock = mutex_try_lock(&d->lock);
if (!got_lock) return false;
nullfs_dh_t *h = (nullfs_dh_t*)malloc(sizeof(nullfs_dh_t));
if (h == 0) goto fail;
h->nitems = hashtbl_count(d->items_idx);
if (h->nitems > 0) {
h->items = (dirent_t*)malloc(h->nitems * sizeof(dirent_t));
if (h->nitems == 0) goto fail;
int i = 0;
for (nullfs_item_t *it = d->items_list; it != 0; it = it->next) {
strncpy(h->items[i].name, it->name, DIR_MAX);
h->items[i].name[DIR_MAX-1] = 0; // make sur it's null-terminated
if (it->ops->stat) {
it->ops->stat(it->data, &h->items[i].st);
} else {
// no stat operation : should we do something else ?
memset(&h->items[i].st, 0, sizeof(stat_t));
}
i++;
}
ASSERT(i == h->nitems);
}
h->d = d;
h->i = 0;
s->data = h;
s->ops = &nullfs_dh_ops;
s->mode = FM_READDIR;
mutex_unlock(&d->lock);
return true;
fail:
mutex_unlock(&d->lock);
if (h && h->items) free(h->items);
if (h) free(h);
return false;
}
bool nullfs_d_stat(fs_node_ptr n, stat_t *st) {
nullfs_dir_t* d = (nullfs_dir_t*)n;
mutex_lock(&d->lock);
st->type = FT_DIR;
st->access = FM_READDIR
| (d->fs->can_create ? FM_DCREATE : 0)
| (d->fs->can_move ? FM_DMOVE : 0)
| (d->fs->can_delete ? FM_DUNLINK : 0);
st->size = 0;
for (nullfs_item_t *i = d->items_list; i != 0; i = i->next)
st->size++;
mutex_unlock(&d->lock);
return true;
}
bool nullfs_d_walk(fs_node_ptr n, const char* file, struct fs_node *node_d) {
nullfs_dir_t* d = (nullfs_dir_t*)n;
mutex_lock(&d->lock);
nullfs_item_t* x = (nullfs_item_t*)hashtbl_find(d->items_idx, file);
if (x == 0) {
mutex_unlock(&d->lock);
return false;
}
node_d->ops = x->ops;
node_d->data = x->data;
mutex_unlock(&d->lock);
return true;
}
bool nullfs_d_delete(fs_node_ptr n, const char* file) {
nullfs_dir_t* d = (nullfs_dir_t*)n;
mutex_lock(&d->lock);
if (!d->fs->can_delete) goto error;
nullfs_item_t *i = hashtbl_find(d->items_idx, file);
if (i == 0) goto error;
if (i->ops == &nullfs_d_ops) {
// if it is a subdirectory, check it is empty
nullfs_dir_t* sd = (nullfs_dir_t*)i->data;
if (!mutex_try_lock(&sd->lock)) goto error; // in use
if (sd->items_list != 0) goto error; // cannot delete non-empty directory
delete_hashtbl(sd->items_idx);
free(sd);
} else if (i->ops == &nullfs_f_ops) {
nullfs_file_t* f = (nullfs_file_t*)i->data;
if (!mutex_try_lock(&f->lock)) goto error; // in use
if (f->own_data) free(f->data);
free(f);
} else {
goto error; // special nodes (devices, ...) may not be deleted
}
hashtbl_remove(d->items_idx, i->name);
if (d->items_list == i) {
d->items_list = i->next;
} else {
for (nullfs_item_t* it = d->items_list; it != 0; it++) {
if (it->next == i) {
it->next = i->next;
break;
}
}
}
free(i->name);
free(i);
mutex_unlock(&d->lock);
return true;
error:
mutex_unlock(&d->lock);
return false;
}
bool nullfs_d_move(fs_node_ptr n, const char* old_name, fs_node_t *new_parent, const char *new_name) {
dbg_printf("Not implemented: move in nullfs. Failing potentially valid move request.\n");
return false; //TODO
}
bool nullfs_d_create(fs_node_ptr n, const char* file, int type) {
nullfs_dir_t *d = (nullfs_dir_t*)n;
nullfs_item_t *i = 0;
if (type == FT_REGULAR) {
mutex_lock(&d->lock);
nullfs_file_t *f = (nullfs_file_t*)malloc(sizeof(nullfs_file_t));
if (f == 0) goto f_error;
f->ok_modes = FM_READ | FM_WRITE | FM_TRUNC | FM_APPEND;
f->data = 0;
f->size = 0;
f->own_data = false;
f->lock = MUTEX_UNLOCKED;
i = (nullfs_item_t*)malloc(sizeof(nullfs_item_t));
if (i == 0) goto f_error;
i->name = strdup(file);
if (i->name == 0) goto f_error;
i->ops = &nullfs_f_ops;
i->data = f;
bool add_ok = hashtbl_add(d->items_idx, i->name, i);
if (!add_ok) goto f_error;
i->next = d->items_list;
d->items_list = i;
mutex_unlock(&d->lock);
return true;
f_error:
if (i != 0 && i->name != 0) free(i->name);
if (i != 0) free(i);
if (f != 0) free(f);
mutex_unlock(&d->lock);
return false;
} else if (type == FT_DIR) {
mutex_lock(&d->lock);
nullfs_dir_t *x = (nullfs_dir_t*)malloc(sizeof(nullfs_dir_t));
if (x == 0) goto d_error;
x->items_idx = create_hashtbl(str_key_eq_fun, str_hash_fun, 0);
if (x->items_idx == 0) goto d_error;
x->items_list = 0;
x->lock = MUTEX_UNLOCKED;
x->fs = d->fs;
i = (nullfs_item_t*)malloc(sizeof(nullfs_item_t));
if (i == 0) goto d_error;
i->name = strdup(file);
if (i->name == 0) goto d_error;
i->ops = &nullfs_d_ops;
i->data = x;
bool add_ok = hashtbl_add(d->items_idx, i->name, i);
if (!add_ok) goto d_error;
i->next = d->items_list;
d->items_list = i;
mutex_unlock(&d->lock);
return true;
d_error:
if (i != 0 && i->name != 0) free(i->name);
if (i != 0) free(i);
if (x != 0 && x->items_idx != 0) delete_hashtbl(x->items_idx);
if (x != 0) free(x);
mutex_unlock(&d->lock);
return false;
} else {
return false;
}
}
void nullfs_d_dispose(fs_node_ptr n) {
// nothing to do
}
// -- Directory handle --
bool nullfs_dh_readdir(fs_handle_ptr f, dirent_t *d) {
nullfs_dh_t *h = (nullfs_dh_t*)f;
int i = h->i;
if (i >= h->nitems) {
return false;
} else {
memcpy(d, &h->items[i], sizeof(dirent_t));
h->i++;
return true;
}
}
void nullfs_dh_close(fs_handle_ptr f) {
nullfs_dh_t *h = (nullfs_dh_t*)f;
if (h->items) free(h->items);
free(h);
}
// -- File node --
bool nullfs_f_open(fs_node_ptr n, int mode, fs_handle_t *s) {
nullfs_file_t *f = (nullfs_file_t*)n;
if (mode & ~f->ok_modes) return false;
if (mode & FM_TRUNC) {
// truncate file
mutex_lock(&f->lock);
if (f->own_data) free(f->data);
f->size = 0;
f->own_data = false;
f->data = 0;
mutex_unlock(&f->lock);
}
s->mode = mode;
s->data = f;
s->ops = &nullfs_fh_ops;
return true;
}
bool nullfs_f_stat(fs_node_ptr n, stat_t *st) {
nullfs_file_t *f = (nullfs_file_t*)n;
mutex_lock(&f->lock);
st->type = FT_REGULAR;
st->access = f->ok_modes;
st->size = f->size;
mutex_unlock(&f->lock);
return true;
}
void nullfs_f_dispose(fs_node_ptr n) {
// nothing to do
}
// -- File handle --
static size_t nullfs_fh_read(fs_handle_ptr h, size_t offset, size_t len, char* buf) {
nullfs_file_t *f = (nullfs_file_t*)h;
mutex_lock(&f->lock);
size_t ret = 0;
if (offset >= f->size) goto end_read;
if (offset + len > f->size) len = f->size - offset;
memcpy(buf, f->data + offset, len);
ret = len;
end_read:
mutex_unlock(&f->lock);
return ret;
}
static size_t nullfs_fh_write(fs_handle_ptr h, size_t offset, size_t len, const char* buf) {
nullfs_file_t *f = (nullfs_file_t*)h;
mutex_lock(&f->lock);
size_t ret = 0;
if (offset + len > f->size) {
// resize buffer (zero out new portion)
void* new_buffer = malloc(offset + len);
if (new_buffer == 0) goto end_write;
memcpy(new_buffer, f->data, f->size);
if (offset > f->size)
memset(new_buffer + f->size, 0, offset - f->size);
if (f->own_data) free(f->data);
f->data = new_buffer;
f->own_data = true;
f->size = offset + len;
}
memcpy(f->data + offset, buf, len);
ret = len;
end_write:
mutex_unlock(&f->lock);
return ret;
}
static void nullfs_fh_close(fs_handle_ptr h) {
// nothing to do
}
/* vim: set ts=4 sw=4 tw=0 noet :*/