summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/fstree.h7
-rw-r--r--lib/Makemodule.am2
-rw-r--r--lib/fstree/fstree_from_dir.c189
-rw-r--r--mkfs/mkfs.c14
-rw-r--r--mkfs/mkfs.h8
-rw-r--r--mkfs/options.c110
6 files changed, 283 insertions, 47 deletions
diff --git a/include/fstree.h b/include/fstree.h
index 6a29093..13d4c78 100644
--- a/include/fstree.h
+++ b/include/fstree.h
@@ -229,6 +229,13 @@ void fstree_xattr_deduplicate(fstree_t *fs);
*/
int fstree_from_file(fstree_t *fs, const char *filename);
+/*
+ Recursively scan a directory and generate a file system tree from it.
+
+ Returns 0 on success, prints errors to stderr.
+ */
+int fstree_from_dir(fstree_t *fs, const char *path);
+
/* Lexicographically sort all directory contents. */
void fstree_sort(fstree_t *fs);
diff --git a/lib/Makemodule.am b/lib/Makemodule.am
index efb6b77..447acfb 100644
--- a/lib/Makemodule.am
+++ b/lib/Makemodule.am
@@ -1,5 +1,5 @@
libfstree_a_SOURCES = lib/fstree/fstree.c lib/fstree/fstree_from_file.c
-libfstree_a_SOURCES += lib/fstree/fstree_sort.c
+libfstree_a_SOURCES += lib/fstree/fstree_sort.c lib/fstree/fstree_from_dir.c
libfstree_a_SOURCES += include/fstree.h
libfstree_a_CFLAGS = $(AM_CFLAGS)
libfstree_a_CPPFLAGS = $(AM_CPPFLAGS)
diff --git a/lib/fstree/fstree_from_dir.c b/lib/fstree/fstree_from_dir.c
new file mode 100644
index 0000000..fb906f1
--- /dev/null
+++ b/lib/fstree/fstree_from_dir.c
@@ -0,0 +1,189 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+#include "fstree.h"
+#include "util.h"
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+
+static size_t path_size(tree_node_t *n)
+{
+ size_t size = 0;
+
+ while (n != NULL) {
+ size += strlen(n->name) + 1;
+ n = n->parent;
+ }
+
+ return size;
+}
+
+static char *print_path(char *ptr, tree_node_t *n)
+{
+ if (n->parent != NULL) {
+ ptr = print_path(ptr, n->parent);
+ *(ptr++) = '/';
+ strcpy(ptr, n->name);
+ return ptr + strlen(ptr);
+ }
+
+ return ptr;
+}
+
+static int populate_dir(tree_node_t *root, int fd, size_t blocksize,
+ const char *rootdir)
+{
+ size_t size, blockcount;
+ struct dirent *ent;
+ struct stat sb;
+ tree_node_t *n;
+ ssize_t ret;
+ void *ptr;
+ DIR *dir;
+ int cfd;
+
+ dir = fdopendir(fd);
+ if (dir == NULL) {
+ perror("fdopendir");
+ close(fd);
+ return -1;
+ }
+
+ for (;;) {
+ n = NULL;
+ errno = 0;
+ ent = readdir(dir);
+
+ if (ent == NULL) {
+ if (errno) {
+ perror("readdir");
+ goto fail;
+ }
+ break;
+ }
+
+ if (!strcmp(ent->d_name, "..") || !strcmp(ent->d_name, "."))
+ continue;
+
+ if (fstatat(fd, ent->d_name, &sb, AT_SYMLINK_NOFOLLOW)) {
+ perror(ent->d_name);
+ goto fail;
+ }
+
+ size = sizeof(tree_node_t) + strlen(ent->d_name) + 1;
+
+ switch (sb.st_mode & S_IFMT) {
+ case S_IFLNK:
+ size += sb.st_size + 1;
+ break;
+ case S_IFREG:
+ blockcount = sb.st_size / blocksize + 1;
+
+ size += sizeof(file_info_t);
+ size += sizeof(uint32_t) * blockcount;
+
+ size += path_size(root) + strlen(ent->d_name) + 1;
+ size += strlen(rootdir) + 2;
+ break;
+ case S_IFDIR:
+ size += sizeof(dir_info_t);
+ break;
+ }
+
+ n = calloc(1, size);
+ if (n == NULL) {
+ perror("allocating tree node");
+ goto fail;
+ }
+
+ n->uid = sb.st_uid;
+ n->gid = sb.st_gid;
+ n->mode = sb.st_mode;
+ n->parent = root;
+
+ ptr = n->payload;
+
+ switch (sb.st_mode & S_IFMT) {
+ case S_IFLNK:
+ ret = readlinkat(fd, ent->d_name, ptr, sb.st_size);
+ if (ret < 0) {
+ perror("readlink");
+ goto fail;
+ }
+
+ n->data.slink_target = ptr;
+ ptr = (char *)ptr + strlen(ptr) + 1;
+ break;
+ case S_IFBLK:
+ case S_IFCHR:
+ n->data.devno = sb.st_rdev;
+ break;
+ case S_IFDIR:
+ n->data.dir = ptr;
+ ptr = (char *)ptr + sizeof(dir_info_t);
+ break;
+ case S_IFREG:
+ n->data.file = ptr;
+ ptr = (char *)ptr + sizeof(file_info_t);
+ ptr = (char *)ptr + sizeof(uint32_t) * blockcount;
+
+ n->data.file->input_file = ptr;
+ n->data.file->size = sb.st_size;
+
+ strcpy(ptr, rootdir);
+ ptr = (char *)ptr + strlen(ptr);
+ ptr = print_path(ptr, root);
+
+ *((char*)ptr) = '/';
+ strcpy((char *)ptr + 1, ent->d_name);
+ ptr = (char *)ptr + strlen(ptr) + 1;
+ break;
+ }
+
+ n->name = ptr;
+ strcpy(ptr, ent->d_name);
+
+ n->next = root->data.dir->children;
+ root->data.dir->children = n;
+ }
+
+ for (n = root->data.dir->children; n != NULL; n = n->next) {
+ if (S_ISDIR(n->mode)) {
+ cfd = openat(fd, n->name, O_RDONLY | O_DIRECTORY);
+ if (cfd < 0) {
+ perror(n->name);
+ goto fail_dir;
+ }
+
+ if (populate_dir(n, cfd, blocksize, rootdir)) {
+ close(cfd);
+ goto fail_dir;
+ }
+ }
+ }
+
+ closedir(dir);
+ return 0;
+fail:
+ free(n);
+fail_dir:
+ closedir(dir);
+ return -1;
+}
+
+int fstree_from_dir(fstree_t *fs, const char *path)
+{
+ int fd = open(path, O_RDONLY | O_DIRECTORY);
+
+ if (fd < 0) {
+ perror(path);
+ return -1;
+ }
+
+ return populate_dir(fs->root, fd, fs->block_size, path);
+}
diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c
index 1fd95be..63b9c98 100644
--- a/mkfs/mkfs.c
+++ b/mkfs/mkfs.c
@@ -70,8 +70,18 @@ int main(int argc, char **argv)
goto out_outfd;
}
- if (fstree_from_file(&info.fs, info.opt.infile))
- goto out_fstree;
+ switch (info.opt.mode) {
+ case PACK_FILE:
+ if (fstree_from_file(&info.fs, info.opt.infile))
+ goto out_fstree;
+ break;
+ case PACK_DIR:
+ if (fstree_from_dir(&info.fs, info.opt.infile))
+ goto out_fstree;
+ break;
+ default:
+ assert(0);
+ }
#ifdef WITH_SELINUX
if (info.opt.selinux != NULL) {
diff --git a/mkfs/mkfs.h b/mkfs/mkfs.h
index ca998ac..67dc994 100644
--- a/mkfs/mkfs.h
+++ b/mkfs/mkfs.h
@@ -9,6 +9,7 @@
#include "config.h"
#include "table.h"
+#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
@@ -16,6 +17,12 @@
#include <fcntl.h>
#include <errno.h>
+typedef enum {
+ PACK_NONE,
+ PACK_FILE,
+ PACK_DIR,
+} E_PACK_MODE;
+
typedef struct {
unsigned int def_uid;
unsigned int def_gid;
@@ -30,6 +37,7 @@ typedef struct {
const char *outfile;
const char *selinux;
char *comp_extra;
+ E_PACK_MODE mode;
} options_t;
typedef struct {
diff --git a/mkfs/options.c b/mkfs/options.c
index 36539b7..56bc7d6 100644
--- a/mkfs/options.c
+++ b/mkfs/options.c
@@ -17,6 +17,8 @@ static struct option long_opts[] = {
{ "dev-block-size", required_argument, NULL, 'B' },
{ "defaults", required_argument, NULL, 'd' },
{ "comp-extra", required_argument, NULL, 'X' },
+ { "pack-file", required_argument, NULL, 'F' },
+ { "pack-dir", required_argument, NULL, 'D' },
{ "force", no_argument, NULL, 'f' },
{ "quiet", no_argument, NULL, 'q' },
#ifdef WITH_SELINUX
@@ -27,9 +29,9 @@ static struct option long_opts[] = {
};
#ifdef WITH_SELINUX
-static const char *short_opts = "s:X:c:b:B:d:fqhV";
+static const char *short_opts = "s:F:D:X:c:b:B:d:fqhV";
#else
-static const char *short_opts = "X:c:b:B:d:fqhV";
+static const char *short_opts = "F:D:X:c:b:B:d:fqhV";
#endif
enum {
@@ -50,48 +52,18 @@ static const char *defaults[] = {
extern char *__progname;
static const char *help_string =
-"Usage: %s [OPTIONS] <file-list> <squashfs-file>\n"
-"\n"
-"<file-list> is a file containing newline separated entries that describe\n"
-"the files to be included in the squashfs image:\n"
-"\n"
-"# a comment\n"
-"file <path> <mode> <uid> <gid> [<location>]\n"
-"dir <path> <mode> <uid> <gid>\n"
-"nod <path> <mode> <uid> <gid> <dev_type> <maj> <min>\n"
-"slink <path> <mode> <uid> <gid> <target>\n"
-"pipe <path> <mode> <uid> <gid>\n"
-"sock <path> <mode> <uid> <gid>\n"
-"\n"
-"<path> Absolute path of the entry in the image.\n"
-"<location> If given, location of the input file. Either absolute or relative\n"
-" to the description file. If omitted, the image path is used,\n"
-" relative to the description file.\n"
-"<target> Symlink target.\n"
-"<mode> Mode/permissions of the entry.\n"
-"<uid> Numeric user id.\n"
-"<gid> Numeric group id.\n"
-"<dev_type> Device type (b=block, c=character).\n"
-"<maj> Major number of a device special file.\n"
-"<min> Minor number of a device special file.\n"
-"\n"
-"Example:\n"
-"# A simple squashfs image\n"
-"dir /dev 0755 0 0\n"
-"nod /dev/console 0600 0 0 c 5 1\n"
-"dir /root 0700 0 0\n"
-"dir /sbin 0755 0 0\n"
-"\n"
-"# Add a file. Input is relative to this listing.\n"
-"file /sbin/init 0755 0 0 ../init/sbin/init\n"
-"\n"
-"# Read from ./bin/bash. /bin is created implicitly with default attributes.\n"
-"file /bin/bash 0755 0 0"
+"Usage: %s [OPTIONS...] <squashfs-file>\n"
"\n"
"Possible options:\n"
"\n"
+" --pack-file, -F <file> Use a `gen_init_cpio` style description file.\n"
+" The file format is specified below.\n"
+" --pack-dir, -D <directory> Pack the contents of the given directory into\n"
+" a SquashFS image. The directory becomes the\n"
+" root of the file system.\n"
" --compressor, -c <name> Select the compressor to use.\n"
-" directories (defaults to 'xz').\n"
+" A list of available compressors is below.\n"
+" Defaults to 'xz'.\n"
" --comp-extra, -X <options> A comma seperated list of extra options for\n"
" the selected compressor. Specify 'help' to\n"
" get a list of available options.\n"
@@ -116,7 +88,44 @@ static const char *help_string =
" --quiet, -q Do not print out progress reports.\n"
" --help, -h Print help text and exit.\n"
" --version, -V Print version information and exit.\n"
-"\n";
+"\n"
+"When using the pack file option, the given file is expected to contain\n"
+"newline separated entries that describe the files to be included in the\n"
+"SquashFS image. The following entry types can be specified:\n"
+"\n"
+"# a comment\n"
+"file <path> <mode> <uid> <gid> [<location>]\n"
+"dir <path> <mode> <uid> <gid>\n"
+"nod <path> <mode> <uid> <gid> <dev_type> <maj> <min>\n"
+"slink <path> <mode> <uid> <gid> <target>\n"
+"pipe <path> <mode> <uid> <gid>\n"
+"sock <path> <mode> <uid> <gid>\n"
+"\n"
+"<path> Absolute path of the entry in the image.\n"
+"<location> If given, location of the input file. Either absolute or relative\n"
+" to the description file. If omitted, the image path is used,\n"
+" relative to the description file.\n"
+"<target> Symlink target.\n"
+"<mode> Mode/permissions of the entry.\n"
+"<uid> Numeric user id.\n"
+"<gid> Numeric group id.\n"
+"<dev_type> Device type (b=block, c=character).\n"
+"<maj> Major number of a device special file.\n"
+"<min> Minor number of a device special file.\n"
+"\n"
+"Example:\n"
+" # A simple squashfs image\n"
+" dir /dev 0755 0 0\n"
+" nod /dev/console 0600 0 0 c 5 1\n"
+" dir /root 0700 0 0\n"
+" dir /sbin 0755 0 0\n"
+" \n"
+" # Add a file. Input is relative to this listing.\n"
+" file /sbin/init 0755 0 0 ../init/sbin/init\n"
+" \n"
+" # Read from bin/bash relative to the listing. Implicitly create /bin.\n"
+" file /bin/bash 0755 0 0"
+"\n\n";
static const char *compressors[] = {
[SQFS_COMP_GZIP] = "gzip",
@@ -237,6 +246,7 @@ void process_command_line(options_t *opt, int argc, char **argv)
opt->devblksz = SQFS_DEVBLK_SIZE;
opt->infile = NULL;
opt->outfile = NULL;
+ opt->mode = PACK_NONE;
for (;;) {
i = getopt_long(argc, argv, short_opts, long_opts, NULL);
@@ -283,6 +293,14 @@ void process_command_line(options_t *opt, int argc, char **argv)
case 'X':
opt->comp_extra = optarg;
break;
+ case 'F':
+ opt->mode = PACK_FILE;
+ opt->infile = optarg;
+ break;
+ case 'D':
+ opt->mode = PACK_DIR;
+ opt->infile = optarg;
+ break;
#ifdef WITH_SELINUX
case 's':
opt->selinux = optarg;
@@ -313,12 +331,16 @@ void process_command_line(options_t *opt, int argc, char **argv)
exit(EXIT_SUCCESS);
}
- if ((optind + 1) >= argc) {
- fputs("Missing arguments: input and output files.\n", stderr);
+ if (opt->mode == PACK_NONE) {
+ fputs("No input file or directory specified.\n", stderr);
+ goto fail_arg;
+ }
+
+ if (optind >= argc) {
+ fputs("No output file specified.\n", stderr);
goto fail_arg;
}
- opt->infile = argv[optind++];
opt->outfile = argv[optind++];
return;
fail_arg: