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