Fill st_ino in stat replies.
[vcfs.git] / vcfs.c
... / ...
CommitLineData
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, 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
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), 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
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), 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
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 sb.st_ino = ino;
311 fuse_reply_attr(req, &sb, 0);
312}
313
314static 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
346static 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
397static 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
411static 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
436static 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
458static 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
534static 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
576static 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
586int 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}