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"
16 #include "navymail/navymail.h"
17 #include "navymail/gbox-walk.h"
19 #define FUSE_USE_VERSION 26
22 static const char * const mount_usage
[] = {
23 "navymail mount <mountpoint> [-- fuse-options]",
27 static const struct option mount_options
[] = {
33 /* just tell for_each_ref* caller, we were here, i.e. there were non-zero refs
35 static int __refs_nonempty(const char *refname
, const unsigned char *sha1
,
36 int flags
, void *cb_data
)
41 static int navymailfs_getattr(const char *path
, struct stat
*st
)
45 memset(st
, 0, sizeof(*st
));
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;
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...
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 */
78 /* it could be a dir still */
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;
94 strbuf_release(&refname
);
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
);
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
))
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
);
141 ctx
->filler(ctx
->buf
, entry
.buf
, NULL
, 0);
144 strbuf_release(&entry
);
148 static int navymailfs_readdir(const char *path
, void *buf
, fuse_fill_dir_t
149 filler
, off_t offset
, struct fuse_file_info
*fi
)
152 struct readdir_ctx ctx
= { .buf
= buf
, .filler
= filler
, .prev_entry
= STRBUF_INIT
};
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
);
182 strbuf_release(&ctx
.prev_entry
);
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 */
199 struct strbuf refname
;
200 struct gbox_walk gbox_walk
;
201 struct msgmap_index msgmap
;
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
)
212 struct strbuf refname
= STRBUF_INIT
;
213 struct gbox_ctx
*ctx
= NULL
;
216 if (!!prefixcmp(path
, "/mail/")) {
221 strbuf_addf(&refname
, "refs%s", path
);
223 if (!ref_exists(refname
.buf
)) {
228 if ((fi
->flags
& (O_RDONLY
| O_WRONLY
| O_RDWR
)) != O_RDONLY
) {
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 ?
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
);
249 fi
->fh
= (intptr_t)ctx
;
252 strbuf_release(&refname
);
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) {
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
)
287 return a
*_
.quot
+ a
*_
.rem
/c
;
291 #define DEBUG_LOOKUP 0
294 # define TRACE_LOOKUP(fmt...) do { fprintf(stderr, fmt); } while (0)
296 # define TRACE_LOOKUP(fmt...) do {} while (0)
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)
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)
334 * Si_1 <= offset < Si <=> offset is in i'th msg (3)
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
])
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
;
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
);
359 break; /* found, see (3) */
361 /* offset < Si_1 , so because of (1) */
365 /* offset >= Si , see (2) */
369 /* now we know offset is somewhere in [lo,hi];
370 * estimate new i, assuming messages are of approximately equal size.
378 * |----|----|-- ... --|----|----|
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
396 * i = lo + (hi - lo)*----------------- (7)
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
];
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)
429 for (;size
!= 0; ++i
) {
431 if (i
!= ctx
->msg_idx
) {
432 if (ctx
->msg_idx
!= -1) {
439 if (i
== ctx
->msgmap
.nr
) {
440 /* fetch next msg from gbox */
441 sha1
= next_gbox_entry(&ctx
->gbox_walk
);
446 sha1
= ctx
->msgmap
.sha1
[i
];
448 ctx
->msg_buf
= read_sha1_file(sha1
, &type
, &ctx
->msg_size
);
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
);
461 ctx
->msgmap
.sumsize
[i
] = (i
> 0 ? ctx
->msgmap
.sumsize
[i
-1] : 0)
463 hashcpy(ctx
->msgmap
.sha1
[i
], sha1
);
469 Si
= ctx
->msgmap
.sumsize
[i
];
470 Si_1
= i
>= 1 ? ctx
->msgmap
.sumsize
[i
-1] : 0;
473 /* i'th msg overlaps - use it */
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
);
483 read_total
+= read_size
;
491 static struct fuse_operations navymailfs_ops
= {
492 .getattr
= navymailfs_getattr
,
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
)
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); ? */
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
);