An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
720 lines
24 KiB
C
720 lines
24 KiB
C
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// schfs_fsck — inspect, verify and extract a SchFS image.
|
|
//
|
|
// "fsck" is a slight misnomer: copy-on-write means there is nothing to repair.
|
|
// A crash leaves either the old tree or the new one, so a volume is either
|
|
// consistent or it has no valid uberblock at all. What this does is *check* --
|
|
// and, just as usefully, read an image back out so its contents can be compared
|
|
// against whatever produced it.
|
|
//
|
|
// schfs_fsck img --info uberblock ring and volume summary
|
|
// schfs_fsck img --check full structural verification
|
|
// schfs_fsck img --ls PATH list a directory
|
|
// schfs_fsck img --stat PATH one object's metadata
|
|
// schfs_fsck img --cat PATH a file's contents to stdout
|
|
// schfs_fsck img --extract DIR the whole tree onto the host
|
|
// schfs_fsck img --tree every path in the volume
|
|
//
|
|
// --extract is what makes the round-trip test possible, and the reason this
|
|
// reads the image through the same code the kernel will use: if that code is
|
|
// wrong, the extracted tree differs from the source tree and diff says so.
|
|
|
|
#define _FILE_OFFSET_BITS 64
|
|
#define SCHFS_HOSTED 1
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "../kernel/fs/schfs/btree.c"
|
|
#include "../kernel/fs/schfs/space.c"
|
|
#include "../kernel/fs/schfs/vol.c"
|
|
#include "../kernel/fs/schfs/obj.c"
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
struct imgdev {
|
|
FILE *f;
|
|
uint64_t nblocks;
|
|
};
|
|
|
|
static struct imgdev IMG;
|
|
|
|
static int
|
|
img_read(void *ctx, uint64_t b, void *buf)
|
|
{
|
|
struct imgdev *d = ctx;
|
|
if (b >= d->nblocks)
|
|
return -1;
|
|
if (fseeko(d->f, (off_t)b * SCHFS_BLKSIZE, SEEK_SET) != 0)
|
|
return -1;
|
|
if (fread(buf, 1, SCHFS_BLKSIZE, d->f) != SCHFS_BLKSIZE)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
img_write(void *ctx, uint64_t b, const void *buf)
|
|
{
|
|
(void)ctx; (void)b; (void)buf;
|
|
return -1; /* this tool never writes */
|
|
}
|
|
|
|
static uint64_t img_alloc(void *ctx) { (void)ctx; return 0; }
|
|
static void img_nofree(void *ctx, uint64_t b) { (void)ctx; (void)b; }
|
|
|
|
// Pages for the allocation map --check derives. Nothing is written back; this
|
|
// is a check on the map, not a use of it.
|
|
static void *img_palloc(void) { return calloc(1, SCHFS_BLKSIZE); }
|
|
|
|
static struct schfs_vol VOL;
|
|
static char CURBLK[SCHFS_BLKSIZE];
|
|
static char DATABLK[SCHFS_BLKSIZE];
|
|
|
|
// Read-only, so only the first of these is ever touched — but a volume owns all
|
|
// three for its whole life, and handing it two null pointers would turn a
|
|
// mistake into a crash rather than a compile error.
|
|
static char CTX_A[SCHFS_BLKSIZE];
|
|
static char CTX_B[SCHFS_BLKSIZE];
|
|
static char CTX_C[SCHFS_BLKSIZE];
|
|
static struct schfs_ctx CTX = { CTX_A, CTX_B, CTX_C };
|
|
|
|
static const char *
|
|
typename(uint16_t t)
|
|
{
|
|
switch (t) {
|
|
case SCHFS_T_FILE: return "file";
|
|
case SCHFS_T_DIR: return "dir";
|
|
case SCHFS_T_DEV: return "dev";
|
|
case SCHFS_T_SYMLINK: return "symlink";
|
|
default: return "?";
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* --info */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static int
|
|
do_info(void)
|
|
{
|
|
printf("uberblock ring (%d slots from block %d)\n",
|
|
SCHFS_UBER_COUNT, SCHFS_UBER_START);
|
|
|
|
int valid = 0, best = -1;
|
|
uint64_t bestgen = 0;
|
|
for (int i = 0; i < SCHFS_UBER_COUNT; i++) {
|
|
struct schfs_uberblock *u = (struct schfs_uberblock *)CTX_A;
|
|
if (img_read(&IMG, SCHFS_UBER_START + i, u) != 0)
|
|
continue;
|
|
if (u->magic != SCHFS_MAGIC)
|
|
continue;
|
|
uint32_t stored = u->crc;
|
|
u->crc = 0;
|
|
uint32_t calc = schfs_crc32c(0, u, sizeof(*u));
|
|
u->crc = stored;
|
|
if (calc != stored) {
|
|
printf(" slot %3d: magic ok, CHECKSUM BAD (generation %llu)\n",
|
|
i, (unsigned long long)u->generation);
|
|
continue;
|
|
}
|
|
valid++;
|
|
if (best < 0 || u->generation > bestgen) {
|
|
bestgen = u->generation;
|
|
best = i;
|
|
}
|
|
}
|
|
printf(" %d slot%s valid; newest is slot %d at generation %llu\n\n",
|
|
valid, valid == 1 ? "" : "s", best, (unsigned long long)bestgen);
|
|
|
|
printf("volume\n");
|
|
printf(" label %s\n", VOL.label);
|
|
printf(" uuid ");
|
|
for (int i = 0; i < 16; i++)
|
|
printf("%02x%s", VOL.uuid[i], (i == 3 || i == 5 || i == 7 || i == 9) ? "-" : "");
|
|
printf("\n");
|
|
printf(" block size %d bytes\n", SCHFS_BLKSIZE);
|
|
printf(" total blocks %llu (%llu KB)\n",
|
|
(unsigned long long)VOL.total_blocks,
|
|
(unsigned long long)VOL.total_blocks * SCHFS_BLKSIZE / 1024);
|
|
printf(" free blocks %llu (%llu KB)\n",
|
|
(unsigned long long)VOL.free_blocks,
|
|
(unsigned long long)VOL.free_blocks * SCHFS_BLKSIZE / 1024);
|
|
printf(" tree root block %llu, generation %llu\n",
|
|
(unsigned long long)VOL.tree.root,
|
|
(unsigned long long)VOL.tree.generation);
|
|
printf(" next object id %llu\n", (unsigned long long)VOL.next_objectid);
|
|
printf(" root object id %llu\n", (unsigned long long)VOL.root_objectid);
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* --check */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
struct tally {
|
|
long inodes, dirents, extents, csums, other;
|
|
long long bytes;
|
|
int bad;
|
|
};
|
|
|
|
static int
|
|
count_rec(const struct schfs_key *k, const void *val, unsigned len, void *arg)
|
|
{
|
|
struct tally *t = arg;
|
|
(void)val;
|
|
switch (k->type) {
|
|
case SCHFS_KEY_INODE:
|
|
t->inodes++;
|
|
if (len != sizeof(struct schfs_inode)) {
|
|
fprintf(stderr, " object %llu: inode record is %u bytes, expected %zu\n",
|
|
(unsigned long long)k->objectid, len,
|
|
sizeof(struct schfs_inode));
|
|
t->bad++;
|
|
} else {
|
|
t->bytes += ((const struct schfs_inode *)val)->size;
|
|
}
|
|
break;
|
|
case SCHFS_KEY_DIRENT: t->dirents++; break;
|
|
case SCHFS_KEY_EXTENT:
|
|
t->extents++;
|
|
if (len != sizeof(struct schfs_extent)) {
|
|
fprintf(stderr, " object %llu: extent record is %u bytes\n",
|
|
(unsigned long long)k->objectid, len);
|
|
t->bad++;
|
|
}
|
|
break;
|
|
case SCHFS_KEY_CSUM: t->csums++; break;
|
|
default: t->other++; break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Verify every data block against its stored checksum, by reading every file
|
|
in full. This is the part a metadata-only check would miss entirely. */
|
|
static long files_checked, blocks_checked;
|
|
|
|
// Chain of object ids from the root down to whatever is being checked.
|
|
//
|
|
// A directory that is its own ancestor makes any recursive walk run until the
|
|
// stack runs out, and a segfault is a terrible way to be told the filesystem
|
|
// has a loop. Directories cannot be hard-linked, so a repeat in this chain is
|
|
// always a fault -- and the first version of mkschfs produced exactly one, by
|
|
// handing a subdirectory the root's object id.
|
|
#define MAX_DEPTH 64
|
|
static uint64_t chain[MAX_DEPTH];
|
|
static int chain_n;
|
|
|
|
static int
|
|
chain_push(uint64_t ino, const char *path)
|
|
{
|
|
for (int i = 0; i < chain_n; i++)
|
|
if (chain[i] == ino) {
|
|
fprintf(stderr, " %s: directory loop -- object %llu is its own "
|
|
"ancestor\n", path, (unsigned long long)ino);
|
|
return -1;
|
|
}
|
|
if (chain_n >= MAX_DEPTH) {
|
|
fprintf(stderr, " %s: directory nesting deeper than %d\n",
|
|
path, MAX_DEPTH);
|
|
return -1;
|
|
}
|
|
chain[chain_n++] = ino;
|
|
return 0;
|
|
}
|
|
|
|
static void chain_pop(void) { if (chain_n) chain_n--; }
|
|
|
|
static int check_dir(uint64_t ino, const char *path);
|
|
|
|
static int
|
|
check_entry(uint64_t ino, uint16_t type, const char *name, unsigned namelen,
|
|
void *arg)
|
|
{
|
|
const char *parent = arg;
|
|
char path[8192];
|
|
snprintf(path, sizeof(path), "%s%s%.*s",
|
|
parent, strcmp(parent, "/") == 0 ? "" : "/", (int)namelen, name);
|
|
|
|
struct schfs_cursor c;
|
|
schfs_cursor_init(&c, &VOL.tree, CURBLK);
|
|
struct schfs_inode in;
|
|
if (schfs_inode_get(&VOL, &c, ino, &in) != 0) {
|
|
fprintf(stderr, " %s: entry points at object %llu, which has no inode\n",
|
|
path, (unsigned long long)ino);
|
|
return -1;
|
|
}
|
|
if (in.type != type)
|
|
fprintf(stderr, " %s: dirent says %s, inode says %s\n",
|
|
path, typename(type), typename(in.type));
|
|
|
|
if (in.type == SCHFS_T_DIR)
|
|
return check_dir(ino, path);
|
|
|
|
uint64_t off = 0;
|
|
while (off < in.size) {
|
|
// A full block is requested every time, even at the tail. Clamping
|
|
// here instead would mean nothing ever tests that schfs_file_read
|
|
// stops at end-of-file -- and the kernel's read(2) will depend on
|
|
// exactly that, since a real program just asks for a bufferful.
|
|
char buf[SCHFS_BLKSIZE];
|
|
unsigned want = sizeof(buf);
|
|
long got = schfs_file_read(&VOL, &c, ino, off, buf, want, DATABLK);
|
|
if (got <= 0) {
|
|
fprintf(stderr, " %s: read failed at offset %llu (%ld)\n",
|
|
path, (unsigned long long)off, got);
|
|
return -1;
|
|
}
|
|
off += (uint64_t)got;
|
|
blocks_checked++;
|
|
}
|
|
files_checked++;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
check_dir(uint64_t ino, const char *path)
|
|
{
|
|
if (chain_push(ino, path) != 0)
|
|
return -1;
|
|
struct schfs_cursor c;
|
|
schfs_cursor_init(&c, &VOL.tree, CURBLK);
|
|
int r = schfs_dir_iterate(&VOL, &c, ino, check_entry, (void *)path);
|
|
chain_pop();
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
do_check(void)
|
|
{
|
|
printf("checking %s\n", VOL.label);
|
|
|
|
if (schfs_btree_check(&VOL.tree) != 0) {
|
|
fprintf(stderr, " FAIL: tree structure is invalid\n");
|
|
return 1;
|
|
}
|
|
printf(" [ok] tree structure, checksums and key ordering\n");
|
|
|
|
struct tally t;
|
|
memset(&t, 0, sizeof(t));
|
|
if (schfs_btree_iterate(&VOL.tree, count_rec, &t) != 0) {
|
|
fprintf(stderr, " FAIL: could not walk every record\n");
|
|
return 1;
|
|
}
|
|
if (t.bad) {
|
|
fprintf(stderr, " FAIL: %d malformed records\n", t.bad);
|
|
return 1;
|
|
}
|
|
printf(" [ok] %ld inodes, %ld dirent items, %ld extents, %ld data checksums\n",
|
|
t.inodes, t.dirents, t.extents, t.csums);
|
|
|
|
if (check_dir(VOL.root_objectid, "/") != 0) {
|
|
fprintf(stderr, " FAIL: walking the directory tree found a problem\n");
|
|
return 1;
|
|
}
|
|
printf(" [ok] %ld files read end to end, every data block matched its checksum\n",
|
|
files_checked);
|
|
|
|
// Rebuild the allocation map the way a mount would, and hold it against the
|
|
// count the writer left in the uberblock. The map is derived rather than
|
|
// stored, so nothing else would ever notice if the scan and the writer
|
|
// disagreed — right up until a mounted volume handed out a block that was
|
|
// still in use.
|
|
if (schfs_space_init(&VOL.space, VOL.total_blocks, img_palloc) != 0) {
|
|
fprintf(stderr, " FAIL: could not build an allocation map\n");
|
|
return 1;
|
|
}
|
|
if (schfs_space_scan(&VOL) != 0) {
|
|
fprintf(stderr, " FAIL: could not walk the tree to derive free space\n");
|
|
return 1;
|
|
}
|
|
if (VOL.space.free_blocks != VOL.free_blocks) {
|
|
fprintf(stderr, " FAIL: uberblock says %llu blocks free, walking the "
|
|
"tree says %llu\n",
|
|
(unsigned long long)VOL.free_blocks,
|
|
(unsigned long long)VOL.space.free_blocks);
|
|
return 1;
|
|
}
|
|
printf(" [ok] free space derived from the tree matches the uberblock "
|
|
"(%llu of %llu blocks in use)\n",
|
|
(unsigned long long)(VOL.total_blocks - VOL.space.free_blocks),
|
|
(unsigned long long)VOL.total_blocks);
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* --ls / --stat / --cat / --tree */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static int
|
|
print_entry(uint64_t ino, uint16_t type, const char *name, unsigned namelen,
|
|
void *arg)
|
|
{
|
|
(void)arg;
|
|
struct schfs_cursor c;
|
|
schfs_cursor_init(&c, &VOL.tree, CURBLK);
|
|
struct schfs_inode in;
|
|
if (schfs_inode_get(&VOL, &c, ino, &in) != 0) {
|
|
printf(" ? %.*s\n", (int)namelen, name);
|
|
return 0;
|
|
}
|
|
printf(" %-7s %6llu %4o %2u links %.*s\n",
|
|
typename(type), (unsigned long long)in.size, in.mode & 07777,
|
|
in.nlink, (int)namelen, name);
|
|
return 0;
|
|
}
|
|
|
|
static uint64_t
|
|
resolve(const char *path)
|
|
{
|
|
struct schfs_cursor c;
|
|
schfs_cursor_init(&c, &VOL.tree, CURBLK);
|
|
uint64_t ino = schfs_path_lookup(&VOL, &c, path);
|
|
if (ino == 0)
|
|
fprintf(stderr, "schfs_fsck: %s: no such file or directory\n", path);
|
|
return ino;
|
|
}
|
|
|
|
static int
|
|
do_ls(const char *path)
|
|
{
|
|
uint64_t ino = resolve(path);
|
|
if (!ino)
|
|
return 1;
|
|
struct schfs_cursor c;
|
|
schfs_cursor_init(&c, &VOL.tree, CURBLK);
|
|
printf("%s\n", path);
|
|
return schfs_dir_iterate(&VOL, &c, ino, print_entry, NULL) != 0;
|
|
}
|
|
|
|
static int
|
|
do_stat(const char *path)
|
|
{
|
|
uint64_t ino = resolve(path);
|
|
if (!ino)
|
|
return 1;
|
|
struct schfs_cursor c;
|
|
schfs_cursor_init(&c, &VOL.tree, CURBLK);
|
|
struct schfs_inode in;
|
|
if (schfs_inode_get(&VOL, &c, ino, &in) != 0) {
|
|
fprintf(stderr, "schfs_fsck: %s: inode unreadable\n", path);
|
|
return 1;
|
|
}
|
|
printf("path: %s\n", path);
|
|
printf("objectid: %llu\n", (unsigned long long)ino);
|
|
printf("type: %s\n", typename(in.type));
|
|
printf("size: %llu\n", (unsigned long long)in.size);
|
|
printf("blocks: %llu\n", (unsigned long long)in.blocks);
|
|
printf("mode: %04o\n", in.mode & 07777);
|
|
printf("nlink: %u\n", in.nlink);
|
|
printf("uid: %u\n", in.uid);
|
|
printf("gid: %u\n", in.gid);
|
|
printf("mtime: %llu\n", (unsigned long long)in.mtime);
|
|
printf("parent: %llu\n", (unsigned long long)in.parent);
|
|
if (in.type == SCHFS_T_DEV)
|
|
printf("rdev: %u:%u\n", in.rdev_major, in.rdev_minor);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
do_cat(const char *path)
|
|
{
|
|
uint64_t ino = resolve(path);
|
|
if (!ino)
|
|
return 1;
|
|
struct schfs_cursor c;
|
|
schfs_cursor_init(&c, &VOL.tree, CURBLK);
|
|
struct schfs_inode in;
|
|
if (schfs_inode_get(&VOL, &c, ino, &in) != 0)
|
|
return 1;
|
|
|
|
uint64_t off = 0;
|
|
while (off < in.size) {
|
|
// A full block is requested every time, even at the tail. Clamping
|
|
// here instead would mean nothing ever tests that schfs_file_read
|
|
// stops at end-of-file -- and the kernel's read(2) will depend on
|
|
// exactly that, since a real program just asks for a bufferful.
|
|
char buf[SCHFS_BLKSIZE];
|
|
unsigned want = sizeof(buf);
|
|
long got = schfs_file_read(&VOL, &c, ino, off, buf, want, DATABLK);
|
|
if (got <= 0) {
|
|
fprintf(stderr, "schfs_fsck: %s: read error at %llu\n",
|
|
path, (unsigned long long)off);
|
|
return 1;
|
|
}
|
|
fwrite(buf, 1, (size_t)got, stdout);
|
|
off += (uint64_t)got;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int tree_walk(uint64_t ino, const char *path);
|
|
|
|
static int
|
|
tree_entry(uint64_t ino, uint16_t type, const char *name, unsigned namelen,
|
|
void *arg)
|
|
{
|
|
const char *parent = arg;
|
|
char path[8192];
|
|
snprintf(path, sizeof(path), "%s%s%.*s",
|
|
parent, strcmp(parent, "/") == 0 ? "" : "/", (int)namelen, name);
|
|
printf("%-8s %s\n", typename(type), path);
|
|
if (type == SCHFS_T_DIR)
|
|
return tree_walk(ino, path);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
tree_walk(uint64_t ino, const char *path)
|
|
{
|
|
if (chain_push(ino, path) != 0)
|
|
return -1;
|
|
struct schfs_cursor c;
|
|
schfs_cursor_init(&c, &VOL.tree, CURBLK);
|
|
int r = schfs_dir_iterate(&VOL, &c, ino, tree_entry, (void *)path);
|
|
chain_pop();
|
|
return r;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* --extract */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
// Objects already written out, so a hard link on the image becomes a hard link
|
|
// on the host rather than a second copy. Without this the round-trip test's
|
|
// link-count check could not distinguish dedup from duplication.
|
|
struct seen_ent { uint64_t ino; char *path; };
|
|
static struct seen_ent *seen;
|
|
static int seen_n, seen_cap;
|
|
|
|
static const char *
|
|
seen_lookup(uint64_t ino)
|
|
{
|
|
for (int i = 0; i < seen_n; i++)
|
|
if (seen[i].ino == ino)
|
|
return seen[i].path;
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
seen_add(uint64_t ino, const char *path)
|
|
{
|
|
if (seen_n == seen_cap) {
|
|
seen_cap = seen_cap ? seen_cap * 2 : 128;
|
|
seen = realloc(seen, (size_t)seen_cap * sizeof(*seen));
|
|
if (!seen) { perror("realloc"); exit(1); }
|
|
}
|
|
seen[seen_n].ino = ino;
|
|
seen[seen_n].path = strdup(path);
|
|
seen_n++;
|
|
}
|
|
|
|
static long xfiles, xdirs, xlinks, xsymlinks;
|
|
|
|
static int extract_dir(uint64_t ino, const char *hostpath);
|
|
|
|
static int
|
|
extract_entry(uint64_t ino, uint16_t type, const char *name, unsigned namelen,
|
|
void *arg)
|
|
{
|
|
const char *parent = arg;
|
|
char host[8192];
|
|
(void)type; /* the inode is authoritative, not the dirent */
|
|
snprintf(host, sizeof(host), "%s/%.*s", parent, (int)namelen, name);
|
|
|
|
struct schfs_cursor c;
|
|
schfs_cursor_init(&c, &VOL.tree, CURBLK);
|
|
struct schfs_inode in;
|
|
if (schfs_inode_get(&VOL, &c, ino, &in) != 0) {
|
|
fprintf(stderr, "schfs_fsck: %s: inode %llu unreadable\n",
|
|
host, (unsigned long long)ino);
|
|
return -1;
|
|
}
|
|
|
|
if (in.type == SCHFS_T_DIR) {
|
|
if (mkdir(host, in.mode & 07777) != 0 && errno != EEXIST) {
|
|
perror(host);
|
|
return -1;
|
|
}
|
|
xdirs++;
|
|
return extract_dir(ino, host);
|
|
}
|
|
|
|
if (in.type == SCHFS_T_SYMLINK) {
|
|
char target[4096];
|
|
if (in.size >= sizeof(target)) {
|
|
fprintf(stderr, "schfs_fsck: %s: symlink target too long\n", host);
|
|
return -1;
|
|
}
|
|
long got = schfs_file_read(&VOL, &c, ino, 0, target,
|
|
(unsigned)in.size, DATABLK);
|
|
if (got < 0 || (uint64_t)got != in.size) {
|
|
fprintf(stderr, "schfs_fsck: %s: could not read symlink target\n", host);
|
|
return -1;
|
|
}
|
|
target[in.size] = '\0';
|
|
unlink(host);
|
|
if (symlink(target, host) != 0) { perror(host); return -1; }
|
|
xsymlinks++;
|
|
return 0;
|
|
}
|
|
|
|
if (in.type == SCHFS_T_DEV) {
|
|
// Creating a real device node needs privileges the build does not have,
|
|
// so record it as an empty placeholder. The round-trip tree contains no
|
|
// device nodes; --check is what validates them.
|
|
FILE *f = fopen(host, "wb");
|
|
if (!f) { perror(host); return -1; }
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
// A second name for an object already written: make it a real hard link so
|
|
// the extracted tree has the same link structure as the image.
|
|
const char *first = seen_lookup(ino);
|
|
if (first) {
|
|
unlink(host);
|
|
if (link(first, host) != 0) { perror(host); return -1; }
|
|
xlinks++;
|
|
return 0;
|
|
}
|
|
|
|
FILE *f = fopen(host, "wb");
|
|
if (!f) { perror(host); return -1; }
|
|
uint64_t off = 0;
|
|
while (off < in.size) {
|
|
// A full block is requested every time, even at the tail. Clamping
|
|
// here instead would mean nothing ever tests that schfs_file_read
|
|
// stops at end-of-file -- and the kernel's read(2) will depend on
|
|
// exactly that, since a real program just asks for a bufferful.
|
|
char buf[SCHFS_BLKSIZE];
|
|
unsigned want = sizeof(buf);
|
|
long got = schfs_file_read(&VOL, &c, ino, off, buf, want, DATABLK);
|
|
if (got <= 0) {
|
|
fprintf(stderr, "schfs_fsck: %s: read error at %llu\n",
|
|
host, (unsigned long long)off);
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
fwrite(buf, 1, (size_t)got, f);
|
|
off += (uint64_t)got;
|
|
}
|
|
fclose(f);
|
|
if (chmod(host, in.mode & 07777) != 0)
|
|
perror(host);
|
|
seen_add(ino, host);
|
|
xfiles++;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
extract_dir(uint64_t ino, const char *hostpath)
|
|
{
|
|
if (chain_push(ino, hostpath) != 0)
|
|
return -1;
|
|
struct schfs_cursor c;
|
|
schfs_cursor_init(&c, &VOL.tree, CURBLK);
|
|
int r = schfs_dir_iterate(&VOL, &c, ino, extract_entry, (void *)hostpath);
|
|
chain_pop();
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
do_extract(const char *dir)
|
|
{
|
|
if (mkdir(dir, 0755) != 0 && errno != EEXIST) {
|
|
perror(dir);
|
|
return 1;
|
|
}
|
|
if (extract_dir(VOL.root_objectid, dir) != 0)
|
|
return 1;
|
|
printf("extracted %ld dirs, %ld files, %ld hard links, %ld symlinks into %s\n",
|
|
xdirs, xfiles, xlinks, xsymlinks, dir);
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
if (argc < 3) {
|
|
fprintf(stderr,
|
|
"usage: schfs_fsck img.schfs <action>\n"
|
|
" --info uberblock ring and volume summary\n"
|
|
" --check full structural and data verification\n"
|
|
" --ls PATH list a directory\n"
|
|
" --stat PATH one object's metadata\n"
|
|
" --cat PATH a file's contents\n"
|
|
" --tree every path in the volume\n"
|
|
" --extract DIR write the whole tree onto the host\n");
|
|
return 2;
|
|
}
|
|
|
|
const char *imgpath = argv[1];
|
|
const char *action = argv[2];
|
|
const char *arg = argc > 3 ? argv[3] : NULL;
|
|
|
|
// Must match whatever mkschfs used, or every directory lookup misses.
|
|
for (int i = 3; i + 1 < argc; i++)
|
|
if (strcmp(argv[i], "--hash-bits") == 0) {
|
|
int bits = atoi(argv[i + 1]);
|
|
schfs_hash_mask = bits >= 64 ? ~0ULL : ((1ULL << bits) - 1);
|
|
if (arg == argv[i])
|
|
arg = NULL;
|
|
}
|
|
|
|
FILE *f = fopen(imgpath, "rb");
|
|
if (!f) { perror(imgpath); return 1; }
|
|
if (fseeko(f, 0, SEEK_END) != 0) { perror(imgpath); return 1; }
|
|
off_t sz = ftello(f);
|
|
|
|
IMG.f = f;
|
|
IMG.nblocks = (uint64_t)sz / SCHFS_BLKSIZE;
|
|
|
|
struct schfs_bdev bd;
|
|
bd.ctx = &IMG;
|
|
bd.bread = img_read;
|
|
bd.bwrite = img_write;
|
|
bd.balloc = img_alloc;
|
|
bd.bfree = img_nofree;
|
|
bd.balloc_run = NULL; /* read-only: nothing is ever allocated */
|
|
|
|
// No page allocator: this tool never writes, and a volume without an
|
|
// allocation map cannot allocate even by accident.
|
|
int r = schfs_vol_open(&VOL, &bd, &CTX, NULL);
|
|
if (r == -1) {
|
|
fprintf(stderr, "schfs_fsck: %s: no valid uberblock -- not a SchFS "
|
|
"volume, or never successfully written\n", imgpath);
|
|
return 1;
|
|
}
|
|
if (r != 0) {
|
|
fprintf(stderr, "schfs_fsck: %s: newest uberblock names an unreadable "
|
|
"tree root\n", imgpath);
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp(action, "--info") == 0)
|
|
return do_info();
|
|
if (strcmp(action, "--check") == 0)
|
|
return do_check();
|
|
if (strcmp(action, "--tree") == 0)
|
|
return tree_walk(VOL.root_objectid, "");
|
|
if (strcmp(action, "--ls") == 0 && arg)
|
|
return do_ls(arg);
|
|
if (strcmp(action, "--stat") == 0 && arg)
|
|
return do_stat(arg);
|
|
if (strcmp(action, "--cat") == 0 && arg)
|
|
return do_cat(arg);
|
|
if (strcmp(action, "--extract") == 0 && arg)
|
|
return do_extract(arg);
|
|
|
|
fprintf(stderr, "schfs_fsck: unknown or incomplete action '%s'\n", action);
|
|
return 2;
|
|
}
|