Fix cacheput memmove bug.
[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     struct btop ops[2];
412     struct dentry dent;
413     ssize_t sz;
414     
415     if((di < 0) || (di >= ino->size)) {
416         errno = ERANGE;
417         return(-1);
418     }
419     if(di == ino->size - 1) {
420         if(btput(fsd->st, &ino->data, ino->size - 1, NULL, 0))
421             return(-1);
422     } else {
423         if((sz = btget(fsd->st, &ino->data, ino->size - 1, &dent, sizeof(dent))) < 0)
424             return(-1);
425         btmkop(ops + 0, di, &dent, sz);
426         btmkop(ops + 1, ino->size - 1, NULL, 0);
427         if(btputmany(fsd->st, &ino->data, ops, 2))
428             return(-1);
429     }
430     return(0);
431 }
432
433 static int setdentry(struct vcfsdata *fsd, struct inode *ino, int di, const char *name, vc_ino_t target)
434 {
435     struct dentry dent;
436     ssize_t sz;
437     
438     if(strlen(name) > 255) {
439         errno = ENAMETOOLONG;
440         return(-1);
441     }
442     memset(&dent, 0, sizeof(dent));
443     strcpy(dent.name, name);
444     dent.inode = target;
445     sz = sizeof(dent) - sizeof(dent.name) + strlen(name) + 1;
446     if((di == -1) || (di == ino->size)) {
447         if(btput(fsd->st, &ino->data, ino->size, &dent, sz))
448             return(-1);
449         ino->size++;
450         return(0);
451     }
452     return(btput(fsd->st, &ino->data, di, &dent, sz));
453 }
454
455 static void fusemkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
456 {
457     struct vcfsdata *fsd;
458     struct inoc *inoc;
459     struct inode file, new;
460     struct btnode inotab;
461     struct fuse_entry_param e;
462     const struct fuse_ctx *ctx;
463     struct btop ops[2];
464     
465     fsd = fuse_req_userdata(req);
466     ctx = fuse_req_ctx(req);
467     if((inoc = getinocbf(fsd, parent)) == NULL) {
468         fuse_reply_err(req, ENOENT);
469         return;
470     }
471     if(inoc->inotab.d != 0) {
472         fuse_reply_err(req, EROFS);
473         return;
474     }
475     if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
476         fuse_reply_err(req, errno);
477         return;
478     }
479     if(!S_ISDIR(file.mode)) {
480         fuse_reply_err(req, ENOTDIR);
481         return;
482     }
483     if(dirlookup(fsd, &file.data, name, NULL) != -1) {
484         fuse_reply_err(req, EEXIST);
485         return;
486     }
487     
488     memset(&new, 0, sizeof(new));
489     new.mode = S_IFDIR | mode;
490     new.mtime = new.ctime = time(NULL);
491     new.size = 0;
492     new.uid = ctx->uid;
493     new.gid = ctx->gid;
494     new.links = 2;
495     if(setdentry(fsd, &new, -1, ".", fsd->nextino) || setdentry(fsd, &new, -1, "..", inoc->inode)) {
496         fuse_reply_err(req, errno);
497         return;
498     }
499     
500     inotab = fsd->inotab;
501     if(setdentry(fsd, &file, -1, name, fsd->nextino)) {
502         fuse_reply_err(req, errno);
503         return;
504     }
505     file.links++;
506     btmkop(ops + 0, inoc->inode, &file, sizeof(file));
507     btmkop(ops + 1, fsd->nextino, &new, sizeof(new));
508     if(btputmany(fsd->st, &inotab, ops, 2)) {
509         fuse_reply_err(req, errno);
510         return;
511     }
512     /*
513     if(btput(fsd->st, &inotab, fsd->nextino, &new, sizeof(new))) {
514         fuse_reply_err(req, errno);
515         return;
516     }
517     if(btput(fsd->st, &inotab, inoc->inode, &file, sizeof(file))) {
518         fuse_reply_err(req, errno);
519         return;
520     }
521     */
522     commit(fsd, inotab);
523     
524     memset(&e, 0, sizeof(e));
525     e.ino = cacheinode(fsd, fsd->nextino++, nilnode);
526     fillstat(&e.attr, &new);
527     fuse_reply_entry(req, &e);
528 }
529
530 static void fuseunlink(fuse_req_t req, fuse_ino_t parent, const char *name)
531 {
532     struct vcfsdata *fsd;
533     struct inoc *inoc;
534     struct inode file;
535     int di;
536     struct btnode inotab;
537     
538     fsd = fuse_req_userdata(req);
539     if((inoc = getinocbf(fsd, parent)) == NULL) {
540         fuse_reply_err(req, ENOENT);
541         return;
542     }
543     if(inoc->inotab.d != 0) {
544         fuse_reply_err(req, EROFS);
545         return;
546     }
547     if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
548         fuse_reply_err(req, errno);
549         return;
550     }
551     if(!S_ISDIR(file.mode)) {
552         fuse_reply_err(req, ENOTDIR);
553         return;
554     }
555     if(dirlookup(fsd, &file.data, name, &di) == -1) {
556         fuse_reply_err(req, ENOENT);
557         return;
558     }
559     inotab = fsd->inotab;
560     if(deldentry(fsd, &file, di)) {
561         fuse_reply_err(req, errno);
562         return;
563     }
564     if(btput(fsd->st, &inotab, inoc->inode, &file, sizeof(file))) {
565         fuse_reply_err(req, errno);
566         return;
567     }
568     commit(fsd, inotab);
569     fuse_reply_err(req, 0);
570 }
571
572 static struct fuse_lowlevel_ops fuseops = {
573     .destroy = (void (*)(void *))fusedestroy,
574     .lookup = fuselookup,
575     .getattr = fusegetattr,
576     .readdir = fusereaddir,
577     .mkdir = fusemkdir,
578     .rmdir = fuseunlink,
579     .unlink = fuseunlink,
580 };
581
582 int main(int argc, char **argv)
583 {
584     struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
585     struct fuse_session *fs;
586     struct fuse_chan *ch;
587     struct vcfsdata *fsd;
588     char *mtpt;
589     int err, fd;
590     
591     if((fsd = initvcfs(".")) == NULL)
592         exit(1);
593     if(fuse_parse_cmdline(&args, &mtpt, NULL, NULL) < 0)
594         exit(1);
595     if((fd = fuse_mount(mtpt, &args)) < 0)
596         exit(1);
597     if((fs = fuse_lowlevel_new(&args, &fuseops, sizeof(fuseops), fsd)) == NULL) {
598         fuse_unmount(mtpt, fd);
599         close(fd);
600         fprintf(stderr, "vcfs: could not initialize fuse\n");
601         exit(1);
602     }
603     fuse_set_signal_handlers(fs);
604     if((ch = fuse_kern_chan_new(fd)) == NULL) {
605         fuse_remove_signal_handlers(fs);
606         fuse_unmount(mtpt, fd);
607         fuse_session_destroy(fs);
608         close(fd);
609         exit(1);
610     }
611     
612     fuse_session_add_chan(fs, ch);
613     err = fuse_session_loop(fs);
614     
615     fuse_remove_signal_handlers(fs);
616     fuse_unmount(mtpt, fd);
617     fuse_session_destroy(fs);
618     close(fd);
619     return(err?1:0);
620 }