data recovery tool
This commit is contained in:
commit
36a07e26e7
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
dump_inode
|
401
dump_inode.c
Normal file
401
dump_inode.c
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue