Added README, LICENSE, Makefile for future PDP-11 Unix FUSE fs driver project.
[pdp11-unix-fusefs] / unixfs.c
#include "fs.h"
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <fuse_lowlevel.h>
#include "args.h"
uint8 *fsdata;
int fslen;
void
panic(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
exit(1);
}
void*
emalloc(int size)
{
void *p;
p = malloc(size);
if(p == nil)
panic("error: no memory");
return p;
}
FILE*
mustopen(const char *name, const char *mode)
{
FILE *f;
if(f = fopen(name, mode), f == nil)
panic("couldn't open file: %s", name);
return f;
}
typedef struct Options Options;
struct Options
{
int singlethread;
int foreground;
int debug;
int nodefault_subtype;
char *device;
char *mountpoint;
int show_version;
int show_help;
int clone_fd;
unsigned int max_idle_threads;
};
static Options options;
#define offsetof(type, field) ((long)&((type*)0)->field)
#define OPTION(t, p) \
{ t, offsetof(Options, p), 1 }
static const struct fuse_opt myopts[] = {
OPTION("-h", show_help),
OPTION("--help", show_help),
OPTION("-V", show_version),
OPTION("--version", show_version),
OPTION("-d", debug),
OPTION("debug", debug),
OPTION("-d", foreground),
OPTION("debug", foreground),
FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP),
FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP),
OPTION("-f", foreground),
OPTION("-s", singlethread),
OPTION("clone_fd", clone_fd),
OPTION("max_idle_threads=%u", max_idle_threads),
FUSE_OPT_END
};
static int
opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs)
{
(void)data; (void)outargs;
switch(key){
case FUSE_OPT_KEY_NONOPT:
if(options.device == nil){
options.device = strdup(arg);
return 0;
}
if(options.mountpoint == nil){
options.mountpoint = strdup(arg);
return 0;
}
return -1;
default:
panic("invalid option");
}
return -1;
}
void
usage(void)
{
fprintf(stderr,
" usage: asdf [options] filesystem mountpoint\n"
" -h --help print help\n"
" -V --version print version\n"
" -d -o debug enable debug output (implies -f)\n"
" -f foreground operation\n"
" -s disable multi-threaded operation\n"
" -o clone_fd use separate fuse device fd for each thread\n"
" (may improve performance)\n"
" -o max_idle_threads the maximum number of idle worker threads\n"
" allowed (default: 10)\n");
exit(1);
}
void
dirbuf_add(fuse_req_t req, Dirbuf *b, const char *name, fuse_ino_t ino)
{
struct stat stbuf;
size_t oldsize = b->size;
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
b->p = (char *) realloc(b->p, b->size);
memset(&stbuf, 0, sizeof(stbuf));
stbuf.st_ino = ino;
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
b->size);
}
#define min(x, y) ((x) < (y) ? (x) : (y))
int
reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
off_t off, size_t maxsize)
{
if(off < bufsize)
return fuse_reply_buf(req, buf + off,
min(bufsize - off, maxsize));
else
return fuse_reply_buf(req, NULL, 0);
}
static void
vfs_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
{
struct stat stbuf;
USED(fi);
memset(&stbuf, 0, sizeof(stbuf));
if(fs_stat(ino, &stbuf) == -1)
fuse_reply_err(req, ENOENT);
else
fuse_reply_attr(req, &stbuf, 1.0);
}
static void
vfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
{
int ret = 0;
USED(fi);
if(to_set & FUSE_SET_ATTR_ATIME)
ret = fs_atime(ino, attr->st_atime);
if(to_set & FUSE_SET_ATTR_MTIME)
ret = fs_mtime(ino, attr->st_mtime);
if(to_set & FUSE_SET_ATTR_GID)
ret = fs_gid(ino, attr->st_gid);
if(to_set & FUSE_SET_ATTR_UID)
ret = fs_uid(ino, attr->st_uid);
if(ret)
fuse_reply_err(req, ret);
else
vfs_getattr(req, ino, fi);
}
void
vfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
{
int e;
e = fs_open(ino, fi->flags);
if(e)
fuse_reply_err(req, e);
else
fuse_reply_open(req, fi);
}
void
vfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
off_t off, struct fuse_file_info *fi)
{
USED(fi);
char *buf;
int n;
buf = malloc(size);
n = fs_read(ino, buf, off, size);
reply_buf_limited(req, buf, n, off, size);
free(buf);
}
void
vfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
size_t size, off_t off, struct fuse_file_info *fi)
{
int n;
USED(fi);
n = fs_write(ino, (void*)buf, off, size);
fuse_reply_write(req, n);
}
void
vfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
off_t off, struct fuse_file_info *fi)
{
struct dirent *dirs, *dp;
Dirbuf b;
USED(fi);
dirs = fs_readdir(ino);
if(dirs == nil){
fuse_reply_err(req, ENOTDIR);
return;
}
memset(&b, 0, sizeof(b));
for(dp = dirs; dp->d_ino; dp++)
dirbuf_add(req, &b, dp->d_name, dp->d_ino);
free(dirs);
reply_buf_limited(req, b.p, b.size, off, size);
free(b.p);
}
/* returns static pointer! */
struct dirent*
lookup(uint ino, const char *name)
{
struct dirent *dirs, *dp;
static struct dirent de;
dirs = fs_readdir(ino);
if(dirs == nil)
return nil;
for(dp = dirs; dp->d_ino; dp++)
if(strcmp(dp->d_name, name) == 0){
de = *dp;
free(dirs);
return &de;
}
free(dirs);
return nil;
}
void
vfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
{
struct fuse_entry_param e;
struct dirent *de;
de = lookup(parent, name);
if(de == nil){
fuse_reply_err(req, ENOENT);
return;
}
memset(&e, 0, sizeof(e));
e.ino = de->d_ino;
e.attr_timeout = 1.0;
e.entry_timeout = 1.0;
fs_stat(e.ino, &e.attr);
/* increment ref count */
fs_iget(e.ino);
fuse_reply_entry(req, &e);
}
void
vfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
{
Inode *ip;
printf("forgetting %d\n", nlookup);
ip = fs_iget(ino);
fs_iput(ip);
while(nlookup--)
fs_iput(ip);
fuse_reply_none(req);
}
void
vfs_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
{
int ret;
ret = fs_mknod(parent, name, mode, rdev, nil);
if(ret)
fuse_reply_err(req, ret);
else
vfs_lookup(req, parent, name);
}
void
vfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
{
int ret;
ret = fs_mkdir(parent, name, mode);
if(ret)
fuse_reply_err(req, ret);
else
vfs_lookup(req, parent, name);
}
void
vfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
{
int ret;
ret = fs_unlink(parent, name);
fuse_reply_err(req, ret);
}
int
isdirempty(struct dirent *de)
{
for(; de->d_ino; de++)
if(strcmp(de->d_name, ".") != 0 &&
strcmp(de->d_name, "..") != 0)
return 0;
return 1;
}
void
vfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
{
struct dirent *dir, *de;
int ret;
int dino;
ret = 0;
de = lookup(parent, name);
/* this shouldn't happen */
if(de == nil)
return;
dino = de->d_ino;
dir = fs_readdir(dino);
assert(dir);
if(!isdirempty(dir)){
free(dir);
fuse_reply_err(req, ENOTEMPTY);
return;
}
printf("dir to delete: %d %s\n", dino, name);
for(de = dir; de->d_ino; de++)
fs_unlink(dino, de->d_name);
free(dir);
fs_unlink(parent, name);
fuse_reply_err(req, ret);
}
void
vfs_link(fuse_req_t req, fuse_ino_t ino,
fuse_ino_t newparent, const char *newname)
{
int ret;
ret = fs_link(ino, newparent, newname);
if(ret)
fuse_reply_err(req, ret);
else
vfs_lookup(req, newparent, newname);
}
static struct fuse_lowlevel_ops hello_ll_oper = {
.lookup = vfs_lookup,
.getattr = vfs_getattr,
.setattr = vfs_setattr,
.readdir = vfs_readdir,
.open = vfs_open,
.read = vfs_read,
.write = vfs_write,
.mknod = vfs_mknod,
.mkdir = vfs_mkdir,
.unlink = vfs_unlink,
.rmdir = vfs_rmdir,
.link = vfs_link,
.forget = vfs_forget,
// TODO
// rename
};
void
fsload(const char *filename)
{
FILE *f = mustopen(filename, "rb");
fseek(f, 0, SEEK_END);
fslen = ftell(f);
fseek(f, 0, SEEK_SET);
fsdata = emalloc(fslen);
fread(fsdata, 1, fslen, f);
fclose(f);
}
int
main(int argc, char *argv[])
{
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
struct fuse_session *se;
char *m;
int ret = -1;
if(fuse_opt_parse(&args, &options, myopts, opt_proc) == -1 ||
options.device == nil || options.mountpoint == nil)
usage();
m = options.mountpoint;
options.mountpoint = realpath(m, nil);
if(options.mountpoint == nil)
panic("bad mount point %s", m);
printf("device: %s\n", options.device);
printf("mountpoint: %s\n", options.mountpoint);
if(options.show_help){
usage();
return 0;
}
if(options.show_version){
printf("FUSE library version %s\n", fuse_pkgversion());
fuse_lowlevel_version();
return 0;
}
fsload(options.device);
fs_init();
dcheck();
se = fuse_session_new(&args, &hello_ll_oper,
sizeof(hello_ll_oper), NULL);
if(se == NULL)
return 1;
if(fuse_set_signal_handlers(se) != 0)
goto err_out2;
if(fuse_session_mount(se, options.mountpoint) != 0)
goto err_out3;
fuse_daemonize(options.foreground);
/* Block until ctrl+c or fusermount -u */
if(options.singlethread)
ret = fuse_session_loop(se);
else
ret = fuse_session_loop_mt(se, options.clone_fd);
fuse_session_unmount(se);
err_out3:
fuse_remove_signal_handlers(se);
err_out2:
fuse_session_destroy(se);
free(options.mountpoint);
fuse_opt_free_args(&args);
return ret ? 1 : 0;
}