1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Created by Li Guifu <blucerlee@gmail.com>
4 */
5 #include <stdlib.h>
6 #include <string.h>
7 #include <signal.h>
8 #include <libgen.h>
9 #include <fuse.h>
10 #include <fuse_opt.h>
11 #include "macosx.h"
12 #include "erofs/config.h"
13 #include "erofs/print.h"
14 #include "erofs/io.h"
15 #include "erofs/dir.h"
16 #include "erofs/inode.h"
17
18 struct erofsfuse_dir_context {
19 struct erofs_dir_context ctx;
20 fuse_fill_dir_t filler;
21 struct fuse_file_info *fi;
22 void *buf;
23 };
24
erofsfuse_fill_dentries(struct erofs_dir_context * ctx)25 static int erofsfuse_fill_dentries(struct erofs_dir_context *ctx)
26 {
27 struct erofsfuse_dir_context *fusectx = (void *)ctx;
28 struct stat st = {0};
29 char dname[EROFS_NAME_LEN + 1];
30
31 strncpy(dname, ctx->dname, ctx->de_namelen);
32 dname[ctx->de_namelen] = '\0';
33 st.st_mode = erofs_ftype_to_dtype(ctx->de_ftype) << 12;
34 fusectx->filler(fusectx->buf, dname, &st, 0);
35 return 0;
36 }
37
erofsfuse_readdir(const char * path,void * buf,fuse_fill_dir_t filler,off_t offset,struct fuse_file_info * fi)38 int erofsfuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
39 off_t offset, struct fuse_file_info *fi)
40 {
41 int ret;
42 struct erofs_inode dir;
43 struct erofsfuse_dir_context ctx = {
44 .ctx.dir = &dir,
45 .ctx.cb = erofsfuse_fill_dentries,
46 .filler = filler,
47 .fi = fi,
48 .buf = buf,
49 };
50 erofs_dbg("readdir:%s offset=%llu", path, (long long)offset);
51
52 dir.sbi = &sbi;
53 ret = erofs_ilookup(path, &dir);
54 if (ret)
55 return ret;
56
57 erofs_dbg("path=%s nid = %llu", path, dir.nid | 0ULL);
58 if (!S_ISDIR(dir.i_mode))
59 return -ENOTDIR;
60
61 if (!dir.i_size)
62 return 0;
63 #ifdef NDEBUG
64 return erofs_iterate_dir(&ctx.ctx, false);
65 #else
66 return erofs_iterate_dir(&ctx.ctx, true);
67 #endif
68 }
69
erofsfuse_init(struct fuse_conn_info * info)70 static void *erofsfuse_init(struct fuse_conn_info *info)
71 {
72 erofs_info("Using FUSE protocol %d.%d", info->proto_major, info->proto_minor);
73 return NULL;
74 }
75
erofsfuse_open(const char * path,struct fuse_file_info * fi)76 static int erofsfuse_open(const char *path, struct fuse_file_info *fi)
77 {
78 erofs_dbg("open path=%s", path);
79
80 if ((fi->flags & O_ACCMODE) != O_RDONLY)
81 return -EACCES;
82
83 return 0;
84 }
85
erofsfuse_getattr(const char * path,struct stat * stbuf)86 static int erofsfuse_getattr(const char *path, struct stat *stbuf)
87 {
88 struct erofs_inode vi = { .sbi = &sbi };
89 int ret;
90
91 erofs_dbg("getattr(%s)", path);
92 ret = erofs_ilookup(path, &vi);
93 if (ret)
94 return -ENOENT;
95
96 stbuf->st_mode = vi.i_mode;
97 stbuf->st_nlink = vi.i_nlink;
98 stbuf->st_size = vi.i_size;
99 stbuf->st_blocks = roundup(vi.i_size, erofs_blksiz(vi.sbi)) >> 9;
100 stbuf->st_uid = vi.i_uid;
101 stbuf->st_gid = vi.i_gid;
102 if (S_ISBLK(vi.i_mode) || S_ISCHR(vi.i_mode))
103 stbuf->st_rdev = vi.u.i_rdev;
104 stbuf->st_ctime = vi.i_mtime;
105 stbuf->st_mtime = stbuf->st_ctime;
106 stbuf->st_atime = stbuf->st_ctime;
107 return 0;
108 }
109
erofsfuse_read(const char * path,char * buffer,size_t size,off_t offset,struct fuse_file_info * fi)110 static int erofsfuse_read(const char *path, char *buffer,
111 size_t size, off_t offset,
112 struct fuse_file_info *fi)
113 {
114 int ret;
115 struct erofs_inode vi;
116
117 erofs_dbg("path:%s size=%zd offset=%llu", path, size, (long long)offset);
118
119 vi.sbi = &sbi;
120 ret = erofs_ilookup(path, &vi);
121 if (ret)
122 return ret;
123
124 ret = erofs_pread(&vi, buffer, size, offset);
125 if (ret)
126 return ret;
127 if (offset >= vi.i_size)
128 return 0;
129 if (offset + size > vi.i_size)
130 return vi.i_size - offset;
131 return size;
132 }
133
erofsfuse_readlink(const char * path,char * buffer,size_t size)134 static int erofsfuse_readlink(const char *path, char *buffer, size_t size)
135 {
136 int ret = erofsfuse_read(path, buffer, size, 0, NULL);
137
138 if (ret < 0)
139 return ret;
140 DBG_BUGON(ret > size);
141 if (ret == size)
142 buffer[size - 1] = '\0';
143 erofs_dbg("readlink(%s): %s", path, buffer);
144 return 0;
145 }
146
erofsfuse_getxattr(const char * path,const char * name,char * value,size_t size,uint32_t position)147 static int erofsfuse_getxattr(const char *path, const char *name, char *value,
148 size_t size
149 #ifdef __APPLE__
150 , uint32_t position)
151 #else
152 )
153 #endif
154 {
155 int ret;
156 struct erofs_inode vi;
157
158 erofs_dbg("getxattr(%s): name=%s size=%llu", path, name, size);
159
160 vi.sbi = &sbi;
161 ret = erofs_ilookup(path, &vi);
162 if (ret)
163 return ret;
164
165 return erofs_getxattr(&vi, name, value, size);
166 }
167
erofsfuse_listxattr(const char * path,char * list,size_t size)168 static int erofsfuse_listxattr(const char *path, char *list, size_t size)
169 {
170 int ret;
171 struct erofs_inode vi;
172
173 erofs_dbg("listxattr(%s): size=%llu", path, size);
174
175 vi.sbi = &sbi;
176 ret = erofs_ilookup(path, &vi);
177 if (ret)
178 return ret;
179
180 return erofs_listxattr(&vi, list, size);
181 }
182
183 static struct fuse_operations erofs_ops = {
184 .getxattr = erofsfuse_getxattr,
185 .listxattr = erofsfuse_listxattr,
186 .readlink = erofsfuse_readlink,
187 .getattr = erofsfuse_getattr,
188 .readdir = erofsfuse_readdir,
189 .open = erofsfuse_open,
190 .read = erofsfuse_read,
191 .init = erofsfuse_init,
192 };
193
194 static struct options {
195 const char *disk;
196 const char *mountpoint;
197 u64 offset;
198 unsigned int debug_lvl;
199 bool show_help;
200 bool odebug;
201 } fusecfg;
202
203 #define OPTION(t, p) { t, offsetof(struct options, p), 1 }
204 static const struct fuse_opt option_spec[] = {
205 OPTION("--offset=%lu", offset),
206 OPTION("--dbglevel=%u", debug_lvl),
207 OPTION("--help", show_help),
208 FUSE_OPT_KEY("--device=", 1),
209 FUSE_OPT_END
210 };
211
usage(void)212 static void usage(void)
213 {
214 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
215
216 fputs("usage: [options] IMAGE MOUNTPOINT\n\n"
217 "Options:\n"
218 " --offset=# skip # bytes when reading IMAGE\n"
219 " --dbglevel=# set output message level to # (maximum 9)\n"
220 " --device=# specify an extra device to be used together\n"
221 #if FUSE_MAJOR_VERSION < 3
222 " --help display this help and exit\n"
223 #endif
224 "\n", stderr);
225
226 #if FUSE_MAJOR_VERSION >= 3
227 fuse_cmdline_help();
228 #else
229 fuse_opt_add_arg(&args, ""); /* progname */
230 fuse_opt_add_arg(&args, "-ho"); /* progname */
231 fuse_parse_cmdline(&args, NULL, NULL, NULL);
232 #endif
233 exit(EXIT_FAILURE);
234 }
235
erofsfuse_dumpcfg(void)236 static void erofsfuse_dumpcfg(void)
237 {
238 erofs_dump("disk: %s\n", fusecfg.disk);
239 erofs_dump("offset: %llu\n", fusecfg.offset | 0ULL);
240 erofs_dump("mountpoint: %s\n", fusecfg.mountpoint);
241 erofs_dump("dbglevel: %u\n", cfg.c_dbg_lvl);
242 }
243
optional_opt_func(void * data,const char * arg,int key,struct fuse_args * outargs)244 static int optional_opt_func(void *data, const char *arg, int key,
245 struct fuse_args *outargs)
246 {
247 int ret;
248
249 switch (key) {
250 case 1:
251 ret = blob_open_ro(&sbi, arg + sizeof("--device=") - 1);
252 if (ret)
253 return -1;
254 ++sbi.extra_devices;
255 return 0;
256 case FUSE_OPT_KEY_NONOPT:
257 if (fusecfg.mountpoint)
258 return -1; /* Too many args */
259
260 if (!fusecfg.disk) {
261 fusecfg.disk = strdup(arg);
262 return 0;
263 }
264 if (!fusecfg.mountpoint)
265 fusecfg.mountpoint = strdup(arg);
266 case FUSE_OPT_KEY_OPT:
267 if (!strcmp(arg, "-d"))
268 fusecfg.odebug = true;
269 break;
270 default:
271 DBG_BUGON(1);
272 break;
273 }
274 return 1;
275 }
276
277 #if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE)
278 #include <execinfo.h>
279
signal_handle_sigsegv(int signal)280 static void signal_handle_sigsegv(int signal)
281 {
282 void *array[10];
283 size_t nptrs;
284 char **strings;
285 size_t i;
286
287 erofs_dump("========================================\n");
288 erofs_dump("Segmentation Fault. Starting backtrace:\n");
289 nptrs = backtrace(array, 10);
290 strings = backtrace_symbols(array, nptrs);
291 if (strings) {
292 for (i = 0; i < nptrs; i++)
293 erofs_dump("%s\n", strings[i]);
294 free(strings);
295 }
296 erofs_dump("========================================\n");
297 abort();
298 }
299 #endif
300
main(int argc,char * argv[])301 int main(int argc, char *argv[])
302 {
303 int ret;
304 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
305
306 erofs_init_configure();
307 printf("%s %s\n", basename(argv[0]), cfg.c_version);
308
309 #if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE)
310 if (signal(SIGSEGV, signal_handle_sigsegv) == SIG_ERR) {
311 fprintf(stderr, "failed to initialize signals\n");
312 ret = -errno;
313 goto err;
314 }
315 #endif
316
317 /* parse options */
318 ret = fuse_opt_parse(&args, &fusecfg, option_spec, optional_opt_func);
319 if (ret)
320 goto err;
321
322 if (fusecfg.show_help || !fusecfg.mountpoint)
323 usage();
324 cfg.c_dbg_lvl = fusecfg.debug_lvl;
325
326 if (fusecfg.odebug && cfg.c_dbg_lvl < EROFS_DBG)
327 cfg.c_dbg_lvl = EROFS_DBG;
328
329 cfg.c_offset = fusecfg.offset;
330
331 erofsfuse_dumpcfg();
332 ret = dev_open_ro(&sbi, fusecfg.disk);
333 if (ret) {
334 fprintf(stderr, "failed to open: %s\n", fusecfg.disk);
335 goto err_fuse_free_args;
336 }
337
338 ret = erofs_read_superblock(&sbi);
339 if (ret) {
340 fprintf(stderr, "failed to read erofs super block\n");
341 goto err_dev_close;
342 }
343
344 ret = fuse_main(args.argc, args.argv, &erofs_ops, NULL);
345
346 erofs_put_super(&sbi);
347 err_dev_close:
348 blob_closeall(&sbi);
349 dev_close(&sbi);
350 err_fuse_free_args:
351 fuse_opt_free_args(&args);
352 err:
353 erofs_exit_configure();
354 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
355 }
356