Initial commit. This is a FUSE driver I found somewhere for V6 UNIX filesystems....
authorAaron Taylor <ataylor@subgeniuskitty.com>
Sun, 22 Nov 2020 10:28:28 +0000 (02:28 -0800)
committerAaron Taylor <ataylor@subgeniuskitty.com>
Sun, 22 Nov 2020 10:28:28 +0000 (02:28 -0800)
args.h [new file with mode: 0644]
fs.h [new file with mode: 0644]
unixfs.c [new file with mode: 0644]
v6.c [new file with mode: 0644]

diff --git a/args.h b/args.h
new file mode 100644 (file)
index 0000000..0e666c1
--- /dev/null
+++ b/args.h
@@ -0,0 +1,24 @@
+extern char *argv0;
+#define USED(x) ((void)x)
+#define SET(x) ((x)=0)
+
+#define        ARGBEGIN        for((argv0||(argv0=*argv)),argv++,argc--;\
+                           argv[0] && argv[0][0]=='-' && argv[0][1];\
+                           argc--, argv++) {\
+                               char *_args, *_argt;\
+                               char _argc;\
+                               _args = &argv[0][1];\
+                               if(_args[0]=='-' && _args[1]==0){\
+                                       argc--; argv++; break;\
+                               }\
+                               _argc = 0;\
+                               while(*_args && (_argc = *_args++))\
+                               switch(_argc)
+#define        ARGEND          SET(_argt);USED(_argt);USED(_argc);USED(_args);}USED(argv);USED(argc);
+#define        ARGF()          (_argt=_args, _args=(char*)"",\
+                               (*_argt? _argt: argv[1]? (argc--, *++argv): 0))
+#define        EARGF(x)        (_argt=_args, _args=(char*)"",\
+                               (*_argt? _argt: argv[1]? (argc--, *++argv): ((x), abort(), (char*)0)))
+
+#define        ARGC()          _argc
+
diff --git a/fs.h b/fs.h
new file mode 100644 (file)
index 0000000..1fedcb2
--- /dev/null
+++ b/fs.h
@@ -0,0 +1,69 @@
+#define FUSE_USE_VERSION 31
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <time.h>
+#include <sys/sysmacros.h>
+
+
+#define nil NULL
+typedef int8_t int8;
+typedef uint8_t uint8;
+typedef uint16_t uint16;
+typedef int16_t int16;
+typedef uint32_t uint32;
+typedef int32_t int32;
+typedef unsigned int uint;
+
+#define USED(x) ((void)x)
+
+/* the data containing our file system */
+extern uint8 *fsdata;
+extern int fslen;
+
+void panic(char *fmt, ...);
+void *emalloc(int size);
+FILE *mustopen(const char *name, const char *mode);
+
+typedef struct Dirbuf Dirbuf;
+struct Dirbuf
+{
+       char *p;
+       size_t size;
+};
+
+typedef struct DInode DInode;
+struct DInode;
+
+typedef struct Inode Inode;
+struct Inode
+{
+       int count;
+       int ino;        /* not really needed */
+       DInode *i;
+};
+
+void fs_init(void);
+Inode *fs_iget(uint ino);
+void fs_iput(Inode *ip);
+int fs_open(uint ino, int flags);
+int fs_stat(uint ino, struct stat *stbuf);
+int fs_read(uint ino, void *vdst, int offset, int len);
+int fs_write(uint ino, void *vsrc, int offset, int len);
+struct dirent *fs_readdir(uint ino);
+int fs_mknod(uint ino, const char *name, mode_t mode, dev_t rdev, uint *newino);
+int fs_mkdir(uint ino, const char *name, mode_t mode);
+int fs_unlink(uint parent, const char *name);
+int fs_link(uint ino, uint parent, const char *name);
+int fs_atime(uint ino, int32 time);
+int fs_mtime(uint ino, int32 time);
+int fs_uid(uint ino, int32 uid);
+int fs_gid(uint ino, int32 gid);
+
+void dcheck(void);
diff --git a/unixfs.c b/unixfs.c
new file mode 100644 (file)
index 0000000..67f07e5
--- /dev/null
+++ b/unixfs.c
@@ -0,0 +1,473 @@
+#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;
+}
diff --git a/v6.c b/v6.c
new file mode 100644 (file)
index 0000000..69780b8
--- /dev/null
+++ b/v6.c
@@ -0,0 +1,736 @@
+#include "fs.h"
+
+/* NB: not portable! assumes little endian (and probably other things) */
+
+#define NIPB (512/sizeof(DInode))
+#define NDPB (512/sizeof(Dirent))
+
+/* v6 superblock */
+typedef struct Filsys Filsys;
+struct Filsys
+{
+       int16   s_isize;        /* size in blocks of I list */
+       int16   s_fsize;        /* size in blocks of entire volume */
+       int16   s_nfree;        /* number of in core free blocks (0-100) */
+       int16   s_free[100];    /* in core free blocks */
+       int16   s_ninode;       /* number of in core I nodes (0-100) */
+       int16   s_inode[100];   /* in core free I nodes */
+       uint8   s_flock;        /* lock during free list manipulation */
+       uint8   s_ilock;        /* lock during I list manipulation */
+       uint8   s_fmod;         /* super block modified flag */
+       uint8   s_ronly;        /* mounted read-only flag */
+       int16   s_time[2];      /* current date of last update */
+       int16   pad[48];
+};
+
+/* v6 and PWB/1.0 disk inode */
+struct DInode
+{
+       uint16  i_mode;
+       int8    i_nlink;
+       uint8   i_uid;
+       uint8   i_gid;
+       uint8   i_size0;
+       uint16  i_size1;
+       uint16  i_addr[8];
+       uint16  i_atime[2];
+       uint16  i_mtime[2];
+};
+
+/* modes */
+#define        IALLOC  0100000
+#define        IFMT    060000
+#define        IFDIR   040000
+#define        IFCHR   020000
+#define        IFBLK   060000
+#define        ILARG   010000
+#define        ISUID   04000
+#define        ISGID   02000
+#define ISVTX  01000
+#define        IREAD   0400
+#define        IWRITE  0200
+#define        IEXEC   0100
+
+typedef struct Dirent Dirent;
+struct Dirent
+{
+       uint16 inode;
+       char name[14];
+};
+
+Filsys *filesys;
+DInode *dinodes;
+Inode *inodes;
+
+#define ISIZE(ip) ((ip)->i_size0<<16 | (ip)->i_size1)
+int32
+pdplong(void *p)
+{
+       uint8 *up;
+       uint32 ui;
+       up = p;
+       ui = up[1]<<24 | up[0]<<16 | up[3]<<8 | up[2];
+       return *(int32*)&ui;
+}
+
+void
+setpdplong(void *p, int32 i)
+{
+       uint8 *up;
+       uint32 ui;
+       up = p;
+       ui = *(uint32*)&i;
+       up[1] = ui>>24;
+       up[0] = ui>>16;
+       up[3] = ui>>8;
+       up[2] = ui;
+}
+
+void
+setint24(void *p, int32 i)
+{
+       uint8 *up;
+       uint32 ui;
+       up = p;
+       ui = *(uint32*)&i;
+       up[0] = ui>>16;
+       up[2] = ui>>8;
+       up[1] = ui;
+}
+
+void *bget(int n) { return &fsdata[n*512]; }
+
+/* returns a free block or 0 */
+int
+balloc(void)
+{
+       int i, bno;
+       uint16 *b;
+       filesys->s_nfree--;
+       if(filesys->s_nfree < 0 || filesys->s_nfree >= 100){
+               printf("bad free count\n");
+               return 0;
+       }
+       bno = filesys->s_free[filesys->s_nfree];
+       filesys->s_free[filesys->s_nfree] = 0;
+       if(bno == 0)
+               return 0;
+       if(bno < filesys->s_isize+2 || bno >= filesys->s_fsize){
+               printf("bad free block (%d)\n", bno);
+               return 0;
+       }
+       if(filesys->s_nfree <= 0){
+               b = bget(bno);
+               filesys->s_nfree = b[0];
+               for(i = 0; i < 100; i++)
+                       filesys->s_free[i] = b[i+1];
+       }
+       return bno;
+}
+
+void
+bfree(int bno)
+{
+       int i;
+       uint16 *b;
+
+printf("freeing block\n");
+       if(filesys->s_nfree >= 100){
+               b = bget(bno);
+               b[0] = filesys->s_nfree;
+               for(i = 0; i < 100; i++)
+                       b[i+1] = filesys->s_free[i];
+               filesys->s_nfree = 0;
+       }
+       filesys->s_free[filesys->s_nfree] = bno;
+       filesys->s_nfree++;
+}
+
+int
+zalloc(void)
+{
+       int bn;
+       bn = balloc();
+       if(bn)
+               memset(bget(bn), 0, 512);
+       return bn;
+}
+
+/* returns a free inode or 0 */
+int
+ialloc(void)
+{
+       int ino;
+       DInode *ip;
+       int i, j;
+
+       if(filesys->s_ninode <= 0){
+               ino = 0;
+               for(i = 0; i < filesys->s_isize; i++)
+                       for(j = 0; j < NIPB; j++){
+                               ino++;
+                               ip = &dinodes[ino];
+                               if(ip->i_mode)
+                                       continue;
+                               filesys->s_inode[filesys->s_ninode++] = ino;
+                               if(filesys->s_ninode >= 100)
+                                       goto brk;
+                       }
+       }
+brk:
+       if(filesys->s_ninode > 0){
+               ino = filesys->s_inode[--filesys->s_ninode];
+               return ino;
+       }
+       return 0;
+}
+
+void
+ifree(int ino)
+{
+printf("freeing inode\n");
+       if(filesys->s_ninode >= 100)
+               return;
+       filesys->s_inode[filesys->s_ninode++] = ino;
+}
+
+void
+itrunc(DInode *ip)
+{
+       int i;
+       uint16 *bp, *cp, *dp, *ep;
+
+       if(ip->i_mode & (IFCHR|IFBLK))
+               return;
+       for(i = 7; i >= 0; i--)
+       if(ip->i_addr[i]){
+               if(ip->i_mode & ILARG){
+                       bp = bget(ip->i_addr[i]);
+                       for(cp = &bp[255]; cp >= bp; cp--)
+                       if(*cp){
+                               if(i == 7){
+                                       dp = bget(*cp);
+                                       for(ep = &dp[255]; ep >= dp; ep--)
+                                       if(*ep)
+                                               bfree(*ep);
+                               }
+                               bfree(*cp);
+                       }
+               }
+               bfree(ip->i_addr[i]);
+               ip->i_addr[i] = 0;
+       }
+       ip->i_mode &= ~ILARG;
+       setint24(&ip->i_size0, 0);
+}
+
+void
+dcheck(void)
+{
+       Filsys fs;
+       int i;
+
+       fs = *filesys;
+       i = 0;
+       while(balloc())
+               i++;
+       printf("free: %d\n", i);
+       *filesys = fs;
+}
+
+int
+bmap(DInode *ip, uint bn)
+{
+       uint i;
+       uint nb;
+       uint16 *b;
+
+       if(bn & ~077777)
+               return 0;
+
+       if((ip->i_mode & ILARG) == 0){
+
+               /* small file, direct fetch */
+
+               if(bn & ~7){
+                       /* convert to large */
+                       nb = zalloc();
+                       if(nb == 0)
+                               return 0;
+                       b = bget(nb);
+                       for(i = 0; i < 8; i++){
+                               b[i] = ip->i_addr[i];
+                               ip->i_addr[i] = 0;
+                       }
+                       ip->i_addr[0] = nb;
+                       ip->i_mode |= ILARG;
+                       goto large;
+               }
+
+               nb = ip->i_addr[bn];
+               if(nb == 0){
+                       nb = zalloc();
+                       if(nb == 0)
+                               return 0;
+                       ip->i_addr[bn] = nb;
+               }
+               return nb;
+       }
+
+       /* large file, 7 indirect blocks */
+
+large:
+       i = bn>>8;
+       if(i > 7)
+               i = 7;
+       nb = ip->i_addr[i];
+       if(nb == 0){
+               nb = zalloc();
+               if(nb == 0)
+                       return 0;
+               ip->i_addr[bn] = nb;
+       }
+       b = bget(nb);
+
+       if(i == 7){
+
+               /* huge file, double indirect last block */
+
+               i = (bn>>8) - 7;
+               nb = b[i&0377];
+               if(nb == 0){
+                       nb = zalloc();
+                       if(nb == 0)
+                               return 0;
+                       b[i&0377] = nb;
+               }
+               b = bget(nb);
+       }
+
+       nb = b[bn&0377];
+       return nb;
+};
+
+uint8*
+getblock(DInode *ip, uint bn)
+{
+       bn = bmap(ip, bn);
+       if(bn == 0)
+               return nil;
+       return bget(bn);
+}
+
+void
+fs_init(void)
+{
+       int i, ni;
+
+       filesys = bget(1);
+       dinodes = (DInode*)bget(2) - 1;
+       ni = filesys->s_isize*NIPB + 1;
+       inodes = malloc(ni*sizeof(Inode));
+       for(i = 0; i < ni; i++){
+               inodes[i].ino = i;
+               inodes[i].count = 0;
+               inodes[i].i = &dinodes[i];
+       }
+}
+
+int
+fs_open(uint ino, int flags)
+{
+       DInode *ip;
+
+       ip = &dinodes[ino];
+       if((ip->i_mode & IFMT) == IFDIR)
+               return EISDIR;
+//     if((flags & 3) != O_RDONLY)
+//             return EACCES;
+       return 0;
+}
+
+int
+fs_stat(uint ino, struct stat *stbuf)
+{
+       DInode *ip;
+       ip = &dinodes[ino];
+
+       stbuf->st_ino = ino;
+       if((ip->i_mode & IFMT) == IFDIR)
+               stbuf->st_mode = S_IFDIR;
+       else if((ip->i_mode & IFMT) == IFCHR)
+               stbuf->st_mode = S_IFCHR;
+       else if((ip->i_mode & IFMT) == IFBLK)
+               stbuf->st_mode = S_IFBLK;
+       else
+               stbuf->st_mode = S_IFREG;
+       stbuf->st_mode |= ip->i_mode & 0777;
+       stbuf->st_nlink = ip->i_nlink;
+       stbuf->st_size = ISIZE(ip);
+       if((ip->i_mode & IFMT) == IFCHR ||
+          (ip->i_mode & IFMT) == IFBLK)
+               stbuf->st_rdev = makedev(ip->i_addr[0]>>8 & 0xFF, ip->i_addr[0]&0xFF);
+       stbuf->st_uid = ip->i_uid;
+       stbuf->st_gid = ip->i_gid;
+       stbuf->st_atime = pdplong(ip->i_atime);
+       stbuf->st_mtime = pdplong(ip->i_mtime);
+
+       return 0;
+}
+
+int
+fs_atime(uint ino, int32 time)
+{
+       DInode *ip;
+       ip = &dinodes[ino];
+       setpdplong(ip->i_atime, time);
+       return 0;
+}
+
+int
+fs_mtime(uint ino, int32 time)
+{
+       DInode *ip;
+       ip = &dinodes[ino];
+       setpdplong(ip->i_mtime, time);
+       return 0;
+}
+
+int
+fs_uid(uint ino, int32 uid)
+{
+       DInode *ip;
+       ip = &dinodes[ino];
+       ip->i_uid = uid;
+       return 0;
+}
+
+int
+fs_gid(uint ino, int32 gid)
+{
+       DInode *ip;
+       ip = &dinodes[ino];
+       ip->i_gid = gid;
+       return 0;
+}
+
+int
+fs_read(uint ino, void *vdst, int offset, int len)
+{
+       DInode *ip;
+       int bn;
+       uint8 *b;
+       int isize;
+       int n;
+       uint8 *dst;
+
+       ip = &dinodes[ino];
+       dst = vdst;
+       isize = ISIZE(ip);
+       if(offset + len > isize)
+               len = isize - offset;
+       if(len <= 0)
+               return 0;
+
+       bn = offset/512;
+       offset %= 512;
+
+       b = getblock(ip, bn);
+       if(b == nil)
+               /* TODO: handle gracefully */
+               panic("no block");
+
+       if(len < 512-offset){
+               /* smaller than block */
+               memcpy(dst, b+offset, len);
+               return len;
+       }
+
+       /* at least one block */
+       memcpy(dst, b+offset, 512-offset);
+       n = 512-offset;
+       len -= n;
+       dst += n;
+       while(len > 0){
+               bn++;
+               b = getblock(ip, bn);
+               if(b == nil)
+                       /* TODO: handle gracefully */
+                       panic("no block");
+               memcpy(dst, b, len > 512 ? 512 : len);
+               n += len > 512 ? 512 : len;
+               len -= 512;
+               dst += 512;
+       }
+       return n;
+}
+
+int
+fs_write(uint ino, void *vsrc, int offset, int len)
+{
+       DInode *ip;
+       int bn;
+       uint8 *b;
+       int n;
+       int32 size;
+       uint8 *src;
+
+       ip = &dinodes[ino];
+       src = vsrc;
+       size = ISIZE(ip);
+       // TODO: make this better:
+       if(offset+len > size)
+               setint24(&ip->i_size0, offset+len);
+       if(len <= 0)
+               return 0;
+
+       bn = offset/512;
+       offset %= 512;
+
+       b = getblock(ip, bn);
+       if(b == nil)
+               /* TODO: handle gracefully */
+               panic("no block");
+
+       if(len < 512-offset){
+               /* smaller than block */
+               memcpy(b+offset, src, len);
+               return len;
+       }
+
+       /* at least one block */
+       memcpy(b+offset, src, 512-offset);
+       n = 512-offset;
+       len -= n;
+       src += n;
+       while(len > 0){
+               bn++;
+               b = getblock(ip, bn);
+               if(b == nil)
+                       /* TODO: handle gracefully */
+                       panic("no block");
+               memcpy(b, src, len > 512 ? 512 : len);
+               n += len > 512 ? 512 : len;
+               len -= 512;
+               src += 512;
+       }
+       return n;
+}
+
+/* return a malloc'd array of dirents for a given inode */
+struct dirent*
+fs_readdir(uint ino)
+{
+       DInode *ip;
+       struct dirent *des, *dp;
+       int size;
+       int ndes;
+       Dirent de;
+       int offset;
+
+       ip = &dinodes[ino];
+       if((ip->i_mode & IFDIR) == 0)
+               return nil;
+
+       size = ISIZE(ip);
+       ndes = size/sizeof(Dirent);
+       des = malloc((ndes+1)*sizeof(struct dirent));
+
+       offset = 0;
+       dp = des;
+       while(fs_read(ino, &de, offset, sizeof(Dirent)) == sizeof(Dirent)){
+               if(de.inode){
+                       dp->d_ino = de.inode;
+                       memcpy(dp->d_name, de.name, 14);
+                       dp->d_name[14] = '\0';
+                       dp++;
+               }
+               offset += sizeof(Dirent);
+       }
+       dp->d_ino = 0;
+       dp->d_name[0] = '\0';
+
+       return des;
+}
+
+static int
+lookup(uint ino, const char *name, Dirent *de)
+{
+       int offset;
+
+       assert(de);
+       offset = 0;
+       while(fs_read(ino, de, offset, sizeof(Dirent)) == sizeof(Dirent)){
+               if(de->inode)
+                       if(strcmp(de->name, name) == 0)
+                               return offset;
+               offset += sizeof(Dirent);
+       }
+       memset(de, 0, sizeof(Dirent));
+       return -1;
+}
+
+static int
+allocdirent(uint ino)
+{
+       Dirent de;
+       int offset;
+
+       offset = 0;
+       while(fs_read(ino, &de, offset, sizeof(Dirent)) == sizeof(Dirent)){
+               if(de.inode == 0)
+                       return offset;
+               offset += sizeof(Dirent);
+       }
+       memset(&de, 0, sizeof(Dirent));
+       fs_write(ino, &de, offset, sizeof(Dirent));
+       return offset;
+}
+
+int
+fs_link(uint ino, uint parent, const char *name)
+{
+       Dirent de;
+       int offset;
+
+       offset = allocdirent(parent);
+       /* TODO: can't happen right now */
+       if(offset < 0)
+               return ENOSPC;
+       memset(&de, 0, sizeof(Dirent));
+       de.inode = ino;
+       strncpy(de.name, name, 14);
+       fs_write(parent, &de, offset, sizeof(de));
+       fs_mtime(ino, time(nil));
+       fs_atime(ino, time(nil));
+       dinodes[ino].i_nlink++;
+
+       return 0;
+}
+
+int
+fs_mknod(uint parent, const char *name, mode_t mode, dev_t rdev, uint *newino)
+{
+       uint ino;
+       DInode *ip, *ipp;
+       int offset;
+       Dirent de;
+
+       USED(rdev);
+       // TODO: support more files
+       if((mode & S_IFMT) != S_IFREG &&
+          (mode & S_IFMT) != S_IFDIR)
+               return EACCES;
+
+       ino = ialloc();
+       if(ino == 0)
+               return ENOSPC;
+       ip = &dinodes[ino];
+       memset(ip, 0, sizeof(DInode));
+       ip->i_mode = (mode&0777) | IALLOC;
+       if((mode & S_IFMT) == S_IFDIR)
+               ip->i_mode |= IFDIR;
+       else if((mode & S_IFMT) == S_IFCHR)
+               ip->i_mode |= IFCHR;
+       else if((mode & S_IFMT) == S_IFBLK)
+               ip->i_mode |= IFBLK;
+       if((ip->i_mode & IFMT) == IFCHR ||
+          (ip->i_mode & IFMT) == IFBLK)
+               ip->i_addr[0] =
+                       major(rdev)<<8 & 0xFF00 | minor(rdev) & 0xFF;
+
+       ipp = &dinodes[parent];
+       assert((ipp->i_mode & IFMT) == IFDIR);
+
+       offset = allocdirent(parent);
+       /* TODO: can't happen right now */
+       if(offset < 0){
+               ip->i_mode = 0;
+               return ENOSPC;
+       }
+
+       ip->i_nlink = 1;
+       memset(&de, 0, sizeof(Dirent));
+       de.inode = ino;
+       strncpy(de.name, name, 14);
+       fs_write(parent, &de, offset, sizeof(de));
+       fs_mtime(ino, time(nil));
+       fs_atime(ino, time(nil));
+
+       if(newino)
+               *newino = ino;
+       printf("making node %s %o\n", name, mode);
+       return 0;
+}
+
+int
+fs_mkdir(uint parent, const char *name, mode_t mode)
+{
+       int bn;
+       int ino;
+       int ret;
+       DInode *ip;
+       Dirent des[2];
+
+       /* Make sure we have space */
+       bn = balloc();
+       if(bn == 0)
+               return ENOSPC;
+       ret = fs_mknod(parent, name, mode | S_IFDIR, 0, &ino);
+       if(ret){
+               bfree(bn);
+               return ret;
+       }
+       ip = &dinodes[ino];
+       ip->i_addr[0] = bn;
+
+       memset(des, 0, 2*sizeof(Dirent));
+       des[0].inode = ino;
+       strcpy(des[0].name, ".");
+       ip->i_nlink++;
+       des[1].inode = parent;
+       strcpy(des[1].name, "..");
+       dinodes[parent].i_nlink++;
+       fs_write(ino, des, 0, 2*sizeof(Dirent));
+       return 0;
+}
+
+Inode*
+fs_iget(uint ino)
+{
+       Inode *ip;
+       ip = &inodes[ino];
+       ip->count++;
+       return ip;
+}
+
+void
+fs_iput(Inode *ip)
+{
+       assert(ip->count > 0);
+       ip->count--;
+       if(ip->count == 0)
+               if(ip->i->i_nlink <= 0){
+       printf("truncating inode %d (%d)\n", ip->ino, ip->i->i_nlink);
+                       itrunc(ip->i);
+                       ip->i->i_mode = 0;
+                       ifree(ip->ino);
+               }
+}
+
+int
+fs_unlink(uint parent, const char *name)
+{
+       Dirent de;
+       int offset;
+
+       offset = lookup(parent, name, &de);
+       if(offset < 0)
+               /* shouldn't happen */
+               return 1;
+       dinodes[de.inode].i_nlink--;
+
+       de.inode = 0;
+       fs_write(parent, &de, offset, sizeof(Dirent));
+       return 0;
+}