OSDN Git Service

Report block size = fragment size = cluster size.
[android-x86/external-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         Copyright (C) 2009, 2010  Andrew Nayenko
6
7         This program is free software: you can redistribute it and/or modify
8         it under the terms of the GNU General Public License as published by
9         the Free Software Foundation, either version 3 of the License, or
10         (at your option) any later version.
11
12         This program is distributed in the hope that it will be useful,
13         but WITHOUT ANY WARRANTY; without even the implied warranty of
14         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15         GNU General Public License for more details.
16
17         You should have received a copy of the GNU General Public License
18         along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <fuse.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <exfat.h>
28 #include <inttypes.h>
29 #include <limits.h>
30
31 #define exfat_debug(format, ...)
32
33 #if !defined(FUSE_VERSION) || (FUSE_VERSION < 26)
34         #error FUSE 2.6 or later is required
35 #endif
36
37 const char* default_options = "allow_other,blkdev";
38
39 struct exfat ef;
40
41 static struct exfat_node* get_node(const struct fuse_file_info* fi)
42 {
43         return (struct exfat_node*) (size_t) fi->fh;
44 }
45
46 static void set_node(struct fuse_file_info* fi, struct exfat_node* node)
47 {
48         fi->fh = (uint64_t) (size_t) node;
49 }
50
51 static int fuse_exfat_getattr(const char* path, struct stat* stbuf)
52 {
53         struct exfat_node* node;
54         int rc;
55
56         exfat_debug("[fuse_exfat_getattr] %s", path);
57
58         rc = exfat_lookup(&ef, &node, path);
59         if (rc != 0)
60                 return rc;
61
62         exfat_stat(&ef, node, stbuf);
63         exfat_put_node(&ef, node);
64         return 0;
65 }
66
67 static int fuse_exfat_truncate(const char* path, off_t size)
68 {
69         struct exfat_node* node;
70         int rc;
71
72         exfat_debug("[fuse_exfat_truncate] %s, %"PRIu64, path, (uint64_t) size);
73
74         rc = exfat_lookup(&ef, &node, path);
75         if (rc != 0)
76                 return rc;
77
78         rc = exfat_truncate(&ef, node, size);
79         exfat_put_node(&ef, node);
80         return rc;
81 }
82
83 static int fuse_exfat_readdir(const char* path, void* buffer,
84                 fuse_fill_dir_t filler, off_t offset, struct fuse_file_info* fi)
85 {
86         struct exfat_node* parent;
87         struct exfat_node* node;
88         struct exfat_iterator it;
89         int rc;
90         char name[EXFAT_NAME_MAX + 1];
91
92         exfat_debug("[fuse_exfat_readdir] %s", path);
93
94         rc = exfat_lookup(&ef, &parent, path);
95         if (rc != 0)
96                 return rc;
97         if (!(parent->flags & EXFAT_ATTRIB_DIR))
98         {
99                 exfat_put_node(&ef, parent);
100                 exfat_error("`%s' is not a directory (0x%x)", path, parent->flags);
101                 return -ENOTDIR;
102         }
103
104         filler(buffer, ".", NULL, 0);
105         filler(buffer, "..", NULL, 0);
106
107         rc = exfat_opendir(&ef, parent, &it);
108         if (rc != 0)
109         {
110                 exfat_put_node(&ef, parent);
111                 exfat_error("failed to open directory `%s'", path);
112                 return rc;
113         }
114         while ((node = exfat_readdir(&ef, &it)))
115         {
116                 exfat_get_name(node, name, EXFAT_NAME_MAX);
117                 exfat_debug("[fuse_exfat_readdir] %s: %s, %"PRIu64" bytes, cluster %u",
118                                 name, IS_CONTIGUOUS(*node) ? "contiguous" : "fragmented",
119                                 (uint64_t) node->size, node->start_cluster);
120                 filler(buffer, name, NULL, 0);
121                 exfat_put_node(&ef, node);
122         }
123         exfat_closedir(&ef, &it);
124         exfat_put_node(&ef, parent);
125         return 0;
126 }
127
128 static int fuse_exfat_open(const char* path, struct fuse_file_info* fi)
129 {
130         struct exfat_node* node;
131         int rc;
132
133         exfat_debug("[fuse_exfat_open] %s", path);
134
135         rc = exfat_lookup(&ef, &node, path);
136         if (rc != 0)
137                 return rc;
138         set_node(fi, node);
139         return 0;
140 }
141
142 static int fuse_exfat_release(const char* path, struct fuse_file_info* fi)
143 {
144         exfat_put_node(&ef, get_node(fi));
145         return 0;
146 }
147
148 static int fuse_exfat_read(const char* path, char* buffer, size_t size,
149                 off_t offset, struct fuse_file_info* fi)
150 {
151         exfat_debug("[fuse_exfat_read] %s (%zu bytes)", path, size);
152         return exfat_read(&ef, get_node(fi), buffer, size, offset);
153 }
154
155 static int fuse_exfat_write(const char* path, const char* buffer, size_t size,
156                 off_t offset, struct fuse_file_info* fi)
157 {
158         exfat_debug("[fuse_exfat_write] %s (%zu bytes)", path, size);
159         return exfat_write(&ef, get_node(fi), buffer, size, offset);
160 }
161
162 static int fuse_exfat_unlink(const char* path)
163 {
164         struct exfat_node* node;
165         int rc;
166
167         exfat_debug("[fuse_exfat_unlink] %s", path);
168
169         rc = exfat_lookup(&ef, &node, path);
170         if (rc != 0)
171                 return rc;
172
173         rc = exfat_unlink(&ef, node);
174         exfat_put_node(&ef, node);
175         return rc;
176 }
177
178 static int fuse_exfat_rmdir(const char* path)
179 {
180         struct exfat_node* node;
181         int rc;
182
183         exfat_debug("[fuse_exfat_rmdir] %s", path);
184
185         rc = exfat_lookup(&ef, &node, path);
186         if (rc != 0)
187                 return rc;
188
189         rc = exfat_rmdir(&ef, node);
190         exfat_put_node(&ef, node);
191         return rc;
192 }
193
194 static int fuse_exfat_mknod(const char* path, mode_t mode, dev_t dev)
195 {
196         exfat_debug("[fuse_exfat_mknod] %s", path);
197         return exfat_mknod(&ef, path);
198 }
199
200 static int fuse_exfat_mkdir(const char* path, mode_t mode)
201 {
202         exfat_debug("[fuse_exfat_mkdir] %s", path);
203         return exfat_mkdir(&ef, path);
204 }
205
206 static int fuse_exfat_rename(const char* old_path, const char* new_path)
207 {
208         exfat_debug("[fuse_exfat_rename] %s => %s", old_path, new_path);
209         return exfat_rename(&ef, old_path, new_path);
210 }
211
212 static int fuse_exfat_utimens(const char* path, const struct timespec tv[2])
213 {
214         struct exfat_node* node;
215         int rc;
216
217         exfat_debug("[fuse_exfat_utimens] %s", path);
218
219         rc = exfat_lookup(&ef, &node, path);
220         if (rc != 0)
221                 return rc;
222
223         exfat_utimes(node, tv);
224         exfat_put_node(&ef, node);
225         return 0;
226 }
227
228 static int fuse_exfat_statfs(const char* path, struct statvfs* sfs)
229 {
230         sfs->f_bsize = CLUSTER_SIZE(*ef.sb);
231         sfs->f_frsize = CLUSTER_SIZE(*ef.sb);
232         sfs->f_blocks = le64_to_cpu(ef.sb->block_count) >> ef.sb->bpc_bits;
233         sfs->f_bavail = exfat_count_free_clusters(&ef);
234         sfs->f_bfree = sfs->f_bavail;
235         sfs->f_namemax = EXFAT_NAME_MAX;
236
237         /*
238            Below are fake values because in exFAT there is
239            a) no simple way to count files;
240            b) no such thing as inode;
241            So here we assume that inode = cluster.
242         */
243         sfs->f_files = (sfs->f_blocks - sfs->f_bfree) >> ef.sb->bpc_bits;
244         sfs->f_favail = sfs->f_bfree >> ef.sb->bpc_bits;
245         sfs->f_ffree = sfs->f_bavail;
246
247         return 0;
248 }
249
250 static void fuse_exfat_destroy(void* unused)
251 {
252         exfat_unmount(&ef);
253 }
254
255 static void usage(const char* prog)
256 {
257         fprintf(stderr, "Usage: %s <spec> <mountpoint> [-o options]\n", prog);
258         exit(1);
259 }
260
261 static struct fuse_operations fuse_exfat_ops =
262 {
263         .getattr        = fuse_exfat_getattr,
264         .truncate       = fuse_exfat_truncate,
265         .readdir        = fuse_exfat_readdir,
266         .open           = fuse_exfat_open,
267         .release        = fuse_exfat_release,
268         .read           = fuse_exfat_read,
269         .write          = fuse_exfat_write,
270         .unlink         = fuse_exfat_unlink,
271         .rmdir          = fuse_exfat_rmdir,
272         .mknod          = fuse_exfat_mknod,
273         .mkdir          = fuse_exfat_mkdir,
274         .rename         = fuse_exfat_rename,
275         .utimens        = fuse_exfat_utimens,
276         .statfs         = fuse_exfat_statfs,
277         .destroy        = fuse_exfat_destroy,
278 };
279
280 static char* add_option(char* options, const char* name, const char* value)
281 {
282         size_t size;
283
284         if (value)
285                 size = strlen(options) + strlen(name) + strlen(value) + 3;
286         else
287                 size = strlen(options) + strlen(name) + 2;
288
289         options = realloc(options, size);
290         if (options == NULL)
291         {
292                 exfat_error("failed to reallocate options string");
293                 return NULL;
294         }
295         strcat(options, ",");
296         strcat(options, name);
297         if (value)
298         {
299                 strcat(options, "=");
300                 strcat(options, value);
301         }
302         return options;
303 }
304
305 static char* add_fsname_option(char* options, const char* spec)
306 {
307         char spec_abs[PATH_MAX];
308
309         if (realpath(spec, spec_abs) == NULL)
310         {
311                 free(options);
312                 exfat_error("failed to get absolute path for `%s'", spec);
313                 return NULL;
314         }
315         return add_option(options, "fsname", spec_abs);
316 }
317
318 int main(int argc, char* argv[])
319 {
320         struct fuse_args mount_args = FUSE_ARGS_INIT(0, NULL);
321         struct fuse_args newfs_args = FUSE_ARGS_INIT(0, NULL);
322         const char* spec = NULL;
323         const char* mount_point = NULL;
324         char* mount_options;
325         int debug = 0;
326         struct fuse_chan* fc = NULL;
327         struct fuse* fh = NULL;
328         char** pp;
329
330         printf("FUSE exfat %u.%u.%u\n",
331                         EXFAT_VERSION_MAJOR, EXFAT_VERSION_MINOR, EXFAT_VERSION_PATCH);
332
333         mount_options = strdup(default_options);
334         if (mount_options == NULL)
335         {
336                 exfat_error("failed to allocate options string");
337                 return 1;
338         }
339
340         for (pp = argv + 1; *pp; pp++)
341         {
342                 if (strcmp(*pp, "-o") == 0)
343                 {
344                         pp++;
345                         if (*pp == NULL)
346                                 usage(argv[0]);
347                         mount_options = add_option(mount_options, *pp, NULL);
348                         if (mount_options == NULL)
349                                 return 1;
350                 }
351                 else if (strcmp(*pp, "-d") == 0)
352                         debug = 1;
353                 else if (spec == NULL)
354                         spec = *pp;
355                 else if (mount_point == NULL)
356                         mount_point = *pp;
357                 else
358                 {
359                         free(mount_options);
360                         usage(argv[0]);
361                 }
362         }
363         if (spec == NULL || mount_point == NULL)
364         {
365                 free(mount_options);
366                 usage(argv[0]);
367         }
368         mount_options = add_fsname_option(mount_options, spec);
369         if (mount_options == NULL)
370                 return 1;
371
372         /* create arguments for fuse_mount() */
373         if (fuse_opt_add_arg(&mount_args, "exfat") != 0 ||
374                 fuse_opt_add_arg(&mount_args, "-o") != 0 ||
375                 fuse_opt_add_arg(&mount_args, mount_options) != 0)
376         {
377                 free(mount_options);
378                 return 1;
379         }
380
381         /* create FUSE mount point */
382         fc = fuse_mount(mount_point, &mount_args);
383         fuse_opt_free_args(&mount_args);
384         if (fc == NULL)
385         {
386                 free(mount_options);
387                 return 1;
388         }
389
390         /* create arguments for fuse_new() */
391         if (fuse_opt_add_arg(&newfs_args, "") != 0 ||
392                 (debug && fuse_opt_add_arg(&newfs_args, "-d") != 0))
393         {
394                 fuse_unmount(mount_point, fc);
395                 free(mount_options);
396                 return 1;
397         }
398
399         /* create new FUSE file system */
400         fh = fuse_new(fc, &newfs_args, &fuse_exfat_ops,
401                         sizeof(struct fuse_operations), NULL);
402         fuse_opt_free_args(&newfs_args);
403         if (fh == NULL)
404         {
405                 fuse_unmount(mount_point, fc);
406                 free(mount_options);
407                 return 1;
408         }
409
410         /* exit session on HUP, TERM and INT signals and ignore PIPE signal */
411         if (fuse_set_signal_handlers(fuse_get_session(fh)))
412         {
413                 fuse_unmount(mount_point, fc);
414                 fuse_destroy(fh);
415                 free(mount_options);
416                 return 1;
417         }
418
419         if (exfat_mount(&ef, spec, mount_options) != 0)
420         {
421                 fuse_unmount(mount_point, fc);
422                 fuse_destroy(fh);
423                 free(mount_options);
424                 return 1;
425         }
426         free(mount_options);
427
428         /* go to background unless "-d" option is passed */
429         fuse_daemonize(debug);
430
431         /* FUSE main loop */
432         fuse_loop(fh);
433
434         /* it's quite illogical but fuse_unmount() must be called BEFORE
435            fuse_destroy() */
436         fuse_unmount(mount_point, fc);
437         fuse_destroy(fh);
438         return 0;
439 }