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