dump_inode/dump_inode.c

473 lines
13 KiB
C

/*
* Copyright (c) 2023 Thomas Lindner <tom@dl6tom.de>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/stat.h>
#include <ufs/ffs/fs.h>
#include <ufs/ufs/dinode.h>
#include <ufs/ufs/dir.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>
int diskfd;
int filefd;
struct fs *sblock;
union dinode {
struct ufs1_dinode dp1;
struct ufs2_dinode dp2;
};
#define DIP(dp, field) \
((sblock->fs_magic == FS_UFS1_MAGIC) ? (dp)->dp1.field : (dp)->dp2.field)
typedef void (*process_fn)(union dinode *dp, void *datablock, size_t blksize,
off_t blkoff);
void usage(void);
void dump_inode(ino_t inum, union dinode *dp);
void dump_data(union dinode *dp, process_fn process_data,
process_fn process_gap);
void dump_indirect(union dinode *dp, void *indirblock, int level,
off_t *filesize, process_fn process_data,
process_fn process_gap);
void process_directory(union dinode *dp, void *datablock, size_t blksize,
off_t blkoff);
void process_dirblk(int deleted, void *dirblk, size_t size);
void process_file(union dinode *dp, void *datablock, size_t blksize,
off_t blkoff);
void process_file_gap(union dinode *dp, void *datablock, size_t gapsize,
off_t blkoff);
void *xmalloc(size_t size);
void xpread(int fd, void *buf, size_t nbytes, off_t offset);
void xwrite(int fd, void *buf, size_t nbytes);
void xpwrite(int fd, void *buf, size_t nbytes, off_t offset);
int main(int argc, char **argv) {
#define MODE_ALL 1
#define MODE_INODE 2
#define MODE_DIRECTORY 4
#define MODE_FILE 8
unsigned mode = 0;
ino_t inode;
int opt;
const char *errstr;
while ((opt = getopt(argc, argv, "adi:o:")) != -1) {
switch (opt) {
case 'a':
if (mode & MODE_INODE) {
usage();
}
mode |= MODE_ALL;
break;
case 'i':
if (mode & MODE_ALL) {
usage();
}
mode |= MODE_INODE;
inode = strtonum(optarg, 2, UINT32_MAX, &errstr);
if (errstr) {
errx(1, "Invalid inode %s: %s", optarg, errstr);
}
break;
case 'd':
if (mode & MODE_FILE) {
usage();
}
mode |= MODE_DIRECTORY;
break;
case 'o':
if (mode & (MODE_ALL | MODE_DIRECTORY)) {
usage();
}
mode |= MODE_FILE;
if (!strcmp(optarg, "-")) {
filefd = STDOUT_FILENO;
break;
}
filefd = open(optarg, O_WRONLY | O_CREAT | O_EXCL,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (filefd == -1) {
err(1, "Cannot open %s", optarg);
}
break;
default:
usage();
}
}
if (!(mode & (MODE_ALL | MODE_INODE)) || optind + 1 != argc) {
usage();
}
// open disk
char *realpath;
diskfd = opendev(argv[optind], O_RDONLY | O_NOFOLLOW, 0, &realpath);
if (diskfd == -1) {
err(1, "Cannot open %s (%s)", argv[optind], realpath);
}
// find superblock
sblock = xmalloc(SBLOCKSIZE);
off_t sblock_try[] = SBLOCKSEARCH;
int i;
for (i = 0; sblock_try[i] != -1; i++) {
xpread(diskfd, sblock, SBLOCKSIZE, sblock_try[i]);
if ((sblock->fs_magic == FS_UFS1_MAGIC ||
(sblock->fs_magic == FS_UFS2_MAGIC &&
sblock->fs_sblockloc == sblock_try[i])) &&
sblock->fs_bsize <= MAXBSIZE && sblock->fs_bsize >= sizeof(struct fs)) {
break;
}
}
if (sblock_try[i] == -1) {
errx(1, "Cannot find filesystem superblock");
}
void *inoblock = xmalloc(sblock->fs_bsize);
if (mode & MODE_ALL) {
// iterate over all inodes
ino_t maxino = (ino_t)sblock->fs_ipg * sblock->fs_ncg;
for (ino_t inum = 0; inum < maxino; inum++) {
// read file system block
if (!ino_to_fsbo(sblock, inum)) {
daddr_t blkno = fsbtodb(sblock, ino_to_fsba(sblock, inum));
xpread(diskfd, inoblock, sblock->fs_bsize, blkno * DEV_BSIZE);
}
// inode 0 is used as placeholder and 1 was used for bad blocks
if (inum < 2) {
continue;
}
union dinode *dp;
if (sblock->fs_magic == FS_UFS1_MAGIC) {
dp = (union dinode *)&(
(struct ufs1_dinode *)inoblock)[ino_to_fsbo(sblock, inum)];
} else {
dp = (union dinode *)&(
(struct ufs2_dinode *)inoblock)[ino_to_fsbo(sblock, inum)];
}
if (mode & MODE_DIRECTORY) {
if ((DIP(dp, di_mode) & IFMT) == IFDIR) {
dump_data(dp, process_directory, NULL);
}
} else {
dump_inode(inum, dp);
}
}
} else {
daddr_t blkno = fsbtodb(sblock, ino_to_fsba(sblock, inode));
xpread(diskfd, inoblock, sblock->fs_bsize, blkno * DEV_BSIZE);
union dinode *dp;
if (sblock->fs_magic == FS_UFS1_MAGIC) {
dp = (union dinode *)&(
(struct ufs1_dinode *)inoblock)[ino_to_fsbo(sblock, inode)];
} else {
dp = (union dinode *)&(
(struct ufs2_dinode *)inoblock)[ino_to_fsbo(sblock, inode)];
}
if (mode & MODE_FILE) {
dump_data(dp, process_file, process_file_gap);
fprintf(stderr, "\r%" PRIu64 "/%" PRIu64 "\n", DIP(dp, di_size),
DIP(dp, di_size));
} else if (mode & MODE_DIRECTORY) {
if ((DIP(dp, di_mode) & IFMT) != IFDIR) {
errx(1, "Not a directory");
}
dump_data(dp, process_directory, NULL);
} else {
dump_inode(inode, dp);
}
}
return 0;
}
void usage(void) {
fprintf(stderr, "Usage: %s (-a|-i inode) [-d|-o outputfile] disk\n",
getprogname());
exit(1);
}
void dump_inode(ino_t inum, union dinode *dp) {
char type;
switch (DIP(dp, di_mode) & IFMT) {
case IFIFO:
type = 'p';
break;
case IFCHR:
type = 'c';
break;
case IFDIR:
type = 'd';
break;
case IFBLK:
type = 'b';
break;
case IFREG:
type = 'f';
break;
case IFLNK:
type = 'l';
break;
case IFSOCK:
type = 's';
break;
case IFWHT:
type = 'w';
break;
default:
type = 'u';
}
printf("inode:%" PRIu32 " type:%c mode:%" PRIo16 " nlink:%" PRId16
" uid:%" PRIu32 " gid:%" PRIu32 " size:%" PRIu64 " atime:%" PRId64
" mtime:%" PRId64 " ctime:%" PRId64 "\n",
(uint32_t)inum, type, DIP(dp, di_mode) & ~IFMT, DIP(dp, di_nlink),
DIP(dp, di_uid), DIP(dp, di_gid), DIP(dp, di_size), DIP(dp, di_atime),
DIP(dp, di_mtime), DIP(dp, di_ctime));
}
void dump_data(union dinode *dp, process_fn process_data,
process_fn process_gap) {
char *datablock = xmalloc(sblock->fs_bsize);
off_t filesize = DIP(dp, di_size);
for (int i = 0; filesize > 0 && i < NDADDR;
filesize -= sblock->fs_bsize, i++) {
if (!DIP(dp, di_db[i])) {
if (process_gap) {
memset(datablock, 0, sblock->fs_bsize);
process_gap(dp, datablock, sblock->fs_bsize,
DIP(dp, di_size) - filesize);
}
continue;
}
size_t blksize = sblksize(sblock, DIP(dp, di_size), i);
daddr_t blkno = fsbtodb(sblock, DIP(dp, di_db[i]));
xpread(diskfd, datablock, blksize, blkno * DEV_BSIZE);
if (filesize < blksize) {
blksize = filesize;
}
process_data(dp, datablock, blksize, DIP(dp, di_size) - filesize);
}
for (int i = 0; filesize > 0 && i < NIADDR; i++) {
if (!DIP(dp, di_ib[i])) {
size_t gapsize = NINDIR(sblock) * sblock->fs_bsize;
for (int j = 0; j < i; j++) {
gapsize *= NINDIR(sblock);
}
if (process_gap) {
memset(datablock, 0, sblock->fs_bsize);
process_gap(dp, datablock, gapsize, DIP(dp, di_size) - filesize);
}
filesize -= gapsize;
continue;
}
daddr_t blkno = fsbtodb(sblock, DIP(dp, di_ib[i]));
xpread(diskfd, datablock, sblock->fs_bsize, blkno * DEV_BSIZE);
dump_indirect(dp, datablock, i, &filesize, process_data, process_gap);
}
free(datablock);
}
void dump_indirect(union dinode *dp, void *indirblock, int level,
off_t *filesize, process_fn process_data,
process_fn process_gap) {
void *datablock = xmalloc(sblock->fs_bsize);
for (int i = 0; *filesize > 0 && i < NINDIR(sblock); i++) {
daddr_t fsblkno;
if (sblock->fs_magic == FS_UFS1_MAGIC) {
fsblkno = ((uint32_t *)indirblock)[i];
} else {
fsblkno = ((uint64_t *)indirblock)[i];
}
if (!fsblkno) {
size_t gapsize = sblock->fs_bsize;
for (int j = 0; j < level; j++) {
gapsize *= NINDIR(sblock);
}
if (process_gap) {
memset(datablock, 0, sblock->fs_bsize);
process_gap(dp, datablock, gapsize, DIP(dp, di_size) - *filesize);
}
*filesize -= gapsize;
continue;
}
size_t blksize = sblock->fs_bsize;
daddr_t blkno = fsbtodb(sblock, fsblkno);
xpread(diskfd, datablock, blksize, blkno * DEV_BSIZE);
if (level) {
dump_indirect(dp, datablock, level - 1, filesize, process_data,
process_gap);
} else {
if (*filesize < blksize) {
blksize = *filesize;
}
process_data(dp, datablock, blksize, DIP(dp, di_size) - *filesize);
*filesize -= sblock->fs_bsize;
}
}
free(datablock);
}
void process_directory(union dinode *dp, void *datablock, size_t blksize,
off_t blkoff) {
for (size_t j = 0; j < blksize; j += DIRBLKSIZ) {
process_dirblk(0, datablock + j, MIN(blksize - j, DIRBLKSIZ));
}
}
void process_dirblk(int deleted, void *dirblk, size_t size) {
char *end = (char *)dirblk + size;
for (struct direct *d = dirblk;
(char *)&d->d_namlen < end && d->d_name + d->d_namlen - 1 < end;
d = (struct direct *)((char *)d + d->d_reclen)) {
char type;
switch (d->d_type) {
case DT_FIFO:
type = 'p';
break;
case DT_CHR:
type = 'c';
break;
case DT_DIR:
type = 'd';
break;
case DT_BLK:
type = 'b';
break;
case DT_REG:
type = 'f';
break;
case DT_LNK:
type = 'l';
break;
case DT_SOCK:
type = 's';
break;
default:
type = 'u';
}
char name[MAXNAMLEN + 1];
memcpy(name, d->d_name, d->d_namlen);
name[d->d_namlen] = '\0';
printf("deleted:%d inode:%" PRIu32 " type:%c name:%s\n",
deleted || !d->d_ino, d->d_ino, type, name);
d->d_reclen = MIN(d->d_reclen, end - (char *)d);
d->d_reclen &= ~3;
if (!d->d_reclen) {
if (!deleted) {
warnx("Corrupted directory entry");
}
return;
}
if (d->d_reclen > DIRSIZ(NEWDIRFMT, d)) {
process_dirblk(1, (char *)d + DIRSIZ(NEWDIRFMT, d),
d->d_reclen - DIRSIZ(NEWDIRFMT, d));
}
}
}
void process_file(union dinode *dp, void *datablock, size_t blksize,
off_t blkoff) {
if (filefd == STDOUT_FILENO) {
xwrite(filefd, datablock, blksize);
} else {
xpwrite(filefd, datablock, blksize, blkoff);
}
if (blkoff / (1024 * 1024) != (blkoff + blksize) / (1024 * 1024)) {
fprintf(stderr, "\r%" PRIu64 "/%" PRIu64, blkoff + blksize,
DIP(dp, di_size));
}
}
void process_file_gap(union dinode *dp, void *datablock, size_t gapsize,
off_t blkoff) {
if (filefd == STDOUT_FILENO) {
for (int j = 0; j < gapsize / sblock->fs_bsize; j++) {
xwrite(filefd, datablock, sblock->fs_bsize);
}
} else {
if (ftruncate(filefd, blkoff + gapsize) == -1) {
err(1, "ftruncate() failed");
}
}
if (blkoff / (1024 * 1024) != (blkoff + gapsize) / (1024 * 1024)) {
fprintf(stderr, "\r%" PRIu64 "/%" PRIu64, blkoff + gapsize,
DIP(dp, di_size));
}
}
void *xmalloc(size_t size) {
void *p = malloc(size);
if (!p) {
err(1, "malloc() failed");
}
return p;
}
void xpread(int fd, void *buf, size_t nbytes, off_t offset) {
while (nbytes) {
ssize_t n = pread(fd, buf, nbytes, offset);
if (n == -1) {
if (errno == EINTR) {
continue;
}
err(1, "pread() failed");
}
buf = (char *)buf + n;
nbytes -= n;
offset += n;
}
}
void xwrite(int fd, void *buf, size_t nbytes) {
while (nbytes) {
ssize_t n = write(fd, buf, nbytes);
if (n == -1) {
if (errno == EINTR || errno == EAGAIN) {
continue;
}
err(1, "write() failed");
}
buf = (char *)buf + n;
nbytes -= n;
}
}
void xpwrite(int fd, void *buf, size_t nbytes, off_t offset) {
while (nbytes) {
ssize_t n = pwrite(fd, buf, nbytes, offset);
if (n == -1) {
if (errno == EINTR) {
continue;
}
err(1, "pwrite() failed");
}
buf = (char *)buf + n;
nbytes -= n;
offset += n;
}
}