blob: 29f01035dfd294d1b56ba6dd22887873f971474e [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2021 Google LLC
* Author: Daeho Jeong <daehojeong@google.com>
*/
#include <stdlib.h>
#include <getopt.h>
#include <time.h>
#include <utime.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include "erofs/print.h"
#include "erofs/compress.h"
#include "erofs/decompress.h"
#include "erofs/dir.h"
#include "erofs/xattr.h"
#include "fssum.h"
#include "../lib/compressor.h"
static int erofsfsck_check_inode(erofs_nid_t pnid, erofs_nid_t nid);
struct erofsfsck_cfg {
u64 physical_blocks;
u64 logical_blocks;
char *extract_path;
size_t extract_pos;
mode_t umask;
bool superuser;
bool corrupted;
bool print_comp_ratio;
bool check_decomp;
bool force;
bool overwrite;
bool preserve_owner;
bool preserve_perms;
bool dump_xattrs;
bool fssum;
};
static struct erofsfsck_cfg fsckcfg;
static struct option long_options[] = {
{"version", no_argument, 0, 'V'},
{"help", no_argument, 0, 'h'},
{"extract", optional_argument, 0, 2},
{"device", required_argument, 0, 3},
{"force", no_argument, 0, 4},
{"overwrite", no_argument, 0, 5},
{"preserve", no_argument, 0, 6},
{"preserve-owner", no_argument, 0, 7},
{"preserve-perms", no_argument, 0, 8},
{"no-preserve", no_argument, 0, 9},
{"no-preserve-owner", no_argument, 0, 10},
{"no-preserve-perms", no_argument, 0, 11},
{"offset", required_argument, 0, 12},
{"xattrs", no_argument, 0, 13},
{"no-xattrs", no_argument, 0, 14},
{"fssum", no_argument, 0, 13},
{0, 0, 0, 0},
};
#define NR_HARDLINK_HASHTABLE 16384
struct erofsfsck_hardlink_entry {
struct list_head list;
erofs_nid_t nid;
char *path;
};
static struct list_head erofsfsck_link_hashtable[NR_HARDLINK_HASHTABLE];
static void print_available_decompressors(FILE *f, const char *delim)
{
int i = 0;
bool comma = false;
const struct erofs_algorithm *s;
while ((s = z_erofs_list_available_compressors(&i)) != NULL) {
if (comma)
fputs(delim, f);
fputs(s->name, f);
comma = true;
}
fputc('\n', f);
}
static void usage(int argc, char **argv)
{
// " 1 2 3 4 5 6 7 8 "
// "12345678901234567890123456789012345678901234567890123456789012345678901234567890\n"
printf(
"Usage: %s [OPTIONS] IMAGE\n"
"Check erofs filesystem compatibility and integrity of IMAGE.\n"
"\n"
"This version of fsck.erofs is capable of checking images that use any of the\n"
"following algorithms: ", argv[0]);
print_available_decompressors(stdout, ", ");
printf("\n"
"General options:\n"
" -V, --version print the version number of fsck.erofs and exit\n"
" -h, --help display this help and exit\n"
"\n"
" -d<0-9> set output verbosity; 0=quiet, 9=verbose (default=%i)\n"
" -p print total compression ratio of all files\n"
" --device=X specify an extra device to be used together\n"
" --extract[=X] check if all files are well encoded, optionally\n"
" extract to X\n"
" --offset=# skip # bytes at the beginning of IMAGE\n"
" --[no-]xattrs whether to dump extended attributes (default off)\n"
" --fssum calculate the checksum of iamge\n"
"\n"
" -a, -A, -y no-op, for compatibility with fsck of other filesystems\n"
"\n"
"Extraction options (--extract=X is required):\n"
" --force allow extracting to root\n"
" --overwrite overwrite files that already exist\n"
" --[no-]preserve same as --[no-]preserve-owner --[no-]preserve-perms\n"
" --[no-]preserve-owner whether to preserve the ownership from the\n"
" filesystem (default for superuser), or to extract as\n"
" yourself (default for ordinary users)\n"
" --[no-]preserve-perms whether to preserve the exact permissions from the\n"
" filesystem without applying umask (default for\n"
" superuser), or to modify the permissions by applying\n"
" umask (default for ordinary users)\n",
EROFS_WARN);
}
static void erofsfsck_print_version(void)
{
printf("fsck.erofs (erofs-utils) %s\navailable decompressors: ",
cfg.c_version);
print_available_decompressors(stdout, ", ");
}
static int erofsfsck_parse_options_cfg(int argc, char **argv)
{
char *endptr;
int opt, ret;
bool has_opt_preserve = false;
while ((opt = getopt_long(argc, argv, "Vd:phaAy",
long_options, NULL)) != -1) {
switch (opt) {
case 'V':
erofsfsck_print_version();
exit(0);
case 'd':
ret = atoi(optarg);
if (ret < EROFS_MSG_MIN || ret > EROFS_MSG_MAX) {
erofs_err("invalid debug level %d", ret);
return -EINVAL;
}
cfg.c_dbg_lvl = ret;
break;
case 'p':
fsckcfg.print_comp_ratio = true;
break;
case 'h':
usage(argc, argv);
exit(0);
case 'a':
case 'A':
case 'y':
break;
case 2:
fsckcfg.check_decomp = true;
if (optarg) {
size_t len = strlen(optarg);
if (len == 0) {
erofs_err("empty value given for --extract=X");
return -EINVAL;
}
/* remove trailing slashes except root */
while (len > 1 && optarg[len - 1] == '/')
len--;
if (len >= PATH_MAX) {
erofs_err("target directory name too long!");
return -ENAMETOOLONG;
}
fsckcfg.extract_path = malloc(PATH_MAX);
if (!fsckcfg.extract_path)
return -ENOMEM;
strncpy(fsckcfg.extract_path, optarg, len);
fsckcfg.extract_path[len] = '\0';
/* if path is root, start writing from position 0 */
if (len == 1 && fsckcfg.extract_path[0] == '/')
len = 0;
fsckcfg.extract_pos = len;
}
break;
case 3:
ret = erofs_blob_open_ro(&g_sbi, optarg);
if (ret)
return ret;
++g_sbi.extra_devices;
break;
case 4:
fsckcfg.force = true;
break;
case 5:
fsckcfg.overwrite = true;
break;
case 6:
fsckcfg.preserve_owner = fsckcfg.preserve_perms = true;
has_opt_preserve = true;
break;
case 7:
fsckcfg.preserve_owner = true;
has_opt_preserve = true;
break;
case 8:
fsckcfg.preserve_perms = true;
has_opt_preserve = true;
break;
case 9:
fsckcfg.preserve_owner = fsckcfg.preserve_perms = false;
has_opt_preserve = true;
break;
case 10:
fsckcfg.preserve_owner = false;
has_opt_preserve = true;
break;
case 11:
fsckcfg.preserve_perms = false;
has_opt_preserve = true;
break;
case 12:
g_sbi.bdev.offset = strtoull(optarg, &endptr, 0);
if (*endptr != '\0') {
erofs_err("invalid disk offset %s", optarg);
return -EINVAL;
}
break;
case 13:
fsckcfg.dump_xattrs = true;
break;
case 14:
fsckcfg.fssum = true;
break;
default:
return -EINVAL;
}
}
if (fsckcfg.extract_path) {
if (!fsckcfg.extract_pos && !fsckcfg.force) {
erofs_err("--extract=/ must be used together with --force");
return -EINVAL;
}
} else {
if (fsckcfg.force) {
erofs_err("--force must be used together with --extract=X");
return -EINVAL;
}
if (fsckcfg.overwrite) {
erofs_err("--overwrite must be used together with --extract=X");
return -EINVAL;
}
if (has_opt_preserve) {
erofs_err("--[no-]preserve[-owner/-perms] must be used together with --extract=X");
return -EINVAL;
}
}
if (optind >= argc) {
erofs_err("missing argument: IMAGE");
return -EINVAL;
}
cfg.c_img_path = strdup(argv[optind++]);
if (!cfg.c_img_path)
return -ENOMEM;
if (optind < argc) {
erofs_err("unexpected argument: %s", argv[optind]);
return -EINVAL;
}
return 0;
}
static void erofsfsck_set_attributes(struct erofs_inode *inode, char *path)
{
int ret;
/* don't apply attributes when fsck is used without extraction */
if (!fsckcfg.extract_path)
return;
#ifdef HAVE_UTIMENSAT
if (utimensat(AT_FDCWD, path, (struct timespec []) {
[0] = { .tv_sec = inode->i_mtime,
.tv_nsec = inode->i_mtime_nsec },
[1] = { .tv_sec = inode->i_mtime,
.tv_nsec = inode->i_mtime_nsec },
}, AT_SYMLINK_NOFOLLOW) < 0)
#else
if (utime(path, &((struct utimbuf){.actime = inode->i_mtime,
.modtime = inode->i_mtime})) < 0)
#endif
erofs_warn("failed to set times: %s", path);
if (!S_ISLNK(inode->i_mode)) {
if (fsckcfg.preserve_perms)
ret = chmod(path, inode->i_mode);
else
ret = chmod(path, inode->i_mode & ~fsckcfg.umask);
if (ret < 0)
erofs_warn("failed to set permissions: %s", path);
}
if (fsckcfg.preserve_owner) {
ret = lchown(path, inode->i_uid, inode->i_gid);
if (ret < 0)
erofs_warn("failed to change ownership: %s", path);
}
}
static int erofs_check_sb_chksum(void)
{
#ifndef FUZZING
u8 buf[EROFS_MAX_BLOCK_SIZE];
u32 crc;
struct erofs_super_block *sb;
int ret;
ret = erofs_blk_read(&g_sbi, 0, buf, 0, 1);
if (ret) {
erofs_err("failed to read superblock to check checksum: %d",
ret);
return -1;
}
sb = (struct erofs_super_block *)(buf + EROFS_SUPER_OFFSET);
sb->checksum = 0;
crc = erofs_crc32c(~0, (u8 *)sb, erofs_blksiz(&g_sbi) - EROFS_SUPER_OFFSET);
if (crc != g_sbi.checksum) {
erofs_err("superblock chksum doesn't match: saved(%08xh) calculated(%08xh)",
g_sbi.checksum, crc);
fsckcfg.corrupted = true;
return -1;
}
#endif
return 0;
}
static int erofs_verify_xattr(struct erofs_inode *inode)
{
struct erofs_sb_info *sbi = inode->sbi;
unsigned int xattr_hdr_size = sizeof(struct erofs_xattr_ibody_header);
unsigned int xattr_entry_size = sizeof(struct erofs_xattr_entry);
erofs_off_t addr;
unsigned int ofs, xattr_shared_count;
struct erofs_xattr_ibody_header *ih;
struct erofs_xattr_entry *entry;
int i, remaining = inode->xattr_isize, ret = 0;
char buf[EROFS_MAX_BLOCK_SIZE];
if (inode->xattr_isize == xattr_hdr_size) {
erofs_err("xattr_isize %d of nid %llu is not supported yet",
inode->xattr_isize, inode->nid | 0ULL);
ret = -EFSCORRUPTED;
goto out;
} else if (inode->xattr_isize < xattr_hdr_size) {
if (inode->xattr_isize) {
erofs_err("bogus xattr ibody @ nid %llu",
inode->nid | 0ULL);
ret = -EFSCORRUPTED;
goto out;
}
}
addr = erofs_iloc(inode) + inode->inode_isize;
ret = erofs_dev_read(sbi, 0, buf, addr, xattr_hdr_size);
if (ret < 0) {
erofs_err("failed to read xattr header @ nid %llu: %d",
inode->nid | 0ULL, ret);
goto out;
}
ih = (struct erofs_xattr_ibody_header *)buf;
xattr_shared_count = ih->h_shared_count;
ofs = erofs_blkoff(sbi, addr) + xattr_hdr_size;
addr += xattr_hdr_size;
remaining -= xattr_hdr_size;
for (i = 0; i < xattr_shared_count; ++i) {
if (ofs >= erofs_blksiz(sbi)) {
if (ofs != erofs_blksiz(sbi)) {
erofs_err("unaligned xattr entry in xattr shared area @ nid %llu",
inode->nid | 0ULL);
ret = -EFSCORRUPTED;
goto out;
}
ofs = 0;
}
ofs += xattr_entry_size;
addr += xattr_entry_size;
remaining -= xattr_entry_size;
}
while (remaining > 0) {
unsigned int entry_sz;
ret = erofs_dev_read(sbi, 0, buf, addr, xattr_entry_size);
if (ret) {
erofs_err("failed to read xattr entry @ nid %llu: %d",
inode->nid | 0ULL, ret);
goto out;
}
entry = (struct erofs_xattr_entry *)buf;
entry_sz = erofs_xattr_entry_size(entry);
if (remaining < entry_sz) {
erofs_err("xattr on-disk corruption: xattr entry beyond xattr_isize @ nid %llu",
inode->nid | 0ULL);
ret = -EFSCORRUPTED;
goto out;
}
addr += entry_sz;
remaining -= entry_sz;
}
out:
return ret;
}
static int erofsfsck_dump_xattrs(struct erofs_inode *inode)
{
static bool ignore_xattrs = false;
char *keylst, *key;
ssize_t kllen;
int ret;
kllen = erofs_listxattr(inode, NULL, 0);
if (kllen <= 0)
return kllen;
keylst = malloc(kllen);
if (!keylst)
return -ENOMEM;
ret = erofs_listxattr(inode, keylst, kllen);
if (ret != kllen) {
erofs_err("failed to list xattrs @ nid %llu",
inode->nid | 0ULL);
ret = -EINVAL;
goto out;
}
ret = 0;
for (key = keylst; key < keylst + kllen; key += strlen(key) + 1) {
unsigned int index, len;
void *value = NULL;
size_t size = 0;
ret = erofs_getxattr(inode, key, NULL, 0);
if (ret <= 0) {
DBG_BUGON(1);
erofs_err("failed to get xattr value size of `%s` @ nid %llu",
key, inode->nid | 0ULL);
break;
}
size = ret;
value = malloc(size);
if (!value) {
ret = -ENOMEM;
break;
}
ret = erofs_getxattr(inode, key, value, size);
if (ret < 0) {
erofs_err("failed to get xattr `%s` @ nid %llu, because of `%s`", key,
inode->nid | 0ULL, erofs_strerror(ret));
free(value);
break;
}
if (fsckcfg.extract_path)
#ifdef HAVE_LSETXATTR
ret = lsetxattr(fsckcfg.extract_path, key, value, size,
0);
#elif defined(__APPLE__)
ret = setxattr(fsckcfg.extract_path, key, value, size,
0, XATTR_NOFOLLOW);
#else
ret = -EOPNOTSUPP;
#endif
else
ret = 0;
free(value);
if (ret == -EPERM && !fsckcfg.superuser) {
if (__erofs_unlikely(!erofs_xattr_prefix_matches(key,
&index, &len))) {
erofs_err("failed to match the prefix of `%s` @ nid %llu",
key, inode->nid | 0ULL);
ret = -EINVAL;
break;
}
if (index != EROFS_XATTR_INDEX_USER) {
if (!ignore_xattrs) {
erofs_warn("ignored xattr `%s` @ nid %llu, due to non-superuser",
key, inode->nid | 0ULL);
ignore_xattrs = true;
}
ret = 0;
continue;
}
}
if (ret) {
erofs_err("failed to set xattr `%s` @ nid %llu because of `%s`",
key, inode->nid | 0ULL, erofs_strerror(ret));
break;
}
}
out:
free(keylst);
return ret;
}
static int erofs_verify_inode_data(struct erofs_inode *inode, int outfd)
{
struct erofs_map_blocks map = {
.index = UINT_MAX,
};
int ret = 0;
bool compressed;
erofs_off_t pos = 0;
u64 pchunk_len = 0;
unsigned int raw_size = 0, buffer_size = 0;
char *raw = NULL, *buffer = NULL;
erofs_dbg("verify data chunk of nid(%llu): type(%d)",
inode->nid | 0ULL, inode->datalayout);
switch (inode->datalayout) {
case EROFS_INODE_FLAT_PLAIN:
case EROFS_INODE_FLAT_INLINE:
case EROFS_INODE_CHUNK_BASED:
compressed = false;
break;
case EROFS_INODE_COMPRESSED_FULL:
case EROFS_INODE_COMPRESSED_COMPACT:
compressed = true;
break;
default:
erofs_err("unknown datalayout");
return -EINVAL;
}
while (pos < inode->i_size) {
unsigned int alloc_rawsize;
map.m_la = pos;
if (compressed)
ret = z_erofs_map_blocks_iter(inode, &map,
EROFS_GET_BLOCKS_FIEMAP);
else
ret = erofs_map_blocks(inode, &map,
EROFS_GET_BLOCKS_FIEMAP);
if (ret)
goto out;
if (!compressed && map.m_llen != map.m_plen) {
erofs_err("broken chunk length m_la %" PRIu64 " m_llen %" PRIu64 " m_plen %" PRIu64,
map.m_la, map.m_llen, map.m_plen);
ret = -EFSCORRUPTED;
goto out;
}
/* the last lcluster can be divided into 3 parts */
if (map.m_la + map.m_llen > inode->i_size)
map.m_llen = inode->i_size - map.m_la;
pchunk_len += map.m_plen;
pos += map.m_llen;
/* should skip decomp? */
if (map.m_la >= inode->i_size || !fsckcfg.check_decomp)
continue;
if (outfd >= 0 && !(map.m_flags & EROFS_MAP_MAPPED)) {
ret = lseek(outfd, map.m_llen, SEEK_CUR);
if (ret < 0) {
ret = -errno;
goto out;
}
continue;
}
if (map.m_plen > Z_EROFS_PCLUSTER_MAX_SIZE) {
if (compressed) {
erofs_err("invalid pcluster size %" PRIu64 " @ offset %" PRIu64 " of nid %" PRIu64,
map.m_plen, map.m_la,
inode->nid | 0ULL);
ret = -EFSCORRUPTED;
goto out;
}
alloc_rawsize = Z_EROFS_PCLUSTER_MAX_SIZE;
} else {
alloc_rawsize = map.m_plen;
}
if (alloc_rawsize > raw_size) {
char *newraw = realloc(raw, alloc_rawsize);
if (!newraw) {
ret = -ENOMEM;
goto out;
}
raw = newraw;
raw_size = alloc_rawsize;
}
if (compressed) {
if (map.m_llen > buffer_size) {
char *newbuffer;
buffer_size = map.m_llen;
newbuffer = realloc(buffer, buffer_size);
if (!newbuffer) {
ret = -ENOMEM;
goto out;
}
buffer = newbuffer;
}
ret = z_erofs_read_one_data(inode, &map, raw, buffer,
0, map.m_llen, false);
if (ret)
goto out;
if (outfd >= 0 && write(outfd, buffer, map.m_llen) < 0)
goto fail_eio;
} else {
u64 p = 0;
do {
u64 count = min_t(u64, alloc_rawsize,
map.m_llen);
ret = erofs_read_one_data(inode, &map, raw, p, count);
if (ret)
goto out;
if (outfd >= 0 && write(outfd, raw, count) < 0)
goto fail_eio;
map.m_llen -= count;
p += count;
} while (map.m_llen);
}
}
if (fsckcfg.print_comp_ratio) {
if (!erofs_is_packed_inode(inode))
fsckcfg.logical_blocks += BLK_ROUND_UP(inode->sbi, inode->i_size);
fsckcfg.physical_blocks += BLK_ROUND_UP(inode->sbi, pchunk_len);
}
out:
if (raw)
free(raw);
if (buffer)
free(buffer);
return ret < 0 ? ret : 0;
fail_eio:
erofs_err("I/O error occurred when verifying data chunk @ nid %llu",
inode->nid | 0ULL);
ret = -EIO;
goto out;
}
static inline int erofs_extract_dir(struct erofs_inode *inode)
{
int ret;
erofs_dbg("create directory %s", fsckcfg.extract_path);
/* verify data chunk layout */
ret = erofs_verify_inode_data(inode, -1);
if (ret)
return ret;
/*
* Make directory with default user rwx permissions rather than
* the permissions from the filesystem, as these may not have
* write/execute permission. These are fixed up later in
* erofsfsck_set_attributes().
*/
if (mkdir(fsckcfg.extract_path, 0700) < 0) {
struct stat st;
if (errno != EEXIST) {
erofs_err("failed to create directory: %s (%s)",
fsckcfg.extract_path, strerror(errno));
return -errno;
}
if (lstat(fsckcfg.extract_path, &st) ||
!S_ISDIR(st.st_mode)) {
erofs_err("path is not a directory: %s",
fsckcfg.extract_path);
return -ENOTDIR;
}
/*
* Try to change permissions of existing directory so
* that we can write to it
*/
if (chmod(fsckcfg.extract_path, 0700) < 0) {
erofs_err("failed to set permissions: %s (%s)",
fsckcfg.extract_path, strerror(errno));
return -errno;
}
}
return 0;
}
static char *erofsfsck_hardlink_find(erofs_nid_t nid)
{
struct list_head *head =
&erofsfsck_link_hashtable[nid % NR_HARDLINK_HASHTABLE];
struct erofsfsck_hardlink_entry *entry;
list_for_each_entry(entry, head, list)
if (entry->nid == nid)
return entry->path;
return NULL;
}
static int erofsfsck_hardlink_insert(erofs_nid_t nid, const char *path)
{
struct erofsfsck_hardlink_entry *entry;
entry = malloc(sizeof(*entry));
if (!entry)
return -ENOMEM;
entry->nid = nid;
entry->path = strdup(path);
if (!entry->path) {
free(entry);
return -ENOMEM;
}
list_add_tail(&entry->list,
&erofsfsck_link_hashtable[nid % NR_HARDLINK_HASHTABLE]);
return 0;
}
static void erofsfsck_hardlink_init(void)
{
unsigned int i;
for (i = 0; i < NR_HARDLINK_HASHTABLE; ++i)
init_list_head(&erofsfsck_link_hashtable[i]);
}
static void erofsfsck_hardlink_exit(void)
{
struct erofsfsck_hardlink_entry *entry, *n;
struct list_head *head;
unsigned int i;
for (i = 0; i < NR_HARDLINK_HASHTABLE; ++i) {
head = &erofsfsck_link_hashtable[i];
list_for_each_entry_safe(entry, n, head, list) {
if (entry->path)
free(entry->path);
free(entry);
}
}
}
static inline int erofs_extract_file(struct erofs_inode *inode)
{
bool tryagain = true;
int ret, fd;
erofs_dbg("extract file to path: %s", fsckcfg.extract_path);
again:
fd = open(fsckcfg.extract_path,
O_WRONLY | O_CREAT | O_NOFOLLOW |
(fsckcfg.overwrite ? O_TRUNC : O_EXCL), 0700);
if (fd < 0) {
if (fsckcfg.overwrite && tryagain) {
if (errno == EISDIR) {
erofs_warn("try to forcely remove directory %s",
fsckcfg.extract_path);
if (rmdir(fsckcfg.extract_path) < 0) {
erofs_err("failed to remove: %s (%s)",
fsckcfg.extract_path, strerror(errno));
return -EISDIR;
}
} else if (errno == EACCES &&
chmod(fsckcfg.extract_path, 0700) < 0) {
erofs_err("failed to set permissions: %s (%s)",
fsckcfg.extract_path, strerror(errno));
return -errno;
}
tryagain = false;
goto again;
}
erofs_err("failed to open: %s (%s)", fsckcfg.extract_path,
strerror(errno));
return -errno;
}
/* verify data chunk layout */
ret = erofs_verify_inode_data(inode, fd);
close(fd);
return ret;
}
static inline int erofs_extract_symlink(struct erofs_inode *inode)
{
bool tryagain = true;
int ret;
char *buf = NULL;
erofs_dbg("extract symlink to path: %s", fsckcfg.extract_path);
/* verify data chunk layout */
ret = erofs_verify_inode_data(inode, -1);
if (ret)
return ret;
buf = malloc(inode->i_size + 1);
if (!buf) {
ret = -ENOMEM;
goto out;
}
ret = erofs_pread(inode, buf, inode->i_size, 0);
if (ret) {
erofs_err("I/O error occurred when reading symlink @ nid %llu: %d",
inode->nid | 0ULL, ret);
goto out;
}
buf[inode->i_size] = '\0';
again:
if (symlink(buf, fsckcfg.extract_path) < 0) {
if (errno == EEXIST && fsckcfg.overwrite && tryagain) {
erofs_warn("try to forcely remove file %s",
fsckcfg.extract_path);
if (unlink(fsckcfg.extract_path) < 0) {
erofs_err("failed to remove: %s",
fsckcfg.extract_path);
ret = -errno;
goto out;
}
tryagain = false;
goto again;
}
erofs_err("failed to create symlink: %s",
fsckcfg.extract_path);
ret = -errno;
}
out:
if (buf)
free(buf);
return ret;
}
static int erofs_extract_special(struct erofs_inode *inode)
{
bool tryagain = true;
int ret;
erofs_dbg("extract special to path: %s", fsckcfg.extract_path);
/* verify data chunk layout */
ret = erofs_verify_inode_data(inode, -1);
if (ret)
return ret;
again:
if (mknod(fsckcfg.extract_path, inode->i_mode, inode->u.i_rdev) < 0) {
if (errno == EEXIST && fsckcfg.overwrite && tryagain) {
erofs_warn("try to forcely remove file %s",
fsckcfg.extract_path);
if (unlink(fsckcfg.extract_path) < 0) {
erofs_err("failed to remove: %s",
fsckcfg.extract_path);
return -errno;
}
tryagain = false;
goto again;
}
if (errno == EEXIST || fsckcfg.superuser) {
erofs_err("failed to create special file: %s",
fsckcfg.extract_path);
ret = -errno;
} else {
erofs_warn("failed to create special file: %s, skipped",
fsckcfg.extract_path);
ret = -ECANCELED;
}
}
return ret;
}
static int erofsfsck_dirent_iter(struct erofs_dir_context *ctx)
{
int ret;
size_t prev_pos, curr_pos;
if (ctx->dot_dotdot)
return 0;
prev_pos = fsckcfg.extract_pos;
curr_pos = prev_pos;
if (prev_pos + ctx->de_namelen >= PATH_MAX) {
erofs_err("unable to fsck since the path is too long (%llu)",
(curr_pos + ctx->de_namelen) | 0ULL);
return -EOPNOTSUPP;
}
if (fsckcfg.extract_path) {
fsckcfg.extract_path[curr_pos++] = '/';
strncpy(fsckcfg.extract_path + curr_pos, ctx->dname,
ctx->de_namelen);
curr_pos += ctx->de_namelen;
fsckcfg.extract_path[curr_pos] = '\0';
} else {
curr_pos += ctx->de_namelen;
}
fsckcfg.extract_pos = curr_pos;
ret = erofsfsck_check_inode(ctx->dir->nid, ctx->de_nid);
if (fsckcfg.extract_path)
fsckcfg.extract_path[prev_pos] = '\0';
fsckcfg.extract_pos = prev_pos;
return ret;
}
static int erofsfsck_extract_inode(struct erofs_inode *inode)
{
int ret;
char *oldpath;
if (!fsckcfg.extract_path) {
verify:
/* verify data chunk layout */
return erofs_verify_inode_data(inode, -1);
}
oldpath = erofsfsck_hardlink_find(inode->nid);
if (oldpath) {
if (link(oldpath, fsckcfg.extract_path) == -1) {
erofs_err("failed to extract hard link: %s (%s)",
fsckcfg.extract_path, strerror(errno));
return -errno;
}
return 0;
}
switch (inode->i_mode & S_IFMT) {
case S_IFDIR:
ret = erofs_extract_dir(inode);
break;
case S_IFREG:
if (erofs_is_packed_inode(inode))
goto verify;
ret = erofs_extract_file(inode);
break;
case S_IFLNK:
ret = erofs_extract_symlink(inode);
break;
case S_IFCHR:
case S_IFBLK:
case S_IFIFO:
case S_IFSOCK:
ret = erofs_extract_special(inode);
break;
default:
/* TODO */
goto verify;
}
if (ret && ret != -ECANCELED)
return ret;
/* record nid and old path for hardlink */
if (inode->i_nlink > 1 && !S_ISDIR(inode->i_mode))
ret = erofsfsck_hardlink_insert(inode->nid,
fsckcfg.extract_path);
return ret;
}
static int erofsfsck_check_inode(erofs_nid_t pnid, erofs_nid_t nid)
{
int ret;
struct erofs_inode inode;
erofs_dbg("check inode: nid(%llu)", nid | 0ULL);
inode.nid = nid;
inode.sbi = &g_sbi;
ret = erofs_read_inode_from_disk(&inode);
if (ret) {
if (ret == -EIO)
erofs_err("I/O error occurred when reading nid(%llu)",
nid | 0ULL);
goto out;
}
if (!(fsckcfg.check_decomp && fsckcfg.dump_xattrs)) {
/* verify xattr field */
ret = erofs_verify_xattr(&inode);
if (ret)
goto out;
}
ret = erofsfsck_extract_inode(&inode);
if (ret && ret != -ECANCELED)
goto out;
if (fsckcfg.check_decomp && fsckcfg.dump_xattrs) {
ret = erofsfsck_dump_xattrs(&inode);
if (ret)
return ret;
}
/* XXXX: the dir depth should be restricted in order to avoid loops */
if (S_ISDIR(inode.i_mode)) {
struct erofs_dir_context ctx = {
.flags = EROFS_READDIR_VALID_PNID,
.pnid = pnid,
.dir = &inode,
.cb = erofsfsck_dirent_iter,
};
ret = erofs_iterate_dir(&ctx, true);
}
if (!ret && !erofs_is_packed_inode(&inode))
erofsfsck_set_attributes(&inode, fsckcfg.extract_path);
if (ret == -ECANCELED)
ret = 0;
out:
if (ret && ret != -EIO)
fsckcfg.corrupted = true;
return ret;
}
static int erofsfsck_sum_image(struct erofs_sb_info *sbi)
{
struct erofs_dir_context ctx = {
.flags = 0,
.pnid = 0,
.dir = NULL,
.de_nid = sbi->root_nid,
.dname = "",
.de_namelen = 0,
};
return erofs_fssum_calculate(&ctx);
}
#ifdef FUZZING
int erofsfsck_fuzz_one(int argc, char *argv[])
#else
int main(int argc, char *argv[])
#endif
{
int err;
erofs_init_configure();
fsckcfg.physical_blocks = 0;
fsckcfg.logical_blocks = 0;
fsckcfg.extract_path = NULL;
fsckcfg.extract_pos = 0;
fsckcfg.umask = umask(0);
fsckcfg.superuser = geteuid() == 0;
fsckcfg.corrupted = false;
fsckcfg.print_comp_ratio = false;
fsckcfg.check_decomp = false;
fsckcfg.force = false;
fsckcfg.overwrite = false;
fsckcfg.fssum = false;
fsckcfg.preserve_owner = fsckcfg.superuser;
fsckcfg.preserve_perms = fsckcfg.superuser;
fsckcfg.dump_xattrs = false;
err = erofsfsck_parse_options_cfg(argc, argv);
if (err) {
if (err == -EINVAL)
fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
goto exit;
}
#ifdef FUZZING
cfg.c_dbg_lvl = -1;
#endif
err = erofs_dev_open(&g_sbi, cfg.c_img_path, O_RDONLY);
if (err) {
erofs_err("failed to open image file");
goto exit;
}
err = erofs_read_superblock(&g_sbi);
if (err) {
erofs_err("failed to read superblock");
goto exit_dev_close;
}
if (erofs_sb_has_sb_chksum(&g_sbi) && erofs_check_sb_chksum()) {
erofs_err("failed to verify superblock checksum");
goto exit_put_super;
}
if (fsckcfg.extract_path)
erofsfsck_hardlink_init();
if (erofs_sb_has_fragments(&g_sbi) && g_sbi.packed_nid > 0) {
err = erofsfsck_check_inode(g_sbi.packed_nid, g_sbi.packed_nid);
if (err) {
erofs_err("failed to verify packed file");
goto exit_hardlink;
}
}
err = erofsfsck_check_inode(g_sbi.root_nid, g_sbi.root_nid);
if (fsckcfg.corrupted) {
if (!fsckcfg.extract_path)
erofs_err("Found some filesystem corruption");
else
erofs_err("Failed to extract filesystem");
err = -EFSCORRUPTED;
} else if (!err) {
if (!fsckcfg.extract_path)
erofs_info("No errors found");
else
erofs_info("Extracted filesystem successfully");
if (fsckcfg.print_comp_ratio) {
double comp_ratio =
(double)fsckcfg.physical_blocks * 100 /
(double)fsckcfg.logical_blocks;
erofs_info("Compression ratio: %.2f(%%)", comp_ratio);
}
}
if (fsckcfg.fssum) {
err = erofsfsck_sum_image(&g_sbi);
if (err)
erofs_err("fssum calculation for image falied");
}
exit_hardlink:
if (fsckcfg.extract_path)
erofsfsck_hardlink_exit();
exit_put_super:
erofs_put_super(&g_sbi);
exit_dev_close:
erofs_dev_close(&g_sbi);
exit:
erofs_blob_closeall(&g_sbi);
erofs_exit_configure();
return err ? 1 : 0;
}
#ifdef FUZZING
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
{
int fd, ret;
char filename[] = "/tmp/erofsfsck_libfuzzer_XXXXXX";
char *argv[] = {
"fsck.erofs",
"--extract",
filename,
};
fd = mkstemp(filename);
if (fd < 0)
return -errno;
if (write(fd, Data, Size) != Size) {
close(fd);
return -EIO;
}
close(fd);
ret = erofsfsck_fuzz_one(ARRAY_SIZE(argv), argv);
unlink(filename);
return ret ? -1 : 0;
}
#endif