os161 / userland / sbin / dumpsfs / dumpsfs.c
dumpsfs.c
Raw
/*
 * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009, 2014
 *	The President and Fellows of Harvard College.
 *
 * 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 University 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 UNIVERSITY 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 UNIVERSITY 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 <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#include <err.h>

#include "support.h"
#include "kern/sfs.h"


#ifdef HOST
/*
 * OS/161 runs natively on a big-endian platform, so we can
 * conveniently use the byteswapping functions for network byte order.
 */
#include <netinet/in.h> // for arpa/inet.h
#include <arpa/inet.h>  // for ntohl
#include "hostcompat.h"
#define SWAP64(x) ntohll(x)
#define SWAP32(x) ntohl(x)
#define SWAP16(x) ntohs(x)

extern const char *hostcompat_progname;

#else

#define SWAP64(x) (x)
#define SWAP32(x) (x)
#define SWAP16(x) (x)

#endif

#include "disk.h"

#define ARRAYCOUNT(a) (sizeof(a) / sizeof((a)[0]))
#define DIVROUNDUP(a, b) (((a) + (b) - 1) / (b))

static bool dofiles, dodirs;
static bool doindirect;
static bool recurse;

////////////////////////////////////////////////////////////
// printouts

static unsigned dumppos;

static
void
dumpval(const char *desc, const char *val)
{
	size_t dlen, vlen, used;

	dlen = strlen(desc);
	vlen = strlen(val);

	printf("    ");

	printf("%s: %s", desc, val);

	used = dlen + 2 + vlen;
	for (; used < 36; used++) {
		putchar(' ');
	}

	if (dumppos % 2 == 1) {
		printf("\n");
	}
	dumppos++;
}

static
void
dumpvalf(const char *desc, const char *valf, ...)
{
	va_list ap;
	char buf[128];

	va_start(ap, valf);
	vsnprintf(buf, sizeof(buf), valf, ap);
	va_end(ap);
	dumpval(desc, buf);
}

static
void
dumplval(const char *desc, const char *lval)
{
	if (dumppos % 2 == 1) {
		printf("\n");
		dumppos++;
	}
	printf("    %s: %s\n", desc, lval);
	dumppos += 2;
}

////////////////////////////////////////////////////////////
// fs structures

static void dumpinode(uint32_t ino, const char *name);

static
uint32_t
readsb(void)
{
	struct sfs_superblock sb;

	diskread(&sb, SFS_SUPER_BLOCK);
	if (SWAP32(sb.sb_magic) != SFS_MAGIC) {
		errx(1, "Not an sfs filesystem");
	}
	return SWAP32(sb.sb_nblocks);
}

static
void
dumpsb(void)
{
	struct sfs_superblock sb;
	unsigned i;

	diskread(&sb, SFS_SUPER_BLOCK);
	sb.sb_volname[sizeof(sb.sb_volname)-1] = 0;

	printf("Superblock\n");
	printf("----------\n");
	dumpvalf("Magic", "0x%8x", SWAP32(sb.sb_magic));
	dumpvalf("Size", "%u blocks", SWAP32(sb.sb_nblocks));
	dumpvalf("Freemap size", "%u blocks",
		 SFS_FREEMAPBLOCKS(SWAP32(sb.sb_nblocks)));
	dumpvalf("Block size", "%u bytes", SFS_BLOCKSIZE);
	dumplval("Volume name", sb.sb_volname);

	for (i=0; i<ARRAYCOUNT(sb.reserved); i++) {
		if (sb.reserved[i] != 0) {
			printf("    Word %u in reserved area: 0x%x\n",
			       i, SWAP32(sb.reserved[i]));
		}
	}
	printf("\n");
}

