Commit | Line | Data |
---|---|---|
845dfc67 AT |
1 | #include "fs.h" |
2 | #include <errno.h> | |
3 | #include <fcntl.h> | |
4 | #include <unistd.h> | |
5 | #include <fuse_lowlevel.h> | |
6 | ||
7 | #include "args.h" | |
8 | ||
9 | uint8 *fsdata; | |
10 | int fslen; | |
11 | ||
12 | void | |
13 | panic(char *fmt, ...) | |
14 | { | |
15 | va_list ap; | |
16 | va_start(ap, fmt); | |
17 | vfprintf(stderr, fmt, ap); | |
18 | fprintf(stderr, "\n"); | |
19 | va_end(ap); | |
20 | exit(1); | |
21 | } | |
22 | ||
23 | void* | |
24 | emalloc(int size) | |
25 | { | |
26 | void *p; | |
27 | p = malloc(size); | |
28 | if(p == nil) | |
29 | panic("error: no memory"); | |
30 | return p; | |
31 | } | |
32 | ||
33 | FILE* | |
34 | mustopen(const char *name, const char *mode) | |
35 | { | |
36 | FILE *f; | |
37 | if(f = fopen(name, mode), f == nil) | |
38 | panic("couldn't open file: %s", name); | |
39 | return f; | |
40 | } | |
41 | ||
42 | ||
43 | typedef struct Options Options; | |
44 | struct Options | |
45 | { | |
46 | int singlethread; | |
47 | int foreground; | |
48 | int debug; | |
49 | int nodefault_subtype; | |
50 | char *device; | |
51 | char *mountpoint; | |
52 | int show_version; | |
53 | int show_help; | |
54 | int clone_fd; | |
55 | unsigned int max_idle_threads; | |
56 | }; | |
57 | static Options options; | |
58 | ||
59 | #define offsetof(type, field) ((long)&((type*)0)->field) | |
60 | #define OPTION(t, p) \ | |
61 | { t, offsetof(Options, p), 1 } | |
62 | ||
63 | static const struct fuse_opt myopts[] = { | |
64 | OPTION("-h", show_help), | |
65 | OPTION("--help", show_help), | |
66 | OPTION("-V", show_version), | |
67 | OPTION("--version", show_version), | |
68 | OPTION("-d", debug), | |
69 | OPTION("debug", debug), | |
70 | OPTION("-d", foreground), | |
71 | OPTION("debug", foreground), | |
72 | FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP), | |
73 | FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP), | |
74 | OPTION("-f", foreground), | |
75 | OPTION("-s", singlethread), | |
76 | OPTION("clone_fd", clone_fd), | |
77 | OPTION("max_idle_threads=%u", max_idle_threads), | |
78 | FUSE_OPT_END | |
79 | }; | |
80 | ||
81 | static int | |
82 | opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) | |
83 | { | |
84 | (void)data; (void)outargs; | |
85 | switch(key){ | |
86 | case FUSE_OPT_KEY_NONOPT: | |
87 | if(options.device == nil){ | |
88 | options.device = strdup(arg); | |
89 | return 0; | |
90 | } | |
91 | if(options.mountpoint == nil){ | |
92 | options.mountpoint = strdup(arg); | |
93 | return 0; | |
94 | } | |
95 | return -1; | |
96 | default: | |
97 | panic("invalid option"); | |
98 | } | |
99 | return -1; | |
100 | } | |
101 | ||
102 | void | |
103 | usage(void) | |
104 | { | |
105 | fprintf(stderr, | |
106 | " usage: asdf [options] filesystem mountpoint\n" | |
107 | " -h --help print help\n" | |
108 | " -V --version print version\n" | |
109 | " -d -o debug enable debug output (implies -f)\n" | |
110 | " -f foreground operation\n" | |
111 | " -s disable multi-threaded operation\n" | |
112 | " -o clone_fd use separate fuse device fd for each thread\n" | |
113 | " (may improve performance)\n" | |
114 | " -o max_idle_threads the maximum number of idle worker threads\n" | |
115 | " allowed (default: 10)\n"); | |
116 | exit(1); | |
117 | } | |
118 | ||
119 | ||
120 | ||
121 | ||
122 | ||
123 | void | |
124 | dirbuf_add(fuse_req_t req, Dirbuf *b, const char *name, fuse_ino_t ino) | |
125 | { | |
126 | struct stat stbuf; | |
127 | size_t oldsize = b->size; | |
128 | b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); | |
129 | b->p = (char *) realloc(b->p, b->size); | |
130 | memset(&stbuf, 0, sizeof(stbuf)); | |
131 | stbuf.st_ino = ino; | |
132 | fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, | |
133 | b->size); | |
134 | } | |
135 | ||
136 | #define min(x, y) ((x) < (y) ? (x) : (y)) | |
137 | ||
138 | int | |
139 | reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, | |
140 | off_t off, size_t maxsize) | |
141 | { | |
142 | if(off < bufsize) | |
143 | return fuse_reply_buf(req, buf + off, | |
144 | min(bufsize - off, maxsize)); | |
145 | else | |
146 | return fuse_reply_buf(req, NULL, 0); | |
147 | } | |
148 | ||
149 | static void | |
150 | vfs_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) | |
151 | { | |
152 | struct stat stbuf; | |
153 | USED(fi); | |
154 | ||
155 | memset(&stbuf, 0, sizeof(stbuf)); | |
156 | if(fs_stat(ino, &stbuf) == -1) | |
157 | fuse_reply_err(req, ENOENT); | |
158 | else | |
159 | fuse_reply_attr(req, &stbuf, 1.0); | |
160 | } | |
161 | ||
162 | static void | |
163 | vfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi) | |
164 | { | |
165 | int ret = 0; | |
166 | USED(fi); | |
167 | ||
168 | if(to_set & FUSE_SET_ATTR_ATIME) | |
169 | ret = fs_atime(ino, attr->st_atime); | |
170 | if(to_set & FUSE_SET_ATTR_MTIME) | |
171 | ret = fs_mtime(ino, attr->st_mtime); | |
172 | if(to_set & FUSE_SET_ATTR_GID) | |
173 | ret = fs_gid(ino, attr->st_gid); | |
174 | if(to_set & FUSE_SET_ATTR_UID) | |
175 | ret = fs_uid(ino, attr->st_uid); | |
176 | ||
177 | if(ret) | |
178 | fuse_reply_err(req, ret); | |
179 | else | |
180 | vfs_getattr(req, ino, fi); | |
181 | } | |
182 | ||
183 | void | |
184 | vfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) | |
185 | { | |
186 | int e; | |
187 | ||
188 | e = fs_open(ino, fi->flags); | |
189 | if(e) | |
190 | fuse_reply_err(req, e); | |
191 | else | |
192 | fuse_reply_open(req, fi); | |
193 | } | |
194 | ||
195 | void | |
196 | vfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, | |
197 | off_t off, struct fuse_file_info *fi) | |
198 | { | |
199 | USED(fi); | |
200 | char *buf; | |
201 | int n; | |
202 | ||
203 | buf = malloc(size); | |
204 | n = fs_read(ino, buf, off, size); | |
205 | reply_buf_limited(req, buf, n, off, size); | |
206 | free(buf); | |
207 | } | |
208 | ||
209 | void | |
210 | vfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, | |
211 | size_t size, off_t off, struct fuse_file_info *fi) | |
212 | { | |
213 | int n; | |
214 | USED(fi); | |
215 | n = fs_write(ino, (void*)buf, off, size); | |
216 | fuse_reply_write(req, n); | |
217 | } | |
218 | ||
219 | void | |
220 | vfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, | |
221 | off_t off, struct fuse_file_info *fi) | |
222 | { | |
223 | struct dirent *dirs, *dp; | |
224 | Dirbuf b; | |
225 | ||
226 | USED(fi); | |
227 | dirs = fs_readdir(ino); | |
228 | if(dirs == nil){ | |
229 | fuse_reply_err(req, ENOTDIR); | |
230 | return; | |
231 | } | |
232 | memset(&b, 0, sizeof(b)); | |
233 | for(dp = dirs; dp->d_ino; dp++) | |
234 | dirbuf_add(req, &b, dp->d_name, dp->d_ino); | |
235 | free(dirs); | |
236 | reply_buf_limited(req, b.p, b.size, off, size); | |
237 | free(b.p); | |
238 | } | |
239 | ||
240 | /* returns static pointer! */ | |
241 | struct dirent* | |
242 | lookup(uint ino, const char *name) | |
243 | { | |
244 | struct dirent *dirs, *dp; | |
245 | static struct dirent de; | |
246 | ||
247 | dirs = fs_readdir(ino); | |
248 | if(dirs == nil) | |
249 | return nil; | |
250 | for(dp = dirs; dp->d_ino; dp++) | |
251 | if(strcmp(dp->d_name, name) == 0){ | |
252 | de = *dp; | |
253 | free(dirs); | |
254 | return &de; | |
255 | } | |
256 | free(dirs); | |
257 | return nil; | |
258 | } | |
259 | ||
260 | void | |
261 | vfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) | |
262 | { | |
263 | struct fuse_entry_param e; | |
264 | struct dirent *de; | |
265 | ||
266 | de = lookup(parent, name); | |
267 | if(de == nil){ | |
268 | fuse_reply_err(req, ENOENT); | |
269 | return; | |
270 | } | |
271 | memset(&e, 0, sizeof(e)); | |
272 | e.ino = de->d_ino; | |
273 | e.attr_timeout = 1.0; | |
274 | e.entry_timeout = 1.0; | |
275 | fs_stat(e.ino, &e.attr); | |
276 | ||
277 | /* increment ref count */ | |
278 | fs_iget(e.ino); | |
279 | fuse_reply_entry(req, &e); | |
280 | } | |
281 | ||
282 | void | |
283 | vfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) | |
284 | { | |
285 | Inode *ip; | |
286 | ||
287 | printf("forgetting %d\n", nlookup); | |
288 | ip = fs_iget(ino); | |
289 | fs_iput(ip); | |
290 | while(nlookup--) | |
291 | fs_iput(ip); | |
292 | fuse_reply_none(req); | |
293 | } | |
294 | ||
295 | void | |
296 | vfs_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev) | |
297 | { | |
298 | int ret; | |
299 | ||
300 | ret = fs_mknod(parent, name, mode, rdev, nil); | |
301 | if(ret) | |
302 | fuse_reply_err(req, ret); | |
303 | else | |
304 | vfs_lookup(req, parent, name); | |
305 | } | |
306 | ||
307 | void | |
308 | vfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode) | |
309 | { | |
310 | int ret; | |
311 | ||
312 | ret = fs_mkdir(parent, name, mode); | |
313 | if(ret) | |
314 | fuse_reply_err(req, ret); | |
315 | else | |
316 | vfs_lookup(req, parent, name); | |
317 | } | |
318 | ||
319 | void | |
320 | vfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) | |
321 | { | |
322 | int ret; | |
323 | ||
324 | ret = fs_unlink(parent, name); | |
325 | fuse_reply_err(req, ret); | |
326 | } | |
327 | ||
328 | int | |
329 | isdirempty(struct dirent *de) | |
330 | { | |
331 | for(; de->d_ino; de++) | |
332 | if(strcmp(de->d_name, ".") != 0 && | |
333 | strcmp(de->d_name, "..") != 0) | |
334 | return 0; | |
335 | return 1; | |
336 | } | |
337 | ||
338 | void | |
339 | vfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) | |
340 | { | |
341 | struct dirent *dir, *de; | |
342 | int ret; | |
343 | int dino; | |
344 | ||
345 | ret = 0; | |
346 | de = lookup(parent, name); | |
347 | /* this shouldn't happen */ | |
348 | if(de == nil) | |
349 | return; | |
350 | dino = de->d_ino; | |
351 | dir = fs_readdir(dino); | |
352 | assert(dir); | |
353 | if(!isdirempty(dir)){ | |
354 | free(dir); | |
355 | fuse_reply_err(req, ENOTEMPTY); | |
356 | return; | |
357 | } | |
358 | printf("dir to delete: %d %s\n", dino, name); | |
359 | for(de = dir; de->d_ino; de++) | |
360 | fs_unlink(dino, de->d_name); | |
361 | free(dir); | |
362 | fs_unlink(parent, name); | |
363 | ||
364 | fuse_reply_err(req, ret); | |
365 | } | |
366 | ||
367 | void | |
368 | vfs_link(fuse_req_t req, fuse_ino_t ino, | |
369 | fuse_ino_t newparent, const char *newname) | |
370 | { | |
371 | int ret; | |
372 | ||
373 | ret = fs_link(ino, newparent, newname); | |
374 | if(ret) | |
375 | fuse_reply_err(req, ret); | |
376 | else | |
377 | vfs_lookup(req, newparent, newname); | |
378 | } | |
379 | ||
380 | static struct fuse_lowlevel_ops hello_ll_oper = { | |
381 | .lookup = vfs_lookup, | |
382 | .getattr = vfs_getattr, | |
383 | .setattr = vfs_setattr, | |
384 | .readdir = vfs_readdir, | |
385 | .open = vfs_open, | |
386 | .read = vfs_read, | |
387 | .write = vfs_write, | |
388 | .mknod = vfs_mknod, | |
389 | .mkdir = vfs_mkdir, | |
390 | .unlink = vfs_unlink, | |
391 | .rmdir = vfs_rmdir, | |
392 | .link = vfs_link, | |
393 | .forget = vfs_forget, | |
394 | // TODO | |
395 | // rename | |
396 | }; | |
397 | ||
398 | ||
399 | void | |
400 | fsload(const char *filename) | |
401 | { | |
402 | FILE *f = mustopen(filename, "rb"); | |
403 | fseek(f, 0, SEEK_END); | |
404 | fslen = ftell(f); | |
405 | fseek(f, 0, SEEK_SET); | |
406 | fsdata = emalloc(fslen); | |
407 | fread(fsdata, 1, fslen, f); | |
408 | fclose(f); | |
409 | } | |
410 | ||
411 | int | |
412 | main(int argc, char *argv[]) | |
413 | { | |
414 | struct fuse_args args = FUSE_ARGS_INIT(argc, argv); | |
415 | struct fuse_session *se; | |
416 | char *m; | |
417 | int ret = -1; | |
418 | ||
419 | if(fuse_opt_parse(&args, &options, myopts, opt_proc) == -1 || | |
420 | options.device == nil || options.mountpoint == nil) | |
421 | usage(); | |
422 | ||
423 | m = options.mountpoint; | |
424 | options.mountpoint = realpath(m, nil); | |
425 | if(options.mountpoint == nil) | |
426 | panic("bad mount point %s", m); | |
427 | ||
428 | printf("device: %s\n", options.device); | |
429 | printf("mountpoint: %s\n", options.mountpoint); | |
430 | ||
431 | if(options.show_help){ | |
432 | usage(); | |
433 | return 0; | |
434 | } | |
435 | if(options.show_version){ | |
436 | printf("FUSE library version %s\n", fuse_pkgversion()); | |
437 | fuse_lowlevel_version(); | |
438 | return 0; | |
439 | } | |
440 | ||
441 | fsload(options.device); | |
442 | fs_init(); | |
443 | dcheck(); | |
444 | ||
445 | se = fuse_session_new(&args, &hello_ll_oper, | |
446 | sizeof(hello_ll_oper), NULL); | |
447 | if(se == NULL) | |
448 | return 1; | |
449 | ||
450 | if(fuse_set_signal_handlers(se) != 0) | |
451 | goto err_out2; | |
452 | ||
453 | if(fuse_session_mount(se, options.mountpoint) != 0) | |
454 | goto err_out3; | |
455 | ||
456 | fuse_daemonize(options.foreground); | |
457 | ||
458 | /* Block until ctrl+c or fusermount -u */ | |
459 | if(options.singlethread) | |
460 | ret = fuse_session_loop(se); | |
461 | else | |
462 | ret = fuse_session_loop_mt(se, options.clone_fd); | |
463 | ||
464 | fuse_session_unmount(se); | |
465 | err_out3: | |
466 | fuse_remove_signal_handlers(se); | |
467 | err_out2: | |
468 | fuse_session_destroy(se); | |
469 | free(options.mountpoint); | |
470 | fuse_opt_free_args(&args); | |
471 | ||
472 | return ret ? 1 : 0; | |
473 | } |