1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Copyright 2021 Google LLC
4 * Author: Daeho Jeong <daehojeong@google.com>
5 */
6 #include <stdlib.h>
7 #include <getopt.h>
8 #include <time.h>
9 #include <utime.h>
10 #include <unistd.h>
11 #include <sys/stat.h>
12 #include <sys/xattr.h>
13 #include "erofs/print.h"
14 #include "erofs/compress.h"
15 #include "erofs/decompress.h"
16 #include "erofs/dir.h"
17 #include "erofs/xattr.h"
18 #include "../lib/compressor.h"
19
20 static int erofsfsck_check_inode(erofs_nid_t pnid, erofs_nid_t nid);
21
22 struct erofsfsck_cfg {
23 u64 physical_blocks;
24 u64 logical_blocks;
25 char *extract_path;
26 size_t extract_pos;
27 mode_t umask;
28 bool superuser;
29 bool corrupted;
30 bool print_comp_ratio;
31 bool check_decomp;
32 bool force;
33 bool overwrite;
34 bool preserve_owner;
35 bool preserve_perms;
36 bool dump_xattrs;
37 };
38 static struct erofsfsck_cfg fsckcfg;
39
40 static struct option long_options[] = {
41 {"version", no_argument, 0, 'V'},
42 {"help", no_argument, 0, 'h'},
43 {"extract", optional_argument, 0, 2},
44 {"device", required_argument, 0, 3},
45 {"force", no_argument, 0, 4},
46 {"overwrite", no_argument, 0, 5},
47 {"preserve", no_argument, 0, 6},
48 {"preserve-owner", no_argument, 0, 7},
49 {"preserve-perms", no_argument, 0, 8},
50 {"no-preserve", no_argument, 0, 9},
51 {"no-preserve-owner", no_argument, 0, 10},
52 {"no-preserve-perms", no_argument, 0, 11},
53 {"offset", required_argument, 0, 12},
54 {"xattrs", no_argument, 0, 13},
55 {"no-xattrs", no_argument, 0, 14},
56 {0, 0, 0, 0},
57 };
58
59 #define NR_HARDLINK_HASHTABLE 16384
60
61 struct erofsfsck_hardlink_entry {
62 struct list_head list;
63 erofs_nid_t nid;
64 char *path;
65 };
66
67 static struct list_head erofsfsck_link_hashtable[NR_HARDLINK_HASHTABLE];
68
print_available_decompressors(FILE * f,const char * delim)69 static void print_available_decompressors(FILE *f, const char *delim)
70 {
71 int i = 0;
72 bool comma = false;
73 const struct erofs_algorithm *s;
74
75 while ((s = z_erofs_list_available_compressors(&i)) != NULL) {
76 if (comma)
77 fputs(delim, f);
78 fputs(s->name, f);
79 comma = true;
80 }
81 fputc('\n', f);
82 }
83
usage(int argc,char ** argv)84 static void usage(int argc, char **argv)
85 {
86 // " 1 2 3 4 5 6 7 8 "
87 // "12345678901234567890123456789012345678901234567890123456789012345678901234567890\n"
88 printf(
89 "Usage: %s [OPTIONS] IMAGE\n"
90 "Check erofs filesystem compatibility and integrity of IMAGE.\n"
91 "\n"
92 "This version of fsck.erofs is capable of checking images that use any of the\n"
93 "following algorithms: ", argv[0]);
94 print_available_decompressors(stdout, ", ");
95 printf("\n"
96 "General options:\n"
97 " -V, --version print the version number of fsck.erofs and exit\n"
98 " -h, --help display this help and exit\n"
99 "\n"
100 " -d<0-9> set output verbosity; 0=quiet, 9=verbose (default=%i)\n"
101 " -p print total compression ratio of all files\n"
102 " --device=X specify an extra device to be used together\n"
103 " --extract[=X] check if all files are well encoded, optionally\n"
104 " extract to X\n"
105 " --offset=# skip # bytes at the beginning of IMAGE\n"
106 " --[no-]xattrs whether to dump extended attributes (default off)\n"
107 "\n"
108 " -a, -A, -y no-op, for compatibility with fsck of other filesystems\n"
109 "\n"
110 "Extraction options (--extract=X is required):\n"
111 " --force allow extracting to root\n"
112 " --overwrite overwrite files that already exist\n"
113 " --[no-]preserve same as --[no-]preserve-owner --[no-]preserve-perms\n"
114 " --[no-]preserve-owner whether to preserve the ownership from the\n"
115 " filesystem (default for superuser), or to extract as\n"
116 " yourself (default for ordinary users)\n"
117 " --[no-]preserve-perms whether to preserve the exact permissions from the\n"
118 " filesystem without applying umask (default for\n"
119 " superuser), or to modify the permissions by applying\n"
120 " umask (default for ordinary users)\n",
121 EROFS_WARN);
122 }
123
erofsfsck_print_version(void)124 static void erofsfsck_print_version(void)
125 {
126 printf("fsck.erofs (erofs-utils) %s\navailable decompressors: ",
127 cfg.c_version);
128 print_available_decompressors(stdout, ", ");
129 }
130
erofsfsck_parse_options_cfg(int argc,char ** argv)131 static int erofsfsck_parse_options_cfg(int argc, char **argv)
132 {
133 char *endptr;
134 int opt, ret;
135 bool has_opt_preserve = false;
136
137 while ((opt = getopt_long(argc, argv, "Vd:phaAy",
138 long_options, NULL)) != -1) {
139 switch (opt) {
140 case 'V':
141 erofsfsck_print_version();
142 exit(0);
143 case 'd':
144 ret = atoi(optarg);
145 if (ret < EROFS_MSG_MIN || ret > EROFS_MSG_MAX) {
146 erofs_err("invalid debug level %d", ret);
147 return -EINVAL;
148 }
149 cfg.c_dbg_lvl = ret;
150 break;
151 case 'p':
152 fsckcfg.print_comp_ratio = true;
153 break;
154 case 'h':
155 usage(argc, argv);
156 exit(0);
157 case 'a':
158 case 'A':
159 case 'y':
160 break;
161 case 2:
162 fsckcfg.check_decomp = true;
163 if (optarg) {
164 size_t len = strlen(optarg);
165
166 if (len == 0) {
167 erofs_err("empty value given for --extract=X");
168 return -EINVAL;
169 }
170
171 /* remove trailing slashes except root */
172 while (len > 1 && optarg[len - 1] == '/')
173 len--;
174
175 if (len >= PATH_MAX) {
176 erofs_err("target directory name too long!");
177 return -ENAMETOOLONG;
178 }
179
180 fsckcfg.extract_path = malloc(PATH_MAX);
181 if (!fsckcfg.extract_path)
182 return -ENOMEM;
183 strncpy(fsckcfg.extract_path, optarg, len);
184 fsckcfg.extract_path[len] = '\0';
185 /* if path is root, start writing from position 0 */
186 if (len == 1 && fsckcfg.extract_path[0] == '/')
187 len = 0;
188 fsckcfg.extract_pos = len;
189 }
190 break;
191 case 3:
192 ret = erofs_blob_open_ro(&g_sbi, optarg);
193 if (ret)
194 return ret;
195 ++g_sbi.extra_devices;
196 break;
197 case 4:
198 fsckcfg.force = true;
199 break;
200 case 5:
201 fsckcfg.overwrite = true;
202 break;
203 case 6:
204 fsckcfg.preserve_owner = fsckcfg.preserve_perms = true;
205 has_opt_preserve = true;
206 break;
207 case 7:
208 fsckcfg.preserve_owner = true;
209 has_opt_preserve = true;
210 break;
211 case 8:
212 fsckcfg.preserve_perms = true;
213 has_opt_preserve = true;
214 break;
215 case 9:
216 fsckcfg.preserve_owner = fsckcfg.preserve_perms = false;
217 has_opt_preserve = true;
218 break;
219 case 10:
220 fsckcfg.preserve_owner = false;
221 has_opt_preserve = true;
222 break;
223 case 11:
224 fsckcfg.preserve_perms = false;
225 has_opt_preserve = true;
226 break;
227 case 12:
228 g_sbi.bdev.offset = strtoull(optarg, &endptr, 0);
229 if (*endptr != '\0') {
230 erofs_err("invalid disk offset %s", optarg);
231 return -EINVAL;
232 }
233 break;
234 case 13:
235 fsckcfg.dump_xattrs = true;
236 break;
237 case 14:
238 fsckcfg.dump_xattrs = false;
239 break;
240 default:
241 return -EINVAL;
242 }
243 }
244
245 if (fsckcfg.extract_path) {
246 if (!fsckcfg.extract_pos && !fsckcfg.force) {
247 erofs_err("--extract=/ must be used together with --force");
248 return -EINVAL;
249 }
250 } else {
251 if (fsckcfg.force) {
252 erofs_err("--force must be used together with --extract=X");
253 return -EINVAL;
254 }
255 if (fsckcfg.overwrite) {
256 erofs_err("--overwrite must be used together with --extract=X");
257 return -EINVAL;
258 }
259 if (has_opt_preserve) {
260 erofs_err("--[no-]preserve[-owner/-perms] must be used together with --extract=X");
261 return -EINVAL;
262 }
263 }
264
265 if (optind >= argc) {
266 erofs_err("missing argument: IMAGE");
267 return -EINVAL;
268 }
269
270 cfg.c_img_path = strdup(argv[optind++]);
271 if (!cfg.c_img_path)
272 return -ENOMEM;
273
274 if (optind < argc) {
275 erofs_err("unexpected argument: %s", argv[optind]);
276 return -EINVAL;
277 }
278 return 0;
279 }
280
erofsfsck_set_attributes(struct erofs_inode * inode,char * path)281 static void erofsfsck_set_attributes(struct erofs_inode *inode, char *path)
282 {
283 int ret;
284
285 /* don't apply attributes when fsck is used without extraction */
286 if (!fsckcfg.extract_path)
287 return;
288
289 #ifdef HAVE_UTIMENSAT
290 if (utimensat(AT_FDCWD, path, (struct timespec []) {
291 [0] = { .tv_sec = inode->i_mtime,
292 .tv_nsec = inode->i_mtime_nsec },
293 [1] = { .tv_sec = inode->i_mtime,
294 .tv_nsec = inode->i_mtime_nsec },
295 }, AT_SYMLINK_NOFOLLOW) < 0)
296 #else
297 if (utime(path, &((struct utimbuf){.actime = inode->i_mtime,
298 .modtime = inode->i_mtime})) < 0)
299 #endif
300 erofs_warn("failed to set times: %s", path);
301
302 if (!S_ISLNK(inode->i_mode)) {
303 if (fsckcfg.preserve_perms)
304 ret = chmod(path, inode->i_mode);
305 else
306 ret = chmod(path, inode->i_mode & ~fsckcfg.umask);
307 if (ret < 0)
308 erofs_warn("failed to set permissions: %s", path);
309 }
310
311 if (fsckcfg.preserve_owner) {
312 ret = lchown(path, inode->i_uid, inode->i_gid);
313 if (ret < 0)
314 erofs_warn("failed to change ownership: %s", path);
315 }
316 }
317
erofs_check_sb_chksum(void)318 static int erofs_check_sb_chksum(void)
319 {
320 #ifndef FUZZING
321 u8 buf[EROFS_MAX_BLOCK_SIZE];
322 u32 crc;
323 struct erofs_super_block *sb;
324 int ret;
325
326 ret = erofs_blk_read(&g_sbi, 0, buf, 0, 1);
327 if (ret) {
328 erofs_err("failed to read superblock to check checksum: %d",
329 ret);
330 return -1;
331 }
332
333 sb = (struct erofs_super_block *)(buf + EROFS_SUPER_OFFSET);
334 sb->checksum = 0;
335
336 crc = erofs_crc32c(~0, (u8 *)sb, erofs_blksiz(&g_sbi) - EROFS_SUPER_OFFSET);
337 if (crc != g_sbi.checksum) {
338 erofs_err("superblock chksum doesn't match: saved(%08xh) calculated(%08xh)",
339 g_sbi.checksum, crc);
340 fsckcfg.corrupted = true;
341 return -1;
342 }
343 #endif
344 return 0;
345 }
346
erofs_verify_xattr(struct erofs_inode * inode)347 static int erofs_verify_xattr(struct erofs_inode *inode)
348 {
349 struct erofs_sb_info *sbi = inode->sbi;
350 unsigned int xattr_hdr_size = sizeof(struct erofs_xattr_ibody_header);
351 unsigned int xattr_entry_size = sizeof(struct erofs_xattr_entry);
352 erofs_off_t addr;
353 unsigned int ofs, xattr_shared_count;
354 struct erofs_xattr_ibody_header *ih;
355 struct erofs_xattr_entry *entry;
356 int i, remaining = inode->xattr_isize, ret = 0;
357 char buf[EROFS_MAX_BLOCK_SIZE];
358
359 if (inode->xattr_isize == xattr_hdr_size) {
360 erofs_err("xattr_isize %d of nid %llu is not supported yet",
361 inode->xattr_isize, inode->nid | 0ULL);
362 ret = -EFSCORRUPTED;
363 goto out;
364 } else if (inode->xattr_isize < xattr_hdr_size) {
365 if (inode->xattr_isize) {
366 erofs_err("bogus xattr ibody @ nid %llu",
367 inode->nid | 0ULL);
368 ret = -EFSCORRUPTED;
369 goto out;
370 }
371 }
372
373 addr = erofs_iloc(inode) + inode->inode_isize;
374 ret = erofs_dev_read(sbi, 0, buf, addr, xattr_hdr_size);
375 if (ret < 0) {
376 erofs_err("failed to read xattr header @ nid %llu: %d",
377 inode->nid | 0ULL, ret);
378 goto out;
379 }
380 ih = (struct erofs_xattr_ibody_header *)buf;
381 xattr_shared_count = ih->h_shared_count;
382
383 ofs = erofs_blkoff(sbi, addr) + xattr_hdr_size;
384 addr += xattr_hdr_size;
385 remaining -= xattr_hdr_size;
386 for (i = 0; i < xattr_shared_count; ++i) {
387 if (ofs >= erofs_blksiz(sbi)) {
388 if (ofs != erofs_blksiz(sbi)) {
389 erofs_err("unaligned xattr entry in xattr shared area @ nid %llu",
390 inode->nid | 0ULL);
391 ret = -EFSCORRUPTED;
392 goto out;
393 }
394 ofs = 0;
395 }
396 ofs += xattr_entry_size;
397 addr += xattr_entry_size;
398 remaining -= xattr_entry_size;
399 }
400
401 while (remaining > 0) {
402 unsigned int entry_sz;
403
404 ret = erofs_dev_read(sbi, 0, buf, addr, xattr_entry_size);
405 if (ret) {
406 erofs_err("failed to read xattr entry @ nid %llu: %d",
407 inode->nid | 0ULL, ret);
408 goto out;
409 }
410
411 entry = (struct erofs_xattr_entry *)buf;
412 entry_sz = erofs_xattr_entry_size(entry);
413 if (remaining < entry_sz) {
414 erofs_err("xattr on-disk corruption: xattr entry beyond xattr_isize @ nid %llu",
415 inode->nid | 0ULL);
416 ret = -EFSCORRUPTED;
417 goto out;
418 }
419 addr += entry_sz;
420 remaining -= entry_sz;
421 }
422 out:
423 return ret;
424 }
425
erofsfsck_dump_xattrs(struct erofs_inode * inode)426 static int erofsfsck_dump_xattrs(struct erofs_inode *inode)
427 {
428 static bool ignore_xattrs = false;
429 char *keylst, *key;
430 ssize_t kllen;
431 int ret;
432
433 kllen = erofs_listxattr(inode, NULL, 0);
434 if (kllen <= 0)
435 return kllen;
436 keylst = malloc(kllen);
437 if (!keylst)
438 return -ENOMEM;
439 ret = erofs_listxattr(inode, keylst, kllen);
440 if (ret != kllen) {
441 erofs_err("failed to list xattrs @ nid %llu",
442 inode->nid | 0ULL);
443 ret = -EINVAL;
444 goto out;
445 }
446 ret = 0;
447 for (key = keylst; key < keylst + kllen; key += strlen(key) + 1) {
448 unsigned int index, len;
449 void *value = NULL;
450 size_t size = 0;
451
452 ret = erofs_getxattr(inode, key, NULL, 0);
453 if (ret <= 0) {
454 DBG_BUGON(1);
455 erofs_err("failed to get xattr value size of `%s` @ nid %llu",
456 key, inode->nid | 0ULL);
457 break;
458 }
459 size = ret;
460 value = malloc(size);
461 if (!value) {
462 ret = -ENOMEM;
463 break;
464 }
465 ret = erofs_getxattr(inode, key, value, size);
466 if (ret < 0) {
467 erofs_err("failed to get xattr `%s` @ nid %llu, because of `%s`", key,
468 inode->nid | 0ULL, erofs_strerror(ret));
469 free(value);
470 break;
471 }
472 if (fsckcfg.extract_path)
473 #ifdef HAVE_LSETXATTR
474 ret = lsetxattr(fsckcfg.extract_path, key, value, size,
475 0);
476 #elif defined(__APPLE__)
477 ret = setxattr(fsckcfg.extract_path, key, value, size,
478 0, XATTR_NOFOLLOW);
479 #else
480 ret = -EOPNOTSUPP;
481 #endif
482 else
483 ret = 0;
484 free(value);
485 if (ret == -EPERM && !fsckcfg.superuser) {
486 if (__erofs_unlikely(!erofs_xattr_prefix_matches(key,
487 &index, &len))) {
488 erofs_err("failed to match the prefix of `%s` @ nid %llu",
489 key, inode->nid | 0ULL);
490 ret = -EINVAL;
491 break;
492 }
493 if (index != EROFS_XATTR_INDEX_USER) {
494 if (!ignore_xattrs) {
495 erofs_warn("ignored xattr `%s` @ nid %llu, due to non-superuser",
496 key, inode->nid | 0ULL);
497 ignore_xattrs = true;
498 }
499 ret = 0;
500 continue;
501 }
502
503 }
504 if (ret) {
505 erofs_err("failed to set xattr `%s` @ nid %llu because of `%s`",
506 key, inode->nid | 0ULL, erofs_strerror(ret));
507 break;
508 }
509 }
510 out:
511 free(keylst);
512 return ret;
513 }
514
erofs_verify_inode_data(struct erofs_inode * inode,int outfd)515 static int erofs_verify_inode_data(struct erofs_inode *inode, int outfd)
516 {
517 struct erofs_map_blocks map = {
518 .index = UINT_MAX,
519 };
520 int ret = 0;
521 bool compressed;
522 erofs_off_t pos = 0;
523 u64 pchunk_len = 0;
524 unsigned int raw_size = 0, buffer_size = 0;
525 char *raw = NULL, *buffer = NULL;
526
527 erofs_dbg("verify data chunk of nid(%llu): type(%d)",
528 inode->nid | 0ULL, inode->datalayout);
529
530 switch (inode->datalayout) {
531 case EROFS_INODE_FLAT_PLAIN:
532 case EROFS_INODE_FLAT_INLINE:
533 case EROFS_INODE_CHUNK_BASED:
534 compressed = false;
535 break;
536 case EROFS_INODE_COMPRESSED_FULL:
537 case EROFS_INODE_COMPRESSED_COMPACT:
538 compressed = true;
539 break;
540 default:
541 erofs_err("unknown datalayout");
542 return -EINVAL;
543 }
544
545 while (pos < inode->i_size) {
546 unsigned int alloc_rawsize;
547
548 map.m_la = pos;
549 if (compressed)
550 ret = z_erofs_map_blocks_iter(inode, &map,
551 EROFS_GET_BLOCKS_FIEMAP);
552 else
553 ret = erofs_map_blocks(inode, &map,
554 EROFS_GET_BLOCKS_FIEMAP);
555 if (ret)
556 goto out;
557
558 if (!compressed && map.m_llen != map.m_plen) {
559 erofs_err("broken chunk length m_la %" PRIu64 " m_llen %" PRIu64 " m_plen %" PRIu64,
560 map.m_la, map.m_llen, map.m_plen);
561 ret = -EFSCORRUPTED;
562 goto out;
563 }
564
565 /* the last lcluster can be divided into 3 parts */
566 if (map.m_la + map.m_llen > inode->i_size)
567 map.m_llen = inode->i_size - map.m_la;
568
569 pchunk_len += map.m_plen;
570 pos += map.m_llen;
571
572 /* should skip decomp? */
573 if (map.m_la >= inode->i_size || !fsckcfg.check_decomp)
574 continue;
575
576 if (outfd >= 0 && !(map.m_flags & EROFS_MAP_MAPPED)) {
577 ret = lseek(outfd, map.m_llen, SEEK_CUR);
578 if (ret < 0) {
579 ret = -errno;
580 goto out;
581 }
582 continue;
583 }
584
585 if (map.m_plen > Z_EROFS_PCLUSTER_MAX_SIZE) {
586 if (compressed) {
587 erofs_err("invalid pcluster size %" PRIu64 " @ offset %" PRIu64 " of nid %" PRIu64,
588 map.m_plen, map.m_la,
589 inode->nid | 0ULL);
590 ret = -EFSCORRUPTED;
591 goto out;
592 }
593 alloc_rawsize = Z_EROFS_PCLUSTER_MAX_SIZE;
594 } else {
595 alloc_rawsize = map.m_plen;
596 }
597
598 if (alloc_rawsize > raw_size) {
599 char *newraw = realloc(raw, alloc_rawsize);
600
601 if (!newraw) {
602 ret = -ENOMEM;
603 goto out;
604 }
605 raw = newraw;
606 raw_size = alloc_rawsize;
607 }
608
609 if (compressed) {
610 if (map.m_llen > buffer_size) {
611 char *newbuffer;
612
613 buffer_size = map.m_llen;
614 newbuffer = realloc(buffer, buffer_size);
615 if (!newbuffer) {
616 ret = -ENOMEM;
617 goto out;
618 }
619 buffer = newbuffer;
620 }
621 ret = z_erofs_read_one_data(inode, &map, raw, buffer,
622 0, map.m_llen, false);
623 if (ret)
624 goto out;
625
626 if (outfd >= 0 && write(outfd, buffer, map.m_llen) < 0)
627 goto fail_eio;
628 } else {
629 u64 p = 0;
630
631 do {
632 u64 count = min_t(u64, alloc_rawsize,
633 map.m_llen);
634
635 ret = erofs_read_one_data(inode, &map, raw, p, count);
636 if (ret)
637 goto out;
638
639 if (outfd >= 0 && write(outfd, raw, count) < 0)
640 goto fail_eio;
641 map.m_llen -= count;
642 p += count;
643 } while (map.m_llen);
644 }
645 }
646
647 if (fsckcfg.print_comp_ratio) {
648 if (!erofs_is_packed_inode(inode))
649 fsckcfg.logical_blocks += BLK_ROUND_UP(inode->sbi, inode->i_size);
650 fsckcfg.physical_blocks += BLK_ROUND_UP(inode->sbi, pchunk_len);
651 }
652 out:
653 if (raw)
654 free(raw);
655 if (buffer)
656 free(buffer);
657 return ret < 0 ? ret : 0;
658
659 fail_eio:
660 erofs_err("I/O error occurred when verifying data chunk @ nid %llu",
661 inode->nid | 0ULL);
662 ret = -EIO;
663 goto out;
664 }
665
erofs_extract_dir(struct erofs_inode * inode)666 static inline int erofs_extract_dir(struct erofs_inode *inode)
667 {
668 int ret;
669
670 erofs_dbg("create directory %s", fsckcfg.extract_path);
671
672 /* verify data chunk layout */
673 ret = erofs_verify_inode_data(inode, -1);
674 if (ret)
675 return ret;
676
677 /*
678 * Make directory with default user rwx permissions rather than
679 * the permissions from the filesystem, as these may not have
680 * write/execute permission. These are fixed up later in
681 * erofsfsck_set_attributes().
682 */
683 if (mkdir(fsckcfg.extract_path, 0700) < 0) {
684 struct stat st;
685
686 if (errno != EEXIST) {
687 erofs_err("failed to create directory: %s (%s)",
688 fsckcfg.extract_path, strerror(errno));
689 return -errno;
690 }
691
692 if (lstat(fsckcfg.extract_path, &st) ||
693 !S_ISDIR(st.st_mode)) {
694 erofs_err("path is not a directory: %s",
695 fsckcfg.extract_path);
696 return -ENOTDIR;
697 }
698
699 /*
700 * Try to change permissions of existing directory so
701 * that we can write to it
702 */
703 if (chmod(fsckcfg.extract_path, 0700) < 0) {
704 erofs_err("failed to set permissions: %s (%s)",
705 fsckcfg.extract_path, strerror(errno));
706 return -errno;
707 }
708 }
709 return 0;
710 }
711
erofsfsck_hardlink_find(erofs_nid_t nid)712 static char *erofsfsck_hardlink_find(erofs_nid_t nid)
713 {
714 struct list_head *head =
715 &erofsfsck_link_hashtable[nid % NR_HARDLINK_HASHTABLE];
716 struct erofsfsck_hardlink_entry *entry;
717
718 list_for_each_entry(entry, head, list)
719 if (entry->nid == nid)
720 return entry->path;
721 return NULL;
722 }
723
erofsfsck_hardlink_insert(erofs_nid_t nid,const char * path)724 static int erofsfsck_hardlink_insert(erofs_nid_t nid, const char *path)
725 {
726 struct erofsfsck_hardlink_entry *entry;
727
728 entry = malloc(sizeof(*entry));
729 if (!entry)
730 return -ENOMEM;
731
732 entry->nid = nid;
733 entry->path = strdup(path);
734 if (!entry->path) {
735 free(entry);
736 return -ENOMEM;
737 }
738
739 list_add_tail(&entry->list,
740 &erofsfsck_link_hashtable[nid % NR_HARDLINK_HASHTABLE]);
741 return 0;
742 }
743
erofsfsck_hardlink_init(void)744 static void erofsfsck_hardlink_init(void)
745 {
746 unsigned int i;
747
748 for (i = 0; i < NR_HARDLINK_HASHTABLE; ++i)
749 init_list_head(&erofsfsck_link_hashtable[i]);
750 }
751
erofsfsck_hardlink_exit(void)752 static void erofsfsck_hardlink_exit(void)
753 {
754 struct erofsfsck_hardlink_entry *entry, *n;
755 struct list_head *head;
756 unsigned int i;
757
758 for (i = 0; i < NR_HARDLINK_HASHTABLE; ++i) {
759 head = &erofsfsck_link_hashtable[i];
760
761 list_for_each_entry_safe(entry, n, head, list) {
762 if (entry->path)
763 free(entry->path);
764 free(entry);
765 }
766 }
767 }
768
erofs_extract_file(struct erofs_inode * inode)769 static inline int erofs_extract_file(struct erofs_inode *inode)
770 {
771 bool tryagain = true;
772 int ret, fd;
773
774 erofs_dbg("extract file to path: %s", fsckcfg.extract_path);
775
776 again:
777 fd = open(fsckcfg.extract_path,
778 O_WRONLY | O_CREAT | O_NOFOLLOW |
779 (fsckcfg.overwrite ? O_TRUNC : O_EXCL), 0700);
780 if (fd < 0) {
781 if (fsckcfg.overwrite && tryagain) {
782 if (errno == EISDIR) {
783 erofs_warn("try to forcely remove directory %s",
784 fsckcfg.extract_path);
785 if (rmdir(fsckcfg.extract_path) < 0) {
786 erofs_err("failed to remove: %s (%s)",
787 fsckcfg.extract_path, strerror(errno));
788 return -EISDIR;
789 }
790 } else if (errno == EACCES &&
791 chmod(fsckcfg.extract_path, 0700) < 0) {
792 erofs_err("failed to set permissions: %s (%s)",
793 fsckcfg.extract_path, strerror(errno));
794 return -errno;
795 }
796 tryagain = false;
797 goto again;
798 }
799 erofs_err("failed to open: %s (%s)", fsckcfg.extract_path,
800 strerror(errno));
801 return -errno;
802 }
803
804 /* verify data chunk layout */
805 ret = erofs_verify_inode_data(inode, fd);
806 close(fd);
807 return ret;
808 }
809
erofs_extract_symlink(struct erofs_inode * inode)810 static inline int erofs_extract_symlink(struct erofs_inode *inode)
811 {
812 bool tryagain = true;
813 int ret;
814 char *buf = NULL;
815
816 erofs_dbg("extract symlink to path: %s", fsckcfg.extract_path);
817
818 /* verify data chunk layout */
819 ret = erofs_verify_inode_data(inode, -1);
820 if (ret)
821 return ret;
822
823 buf = malloc(inode->i_size + 1);
824 if (!buf) {
825 ret = -ENOMEM;
826 goto out;
827 }
828
829 ret = erofs_pread(inode, buf, inode->i_size, 0);
830 if (ret) {
831 erofs_err("I/O error occurred when reading symlink @ nid %llu: %d",
832 inode->nid | 0ULL, ret);
833 goto out;
834 }
835
836 buf[inode->i_size] = '\0';
837 again:
838 if (symlink(buf, fsckcfg.extract_path) < 0) {
839 if (errno == EEXIST && fsckcfg.overwrite && tryagain) {
840 erofs_warn("try to forcely remove file %s",
841 fsckcfg.extract_path);
842 if (unlink(fsckcfg.extract_path) < 0) {
843 erofs_err("failed to remove: %s",
844 fsckcfg.extract_path);
845 ret = -errno;
846 goto out;
847 }
848 tryagain = false;
849 goto again;
850 }
851 erofs_err("failed to create symlink: %s",
852 fsckcfg.extract_path);
853 ret = -errno;
854 }
855 out:
856 if (buf)
857 free(buf);
858 return ret;
859 }
860
erofs_extract_special(struct erofs_inode * inode)861 static int erofs_extract_special(struct erofs_inode *inode)
862 {
863 bool tryagain = true;
864 int ret;
865
866 erofs_dbg("extract special to path: %s", fsckcfg.extract_path);
867
868 /* verify data chunk layout */
869 ret = erofs_verify_inode_data(inode, -1);
870 if (ret)
871 return ret;
872
873 again:
874 if (mknod(fsckcfg.extract_path, inode->i_mode, inode->u.i_rdev) < 0) {
875 if (errno == EEXIST && fsckcfg.overwrite && tryagain) {
876 erofs_warn("try to forcely remove file %s",
877 fsckcfg.extract_path);
878 if (unlink(fsckcfg.extract_path) < 0) {
879 erofs_err("failed to remove: %s",
880 fsckcfg.extract_path);
881 return -errno;
882 }
883 tryagain = false;
884 goto again;
885 }
886 if (errno == EEXIST || fsckcfg.superuser) {
887 erofs_err("failed to create special file: %s",
888 fsckcfg.extract_path);
889 ret = -errno;
890 } else {
891 erofs_warn("failed to create special file: %s, skipped",
892 fsckcfg.extract_path);
893 ret = -ECANCELED;
894 }
895 }
896 return ret;
897 }
898
erofsfsck_dirent_iter(struct erofs_dir_context * ctx)899 static int erofsfsck_dirent_iter(struct erofs_dir_context *ctx)
900 {
901 int ret;
902 size_t prev_pos, curr_pos;
903
904 if (ctx->dot_dotdot)
905 return 0;
906
907 prev_pos = fsckcfg.extract_pos;
908 curr_pos = prev_pos;
909
910 if (prev_pos + ctx->de_namelen >= PATH_MAX) {
911 erofs_err("unable to fsck since the path is too long (%llu)",
912 (curr_pos + ctx->de_namelen) | 0ULL);
913 return -EOPNOTSUPP;
914 }
915
916 if (fsckcfg.extract_path) {
917 fsckcfg.extract_path[curr_pos++] = '/';
918 strncpy(fsckcfg.extract_path + curr_pos, ctx->dname,
919 ctx->de_namelen);
920 curr_pos += ctx->de_namelen;
921 fsckcfg.extract_path[curr_pos] = '\0';
922 } else {
923 curr_pos += ctx->de_namelen;
924 }
925 fsckcfg.extract_pos = curr_pos;
926 ret = erofsfsck_check_inode(ctx->dir->nid, ctx->de_nid);
927
928 if (fsckcfg.extract_path)
929 fsckcfg.extract_path[prev_pos] = '\0';
930 fsckcfg.extract_pos = prev_pos;
931 return ret;
932 }
933
erofsfsck_extract_inode(struct erofs_inode * inode)934 static int erofsfsck_extract_inode(struct erofs_inode *inode)
935 {
936 int ret;
937 char *oldpath;
938
939 if (!fsckcfg.extract_path) {
940 verify:
941 /* verify data chunk layout */
942 return erofs_verify_inode_data(inode, -1);
943 }
944
945 oldpath = erofsfsck_hardlink_find(inode->nid);
946 if (oldpath) {
947 if (link(oldpath, fsckcfg.extract_path) == -1) {
948 erofs_err("failed to extract hard link: %s (%s)",
949 fsckcfg.extract_path, strerror(errno));
950 return -errno;
951 }
952 return 0;
953 }
954
955 switch (inode->i_mode & S_IFMT) {
956 case S_IFDIR:
957 ret = erofs_extract_dir(inode);
958 break;
959 case S_IFREG:
960 if (erofs_is_packed_inode(inode))
961 goto verify;
962 ret = erofs_extract_file(inode);
963 break;
964 case S_IFLNK:
965 ret = erofs_extract_symlink(inode);
966 break;
967 case S_IFCHR:
968 case S_IFBLK:
969 case S_IFIFO:
970 case S_IFSOCK:
971 ret = erofs_extract_special(inode);
972 break;
973 default:
974 /* TODO */
975 goto verify;
976 }
977 if (ret && ret != -ECANCELED)
978 return ret;
979
980 /* record nid and old path for hardlink */
981 if (inode->i_nlink > 1 && !S_ISDIR(inode->i_mode))
982 ret = erofsfsck_hardlink_insert(inode->nid,
983 fsckcfg.extract_path);
984 return ret;
985 }
986
erofsfsck_check_inode(erofs_nid_t pnid,erofs_nid_t nid)987 static int erofsfsck_check_inode(erofs_nid_t pnid, erofs_nid_t nid)
988 {
989 int ret;
990 struct erofs_inode inode;
991
992 erofs_dbg("check inode: nid(%llu)", nid | 0ULL);
993
994 inode.nid = nid;
995 inode.sbi = &g_sbi;
996 ret = erofs_read_inode_from_disk(&inode);
997 if (ret) {
998 if (ret == -EIO)
999 erofs_err("I/O error occurred when reading nid(%llu)",
1000 nid | 0ULL);
1001 goto out;
1002 }
1003
1004 if (!(fsckcfg.check_decomp && fsckcfg.dump_xattrs)) {
1005 /* verify xattr field */
1006 ret = erofs_verify_xattr(&inode);
1007 if (ret)
1008 goto out;
1009 }
1010
1011 ret = erofsfsck_extract_inode(&inode);
1012 if (ret && ret != -ECANCELED)
1013 goto out;
1014
1015 if (fsckcfg.check_decomp && fsckcfg.dump_xattrs) {
1016 ret = erofsfsck_dump_xattrs(&inode);
1017 if (ret)
1018 return ret;
1019 }
1020
1021 /* XXXX: the dir depth should be restricted in order to avoid loops */
1022 if (S_ISDIR(inode.i_mode)) {
1023 struct erofs_dir_context ctx = {
1024 .flags = EROFS_READDIR_VALID_PNID,
1025 .pnid = pnid,
1026 .dir = &inode,
1027 .cb = erofsfsck_dirent_iter,
1028 };
1029
1030 ret = erofs_iterate_dir(&ctx, true);
1031 }
1032
1033 if (!ret && !erofs_is_packed_inode(&inode))
1034 erofsfsck_set_attributes(&inode, fsckcfg.extract_path);
1035
1036 if (ret == -ECANCELED)
1037 ret = 0;
1038 out:
1039 if (ret && ret != -EIO)
1040 fsckcfg.corrupted = true;
1041 return ret;
1042 }
1043
1044 #ifdef FUZZING
erofsfsck_fuzz_one(int argc,char * argv[])1045 int erofsfsck_fuzz_one(int argc, char *argv[])
1046 #else
1047 int main(int argc, char *argv[])
1048 #endif
1049 {
1050 int err;
1051
1052 erofs_init_configure();
1053
1054 fsckcfg.physical_blocks = 0;
1055 fsckcfg.logical_blocks = 0;
1056 fsckcfg.extract_path = NULL;
1057 fsckcfg.extract_pos = 0;
1058 fsckcfg.umask = umask(0);
1059 fsckcfg.superuser = geteuid() == 0;
1060 fsckcfg.corrupted = false;
1061 fsckcfg.print_comp_ratio = false;
1062 fsckcfg.check_decomp = false;
1063 fsckcfg.force = false;
1064 fsckcfg.overwrite = false;
1065 fsckcfg.preserve_owner = fsckcfg.superuser;
1066 fsckcfg.preserve_perms = fsckcfg.superuser;
1067 fsckcfg.dump_xattrs = false;
1068
1069 err = erofsfsck_parse_options_cfg(argc, argv);
1070 if (err) {
1071 if (err == -EINVAL)
1072 fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
1073 goto exit;
1074 }
1075
1076 #ifdef FUZZING
1077 cfg.c_dbg_lvl = -1;
1078 #endif
1079
1080 err = erofs_dev_open(&g_sbi, cfg.c_img_path, O_RDONLY);
1081 if (err) {
1082 erofs_err("failed to open image file");
1083 goto exit;
1084 }
1085
1086 err = erofs_read_superblock(&g_sbi);
1087 if (err) {
1088 erofs_err("failed to read superblock");
1089 goto exit_dev_close;
1090 }
1091
1092 if (erofs_sb_has_sb_chksum(&g_sbi) && erofs_check_sb_chksum()) {
1093 erofs_err("failed to verify superblock checksum");
1094 goto exit_put_super;
1095 }
1096
1097 if (fsckcfg.extract_path)
1098 erofsfsck_hardlink_init();
1099
1100 if (erofs_sb_has_fragments(&g_sbi) && g_sbi.packed_nid > 0) {
1101 err = erofsfsck_check_inode(g_sbi.packed_nid, g_sbi.packed_nid);
1102 if (err) {
1103 erofs_err("failed to verify packed file");
1104 goto exit_hardlink;
1105 }
1106 }
1107
1108 err = erofsfsck_check_inode(g_sbi.root_nid, g_sbi.root_nid);
1109 if (fsckcfg.corrupted) {
1110 if (!fsckcfg.extract_path)
1111 erofs_err("Found some filesystem corruption");
1112 else
1113 erofs_err("Failed to extract filesystem");
1114 err = -EFSCORRUPTED;
1115 } else if (!err) {
1116 if (!fsckcfg.extract_path)
1117 erofs_info("No errors found");
1118 else
1119 erofs_info("Extracted filesystem successfully");
1120
1121 if (fsckcfg.print_comp_ratio) {
1122 double comp_ratio =
1123 (double)fsckcfg.physical_blocks * 100 /
1124 (double)fsckcfg.logical_blocks;
1125
1126 erofs_info("Compression ratio: %.2f(%%)", comp_ratio);
1127 }
1128 }
1129
1130 exit_hardlink:
1131 if (fsckcfg.extract_path)
1132 erofsfsck_hardlink_exit();
1133 exit_put_super:
1134 erofs_put_super(&g_sbi);
1135 exit_dev_close:
1136 erofs_dev_close(&g_sbi);
1137 exit:
1138 erofs_blob_closeall(&g_sbi);
1139 erofs_exit_configure();
1140 return err ? 1 : 0;
1141 }
1142
1143 #ifdef FUZZING
LLVMFuzzerTestOneInput(const uint8_t * Data,size_t Size)1144 int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
1145 {
1146 int fd, ret;
1147 char filename[] = "/tmp/erofsfsck_libfuzzer_XXXXXX";
1148 char *argv[] = {
1149 "fsck.erofs",
1150 "--extract",
1151 filename,
1152 };
1153
1154 fd = mkstemp(filename);
1155 if (fd < 0)
1156 return -errno;
1157 if (write(fd, Data, Size) != Size) {
1158 close(fd);
1159 return -EIO;
1160 }
1161 close(fd);
1162 ret = erofsfsck_fuzz_one(ARRAY_SIZE(argv), argv);
1163 unlink(filename);
1164 return ret ? -1 : 0;
1165 }
1166 #endif
1167