static
void
dumpfreemap(uint32_t fsblocks)
{
	uint32_t freemapblocks = SFS_FREEMAPBLOCKS(fsblocks);
	uint32_t i, j, k, bn;
	uint8_t data[SFS_BLOCKSIZE], mask;
	char tmp[16];

	printf("Free block bitmap\n");
	printf("-----------------\n");
	for (i=0; i<freemapblocks; i++) {
		diskread(data, SFS_FREEMAP_START+i);
		printf("    Freemap block #%u in disk block %u: blocks %u - %u"
		       " (0x%x - 0x%x)\n",
		       i, SFS_FREEMAP_START+i,
		       i*SFS_BITSPERBLOCK, (i+1)*SFS_BITSPERBLOCK - 1,
		       i*SFS_BITSPERBLOCK, (i+1)*SFS_BITSPERBLOCK - 1);
		for (j=0; j<SFS_BLOCKSIZE; j++) {
			if (j % 8 == 0) {
				snprintf(tmp, sizeof(tmp), "0x%x",
					 i*SFS_BITSPERBLOCK + j*8);
				printf("%-7s ", tmp);
			}
			for (k=0; k<8; k++) {
				bn = i*SFS_BITSPERBLOCK + j*8 + k;
				mask = 1U << k;
				if (bn >= fsblocks) {
					if (data[j] & mask) {
						putchar('x');
					}
					else {
						putchar('!');
					}
				}
				else {
					if (data[j] & mask) {
						putchar('*');
					}
					else {
						putchar('.');
					}
				}
			}
			if (j % 8 == 7) {
				printf("\n");
			}
			else {
				printf(" ");
			}
		}
	}
	printf("\n");
}

static
void
dumpindirect(uint32_t block)
{
	uint32_t ib[SFS_BLOCKSIZE/sizeof(uint32_t)];
	char tmp[128];
	unsigned i;

	if (block == 0) {
		return;
	}
	printf("Indirect block %u\n", block);

	diskread(ib, block);
	for (i=0; i<ARRAYCOUNT(ib); i++) {
		if (i % 4 == 0) {
			printf("@%-3u   ", i);
		}
		snprintf(tmp, sizeof(tmp), "%u (0x%x)",
			 SWAP32(ib[i]), SWAP32(ib[i]));
		printf("  %-16s", tmp);
		if (i % 4 == 3) {
			printf("\n");
		}
	}
}

static
uint32_t
traverse_ib(uint32_t fileblock, uint32_t numblocks, uint32_t block,
	    void (*doblock)(uint32_t, uint32_t))
{
	uint32_t ib[SFS_BLOCKSIZE/sizeof(uint32_t)];
	unsigned i;

	if (block == 0) {
		memset(ib, 0, sizeof(ib));
	}
	else {
		diskread(ib, block);
	}
	for (i=0; i<ARRAYCOUNT(ib) && fileblock < numblocks; i++) {
		doblock(fileblock++, SWAP32(ib[i]));
	}
	return fileblock;
}

static
void
traverse(const struct sfs_dinode *sfi, void (*doblock)(uint32_t, uint32_t))
{
	uint32_t fileblock;
	uint32_t numblocks;
	unsigned i;

	numblocks = DIVROUNDUP(SWAP32(sfi->sfi_size), SFS_BLOCKSIZE);

	fileblock = 0;
	for (i=0; i<SFS_NDIRECT && fileblock < numblocks; i++) {
		doblock(fileblock++, SWAP32(sfi->sfi_direct[i]));
	}
	if (fileblock < numblocks) {
		fileblock = traverse_ib(fileblock, numblocks,
					SWAP32(sfi->sfi_indirect), doblock);
	}
	assert(fileblock == numblocks);
}

static
void
dumpdirblock(uint32_t fileblock, uint32_t diskblock)
{
	struct sfs_direntry sds[SFS_BLOCKSIZE/sizeof(struct sfs_direntry)];
	int nsds = SFS_BLOCKSIZE/sizeof(struct sfs_direntry);
	int i;

	(void)fileblock;
	if (diskblock == 0) {
		printf("    [block %u - empty]\n", diskblock);
		return;
	}
	diskread(&sds, diskblock);

	printf("    [block %u]\n", diskblock);
	for (i=0; i<nsds; i++) {
		uint32_t ino = SWAP32(sds[i].sfd_ino);
		if (ino==SFS_NOINO) {
			printf("        [free entry]\n");
		}
		else {
			sds[i].sfd_name[SFS_NAMELEN-1] = 0; /* just in case */
			printf("        %u %s\n", ino, sds[i].sfd_name);
		}
	}
}

