Block delete function added (probably buggy).
[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);
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
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++) {
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
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;
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
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));
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
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{
411 if((di < 0) || (di >= ino->size)) {
412 errno = ERANGE;
413 return(-1);
414 }
415
416}
417
418static 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
440static 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
515static 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
545static 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
554int 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}