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 | |
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 | { |
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) { |
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); |
d5cf5351 |
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 | |
ea7aa9de |
530 | static void fuseunlink(fuse_req_t req, fuse_ino_t parent, const char *name) |
d5cf5351 |
531 | { |
532 | struct vcfsdata *fsd; |
533 | struct inoc *inoc; |
534 | struct inode file; |
535 | int di; |
ea7aa9de |
536 | struct btnode inotab; |
d5cf5351 |
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 | } |
ea7aa9de |
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); |
d5cf5351 |
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, |
ea7aa9de |
578 | .rmdir = fuseunlink, |
579 | .unlink = fuseunlink, |
d5cf5351 |
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 | } |