static
void
dumpdir(uint32_t ino, const struct sfs_dinode *sfi)
{
	int nentries;

	nentries = SWAP32(sfi->sfi_size) / sizeof(struct sfs_direntry);
	if (SWAP32(sfi->sfi_size) % sizeof(struct sfs_direntry) != 0) {
		warnx("Warning: dir size is not a multiple of dir entry size");
	}
	printf("Directory contents for inode %u: %d entries\n", ino, nentries);
	traverse(sfi, dumpdirblock);
}

static
void
recursedirblock(uint32_t fileblock, uint32_t diskblock)
{
	struct sfs_direntry sds[SFS_BLOCKSIZE/sizeof(struct sfs_direntry)];
	int nsds = SFS_BLOCKSIZE/sizeof(struct sfs_direntry);
	int i;

	(void)fileblock;
	if (diskblock == 0) {
		return;
	}
	diskread(&sds, diskblock);

	for (i=0; i<nsds; i++) {
		uint32_t ino = SWAP32(sds[i].sfd_ino);
		if (ino==SFS_NOINO) {
			continue;
		}
		sds[i].sfd_name[SFS_NAMELEN-1] = 0; /* just in case */
		dumpinode(ino, sds[i].sfd_name);
	}
}

static
void
recursedir(uint32_t ino, const struct sfs_dinode *sfi)
{
	int nentries;

	nentries = SWAP32(sfi->sfi_size) / sizeof(struct sfs_direntry);
	printf("Reading files in directory %u: %d entries\n", ino, nentries);
	traverse(sfi, recursedirblock);
	printf("Done with directory %u\n", ino);
}

static
void dumpfileblock(uint32_t fileblock, uint32_t diskblock)
{
	uint8_t data[SFS_BLOCKSIZE];
	unsigned i, j;
	char tmp[128];

	if (diskblock == 0) {
		printf("    0x%6x  [sparse]\n", fileblock * SFS_BLOCKSIZE);
		return;
	}

	diskread(data, diskblock);
	for (i=0; i<SFS_BLOCKSIZE; i++) {
		if (i % 16 == 0) {
			snprintf(tmp, sizeof(tmp), "0x%x",
				 fileblock * SFS_BLOCKSIZE + i);
			printf("%8s", tmp);
		}
		if (i % 8 == 0) {
			printf("  ");
		}
		else {
			printf(" ");
		}
		printf("%02x", data[i]);
		if (i % 16 == 15) {
			printf("  ");
			for (j = i-15; j<=i; j++) {
				if (data[j] < 32 || data[j] > 126) {
					putchar('.');
				}
				else {
					putchar(data[j]);
				}
			}
			printf("\n");
		}
	}
}

static
void
dumpfile(uint32_t ino, const struct sfs_dinode *sfi)
{
	printf("File contents for inode %u:\n", ino);
	traverse(sfi, dumpfileblock);
}

