data recovery tool

This commit is contained in:
Thomas Lindner 2023-02-12 16:44:08 +01:00
commit 36a07e26e7
3 changed files with 405 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
dump_inode

3
Makefile Normal file
View file

@ -0,0 +1,3 @@
LDLIBS := -lutil
dump_inode:

401
dump_inode.c Normal file
View file

@ -0,0 +1,401 @@
/*
* 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/disklabel.h>
#include <sys/dkio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <ufs/ffs/fs.h>
#include <ufs/ufs/dinode.h>
#include <ufs/ufs/dir.h>
#include <err.h>
#include <fcntl.h>
#include <limits.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)
void usage(void);
void dump_inode(ino_t inum, union dinode *dp);
void dump_directory(union dinode *dp);
void dump_file(union dinode *dp);
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, UINT_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 || 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);
int sblock_try[] = SBLOCKSEARCH;
int i;
for (i = 0; sblock_try[i] != -1; i++) {
ssize_t n = pread(diskfd, sblock, SBLOCKSIZE, (off_t)sblock_try[i]);
if (n == SBLOCKSIZE &&
(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) {
dump_directory(dp);
} 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_file(dp);
} else if (mode & MODE_DIRECTORY) {
if ((DIP(dp, di_mode) & IFMT) != IFDIR) {
errx(1, "Not a directory");
}
dump_directory(dp);
} 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:%llu type:%c mode:%o nlink:%u uid:%u gid:%u size:%llu "
"atime:%llu mtime:%llu ctime:%llu \n",
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_dirblk(void *dirblk, size_t size) {
struct direct *d = dirblk;
errx(1, "Not implemented");
}
void dump_directory(union dinode *dp) {
if ((DIP(dp, di_mode) & IFMT) != IFDIR) {
return;
}
void *datablock = xmalloc(sblock->fs_bsize);
off_t filesize = DIP(dp, di_size);
for (int i = 0; filesize > 0 && i < NDADDR; i++) {
filesize -= sblock->fs_bsize;
if (!DIP(dp, di_db[i])) {
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);
dump_dirblk(datablock, blksize);
}
if (filesize > 0) {
errx(1, "Indirect blocks not implemented");
}
free(datablock);
}
void dump_file_indirect(union dinode *dp, void *indirblock, int level,
off_t *filesize) {
void *datablock = xmalloc(sblock->fs_bsize);
if (!level) {
for (int i = 0; *filesize > 0 && i < NINDIR(sblock);
*filesize -= sblock->fs_bsize, i++) {
daddr_t fsblkno;
if (sblock->fs_magic == FS_UFS1_MAGIC) {
fsblkno = ((uint32_t *)indirblock)[i];
} else {
fsblkno = ((uint64_t *)indirblock)[i];
}
if (!fsblkno) {
if (filefd == STDOUT_FILENO) {
memset(datablock, 0, sblock->fs_bsize);
xwrite(filefd, datablock, sblock->fs_bsize);
}
continue;
}
size_t blksize = sblock->fs_bsize;
daddr_t blkno = fsbtodb(sblock, fsblkno);
xpread(diskfd, datablock, blksize, blkno * DEV_BSIZE);
if (*filesize < blksize) {
blksize = *filesize;
}
if (filefd == STDOUT_FILENO) {
xwrite(filefd, datablock, blksize);
} else {
xpwrite(filefd, datablock, blksize, DIP(dp, di_size) - *filesize);
}
}
} else {
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 = NINDIR(sblock) * sblock->fs_bsize;
for (int j = 0; j < i; j++) {
gapsize *= NINDIR(sblock);
}
*filesize -= gapsize;
continue;
}
daddr_t blkno = fsbtodb(sblock, fsblkno);
xpread(diskfd, datablock, sblock->fs_bsize, blkno * DEV_BSIZE);
dump_file_indirect(dp, datablock, level - 1, filesize);
}
}
free(datablock);
fprintf(stderr, "\r%llu/%llu", DIP(dp, di_size) - *filesize,
DIP(dp, di_size));
}
void dump_file(union dinode *dp) {
off_t filesize = DIP(dp, di_size);
if (filefd != STDOUT_FILENO && ftruncate(filefd, filesize) == -1) {
err(1, "ftruncate() failed");
}
void *datablock = xmalloc(sblock->fs_bsize);
int i;
for (i = 0; filesize > 0 && i < NDADDR; filesize -= sblock->fs_bsize, i++) {
if (!DIP(dp, di_db[i])) {
if (filefd == STDOUT_FILENO) {
memset(datablock, 0, sblock->fs_bsize);
xwrite(filefd, datablock, sblksize(sblock, DIP(dp, di_size), i));
}
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;
}
if (filefd == STDOUT_FILENO) {
xwrite(filefd, datablock, blksize);
} else {
xpwrite(filefd, datablock, blksize, DIP(dp, di_size) - filesize);
}
}
for (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);
}
filesize -= gapsize;
continue;
}
daddr_t blkno = fsbtodb(sblock, DIP(dp, di_ib[i]));
xpread(diskfd, datablock, sblock->fs_bsize, blkno * DEV_BSIZE);
dump_file_indirect(dp, datablock, i, &filesize);
}
if (i) {
fputc('\n', stderr);
}
free(datablock);
}
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) {
ssize_t n = pread(fd, buf, nbytes, offset);
if (n == -1) {
err(1, "pread() failed");
}
if (n != nbytes) {
errx(1, "Incomplete read");
}
}
void xwrite(int fd, void *buf, size_t nbytes) {
ssize_t n = write(fd, buf, nbytes);
if (n == -1) {
err(1, "write() failed");
}
if (n != nbytes) {
errx(1, "Incomplete write");
}
}
void xpwrite(int fd, void *buf, size_t nbytes, off_t offset) {
ssize_t n = pwrite(fd, buf, nbytes, offset);
if (n == -1) {
err(1, "pwrite() failed");
}
if (n != nbytes) {
errx(1, "Incomplete write");
}
}