]> git.sven.stormbind.net Git - sven/fuse-exfat.git/blob - fuse/main.c
releasing package fuse-exfat version 1.4.0-2
[sven/fuse-exfat.git] / fuse / main.c
1 /*
2         main.c (01.09.09)
3         FUSE-based exFAT implementation. Requires FUSE 2.6 or later.
4
5         Free exFAT implementation.
6         Copyright (C) 2010-2023  Andrew Nayenko
7
8         This program is free software; you can redistribute it and/or modify
9         it under the terms of the GNU General Public License as published by
10         the Free Software Foundation, either version 2 of the License, or
11         (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
18         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.
21 */
22
23 #include <exfat.h>
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 <inttypes.h>
31 #include <limits.h>
32 #include <sys/types.h>
33 #include <pwd.h>
34 #include <unistd.h>
35
36 #ifndef DEBUG
37         #define exfat_debug(format, ...) do {} while (0)
38 #endif
39
40 #if !defined(FUSE_VERSION) || (FUSE_VERSION < 26)
41         #error FUSE 2.6 or later is required
42 #endif
43
44 struct exfat ef;
45
46 static struct exfat_node* get_node(const struct fuse_file_info* fi)
47 {
48         return (struct exfat_node*) (size_t) fi->fh;
49 }
50
51 static void set_node(struct fuse_file_info* fi, struct exfat_node* node)
52 {
53         fi->fh = (uint64_t) (size_t) node;
54         fi->keep_cache = 1;
55 }
56
57 static int fuse_exfat_getattr(const char* path, struct stat* stbuf
58 #if FUSE_USE_VERSION >= 30
59                 , UNUSED struct fuse_file_info* fi
60 #endif
61                 )
62 {
63         struct exfat_node* node;
64         int rc;
65
66         exfat_debug("[%s] %s", __func__, path);
67
68         rc = exfat_lookup(&ef, &node, path);
69         if (rc != 0)
70                 return rc;
71
72         exfat_stat(&ef, node, stbuf);
73         exfat_put_node(&ef, node);
74         return 0;
75 }
76
77 static int fuse_exfat_truncate(const char* path, off_t size
78 #if FUSE_USE_VERSION >= 30
79                 , UNUSED struct fuse_file_info* fi
80 #endif
81                 )
82 {
83         struct exfat_node* node;
84         int rc;
85
86         exfat_debug("[%s] %s, %"PRId64, __func__, path, size);
87
88         rc = exfat_lookup(&ef, &node, path);
89         if (rc != 0)
90                 return rc;
91
92         rc = exfat_truncate(&ef, node, size, true);
93         if (rc != 0)
94         {
95                 exfat_flush_node(&ef, node);    /* ignore return code */
96                 exfat_put_node(&ef, node);
97                 return rc;
98         }
99         rc = exfat_flush_node(&ef, node);
100         exfat_put_node(&ef, node);
101         return rc;
102 }
103
104 static int fuse_exfat_readdir(const char* path, void* buffer,
105                 fuse_fill_dir_t filler, UNUSED off_t offset,
106                 UNUSED struct fuse_file_info* fi
107 #if FUSE_USE_VERSION >= 30
108                 , UNUSED enum fuse_readdir_flags flags
109 #endif
110                 )
111 {
112         struct exfat_node* parent;
113         struct exfat_node* node;
114         struct exfat_iterator it;
115         int rc;
116         char name[EXFAT_UTF8_NAME_BUFFER_MAX];
117         struct stat stbuf;
118
119         exfat_debug("[%s] %s", __func__, path);
120
121         rc = exfat_lookup(&ef, &parent, path);
122         if (rc != 0)
123                 return rc;
124         if (!(parent->attrib & EXFAT_ATTRIB_DIR))
125         {
126                 exfat_put_node(&ef, parent);
127                 exfat_error("'%s' is not a directory (%#hx)", path, parent->attrib);
128                 return -ENOTDIR;
129         }
130
131 #if FUSE_USE_VERSION < 30
132         filler(buffer, ".", NULL, 0);
133         filler(buffer, "..", NULL, 0);
134 #else
135         filler(buffer, ".", NULL, 0, 0);
136         filler(buffer, "..", NULL, 0, 0);
137 #endif
138
139         rc = exfat_opendir(&ef, parent, &it);
140         if (rc != 0)
141         {
142                 exfat_put_node(&ef, parent);
143                 exfat_error("failed to open directory '%s'", path);
144                 return rc;
145         }
146         while ((node = exfat_readdir(&it)))
147         {
148                 exfat_get_name(node, name);
149                 exfat_debug("[%s] %s: %s, %"PRId64" bytes, cluster 0x%x", __func__,
150                                 name, node->is_contiguous ? "contiguous" : "fragmented",
151                                 node->size, node->start_cluster);
152                 exfat_stat(&ef, node, &stbuf);
153 #if FUSE_USE_VERSION < 30
154                 filler(buffer, name, &stbuf, 0);
155 #else
156                 filler(buffer, name, &stbuf, 0, 0);
157 #endif
158                 exfat_put_node(&ef, node);
159         }
160         exfat_closedir(&ef, &it);
161         exfat_put_node(&ef, parent);
162         return 0;
163 }
164
165 static int fuse_exfat_open(const char* path, struct fuse_file_info* fi)
166 {
167         struct exfat_node* node;
168         int rc;
169
170         exfat_debug("[%s] %s flags %#x%s%s%s%s%s", __func__, path, fi->flags,
171                         fi->flags & O_RDONLY ? " O_RDONLY" : "",
172                         fi->flags & O_WRONLY ? " O_WRONLY" : "",
173                         fi->flags & O_RDWR   ? " O_RDWR"   : "",
174                         fi->flags & O_APPEND ? " O_APPEND" : "",
175                         fi->flags & O_TRUNC  ? " O_TRUNC"  : "");
176
177         rc = exfat_lookup(&ef, &node, path);
178         if (rc != 0)
179                 return rc;
180         /* FUSE 2.x will call fuse_exfat_truncate() explicitly */
181 #if FUSE_USE_VERSION >= 30
182         if (fi->flags & O_TRUNC)
183         {
184                 rc = exfat_truncate(&ef, node, 0, true);
185                 if (rc != 0)
186                 {
187                         exfat_put_node(&ef, node);
188                         return rc;
189                 }
190         }
191 #endif
192         set_node(fi, node);
193         return 0;
194 }
195
196 static int fuse_exfat_create(const char* path, UNUSED mode_t mode,
197                 struct fuse_file_info* fi)
198 {
199         struct exfat_node* node;
200         int rc;
201
202         exfat_debug("[%s] %s 0%ho", __func__, path, mode);
203
204         rc = exfat_mknod(&ef, path);
205         if (rc != 0)
206                 return rc;
207         rc = exfat_lookup(&ef, &node, path);
208         if (rc != 0)
209                 return rc;
210         set_node(fi, node);
211         return 0;
212 }
213
214 static int fuse_exfat_release(UNUSED const char* path,
215                 struct fuse_file_info* fi)
216 {
217         /*
218            This handler is called by FUSE on close() syscall. If the FUSE
219            implementation does not call flush handler, we will flush node here.
220            But in this case we will not be able to return an error to the caller.
221            See fuse_exfat_flush() below.
222         */
223         exfat_debug("[%s] %s", __func__, path);
224         exfat_flush_node(&ef, get_node(fi));
225         exfat_put_node(&ef, get_node(fi));
226         return 0; /* FUSE ignores this return value */
227 }
228
229 static int fuse_exfat_flush(UNUSED const char* path, struct fuse_file_info* fi)
230 {
231         /*
232            This handler may be called by FUSE on close() syscall. FUSE also deals
233            with removals of open files, so we don't free clusters on close but
234            only on rmdir and unlink. If the FUSE implementation does not call this
235            handler we will flush node on release. See fuse_exfat_release() above.
236         */
237         exfat_debug("[%s] %s", __func__, path);
238         return exfat_flush_node(&ef, get_node(fi));
239 }
240
241 static int fuse_exfat_fsync(UNUSED const char* path, UNUSED int datasync,
242                 UNUSED struct fuse_file_info* fi)
243 {
244         int rc;
245
246         exfat_debug("[%s] %s", __func__, path);
247         rc = exfat_flush_nodes(&ef);
248         if (rc != 0)
249                 return rc;
250         rc = exfat_flush(&ef);
251         if (rc != 0)
252                 return rc;
253         return exfat_fsync(ef.dev);
254 }
255
256 static int fuse_exfat_read(UNUSED const char* path, char* buffer,
257                 size_t size, off_t offset, struct fuse_file_info* fi)
258 {
259         exfat_debug("[%s] %s (%zu bytes)", __func__, path, size);
260         return exfat_generic_pread(&ef, get_node(fi), buffer, size, offset);
261 }
262
263 static int fuse_exfat_write(UNUSED const char* path, const char* buffer,
264                 size_t size, off_t offset, struct fuse_file_info* fi)
265 {
266         exfat_debug("[%s] %s (%zu bytes)", __func__, path, size);
267         return exfat_generic_pwrite(&ef, get_node(fi), buffer, size, offset);
268 }
269
270 static int fuse_exfat_unlink(const char* path)
271 {
272         struct exfat_node* node;
273         int rc;
274
275         exfat_debug("[%s] %s", __func__, path);
276
277         rc = exfat_lookup(&ef, &node, path);
278         if (rc != 0)
279                 return rc;
280
281         rc = exfat_unlink(&ef, node);
282         exfat_put_node(&ef, node);
283         if (rc != 0)
284                 return rc;
285         return exfat_cleanup_node(&ef, node);
286 }
287
288 static int fuse_exfat_rmdir(const char* path)
289 {
290         struct exfat_node* node;
291         int rc;
292
293         exfat_debug("[%s] %s", __func__, path);
294
295         rc = exfat_lookup(&ef, &node, path);
296         if (rc != 0)
297                 return rc;
298
299         rc = exfat_rmdir(&ef, node);
300         exfat_put_node(&ef, node);
301         if (rc != 0)
302                 return rc;
303         return exfat_cleanup_node(&ef, node);
304 }
305
306 static int fuse_exfat_mknod(const char* path, UNUSED mode_t mode,
307                 UNUSED dev_t dev)
308 {
309         exfat_debug("[%s] %s 0%ho", __func__, path, mode);
310         return exfat_mknod(&ef, path);
311 }
312
313 static int fuse_exfat_mkdir(const char* path, UNUSED mode_t mode)
314 {
315         exfat_debug("[%s] %s 0%ho", __func__, path, mode);
316         return exfat_mkdir(&ef, path);
317 }
318
319 static int fuse_exfat_rename(const char* old_path, const char* new_path
320 #if FUSE_USE_VERSION >= 30
321                 , UNUSED unsigned int flags
322 #endif
323                 )
324 {
325         exfat_debug("[%s] %s => %s", __func__, old_path, new_path);
326         return exfat_rename(&ef, old_path, new_path);
327 }
328
329 static int fuse_exfat_utimens(const char* path, const struct timespec tv[2]
330 #if FUSE_USE_VERSION >= 30
331                 , UNUSED struct fuse_file_info* fi
332 #endif
333                 )
334 {
335         struct exfat_node* node;
336         int rc;
337
338         exfat_debug("[%s] %s", __func__, path);
339
340         rc = exfat_lookup(&ef, &node, path);
341         if (rc != 0)
342                 return rc;
343
344         exfat_utimes(node, tv);
345         rc = exfat_flush_node(&ef, node);
346         exfat_put_node(&ef, node);
347         return rc;
348 }
349
350 static int fuse_exfat_chmod(UNUSED const char* path, mode_t mode
351 #if FUSE_USE_VERSION >= 30
352                 , UNUSED struct fuse_file_info* fi
353 #endif
354                 )
355 {
356         const mode_t VALID_MODE_MASK = S_IFREG | S_IFDIR |
357                         S_IRWXU | S_IRWXG | S_IRWXO;
358
359         exfat_debug("[%s] %s 0%ho", __func__, path, mode);
360         if (mode & ~VALID_MODE_MASK)
361                 return -EPERM;
362         return 0;
363 }
364
365 static int fuse_exfat_chown(UNUSED const char* path, uid_t uid, gid_t gid
366 #if FUSE_USE_VERSION >= 30
367                 , UNUSED struct fuse_file_info* fi
368 #endif
369                 )
370 {
371         exfat_debug("[%s] %s %u:%u", __func__, path, uid, gid);
372         if (uid != ef.uid || gid != ef.gid)
373                 return -EPERM;
374         return 0;
375 }
376
377 static int fuse_exfat_statfs(UNUSED const char* path, struct statvfs* sfs)
378 {
379         exfat_debug("[%s]", __func__);
380
381         sfs->f_bsize = CLUSTER_SIZE(*ef.sb);
382         sfs->f_frsize = CLUSTER_SIZE(*ef.sb);
383         sfs->f_blocks = le64_to_cpu(ef.sb->sector_count) >> ef.sb->spc_bits;
384         sfs->f_bavail = exfat_count_free_clusters(&ef);
385         sfs->f_bfree = sfs->f_bavail;
386         sfs->f_namemax = EXFAT_NAME_MAX;
387
388         /*
389            Below are fake values because in exFAT there is
390            a) no simple way to count files;
391            b) no such thing as inode;
392            So here we assume that inode = cluster.
393         */
394         sfs->f_files = le32_to_cpu(ef.sb->cluster_count);
395         sfs->f_favail = sfs->f_bfree >> ef.sb->spc_bits;
396         sfs->f_ffree = sfs->f_bavail;
397
398         return 0;
399 }
400
401 static void* fuse_exfat_init(
402 #ifdef FUSE_CAP_BIG_WRITES
403                 struct fuse_conn_info* fci
404 #else
405                 UNUSED struct fuse_conn_info* fci
406 #endif
407 #if FUSE_USE_VERSION >= 30
408                 , UNUSED struct fuse_config* cfg
409 #endif
410                 )
411 {
412         exfat_debug("[%s]", __func__);
413 #ifdef FUSE_CAP_BIG_WRITES
414         fci->want |= FUSE_CAP_BIG_WRITES;
415 #endif
416
417         /* mark super block as dirty; failure isn't a big deal */
418         exfat_soil_super_block(&ef);
419
420         return NULL;
421 }
422
423 static void fuse_exfat_destroy(UNUSED void* unused)
424 {
425         exfat_debug("[%s]", __func__);
426         exfat_unmount(&ef);
427 }
428
429 static void usage(const char* prog)
430 {
431         fprintf(stderr, "Usage: %s [-d] [-o options] [-V] <device> <dir>\n", prog);
432         exit(1);
433 }
434
435 static struct fuse_operations fuse_exfat_ops =
436 {
437         .getattr        = fuse_exfat_getattr,
438         .truncate       = fuse_exfat_truncate,
439         .readdir        = fuse_exfat_readdir,
440         .open           = fuse_exfat_open,
441         .create         = fuse_exfat_create,
442         .release        = fuse_exfat_release,
443         .flush          = fuse_exfat_flush,
444         .fsync          = fuse_exfat_fsync,
445         .fsyncdir       = fuse_exfat_fsync,
446         .read           = fuse_exfat_read,
447         .write          = fuse_exfat_write,
448         .unlink         = fuse_exfat_unlink,
449         .rmdir          = fuse_exfat_rmdir,
450         .mknod          = fuse_exfat_mknod,
451         .mkdir          = fuse_exfat_mkdir,
452         .rename         = fuse_exfat_rename,
453         .utimens        = fuse_exfat_utimens,
454         .chmod          = fuse_exfat_chmod,
455         .chown          = fuse_exfat_chown,
456         .statfs         = fuse_exfat_statfs,
457         .init           = fuse_exfat_init,
458         .destroy        = fuse_exfat_destroy,
459 };
460
461 static char* add_option(char* options, const char* name, const char* value)
462 {
463         size_t size;
464         char* optionsf = options;
465
466         if (value)
467                 size = strlen(options) + strlen(name) + strlen(value) + 3;
468         else
469                 size = strlen(options) + strlen(name) + 2;
470
471         options = realloc(options, size);
472         if (options == NULL)
473         {
474                 free(optionsf);
475                 exfat_error("failed to reallocate options string");
476                 return NULL;
477         }
478         strcat(options, ",");
479         strcat(options, name);
480         if (value)
481         {
482                 strcat(options, "=");
483                 strcat(options, value);
484         }
485         return options;
486 }
487
488 static void escape(char* escaped, const char* orig)
489 {
490         do
491         {
492                 if (*orig == ',' || *orig == '\\')
493                         *escaped++ = '\\';
494         }
495         while ((*escaped++ = *orig++));
496 }
497
498 static char* add_fsname_option(char* options, const char* spec)
499 {
500         /* escaped string cannot be more than twice as big as the original one */
501         char* escaped = malloc(strlen(spec) * 2 + 1);
502
503         if (escaped == NULL)
504         {
505                 free(options);
506                 exfat_error("failed to allocate escaped string for %s", spec);
507                 return NULL;
508         }
509
510         /* on some platforms (e.g. Android, Solaris) device names can contain
511            commas */
512         escape(escaped, spec);
513         options = add_option(options, "fsname", escaped);
514         free(escaped);
515         return options;
516 }
517
518 static char* add_ro_option(char* options, bool ro)
519 {
520         return ro ? add_option(options, "ro", NULL) : options;
521 }
522
523 #if defined(__linux__)
524 static char* add_user_option(char* options)
525 {
526         struct passwd* pw;
527
528         if (getuid() == 0)
529                 return options;
530
531         pw = getpwuid(getuid());
532         if (pw == NULL || pw->pw_name == NULL)
533         {
534                 free(options);
535                 exfat_error("failed to determine username");
536                 return NULL;
537         }
538         return add_option(options, "user", pw->pw_name);
539 }
540 #endif
541
542 #if defined(__linux__)
543 static char* add_blksize_option(char* options, long cluster_size)
544 {
545         long page_size = sysconf(_SC_PAGESIZE);
546         char blksize[20];
547
548         if (page_size < 1)
549                 page_size = 0x1000;
550
551         snprintf(blksize, sizeof(blksize), "%ld", MIN(page_size, cluster_size));
552         return add_option(options, "blksize", blksize);
553 }
554 #endif
555
556 static char* add_fuse_options(char* options, const char* spec, bool ro)
557 {
558         options = add_fsname_option(options, spec);
559         if (options == NULL)
560                 return NULL;
561         options = add_ro_option(options, ro);
562         if (options == NULL)
563                 return NULL;
564 #if defined(__linux__)
565         options = add_user_option(options);
566         if (options == NULL)
567                 return NULL;
568         options = add_blksize_option(options, CLUSTER_SIZE(*ef.sb));
569         if (options == NULL)
570                 return NULL;
571 #endif
572         return options;
573 }
574
575 static char* add_passthrough_fuse_options(char* fuse_options,
576                 const char* options)
577 {
578         const char* passthrough_list[] =
579         {
580 #if defined(__FreeBSD__)
581                 "automounted",
582 #endif
583                 "nonempty",
584                 NULL
585         };
586         int i;
587
588         for (i = 0; passthrough_list[i] != NULL; i++)
589                 if (exfat_match_option(options, passthrough_list[i]))
590                 {
591                         fuse_options = add_option(fuse_options, passthrough_list[i], NULL);
592                         if (fuse_options == NULL)
593                                 return NULL;
594                 }
595
596         return fuse_options;
597 }
598
599 static int fuse_exfat_main(char* mount_options, char* mount_point)
600 {
601         char* argv[] = {"exfat", "-s", "-o", mount_options, mount_point, NULL};
602         return fuse_main(sizeof(argv) / sizeof(argv[0]) - 1, argv,
603                         &fuse_exfat_ops, NULL);
604 }
605
606 int main(int argc, char* argv[])
607 {
608         const char* spec = NULL;
609         char* mount_point = NULL;
610         char* fuse_options;
611         char* exfat_options;
612         int opt;
613         int rc;
614
615         printf("FUSE exfat %s (libfuse%d)\n", VERSION, FUSE_USE_VERSION / 10);
616
617         fuse_options = strdup("allow_other,"
618 #if FUSE_USE_VERSION < 30 && (defined(__linux__) || defined(__FreeBSD__))
619                         "big_writes,"
620 #endif
621 #if defined(__linux__)
622                         "blkdev,"
623 #endif
624                         "default_permissions");
625         exfat_options = strdup("ro_fallback");
626         if (fuse_options == NULL || exfat_options == NULL)
627         {
628                 free(fuse_options);
629                 free(exfat_options);
630                 exfat_error("failed to allocate options string");
631                 return 1;
632         }
633
634         while ((opt = getopt(argc, argv, "dno:Vv")) != -1)
635         {
636                 switch (opt)
637                 {
638                 case 'd':
639                         fuse_options = add_option(fuse_options, "debug", NULL);
640                         if (fuse_options == NULL)
641                         {
642                                 free(exfat_options);
643                                 return 1;
644                         }
645                         break;
646                 case 'n':
647                         break;
648                 case 'o':
649                         exfat_options = add_option(exfat_options, optarg, NULL);
650                         if (exfat_options == NULL)
651                         {
652                                 free(fuse_options);
653                                 return 1;
654                         }
655                         fuse_options = add_passthrough_fuse_options(fuse_options, optarg);
656                         if (fuse_options == NULL)
657                         {
658                                 free(exfat_options);
659                                 return 1;
660                         }
661                         break;
662                 case 'V':
663                         free(exfat_options);
664                         free(fuse_options);
665                         puts("Copyright (C) 2010-2023  Andrew Nayenko");
666                         return 0;
667                 case 'v':
668                         break;
669                 default:
670                         free(exfat_options);
671                         free(fuse_options);
672                         usage(argv[0]);
673                         break;
674                 }
675         }
676         if (argc - optind != 2)
677         {
678                 free(exfat_options);
679                 free(fuse_options);
680                 usage(argv[0]);
681         }
682         spec = argv[optind];
683         mount_point = argv[optind + 1];
684
685         if (exfat_mount(&ef, spec, exfat_options) != 0)
686         {
687                 free(exfat_options);
688                 free(fuse_options);
689                 return 1;
690         }
691
692         free(exfat_options);
693
694         fuse_options = add_fuse_options(fuse_options, spec, ef.ro != 0);
695         if (fuse_options == NULL)
696         {
697                 exfat_unmount(&ef);
698                 return 1;
699         }
700
701         /* let FUSE do all its wizardry */
702         rc = fuse_exfat_main(fuse_options, mount_point);
703
704         free(fuse_options);
705         return rc;
706 }