static
void
dumpinode(uint32_t ino, const char *name)
{
	struct sfs_dinode sfi;
	const char *typename;
	char tmp[128];
	unsigned i;

	diskread(&sfi, ino);

	printf("Inode %u", ino);
	if (name != NULL) {
		printf(" (%s)", name);
	}
	printf("\n");
	printf("--------------\n");

	switch (SWAP16(sfi.sfi_type)) {
	    case SFS_TYPE_FILE: typename = "regular file"; break;
	    case SFS_TYPE_DIR: typename = "directory"; break;
	    default: typename = "invalid"; break;
	}
	dumpvalf("Type", "%u (%s)", SWAP16(sfi.sfi_type), typename);
	dumpvalf("Size", "%u", SWAP32(sfi.sfi_size));
	dumpvalf("Link count", "%u", SWAP16(sfi.sfi_linkcount));
	printf("\n");

        printf("    Direct blocks:\n");
        for (i=0; i<SFS_NDIRECT; i++) {
		if (i % 4 == 0) {
			printf("@%-2u    ", i);
		}
		/*
		 * Assume the disk size might be > 64K sectors (which
		 * would be 32M) but is < 1024K sectors (512M) so we
		 * need up to 5 hex digits for a block number. And
		 * assume it's actually < 1 million sectors so we need
		 * only up to 6 decimal digits. The complete block
		 * number print then needs up to 16 digits.
		 */
		snprintf(tmp, sizeof(tmp), "%u (0x%x)",
			 SWAP32(sfi.sfi_direct[i]), SWAP32(sfi.sfi_direct[i]));
		printf("  %-16s", tmp);
		if (i % 4 == 3) {
			printf("\n");
		}
	}
	if (i % 4 != 0) {
		printf("\n");
	}
	printf("    Indirect block: %u (0x%x)\n",
	       SWAP32(sfi.sfi_indirect), SWAP32(sfi.sfi_indirect));
	for (i=0; i<ARRAYCOUNT(sfi.sfi_waste); i++) {
		if (sfi.sfi_waste[i] != 0) {
			printf("    Word %u in waste area: 0x%x\n",
			       i, SWAP32(sfi.sfi_waste[i]));
		}
	}

	if (doindirect) {
		dumpindirect(SWAP32(sfi.sfi_indirect));
	}

	if (SWAP16(sfi.sfi_type) == SFS_TYPE_DIR && dodirs) {
		dumpdir(ino, &sfi);
	}
	if (SWAP16(sfi.sfi_type) == SFS_TYPE_FILE && dofiles) {
		dumpfile(ino, &sfi);
	}
	if (SWAP16(sfi.sfi_type) == SFS_TYPE_DIR && recurse) {
		recursedir(ino, &sfi);
	}
}

////////////////////////////////////////////////////////////
// main

static
void
usage(void)
{
	warnx("Usage: dumpsfs [options] device/diskfile");
	warnx("   -s: dump superblock");
	warnx("   -b: dump free block bitmap");
	warnx("   -i ino: dump specified inode");
	warnx("   -I: dump indirect blocks");
	warnx("   -f: dump file contents");
	warnx("   -d: dump directory contents");
	warnx("   -r: recurse into directory contents");
	warnx("   -a: equivalent to -sbdfr -i 1");
	errx(1, "   Default is -i 1");
}

int
main(int argc, char **argv)
{
	bool dosb = false;
	bool dofreemap = false;
	uint32_t dumpino = 0;
	const char *dumpdisk = NULL;

	int i, j;
	uint32_t nblocks;

#ifdef HOST
	/* Don't do this; it frobs the tty and you can't pipe to less */
	/*hostcompat_init(argc, argv);*/
	hostcompat_progname = argv[0];
#endif

	for (i=1; i<argc; i++) {
		if (argv[i][0] == '-') {
			for (j=1; argv[i][j]; j++) {
				switch (argv[i][j]) {
				    case 's': dosb = true; break;
				    case 'b': dofreemap = true; break;
				    case 'i':
					if (argv[i][j+1] == 0) {
						dumpino = atoi(argv[++i]);
					}
					else {
						dumpino = atoi(argv[i]+j+1);
						j = strlen(argv[i]);
					}
					/* XXX ugly */
					goto nextarg;
				    case 'I': doindirect = true; break;
				    case 'f': dofiles = true; break;
				    case 'd': dodirs = true; break;
				    case 'r': recurse = true; break;
				    case 'a':
					dosb = true;
					dofreemap = true;
					dumpino = SFS_ROOTDIR_INO;
					doindirect = true;
					dofiles = true;
					dodirs = true;
					recurse = true;
					break;
				    default:
					usage();
					break;
				}
			}
		}
		else {
			if (dumpdisk != NULL) {
				usage();
			}
			dumpdisk = argv[i];
		}
	 nextarg:
		;
	}
	if (dumpdisk == NULL) {
		usage();
	}

	if (!dosb && !dofreemap && dumpino == 0) {
		dumpino = SFS_ROOTDIR_INO;
	}

	opendisk(dumpdisk);
	nblocks = readsb();

	if (dosb) {
		dumpsb();
	}
	if (dofreemap) {
		dumpfreemap(nblocks);
	}
	if (dumpino != 0) {
		dumpinode(dumpino, NULL);
	}

	closedisk();

	return 0;
}