| /* |
| * dump.c --- dump the contents of an inode out to a file |
| * |
| * Copyright (C) 1994 Theodore Ts'o. This file may be redistributed |
| * under the terms of the GNU Public License. |
| */ |
| |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE /* for O_LARGEFILE */ |
| #endif |
| |
| #include "config.h" |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <string.h> |
| #include <time.h> |
| #ifdef HAVE_ERRNO_H |
| #include <errno.h> |
| #endif |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <utime.h> |
| #ifdef HAVE_GETOPT_H |
| #include <getopt.h> |
| #else |
| extern int optind; |
| extern char *optarg; |
| #endif |
| |
| #include "debugfs.h" |
| |
| #ifndef O_LARGEFILE |
| #define O_LARGEFILE 0 |
| #endif |
| |
| /* |
| * The mode_xlate function translates a linux mode into a native-OS mode_t. |
| */ |
| static struct { |
| __u16 lmask; |
| mode_t mask; |
| } mode_table[] = { |
| { LINUX_S_IRUSR, S_IRUSR }, |
| { LINUX_S_IWUSR, S_IWUSR }, |
| { LINUX_S_IXUSR, S_IXUSR }, |
| { LINUX_S_IRGRP, S_IRGRP }, |
| { LINUX_S_IWGRP, S_IWGRP }, |
| { LINUX_S_IXGRP, S_IXGRP }, |
| { LINUX_S_IROTH, S_IROTH }, |
| { LINUX_S_IWOTH, S_IWOTH }, |
| { LINUX_S_IXOTH, S_IXOTH }, |
| { 0, 0 } |
| }; |
| |
| static mode_t mode_xlate(__u16 lmode) |
| { |
| mode_t mode = 0; |
| int i; |
| |
| for (i=0; mode_table[i].lmask; i++) { |
| if (lmode & mode_table[i].lmask) |
| mode |= mode_table[i].mask; |
| } |
| return mode; |
| } |
| |
| static void fix_perms(const char *cmd, const struct ext2_inode *inode, |
| int fd, const char *name) |
| { |
| struct utimbuf ut; |
| int i; |
| |
| if (fd != -1) |
| i = fchmod(fd, mode_xlate(inode->i_mode)); |
| else |
| i = chmod(name, mode_xlate(inode->i_mode)); |
| if (i == -1) |
| com_err(cmd, errno, "while setting permissions of %s", name); |
| |
| #ifndef HAVE_FCHOWN |
| i = chown(name, inode->i_uid, inode->i_gid); |
| #else |
| if (fd != -1) |
| i = fchown(fd, inode->i_uid, inode->i_gid); |
| else |
| i = chown(name, inode->i_uid, inode->i_gid); |
| #endif |
| if (i == -1) |
| com_err(cmd, errno, "while changing ownership of %s", name); |
| |
| ut.actime = inode->i_atime; |
| ut.modtime = inode->i_mtime; |
| if (utime(name, &ut) == -1) |
| com_err(cmd, errno, "while setting times of %s", name); |
| } |
| |
| static void dump_file(const char *cmdname, ext2_ino_t ino, int fd, |
| int preserve, char *outname) |
| { |
| errcode_t retval; |
| struct ext2_inode inode; |
| char *buf = 0; |
| ext2_file_t e2_file; |
| int nbytes; |
| unsigned int got, blocksize = current_fs->blocksize; |
| |
| if (debugfs_read_inode(ino, &inode, cmdname)) |
| return; |
| |
| retval = ext2fs_file_open(current_fs, ino, 0, &e2_file); |
| if (retval) { |
| com_err(cmdname, retval, "while opening ext2 file"); |
| return; |
| } |
| retval = ext2fs_get_mem(blocksize, &buf); |
| if (retval) { |
| com_err(cmdname, retval, "while allocating memory"); |
| return; |
| } |
| while (1) { |
| retval = ext2fs_file_read(e2_file, buf, blocksize, &got); |
| if (retval) |
| com_err(cmdname, retval, "while reading ext2 file"); |
| if (got == 0) |
| break; |
| nbytes = write(fd, buf, got); |
| if ((unsigned) nbytes != got) |
| com_err(cmdname, errno, "while writing file"); |
| } |
| if (buf) |
| ext2fs_free_mem(&buf); |
| retval = ext2fs_file_close(e2_file); |
| if (retval) { |
| com_err(cmdname, retval, "while closing ext2 file"); |
| return; |
| } |
| |
| if (preserve) |
| fix_perms("dump_file", &inode, fd, outname); |
| |
| return; |
| } |
| |
| void do_dump(int argc, char **argv) |
| { |
| ext2_ino_t inode; |
| int fd; |
| int c; |
| int preserve = 0; |
| char *in_fn, *out_fn; |
| |
| reset_getopt(); |
| while ((c = getopt (argc, argv, "p")) != EOF) { |
| switch (c) { |
| case 'p': |
| preserve++; |
| break; |
| default: |
| print_usage: |
| com_err(argv[0], 0, "Usage: dump_inode [-p] " |
| "<file> <output_file>"); |
| return; |
| } |
| } |
| if (optind != argc-2) |
| goto print_usage; |
| |
| if (check_fs_open(argv[0])) |
| return; |
| |
| in_fn = argv[optind]; |
| out_fn = argv[optind+1]; |
| |
| inode = string_to_inode(in_fn); |
| if (!inode) |
| return; |
| |
| fd = open(out_fn, O_CREAT | O_WRONLY | O_TRUNC | O_LARGEFILE, 0666); |
| if (fd < 0) { |
| com_err(argv[0], errno, "while opening %s for dump_inode", |
| out_fn); |
| return; |
| } |
| |
| dump_file(argv[0], inode, fd, preserve, out_fn); |
| if (close(fd) != 0) { |
| com_err(argv[0], errno, "while closing %s for dump_inode", |
| out_fn); |
| return; |
| } |
| |
| return; |
| } |
| |
| static void rdump_symlink(ext2_ino_t ino, struct ext2_inode *inode, |
| const char *fullname) |
| { |
| ext2_file_t e2_file; |
| char *buf; |
| errcode_t retval; |
| |
| buf = malloc(inode->i_size + 1); |
| if (!buf) { |
| com_err("rdump", errno, "while allocating for symlink"); |
| goto errout; |
| } |
| |
| /* Apparently, this is the right way to detect and handle fast |
| * symlinks; see do_stat() in debugfs.c. */ |
| if (ext2fs_inode_data_blocks2(current_fs, inode) == 0) |
| strcpy(buf, (char *) inode->i_block); |
| else { |
| unsigned bytes = inode->i_size; |
| char *p = buf; |
| retval = ext2fs_file_open(current_fs, ino, 0, &e2_file); |
| if (retval) { |
| com_err("rdump", retval, "while opening symlink"); |
| goto errout; |
| } |
| for (;;) { |
| unsigned int got; |
| retval = ext2fs_file_read(e2_file, p, bytes, &got); |
| if (retval) { |
| com_err("rdump", retval, "while reading symlink"); |
| goto errout; |
| } |
| bytes -= got; |
| p += got; |
| if (got == 0 || bytes == 0) |
| break; |
| } |
| buf[inode->i_size] = 0; |
| retval = ext2fs_file_close(e2_file); |
| if (retval) |
| com_err("rdump", retval, "while closing symlink"); |
| } |
| |
| if (symlink(buf, fullname) == -1) { |
| com_err("rdump", errno, "while creating symlink %s -> %s", buf, fullname); |
| goto errout; |
| } |
| |
| errout: |
| free(buf); |
| } |
| |
| static int rdump_dirent(struct ext2_dir_entry *, int, int, char *, void *); |
| |
| static void rdump_inode(ext2_ino_t ino, struct ext2_inode *inode, |
| const char *name, const char *dumproot) |
| { |
| char *fullname; |
| |
| /* There are more efficient ways to do this, but this method |
| * requires only minimal debugging. */ |
| fullname = malloc(strlen(dumproot) + strlen(name) + 2); |
| if (!fullname) { |
| com_err("rdump", errno, "while allocating memory"); |
| return; |
| } |
| sprintf(fullname, "%s/%s", dumproot, name); |
| |
| if (LINUX_S_ISLNK(inode->i_mode)) |
| rdump_symlink(ino, inode, fullname); |
| else if (LINUX_S_ISREG(inode->i_mode)) { |
| int fd; |
| fd = open(fullname, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, S_IRWXU); |
| if (fd == -1) { |
| com_err("rdump", errno, "while opening %s", fullname); |
| goto errout; |
| } |
| dump_file("rdump", ino, fd, 1, fullname); |
| if (close(fd) != 0) { |
| com_err("rdump", errno, "while closing %s", fullname); |
| goto errout; |
| } |
| } |
| else if (LINUX_S_ISDIR(inode->i_mode) && strcmp(name, ".") && strcmp(name, "..")) { |
| errcode_t retval; |
| |
| /* Create the directory with 0700 permissions, because we |
| * expect to have to create entries it. Then fix its perms |
| * once we've done the traversal. */ |
| if (name[0] && mkdir(fullname, S_IRWXU) == -1) { |
| com_err("rdump", errno, "while making directory %s", fullname); |
| goto errout; |
| } |
| |
| retval = ext2fs_dir_iterate(current_fs, ino, 0, 0, |
| rdump_dirent, (void *) fullname); |
| if (retval) |
| com_err("rdump", retval, "while dumping %s", fullname); |
| |
| fix_perms("rdump", inode, -1, fullname); |
| } |
| /* else do nothing (don't dump device files, sockets, fifos, etc.) */ |
| |
| errout: |
| free(fullname); |
| } |
| |
| static int rdump_dirent(struct ext2_dir_entry *dirent, |
| int offset EXT2FS_ATTR((unused)), |
| int blocksize EXT2FS_ATTR((unused)), |
| char *buf EXT2FS_ATTR((unused)), void *private) |
| { |
| char name[EXT2_NAME_LEN + 1]; |
| int thislen; |
| const char *dumproot = private; |
| struct ext2_inode inode; |
| |
| thislen = ext2fs_dirent_name_len(dirent); |
| strncpy(name, dirent->name, thislen); |
| name[thislen] = 0; |
| |
| if (debugfs_read_inode(dirent->inode, &inode, name)) |
| return 0; |
| |
| rdump_inode(dirent->inode, &inode, name, dumproot); |
| |
| return 0; |
| } |
| |
| void do_rdump(int argc, char **argv) |
| { |
| struct stat st; |
| char *dest_dir; |
| int i; |
| |
| if (common_args_process(argc, argv, 3, INT_MAX, "rdump", |
| "<directory>... <native directory>", 0)) |
| return; |
| |
| /* Pull out last argument */ |
| dest_dir = argv[argc - 1]; |
| argc--; |
| |
| /* Ensure last arg is a directory. */ |
| if (stat(dest_dir, &st) == -1) { |
| com_err("rdump", errno, "while statting %s", dest_dir); |
| return; |
| } |
| if (!S_ISDIR(st.st_mode)) { |
| com_err("rdump", 0, "%s is not a directory", dest_dir); |
| return; |
| } |
| |
| for (i = 1; i < argc; i++) { |
| char *arg = argv[i], *basename; |
| struct ext2_inode inode; |
| ext2_ino_t ino = string_to_inode(arg); |
| if (!ino) |
| continue; |
| |
| if (debugfs_read_inode(ino, &inode, arg)) |
| continue; |
| |
| basename = strrchr(arg, '/'); |
| if (basename) |
| basename++; |
| else |
| basename = arg; |
| |
| rdump_inode(ino, &inode, basename, dest_dir); |
| } |
| } |
| |
| void do_cat(int argc, char **argv) |
| { |
| ext2_ino_t inode; |
| |
| if (common_inode_args_process(argc, argv, &inode, 0)) |
| return; |
| |
| fflush(stdout); |
| fflush(stderr); |
| dump_file(argv[0], inode, 1, 0, argv[2]); |
| |
| return; |
| } |
| |