807 lines
19 KiB
C
807 lines
19 KiB
C
// 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 "erofs/print.h"
|
|
#include "erofs/io.h"
|
|
#include "erofs/compress.h"
|
|
#include "erofs/decompress.h"
|
|
#include "erofs/dir.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;
|
|
};
|
|
static struct erofsfsck_cfg fsckcfg;
|
|
|
|
static struct option long_options[] = {
|
|
{"help", no_argument, 0, 1},
|
|
{"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},
|
|
{0, 0, 0, 0},
|
|
};
|
|
|
|
static void print_available_decompressors(FILE *f, const char *delim)
|
|
{
|
|
unsigned int i = 0;
|
|
const char *s;
|
|
|
|
while ((s = z_erofs_list_available_compressors(i)) != NULL) {
|
|
if (i++)
|
|
fputs(delim, f);
|
|
fputs(s, f);
|
|
}
|
|
fputc('\n', f);
|
|
}
|
|
|
|
static void usage(void)
|
|
{
|
|
fputs("usage: [options] IMAGE\n\n"
|
|
"Check erofs filesystem compatibility and integrity of IMAGE, and [options] are:\n"
|
|
" -V print the version number of fsck.erofs and exit\n"
|
|
" -d# set output message level to # (maximum 9)\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 extract to X\n"
|
|
" --help display this help and exit\n"
|
|
"\nExtraction options (--extract=X is required):\n"
|
|
" --force allow extracting to root\n"
|
|
" --overwrite overwrite files that already exist\n"
|
|
" --preserve extract with the same ownership and permissions as on the filesystem\n"
|
|
" (default for superuser)\n"
|
|
" --preserve-owner extract with the same ownership as on the filesystem\n"
|
|
" --preserve-perms extract with the same permissions as on the filesystem\n"
|
|
" --no-preserve extract as yourself and apply user's umask on permissions\n"
|
|
" (default for ordinary users)\n"
|
|
" --no-preserve-owner extract as yourself\n"
|
|
" --no-preserve-perms apply user's umask when extracting permissions\n"
|
|
"\nSupported algorithms are: ", stderr);
|
|
print_available_decompressors(stderr, ", ");
|
|
}
|
|
|
|
static void erofsfsck_print_version(void)
|
|
{
|
|
fprintf(stderr, "fsck.erofs %s\n", cfg.c_version);
|
|
}
|
|
|
|
static int erofsfsck_parse_options_cfg(int argc, char **argv)
|
|
{
|
|
int opt, ret;
|
|
bool has_opt_preserve = false;
|
|
|
|
while ((opt = getopt_long(argc, argv, "Vd:p",
|
|
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 1:
|
|
usage();
|
|
exit(0);
|
|
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--;
|
|
|
|
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 = blob_open_ro(optarg);
|
|
if (ret)
|
|
return ret;
|
|
++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;
|
|
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)
|
|
{
|
|
int ret;
|
|
u8 buf[EROFS_BLKSIZ];
|
|
u32 crc;
|
|
struct erofs_super_block *sb;
|
|
|
|
ret = blk_read(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 - EROFS_SUPER_OFFSET);
|
|
if (crc != sbi.checksum) {
|
|
erofs_err("superblock chksum doesn't match: saved(%08xh) calculated(%08xh)",
|
|
sbi.checksum, crc);
|
|
fsckcfg.corrupted = true;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int erofs_verify_xattr(struct erofs_inode *inode)
|
|
{
|
|
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_BLKSIZ];
|
|
|
|
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 = iloc(inode->nid) + inode->inode_isize;
|
|
ret = dev_read(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(addr) + xattr_hdr_size;
|
|
addr += xattr_hdr_size;
|
|
remaining -= xattr_hdr_size;
|
|
for (i = 0; i < xattr_shared_count; ++i) {
|
|
if (ofs >= EROFS_BLKSIZ) {
|
|
if (ofs != EROFS_BLKSIZ) {
|
|
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 = dev_read(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 erofs_verify_inode_data(struct erofs_inode *inode, int outfd)
|
|
{
|
|
struct erofs_map_blocks map = {
|
|
.index = UINT_MAX,
|
|
};
|
|
struct erofs_map_dev mdev;
|
|
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_FLAT_COMPRESSION_LEGACY:
|
|
case EROFS_INODE_FLAT_COMPRESSION:
|
|
compressed = true;
|
|
break;
|
|
default:
|
|
erofs_err("unknown datalayout");
|
|
return -EINVAL;
|
|
}
|
|
|
|
while (pos < inode->i_size) {
|
|
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_flags & EROFS_MAP_MAPPED) || !fsckcfg.check_decomp)
|
|
continue;
|
|
|
|
if (map.m_plen > raw_size) {
|
|
raw_size = map.m_plen;
|
|
raw = realloc(raw, raw_size);
|
|
BUG_ON(!raw);
|
|
}
|
|
|
|
mdev = (struct erofs_map_dev) {
|
|
.m_deviceid = map.m_deviceid,
|
|
.m_pa = map.m_pa,
|
|
};
|
|
ret = erofs_map_dev(&sbi, &mdev);
|
|
if (ret) {
|
|
erofs_err("failed to map device of m_pa %" PRIu64 ", m_deviceid %u @ nid %llu: %d",
|
|
map.m_pa, map.m_deviceid, inode->nid | 0ULL,
|
|
ret);
|
|
goto out;
|
|
}
|
|
|
|
if (compressed && map.m_llen > buffer_size) {
|
|
buffer_size = map.m_llen;
|
|
buffer = realloc(buffer, buffer_size);
|
|
BUG_ON(!buffer);
|
|
}
|
|
|
|
ret = dev_read(mdev.m_deviceid, raw, mdev.m_pa, map.m_plen);
|
|
if (ret < 0) {
|
|
erofs_err("failed to read data of m_pa %" PRIu64 ", m_plen %" PRIu64 " @ nid %llu: %d",
|
|
mdev.m_pa, map.m_plen, inode->nid | 0ULL,
|
|
ret);
|
|
goto out;
|
|
}
|
|
|
|
if (compressed) {
|
|
struct z_erofs_decompress_req rq = {
|
|
.in = raw,
|
|
.out = buffer,
|
|
.decodedskip = 0,
|
|
.inputsize = map.m_plen,
|
|
.decodedlength = map.m_llen,
|
|
.alg = map.m_algorithmformat,
|
|
.partial_decoding = 0
|
|
};
|
|
|
|
ret = z_erofs_decompress(&rq);
|
|
if (ret < 0) {
|
|
erofs_err("failed to decompress data of m_pa %" PRIu64 ", m_plen %" PRIu64 " @ nid %llu: %s",
|
|
mdev.m_pa, map.m_plen,
|
|
inode->nid | 0ULL, strerror(-ret));
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (outfd >= 0 && write(outfd, compressed ? buffer : raw,
|
|
map.m_llen) < 0) {
|
|
erofs_err("I/O error occurred when verifying data chunk @ nid %llu",
|
|
inode->nid | 0ULL);
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (fsckcfg.print_comp_ratio) {
|
|
fsckcfg.logical_blocks +=
|
|
DIV_ROUND_UP(inode->i_size, EROFS_BLKSIZ);
|
|
fsckcfg.physical_blocks +=
|
|
DIV_ROUND_UP(pchunk_len, EROFS_BLKSIZ);
|
|
}
|
|
out:
|
|
if (raw)
|
|
free(raw);
|
|
if (buffer)
|
|
free(buffer);
|
|
return ret < 0 ? ret : 0;
|
|
}
|
|
|
|
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 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);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (close(fd))
|
|
return -errno;
|
|
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 erofsfsck_dirent_iter(struct erofs_dir_context *ctx)
|
|
{
|
|
int ret;
|
|
size_t prev_pos = fsckcfg.extract_pos;
|
|
|
|
if (ctx->dot_dotdot)
|
|
return 0;
|
|
|
|
if (fsckcfg.extract_path) {
|
|
size_t curr_pos = prev_pos;
|
|
|
|
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';
|
|
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_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;
|
|
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;
|
|
}
|
|
|
|
/* verify xattr field */
|
|
ret = erofs_verify_xattr(&inode);
|
|
if (ret)
|
|
goto out;
|
|
|
|
if (fsckcfg.extract_path) {
|
|
switch (inode.i_mode & S_IFMT) {
|
|
case S_IFDIR:
|
|
ret = erofs_extract_dir(&inode);
|
|
break;
|
|
case S_IFREG:
|
|
ret = erofs_extract_file(&inode);
|
|
break;
|
|
case S_IFLNK:
|
|
ret = erofs_extract_symlink(&inode);
|
|
break;
|
|
default:
|
|
/* TODO */
|
|
goto verify;
|
|
}
|
|
} else {
|
|
verify:
|
|
/* verify data chunk layout */
|
|
ret = erofs_verify_inode_data(&inode, -1);
|
|
}
|
|
if (ret)
|
|
goto out;
|
|
|
|
/* 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)
|
|
erofsfsck_set_attributes(&inode, fsckcfg.extract_path);
|
|
|
|
out:
|
|
if (ret && ret != -EIO)
|
|
fsckcfg.corrupted = true;
|
|
return ret;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
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.preserve_owner = fsckcfg.superuser;
|
|
fsckcfg.preserve_perms = fsckcfg.superuser;
|
|
|
|
err = erofsfsck_parse_options_cfg(argc, argv);
|
|
if (err) {
|
|
if (err == -EINVAL)
|
|
usage();
|
|
goto exit;
|
|
}
|
|
|
|
err = dev_open_ro(cfg.c_img_path);
|
|
if (err) {
|
|
erofs_err("failed to open image file");
|
|
goto exit;
|
|
}
|
|
|
|
err = erofs_read_superblock();
|
|
if (err) {
|
|
erofs_err("failed to read superblock");
|
|
goto exit_dev_close;
|
|
}
|
|
|
|
if (erofs_sb_has_sb_chksum() && erofs_check_sb_chksum()) {
|
|
erofs_err("failed to verify superblock checksum");
|
|
goto exit_dev_close;
|
|
}
|
|
|
|
err = erofsfsck_check_inode(sbi.root_nid, 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);
|
|
}
|
|
}
|
|
|
|
exit_dev_close:
|
|
dev_close();
|
|
exit:
|
|
blob_closeall();
|
|
erofs_exit_configure();
|
|
return err ? 1 : 0;
|
|
}
|