Converted repo to Git.
[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, INOBLSIZE)) < 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), DIRBLSIZE)) < 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), INOBLSIZE)) < 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     sb.st_ino = ino;
311     fuse_reply_attr(req, &sb, 0);
312 }
313
314 static void fuselookup(fuse_req_t req, fuse_ino_t parent, const char *name)
315 {
316     struct vcfsdata *fsd;
317     struct inode file;
318     struct inoc *inoc;
319     struct fuse_entry_param e;
320     vc_ino_t target;
321     
322     fsd = fuse_req_userdata(req);
323     if((inoc = getinocbf(fsd, parent)) == NULL) {
324         fuse_reply_err(req, ENOENT);
325         return;
326     }
327     if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
328         fuse_reply_err(req, errno);
329         return;
330     }
331     if((target = dirlookup(fsd, &file.data, name, NULL)) < 0) {
332         fuse_reply_err(req, errno);
333         return;
334     }
335     if(getinode(fsd, inoc->inotab, target, &file)) {
336         fuse_reply_err(req, errno);
337         return;
338     }
339     memset(&e, 0, sizeof(e));
340     e.ino = cacheinode(fsd, target, inoc->inotab);
341     fillstat(&e.attr, &file);
342     e.attr.st_ino = e.ino;
343     fuse_reply_entry(req, &e);
344 }
345
346 static void fusereaddir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
347 {
348     struct vcfsdata *fsd;
349     struct inoc *inoc;
350     struct inode file;
351     struct dentry dent;
352     struct stat sb;
353     ssize_t sz, osz, bsz;
354     char *buf;
355     
356     fsd = fuse_req_userdata(req);
357     if((inoc = getinocbf(fsd, ino)) == NULL) {
358         fuse_reply_err(req, ENOENT);
359         return;
360     }
361     if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
362         fuse_reply_err(req, errno);
363         return;
364     }
365     bsz = 0;
366     buf = NULL;
367     while(bsz < size) {
368         memset(&dent, 0, sizeof(dent));
369         if((sz = btget(fsd->st, &file.data, off++, &dent, sizeof(dent), DIRBLSIZE)) < 0) {
370             if(errno == ERANGE) {
371                 if(buf != NULL)
372                     break;
373                 fuse_reply_buf(req, NULL, 0);
374                 return;
375             }
376             fuse_reply_err(req, errno);
377             if(buf != NULL)
378                 free(buf);
379             return;
380         }
381         if(dent.inode < 0)
382             continue;
383         osz = bsz;
384         bsz += fuse_add_direntry(req, NULL, 0, dent.name, NULL, 0);
385         if(bsz > size)
386             break;
387         buf = realloc(buf, bsz);
388         memset(&sb, 0, sizeof(sb));
389         sb.st_ino = cacheinode(fsd, dent.inode, inoc->inotab);
390         fuse_add_direntry(req, buf + osz, bsz - osz, dent.name, &sb, off);
391     }
392     fuse_reply_buf(req, buf, bsz);
393     if(buf != NULL)
394         free(buf);
395 }
396
397 static vc_rev_t commit(struct vcfsdata *fsd, struct btnode inotab)
398 {
399     struct revrec rr;
400     
401     rr.ct = time(NULL);
402     rr.root = inotab;
403     if(writeall(fsd->revfd, &rr, sizeof(rr), (fsd->currev + 1) * sizeof(struct revrec))) {
404         flog(LOG_CRIT, "could not write new revision: %s", strerror(errno));
405         return(-1);
406     }
407     fsd->inotab = inotab;
408     return(++fsd->currev);
409 }
410
411 static int deldentry(struct vcfsdata *fsd, struct inode *ino, int di)
412 {
413     struct btop ops[2];
414     struct dentry dent;
415     ssize_t sz;
416     
417     if((di < 0) || (di >= ino->size)) {
418         errno = ERANGE;
419         return(-1);
420     }
421     if(di == ino->size - 1) {
422         if(btput(fsd->st, &ino->data, ino->size - 1, NULL, 0, DIRBLSIZE))
423             return(-1);
424     } else {
425         if((sz = btget(fsd->st, &ino->data, ino->size - 1, &dent, sizeof(dent), DIRBLSIZE)) < 0)
426             return(-1);
427         btmkop(ops + 0, di, &dent, sz);
428         btmkop(ops + 1, ino->size - 1, NULL, 0);
429         if(btputmany(fsd->st, &ino->data, ops, 2, DIRBLSIZE))
430             return(-1);
431     }
432     ino->size--;
433     return(0);
434 }
435
436 static int setdentry(struct vcfsdata *fsd, struct inode *ino, int di, const char *name, vc_ino_t target)
437 {
438     struct dentry dent;
439     ssize_t sz;
440     
441     if(strlen(name) > 255) {
442         errno = ENAMETOOLONG;
443         return(-1);
444     }
445     memset(&dent, 0, sizeof(dent));
446     strcpy(dent.name, name);
447     dent.inode = target;
448     sz = sizeof(dent) - sizeof(dent.name) + strlen(name) + 1;
449     if((di == -1) || (di == ino->size)) {
450         if(btput(fsd->st, &ino->data, ino->size, &dent, sz, DIRBLSIZE))
451             return(-1);
452         ino->size++;
453         return(0);
454     }
455     return(btput(fsd->st, &ino->data, di, &dent, sz, DIRBLSIZE));
456 }
457
458 static void fusemkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
459 {
460     struct vcfsdata *fsd;
461     struct inoc *inoc;
462     struct inode file, new;
463     struct btnode inotab;
464     struct fuse_entry_param e;
465     const struct fuse_ctx *ctx;
466     struct btop ops[2];
467     
468     fsd = fuse_req_userdata(req);
469     ctx = fuse_req_ctx(req);
470     if((inoc = getinocbf(fsd, parent)) == NULL) {
471         fuse_reply_err(req, ENOENT);
472         return;
473     }
474     if(inoc->inotab.d != 0) {
475         fuse_reply_err(req, EROFS);
476         return;
477     }
478     if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
479         fuse_reply_err(req, errno);
480         return;
481     }
482     if(!S_ISDIR(file.mode)) {
483         fuse_reply_err(req, ENOTDIR);
484         return;
485     }
486     if(dirlookup(fsd, &file.data, name, NULL) != -1) {
487         fuse_reply_err(req, EEXIST);
488         return;
489     }
490     
491     memset(&new, 0, sizeof(new));
492     new.mode = S_IFDIR | mode;
493     new.mtime = new.ctime = time(NULL);
494     new.size = 0;
495     new.uid = ctx->uid;
496     new.gid = ctx->gid;
497     new.links = 2;
498     if(setdentry(fsd, &new, -1, ".", fsd->nextino) || setdentry(fsd, &new, -1, "..", inoc->inode)) {
499         fuse_reply_err(req, errno);
500         return;
501     }
502     
503     inotab = fsd->inotab;
504     if(setdentry(fsd, &file, -1, name, fsd->nextino)) {
505         fuse_reply_err(req, errno);
506         return;
507     }
508     file.links++;
509     btmkop(ops + 0, inoc->inode, &file, sizeof(file));
510     btmkop(ops + 1, fsd->nextino, &new, sizeof(new));
511     if(btputmany(fsd->st, &inotab, ops, 2, INOBLSIZE)) {
512         fuse_reply_err(req, errno);
513         return;
514     }
515     /*
516     if(btput(fsd->st, &inotab, fsd->nextino, &new, sizeof(new))) {
517         fuse_reply_err(req, errno);
518         return;
519     }
520     if(btput(fsd->st, &inotab, inoc->inode, &file, sizeof(file))) {
521         fuse_reply_err(req, errno);
522         return;
523     }
524     */
525     commit(fsd, inotab);
526     
527     memset(&e, 0, sizeof(e));
528     e.ino = cacheinode(fsd, fsd->nextino++, nilnode);
529     fillstat(&e.attr, &new);
530     e.attr.st_ino = e.ino;
531     fuse_reply_entry(req, &e);
532 }
533
534 static void fuseunlink(fuse_req_t req, fuse_ino_t parent, const char *name)
535 {
536     struct vcfsdata *fsd;
537     struct inoc *inoc;
538     struct inode file;
539     int di;
540     struct btnode inotab;
541     
542     fsd = fuse_req_userdata(req);
543     if((inoc = getinocbf(fsd, parent)) == NULL) {
544         fuse_reply_err(req, ENOENT);
545         return;
546     }
547     if(inoc->inotab.d != 0) {
548         fuse_reply_err(req, EROFS);
549         return;
550     }
551     if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
552         fuse_reply_err(req, errno);
553         return;
554     }
555     if(!S_ISDIR(file.mode)) {
556         fuse_reply_err(req, ENOTDIR);
557         return;
558     }
559     if(dirlookup(fsd, &file.data, name, &di) == -1) {
560         fuse_reply_err(req, ENOENT);
561         return;
562     }
563     inotab = fsd->inotab;
564     if(deldentry(fsd, &file, di)) {
565         fuse_reply_err(req, errno);
566         return;
567     }
568     if(btput(fsd->st, &inotab, inoc->inode, &file, sizeof(file), INOBLSIZE)) {
569         fuse_reply_err(req, errno);
570         return;
571     }
572     commit(fsd, inotab);
573     fuse_reply_err(req, 0);
574 }
575
576 static struct fuse_lowlevel_ops fuseops = {
577     .destroy = (void (*)(void *))fusedestroy,
578     .lookup = fuselookup,
579     .getattr = fusegetattr,
580     .readdir = fusereaddir,
581     .mkdir = fusemkdir,
582     .rmdir = fuseunlink,
583     .unlink = fuseunlink,
584 };
585
586 int main(int argc, char **argv)
587 {
588     struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
589     struct fuse_session *fs;
590     struct fuse_chan *ch;
591     struct vcfsdata *fsd;
592     char *mtpt;
593     int err, fd;
594     
595     if((fsd = initvcfs(".")) == NULL)
596         exit(1);
597     if(fuse_parse_cmdline(&args, &mtpt, NULL, NULL) < 0)
598         exit(1);
599     if((fd = fuse_mount(mtpt, &args)) < 0)
600         exit(1);
601     if((fs = fuse_lowlevel_new(&args, &fuseops, sizeof(fuseops), fsd)) == NULL) {
602         fuse_unmount(mtpt, fd);
603         close(fd);
604         fprintf(stderr, "vcfs: could not initialize fuse\n");
605         exit(1);
606     }
607     fuse_set_signal_handlers(fs);
608     if((ch = fuse_kern_chan_new(fd)) == NULL) {
609         fuse_remove_signal_handlers(fs);
610         fuse_unmount(mtpt, fd);
611         fuse_session_destroy(fs);
612         close(fd);
613         exit(1);
614     }
615     
616     fuse_session_add_chan(fs, ch);
617     err = fuse_session_loop(fs);
618     
619     fuse_remove_signal_handlers(fs);
620     fuse_unmount(mtpt, fd);
621     fuse_session_destroy(fs);
622     close(fd);
623     return(err?1:0);
624 }