/* * Utility for dealing with 2.xBSD filesystem images. * * Copyright (C) 2006-2014 Serge Vakulenko, * * Permission to use, copy, modify, and distribute this software * and its documentation for any purpose and without fee is hereby * granted, provided that the above copyright notice appear in all * copies and that both that the copyright notice and this * permission notice and warranty disclaimer appear in supporting * documentation, and that the name of the author not be used in * advertising or publicity pertaining to distribution of the * software without specific, written prior permission. * * The author disclaim 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, 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 "bsdfs.h" #include "manifest.h" int verbose; int extract; int add; int newfs; int check; int fix; int mount; int scan; int repartition; unsigned kbytes; unsigned swap_kbytes; unsigned pindex; static const char *program_version = "BSD 2.x file system utility, version 1.2\n" "Copyright (C) 2011-2014 Serge Vakulenko"; static const char *program_bug_address = ""; static struct option program_options[] = { { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 'V' }, { "verbose", no_argument, 0, 'v' }, { "add", no_argument, 0, 'a' }, { "extract", no_argument, 0, 'x' }, { "check", no_argument, 0, 'c' }, { "fix", no_argument, 0, 'f' }, { "mount", no_argument, 0, 'm' }, { "scan", no_argument, 0, 'S' }, { "new", no_argument, 0, 'n' }, { "size", required_argument, 0, 's' }, { "manifest", required_argument, 0, 'M' }, { "partition", required_argument, 0, 'p' }, { "repartition", required_argument, 0, 'r' }, { 0 } }; static void print_help (char *progname) { char *p = strrchr (progname, '/'); if (p) progname = p+1; printf ("%s\n", program_version); printf ("This program is free software; it comes with ABSOLUTELY NO WARRANTY;\n" "see the BSD 3-Clause License for more details.\n"); printf ("\n"); printf ("Usage:\n"); printf (" %s [--verbose] [--partition=n] disk.img\n", progname); printf (" %s --check [--fix] [--partition=n] disk.img\n", progname); printf (" %s --new [--size=kbytes | --partition=n] [--manifest=file] disk.img [dir]\n", progname); #ifdef ENABLE_FUSE printf (" %s --mount [--partition=n] disk.img dir\n", progname); #endif printf (" %s --add [--partition=n] disk.img files...\n", progname); printf (" %s --extract [--partition=n] disk.img\n", progname); printf (" %s --repartition=format disk.img\n", progname); printf (" %s --scan dir > file\n", progname); printf ("\n"); printf ("Options:\n"); printf (" -c, --check Check filesystem, use -c -f to fix.\n"); printf (" -f, --fix Fix bugs in filesystem.\n"); printf (" -n, --new Create new filesystem; add files from\n"); printf (" specified directory and manifest (optional)\n"); printf (" -s NUM, --size=NUM Size of filesystem in kbytes.\n"); printf (" -M file, --manifest=file\n"); printf (" List of files and attributes to create.\n"); #ifdef ENABLE_FUSE printf (" -m, --mount Mount the filesystem.\n"); #endif printf (" -a, --add Add files to filesystem.\n"); printf (" -x, --extract Extract all files.\n"); printf (" -r format, --repartition=format\n"); printf (" Install new partition table.\n"); printf (" -p NUM, --partition=NUM\n"); printf (" Select a partition.\n"); printf (" -S, --scan Create a manifest from directory contents.\n"); printf (" -v, --verbose Be verbose.\n"); printf (" -V, --version Print version information and then exit.\n"); printf (" -h, --help Print this message.\n"); printf ("\n"); printf ("Report bugs to \"%s\".\n", program_bug_address); } void print_inode (fs_inode_t *inode, char *dirname, char *filename, FILE *out) { fprintf (out, "%s/%s", dirname, filename); switch (inode->mode & INODE_MODE_FMT) { case INODE_MODE_FDIR: if (filename[0] != 0) fprintf (out, "/"); break; case INODE_MODE_FCHR: fprintf (out, " - char %d %d", inode->addr[1] >> 8, inode->addr[1] & 0xff); break; case INODE_MODE_FBLK: fprintf (out, " - block %d %d", inode->addr[1] >> 8, inode->addr[1] & 0xff); break; default: fprintf (out, " - %lu bytes", inode->size); break; } fprintf (out, "\n"); } void print_indirect_block (fs_t *fs, unsigned int bno, FILE *out) { unsigned short nb; unsigned char data [BSDFS_BSIZE]; int i; fprintf (out, " [%d]", bno); if (! fs_read_block (fs, bno, data)) { fprintf (stderr, "read error at block %d\n", bno); return; } for (i=0; imode & INODE_MODE_FMT) == INODE_MODE_FCHR || (inode->mode & INODE_MODE_FMT) == INODE_MODE_FBLK) return; fprintf (out, " "); for (i=0; iaddr[i] == 0) continue; fprintf (out, " %d", inode->addr[i]); } if (inode->addr[NDADDR] != 0) print_indirect_block (inode->fs, inode->addr[NDADDR], out); if (inode->addr[NDADDR+1] != 0) print_double_indirect_block (inode->fs, inode->addr[NDADDR+1], out); if (inode->addr[NDADDR+2] != 0) print_triple_indirect_block (inode->fs, inode->addr[NDADDR+2], out); fprintf (out, "\n"); } void extract_inode (fs_inode_t *inode, char *path) { int fd, n, mode; unsigned long offset; unsigned char data [BSDFS_BSIZE]; /* Allow read/write for user. */ mode = (inode->mode & 0777) | 0600; fd = open (path, O_CREAT | O_RDWR, mode); if (fd < 0) { perror (path); return; } for (offset = 0; offset < inode->size; offset += BSDFS_BSIZE) { n = inode->size - offset; if (n > BSDFS_BSIZE) n = BSDFS_BSIZE; if (! fs_inode_read (inode, offset, data, n)) { fprintf (stderr, "%s: read error at offset %ld\n", path, offset); break; } if (write (fd, data, n) != n) { fprintf (stderr, "%s: write error\n", path); break; } } close (fd); } void extract_symlink (fs_inode_t *inode, char *path) { char data [BSDFS_BSIZE]; if (inode->size >= BSDFS_BSIZE) { fprintf (stderr, "%s: too long symlink\n", path); return; } if (! fs_inode_read (inode, 0, (unsigned char*)data, inode->size)) { fprintf (stderr, "%s: error reading symlink\n", path); return; } data[inode->size] = 0; if (symlink(data, path) < 0) { perror (path); return; } } void extractor (fs_inode_t *dir, fs_inode_t *inode, char *dirname, char *filename, void *arg) { FILE *out = arg; char *path, *relpath; if (verbose) print_inode (inode, dirname, filename, out); if ((inode->mode & INODE_MODE_FMT) != INODE_MODE_FDIR && (inode->mode & INODE_MODE_FMT) != INODE_MODE_FREG && (inode->mode & INODE_MODE_FMT) != INODE_MODE_FLNK) return; path = alloca (strlen (dirname) + strlen (filename) + 2); strcpy (path, dirname); strcat (path, "/"); strcat (path, filename); for (relpath=path; *relpath == '/'; relpath++) continue; switch (inode->mode & INODE_MODE_FMT) { case INODE_MODE_FDIR: if (mkdir (relpath, 0775) < 0 && errno != EEXIST) perror (relpath); /* Scan subdirectory. */ fs_directory_scan (inode, path, extractor, arg); break; case INODE_MODE_FREG: extract_inode (inode, relpath); break; case INODE_MODE_FLNK: extract_symlink (inode, relpath); break; } } void scanner (fs_inode_t *dir, fs_inode_t *inode, char *dirname, char *filename, void *arg) { FILE *out = arg; char *path; print_inode (inode, dirname, filename, out); if (verbose > 1) { /* Print a list of blocks. */ print_inode_blocks (inode, out); if (verbose > 2) { fs_inode_print (inode, out); printf ("--------\n"); } } if ((inode->mode & INODE_MODE_FMT) == INODE_MODE_FDIR && inode->number != BSDFS_ROOT_INODE) { /* Scan subdirectory. */ path = alloca (strlen (dirname) + strlen (filename) + 2); strcpy (path, dirname); strcat (path, "/"); strcat (path, filename); fs_directory_scan (inode, path, scanner, arg); } } /* * Create a directory. */ void add_directory (fs_t *fs, char *name, int mode, int owner, int group) { fs_inode_t dir, parent; char buf [BSDFS_BSIZE], *p; /* Open parent directory. */ strcpy (buf, name); p = strrchr (buf, '/'); if (p) *p = 0; else *buf = 0; if (! fs_inode_lookup (fs, &parent, buf)) { fprintf (stderr, "%s: cannot open directory\n", buf); return; } /* Create directory. */ mode &= 07777; mode |= INODE_MODE_FDIR; int done = fs_inode_create (fs, &dir, name, mode); if (! done) { fprintf (stderr, "%s: directory inode create failed\n", name); return; } if (done == 1) { /* The directory already existed. */ return; } dir.uid = owner; dir.gid = group; fs_inode_save (&dir, 1); /* Make parent link '..' */ strcpy (buf, name); strcat (buf, "/.."); if (! fs_inode_link (fs, &dir, buf, parent.number)) { fprintf (stderr, "%s: dotdot link failed\n", name); return; } if (! fs_inode_get (fs, &parent, parent.number)) { fprintf (stderr, "inode %d: cannot open parent\n", parent.number); return; } ++parent.nlink; fs_inode_save (&parent, 1); /*printf ("*** inode %d: increment link counter to %d\n", parent.number, parent.nlink);*/ } /* * Create a device node. */ void add_device (fs_t *fs, char *name, int mode, int owner, int group, int type, int majr, int minr) { fs_inode_t dev; mode &= 07777; mode |= (type == 'b') ? INODE_MODE_FBLK : INODE_MODE_FCHR; if (! fs_inode_create (fs, &dev, name, mode)) { fprintf (stderr, "%s: device inode create failed\n", name); return; } dev.addr[1] = majr << 8 | minr; dev.uid = owner; dev.gid = group; time (&dev.mtime); fs_inode_save (&dev, 1); } /* * Copy regular file to filesystem. */ void add_file (fs_t *fs, const char *path, const char *dirname, int mode, int owner, int group) { fs_file_t file; FILE *fd; char accpath [BSDFS_BSIZE]; unsigned char data [BSDFS_BSIZE]; struct stat st; int len; if (dirname && *dirname) { /* Concatenate directory name and file name. */ strcpy (accpath, dirname); len = strlen (accpath); if (accpath[len-1] != '/' && path[0] != '/') strcat (accpath, "/"); strcat (accpath, path); } else { /* Use filename relative to current directory. */ strcpy (accpath, path); } fd = fopen (accpath, "r"); if (! fd) { perror (accpath); return; } fstat (fileno(fd), &st); if (mode == -1) mode = st.st_mode; mode &= 07777; mode |= INODE_MODE_FREG; if (! fs_file_create (fs, &file, path, mode)) { fprintf (stderr, "%s: cannot create\n", path); return; } for (;;) { len = fread (data, 1, sizeof (data), fd); /* printf ("read %d bytes from %s\n", len, accpath);*/ if (len < 0) perror (accpath); if (len <= 0) break; if (! fs_file_write (&file, data, len)) { fprintf (stderr, "%s: write error\n", path); break; } } file.inode.uid = owner; file.inode.gid = group; file.inode.mtime = st.st_mtime; file.inode.dirty = 1; fs_file_close (&file); fclose (fd); } /* * Create a symlink. */ void add_symlink (fs_t *fs, const char *path, const char *link, int mode, int owner, int group) { fs_file_t file; int len; mode &= 07777; mode |= INODE_MODE_FLNK; if (! fs_file_create (fs, &file, path, mode)) { fprintf (stderr, "%s: cannot create\n", path); return; } len = strlen (link); if (! fs_file_write (&file, (unsigned char*) link, len)) { fprintf (stderr, "%s: write error\n", path); return; } file.inode.uid = owner; file.inode.gid = group; time (&file.inode.mtime); file.inode.dirty = 1; fs_file_close (&file); } /* * Create a hard link. */ void add_hardlink (fs_t *fs, const char *path, const char *link) { fs_inode_t source, target; /* Find source. */ if (! fs_inode_lookup (fs, &source, link)) { fprintf (stderr, "%s: link source not found\n", link); return; } if ((source.mode & INODE_MODE_FMT) == INODE_MODE_FDIR) { fprintf (stderr, "%s: cannot link directories\n", link); return; } /* Create target link. */ if (! fs_inode_link (fs, &target, path, source.number)) { fprintf (stderr, "%s: link failed\n", path); return; } source.nlink++; fs_inode_save (&source, 1); } /* * Create a file/device/directory in the filesystem. * When name is ended by slash as "name/", directory is created. */ void add_object (fs_t *fs, char *name) { int majr, minr; char type; char *p; if (verbose) { printf ("%s\n", name); } p = strrchr (name, '/'); if (p && p[1] == 0) { *p = 0; add_directory (fs, name, 0777, 0, 0); return; } p = strrchr (name, '!'); if (p) { *p++ = 0; if (sscanf (p, "%c%d:%d", &type, &majr, &minr) != 3 || (type != 'c' && type != 'b') || majr < 0 || majr > 255 || minr < 0 || minr > 255) { fprintf (stderr, "%s: invalid device specification\n", p); fprintf (stderr, "expected c: or b:\n"); return; } add_device (fs, name, 0666, 0, 0, type, majr, minr); return; } add_file (fs, name, 0, -1, 0, 0); } /* * Add the contents from the specified directory. * Use the optional manifest file. */ void add_contents (fs_t *fs, const char *dirname, const char *manifest) { manifest_t m; void *cursor; char *path, *link; int filetype, mode, owner, group, majr, minr; int ndirs = 0, nfiles = 0, nlinks = 0, nsymlinks = 0, ndevs = 0; if (manifest) { /* Load manifest from file. */ if (! manifest_load (&m, manifest)) { fprintf (stderr, "%s: cannot read\n", manifest); return; } } else { /* Create manifest from directory contents. */ if (! manifest_scan (&m, dirname)) { fprintf (stderr, "%s: cannot read\n", dirname); return; } } /* For every file in the manifest, * add it to the target filesystem. */ cursor = 0; while ((filetype = manifest_iterate (&m, &cursor, &path, &link, &mode, &owner, &group, &majr, &minr)) != 0) { switch (filetype) { case 'd': add_directory (fs, path, mode, owner, group); ndirs++; break; case 'f': add_file (fs, path, dirname, mode, owner, group); nfiles++; break; case 'l': add_hardlink (fs, path, link); nlinks++; break; case 's': add_symlink (fs, path, link, mode, owner, group); nsymlinks++; break; case 'b': add_device (fs, path, mode, owner, group, 'b', majr, minr); ndevs++; break; case 'c': add_device (fs, path, mode, owner, group, 'c', majr, minr); ndevs++; break; } } fs_sync (fs, 0); fs_close (fs); printf ("Installed %u directories, %u files, %u devices, %u links, %u symlinks\n", ndirs, nfiles, ndevs, nlinks, nsymlinks); } void create_partition_table (const char *filename, char *format) { unsigned char buf [512], *entry; unsigned pindex, offset; int fd, activated = 0; /* Initialize an empty partition table. */ memset (buf, 0, sizeof(buf)); buf[510] = 0x55; buf[511] = 0xaa; /* Parse format string and fill partition entries. */ offset = 2; char *p = strtok (format, ":"); for (pindex=1; p && pindex<=4; pindex++) { char *q, *endptr; unsigned type, len; /* Split into type and length. */ q = strchr (p, '='); if (! q) { fprintf (stderr, "%s: wrong format '%s' for partition %u\n", filename, p, pindex); exit(-1); } *q++ = 0; /* Get length in sectors. */ len = strtoul (q, &endptr, 0); if (*endptr == 'k') len *= 2; else if (*endptr == 'm' || *endptr == 'M') len *= 2*1024; else if (*endptr != '\0') { fprintf (stderr, "%s: wrong length '%s' for partition %u\n", filename, q, pindex); exit(-1); } /* Get type of partition. */ if (strcmp (p, "fs") == 0) type = 0xb7; else if (strcmp (p, "swap") == 0) type = 0xb8; else { type = strtoul (p, &endptr, 16); if (*endptr != '\0') { fprintf (stderr, "%s: wrong type '%s' for partition %u\n", filename, p, pindex); exit(-1); } } printf ("Allocated partition %u, type %02x, start %u, size %u sectors\n", pindex, type, offset, len); /* Fill partition entry. */ entry = &buf [446 + (pindex-1)*16]; entry [4] = type; *(unsigned*) &entry [8] = offset; *(unsigned*) &entry [12] = len; /* Make first FS active. */ if (type == 0xb7 && ! activated) { entry [0] = 0x80; activated = 1; } p = strtok (NULL, ":"); offset += len; } /* Open or create the file. */ fd = open (filename, O_CREAT | O_RDWR, 0666); if (fd < 0) { fprintf (stderr, "%s: cannot create file\n", filename); exit(-1); } /* Write the partition table to file. */ if (write (fd, buf, 512) != 512) { fprintf (stderr, "%s: error writing partition table\n", filename); exit(-1); } } int main (int argc, char **argv) { int i, key; fs_t fs; fs_inode_t inode; manifest_t m; const char *manifest = 0; char *partition_format = 0; for (;;) { key = getopt_long (argc, argv, "vaxmSncfs:M:p:r:", program_options, 0); if (key == -1) break; switch (key) { case 'v': ++verbose; break; case 'a': ++add; break; case 'x': ++extract; break; case 'n': ++newfs; break; case 'c': ++check; break; case 'f': ++fix; break; case 'm': ++mount; break; case 'S': ++scan; break; case 's': kbytes = strtol (optarg, 0, 0); break; case 'M': manifest = optarg; break; case 'p': pindex = strtol (optarg, 0, 0); if (pindex < 1 || pindex > 4) { fprintf (stderr, "Incorrect partition index %u\n", pindex); return -1; } break; case 'r': ++repartition; partition_format = optarg; break; case 'V': printf ("%s\n", program_version); return 0; case 'h': print_help (argv[0]); return 0; default: print_help (argv[0]); return -1; } } i = optind; if (extract + newfs + check + add + mount + scan + repartition > 1) { print_help (argv[0]); return -1; } if (newfs) { /* Create new filesystem. */ if (i != argc-1 && i != argc-2) { print_help (argv[0]); return -1; } if (! pindex && kbytes < BSDFS_BSIZE * 10 / 1024) { /* Need at least 10 blocks. */ if (! kbytes) fprintf (stderr, "%s: size not specified\n", argv[i]); else fprintf (stderr, "%s: too small\n", argv[i]); return -1; } if (! fs_create (&fs, argv[i], pindex ? -pindex : kbytes, 0)) { fprintf (stderr, "%s: cannot create filesystem\n", argv[i]); return -1; } if (pindex) printf ("Created filesystem at partition %u of %s - %u kbytes\n", pindex, argv[i], fs.part_nsectors/2); else printf ("Created filesystem %s - %u kbytes\n", argv[i], kbytes); if (i == argc-2) { /* Add the contents from the specified directory. * Use the optional manifest file. */ add_contents (&fs, argv[i+1], manifest); } fs_close (&fs); return 0; } if (check) { /* Check filesystem for errors, and optionally fix them. */ if (i != argc-1) { print_help (argv[0]); return -1; } if (! fs_open (&fs, argv[i], fix, pindex)) { fprintf (stderr, "%s: cannot open\n", argv[i]); return -1; } fs_check (&fs); fs_close (&fs); return 0; } if (scan) { /* Create a manifest from directory contents. */ if (i != argc-1) { print_help (argv[0]); return -1; } if (! manifest_scan (&m, argv[i])) { fprintf (stderr, "%s: cannot read\n", argv[i]); return -1; } manifest_print (&m); return 0; } if (repartition) { /* Install a new partition table. */ if (i != argc-1) { print_help (argv[0]); return -1; } create_partition_table (argv[i], partition_format); return 0; } /* Add or extract or info. */ if (i >= argc) { print_help (argv[0]); return -1; } if (! fs_open (&fs, argv[i], (add + mount != 0), pindex)) { fprintf (stderr, "%s: cannot open filesystem\n", argv[i]); return -1; } if (extract) { /* Extract all files to current directory. */ if (i != argc-1) { print_help (argv[0]); return -1; } if (! fs_inode_get (&fs, &inode, BSDFS_ROOT_INODE)) { fprintf (stderr, "%s: cannot get inode 1\n", argv[i]); return -1; } fs_directory_scan (&inode, "", extractor, (void*) stdout); fs_close (&fs); return 0; } if (add) { /* Add files i+1..argc-1 to filesystem. */ if (i >= argc) { print_help (argv[0]); return -1; } while (++i < argc) add_object (&fs, argv[i]); fs_sync (&fs, 0); fs_close (&fs); return 0; } if (mount) { /* Mount the filesystem. */ if (i != argc-2) { print_help (argv[0]); return -1; } #ifdef ENABLE_FUSE return fs_mount(&fs, argv[i+1]); #else fprintf (stderr, "fsutil: -m, --mount options are not supported in this version.\n"); return -1; #endif } /* Print the structure of flesystem. */ if (i != argc-1) { print_help (argv[0]); return -1; } fs_print (&fs, stdout); if (verbose) { printf ("--------\n"); if (! fs_inode_get (&fs, &inode, BSDFS_ROOT_INODE)) { fprintf (stderr, "%s: cannot get inode 1\n", argv[i]); return -1; } printf ("/\n"); if (verbose > 1) { /* Print a list of blocks. */ print_inode_blocks (&inode, stdout); if (verbose > 2) { fs_inode_print (&inode, stdout); printf ("--------\n"); } } fs_directory_scan (&inode, "", scanner, (void*) stdout); } fs_close (&fs); return 0; }