commit 36a07e26e71ad4160a2d1b94c3a2dae4b35eacaf Author: Thomas Lindner Date: Sun Feb 12 16:44:08 2023 +0100 data recovery tool diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0278039 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dump_inode diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..83d6af2 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +LDLIBS := -lutil + +dump_inode: diff --git a/dump_inode.c b/dump_inode.c new file mode 100644 index 0000000..ff49ebc --- /dev/null +++ b/dump_inode.c @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2023 Thomas Lindner + * + * 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 + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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"); + } +}