• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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