Initial import.
[vcfs.git] / vcfs.c
1 #define _LARGEFILE64_SOURCE
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <unistd.h>
5 #include <fcntl.h>
6 #include <string.h>
7 #include <errno.h>
8 #include <assert.h>
9 #include <fuse_lowlevel.h>
10
11 #include "utils.h"
12 #include "log.h"
13 #include "store.h"
14 #include "blocktree.h"
15 #include "vcfs.h"
16
17 /* XXX: The current i-numbering scheme sucks. */
18
19 struct btree {
20     struct btree *l, *r;
21     int h;
22     void *d;
23 };
24
25 struct inoc {
26     vc_ino_t inode;
27     struct btnode inotab;
28     fuse_ino_t cnum;
29 };
30
31 struct vcfsdata {
32     struct store *st;
33     int revfd;
34     vc_rev_t currev;
35     vc_ino_t nextino;
36     struct btnode inotab;
37     struct btree *inocbf, *inocbv;
38     fuse_ino_t inocser;
39 };
40
41 #define max(a, b) (((b) > (a))?(b):(a))
42 static struct btnode nilnode = {0, };
43
44 static int btheight(struct btree *tree)
45 {
46     if(tree == NULL)
47         return(0);
48     return(tree->h);
49 }
50
51 static void btsetheight(struct btree *tree)
52 {
53     if(tree == NULL)
54         return;
55     tree->h = max(btheight(tree->l), btheight(tree->r)) + 1;
56 }
57
58 static void bbtrl(struct btree **tree);
59
60 static void bbtrr(struct btree **tree)
61 {
62     struct btree *m, *l, *r;
63     
64     if(btheight((*tree)->l->r) > btheight((*tree)->l->l))
65         bbtrl(&(*tree)->l);
66     r = (*tree);
67     l = r->l;
68     m = l->r;
69     r->l = m;
70     btsetheight(r);
71     l->r = r;
72     btsetheight(l);
73     *tree = l;
74 }
75
76 static void bbtrl(struct btree **tree)
77 {
78     struct btree *m, *l, *r;
79     
80     if(btheight((*tree)->r->l) > btheight((*tree)->r->r))
81         bbtrr(&(*tree)->r);
82     l = (*tree);
83     r = l->r;
84     m = r->l;
85     l->r = m;
86     btsetheight(l);
87     r->l = l;
88     btsetheight(r);
89     *tree = r;
90 }
91
92 static int bbtreeput(struct btree **tree, void *item, int (*cmp)(void *, void *))
93 {
94     int c, r;
95     
96     if(*tree == NULL) {
97         *tree = calloc(1, sizeof(**tree));
98         (*tree)->d = item;
99         (*tree)->h = 1;
100         return(1);
101     }
102     if((c = cmp(item, (*tree)->d)) < 0)
103         r = bbtreeput(&(*tree)->l, item, cmp);
104     else if(c > 0)
105         r = bbtreeput(&(*tree)->r, item, cmp);
106     else
107         return(0);
108     btsetheight(*tree);
109     if(btheight((*tree)->l) > btheight((*tree)->r) + 1)
110         bbtrr(tree);
111     if(btheight((*tree)->r) > btheight((*tree)->l) + 1)
112         bbtrl(tree);
113     return(r);
114 }
115
116 static void *btreeget(struct btree *tree, void *key, int (*cmp)(void *, void *))
117 {
118     int c;
119     
120     while(1) {
121         if(tree == NULL)
122             return(NULL);
123         c = cmp(key, tree->d);
124         if(c < 0)
125             tree = tree->l;
126         else if(c > 0)
127             tree = tree->r;
128         else
129             return(tree->d);
130     }
131 }
132
133 static void dstrvcfs(struct vcfsdata *fsd)
134 {
135     releasestore(fsd->st);
136     fsync(fsd->revfd);
137     close(fsd->revfd);
138     free(fsd);
139 }
140
141 static int inoccmpbf(struct inoc *a, struct inoc *b)
142 {
143     return(a->cnum - b->cnum);
144 }
145
146 static int inoccmpbv(struct inoc *a, struct inoc *b)
147 {
148     if(a->inode < b->inode)
149         return(-1);
150     if(a->inode > b->inode)
151         return(1);
152     if(a->inotab.d < b->inotab.d)
153         return(-1);
154     if(a->inotab.d > b->inotab.d)
155         return(1);
156     return(addrcmp(&a->inotab.a, &b->inotab.a));
157 }
158
159 static struct inoc *getinocbf(struct vcfsdata *fsd, fuse_ino_t inode)
160 {
161     struct inoc key;
162     
163     key.cnum = inode;
164     return(btreeget(fsd->inocbf, &key, (int (*)(void *, void *))inoccmpbf));
165 }
166
167 static struct inoc *getinocbv(struct vcfsdata *fsd, vc_ino_t inode, struct btnode inotab)
168 {
169     struct inoc key;
170     
171     key.inotab = inotab;
172     key.inode = inode;
173     return(btreeget(fsd->inocbv, &key, (int (*)(void *, void *))inoccmpbv));
174 }
175
176 static fuse_ino_t cacheinode(struct vcfsdata *fsd, vc_ino_t inode, struct btnode inotab)
177 {
178     fuse_ino_t ret;
179     struct inoc *inoc;
180     
181     if((inoc = getinocbv(fsd, inode, inotab)) != NULL)
182         return(inoc->cnum);
183     ret = fsd->inocser++;
184     inoc = calloc(1, sizeof(*inoc));
185     inoc->inode = inode;
186     inoc->inotab = inotab;
187     inoc->cnum = ret;
188     bbtreeput(&fsd->inocbf, inoc, (int (*)(void *, void *))inoccmpbf);
189     bbtreeput(&fsd->inocbv, inoc, (int (*)(void *, void *))inoccmpbv);
190     return(ret);
191 }
192
193 static struct vcfsdata *initvcfs(char *dir)
194 {
195     struct vcfsdata *fsd;
196     char tbuf[1024];
197     struct stat64 sb;
198     struct revrec cr;
199     
200     fsd = calloc(1, sizeof(*fsd));
201     snprintf(tbuf, sizeof(tbuf), "%s/revs", dir);
202     if((fsd->revfd = open(tbuf, O_RDWR | O_LARGEFILE)) < 0) {
203         flog(LOG_ERR, "could not open revision database: %s", strerror(errno));
204         free(fsd);
205         return(NULL);
206     }
207     if(fstat64(fsd->revfd, &sb)) {
208         flog(LOG_ERR, "could not stat revision database: %s", strerror(errno));
209         close(fsd->revfd);
210         free(fsd);
211         return(NULL);
212     }
213     if(sb.st_size % sizeof(struct revrec) != 0) {
214         flog(LOG_ERR, "revision database has illegal size");
215         close(fsd->revfd);
216         free(fsd);
217         return(NULL);
218     }
219     fsd->currev = (sb.st_size / sizeof(struct revrec)) - 1;
220     assert(!readall(fsd->revfd, &cr, sizeof(cr), fsd->currev * sizeof(struct revrec)));
221     fsd->inotab = cr.root;
222     if((fsd->st = newfstore(dir)) == NULL) {
223         close(fsd->revfd);
224         free(fsd);
225         return(NULL);
226     }
227     fsd->inocser = 1;
228     cacheinode(fsd, 0, nilnode);
229     if((fsd->nextino = btcount(fsd->st, &fsd->inotab)) < 0) {
230         flog(LOG_ERR, "could not count inodes: %s");
231         close(fsd->revfd);
232         releasestore(fsd->st);
233         free(fsd);
234         return(NULL);
235     }
236     return(fsd);
237 }
238
239 static vc_ino_t dirlookup(struct vcfsdata *fsd, struct btnode *dirdata, const char *name, int *di)
240 {
241     struct dentry dent;
242     int i;
243     ssize_t sz;
244     
245     for(i = 0; ; i++) {
246         if((sz = btget(fsd->st, dirdata, i, &dent, sizeof(dent))) < 0) {
247             if(errno == ERANGE)
248                 errno = ENOENT;
249             return(-1);
250         }
251         if((dent.inode >= 0) && !strncmp(dent.name, name, sizeof(dent.name))) {
252             if(di != NULL)
253                 *di = i;
254             return(dent.inode);
255         }
256     }
257 }
258
259 static void fusedestroy(struct vcfsdata *fsd)
260 {
261     dstrvcfs(fsd);
262 }
263
264 static void fillstat(struct stat *sb, struct inode *file)
265 {
266     sb->st_mode = file->mode;
267     sb->st_atime = (time_t)file->mtime;
268     sb->st_mtime = (time_t)file->mtime;
269     sb->st_ctime = (time_t)file->ctime;
270     sb->st_size = file->size;
271     sb->st_uid = file->uid;
272     sb->st_gid = file->gid;
273     sb->st_nlink = file->links;
274 }
275
276 static int getinode(struct vcfsdata *fsd, struct btnode inotab, vc_ino_t ino, struct inode *buf)
277 {
278     ssize_t sz;
279     
280     if(inotab.d == 0)
281         inotab = fsd->inotab;
282     if((sz = btget(fsd->st, &inotab, ino, buf, sizeof(*buf))) < 0)
283         return(-1);
284     if(sz != sizeof(*buf)) {
285         flog(LOG_ERR, "illegal size for inode %i", ino);
286         errno = EIO;
287         return(-1);
288     }
289     return(0);
290 }
291
292 static void fusegetattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
293 {
294     struct vcfsdata *fsd;
295     struct stat sb;
296     struct inoc *inoc;
297     struct inode file;
298     
299     fsd = fuse_req_userdata(req);
300     memset(&sb, 0, sizeof(sb));
301     if((inoc = getinocbf(fsd, ino)) == NULL) {
302         fuse_reply_err(req, ENOENT);
303         return;
304     }
305     if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
306         fuse_reply_err(req, errno);
307         return;
308     }
309     fillstat(&sb, &file);
310     fuse_reply_attr(req, &sb, 0);
311 }
312
313 static void fuselookup(fuse_req_t req, fuse_ino_t parent, const char *name)
314 {
315     struct vcfsdata *fsd;
316     struct inode file;
317     struct inoc *inoc;
318     struct fuse_entry_param e;
319     vc_ino_t target;
320     
321     fsd = fuse_req_userdata(req);
322     if((inoc = getinocbf(fsd, parent)) == NULL) {
323         fuse_reply_err(req, ENOENT);
324         return;
325     }
326     if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
327         fuse_reply_err(req, errno);
328         return;
329     }
330     if((target = dirlookup(fsd, &file.data, name, NULL)) < 0) {
331         fuse_reply_err(req, errno);
332         return;
333     }
334     if(getinode(fsd, inoc->inotab, target, &file)) {
335         fuse_reply_err(req, errno);
336         return;
337     }
338     memset(&e, 0, sizeof(e));
339     e.ino = cacheinode(fsd, target, inoc->inotab);
340     fillstat(&e.attr, &file);
341     fuse_reply_entry(req, &e);
342 }
343
344 static void fusereaddir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
345 {
346     struct vcfsdata *fsd;
347     struct inoc *inoc;
348     struct inode file;
349     struct dentry dent;
350     struct stat sb;
351     ssize_t sz, osz, bsz;
352     char *buf;
353     
354     fsd = fuse_req_userdata(req);
355     if((inoc = getinocbf(fsd, ino)) == NULL) {
356         fuse_reply_err(req, ENOENT);
357         return;
358     }
359     if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
360         fuse_reply_err(req, errno);
361         return;
362     }
363     bsz = 0;
364     buf = NULL;
365     while(bsz < size) {
366         memset(&dent, 0, sizeof(dent));
367         if((sz = btget(fsd->st, &file.data, off++, &dent, sizeof(dent))) < 0) {
368             if(errno == ERANGE) {
369                 if(buf != NULL)
370                     break;
371                 fuse_reply_buf(req, NULL, 0);
372                 return;
373             }
374             fuse_reply_err(req, errno);
375             if(buf != NULL)
376                 free(buf);
377             return;
378         }
379         if(dent.inode < 0)
380             continue;
381         osz = bsz;
382         bsz += fuse_add_direntry(req, NULL, 0, dent.name, NULL, 0);
383         if(bsz > size)
384             break;
385         buf = realloc(buf, bsz);
386         memset(&sb, 0, sizeof(sb));
387         sb.st_ino = cacheinode(fsd, dent.inode, inoc->inotab);
388         fuse_add_direntry(req, buf + osz, bsz - osz, dent.name, &sb, off);
389     }
390     fuse_reply_buf(req, buf, bsz);
391     if(buf != NULL)
392         free(buf);
393 }
394
395 static vc_rev_t commit(struct vcfsdata *fsd, struct btnode inotab)
396 {
397     struct revrec rr;
398     
399     rr.ct = time(NULL);
400     rr.root = inotab;
401     if(writeall(fsd->revfd, &rr, sizeof(rr), (fsd->currev + 1) * sizeof(struct revrec))) {
402         flog(LOG_CRIT, "could not write new revision: %s", strerror(errno));
403         return(-1);
404     }
405     fsd->inotab = inotab;
406     return(++fsd->currev);
407 }
408
409 static int deldentry(struct vcfsdata *fsd, struct inode *ino, int di)
410 {
411     if((di < 0) || (di >= ino->size)) {
412         errno = ERANGE;
413         return(-1);
414     }
415     
416 }
417
418 static int setdentry(struct vcfsdata *fsd, struct inode *ino, int di, const char *name, vc_ino_t target)
419 {
420     struct dentry dent;
421     ssize_t sz;
422     
423     if(strlen(name) > 255) {
424         errno = ENAMETOOLONG;
425         return(-1);
426     }
427     memset(&dent, 0, sizeof(dent));
428     strcpy(dent.name, name);
429     dent.inode = target;
430     sz = sizeof(dent) - sizeof(dent.name) + strlen(name) + 1;
431     if((di == -1) || (di == ino->size)) {
432         if(btput(fsd->st, &ino->data, ino->size, &dent, sz))
433             return(-1);
434         ino->size++;
435         return(0);
436     }
437     return(btput(fsd->st, &ino->data, di, &dent, sz));
438 }
439
440 static void fusemkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
441 {
442     struct vcfsdata *fsd;
443     struct inoc *inoc;
444     struct inode file, new;
445     struct btnode inotab;
446     struct fuse_entry_param e;
447     const struct fuse_ctx *ctx;
448     struct btop ops[2];
449     
450     fsd = fuse_req_userdata(req);
451     ctx = fuse_req_ctx(req);
452     if((inoc = getinocbf(fsd, parent)) == NULL) {
453         fuse_reply_err(req, ENOENT);
454         return;
455     }
456     if(inoc->inotab.d != 0) {
457         fuse_reply_err(req, EROFS);
458         return;
459     }
460     if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
461         fuse_reply_err(req, errno);
462         return;
463     }
464     if(!S_ISDIR(file.mode)) {
465         fuse_reply_err(req, ENOTDIR);
466         return;
467     }
468     if(dirlookup(fsd, &file.data, name, NULL) != -1) {
469         fuse_reply_err(req, EEXIST);
470         return;
471     }
472     
473     memset(&new, 0, sizeof(new));
474     new.mode = S_IFDIR | mode;
475     new.mtime = new.ctime = time(NULL);
476     new.size = 0;
477     new.uid = ctx->uid;
478     new.gid = ctx->gid;
479     new.links = 2;
480     if(setdentry(fsd, &new, -1, ".", fsd->nextino) || setdentry(fsd, &new, -1, "..", inoc->inode)) {
481         fuse_reply_err(req, errno);
482         return;
483     }
484     
485     inotab = fsd->inotab;
486     if(setdentry(fsd, &file, -1, name, fsd->nextino)) {
487         fuse_reply_err(req, errno);
488         return;
489     }
490     file.links++;
491     btmkop(ops + 0, inoc->inode, &file, sizeof(file));
492     btmkop(ops + 1, fsd->nextino, &new, sizeof(new));
493     if(btputmany(fsd->st, &inotab, ops, 2)) {
494         fuse_reply_err(req, errno);
495         return;
496     }
497     /*
498     if(btput(fsd->st, &inotab, fsd->nextino, &new, sizeof(new))) {
499         fuse_reply_err(req, errno);
500         return;
501     }
502     if(btput(fsd->st, &inotab, inoc->inode, &file, sizeof(file))) {
503         fuse_reply_err(req, errno);
504         return;
505     }
506     */
507     commit(fsd, inotab);
508     
509     memset(&e, 0, sizeof(e));
510     e.ino = cacheinode(fsd, fsd->nextino++, nilnode);
511     fillstat(&e.attr, &new);
512     fuse_reply_entry(req, &e);
513 }
514
515 static void fusermdir(fuse_req_t req, fuse_ino_t parent, const char *name)
516 {
517     struct vcfsdata *fsd;
518     struct inoc *inoc;
519     struct inode file;
520     int di;
521     
522     fsd = fuse_req_userdata(req);
523     if((inoc = getinocbf(fsd, parent)) == NULL) {
524         fuse_reply_err(req, ENOENT);
525         return;
526     }
527     if(inoc->inotab.d != 0) {
528         fuse_reply_err(req, EROFS);
529         return;
530     }
531     if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
532         fuse_reply_err(req, errno);
533         return;
534     }
535     if(!S_ISDIR(file.mode)) {
536         fuse_reply_err(req, ENOTDIR);
537         return;
538     }
539     if(dirlookup(fsd, &file.data, name, &di) == -1) {
540         fuse_reply_err(req, ENOENT);
541         return;
542     }
543 }
544
545 static struct fuse_lowlevel_ops fuseops = {
546     .destroy = (void (*)(void *))fusedestroy,
547     .lookup = fuselookup,
548     .getattr = fusegetattr,
549     .readdir = fusereaddir,
550     .mkdir = fusemkdir,
551     .rmdir = fusermdir,
552 };
553
554 int main(int argc, char **argv)
555 {
556     struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
557     struct fuse_session *fs;
558     struct fuse_chan *ch;
559     struct vcfsdata *fsd;
560     char *mtpt;
561     int err, fd;
562     
563     if((fsd = initvcfs(".")) == NULL)
564         exit(1);
565     if(fuse_parse_cmdline(&args, &mtpt, NULL, NULL) < 0)
566         exit(1);
567     if((fd = fuse_mount(mtpt, &args)) < 0)
568         exit(1);
569     if((fs = fuse_lowlevel_new(&args, &fuseops, sizeof(fuseops), fsd)) == NULL) {
570         fuse_unmount(mtpt, fd);
571         close(fd);
572         fprintf(stderr, "vcfs: could not initialize fuse\n");
573         exit(1);
574     }
575     fuse_set_signal_handlers(fs);
576     if((ch = fuse_kern_chan_new(fd)) == NULL) {
577         fuse_remove_signal_handlers(fs);
578         fuse_unmount(mtpt, fd);
579         fuse_session_destroy(fs);
580         close(fd);
581         exit(1);
582     }
583     
584     fuse_session_add_chan(fs, ch);
585     err = fuse_session_loop(fs);
586     
587     fuse_remove_signal_handlers(fs);
588     fuse_unmount(mtpt, fd);
589     fuse_session_destroy(fs);
590     close(fd);
591     return(err?1:0);
592 }