build: Rebuild .config.mk.autogen without initial warning
[navymail.git] / fs.c
blobcda3f644cc00f7c5b6f0d3d0e16fba172d048a25
1 /* filesystem interface to navymail store
2 * Copyright (C) 2011 Kirill Smelkov <kirr@navytux.spb.ru>
4 * This program is free software: you can Use, Study, Modify and Redistribute it
5 * under the terms of the GNU General Public License version 2. This program is
6 * distributed WITHOUT ANY WARRANTY. See COPYING file for full License terms.
9 * XXX what about threading/locking?
12 #include "git-compat-util.h"
13 #include "parse-options.h"
14 #include "argv-array.h"
15 #include "refs.h"
16 #include "navymail/navymail.h"
17 #include "navymail/gbox-walk.h"
19 #define FUSE_USE_VERSION 26
20 #include <fuse.h>
22 static const char * const mount_usage[] = {
23 "navymail mount <mountpoint> [-- fuse-options]",
24 NULL
27 static const struct option mount_options[] = {
28 OPT_END()
33 /* just tell for_each_ref* caller, we were here, i.e. there were non-zero refs
34 * iterated */
35 static int __refs_nonempty(const char *refname, const unsigned char *sha1,
36 int flags, void *cb_data)
38 return 1;
41 static int navymailfs_getattr(const char *path, struct stat *st)
43 int ret = 0;
45 memset(st, 0, sizeof(*st));
47 /* XXX dumb */
48 /* XXX what about st_nlink?
49 * XXX what about st_uid/st_gid, st_{a,m,c}time ?
51 if (!strcmp(path, "/")) {
52 st->st_mode = S_IFDIR | 0755;
53 /* st->st_nlink = 2; */
55 else if (!strcmp(path, "/mail")) {
56 st->st_mode = S_IFDIR | 0755;
57 /* st_nlink */
59 else if (!prefixcmp(path, "/mail/")) {
60 struct strbuf refname = STRBUF_INIT;
61 strbuf_addf(&refname, "refs%s", path);
63 /* XXX what to do if there are e.g.
65 * refs/mail/x and refs/mail/x/y
67 * ? i.e. here x is both file and directory - Git allows such refs...
70 /* a file? */
71 if (ref_exists(refname.buf)) {
72 st->st_mode = S_IFREG | 0444;
73 /* don't waste resources computing st_size. but then
74 * this needs direct_io=1 for file to be readable */
75 st->st_size = 0;
78 /* it could be a dir still */
79 else {
80 /* ensure there is / at tail */
81 if (refname.buf[refname.len - 1] != '/')
82 strbuf_addch(&refname, '/');
84 /* read refs with refname as prefix -- if there are
85 * some, then refname is a dir */
86 if (for_each_ref_in(refname.buf, __refs_nonempty, NULL)) {
87 st->st_mode = S_IFDIR | 0755;
88 /* st_nlink */
90 else
91 ret = -ENOENT;
94 strbuf_release(&refname);
96 else
97 ret = -ENOENT;
99 return ret;
103 struct readdir_ctx {
104 void *buf;
105 fuse_fill_dir_t filler;
107 /* previous added entry. empty if no entries were added yet */
108 struct strbuf prev_entry;
111 static int __readdir_handle_ref(const char *refname, const unsigned char *sha1,
112 int flags, void *cb_data)
114 struct readdir_ctx *ctx = (struct readdir_ctx *)cb_data;
115 struct strbuf entry = STRBUF_INIT;
116 const char *subdir_mark;
118 /* extract entry-name from this refname - a file or a subdirectory
119 * (potentially from sub-sub-sub...directory */
120 if ((subdir_mark = strchr(refname, '/')))
121 strbuf_add(&entry, refname, subdir_mark-refname);
122 else
123 strbuf_addf(&entry, "%s", refname);
125 /* refs come here in sorted order, so we can avoid duplicates simply
126 * checking against previous entry */
127 if (!strbuf_cmp(&entry, &ctx->prev_entry))
128 goto out;
130 /* now we know this dir is not empty - emit . and .. before first entry */
131 if (!ctx->prev_entry.len) {
132 ctx->filler(ctx->buf, ".", NULL, 0);
133 ctx->filler(ctx->buf, "..", NULL, 0);
136 /* ctx->prev_entry = entry */
137 strbuf_reset(&ctx->prev_entry);
138 strbuf_addbuf(&ctx->prev_entry, &entry);
140 /* emit entry */
141 ctx->filler(ctx->buf, entry.buf, NULL, 0);
143 out:
144 strbuf_release(&entry);
145 return 0;
148 static int navymailfs_readdir(const char *path, void *buf, fuse_fill_dir_t
149 filler, off_t offset, struct fuse_file_info *fi)
151 int ret = 0;
152 struct readdir_ctx ctx = { .buf = buf, .filler = filler, .prev_entry = STRBUF_INIT };
154 (void) offset;
155 (void) fi;
158 /* XXX dumb */
159 if (!strcmp(path, "/")) {
160 filler(buf, ".", NULL, 0);
161 filler(buf, "..", NULL, 0);
162 filler(buf, "mail", NULL, 0);
164 else if (!strcmp(path, "/mail") || !prefixcmp(path, "/mail/")) {
165 struct strbuf refprefix = STRBUF_INIT;
166 strbuf_addf(&refprefix, "refs%s", path);
167 if (refprefix.buf[refprefix.len - 1] != '/')
168 strbuf_addch(&refprefix, '/'); /* ensure there is / at tail */
170 /* read refs from refprefix (e.g. /refs/mail/dir/) */
171 for_each_ref_in(refprefix.buf, __readdir_handle_ref, &ctx);
173 if (!ctx.prev_entry.len)
174 ret = -ENOENT; /* no entries for refprefix */
176 strbuf_release(&refprefix);
178 else
179 ret = -ENOENT;
182 strbuf_release(&ctx.prev_entry);
183 return ret;
189 /* index for looking up messages in gbox by offset */
190 struct msgmap_index {
191 off_t *sumsize; /* Si = sum_{j=0..i} size(msg_j) */
192 unsigned char (*sha1)[20]; /* sha1_i */
194 int nr;
195 int alloc;
198 struct gbox_ctx {
199 struct strbuf refname;
200 struct gbox_walk gbox_walk;
201 struct msgmap_index msgmap;
203 /* last read msg */
204 char *msg_buf;
205 unsigned long msg_size;
206 int msg_idx; /* index in msgmap */
209 static int navymailfs_open(const char *path, struct fuse_file_info *fi)
211 int ret = 0;
212 struct strbuf refname = STRBUF_INIT;
213 struct gbox_ctx *ctx = NULL;
215 /* XXX dumb */
216 if (!!prefixcmp(path, "/mail/")) {
217 ret = -ENOENT;
218 goto out;
221 strbuf_addf(&refname, "refs%s", path);
223 if (!ref_exists(refname.buf)) {
224 ret = -ENOENT;
225 goto out;
228 if ((fi->flags & (O_RDONLY | O_WRONLY | O_RDWR)) != O_RDONLY) {
229 ret = -ENOENT;
230 goto out;
234 * direct_io is used so that files with st_size=0 could be read
235 * XXX how this affects performance?
236 * XXX what about fi->keep_cache ?
238 fi->direct_io = 1;
240 ctx = xcalloc(1, sizeof(*ctx));
241 strbuf_init(&ctx->refname, 0);
242 strbuf_addbuf(&ctx->refname, &refname);
243 ctx->gbox_walk.gbox = ctx->refname.buf;
244 ctx->gbox_walk.original_order = 1;
245 prepare_gbox_walk(&ctx->gbox_walk);
247 ctx->msg_idx = -1;
249 fi->fh = (intptr_t)ctx;
251 out:
252 strbuf_release(&refname);
253 return ret;
257 static int navymailfs_release(const char *path, struct fuse_file_info *fi)
259 struct gbox_ctx *ctx = (struct gbox_ctx *)(intptr_t)fi->fh;
261 strbuf_release(&ctx->refname);
262 free(ctx->msgmap.sumsize);
263 free(ctx->msgmap.sha1);
265 if (ctx->msg_idx != -1) {
266 free(ctx->msg_buf);
267 ctx->msg_buf = NULL;
268 ctx->msg_size = 0;
269 ctx->msg_idx = -1;
272 free(ctx);
274 return 0;
278 /* compute a*b/c correctly handling overflow in a*b
280 * b = q*c+r -> a*b/c = a*q + a*r/c
282 static intmax_t imaxmuldiv(intmax_t a, intmax_t b, intmax_t c)
284 imaxdiv_t _;
286 _ = imaxdiv(b, c);
287 return a*_.quot + a*_.rem/c;
291 #define DEBUG_LOOKUP 0
293 #if DEBUG_LOOKUP
294 # define TRACE_LOOKUP(fmt...) do { fprintf(stderr, fmt); } while (0)
295 #else
296 # define TRACE_LOOKUP(fmt...) do {} while (0)
297 #endif
300 static int navymailfs_read(const char *path, char *buf, size_t size, off_t
301 offset, struct fuse_file_info *fi)
303 struct gbox_ctx *ctx = (struct gbox_ctx *)(intptr_t)fi->fh;
305 int lo, hi, i, alloc;
306 off_t Si, Si_1, Slo_, Shi;
307 const unsigned char *sha1;
308 enum object_type type;
309 const char *read_start;
310 int read_size, read_total;
312 /* reading optimized for linear reads */
314 TRACE_LOOKUP("READ %s\toffset: %llu\tsize: %u\n", path, offset, size);
317 /* 1) lookup already-seen msg overlapping with offset.
318 * after this phase, either
320 * - i indicates appropriate msg, or
321 * - i points just-after msgmap tail (i.e. we need to fetch next msgs)
324 * ~~~~
325 * some reminders before we begin:
327 * Si = sum_{j=0..i} size(msg_j)
329 * offset < Si <=> offset is in [0,i] (1)
330 * offset >= Si <=> offset is in [i+1,∞] (2)
332 * in particular
334 * Si_1 <= offset < Si <=> offset is in i'th msg (3)
336 lo = 0;
337 hi = ctx->msgmap.nr - 1;
339 /* if we are at tail - there is no chance to find appropriate chunk -
340 * we'll have to skip search and start reading next msgs (see 2)
342 if (hi==-1 || offset >= ctx->msgmap.sumsize[hi])
343 i = hi+1;
345 else {
346 /* now we know offset is somewhere in msgmap;
347 * start searching at last read */
348 i = ctx->msg_idx != -1 ? ctx->msg_idx : hi;
350 while (1) {
351 Si = ctx->msgmap.sumsize[i];
352 Si_1 = i >= 1 ? ctx->msgmap.sumsize[i-1] : 0;
354 TRACE_LOOKUP("\tlo: %i\thi: %i\ti: %i\t Si: %llu\tSi_1: %llu\n", lo, hi, i, Si, Si_1);
357 if (offset < Si) {
358 if (offset >= Si_1)
359 break; /* found, see (3) */
361 /* offset < Si_1 , so because of (1) */
362 hi = i-1;
364 else
365 /* offset >= Si , see (2) */
366 lo = i+1;
369 /* now we know offset is somewhere in [lo,hi];
370 * estimate new i, assuming messages are of approximately equal size.
372 * ~~~~
373 * we have:
375 * offset
377 * lo v hi
378 * |----|----|-- ... --|----|----|
379 * Olo< Ohi>
381 * Olo< = Slo_1
382 * Ohi> = Shi - 1
384 * Olo< <= offset <= Ohi> (4) ,i.e.
385 * Slo_1 <= offset <= Shi - 1 (5)
387 * then new guess, assuming uniformity would be as follows:
389 * offset - Olo< i - lo
390 * ------------- = ------- (6)
391 * Ohi> - Olo< hi - lo
393 * which gives
395 * offset - Slo_1
396 * i = lo + (hi - lo)*----------------- (7)
397 * Shi - Slo_1 - 1
399 * the ratio multiplier for (hi - lo) is in [0,1]
400 * because of (5), so new i should be always in [lo,hi].
402 * The process should converge regardless of sizes
403 * distribution, because at each step we tighten lo or
404 * high by 1 in the above block.
406 * XXX maybe detect pathological cases, and do plain
407 * binary search then?
410 Slo_ = lo >= 1 ? ctx->msgmap.sumsize[lo-1] : 0;
411 Shi = ctx->msgmap.sumsize[hi];
413 /* compute
415 * i = lo + (offset - Slo_)*(hi-lo)/(Shi-Slo_-1);
417 * but avoid overflow on multiply
419 i = lo + imaxmuldiv( (offset - Slo_),(hi-lo),(Shi-Slo_-1) );
424 /* 2) now linearly read messages, until all read request is done
425 * NOTE: we'll maybe have to first skip some msgs, if offset >= Si (see 2)
427 read_total = 0;
429 for (;size != 0; ++i) {
430 /* read i'th msg */
431 if (i != ctx->msg_idx) {
432 if (ctx->msg_idx != -1) {
433 free(ctx->msg_buf);
434 ctx->msg_buf = NULL;
435 ctx->msg_size = 0;
436 ctx->msg_idx = -1;
439 if (i == ctx->msgmap.nr) {
440 /* fetch next msg from gbox */
441 sha1 = next_gbox_entry(&ctx->gbox_walk);
442 if (!sha1)
443 break; /* EOF */
445 else
446 sha1 = ctx->msgmap.sha1[i];
448 ctx->msg_buf = read_sha1_file(sha1, &type, &ctx->msg_size);
449 if (!ctx->msg_buf)
450 die("read_sha1_file(%s) -> NULL", sha1_to_hex(sha1));
451 if (type != OBJ_BLOB)
452 die("%s is not a blob", sha1_to_hex(sha1));
455 if (i == ctx->msgmap.nr) {
456 alloc = ctx->msgmap.alloc;
457 ALLOC_GROW(ctx->msgmap.sumsize, ctx->msgmap.nr +1, alloc);
458 ALLOC_GROW(ctx->msgmap.sha1, ctx->msgmap.nr +1, ctx->msgmap.alloc);
460 ctx->msgmap.nr += 1;
461 ctx->msgmap.sumsize[i] = (i > 0 ? ctx->msgmap.sumsize[i-1] : 0)
462 + ctx->msg_size;
463 hashcpy(ctx->msgmap.sha1[i], sha1);
466 ctx->msg_idx = i;
469 Si = ctx->msgmap.sumsize[i];
470 Si_1 = i >= 1 ? ctx->msgmap.sumsize[i-1] : 0;
473 /* i'th msg overlaps - use it */
474 if (offset < Si) {
475 read_start = ctx->msg_buf + (offset - Si_1);
476 read_size = ctx->msg_size - (offset - Si_1);
477 read_size = MIN(read_size, size);
479 memcpy(buf, read_start, read_size);
480 buf += read_size;
481 size -= read_size;
482 offset += read_size;
483 read_total += read_size;
488 return read_total;
491 static struct fuse_operations navymailfs_ops = {
492 .getattr = navymailfs_getattr,
494 #if 0
495 .opendir =
496 .releasedir =
497 #endif
498 .readdir = navymailfs_readdir,
500 .open = navymailfs_open,
501 .release = navymailfs_release,
502 .read = navymailfs_read,
507 int cmd_mount(int argc, const char **argv, const char *prefix)
509 int i, ret;
510 struct argv_array argvf;
512 argc = parse_options(argc, argv, NULL /*prefix?*/, mount_options, mount_usage, 0);
514 /* git_config(git_default_config, NULL); ? */
516 if (argc < 1)
517 usage_with_options(mount_usage, mount_options);
519 /* XXX only absolute NAVYMAIL_DIR path is ok, because when
520 * daemonizing, fuse does chdir("/"). TODO make this automatically happen */
521 if (!is_absolute_path(getenv(NAVYMAIL_DIR_ENVIRONMENT)))
522 die("mount: TODO atm navymail-dir has to be absolute-path");
524 /* tail to fuse main loop */
525 argv_array_init(&argvf);
526 argv_array_push(&argvf, "navymail-mount");
527 for (i=0; i<argc; ++i)
528 argv_array_push(&argvf, argv[i]);
530 /* TODO hook fuse_session_exit to die (see exit_handler() in fuse_signals.c) */
531 ret = fuse_main(argvf.argc, /* XXX can't we avoid cast? */(char **)argvf.argv, &navymailfs_ops, NULL);
533 argv_array_clear(&argvf);
534 return ret;