blob: abb60e7df5380b023503eb8869231067b1d181a3 [file] [log] [blame]
bigbiff bigbiff9c754052013-01-09 09:09:08 -05001/*
2 main.c (01.09.09)
3 FUSE-based exFAT implementation. Requires FUSE 2.6 or later.
4
bigbiff bigbiff61cdc022013-08-08 08:35:06 -04005 Free exFAT implementation.
bigbiff bigbiffca829c42013-01-28 08:14:25 -05006 Copyright (C) 2010-2013 Andrew Nayenko
bigbiff bigbiff9c754052013-01-09 09:09:08 -05007
bigbiff bigbiff61cdc022013-08-08 08:35:06 -04008 This program is free software; you can redistribute it and/or modify
bigbiff bigbiff9c754052013-01-09 09:09:08 -05009 it under the terms of the GNU General Public License as published by
bigbiff bigbiff61cdc022013-08-08 08:35:06 -040010 the Free Software Foundation, either version 2 of the License, or
bigbiff bigbiff9c754052013-01-09 09:09:08 -050011 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
bigbiff bigbiff61cdc022013-08-08 08:35:06 -040018 You should have received a copy of the GNU General Public License along
19 with this program; if not, write to the Free Software Foundation, Inc.,
20 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
bigbiff bigbiff9c754052013-01-09 09:09:08 -050021*/
22
23#define FUSE_USE_VERSION 26
24#include <fuse.h>
25#include <errno.h>
26#include <fcntl.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <exfat.h>
31#include <inttypes.h>
32#include <limits.h>
33#include <sys/types.h>
34#include <pwd.h>
35#include <unistd.h>
36
37#define exfat_debug(format, ...)
38
39#if !defined(FUSE_VERSION) || (FUSE_VERSION < 26)
40 #error FUSE 2.6 or later is required
41#endif
42
bigbiff bigbiff998716f2013-03-07 09:59:37 -050043const char* default_options = "ro_fallback,allow_other,blkdev,big_writes,"
44 "defer_permissions";
bigbiff bigbiff9c754052013-01-09 09:09:08 -050045
46struct exfat ef;
47
48static struct exfat_node* get_node(const struct fuse_file_info* fi)
49{
50 return (struct exfat_node*) (size_t) fi->fh;
51}
52
53static void set_node(struct fuse_file_info* fi, struct exfat_node* node)
54{
55 fi->fh = (uint64_t) (size_t) node;
56}
57
58static int fuse_exfat_getattr(const char* path, struct stat* stbuf)
59{
60 struct exfat_node* node;
61 int rc;
62
63 exfat_debug("[%s] %s", __func__, path);
64
65 rc = exfat_lookup(&ef, &node, path);
66 if (rc != 0)
67 return rc;
68
69 exfat_stat(&ef, node, stbuf);
70 exfat_put_node(&ef, node);
71 return 0;
72}
73
74static int fuse_exfat_truncate(const char* path, off64_t size)
75{
76 struct exfat_node* node;
77 int rc;
78
79 exfat_debug("[%s] %s, %"PRId64, __func__, path, size);
80
81 rc = exfat_lookup(&ef, &node, path);
82 if (rc != 0)
83 return rc;
84
bigbiff bigbiff998716f2013-03-07 09:59:37 -050085 rc = exfat_truncate(&ef, node, size, true);
bigbiff bigbiff9c754052013-01-09 09:09:08 -050086 exfat_put_node(&ef, node);
87 return rc;
88}
89
90static int fuse_exfat_readdir(const char* path, void* buffer,
91 fuse_fill_dir_t filler, off64_t offset, struct fuse_file_info* fi)
92{
93 struct exfat_node* parent;
94 struct exfat_node* node;
95 struct exfat_iterator it;
96 int rc;
bigbiff bigbiff004e2df2013-07-03 14:52:12 -040097 char name[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
bigbiff bigbiff9c754052013-01-09 09:09:08 -050098
99 exfat_debug("[%s] %s", __func__, path);
100
101 rc = exfat_lookup(&ef, &parent, path);
102 if (rc != 0)
103 return rc;
104 if (!(parent->flags & EXFAT_ATTRIB_DIR))
105 {
106 exfat_put_node(&ef, parent);
107 exfat_error("`%s' is not a directory (0x%x)", path, parent->flags);
108 return -ENOTDIR;
109 }
110
111 filler(buffer, ".", NULL, 0);
112 filler(buffer, "..", NULL, 0);
113
114 rc = exfat_opendir(&ef, parent, &it);
115 if (rc != 0)
116 {
117 exfat_put_node(&ef, parent);
118 exfat_error("failed to open directory `%s'", path);
119 return rc;
120 }
121 while ((node = exfat_readdir(&ef, &it)))
122 {
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400123 exfat_get_name(node, name, sizeof(name) - 1);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500124 exfat_debug("[%s] %s: %s, %"PRId64" bytes, cluster 0x%x", __func__,
125 name, IS_CONTIGUOUS(*node) ? "contiguous" : "fragmented",
126 node->size, node->start_cluster);
127 filler(buffer, name, NULL, 0);
128 exfat_put_node(&ef, node);
129 }
130 exfat_closedir(&ef, &it);
131 exfat_put_node(&ef, parent);
132 return 0;
133}
134
135static int fuse_exfat_open(const char* path, struct fuse_file_info* fi)
136{
137 struct exfat_node* node;
138 int rc;
139
140 exfat_debug("[%s] %s", __func__, path);
141
142 rc = exfat_lookup(&ef, &node, path);
143 if (rc != 0)
144 return rc;
145 set_node(fi, node);
146 fi->keep_cache = 1;
147 return 0;
148}
149
150static int fuse_exfat_release(const char* path, struct fuse_file_info* fi)
151{
152 exfat_debug("[%s] %s", __func__, path);
153 exfat_put_node(&ef, get_node(fi));
154 return 0;
155}
156
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400157static int fuse_exfat_fsync(const char* path, int datasync,
158 struct fuse_file_info *fi)
159{
160 int rc;
161
162 exfat_debug("[%s] %s", __func__, path);
163 rc = exfat_flush_node(&ef, get_node(fi));
164 if (rc != 0)
165 return rc;
166 rc = exfat_flush(&ef);
167 if (rc != 0)
168 return rc;
169 return exfat_fsync(ef.dev);
170}
171
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500172static int fuse_exfat_read(const char* path, char* buffer, size_t size,
173 off64_t offset, struct fuse_file_info* fi)
174{
Dees_Troyb8fdac72013-01-25 19:42:52 +0000175 ssize_t ret;
176
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500177 exfat_debug("[%s] %s (%zu bytes)", __func__, path, size);
Dees_Troyb8fdac72013-01-25 19:42:52 +0000178 ret = exfat_generic_pread(&ef, get_node(fi), buffer, size, offset);
179 if (ret < 0)
180 return -EIO;
181 return ret;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500182}
183
184static int fuse_exfat_write(const char* path, const char* buffer, size_t size,
185 off64_t offset, struct fuse_file_info* fi)
186{
Dees_Troyb8fdac72013-01-25 19:42:52 +0000187 ssize_t ret;
188
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500189 exfat_debug("[%s] %s (%zu bytes)", __func__, path, size);
Dees_Troyb8fdac72013-01-25 19:42:52 +0000190 ret = exfat_generic_pwrite(&ef, get_node(fi), buffer, size, offset);
191 if (ret < 0)
192 return -EIO;
193 return ret;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500194}
195
196static int fuse_exfat_unlink(const char* path)
197{
198 struct exfat_node* node;
199 int rc;
200
201 exfat_debug("[%s] %s", __func__, path);
202
203 rc = exfat_lookup(&ef, &node, path);
204 if (rc != 0)
205 return rc;
206
207 rc = exfat_unlink(&ef, node);
208 exfat_put_node(&ef, node);
209 return rc;
210}
211
212static int fuse_exfat_rmdir(const char* path)
213{
214 struct exfat_node* node;
215 int rc;
216
217 exfat_debug("[%s] %s", __func__, path);
218
219 rc = exfat_lookup(&ef, &node, path);
220 if (rc != 0)
221 return rc;
222
223 rc = exfat_rmdir(&ef, node);
224 exfat_put_node(&ef, node);
225 return rc;
226}
227
228static int fuse_exfat_mknod(const char* path, mode_t mode, dev_t dev)
229{
230 exfat_debug("[%s] %s 0%ho", __func__, path, mode);
231 return exfat_mknod(&ef, path);
232}
233
234static int fuse_exfat_mkdir(const char* path, mode_t mode)
235{
236 exfat_debug("[%s] %s 0%ho", __func__, path, mode);
237 return exfat_mkdir(&ef, path);
238}
239
240static int fuse_exfat_rename(const char* old_path, const char* new_path)
241{
242 exfat_debug("[%s] %s => %s", __func__, old_path, new_path);
243 return exfat_rename(&ef, old_path, new_path);
244}
245
246static int fuse_exfat_utimens(const char* path, const struct timespec tv[2])
247{
248 struct exfat_node* node;
249 int rc;
250
251 exfat_debug("[%s] %s", __func__, path);
252
253 rc = exfat_lookup(&ef, &node, path);
254 if (rc != 0)
255 return rc;
256
257 exfat_utimes(node, tv);
258 exfat_put_node(&ef, node);
259 return 0;
260}
261
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500262static int fuse_exfat_chmod(const char* path, mode_t mode)
263{
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400264 const mode_t VALID_MODE_MASK = S_IFREG | S_IFDIR |
265 S_IRWXU | S_IRWXG | S_IRWXO;
266
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500267 exfat_debug("[%s] %s 0%ho", __func__, path, mode);
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400268 if (mode & ~VALID_MODE_MASK)
269 return -EPERM;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500270 return 0;
271}
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400272
273static int fuse_exfat_chown(const char* path, uid_t uid, gid_t gid)
274{
275 exfat_debug("[%s] %s %u:%u", __func__, path, uid, gid);
276 if (uid != ef.uid || gid != ef.gid)
277 return -EPERM;
278 return 0;
279}
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500280
281static int fuse_exfat_statfs(const char* path, struct statvfs* sfs)
282{
283 exfat_debug("[%s]", __func__);
284
285 sfs->f_bsize = CLUSTER_SIZE(*ef.sb);
286 sfs->f_frsize = CLUSTER_SIZE(*ef.sb);
287 sfs->f_blocks = le64_to_cpu(ef.sb->sector_count) >> ef.sb->spc_bits;
288 sfs->f_bavail = exfat_count_free_clusters(&ef);
289 sfs->f_bfree = sfs->f_bavail;
290 sfs->f_namemax = EXFAT_NAME_MAX;
291
292 /*
293 Below are fake values because in exFAT there is
294 a) no simple way to count files;
295 b) no such thing as inode;
296 So here we assume that inode = cluster.
297 */
298 sfs->f_files = (sfs->f_blocks - sfs->f_bfree) >> ef.sb->spc_bits;
299 sfs->f_favail = sfs->f_bfree >> ef.sb->spc_bits;
300 sfs->f_ffree = sfs->f_bavail;
301
302 return 0;
303}
304
305static void* fuse_exfat_init(struct fuse_conn_info* fci)
306{
307 exfat_debug("[%s]", __func__);
308#ifdef FUSE_CAP_BIG_WRITES
309 fci->want |= FUSE_CAP_BIG_WRITES;
310#endif
311 return NULL;
312}
313
314static void fuse_exfat_destroy(void* unused)
315{
316 exfat_debug("[%s]", __func__);
317 exfat_unmount(&ef);
318}
319
320static void usage(const char* prog)
321{
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400322 fprintf(stderr, "Usage: %s [-d] [-o options] [-V] <device> <dir>\n", prog);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500323 exit(1);
324}
325
326static struct fuse_operations fuse_exfat_ops =
327{
328 .getattr = fuse_exfat_getattr,
329 .truncate = fuse_exfat_truncate,
330 .readdir = fuse_exfat_readdir,
331 .open = fuse_exfat_open,
332 .release = fuse_exfat_release,
bigbiff bigbiff61cdc022013-08-08 08:35:06 -0400333 .fsync = fuse_exfat_fsync,
334 .fsyncdir = fuse_exfat_fsync,
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500335 .read = fuse_exfat_read,
336 .write = fuse_exfat_write,
337 .unlink = fuse_exfat_unlink,
338 .rmdir = fuse_exfat_rmdir,
339 .mknod = fuse_exfat_mknod,
340 .mkdir = fuse_exfat_mkdir,
341 .rename = fuse_exfat_rename,
342 .utimens = fuse_exfat_utimens,
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500343 .chmod = fuse_exfat_chmod,
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400344 .chown = fuse_exfat_chown,
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500345 .statfs = fuse_exfat_statfs,
346 .init = fuse_exfat_init,
347 .destroy = fuse_exfat_destroy,
348};
349
350static char* add_option(char* options, const char* name, const char* value)
351{
352 size_t size;
353
354 if (value)
355 size = strlen(options) + strlen(name) + strlen(value) + 3;
356 else
357 size = strlen(options) + strlen(name) + 2;
358
359 options = realloc(options, size);
360 if (options == NULL)
361 {
362 exfat_error("failed to reallocate options string");
363 return NULL;
364 }
365 strcat(options, ",");
366 strcat(options, name);
367 if (value)
368 {
369 strcat(options, "=");
370 strcat(options, value);
371 }
372 return options;
373}
374
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500375static char* add_user_option(char* options)
376{
377 struct passwd* pw;
378
379 if (getuid() == 0)
380 return options;
381
382 pw = getpwuid(getuid());
383 if (pw == NULL || pw->pw_name == NULL)
384 {
385 free(options);
386 exfat_error("failed to determine username");
387 return NULL;
388 }
389 return add_option(options, "user", pw->pw_name);
390}
391
392static char* add_blksize_option(char* options, long cluster_size)
393{
394 long page_size = sysconf(_SC_PAGESIZE);
395 char blksize[20];
396
397 if (page_size < 1)
398 page_size = 0x1000;
399
400 snprintf(blksize, sizeof(blksize), "%ld", MIN(page_size, cluster_size));
401 return add_option(options, "blksize", blksize);
402}
403
404static char* add_fuse_options(char* options, const char* spec)
405{
bigbiff bigbiff998716f2013-03-07 09:59:37 -0500406 options = add_option(options, "fsname", spec);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500407 if (options == NULL)
408 return NULL;
409 options = add_user_option(options);
410 if (options == NULL)
411 return NULL;
412 options = add_blksize_option(options, CLUSTER_SIZE(*ef.sb));
413 if (options == NULL)
414 return NULL;
415
416 return options;
417}
418
419int main(int argc, char* argv[])
420{
421 struct fuse_args mount_args = FUSE_ARGS_INIT(0, NULL);
422 struct fuse_args newfs_args = FUSE_ARGS_INIT(0, NULL);
423 const char* spec = NULL;
424 const char* mount_point = NULL;
425 char* mount_options;
426 int debug = 0;
427 struct fuse_chan* fc = NULL;
428 struct fuse* fh = NULL;
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400429 int opt;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500430
431 printf("FUSE exfat %u.%u.%u\n",
432 EXFAT_VERSION_MAJOR, EXFAT_VERSION_MINOR, EXFAT_VERSION_PATCH);
433
434 mount_options = strdup(default_options);
435 if (mount_options == NULL)
436 {
437 exfat_error("failed to allocate options string");
438 return 1;
439 }
440
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400441 while ((opt = getopt(argc, argv, "dno:Vv")) != -1)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500442 {
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400443 switch (opt)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500444 {
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400445 case 'd':
446 debug = 1;
447 break;
448 case 'n':
449 break;
450 case 'o':
451 mount_options = add_option(mount_options, optarg, NULL);
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500452 if (mount_options == NULL)
453 return 1;
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400454 break;
455 case 'V':
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500456 free(mount_options);
bigbiff bigbiffca829c42013-01-28 08:14:25 -0500457 puts("Copyright (C) 2010-2013 Andrew Nayenko");
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500458 return 0;
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400459 case 'v':
460 break;
461 default:
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500462 free(mount_options);
463 usage(argv[0]);
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400464 break;
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500465 }
466 }
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400467 if (argc - optind != 2)
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500468 {
469 free(mount_options);
470 usage(argv[0]);
471 }
bigbiff bigbiff004e2df2013-07-03 14:52:12 -0400472 spec = argv[optind];
473 mount_point = argv[optind + 1];
bigbiff bigbiff9c754052013-01-09 09:09:08 -0500474
475 if (exfat_mount(&ef, spec, mount_options) != 0)
476 {
477 free(mount_options);
478 return 1;
479 }
480
481 if (ef.ro == -1) /* read-only fallback was used */
482 {
483 mount_options = add_option(mount_options, "ro", NULL);
484 if (mount_options == NULL)
485 {
486 exfat_unmount(&ef);
487 return 1;
488 }
489 }
490
491 mount_options = add_fuse_options(mount_options, spec);
492 if (mount_options == NULL)
493 {
494 exfat_unmount(&ef);
495 return 1;
496 }
497
498 /* create arguments for fuse_mount() */
499 if (fuse_opt_add_arg(&mount_args, "exfat") != 0 ||
500 fuse_opt_add_arg(&mount_args, "-o") != 0 ||
501 fuse_opt_add_arg(&mount_args, mount_options) != 0)
502 {
503 exfat_unmount(&ef);
504 free(mount_options);
505 return 1;
506 }
507
508 free(mount_options);
509
510 /* create FUSE mount point */
511 fc = fuse_mount(mount_point, &mount_args);
512 fuse_opt_free_args(&mount_args);
513 if (fc == NULL)
514 {
515 exfat_unmount(&ef);
516 return 1;
517 }
518
519 /* create arguments for fuse_new() */
520 if (fuse_opt_add_arg(&newfs_args, "") != 0 ||
521 (debug && fuse_opt_add_arg(&newfs_args, "-d") != 0))
522 {
523 fuse_unmount(mount_point, fc);
524 exfat_unmount(&ef);
525 return 1;
526 }
527
528 /* create new FUSE file system */
529 fh = fuse_new(fc, &newfs_args, &fuse_exfat_ops,
530 sizeof(struct fuse_operations), NULL);
531 fuse_opt_free_args(&newfs_args);
532 if (fh == NULL)
533 {
534 fuse_unmount(mount_point, fc);
535 exfat_unmount(&ef);
536 return 1;
537 }
538
539 /* exit session on HUP, TERM and INT signals and ignore PIPE signal */
540 if (fuse_set_signal_handlers(fuse_get_session(fh)) != 0)
541 {
542 fuse_unmount(mount_point, fc);
543 fuse_destroy(fh);
544 exfat_unmount(&ef);
545 exfat_error("failed to set signal handlers");
546 return 1;
547 }
548
549 /* go to background (unless "-d" option is passed) and run FUSE
550 main loop */
551 if (fuse_daemonize(debug) == 0)
552 {
553 if (fuse_loop(fh) != 0)
554 exfat_error("FUSE loop failure");
555 }
556 else
557 exfat_error("failed to daemonize");
558
559 fuse_remove_signal_handlers(fuse_get_session(fh));
560 /* note that fuse_unmount() must be called BEFORE fuse_destroy() */
561 fuse_unmount(mount_point, fc);
562 fuse_destroy(fh);
563 return 0;
564}