diff --git a/sbin/Makefile b/sbin/Makefile --- a/sbin/Makefile +++ b/sbin/Makefile @@ -20,6 +20,7 @@ ffsinfo \ fsck \ fsck_ffs \ + fsck_hammer2 \ fsck_msdosfs \ fsdb \ fsirand \ @@ -28,6 +29,7 @@ ggate \ growfs \ gvinum \ + hammer2 \ ifconfig \ init \ kldconfig \ @@ -43,12 +45,14 @@ mount \ mount_cd9660 \ mount_fusefs \ + mount_hammer2 \ mount_msdosfs \ mount_nfs \ mount_nullfs \ mount_udf \ mount_unionfs \ newfs \ + newfs_hammer2 \ newfs_msdos \ nfsiod \ nos-tun \ diff --git a/sbin/fsck_hammer2/Makefile b/sbin/fsck_hammer2/Makefile new file mode 100644 --- /dev/null +++ b/sbin/fsck_hammer2/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +PACKAGE=runtime +PROG= fsck_hammer2 +SRCS= fsck_hammer2.c test.c ondisk.c subs.c xxhash.c gsb_crc32.c +MAN= fsck_hammer2.8 + +.PATH: ${SRCTOP}/sbin/hammer2 ${SRCTOP}/sys/fs/hammer2/xxhash ${SRCTOP}/sys/libkern + +WARNS?= 3 + +CFLAGS+= -DXXH_NAMESPACE=h2_ +CFLAGS+= -I${SRCTOP}/sys +CFLAGS+= -I${SRCTOP}/sbin/hammer2 + +LIBADD= md + +.include diff --git a/sbin/fsck_hammer2/fsck_hammer2.h b/sbin/fsck_hammer2/fsck_hammer2.h new file mode 100644 --- /dev/null +++ b/sbin/fsck_hammer2/fsck_hammer2.h @@ -0,0 +1,56 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2019 Tomohiro Kusumi + * Copyright (c) 2019 The DragonFly Project + * All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef FSCK_HAMMER2_H_ +#define FSCK_HAMMER2_H_ + +extern int DebugOpt; +extern int ForceOpt; +extern int VerboseOpt; +extern int QuietOpt; +extern int CountEmpty; +extern int ScanBest; +extern int ScanPFS; +extern int PrintPFS; +extern int NumPFSNames; +extern char **PFSNames; +extern long BlockrefCacheCount; + +int test_hammer2(const char *); + +#endif /* !FSCK_HAMMER2_H_ */ diff --git a/sbin/fsck_hammer2/fsck_hammer2.8 b/sbin/fsck_hammer2/fsck_hammer2.8 new file mode 100644 --- /dev/null +++ b/sbin/fsck_hammer2/fsck_hammer2.8 @@ -0,0 +1,100 @@ +.\" Copyright (c) 2019 Tomohiro Kusumi +.\" Copyright (c) 2019 The DragonFly Project +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The DragonFly Project +.\" by Matthew Dillon +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. Neither the name of The DragonFly Project nor the names of its +.\" contributors may be used to endorse or promote products derived +.\" from this software without specific, prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, +.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd September 18, 2022 +.Dt FSCK_HAMMER2 8 +.Os +.Sh NAME +.Nm fsck_hammer2 +.Nd HAMMER2 file system consistency checker +.Sh SYNOPSIS +.Nm +.Op Fl f +.Op Fl v +.Op Fl q +.Op Fl e +.Op Fl b +.Op Fl p +.Op Fl P +.Op Fl l Ar pfs_names +.Op Fl c Ar cache_count +.Ar special +.Sh DESCRIPTION +The +.Nm +utility verifies +.Tn HAMMER2 +file system. +.Bl -tag -width indent +.It Fl f +Force option. +.It Fl v +Verbose option. +Print blockref data on failure if possible. +.It Fl q +Quiet option. +.It Fl e +Count empty blockrefs. +.It Fl b +Scan only best zone. +.It Fl p +Scan each PFS separately. +.It Fl P +Print PFS information. +.It Fl l +Specify PFS names when +.Fl p +is used. +.It Fl c +Specify blockref cache count. +.El +.Sh SEE ALSO +.Xr fsck 8 , +.Xr hammer2 8 , +.Xr mount_hammer2 8 , +.Xr newfs_hammer2 8 +.Sh HISTORY +The +.Nm +utility appeared in +.Dx 5.7 . +.Sh AUTHORS +.An Tomohiro Kusumi Aq Mt tkusumi@netbsd.org +.Pp +The +.Nm +utility was ported to +.Fx +by +.An Tomohiro Kusumi Aq Mt tkusumi@netbsd.org . diff --git a/sbin/fsck_hammer2/fsck_hammer2.c b/sbin/fsck_hammer2/fsck_hammer2.c new file mode 100644 --- /dev/null +++ b/sbin/fsck_hammer2/fsck_hammer2.c @@ -0,0 +1,187 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2019 Tomohiro Kusumi + * Copyright (c) 2019 The DragonFly Project + * All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "fsck_hammer2.h" + +int DebugOpt; +int ForceOpt; +int VerboseOpt; +int QuietOpt; +int CountEmpty; +int ScanBest; +int ScanPFS; +int PrintPFS; +int NumPFSNames; +char **PFSNames; +long BlockrefCacheCount = -1; + +static void +init_pfs_names(const char *names) +{ + char *name, *h, *p; + int siz = 32; + + PFSNames = calloc(siz, sizeof(char *)); + p = strdup(names); + h = p; + + while ((name = p) != NULL) { + p = strchr(p, ','); + if (p) + *p++ = 0; + if (strlen(name)) { + if (NumPFSNames > siz - 1) { + siz *= 2; + PFSNames = realloc(PFSNames, + siz * sizeof(char *)); + } + PFSNames[NumPFSNames++] = strdup(name); + } + } + free(h); + + if (DebugOpt) { + int i; + for (i = 0; i < NumPFSNames; i++) + printf("PFSNames[%d]=\"%s\"\n", i, PFSNames[i]); + } +} + +static void +cleanup_pfs_names(void) +{ + int i; + + for (i = 0; i < NumPFSNames; i++) + free(PFSNames[i]); + free(PFSNames); +} + +static void +usage(void) +{ + fprintf(stderr, "fsck_hammer2 [-f] [-v] [-q] [-e] [-b] [-p] [-P] " + "[-l pfs_names] [-c cache_count] special\n"); + exit(1); +} + +int +main(int ac, char **av) +{ + int i, ch; + + while ((ch = getopt(ac, av, "dfvqebpPl:c:")) != -1) { + switch(ch) { + case 'd': + DebugOpt++; + break; + case 'f': + ForceOpt = 1; + break; + case 'v': + if (QuietOpt) + --QuietOpt; + else + ++VerboseOpt; + break; + case 'q': + if (VerboseOpt) + --VerboseOpt; + else + ++QuietOpt; + break; + case 'e': + CountEmpty = 1; + break; + case 'b': + ScanBest = 1; + break; + case 'p': + ScanPFS = 1; + break; + case 'P': + PrintPFS = 1; + break; + case 'l': + init_pfs_names(optarg); + break; + case 'c': + errno = 0; + BlockrefCacheCount = strtol(optarg, NULL, 10); + if (errno == ERANGE && + (BlockrefCacheCount == LONG_MIN || + BlockrefCacheCount == LONG_MAX)) { + perror("strtol"); + exit(1); + } + break; + default: + usage(); + /* not reached */ + break; + } + } + + ac -= optind; + av += optind; + if (ac < 1) { + usage(); + /* not reached */ + } + + for (i = 0; i < ac; i++) { + if (ac != 1) + printf("%s\n", av[i]); + if (test_hammer2(av[i]) == -1) + exit(1); + if (i != ac - 1) + printf("----------------------------------------" + "----------------------------------------\n"); + } + + cleanup_pfs_names(); + + return 0; +} diff --git a/sbin/fsck_hammer2/test.c b/sbin/fsck_hammer2/test.c new file mode 100644 --- /dev/null +++ b/sbin/fsck_hammer2/test.c @@ -0,0 +1,1349 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2019 Tomohiro Kusumi + * Copyright (c) 2019 The DragonFly Project + * All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "hammer2_subs.h" +#include "fsck_hammer2.h" + +struct blockref_msg { + TAILQ_ENTRY(blockref_msg) entry; + hammer2_blockref_t bref; + void *msg; +}; + +TAILQ_HEAD(blockref_list, blockref_msg); + +struct blockref_entry { + RB_ENTRY(blockref_entry) entry; + hammer2_off_t data_off; + struct blockref_list head; +}; + +static int +blockref_cmp(struct blockref_entry *b1, struct blockref_entry *b2) +{ + if (b1->data_off < b2->data_off) + return -1; + if (b1->data_off > b2->data_off) + return 1; + return 0; +} + +RB_HEAD(blockref_tree, blockref_entry); +RB_PROTOTYPE(blockref_tree, blockref_entry, entry, blockref_cmp); +RB_GENERATE(blockref_tree, blockref_entry, entry, blockref_cmp); + +typedef struct { + struct blockref_tree root; + uint8_t type; /* HAMMER2_BREF_TYPE_VOLUME or FREEMAP */ + uint64_t total_blockref; + uint64_t total_empty; + uint64_t total_bytes; + union { + /* use volume or freemap depending on type value */ + struct { + uint64_t total_inode; + uint64_t total_indirect; + uint64_t total_data; + uint64_t total_dirent; + } volume; + struct { + uint64_t total_freemap_node; + uint64_t total_freemap_leaf; + } freemap; + }; +} blockref_stats_t; + +typedef struct { + uint64_t total_blockref; + uint64_t total_empty; + uint64_t total_bytes; + struct { + uint64_t total_inode; + uint64_t total_indirect; + uint64_t total_data; + uint64_t total_dirent; + } volume; + struct { + uint64_t total_freemap_node; + uint64_t total_freemap_leaf; + } freemap; + long count; +} delta_stats_t; + +static void print_blockref_entry(struct blockref_tree *); +static void init_blockref_stats(blockref_stats_t *, uint8_t); +static void cleanup_blockref_stats(blockref_stats_t *); +static void init_delta_root(struct blockref_tree *); +static void cleanup_delta_root(struct blockref_tree *); +static void print_blockref_stats(const blockref_stats_t *, bool); +static int verify_volume_header(const hammer2_volume_data_t *); +static int read_media(const hammer2_blockref_t *, hammer2_media_data_t *, + size_t *); +static int verify_blockref(const hammer2_volume_data_t *, + const hammer2_blockref_t *, bool, blockref_stats_t *, + struct blockref_tree *, delta_stats_t *, int, int); +static void print_pfs(const hammer2_inode_data_t *); +static char *get_inode_filename(const hammer2_inode_data_t *); +static int init_pfs_blockref(const hammer2_volume_data_t *, + const hammer2_blockref_t *, struct blockref_list *); +static void cleanup_pfs_blockref(struct blockref_list *); +static void print_media(FILE *, int, const hammer2_blockref_t *, + const hammer2_media_data_t *, size_t); + +static int best_zone = -1; + +#define TAB 8 + +static void +tfprintf(FILE *fp, int tab, const char *ctl, ...) +{ + va_list va; + int ret; + + ret = fprintf(fp, "%*s", tab * TAB, ""); + if (ret < 0) + return; + + va_start(va, ctl); + vfprintf(fp, ctl, va); + va_end(va); +} + +static void +tsnprintf(char *str, size_t siz, int tab, const char *ctl, ...) +{ + va_list va; + int ret; + + ret = snprintf(str, siz, "%*s", tab * TAB, ""); + if (ret < 0 || ret >= (int)siz) + return; + + va_start(va, ctl); + vsnprintf(str + ret, siz - ret, ctl, va); + va_end(va); +} + +static void +tprintf_zone(int tab, int i, const hammer2_blockref_t *bref) +{ + tfprintf(stdout, tab, "zone.%d %016jx%s\n", + i, (uintmax_t)bref->data_off, + (!ScanBest && i == best_zone) ? " (best)" : ""); +} + +static int +init_root_blockref(int i, uint8_t type, hammer2_blockref_t *bref) +{ + hammer2_off_t off; + + assert(type == HAMMER2_BREF_TYPE_EMPTY || + type == HAMMER2_BREF_TYPE_VOLUME || + type == HAMMER2_BREF_TYPE_FREEMAP); + memset(bref, 0, sizeof(*bref)); + bref->type = type; + bref->data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX; + off = bref->data_off & ~HAMMER2_OFF_MASK_RADIX; + + return lseek(hammer2_get_root_volume_fd(), + off - hammer2_get_root_volume_offset(), SEEK_SET); +} + +static int +find_best_zone(void) +{ + hammer2_blockref_t best; + int i, best_i = -1; + + memset(&best, 0, sizeof(best)); + + for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) { + hammer2_volume_data_t voldata; + hammer2_blockref_t broot; + ssize_t ret; + + if (i * HAMMER2_ZONE_BYTES64 >= + hammer2_get_root_volume_size()) + break; + init_root_blockref(i, HAMMER2_BREF_TYPE_EMPTY, &broot); + ret = read(hammer2_get_root_volume_fd(), &voldata, + HAMMER2_PBUFSIZE); + if (ret == HAMMER2_PBUFSIZE) { + if ((voldata.magic != HAMMER2_VOLUME_ID_HBO) && + (voldata.magic != HAMMER2_VOLUME_ID_ABO)) + continue; + broot.mirror_tid = voldata.mirror_tid; + if (best_i < 0 || best.mirror_tid < broot.mirror_tid) { + best_i = i; + best = broot; + } + } else if (ret == -1) { + perror("read"); + return -1; + } else { + tfprintf(stderr, 1, "Failed to read volume header\n"); + return -1; + } + } + + return best_i; +} + +static int +test_volume_header(void) +{ + bool failed = false; + int i; + + for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) { + hammer2_volume_data_t voldata; + hammer2_blockref_t broot; + ssize_t ret; + + if (ScanBest && i != best_zone) + continue; + if (i * HAMMER2_ZONE_BYTES64 >= + hammer2_get_root_volume_size()) { + tfprintf(stderr, 0, "zone.%d exceeds volume size\n", i); + break; + } + init_root_blockref(i, HAMMER2_BREF_TYPE_EMPTY, &broot); + ret = read(hammer2_get_root_volume_fd(), &voldata, + HAMMER2_PBUFSIZE); + if (ret == HAMMER2_PBUFSIZE) { + tprintf_zone(0, i, &broot); + if (verify_volume_header(&voldata) == -1) + failed = true; + } else if (ret == -1) { + perror("read"); + return -1; + } else { + tfprintf(stderr, 1, "Failed to read volume header\n"); + return -1; + } + } + + return failed ? -1 : 0; +} + +static int +test_blockref(uint8_t type) +{ + struct blockref_tree droot; + bool failed = false; + int i; + + init_delta_root(&droot); + for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) { + hammer2_volume_data_t voldata; + hammer2_blockref_t broot; + ssize_t ret; + + if (ScanBest && i != best_zone) + continue; + if (i * HAMMER2_ZONE_BYTES64 >= + hammer2_get_root_volume_size()) { + tfprintf(stderr, 0, "zone.%d exceeds volume size\n", i); + break; + } + init_root_blockref(i, type, &broot); + ret = read(hammer2_get_root_volume_fd(), &voldata, + HAMMER2_PBUFSIZE); + if (ret == HAMMER2_PBUFSIZE) { + blockref_stats_t bstats; + init_blockref_stats(&bstats, type); + delta_stats_t ds; + memset(&ds, 0, sizeof(ds)); + tprintf_zone(0, i, &broot); + if (verify_blockref(&voldata, &broot, false, &bstats, + &droot, &ds, 0, 0) == -1) + failed = true; + print_blockref_stats(&bstats, true); + print_blockref_entry(&bstats.root); + cleanup_blockref_stats(&bstats); + } else if (ret == -1) { + perror("read"); + failed = true; + goto end; + } else { + tfprintf(stderr, 1, "Failed to read volume header\n"); + failed = true; + goto end; + } + } +end: + cleanup_delta_root(&droot); + return failed ? -1 : 0; +} + +static int +test_pfs_blockref(void) +{ + struct blockref_tree droot; + uint8_t type = HAMMER2_BREF_TYPE_VOLUME; + bool failed = false; + int i; + + init_delta_root(&droot); + for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) { + hammer2_volume_data_t voldata; + hammer2_blockref_t broot; + ssize_t ret; + + if (ScanBest && i != best_zone) + continue; + if (i * HAMMER2_ZONE_BYTES64 >= + hammer2_get_root_volume_size()) { + tfprintf(stderr, 0, "zone.%d exceeds volume size\n", i); + break; + } + init_root_blockref(i, type, &broot); + ret = read(hammer2_get_root_volume_fd(), &voldata, + HAMMER2_PBUFSIZE); + if (ret == HAMMER2_PBUFSIZE) { + struct blockref_list blist; + struct blockref_msg *p; + int count = 0; + + tprintf_zone(0, i, &broot); + TAILQ_INIT(&blist); + if (init_pfs_blockref(&voldata, &broot, &blist) == -1) { + tfprintf(stderr, 1, "Failed to read PFS " + "blockref\n"); + failed = true; + continue; + } + if (TAILQ_EMPTY(&blist)) { + tfprintf(stderr, 1, "Failed to find PFS " + "blockref\n"); + failed = true; + continue; + } + TAILQ_FOREACH(p, &blist, entry) { + blockref_stats_t bstats; + bool found = false; + char *f = get_inode_filename(p->msg); + if (NumPFSNames) { + int j; + for (j = 0; j < NumPFSNames; j++) + if (!strcmp(PFSNames[j], f)) + found = true; + } else + found = true; + if (!found) { + free(f); + continue; + } + count++; + if (PrintPFS) { + print_pfs(p->msg); + free(f); + continue; + } + tfprintf(stdout, 1, "%s\n", f); + free(f); + init_blockref_stats(&bstats, type); + delta_stats_t ds; + memset(&ds, 0, sizeof(ds)); + if (verify_blockref(&voldata, &p->bref, false, + &bstats, &droot, &ds, 0, 0) == -1) + failed = true; + print_blockref_stats(&bstats, true); + print_blockref_entry(&bstats.root); + cleanup_blockref_stats(&bstats); + } + cleanup_pfs_blockref(&blist); + if (NumPFSNames && !count) { + tfprintf(stderr, 1, "PFS not found\n"); + failed = true; + } + } else if (ret == -1) { + perror("read"); + failed = true; + goto end; + } else { + tfprintf(stderr, 1, "Failed to read volume header\n"); + failed = true; + goto end; + } + } +end: + cleanup_delta_root(&droot); + return failed ? -1 : 0; +} + +static int +charsperline(void) +{ + int columns; + char *cp; + struct winsize ws; + + columns = 0; + if (ioctl(0, TIOCGWINSZ, &ws) != -1) + columns = ws.ws_col; + if (columns == 0 && (cp = getenv("COLUMNS"))) + columns = atoi(cp); + if (columns == 0) + columns = 80; /* last resort */ + + return columns; +} + +static void +cleanup_blockref_msg(struct blockref_list *head) +{ + struct blockref_msg *p; + + while ((p = TAILQ_FIRST(head)) != NULL) { + TAILQ_REMOVE(head, p, entry); + free(p->msg); + free(p); + } + assert(TAILQ_EMPTY(head)); +} + +static void +cleanup_blockref_entry(struct blockref_tree *root) +{ + struct blockref_entry *e; + + while ((e = RB_ROOT(root)) != NULL) { + RB_REMOVE(blockref_tree, root, e); + cleanup_blockref_msg(&e->head); + free(e); + } + assert(RB_EMPTY(root)); +} + +static void +add_blockref_msg(struct blockref_list *head, const hammer2_blockref_t *bref, + const void *msg, size_t siz) +{ + struct blockref_msg *m; + void *p; + + m = calloc(1, sizeof(*m)); + assert(m); + m->bref = *bref; + p = calloc(1, siz); + assert(p); + memcpy(p, msg, siz); + m->msg = p; + + TAILQ_INSERT_TAIL(head, m, entry); +} + +static void +add_blockref_entry(struct blockref_tree *root, const hammer2_blockref_t *bref, + const void *msg, size_t siz) +{ + struct blockref_entry *e, bref_find; + + memset(&bref_find, 0, sizeof(bref_find)); + bref_find.data_off = bref->data_off; + e = RB_FIND(blockref_tree, root, &bref_find); + if (!e) { + e = calloc(1, sizeof(*e)); + assert(e); + TAILQ_INIT(&e->head); + e->data_off = bref->data_off; + } + + add_blockref_msg(&e->head, bref, msg, siz); + + RB_INSERT(blockref_tree, root, e); +} + +static void +__print_blockref(FILE *fp, int tab, const hammer2_blockref_t *bref, + const char *msg) +{ + tfprintf(fp, tab, "%016jx %-12s %016jx/%-2d%s%s\n", + (uintmax_t)bref->data_off, + hammer2_breftype_to_str(bref->type), + (uintmax_t)bref->key, + bref->keybits, + msg ? " " : "", + msg ? msg : ""); +} + +static void +print_blockref(FILE *fp, const hammer2_blockref_t *bref, const char *msg) +{ + __print_blockref(fp, 1, bref, msg); +} + +static void +print_blockref_debug(FILE *fp, int depth, int index, + const hammer2_blockref_t *bref, const char *msg) +{ + if (DebugOpt > 1) { + char buf[256]; + int i; + + memset(buf, 0, sizeof(buf)); + for (i = 0; i < depth * 2; i++) + strlcat(buf, " ", sizeof(buf)); + tfprintf(fp, 1, buf); + fprintf(fp, "%-2d %-3d ", depth, index); + __print_blockref(fp, 0, bref, msg); + } else if (DebugOpt > 0) + print_blockref(fp, bref, msg); +} + +static void +print_blockref_msg(const struct blockref_list *head) +{ + struct blockref_msg *m; + + TAILQ_FOREACH(m, head, entry) { + hammer2_blockref_t *bref = &m->bref; + print_blockref(stderr, bref, m->msg); + if (VerboseOpt > 0) { + hammer2_media_data_t media; + size_t bytes; + if (!read_media(bref, &media, &bytes)) + print_media(stderr, 2, bref, &media, bytes); + else + tfprintf(stderr, 2, "Failed to read media\n"); + } + } +} + +static void +print_blockref_entry(struct blockref_tree *root) +{ + struct blockref_entry *e; + + RB_FOREACH(e, blockref_tree, root) + print_blockref_msg(&e->head); +} + +static void +init_blockref_stats(blockref_stats_t *bstats, uint8_t type) +{ + memset(bstats, 0, sizeof(*bstats)); + RB_INIT(&bstats->root); + bstats->type = type; +} + +static void +cleanup_blockref_stats(blockref_stats_t *bstats) +{ + cleanup_blockref_entry(&bstats->root); +} + +static void +init_delta_root(struct blockref_tree *droot) +{ + RB_INIT(droot); +} + +static void +cleanup_delta_root(struct blockref_tree *droot) +{ + cleanup_blockref_entry(droot); +} + +static void +print_blockref_stats(const blockref_stats_t *bstats, bool newline) +{ + size_t siz = charsperline(); + char *buf = calloc(1, siz); + char emptybuf[128]; + + assert(buf); + + if (CountEmpty) + snprintf(emptybuf, sizeof(emptybuf), ", %ju empty", + (uintmax_t)bstats->total_empty); + else + strlcpy(emptybuf, "", sizeof(emptybuf)); + + switch (bstats->type) { + case HAMMER2_BREF_TYPE_VOLUME: + tsnprintf(buf, siz, 1, "%ju blockref (%ju inode, %ju indirect, " + "%ju data, %ju dirent%s), %s", + (uintmax_t)bstats->total_blockref, + (uintmax_t)bstats->volume.total_inode, + (uintmax_t)bstats->volume.total_indirect, + (uintmax_t)bstats->volume.total_data, + (uintmax_t)bstats->volume.total_dirent, + emptybuf, + sizetostr(bstats->total_bytes)); + break; + case HAMMER2_BREF_TYPE_FREEMAP: + tsnprintf(buf, siz, 1, "%ju blockref (%ju node, %ju leaf%s), " + "%s", + (uintmax_t)bstats->total_blockref, + (uintmax_t)bstats->freemap.total_freemap_node, + (uintmax_t)bstats->freemap.total_freemap_leaf, + emptybuf, + sizetostr(bstats->total_bytes)); + break; + default: + assert(0); + break; + } + + if (newline) { + printf("%s\n", buf); + } else { + printf("%s\r", buf); + fflush(stdout); + } + free(buf); +} + +static int +verify_volume_header(const hammer2_volume_data_t *voldata) +{ + hammer2_crc32_t crc0, crc1; + const char *p = (const char*)voldata; + + if ((voldata->magic != HAMMER2_VOLUME_ID_HBO) && + (voldata->magic != HAMMER2_VOLUME_ID_ABO)) { + tfprintf(stderr, 1, "Bad magic %jX\n", voldata->magic); + return -1; + } + + if (voldata->magic == HAMMER2_VOLUME_ID_ABO) + tfprintf(stderr, 1, "Reverse endian\n"); + + crc0 = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT0]; + crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC0_OFF, + HAMMER2_VOLUME_ICRC0_SIZE); + if (crc0 != crc1) { + tfprintf(stderr, 1, "Bad HAMMER2_VOL_ICRC_SECT0 CRC\n"); + return -1; + } + + crc0 = voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT1]; + crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC1_OFF, + HAMMER2_VOLUME_ICRC1_SIZE); + if (crc0 != crc1) { + tfprintf(stderr, 1, "Bad HAMMER2_VOL_ICRC_SECT1 CRC\n"); + return -1; + } + + crc0 = voldata->icrc_volheader; + crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRCVH_OFF, + HAMMER2_VOLUME_ICRCVH_SIZE); + if (crc0 != crc1) { + tfprintf(stderr, 1, "Bad volume header CRC\n"); + return -1; + } + + return 0; +} + +static int +read_media(const hammer2_blockref_t *bref, hammer2_media_data_t *media, + size_t *media_bytes) +{ + hammer2_off_t io_off, io_base; + size_t bytes, io_bytes, boff; + int fd; + + bytes = (bref->data_off & HAMMER2_OFF_MASK_RADIX); + if (bytes) + bytes = (size_t)1 << bytes; + if (media_bytes) + *media_bytes = bytes; + + if (!bytes) + return 0; + + io_off = bref->data_off & ~HAMMER2_OFF_MASK_RADIX; + io_base = io_off & ~(hammer2_off_t)(HAMMER2_LBUFSIZE - 1); + boff = io_off - io_base; + + io_bytes = HAMMER2_LBUFSIZE; + while (io_bytes + boff < bytes) + io_bytes <<= 1; + + if (io_bytes > sizeof(*media)) + return -1; + fd = hammer2_get_volume_fd(io_off); + if (lseek(fd, io_base - hammer2_get_volume_offset(io_base), SEEK_SET) + == -1) + return -2; + if (read(fd, media, io_bytes) != (ssize_t)io_bytes) + return -2; + if (boff) + memmove(media, (char *)media + boff, bytes); + + return 0; +} + +static void +load_delta_stats(blockref_stats_t *bstats, const delta_stats_t *dstats) +{ + bstats->total_blockref += dstats->total_blockref; + bstats->total_empty += dstats->total_empty; + bstats->total_bytes += dstats->total_bytes; + + switch (bstats->type) { + case HAMMER2_BREF_TYPE_VOLUME: + bstats->volume.total_inode += dstats->volume.total_inode; + bstats->volume.total_indirect += dstats->volume.total_indirect; + bstats->volume.total_data += dstats->volume.total_data; + bstats->volume.total_dirent += dstats->volume.total_dirent; + break; + case HAMMER2_BREF_TYPE_FREEMAP: + bstats->freemap.total_freemap_node += + dstats->freemap.total_freemap_node; + bstats->freemap.total_freemap_leaf += + dstats->freemap.total_freemap_leaf; + break; + default: + assert(0); + break; + } +} + +static void +accumulate_delta_stats(delta_stats_t *dst, const delta_stats_t *src) +{ + dst->total_blockref += src->total_blockref; + dst->total_empty += src->total_empty; + dst->total_bytes += src->total_bytes; + + dst->volume.total_inode += src->volume.total_inode; + dst->volume.total_indirect += src->volume.total_indirect; + dst->volume.total_data += src->volume.total_data; + dst->volume.total_dirent += src->volume.total_dirent; + + dst->freemap.total_freemap_node += src->freemap.total_freemap_node; + dst->freemap.total_freemap_leaf += src->freemap.total_freemap_leaf; + + dst->count += src->count; +} + +static int +verify_blockref(const hammer2_volume_data_t *voldata, + const hammer2_blockref_t *bref, bool norecurse, blockref_stats_t *bstats, + struct blockref_tree *droot, delta_stats_t *dstats, int depth, int index) +{ + hammer2_media_data_t media; + hammer2_blockref_t *bscan; + int i, bcount; + bool failed = false; + size_t bytes; + uint32_t cv; + uint64_t cv64; + char msg[256]; + SHA256_CTX hash_ctx; + union { + uint8_t digest[SHA256_DIGEST_LENGTH]; + uint64_t digest64[SHA256_DIGEST_LENGTH/8]; + } u; + + /* only for DebugOpt > 1 */ + if (DebugOpt > 1) + print_blockref_debug(stdout, depth, index, bref, NULL); + + if (bref->data_off) { + struct blockref_entry *e, bref_find; + memset(&bref_find, 0, sizeof(bref_find)); + bref_find.data_off = bref->data_off; + e = RB_FIND(blockref_tree, droot, &bref_find); + if (e) { + struct blockref_msg *m; + TAILQ_FOREACH(m, &e->head, entry) { + delta_stats_t *ds = m->msg; + if (!memcmp(&m->bref, bref, sizeof(*bref))) { + /* delta contains cached delta */ + accumulate_delta_stats(dstats, ds); + load_delta_stats(bstats, ds); + print_blockref_debug(stdout, depth, + index, &m->bref, "cache-hit"); + return 0; + } + } + } + } + + bstats->total_blockref++; + dstats->total_blockref++; + + switch (bref->type) { + case HAMMER2_BREF_TYPE_EMPTY: + if (CountEmpty) { + bstats->total_empty++; + dstats->total_empty++; + } else { + bstats->total_blockref--; + dstats->total_blockref--; + } + break; + case HAMMER2_BREF_TYPE_INODE: + bstats->volume.total_inode++; + dstats->volume.total_inode++; + break; + case HAMMER2_BREF_TYPE_INDIRECT: + bstats->volume.total_indirect++; + dstats->volume.total_indirect++; + break; + case HAMMER2_BREF_TYPE_DATA: + bstats->volume.total_data++; + dstats->volume.total_data++; + break; + case HAMMER2_BREF_TYPE_DIRENT: + bstats->volume.total_dirent++; + dstats->volume.total_dirent++; + break; + case HAMMER2_BREF_TYPE_FREEMAP_NODE: + bstats->freemap.total_freemap_node++; + dstats->freemap.total_freemap_node++; + break; + case HAMMER2_BREF_TYPE_FREEMAP_LEAF: + bstats->freemap.total_freemap_leaf++; + dstats->freemap.total_freemap_leaf++; + break; + case HAMMER2_BREF_TYPE_VOLUME: + bstats->total_blockref--; + dstats->total_blockref--; + break; + case HAMMER2_BREF_TYPE_FREEMAP: + bstats->total_blockref--; + dstats->total_blockref--; + break; + default: + snprintf(msg, sizeof(msg), "Invalid blockref type %d", + bref->type); + add_blockref_entry(&bstats->root, bref, msg, strlen(msg) + 1); + print_blockref_debug(stdout, depth, index, bref, msg); + failed = true; + break; + } + + switch (read_media(bref, &media, &bytes)) { + case -1: + strlcpy(msg, "Bad I/O bytes", sizeof(msg)); + add_blockref_entry(&bstats->root, bref, msg, strlen(msg) + 1); + print_blockref_debug(stdout, depth, index, bref, msg); + return -1; + case -2: + strlcpy(msg, "Failed to read media", sizeof(msg)); + add_blockref_entry(&bstats->root, bref, msg, strlen(msg) + 1); + print_blockref_debug(stdout, depth, index, bref, msg); + return -1; + default: + break; + } + + if (bref->type != HAMMER2_BREF_TYPE_VOLUME && + bref->type != HAMMER2_BREF_TYPE_FREEMAP) { + bstats->total_bytes += bytes; + dstats->total_bytes += bytes; + } + + if (!CountEmpty && bref->type == HAMMER2_BREF_TYPE_EMPTY) { + assert(bytes == 0); + bstats->total_bytes -= bytes; + dstats->total_bytes -= bytes; + } + + if (!DebugOpt && QuietOpt <= 0 && (bstats->total_blockref % 100) == 0) + print_blockref_stats(bstats, false); + + if (!bytes) + goto end; + + switch (HAMMER2_DEC_CHECK(bref->methods)) { + case HAMMER2_CHECK_ISCSI32: + cv = hammer2_icrc32(&media, bytes); + if (bref->check.iscsi32.value != cv) { + strlcpy(msg, "Bad HAMMER2_CHECK_ISCSI32", sizeof(msg)); + add_blockref_entry(&bstats->root, bref, msg, + strlen(msg) + 1); + print_blockref_debug(stdout, depth, index, bref, msg); + failed = true; + } + break; + case HAMMER2_CHECK_XXHASH64: + cv64 = XXH64(&media, bytes, XXH_HAMMER2_SEED); + if (bref->check.xxhash64.value != cv64) { + strlcpy(msg, "Bad HAMMER2_CHECK_XXHASH64", sizeof(msg)); + add_blockref_entry(&bstats->root, bref, msg, + strlen(msg) + 1); + print_blockref_debug(stdout, depth, index, bref, msg); + failed = true; + } + break; + case HAMMER2_CHECK_SHA192: + SHA256_Init(&hash_ctx); + SHA256_Update(&hash_ctx, &media, bytes); + SHA256_Final(u.digest, &hash_ctx); + u.digest64[2] ^= u.digest64[3]; + if (memcmp(u.digest, bref->check.sha192.data, + sizeof(bref->check.sha192.data))) { + strlcpy(msg, "Bad HAMMER2_CHECK_SHA192", sizeof(msg)); + add_blockref_entry(&bstats->root, bref, msg, + strlen(msg) + 1); + print_blockref_debug(stdout, depth, index, bref, msg); + failed = true; + } + break; + case HAMMER2_CHECK_FREEMAP: + cv = hammer2_icrc32(&media, bytes); + if (bref->check.freemap.icrc32 != cv) { + strlcpy(msg, "Bad HAMMER2_CHECK_FREEMAP", sizeof(msg)); + add_blockref_entry(&bstats->root, bref, msg, + strlen(msg) + 1); + print_blockref_debug(stdout, depth, index, bref, msg); + failed = true; + } + break; + } + + switch (bref->type) { + case HAMMER2_BREF_TYPE_INODE: + if (!(media.ipdata.meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA)) { + bscan = &media.ipdata.u.blockset.blockref[0]; + bcount = HAMMER2_SET_COUNT; + } else { + bscan = NULL; + bcount = 0; + } + break; + case HAMMER2_BREF_TYPE_INDIRECT: + bscan = &media.npdata[0]; + bcount = bytes / sizeof(hammer2_blockref_t); + break; + case HAMMER2_BREF_TYPE_FREEMAP_NODE: + bscan = &media.npdata[0]; + bcount = bytes / sizeof(hammer2_blockref_t); + break; + case HAMMER2_BREF_TYPE_VOLUME: + bscan = &media.voldata.sroot_blockset.blockref[0]; + bcount = HAMMER2_SET_COUNT; + break; + case HAMMER2_BREF_TYPE_FREEMAP: + bscan = &media.voldata.freemap_blockset.blockref[0]; + bcount = HAMMER2_SET_COUNT; + break; + default: + bscan = NULL; + bcount = 0; + break; + } + + if (ForceOpt) + norecurse = false; + /* + * If failed, no recurse, but still verify its direct children. + * Beyond that is probably garbage. + */ + for (i = 0; norecurse == false && i < bcount; ++i) { + delta_stats_t ds; + memset(&ds, 0, sizeof(ds)); + if (verify_blockref(voldata, &bscan[i], failed, bstats, droot, + &ds, depth + 1, i) == -1) + return -1; + if (!failed) + accumulate_delta_stats(dstats, &ds); + } +end: + if (failed) + return -1; + + dstats->count++; + if (bref->data_off && BlockrefCacheCount > 0 && + dstats->count >= BlockrefCacheCount) { + assert(bytes); + add_blockref_entry(droot, bref, dstats, sizeof(*dstats)); + print_blockref_debug(stdout, depth, index, bref, "cache-add"); + } + + return 0; +} + +static void +print_pfs(const hammer2_inode_data_t *ipdata) +{ + const hammer2_inode_meta_t *meta = &ipdata->meta; + char *f, *pfs_id_str = NULL; + const char *type_str; + uuid_t uuid; + + f = get_inode_filename(ipdata); + uuid = meta->pfs_clid; + hammer2_uuid_to_str(&uuid, &pfs_id_str); + if (meta->pfs_type == HAMMER2_PFSTYPE_MASTER) { + if (meta->pfs_subtype == HAMMER2_PFSSUBTYPE_NONE) + type_str = "MASTER"; + else + type_str = hammer2_pfssubtype_to_str(meta->pfs_subtype); + } else { + type_str = hammer2_pfstype_to_str(meta->pfs_type); + } + tfprintf(stdout, 1, "%-11s %s %s\n", type_str, pfs_id_str, f); + + free(f); + free(pfs_id_str); +} + +static char* +get_inode_filename(const hammer2_inode_data_t *ipdata) +{ + char *p = malloc(HAMMER2_INODE_MAXNAME + 1); + + memcpy(p, ipdata->filename, sizeof(ipdata->filename)); + p[HAMMER2_INODE_MAXNAME] = '\0'; + + return p; +} + +static void +__add_pfs_blockref(const hammer2_blockref_t *bref, struct blockref_list *blist, + const hammer2_inode_data_t *ipdata) +{ + struct blockref_msg *newp, *p; + + newp = calloc(1, sizeof(*newp)); + newp->bref = *bref; + newp->msg = calloc(1, sizeof(*ipdata)); + memcpy(newp->msg, ipdata, sizeof(*ipdata)); + + p = TAILQ_FIRST(blist); + while (p) { + char *f1 = get_inode_filename(newp->msg); + char *f2 = get_inode_filename(p->msg); + if (strcmp(f1, f2) <= 0) { + TAILQ_INSERT_BEFORE(p, newp, entry); + free(f1); + free(f2); + break; + } + p = TAILQ_NEXT(p, entry); + free(f1); + free(f2); + } + if (!p) + TAILQ_INSERT_TAIL(blist, newp, entry); +} + +static int +init_pfs_blockref(const hammer2_volume_data_t *voldata, + const hammer2_blockref_t *bref, struct blockref_list *blist) +{ + hammer2_media_data_t media; + hammer2_inode_data_t ipdata; + hammer2_blockref_t *bscan; + int i, bcount; + size_t bytes; + + if (read_media(bref, &media, &bytes)) + return -1; + if (!bytes) + return 0; + + switch (bref->type) { + case HAMMER2_BREF_TYPE_INODE: + ipdata = media.ipdata; + if (ipdata.meta.pfs_type == HAMMER2_PFSTYPE_SUPROOT) { + bscan = &ipdata.u.blockset.blockref[0]; + bcount = HAMMER2_SET_COUNT; + } else { + bscan = NULL; + bcount = 0; + if (ipdata.meta.op_flags & HAMMER2_OPFLAG_PFSROOT) + __add_pfs_blockref(bref, blist, &ipdata); + else + assert(0); /* should only see SUPROOT or PFS */ + } + break; + case HAMMER2_BREF_TYPE_INDIRECT: + bscan = &media.npdata[0]; + bcount = bytes / sizeof(hammer2_blockref_t); + break; + case HAMMER2_BREF_TYPE_VOLUME: + bscan = &media.voldata.sroot_blockset.blockref[0]; + bcount = HAMMER2_SET_COUNT; + break; + default: + bscan = NULL; + bcount = 0; + break; + } + + for (i = 0; i < bcount; ++i) + if (init_pfs_blockref(voldata, &bscan[i], blist) == -1) + return -1; + return 0; +} + +static void +cleanup_pfs_blockref(struct blockref_list *blist) +{ + cleanup_blockref_msg(blist); +} + +static void +print_media(FILE *fp, int tab, const hammer2_blockref_t *bref, + const hammer2_media_data_t *media, size_t media_bytes) +{ + const hammer2_blockref_t *bscan; + const hammer2_inode_data_t *ipdata; + int i, bcount, namelen; + char *str = NULL; + uuid_t uuid; + + switch (bref->type) { + case HAMMER2_BREF_TYPE_INODE: + ipdata = &media->ipdata; + namelen = ipdata->meta.name_len; + if (namelen > HAMMER2_INODE_MAXNAME) + namelen = 0; + tfprintf(fp, tab, "filename \"%*.*s\"\n", namelen, namelen, + ipdata->filename); + tfprintf(fp, tab, "version %d\n", ipdata->meta.version); + if ((ipdata->meta.op_flags & HAMMER2_OPFLAG_PFSROOT) || + ipdata->meta.pfs_type == HAMMER2_PFSTYPE_SUPROOT) + tfprintf(fp, tab, "pfs_subtype %d (%s)\n", + ipdata->meta.pfs_subtype, + hammer2_pfssubtype_to_str(ipdata->meta.pfs_subtype)); + tfprintf(fp, tab, "uflags 0x%08x\n", ipdata->meta.uflags); + if (ipdata->meta.rmajor || ipdata->meta.rminor) { + tfprintf(fp, tab, "rmajor %d\n", ipdata->meta.rmajor); + tfprintf(fp, tab, "rminor %d\n", ipdata->meta.rminor); + } + tfprintf(fp, tab, "ctime %s\n", + hammer2_time64_to_str(ipdata->meta.ctime, &str)); + tfprintf(fp, tab, "mtime %s\n", + hammer2_time64_to_str(ipdata->meta.mtime, &str)); + tfprintf(fp, tab, "atime %s\n", + hammer2_time64_to_str(ipdata->meta.atime, &str)); + tfprintf(fp, tab, "btime %s\n", + hammer2_time64_to_str(ipdata->meta.btime, &str)); + uuid = ipdata->meta.uid; + tfprintf(fp, tab, "uid %s\n", hammer2_uuid_to_str(&uuid, &str)); + uuid = ipdata->meta.gid; + tfprintf(fp, tab, "gid %s\n", hammer2_uuid_to_str(&uuid, &str)); + tfprintf(fp, tab, "type %s\n", + hammer2_iptype_to_str(ipdata->meta.type)); + tfprintf(fp, tab, "op_flags 0x%02x\n", ipdata->meta.op_flags); + tfprintf(fp, tab, "cap_flags 0x%04x\n", ipdata->meta.cap_flags); + tfprintf(fp, tab, "mode %-7o\n", ipdata->meta.mode); + tfprintf(fp, tab, "inum 0x%016jx\n", ipdata->meta.inum); + tfprintf(fp, tab, "size %ju ", (uintmax_t)ipdata->meta.size); + if (ipdata->meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA && + ipdata->meta.size <= HAMMER2_EMBEDDED_BYTES) + printf("(embedded data)\n"); + else + printf("\n"); + tfprintf(fp, tab, "nlinks %ju\n", + (uintmax_t)ipdata->meta.nlinks); + tfprintf(fp, tab, "iparent 0x%016jx\n", + (uintmax_t)ipdata->meta.iparent); + tfprintf(fp, tab, "name_key 0x%016jx\n", + (uintmax_t)ipdata->meta.name_key); + tfprintf(fp, tab, "name_len %u\n", ipdata->meta.name_len); + tfprintf(fp, tab, "ncopies %u\n", ipdata->meta.ncopies); + tfprintf(fp, tab, "comp_algo %u\n", ipdata->meta.comp_algo); + tfprintf(fp, tab, "target_type %u\n", ipdata->meta.target_type); + tfprintf(fp, tab, "check_algo %u\n", ipdata->meta.check_algo); + if ((ipdata->meta.op_flags & HAMMER2_OPFLAG_PFSROOT) || + ipdata->meta.pfs_type == HAMMER2_PFSTYPE_SUPROOT) { + tfprintf(fp, tab, "pfs_nmasters %u\n", + ipdata->meta.pfs_nmasters); + tfprintf(fp, tab, "pfs_type %u (%s)\n", + ipdata->meta.pfs_type, + hammer2_pfstype_to_str(ipdata->meta.pfs_type)); + tfprintf(fp, tab, "pfs_inum 0x%016jx\n", + (uintmax_t)ipdata->meta.pfs_inum); + uuid = ipdata->meta.pfs_clid; + tfprintf(fp, tab, "pfs_clid %s\n", + hammer2_uuid_to_str(&uuid, &str)); + uuid = ipdata->meta.pfs_fsid; + tfprintf(fp, tab, "pfs_fsid %s\n", + hammer2_uuid_to_str(&uuid, &str)); + tfprintf(fp, tab, "pfs_lsnap_tid 0x%016jx\n", + (uintmax_t)ipdata->meta.pfs_lsnap_tid); + } + tfprintf(fp, tab, "data_quota %ju\n", + (uintmax_t)ipdata->meta.data_quota); + tfprintf(fp, tab, "data_count %ju\n", + (uintmax_t)bref->embed.stats.data_count); + tfprintf(fp, tab, "inode_quota %ju\n", + (uintmax_t)ipdata->meta.inode_quota); + tfprintf(fp, tab, "inode_count %ju\n", + (uintmax_t)bref->embed.stats.inode_count); + break; + case HAMMER2_BREF_TYPE_INDIRECT: + bcount = media_bytes / sizeof(hammer2_blockref_t); + for (i = 0; i < bcount; ++i) { + bscan = &media->npdata[i]; + tfprintf(fp, tab, "%3d %016jx %-12s %016jx/%-2d\n", + i, (uintmax_t)bscan->data_off, + hammer2_breftype_to_str(bscan->type), + (uintmax_t)bscan->key, + bscan->keybits); + } + break; + case HAMMER2_BREF_TYPE_DIRENT: + if (bref->embed.dirent.namlen <= sizeof(bref->check.buf)) { + tfprintf(fp, tab, "filename \"%*.*s\"\n", + bref->embed.dirent.namlen, + bref->embed.dirent.namlen, + bref->check.buf); + } else { + tfprintf(fp, tab, "filename \"%*.*s\"\n", + bref->embed.dirent.namlen, + bref->embed.dirent.namlen, + media->buf); + } + tfprintf(fp, tab, "inum 0x%016jx\n", + (uintmax_t)bref->embed.dirent.inum); + tfprintf(fp, tab, "namlen %d\n", + (uintmax_t)bref->embed.dirent.namlen); + tfprintf(fp, tab, "type %s\n", + hammer2_iptype_to_str(bref->embed.dirent.type)); + break; + case HAMMER2_BREF_TYPE_FREEMAP_NODE: + bcount = media_bytes / sizeof(hammer2_blockref_t); + for (i = 0; i < bcount; ++i) { + bscan = &media->npdata[i]; + tfprintf(fp, tab, "%3d %016jx %-12s %016jx/%-2d\n", + i, (uintmax_t)bscan->data_off, + hammer2_breftype_to_str(bscan->type), + (uintmax_t)bscan->key, + bscan->keybits); + } + break; + case HAMMER2_BREF_TYPE_FREEMAP_LEAF: + for (i = 0; i < HAMMER2_FREEMAP_COUNT; ++i) { + hammer2_off_t data_off = bref->key + + i * HAMMER2_FREEMAP_LEVEL0_SIZE; +#if HAMMER2_BMAP_ELEMENTS != 8 +#error "HAMMER2_BMAP_ELEMENTS != 8" +#endif + tfprintf(fp, tab, "%016jx %04d.%04x (avail=%7d) " + "%016jx %016jx %016jx %016jx " + "%016jx %016jx %016jx %016jx\n", + data_off, i, media->bmdata[i].class, + media->bmdata[i].avail, + media->bmdata[i].bitmapq[0], + media->bmdata[i].bitmapq[1], + media->bmdata[i].bitmapq[2], + media->bmdata[i].bitmapq[3], + media->bmdata[i].bitmapq[4], + media->bmdata[i].bitmapq[5], + media->bmdata[i].bitmapq[6], + media->bmdata[i].bitmapq[7]); + } + break; + default: + break; + } + if (str) + free(str); +} + +int +test_hammer2(const char *devpath) +{ + bool failed = false; + + hammer2_init_volumes(devpath, 1); + + best_zone = find_best_zone(); + if (best_zone == -1) + fprintf(stderr, "Failed to find best zone\n"); + + if (PrintPFS) { + if (test_pfs_blockref() == -1) + failed = true; + goto end; /* print PFS info and exit */ + } + + printf("volume header\n"); + if (test_volume_header() == -1) { + failed = true; + if (!ForceOpt) + goto end; + } + + printf("freemap\n"); + if (test_blockref(HAMMER2_BREF_TYPE_FREEMAP) == -1) { + failed = true; + if (!ForceOpt) + goto end; + } + printf("volume\n"); + if (!ScanPFS) { + if (test_blockref(HAMMER2_BREF_TYPE_VOLUME) == -1) { + failed = true; + if (!ForceOpt) + goto end; + } + } else { + if (test_pfs_blockref() == -1) { + failed = true; + if (!ForceOpt) + goto end; + } + } +end: + hammer2_cleanup_volumes(); + + return failed ? -1 : 0; +} diff --git a/sbin/hammer2/Makefile b/sbin/hammer2/Makefile new file mode 100644 --- /dev/null +++ b/sbin/hammer2/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +PACKAGE=runtime +PROG= hammer2 +SRCS= cmd_debug.c cmd_pfs.c cmd_stat.c cmd_volume.c main.c ondisk.c \ + print_inode.c subs.c xxhash.c gsb_crc32.c +MAN= hammer2.8 + +.PATH: ${SRCTOP}/sys/fs/hammer2/xxhash ${SRCTOP}/sys/libkern + +WARNS?= 3 + +CFLAGS+= -DXXH_NAMESPACE=h2_ +CFLAGS+= -I${SRCTOP}/sys + +LIBADD= md + +.include diff --git a/sbin/hammer2/cmd_debug.c b/sbin/hammer2/cmd_debug.c new file mode 100644 --- /dev/null +++ b/sbin/hammer2/cmd_debug.c @@ -0,0 +1,953 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2022 Tomohiro Kusumi + * Copyright (c) 2011-2012 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * by Venkatesh Srinivas + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hammer2.h" + +#include + +#define GIG (1024LL*1024*1024) + +static int show_all_volume_headers = 0; +static int show_tab = 2; +static int show_depth = -1; +static hammer2_tid_t show_min_mirror_tid = 0; +static hammer2_tid_t show_min_modify_tid = 0; + +static void count_blocks(hammer2_bmap_data_t *bmap, int value, + hammer2_off_t *accum16, hammer2_off_t *accum64); + +/************************************************************************ + * SHOW * + ************************************************************************/ + +static void show_volhdr(hammer2_volume_data_t *voldata, int bi); +static void show_bref(hammer2_volume_data_t *voldata, int tab, + int bi, hammer2_blockref_t *bref, int norecurse); +static void tabprintf(int tab, const char *ctl, ...); + +static hammer2_off_t TotalAccum16[4]; /* includes TotalAccum64 */ +static hammer2_off_t TotalAccum64[4]; +static hammer2_off_t TotalUnavail; +static hammer2_off_t TotalFreemap; + +static +hammer2_off_t +get_next_volume(hammer2_volume_data_t *voldata, hammer2_off_t volu_loff) +{ + hammer2_off_t ret = -1; + int i; + + for (i = 0; i < HAMMER2_MAX_VOLUMES; ++i) { + hammer2_off_t tmp = voldata->volu_loff[i]; + if (tmp > volu_loff) { + ret = tmp; + break; + } + } + return ret; +} + +int +cmd_show(const char *devpath, int which) +{ + hammer2_blockref_t broot; + hammer2_blockref_t best; + hammer2_media_data_t media; + hammer2_media_data_t best_media; + hammer2_off_t off, volu_loff, next_volu_loff = 0; + int fd; + int i; + int best_i; + char *env; + + memset(TotalAccum16, 0, sizeof(TotalAccum16)); + memset(TotalAccum64, 0, sizeof(TotalAccum64)); + TotalUnavail = TotalFreemap = 0; + + env = getenv("HAMMER2_SHOW_ALL_VOLUME_HEADERS"); + if (env != NULL) { + show_all_volume_headers = (int)strtol(env, NULL, 0); + if (errno) + show_all_volume_headers = 0; + } + env = getenv("HAMMER2_SHOW_TAB"); + if (env != NULL) { + show_tab = (int)strtol(env, NULL, 0); + if (errno || show_tab < 0 || show_tab > 8) + show_tab = 2; + } + env = getenv("HAMMER2_SHOW_DEPTH"); + if (env != NULL) { + show_depth = (int)strtol(env, NULL, 0); + if (errno || show_depth < 0) + show_depth = -1; + } + env = getenv("HAMMER2_SHOW_MIN_MIRROR_TID"); + if (env != NULL) { + show_min_mirror_tid = (hammer2_tid_t)strtoull(env, NULL, 16); + if (errno) + show_min_mirror_tid = 0; + } + env = getenv("HAMMER2_SHOW_MIN_MODIFY_TID"); + if (env != NULL) { + show_min_modify_tid = (hammer2_tid_t)strtoull(env, NULL, 16); + if (errno) + show_min_modify_tid = 0; + } + + hammer2_init_volumes(devpath, 1); + int all_volume_headers = VerboseOpt >= 3 || show_all_volume_headers; +next_volume: + volu_loff = next_volu_loff; + next_volu_loff = -1; + printf("%s\n", hammer2_get_volume_path(volu_loff)); + /* + * Show the tree using the best volume header. + * -vvv will show the tree for all four volume headers. + */ + best_i = -1; + bzero(&best, sizeof(best)); + bzero(&best_media, sizeof(best_media)); + for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) { + bzero(&broot, sizeof(broot)); + broot.data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX; + off = broot.data_off & ~HAMMER2_OFF_MASK_RADIX; + fd = hammer2_get_volume_fd(volu_loff); + lseek(fd, off, SEEK_SET); + if (read(fd, &media, HAMMER2_PBUFSIZE) == + (ssize_t)HAMMER2_PBUFSIZE) { + broot.mirror_tid = media.voldata.mirror_tid; + if (best_i < 0 || best.mirror_tid < broot.mirror_tid) { + best_i = i; + best = broot; + best_media = media; + } + printf("Volume header %d: mirror_tid=%016jx\n", + i, (intmax_t)broot.mirror_tid); + + if (all_volume_headers) { + switch(which) { + case 0: + broot.type = HAMMER2_BREF_TYPE_VOLUME; + show_bref(&media.voldata, 0, i, &broot, + 0); + break; + case 1: + broot.type = HAMMER2_BREF_TYPE_FREEMAP; + show_bref(&media.voldata, 0, i, &broot, + 0); + break; + default: + show_volhdr(&media.voldata, i); + if (i == 0) + next_volu_loff = get_next_volume(&media.voldata, volu_loff); + break; + } + if (i != HAMMER2_NUM_VOLHDRS - 1) + printf("\n"); + } + } + } + if (next_volu_loff != (hammer2_off_t)-1) { + printf("---------------------------------------------\n"); + goto next_volume; + } + + if (!all_volume_headers) { + switch(which) { + case 0: + best.type = HAMMER2_BREF_TYPE_VOLUME; + show_bref(&best_media.voldata, 0, best_i, &best, 0); + break; + case 1: + best.type = HAMMER2_BREF_TYPE_FREEMAP; + show_bref(&best_media.voldata, 0, best_i, &best, 0); + break; + default: + show_volhdr(&best_media.voldata, best_i); + next_volu_loff = get_next_volume(&best_media.voldata, volu_loff); + if (next_volu_loff != (hammer2_off_t)-1) { + printf("---------------------------------------------\n"); + goto next_volume; + } + break; + } + } + + if (which == 1 && VerboseOpt < 3) { + printf("Total unallocated storage: %6.3fGiB (%6.3fGiB in 64KB chunks)\n", + (double)TotalAccum16[0] / GIG, + (double)TotalAccum64[0] / GIG); + printf("Total possibly free storage: %6.3fGiB (%6.3fGiB in 64KB chunks)\n", + (double)TotalAccum16[2] / GIG, + (double)TotalAccum64[2] / GIG); + printf("Total allocated storage: %6.3fGiB (%6.3fGiB in 64KB chunks)\n", + (double)TotalAccum16[3] / GIG, + (double)TotalAccum64[3] / GIG); + printf("Total unavailable storage: %6.3fGiB\n", + (double)TotalUnavail / GIG); + printf("Total freemap storage: %6.3fGiB\n", + (double)TotalFreemap / GIG); + } + hammer2_cleanup_volumes(); + + return 0; +} + +static void +show_volhdr(hammer2_volume_data_t *voldata, int bi) +{ + uint32_t i; + char *str; + const char *name; + char *buf; + uuid_t uuid; + + printf("\nVolume header %d {\n", bi); + printf(" magic 0x%016jx\n", (intmax_t)voldata->magic); + printf(" boot_beg 0x%016jx\n", (intmax_t)voldata->boot_beg); + printf(" boot_end 0x%016jx (%6.2fMB)\n", + (intmax_t)voldata->boot_end, + (double)(voldata->boot_end - voldata->boot_beg) / + (1024.0*1024.0)); + printf(" aux_beg 0x%016jx\n", (intmax_t)voldata->aux_beg); + printf(" aux_end 0x%016jx (%6.2fMB)\n", + (intmax_t)voldata->aux_end, + (double)(voldata->aux_end - voldata->aux_beg) / + (1024.0*1024.0)); + printf(" volu_size 0x%016jx (%6.2fGiB)\n", + (intmax_t)voldata->volu_size, + (double)voldata->volu_size / GIG); + printf(" version %d\n", voldata->version); + printf(" flags 0x%08x\n", voldata->flags); + printf(" copyid %d\n", voldata->copyid); + printf(" freemap_vers %d\n", voldata->freemap_version); + printf(" peer_type %d\n", voldata->peer_type); + printf(" volu_id %d\n", voldata->volu_id); + printf(" nvolumes %d\n", voldata->nvolumes); + + str = NULL; + uuid = voldata->fsid; + hammer2_uuid_to_str(&uuid, &str); + printf(" fsid %s\n", str); + free(str); + + str = NULL; + uuid = voldata->fstype; + hammer2_uuid_to_str(&uuid, &str); + printf(" fstype %s\n", str); + if (!strcmp(str, "5cbb9ad1-862d-11dc-a94d-01301bb8a9f5")) + name = "DragonFly HAMMER2"; + else + name = "?"; + printf(" (%s)\n", name); + free(str); + + printf(" allocator_size 0x%016jx (%6.2fGiB)\n", + voldata->allocator_size, + (double)voldata->allocator_size / GIG); + printf(" allocator_free 0x%016jx (%6.2fGiB)\n", + voldata->allocator_free, + (double)voldata->allocator_free / GIG); + printf(" allocator_beg 0x%016jx (%6.2fGiB)\n", + voldata->allocator_beg, + (double)voldata->allocator_beg / GIG); + + printf(" mirror_tid 0x%016jx\n", voldata->mirror_tid); + printf(" reserved0080 0x%016jx\n", voldata->reserved0080); + printf(" reserved0088 0x%016jx\n", voldata->reserved0088); + printf(" freemap_tid 0x%016jx\n", voldata->freemap_tid); + printf(" bulkfree_tid 0x%016jx\n", voldata->bulkfree_tid); + for (i = 0; i < nitems(voldata->reserved00A0); ++i) { + printf(" reserved00A0/%u 0x%016jx\n", + i, voldata->reserved00A0[0]); + } + printf(" total_size 0x%016jx\n", voldata->total_size); + + printf(" copyexists "); + for (i = 0; i < nitems(voldata->copyexists); ++i) + printf(" 0x%02x", voldata->copyexists[i]); + printf("\n"); + + /* + * NOTE: Index numbers and ICRC_SECTn definitions are not matched, + * the ICRC for sector 0 actually uses the last index, for + * example. + * + * NOTE: The whole voldata CRC does not have to match critically + * as certain sub-areas of the volume header have their own + * CRCs. + */ + printf("\n"); + for (i = 0; i < nitems(voldata->icrc_sects); ++i) { + printf(" icrc_sects[%u] ", i); + switch(i) { + case HAMMER2_VOL_ICRC_SECT0: + printf("0x%08x/0x%08x", + hammer2_icrc32((char *)voldata + + HAMMER2_VOLUME_ICRC0_OFF, + HAMMER2_VOLUME_ICRC0_SIZE), + voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT0]); + if (hammer2_icrc32((char *)voldata + + HAMMER2_VOLUME_ICRC0_OFF, + HAMMER2_VOLUME_ICRC0_SIZE) == + voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT0]) { + printf(" (OK)"); + } else { + printf(" (FAILED)"); + } + break; + case HAMMER2_VOL_ICRC_SECT1: + printf("0x%08x/0x%08x", + hammer2_icrc32((char *)voldata + + HAMMER2_VOLUME_ICRC1_OFF, + HAMMER2_VOLUME_ICRC1_SIZE), + voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT1]); + if (hammer2_icrc32((char *)voldata + + HAMMER2_VOLUME_ICRC1_OFF, + HAMMER2_VOLUME_ICRC1_SIZE) == + voldata->icrc_sects[HAMMER2_VOL_ICRC_SECT1]) { + printf(" (OK)"); + } else { + printf(" (FAILED)"); + } + + break; + default: + printf("0x%08x (reserved)", voldata->icrc_sects[i]); + break; + } + printf("\n"); + } + printf(" icrc_volhdr 0x%08x/0x%08x", + hammer2_icrc32((char *)voldata + HAMMER2_VOLUME_ICRCVH_OFF, + HAMMER2_VOLUME_ICRCVH_SIZE), + voldata->icrc_volheader); + if (hammer2_icrc32((char *)voldata + HAMMER2_VOLUME_ICRCVH_OFF, + HAMMER2_VOLUME_ICRCVH_SIZE) == + voldata->icrc_volheader) { + printf(" (OK)\n"); + } else { + printf(" (FAILED - not a critical error)\n"); + } + + /* + * The super-root and freemap blocksets (not recursed) + */ + printf("\n"); + printf(" sroot_blockset {\n"); + for (i = 0; i < HAMMER2_SET_COUNT; ++i) { + show_bref(voldata, 16, i, + &voldata->sroot_blockset.blockref[i], 2); + } + printf(" }\n"); + + printf(" freemap_blockset {\n"); + for (i = 0; i < HAMMER2_SET_COUNT; ++i) { + show_bref(voldata, 16, i, + &voldata->freemap_blockset.blockref[i], 2); + } + printf(" }\n"); + + buf = calloc(1, sizeof(voldata->volu_loff)); + if (bcmp(buf, voldata->volu_loff, sizeof(voldata->volu_loff))) { + printf("\n"); + for (i = 0; i < HAMMER2_MAX_VOLUMES; ++i) { + hammer2_off_t loff = voldata->volu_loff[i]; + if (loff != (hammer2_off_t)-1) + printf(" volu_loff[%d] 0x%016jx\n", i, loff); + } + } + free(buf); + + printf("}\n"); +} + +static void +show_bref(hammer2_volume_data_t *voldata, int tab, int bi, + hammer2_blockref_t *bref, int norecurse) +{ + hammer2_media_data_t media; + hammer2_blockref_t *bscan; + hammer2_off_t tmp; + int i, bcount, namelen, failed, obrace, fd; + int type_pad; + size_t bytes; + const char *type_str; + char *str = NULL; + uint32_t cv; + uint64_t cv64; + static int init_tab = -1; + uuid_t uuid; + + SHA256_CTX hash_ctx; + union { + uint8_t digest[SHA256_DIGEST_LENGTH]; + uint64_t digest64[SHA256_DIGEST_LENGTH/8]; + } u; + + /* omit if smaller than mininum mirror_tid threshold */ + if (bref->mirror_tid < show_min_mirror_tid) + return; + /* omit if smaller than mininum modify_tid threshold */ + if (bref->modify_tid < show_min_modify_tid) { + if (bref->modify_tid) + return; + else if (bref->type == HAMMER2_BREF_TYPE_INODE && !bref->leaf_count) + return; + } + + if (init_tab == -1) + init_tab = tab; + + bytes = (bref->data_off & HAMMER2_OFF_MASK_RADIX); + if (bytes) + bytes = (size_t)1 << bytes; + if (bytes) { + hammer2_off_t io_off; + hammer2_off_t io_base; + size_t io_bytes; + size_t boff; + + io_off = bref->data_off & ~HAMMER2_OFF_MASK_RADIX; + io_base = io_off & ~(hammer2_off_t)(HAMMER2_LBUFSIZE - 1); + boff = io_off - io_base; + + io_bytes = HAMMER2_LBUFSIZE; + while (io_bytes + boff < bytes) + io_bytes <<= 1; + + if (io_bytes > sizeof(media)) { + printf("(bad block size %zu)\n", bytes); + return; + } + if (bref->type != HAMMER2_BREF_TYPE_DATA || VerboseOpt >= 1) { + fd = hammer2_get_volume_fd(io_off); + lseek(fd, io_base - hammer2_get_volume_offset(io_base), + SEEK_SET); + if (read(fd, &media, io_bytes) != (ssize_t)io_bytes) { + printf("(media read failed)\n"); + return; + } + if (boff) + bcopy((char *)&media + boff, &media, bytes); + } + } + + bscan = NULL; + bcount = 0; + namelen = 0; + failed = 0; + obrace = 1; + + type_str = hammer2_breftype_to_str(bref->type); + type_pad = 8 - strlen(type_str); + if (type_pad < 0) + type_pad = 0; + + switch(bref->type) { + case HAMMER2_BREF_TYPE_INODE: + assert(bytes); + if (!(media.ipdata.meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA)) { + bscan = &media.ipdata.u.blockset.blockref[0]; + bcount = HAMMER2_SET_COUNT; + } + break; + case HAMMER2_BREF_TYPE_INDIRECT: + assert(bytes); + bscan = &media.npdata[0]; + bcount = bytes / sizeof(hammer2_blockref_t); + break; + case HAMMER2_BREF_TYPE_VOLUME: + bscan = &media.voldata.sroot_blockset.blockref[0]; + bcount = HAMMER2_SET_COUNT; + break; + case HAMMER2_BREF_TYPE_FREEMAP: + bscan = &media.voldata.freemap_blockset.blockref[0]; + bcount = HAMMER2_SET_COUNT; + break; + case HAMMER2_BREF_TYPE_FREEMAP_NODE: + assert(bytes); + bscan = &media.npdata[0]; + bcount = bytes / sizeof(hammer2_blockref_t); + break; + } + + if (QuietOpt > 0) { + tabprintf(tab, + "%s.%-3d %016jx %016jx/%-2d " + "vol=%d mir=%016jx mod=%016jx leafcnt=%d ", + type_str, bi, (intmax_t)bref->data_off, + (intmax_t)bref->key, (intmax_t)bref->keybits, + hammer2_get_volume_id(bref->data_off), + (intmax_t)bref->mirror_tid, + (intmax_t)bref->modify_tid, + bref->leaf_count); + } else { + tabprintf(tab, "%s.%-3d%*.*s %016jx %016jx/%-2d ", + type_str, bi, type_pad, type_pad, "", + (intmax_t)bref->data_off, + (intmax_t)bref->key, (intmax_t)bref->keybits); + /*if (norecurse > 1)*/ { + printf("\n"); + tabprintf(tab + 13, ""); + } + printf("vol=%d mir=%016jx mod=%016jx lfcnt=%d ", + hammer2_get_volume_id(bref->data_off), + (intmax_t)bref->mirror_tid, (intmax_t)bref->modify_tid, + bref->leaf_count); + if (/*norecurse > 1 && */ (bcount || bref->flags || + bref->type == HAMMER2_BREF_TYPE_FREEMAP_NODE || + bref->type == HAMMER2_BREF_TYPE_FREEMAP_LEAF)) { + printf("\n"); + tabprintf(tab + 13, ""); + } + } + + if (bcount) + printf("bcnt=%d ", bcount); + if (bref->flags) + printf("flags=%02x ", bref->flags); + if (bref->type == HAMMER2_BREF_TYPE_FREEMAP_NODE || + bref->type == HAMMER2_BREF_TYPE_FREEMAP_LEAF) { + printf("bigmask=%08x avail=%ju ", + bref->check.freemap.bigmask, + (uintmax_t)bref->check.freemap.avail); + } + + /* + * Check data integrity in verbose mode, otherwise we are just doing + * a quick meta-data scan. Meta-data integrity is always checked. + * (Also see the check above that ensures the media data is loaded, + * otherwise there's no data to check!). + * + * WARNING! bref->check state may be used for other things when + * bref has no data (bytes == 0). + */ + if (bytes && + (bref->type != HAMMER2_BREF_TYPE_DATA || VerboseOpt >= 1)) { + if (!(QuietOpt > 0)) { + /*if (norecurse > 1)*/ { + printf("\n"); + tabprintf(tab + 13, ""); + } + } + + switch(HAMMER2_DEC_CHECK(bref->methods)) { + case HAMMER2_CHECK_NONE: + printf("meth=%02x ", bref->methods); + break; + case HAMMER2_CHECK_DISABLED: + printf("meth=%02x ", bref->methods); + break; + case HAMMER2_CHECK_ISCSI32: + cv = hammer2_icrc32(&media, bytes); + if (bref->check.iscsi32.value != cv) { + printf("(icrc %02x:%08x/%08x failed) ", + bref->methods, + bref->check.iscsi32.value, + cv); + failed = 1; + } else { + printf("meth=%02x iscsi32=%08x ", + bref->methods, cv); + } + break; + case HAMMER2_CHECK_XXHASH64: + cv64 = XXH64(&media, bytes, XXH_HAMMER2_SEED); + if (bref->check.xxhash64.value != cv64) { + printf("(xxhash64 %02x:%016jx/%016jx failed) ", + bref->methods, + bref->check.xxhash64.value, + cv64); + failed = 1; + } else { + printf("meth=%02x xxh=%016jx ", + bref->methods, cv64); + } + break; + case HAMMER2_CHECK_SHA192: + SHA256_Init(&hash_ctx); + SHA256_Update(&hash_ctx, &media, bytes); + SHA256_Final(u.digest, &hash_ctx); + u.digest64[2] ^= u.digest64[3]; + if (memcmp(u.digest, bref->check.sha192.data, + sizeof(bref->check.sha192.data))) { + printf("(sha192 failed) "); + failed = 1; + } else { + printf("meth=%02x ", bref->methods); + } + break; + case HAMMER2_CHECK_FREEMAP: + cv = hammer2_icrc32(&media, bytes); + if (bref->check.freemap.icrc32 != cv) { + printf("(fcrc %02x:%08x/%08x failed) ", + bref->methods, + bref->check.freemap.icrc32, + cv); + failed = 1; + } else { + printf("meth=%02x fcrc=%08x ", + bref->methods, cv); + } + break; + } + } + + tab += show_tab; + + if (QuietOpt > 0) { + obrace = 0; + printf("\n"); + goto skip_data; + } + + switch(bref->type) { + case HAMMER2_BREF_TYPE_EMPTY: + if (norecurse) + printf("\n"); + obrace = 0; + break; + case HAMMER2_BREF_TYPE_DIRENT: + printf("{\n"); + if (bref->embed.dirent.namlen <= sizeof(bref->check.buf)) { + tabprintf(tab, "filename \"%*.*s\"\n", + bref->embed.dirent.namlen, + bref->embed.dirent.namlen, + bref->check.buf); + } else { + tabprintf(tab, "filename \"%*.*s\"\n", + bref->embed.dirent.namlen, + bref->embed.dirent.namlen, + media.buf); + } + tabprintf(tab, "inum 0x%016jx\n", + (uintmax_t)bref->embed.dirent.inum); + tabprintf(tab, "nlen %d\n", bref->embed.dirent.namlen); + tabprintf(tab, "type %s\n", + hammer2_iptype_to_str(bref->embed.dirent.type)); + break; + case HAMMER2_BREF_TYPE_INODE: + printf("{\n"); + namelen = media.ipdata.meta.name_len; + if (namelen > HAMMER2_INODE_MAXNAME) + namelen = 0; + tabprintf(tab, "filename \"%*.*s\"\n", + namelen, namelen, media.ipdata.filename); + tabprintf(tab, "version %d\n", media.ipdata.meta.version); + if ((media.ipdata.meta.op_flags & HAMMER2_OPFLAG_PFSROOT) || + media.ipdata.meta.pfs_type == HAMMER2_PFSTYPE_SUPROOT) { + tabprintf(tab, "pfs_st %d (%s)\n", + media.ipdata.meta.pfs_subtype, + hammer2_pfssubtype_to_str(media.ipdata.meta.pfs_subtype)); + } + tabprintf(tab, "uflags 0x%08x\n", + media.ipdata.meta.uflags); + if (media.ipdata.meta.rmajor || media.ipdata.meta.rminor) { + tabprintf(tab, "rmajor %d\n", + media.ipdata.meta.rmajor); + tabprintf(tab, "rminor %d\n", + media.ipdata.meta.rminor); + } + tabprintf(tab, "ctime %s\n", + hammer2_time64_to_str(media.ipdata.meta.ctime, &str)); + tabprintf(tab, "mtime %s\n", + hammer2_time64_to_str(media.ipdata.meta.mtime, &str)); + tabprintf(tab, "atime %s\n", + hammer2_time64_to_str(media.ipdata.meta.atime, &str)); + tabprintf(tab, "btime %s\n", + hammer2_time64_to_str(media.ipdata.meta.btime, &str)); + uuid = media.ipdata.meta.uid; + tabprintf(tab, "uid %s\n", + hammer2_uuid_to_str(&uuid, &str)); + uuid = media.ipdata.meta.gid; + tabprintf(tab, "gid %s\n", + hammer2_uuid_to_str(&uuid, &str)); + tabprintf(tab, "type %s\n", + hammer2_iptype_to_str(media.ipdata.meta.type)); + tabprintf(tab, "opflgs 0x%02x\n", + media.ipdata.meta.op_flags); + tabprintf(tab, "capflgs 0x%04x\n", + media.ipdata.meta.cap_flags); + tabprintf(tab, "mode %-7o\n", + media.ipdata.meta.mode); + tabprintf(tab, "inum 0x%016jx\n", + media.ipdata.meta.inum); + tabprintf(tab, "size %ju ", + (uintmax_t)media.ipdata.meta.size); + if (media.ipdata.meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA && + media.ipdata.meta.size <= HAMMER2_EMBEDDED_BYTES) + printf("(embedded data)\n"); + else + printf("\n"); + tabprintf(tab, "nlinks %ju\n", + (uintmax_t)media.ipdata.meta.nlinks); + tabprintf(tab, "iparent 0x%016jx\n", + (uintmax_t)media.ipdata.meta.iparent); + tabprintf(tab, "name_key 0x%016jx\n", + (uintmax_t)media.ipdata.meta.name_key); + tabprintf(tab, "name_len %u\n", + media.ipdata.meta.name_len); + tabprintf(tab, "ncopies %u\n", + media.ipdata.meta.ncopies); + tabprintf(tab, "compalg %u\n", + media.ipdata.meta.comp_algo); + tabprintf(tab, "target_t %u\n", + media.ipdata.meta.target_type); + tabprintf(tab, "checkalg %u\n", + media.ipdata.meta.check_algo); + if ((media.ipdata.meta.op_flags & HAMMER2_OPFLAG_PFSROOT) || + media.ipdata.meta.pfs_type == HAMMER2_PFSTYPE_SUPROOT) { + tabprintf(tab, "pfs_nmas %u\n", + media.ipdata.meta.pfs_nmasters); + tabprintf(tab, "pfs_type %u (%s)\n", + media.ipdata.meta.pfs_type, + hammer2_pfstype_to_str(media.ipdata.meta.pfs_type)); + tabprintf(tab, "pfs_inum 0x%016jx\n", + (uintmax_t)media.ipdata.meta.pfs_inum); + uuid = media.ipdata.meta.pfs_clid; + tabprintf(tab, "pfs_clid %s\n", + hammer2_uuid_to_str(&uuid, &str)); + uuid = media.ipdata.meta.pfs_fsid; + tabprintf(tab, "pfs_fsid %s\n", + hammer2_uuid_to_str(&uuid, &str)); + tabprintf(tab, "pfs_lsnap_tid 0x%016jx\n", + (uintmax_t)media.ipdata.meta.pfs_lsnap_tid); + } + tabprintf(tab, "data_quota %ju\n", + (uintmax_t)media.ipdata.meta.data_quota); + tabprintf(tab, "data_count %ju\n", + (uintmax_t)bref->embed.stats.data_count); + tabprintf(tab, "inode_quota %ju\n", + (uintmax_t)media.ipdata.meta.inode_quota); + tabprintf(tab, "inode_count %ju\n", + (uintmax_t)bref->embed.stats.inode_count); + break; + case HAMMER2_BREF_TYPE_INDIRECT: + printf("{\n"); + break; + case HAMMER2_BREF_TYPE_DATA: + printf("\n"); + obrace = 0; + break; + case HAMMER2_BREF_TYPE_VOLUME: + printf("mirror_tid=%016jx freemap_tid=%016jx ", + media.voldata.mirror_tid, + media.voldata.freemap_tid); + printf("{\n"); + break; + case HAMMER2_BREF_TYPE_FREEMAP: + printf("mirror_tid=%016jx freemap_tid=%016jx ", + media.voldata.mirror_tid, + media.voldata.freemap_tid); + printf("{\n"); + break; + case HAMMER2_BREF_TYPE_FREEMAP_LEAF: + printf("{\n"); + tmp = bref->data_off & ~HAMMER2_OFF_MASK_RADIX; + tmp &= HAMMER2_SEGMASK; + tmp /= HAMMER2_PBUFSIZE; + assert(tmp >= HAMMER2_ZONE_FREEMAP_00); + assert(tmp < HAMMER2_ZONE_FREEMAP_END); + tmp -= HAMMER2_ZONE_FREEMAP_00; + tmp /= HAMMER2_ZONE_FREEMAP_INC; + tabprintf(tab, "rotation=%d\n", (int)tmp); + + for (i = 0; i < HAMMER2_FREEMAP_COUNT; ++i) { + hammer2_off_t data_off = bref->key + + i * HAMMER2_FREEMAP_LEVEL0_SIZE; +#if HAMMER2_BMAP_ELEMENTS != 8 +#error "cmd_debug.c: HAMMER2_BMAP_ELEMENTS expected to be 8" +#endif + tabprintf(tab + 4, "%016jx %04d.%04x linear=%06x avail=%06x " + "%016jx %016jx %016jx %016jx " + "%016jx %016jx %016jx %016jx\n", + data_off, i, media.bmdata[i].class, + media.bmdata[i].linear, + media.bmdata[i].avail, + media.bmdata[i].bitmapq[0], + media.bmdata[i].bitmapq[1], + media.bmdata[i].bitmapq[2], + media.bmdata[i].bitmapq[3], + media.bmdata[i].bitmapq[4], + media.bmdata[i].bitmapq[5], + media.bmdata[i].bitmapq[6], + media.bmdata[i].bitmapq[7]); + } + tabprintf(tab, "}\n"); + break; + case HAMMER2_BREF_TYPE_FREEMAP_NODE: + printf("{\n"); + tmp = bref->data_off & ~HAMMER2_OFF_MASK_RADIX; + tmp &= HAMMER2_SEGMASK; + tmp /= HAMMER2_PBUFSIZE; + assert(tmp >= HAMMER2_ZONE_FREEMAP_00); + assert(tmp < HAMMER2_ZONE_FREEMAP_END); + tmp -= HAMMER2_ZONE_FREEMAP_00; + tmp /= HAMMER2_ZONE_FREEMAP_INC; + tabprintf(tab, "rotation=%d\n", (int)tmp); + break; + default: + printf("\n"); + obrace = 0; + break; + } + if (str) + free(str); + +skip_data: + /* + * Update statistics. + */ + switch(bref->type) { + case HAMMER2_BREF_TYPE_FREEMAP_LEAF: + for (i = 0; i < HAMMER2_FREEMAP_COUNT; ++i) { + hammer2_off_t data_off = bref->key + + i * HAMMER2_FREEMAP_LEVEL0_SIZE; + if (data_off >= voldata->aux_end && + data_off < hammer2_get_total_size()) { + int j; + for (j = 0; j < 4; ++j) + count_blocks(&media.bmdata[i], j, + &TotalAccum16[j], + &TotalAccum64[j]); + } else + TotalUnavail += HAMMER2_FREEMAP_LEVEL0_SIZE; + } + TotalFreemap += HAMMER2_FREEMAP_LEVEL1_SIZE; + break; + default: + break; + } + + /* + * Recurse if norecurse == 0. If the CRC failed, pass norecurse = 1. + * That is, if an indirect or inode fails we still try to list its + * direct children to help with debugging, but go no further than + * that because they are probably garbage. + */ + if (show_depth == -1 || ((tab - init_tab) / show_tab) < show_depth) { + for (i = 0; norecurse == 0 && i < bcount; ++i) { + if (bscan[i].type != HAMMER2_BREF_TYPE_EMPTY) { + show_bref(voldata, tab, i, &bscan[i], + failed); + } + } + } + tab -= show_tab; + if (obrace) { + if (bref->type == HAMMER2_BREF_TYPE_INODE) + tabprintf(tab, "} (%s.%d, \"%*.*s\")\n", + type_str, bi, namelen, namelen, + media.ipdata.filename); + else + tabprintf(tab, "} (%s.%d)\n", type_str, bi); + } +} + +static +void +count_blocks(hammer2_bmap_data_t *bmap, int value, + hammer2_off_t *accum16, hammer2_off_t *accum64) +{ + int i, j, bits; + hammer2_bitmap_t value16, value64; + + bits = (int)sizeof(hammer2_bitmap_t) * 8; + assert(bits == 64); + + value16 = value; + assert(value16 < 4); + value64 = (value16 << 6) | (value16 << 4) | (value16 << 2) | value16; + assert(value64 < 256); + + for (i = 0; i < HAMMER2_BMAP_ELEMENTS; ++i) { + hammer2_bitmap_t bm = bmap->bitmapq[i]; + hammer2_bitmap_t bm_save = bm; + hammer2_bitmap_t mask; + + mask = 0x03; /* 2 bits per 16KB */ + for (j = 0; j < bits; j += 2) { + if ((bm & mask) == value16) + *accum16 += 16384; + bm >>= 2; + } + + bm = bm_save; + mask = 0xFF; /* 8 bits per 64KB chunk */ + for (j = 0; j < bits; j += 8) { + if ((bm & mask) == value64) + *accum64 += 65536; + bm >>= 8; + } + } +} + +int +cmd_dumpchain(const char *path, u_int flags) +{ + int dummy = (int)flags; + int ecode = 0; + int fd; + + fd = open(path, O_RDONLY); + if (fd >= 0) { + if (ioctl(fd, HAMMER2IOC_DEBUG_DUMP, &dummy) < 0) { + fprintf(stderr, "%s: %s\n", path, strerror(errno)); + ecode = 1; + } + close(fd); + } else { + fprintf(stderr, "unable to open %s\n", path); + ecode = 1; + } + return ecode; +} + +static +void +tabprintf(int tab, const char *ctl, ...) +{ + va_list va; + + printf("%*.*s", tab, tab, ""); + va_start(va, ctl); + vprintf(ctl, va); + va_end(va); +} diff --git a/sbin/hammer2/cmd_pfs.c b/sbin/hammer2/cmd_pfs.c new file mode 100644 --- /dev/null +++ b/sbin/hammer2/cmd_pfs.c @@ -0,0 +1,152 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2022 Tomohiro Kusumi + * Copyright (c) 2011-2012 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * by Venkatesh Srinivas + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hammer2.h" + +struct pfs_entry { + TAILQ_ENTRY(pfs_entry) entry; + char name[NAME_MAX+1]; + char s[NAME_MAX+1]; +}; + +int +cmd_pfs_list(int ac, char **av) +{ + hammer2_ioc_pfs_t pfs; + int ecode = 0; + int fd; + int i; + int all = 0; + char *pfs_id_str = NULL; + const char *type_str; + TAILQ_HEAD(, pfs_entry) head; + struct pfs_entry *p, *e; + + if (ac == 1 && av[0] == NULL) { + av = get_hammer2_mounts(&ac); + all = 1; + } + + for (i = 0; i < ac; ++i) { + if ((fd = hammer2_ioctl_handle(av[i])) < 0) + return(1); + bzero(&pfs, sizeof(pfs)); + TAILQ_INIT(&head); + if (i) + printf("\n"); + + while ((pfs.name_key = pfs.name_next) != (hammer2_key_t)-1) { + if (ioctl(fd, HAMMER2IOC_PFS_GET, &pfs) < 0) { + perror("ioctl"); + ecode = 1; + break; + } + hammer2_uuid_to_str(&pfs.pfs_clid, &pfs_id_str); + if (pfs.pfs_type == HAMMER2_PFSTYPE_MASTER) { + if (pfs.pfs_subtype == HAMMER2_PFSSUBTYPE_NONE) + type_str = "MASTER"; + else + type_str = hammer2_pfssubtype_to_str( + pfs.pfs_subtype); + } else { + type_str = hammer2_pfstype_to_str(pfs.pfs_type); + } + e = calloc(1, sizeof(*e)); + snprintf(e->name, sizeof(e->name), "%s", pfs.name); + snprintf(e->s, sizeof(e->s), "%-11s %s", + type_str, pfs_id_str); + free(pfs_id_str); + pfs_id_str = NULL; + + p = TAILQ_FIRST(&head); + while (p) { + if (strcmp(e->name, p->name) <= 0) { + TAILQ_INSERT_BEFORE(p, e, entry); + break; + } + p = TAILQ_NEXT(p, entry); + } + if (!p) + TAILQ_INSERT_TAIL(&head, e, entry); + } + close(fd); + + printf("Type " + "ClusterId (pfs_clid) " + "Label on %s\n", av[i]); + while ((p = TAILQ_FIRST(&head)) != NULL) { + printf("%s %s\n", p->s, p->name); + TAILQ_REMOVE(&head, p, entry); + free(p); + } + } + + if (all) + put_hammer2_mounts(ac, av); + + return (ecode); +} + +int +cmd_pfs_getid(const char *sel_path, const char *name, int privateid) +{ + hammer2_ioc_pfs_t pfs; + int ecode = 0; + int fd; + char *pfs_id_str = NULL; + + if ((fd = hammer2_ioctl_handle(sel_path)) < 0) + return(1); + bzero(&pfs, sizeof(pfs)); + + snprintf(pfs.name, sizeof(pfs.name), "%s", name); + if (ioctl(fd, HAMMER2IOC_PFS_LOOKUP, &pfs) < 0) { + perror("ioctl"); + ecode = 1; + } else { + if (privateid) + hammer2_uuid_to_str(&pfs.pfs_fsid, &pfs_id_str); + else + hammer2_uuid_to_str(&pfs.pfs_clid, &pfs_id_str); + printf("%s\n", pfs_id_str); + free(pfs_id_str); + pfs_id_str = NULL; + } + close(fd); + return (ecode); +} diff --git a/sbin/hammer2/cmd_stat.c b/sbin/hammer2/cmd_stat.c new file mode 100644 --- /dev/null +++ b/sbin/hammer2/cmd_stat.c @@ -0,0 +1,151 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2022 Tomohiro Kusumi + * Copyright (c) 2011-2012 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hammer2.h" + +static const char *compmodestr(uint8_t comp_algo); +static const char *checkmodestr(uint8_t comp_algo); + +/* + * Should be run as root. Creates /etc/hammer2/rsa.{pub,prv} using + * an openssl command. + */ +int +cmd_stat(int ac, const char **av) +{ + hammer2_ioc_inode_t ino; + const char *cdir = "."; + int ecode = 0; + int w; + int i; + int fd; + + if (ac == 0) { + ac = 1; + av = &cdir; + } + for (i = w = 0; i < ac; ++i) { + if (w < (int)strlen(av[i])) + w = (int)strlen(av[i]); + } + if (w < 16) + w = 16; + printf("%-*.*s ncp data-use inode-use comp check quota\n", + w, w, "PATH"); + for (i = 0; i < ac; ++i) { + if ((fd = open(av[i], O_RDONLY)) < 0) { + fprintf(stderr, "%s: %s\n", av[i], strerror(errno)); + ecode = 1; + continue; + } + if (ioctl(fd, HAMMER2IOC_INODE_GET, &ino) < 0) { + fprintf(stderr, "%s: %s\n", av[i], strerror(errno)); + ecode = 1; + continue; + } + printf("%-*.*s ", w, w, av[i]); + printf("%3d ", ino.ip_data.meta.ncopies); + printf("%9s ", sizetostr(ino.data_count)); + printf("%9s ", counttostr(ino.inode_count)); + printf("%-18s ", compmodestr(ino.ip_data.meta.comp_algo)); + printf("%-12s ", checkmodestr(ino.ip_data.meta.check_algo)); + if (ino.ip_data.meta.data_quota || + ino.ip_data.meta.inode_quota) { + printf("%s", + sizetostr(ino.ip_data.meta.data_quota)); + printf("/%-12s", + counttostr(ino.ip_data.meta.inode_quota)); + } + printf("\n"); + } + return ecode; +} + +static +const char * +compmodestr(uint8_t comp_algo) +{ + static char buf[64]; + static const char *comps[] = HAMMER2_COMP_STRINGS; + int comp = HAMMER2_DEC_ALGO(comp_algo); + int level = HAMMER2_DEC_LEVEL(comp_algo); + + if (level) { + if (comp >= 0 && comp < HAMMER2_COMP_STRINGS_COUNT) + snprintf(buf, sizeof(buf), "%s:%d", + comps[comp], level); + else + snprintf(buf, sizeof(buf), "unknown(%d):%d", + comp, level); + } else { + if (comp >= 0 && comp < HAMMER2_COMP_STRINGS_COUNT) + snprintf(buf, sizeof(buf), "%s:default", + comps[comp]); + else + snprintf(buf, sizeof(buf), "unknown(%d):default", + comp); + } + return (buf); +} + +static +const char * +checkmodestr(uint8_t check_algo) +{ + static char buf[64]; + static const char *checks[] = HAMMER2_CHECK_STRINGS; + int check = HAMMER2_DEC_ALGO(check_algo); + int level = HAMMER2_DEC_LEVEL(check_algo); + + /* + * NOTE: Check algorithms normally do not encode any level. + */ + if (level) { + if (check >= 0 && check < HAMMER2_CHECK_STRINGS_COUNT) + snprintf(buf, sizeof(buf), "%s:%d", + checks[check], level); + else + snprintf(buf, sizeof(buf), "unknown(%d):%d", + check, level); + } else { + if (check >= 0 && check < HAMMER2_CHECK_STRINGS_COUNT) + snprintf(buf, sizeof(buf), "%s", checks[check]); + else + snprintf(buf, sizeof(buf), "unknown(%d)", check); + } + return (buf); +} diff --git a/sbin/hammer2/cmd_volume.c b/sbin/hammer2/cmd_volume.c new file mode 100644 --- /dev/null +++ b/sbin/hammer2/cmd_volume.c @@ -0,0 +1,108 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2020 Tomohiro Kusumi + * Copyright (c) 2020 The DragonFly Project + * All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hammer2.h" + +int +cmd_volume_list(int ac, char **av) +{ + hammer2_ioc_volume_list_t vollist; + hammer2_ioc_volume_t *entry; + int fd, i, j, n, w, all = 0, ecode = 0; + + if (ac == 1 && av[0] == NULL) { + av = get_hammer2_mounts(&ac); + all = 1; + } + vollist.volumes = calloc(HAMMER2_MAX_VOLUMES, sizeof(*vollist.volumes)); + + for (i = 0; i < ac; ++i) { + if (i) + printf("\n"); + if (ac > 1 || all) + printf("%s\n", av[i]); + if ((fd = hammer2_ioctl_handle(av[i])) < 0) { + ecode = 1; + goto failed; + } + + vollist.nvolumes = HAMMER2_MAX_VOLUMES; + if (ioctl(fd, HAMMER2IOC_VOLUME_LIST, &vollist) < 0) { + perror("ioctl"); + close(fd); + ecode = 1; + goto failed; + } + + w = 0; + for (j = 0; j < vollist.nvolumes; ++j) { + entry = &vollist.volumes[j]; + n = (int)strlen(entry->path); + if (n > w) + w = n; + } + + if (QuietOpt > 0) { + for (j = 0; j < vollist.nvolumes; ++j) { + entry = &vollist.volumes[j]; + printf("%s\n", entry->path); + } + } else { + printf("version %d\n", vollist.version); + printf("@%s\n", vollist.pfs_name); + for (j = 0; j < vollist.nvolumes; ++j) { + entry = &vollist.volumes[j]; + printf("volume%-2d %-*.*s %s", + entry->id, w, w, entry->path, + sizetostr(entry->size)); + if (VerboseOpt > 0) + printf(" 0x%016jx 0x%016jx", + (intmax_t)entry->offset, + (intmax_t)entry->size); + printf("\n"); + } + } + close(fd); + } +failed: + free(vollist.volumes); + if (all) + put_hammer2_mounts(ac, av); + + return (ecode); +} diff --git a/sbin/hammer2/hammer2.h b/sbin/hammer2/hammer2.h new file mode 100644 --- /dev/null +++ b/sbin/hammer2/hammer2.h @@ -0,0 +1,100 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2022 Tomohiro Kusumi + * Copyright (c) 2011-2012 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * by Venkatesh Srinivas + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef HAMMER2_HAMMER2_H_ +#define HAMMER2_HAMMER2_H_ + +/* + * Rollup headers for hammer2 utility + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hammer2_subs.h" + +/* user-specifiable check modes only */ +#define HAMMER2_CHECK_STRINGS { "none", "disabled", "crc32", \ + "xxhash64", "sha192" } +#define HAMMER2_CHECK_STRINGS_COUNT 5 + +#define HAMMER2_COMP_STRINGS { "none", "autozero", "lz4", "zlib" } +#define HAMMER2_COMP_STRINGS_COUNT 4 + +extern int VerboseOpt; +extern int QuietOpt; + +/* + * Hammer2 command APIs + */ +int cmd_pfs_getid(const char *sel_path, const char *name, int privateid); +int cmd_pfs_list(int ac, char **av); +int cmd_info(int ac, const char **av); +int cmd_mountall(int ac, const char **av); +int cmd_stat(int ac, const char **av); +int cmd_dumpchain(const char *path, u_int flags); +int cmd_show(const char *devpath, int which); +int cmd_volume_list(int ac, char **av); + +void print_inode(const char *path); + +#endif /* !HAMMER2_HAMMER2_H_ */ diff --git a/sbin/hammer2/hammer2.8 b/sbin/hammer2/hammer2.8 new file mode 100644 --- /dev/null +++ b/sbin/hammer2/hammer2.8 @@ -0,0 +1,137 @@ +.\" Copyright (c) 2015-2019 The DragonFly Project. All rights reserved. +.\" +.\" This code is derived from software contributed to The DragonFly Project +.\" by Matthew Dillon +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. Neither the name of The DragonFly Project nor the names of its +.\" contributors may be used to endorse or promote products derived +.\" from this software without specific, prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, +.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd September 18, 2022 +.Dt HAMMER2 8 +.Os +.Sh NAME +.Nm hammer2 +.Nd hammer2 file system utility +.Sh SYNOPSIS +.Nm +.Fl h +.Nm +.Op Fl s Ar path +.Ar command +.Op Ar argument ... +.Sh DESCRIPTION +The +.Nm +utility provides miscellaneous support functions for a +read-only HAMMER2 file system. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl s Ar path +Specify the path to a mounted HAMMER2 filesystem. +At least one PFS on a HAMMER2 filesystem must be mounted for the system +to act on all PFSs managed by it. +Every HAMMER2 filesystem typically has a PFS called "LOCAL" for this purpose. +.El +.Pp +.Nm +directives are as shown below. +Note that most directives require you to either be CD'd into a hammer2 +filesystem, specify a path to a mounted hammer2 filesystem via the +.Fl s +option, or specify a path after the directive. +It depends on the directive. +All hammer2 filesystem have a PFS called "LOCAL" which is typically mounted +locally on the host in order to be able to issue commands for other PFSs +on the filesystem. +The mount also enables PFS configuration scanning for that filesystem. +.Bl -tag -width indent +.\" ==== pfs-list ==== +.It Cm pfs-list Op path... +List all PFSs associated with all mounted hammer2 storage devices. +The list may be restricted to a particular filesystem using +.Fl s Ar mount . +.Pp +Note that hammer2 PFSs associated with storage devices which have not been +mounted in any fashion will not be listed. +At least one hammer2 label must be mounted for the PFSs on that device to be +visible. +.\" ==== pfs-clid ==== +.It Cm pfs-clid Ar label +Print the cluster id for a PFS specified by name. +.\" ==== pfs-fsid ==== +.It Cm pfs-fsid Ar label +Print the unique filesystem id for a PFS specified by name. +.\" ==== stat ==== +.It Cm stat Op path... +Print the inode statistics, compression, and other meta-data associated +with a list of paths. +.\" ==== show ==== +.It Cm show Ar devpath +Dump the radix tree for the HAMMER2 filesystem by scanning a +block device directly. +No mount is required. +.\" ==== freemap ==== +.It Cm freemap Ar devpath +Dump the freemap tree for the HAMMER2 filesystem by scanning a +block device directly. +No mount is required. +.\" ==== volhdr ==== +.It Cm volhdr Ar devpath +Dump the volume header for the HAMMER2 filesystem by scanning a +block device directly. +No mount is required. +.\" ==== volume-list ==== +.It Cm volume-list Op path... +List all volumes associated with all mounted hammer2 storage devices. +The list may be restricted to a particular filesystem using +.Fl s Ar mount . +.Pp +Note that hammer2 volumes associated with storage devices which have not been +mounted in any fashion will not be listed. +At least one hammer2 label must be mounted for the volumes on that device to be +visible. +.\" ==== printinode ==== +.It Cm printinode Ar path +Dump inode. +.Sh SEE ALSO +.Xr mount_hammer2 8 , +.Xr newfs_hammer2 8 +.Sh HISTORY +The +.Nm +utility first appeared in +.Dx 4.1 . +.Sh AUTHORS +.An Matthew Dillon Aq Mt dillon@backplane.com +.Pp +The +.Nm +utility was ported to +.Fx +by +.An Tomohiro Kusumi Aq Mt tkusumi@netbsd.org . diff --git a/sbin/hammer2/hammer2_subs.h b/sbin/hammer2/hammer2_subs.h new file mode 100644 --- /dev/null +++ b/sbin/hammer2/hammer2_subs.h @@ -0,0 +1,121 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2022 Tomohiro Kusumi + * Copyright (c) 2019 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef HAMMER2_HAMMER2_SUBS_H_ +#define HAMMER2_HAMMER2_SUBS_H_ + +#include +#include + +#include + +typedef struct hammer2_volume { + int fd; + int id; + char *path; + hammer2_off_t offset; + hammer2_off_t size; +} hammer2_volume_t; + +typedef struct hammer2_ondisk { + int version; + int nvolumes; + hammer2_volume_t volumes[HAMMER2_MAX_VOLUMES]; + hammer2_off_t total_size; + hammer2_off_t free_size; + uuid_t fsid; + uuid_t fstype; +} hammer2_ondisk_t; + +/* + * Misc functions + */ +int hammer2_ioctl_handle(const char *sel_path); +const char *hammer2_time64_to_str(uint64_t htime64, char **strp); +const char *hammer2_uuid_to_str(const uuid_t *uuid, char **strp); +const char *hammer2_iptype_to_str(uint8_t type); +const char *hammer2_pfstype_to_str(uint8_t type); +const char *hammer2_pfssubtype_to_str(uint8_t subtype); +const char *hammer2_breftype_to_str(uint8_t type); +const char *sizetostr(hammer2_off_t size); +const char *counttostr(hammer2_off_t size); +hammer2_off_t check_volume(int fd); +hammer2_key_t dirhash(const unsigned char *name, size_t len); + +uint32_t calculate_crc32c(uint32_t, const void *, size_t); + +char **get_hammer2_mounts(int *acp); +void put_hammer2_mounts(int ac, char **av); + +void hammer2_init_ondisk(hammer2_ondisk_t *fsp); +void hammer2_install_volume(hammer2_volume_t *vol, int fd, int id, + const char *path, hammer2_off_t offset, hammer2_off_t size); +void hammer2_uninstall_volume(hammer2_volume_t *vol); +void hammer2_verify_volumes(hammer2_ondisk_t *fsp, + const hammer2_volume_data_t *rootvoldata); +void hammer2_print_volumes(const hammer2_ondisk_t *fsp); +void hammer2_init_volumes(const char *blkdevs, int rdonly); +void hammer2_cleanup_volumes(void); + +int hammer2_get_volume_fd(hammer2_off_t offset); +int hammer2_get_root_volume_fd(void); +int hammer2_get_volume_id(hammer2_off_t offset); +int hammer2_get_root_volume_id(void); +const char *hammer2_get_volume_path(hammer2_off_t offset); +const char *hammer2_get_root_volume_path(void); +hammer2_off_t hammer2_get_volume_offset(hammer2_off_t offset); +hammer2_off_t hammer2_get_root_volume_offset(void); +hammer2_off_t hammer2_get_volume_size(hammer2_off_t offset); +hammer2_off_t hammer2_get_root_volume_size(void); + +hammer2_off_t hammer2_get_total_size(void); +hammer2_volume_data_t* hammer2_read_root_volume_header(void); + +static __inline +uint32_t +hammer2_icrc32(const void *buf, size_t size) +{ + return (~calculate_crc32c(-1, buf, size)); +} + +static __inline +uint32_t +hammer2_icrc32c(const void *buf, size_t size, uint32_t ocrc) +{ + return (~calculate_crc32c(~ocrc, buf, size)); +} +#endif /* !HAMMER2_HAMMER2_SUBS_H_ */ diff --git a/sbin/hammer2/main.c b/sbin/hammer2/main.c new file mode 100644 --- /dev/null +++ b/sbin/hammer2/main.c @@ -0,0 +1,224 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2022 Tomohiro Kusumi + * Copyright (c) 2011-2019 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * by Venkatesh Srinivas + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * XXX HAMMER2 userspace consists of sbin/{hammer2,newfs_hammer2, + * mount_hammer2,fsck_hammer2}. These are basically same as DragonFly + * except that write related are currently removed. Avoid non functional + * changes in the name of cleanup which makes it less easy to sync with + * DragonFly. + */ + +#include "hammer2.h" + +int VerboseOpt; +int QuietOpt; + +static void usage(int code); + +int +main(int ac, char **av) +{ + char *sel_path = NULL; + int ecode = 0; + int ch; + + /* + * Core options + */ + while ((ch = getopt(ac, av, "s:vq")) != -1) { + switch(ch) { + case 's': + sel_path = strdup(optarg); + break; + case 'v': + if (QuietOpt) + --QuietOpt; + else + ++VerboseOpt; + break; + case 'q': + if (VerboseOpt) + --VerboseOpt; + else + ++QuietOpt; + break; + default: + fprintf(stderr, "Unknown option: %c\n", ch); + usage(1); + /* not reached */ + break; + } + } + + /* + * Adjust, then process the command + */ + ac -= optind; + av += optind; + if (ac < 1) { + fprintf(stderr, "Missing command\n"); + usage(1); + /* not reached */ + } + + if (strcmp(av[0], "dumpchain") == 0) { + if (ac < 2) + ecode = cmd_dumpchain(".", (u_int)-1); + else if (ac < 3) + ecode = cmd_dumpchain(av[1], (u_int)-1); + else + ecode = cmd_dumpchain(av[1], + (u_int)strtoul(av[2], NULL, 0)); + } else if (strcmp(av[0], "pfs-clid") == 0) { + /* + * Print cluster id (uuid) for specific PFS + */ + if (ac < 2) { + fprintf(stderr, "pfs-clid: requires name\n"); + usage(1); + } + ecode = cmd_pfs_getid(sel_path, av[1], 0); + } else if (strcmp(av[0], "pfs-fsid") == 0) { + /* + * Print private id (uuid) for specific PFS + */ + if (ac < 2) { + fprintf(stderr, "pfs-fsid: requires name\n"); + usage(1); + } + ecode = cmd_pfs_getid(sel_path, av[1], 1); + } else if (strcmp(av[0], "pfs-list") == 0) { + /* + * List all PFSs + */ + if (ac >= 2) { + ecode = cmd_pfs_list(ac - 1, + (char **)(void *)&av[1]); + } else { + ecode = cmd_pfs_list(1, &sel_path); + } + } else if (strcmp(av[0], "stat") == 0) { + ecode = cmd_stat(ac - 1, (const char **)(void *)&av[1]); + } else if (strcmp(av[0], "show") == 0) { + /* + * Raw dump of filesystem. Use -v to check all crc's, and + * -vv to dump bulk file data. + */ + if (ac != 2) { + fprintf(stderr, "show: requires device path\n"); + usage(1); + } else { + cmd_show(av[1], 0); + } + } else if (strcmp(av[0], "freemap") == 0) { + /* + * Raw dump of freemap. Use -v to check all crc's, and + * -vv to dump bulk file data. + */ + if (ac != 2) { + fprintf(stderr, "freemap: requires device path\n"); + usage(1); + } else { + cmd_show(av[1], 1); + } + } else if (strcmp(av[0], "volhdr") == 0) { + /* + * Dump the volume header. + */ + if (ac != 2) { + fprintf(stderr, "volhdr: requires device path\n"); + usage(1); + } else { + cmd_show(av[1], 2); + } + } else if (strcmp(av[0], "volume-list") == 0) { + /* + * List all volumes + */ + if (ac >= 2) { + ecode = cmd_volume_list(ac - 1, + (char **)(void *)&av[1]); + } else { + ecode = cmd_volume_list(1, &sel_path); + } + } else if (strcmp(av[0], "printinode") == 0) { + if (ac != 2) { + fprintf(stderr, + "printinode: requires directory/file path\n"); + usage(1); + } else { + print_inode(av[1]); + } + } else { + fprintf(stderr, "Unrecognized command: %s\n", av[0]); + usage(1); + } + + return (ecode); +} + +static +void +usage(int code) +{ + fprintf(stderr, + "hammer2 [options] command [argument ...]\n" + " -s path Select filesystem\n" + "\n" + " pfs-list [...] " + "List PFSs\n" + " pfs-clid