Bleh
[vcfs.git] / vcfs.c
CommitLineData
d5cf5351 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
19struct btree {
20 struct btree *l, *r;
21 int h;
22 void *d;
23};
24
25struct inoc {
26 vc_ino_t inode;
27 struct btnode inotab;
28 fuse_ino_t cnum;
29};
30
31struct 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))
42static struct btnode nilnode = {0, };
43
44static int btheight(struct btree *tree)
45{
46 if(tree == NULL)
47 return(0);
48 return(tree->h);
49}
50
51static 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
58static void bbtrl(struct btree **tree);
59
60static 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
76static 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
92static 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
116static 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
133static void dstrvcfs(struct vcfsdata *fsd)
134{
135 releasestore(fsd->st);
136 fsync(fsd->revfd);
137 close(fsd->revfd);
138 free(fsd);
139}
140
141static int inoccmpbf(struct inoc *a, struct inoc *b)
142{
143 return(a->cnum - b->cnum);
144}
145
146static 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
159static 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
167static 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
176static 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
193static 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);
34b0b353 229 if((fsd->nextino = btcount(fsd->st, &fsd->inotab, INOBLSIZE)) < 0) {
d5cf5351 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
239static 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++) {
34b0b353 246 if((sz = btget(fsd->st, dirdata, i, &dent, sizeof(dent), DIRBLSIZE)) < 0) {
d5cf5351 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
259static void fusedestroy(struct vcfsdata *fsd)
260{
261 dstrvcfs(fsd);
262}
263
264static 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
276static 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;
34b0b353 282 if((sz = btget(fsd->st, &inotab, ino, buf, sizeof(*buf), INOBLSIZE)) < 0)
d5cf5351 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
292static 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
313static 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
344static 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));
34b0b353 367 if((sz = btget(fsd->st, &file.data, off++, &dent, sizeof(dent), DIRBLSIZE)) < 0) {
d5cf5351 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
395static 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
409static int deldentry(struct vcfsdata *fsd, struct inode *ino, int di)
410{
ea7aa9de 411 struct btop ops[2];
412 struct dentry dent;
413 ssize_t sz;
414
d5cf5351 415 if((di < 0) || (di >= ino->size)) {
416 errno = ERANGE;
417 return(-1);
418 }
ea7aa9de 419 if(di == ino->size - 1) {
34b0b353 420 if(btput(fsd->st, &ino->data, ino->size - 1, NULL, 0, DIRBLSIZE))
ea7aa9de 421 return(-1);
422 } else {
34b0b353 423 if((sz = btget(fsd->st, &ino->data, ino->size - 1, &dent, sizeof(dent), DIRBLSIZE)) < 0)
ea7aa9de 424 return(-1);
425 btmkop(ops + 0, di, &dent, sz);
426 btmkop(ops + 1, ino->size - 1, NULL, 0);
34b0b353 427 if(btputmany(fsd->st, &ino->data, ops, 2, DIRBLSIZE))
ea7aa9de 428 return(-1);
429 }
34b0b353 430 ino->size--;
ea7aa9de 431 return(0);
d5cf5351 432}
433
434static int setdentry(struct vcfsdata *fsd, struct inode *ino, int di, const char *name, vc_ino_t target)
435{
436 struct dentry dent;
437 ssize_t sz;
438
439 if(strlen(name) > 255) {
440 errno = ENAMETOOLONG;
441 return(-1);
442 }
443 memset(&dent, 0, sizeof(dent));
444 strcpy(dent.name, name);
445 dent.inode = target;
446 sz = sizeof(dent) - sizeof(dent.name) + strlen(name) + 1;
447 if((di == -1) || (di == ino->size)) {
34b0b353 448 if(btput(fsd->st, &ino->data, ino->size, &dent, sz, DIRBLSIZE))
d5cf5351 449 return(-1);
450 ino->size++;
451 return(0);
452 }
34b0b353 453 return(btput(fsd->st, &ino->data, di, &dent, sz, DIRBLSIZE));
d5cf5351 454}
455
456static void fusemkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
457{
458 struct vcfsdata *fsd;
459 struct inoc *inoc;
460 struct inode file, new;
461 struct btnode inotab;
462 struct fuse_entry_param e;
463 const struct fuse_ctx *ctx;
464 struct btop ops[2];
465
466 fsd = fuse_req_userdata(req);
467 ctx = fuse_req_ctx(req);
468 if((inoc = getinocbf(fsd, parent)) == NULL) {
469 fuse_reply_err(req, ENOENT);
470 return;
471 }
472 if(inoc->inotab.d != 0) {
473 fuse_reply_err(req, EROFS);
474 return;
475 }
476 if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
477 fuse_reply_err(req, errno);
478 return;
479 }
480 if(!S_ISDIR(file.mode)) {
481 fuse_reply_err(req, ENOTDIR);
482 return;
483 }
484 if(dirlookup(fsd, &file.data, name, NULL) != -1) {
485 fuse_reply_err(req, EEXIST);
486 return;
487 }
488
489 memset(&new, 0, sizeof(new));
490 new.mode = S_IFDIR | mode;
491 new.mtime = new.ctime = time(NULL);
492 new.size = 0;
493 new.uid = ctx->uid;
494 new.gid = ctx->gid;
495 new.links = 2;
496 if(setdentry(fsd, &new, -1, ".", fsd->nextino) || setdentry(fsd, &new, -1, "..", inoc->inode)) {
497 fuse_reply_err(req, errno);
498 return;
499 }
500
501 inotab = fsd->inotab;
502 if(setdentry(fsd, &file, -1, name, fsd->nextino)) {
503 fuse_reply_err(req, errno);
504 return;
505 }
506 file.links++;
507 btmkop(ops + 0, inoc->inode, &file, sizeof(file));
508 btmkop(ops + 1, fsd->nextino, &new, sizeof(new));
34b0b353 509 if(btputmany(fsd->st, &inotab, ops, 2, INOBLSIZE)) {
d5cf5351 510 fuse_reply_err(req, errno);
511 return;
512 }
513 /*
514 if(btput(fsd->st, &inotab, fsd->nextino, &new, sizeof(new))) {
515 fuse_reply_err(req, errno);
516 return;
517 }
518 if(btput(fsd->st, &inotab, inoc->inode, &file, sizeof(file))) {
519 fuse_reply_err(req, errno);
520 return;
521 }
522 */
523 commit(fsd, inotab);
524
525 memset(&e, 0, sizeof(e));
526 e.ino = cacheinode(fsd, fsd->nextino++, nilnode);
527 fillstat(&e.attr, &new);
528 fuse_reply_entry(req, &e);
529}
530
ea7aa9de 531static void fuseunlink(fuse_req_t req, fuse_ino_t parent, const char *name)
d5cf5351 532{
533 struct vcfsdata *fsd;
534 struct inoc *inoc;
535 struct inode file;
536 int di;
ea7aa9de 537 struct btnode inotab;
d5cf5351 538
539 fsd = fuse_req_userdata(req);
540 if((inoc = getinocbf(fsd, parent)) == NULL) {
541 fuse_reply_err(req, ENOENT);
542 return;
543 }
544 if(inoc->inotab.d != 0) {
545 fuse_reply_err(req, EROFS);
546 return;
547 }
548 if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
549 fuse_reply_err(req, errno);
550 return;
551 }
552 if(!S_ISDIR(file.mode)) {
553 fuse_reply_err(req, ENOTDIR);
554 return;
555 }
556 if(dirlookup(fsd, &file.data, name, &di) == -1) {
557 fuse_reply_err(req, ENOENT);
558 return;
559 }
ea7aa9de 560 inotab = fsd->inotab;
561 if(deldentry(fsd, &file, di)) {
562 fuse_reply_err(req, errno);
563 return;
564 }
34b0b353 565 if(btput(fsd->st, &inotab, inoc->inode, &file, sizeof(file), INOBLSIZE)) {
ea7aa9de 566 fuse_reply_err(req, errno);
567 return;
568 }
569 commit(fsd, inotab);
570 fuse_reply_err(req, 0);
d5cf5351 571}
572
573static struct fuse_lowlevel_ops fuseops = {
574 .destroy = (void (*)(void *))fusedestroy,
575 .lookup = fuselookup,
576 .getattr = fusegetattr,
577 .readdir = fusereaddir,
578 .mkdir = fusemkdir,
ea7aa9de 579 .rmdir = fuseunlink,
580 .unlink = fuseunlink,
d5cf5351 581};
582
583int main(int argc, char **argv)
584{
585 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
586 struct fuse_session *fs;
587 struct fuse_chan *ch;
588 struct vcfsdata *fsd;
589 char *mtpt;
590 int err, fd;
591
592 if((fsd = initvcfs(".")) == NULL)
593 exit(1);
594 if(fuse_parse_cmdline(&args, &mtpt, NULL, NULL) < 0)
595 exit(1);
596 if((fd = fuse_mount(mtpt, &args)) < 0)
597 exit(1);
598 if((fs = fuse_lowlevel_new(&args, &fuseops, sizeof(fuseops), fsd)) == NULL) {
599 fuse_unmount(mtpt, fd);
600 close(fd);
601 fprintf(stderr, "vcfs: could not initialize fuse\n");
602 exit(1);
603 }
604 fuse_set_signal_handlers(fs);
605 if((ch = fuse_kern_chan_new(fd)) == NULL) {
606 fuse_remove_signal_handlers(fs);
607 fuse_unmount(mtpt, fd);
608 fuse_session_destroy(fs);
609 close(fd);
610 exit(1);
611 }
612
613 fuse_session_add_chan(fs, ch);
614 err = fuse_session_loop(fs);
615
616 fuse_remove_signal_handlers(fs);
617 fuse_unmount(mtpt, fd);
618 fuse_session_destroy(fs);
619 close(fd);
620 return(err?1:0);
621}