aboutsummaryrefslogtreecommitdiff
path: root/mkfs
diff options
context:
space:
mode:
Diffstat (limited to 'mkfs')
-rw-r--r--mkfs/Makemodule.am5
-rw-r--r--mkfs/mksquashfs.c70
-rw-r--r--mkfs/options.c295
-rw-r--r--mkfs/options.h20
4 files changed, 390 insertions, 0 deletions
diff --git a/mkfs/Makemodule.am b/mkfs/Makemodule.am
new file mode 100644
index 0000000..1d062b9
--- /dev/null
+++ b/mkfs/Makemodule.am
@@ -0,0 +1,5 @@
+mksquashfs_SOURCES = mkfs/mksquashfs.c mkfs/options.c mkfs/options.h
+mksquashfs_SOURCES += include/squashfs.h
+mksquashfs_LDADD = libfstree.a
+
+bin_PROGRAMS += mksquashfs
diff --git a/mkfs/mksquashfs.c b/mkfs/mksquashfs.c
new file mode 100644
index 0000000..bc2a215
--- /dev/null
+++ b/mkfs/mksquashfs.c
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+#include "squashfs.h"
+#include "options.h"
+#include "fstree.h"
+
+#include <sys/sysmacros.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <ctype.h>
+
+static void print_tree(int level, tree_node_t *node)
+{
+ tree_node_t *n;
+ int i;
+
+ for (i = 1; i < level; ++i) {
+ fputs("| ", stdout);
+ }
+
+ if (level)
+ fputs("+- ", stdout);
+
+ if (S_ISDIR(node->mode)) {
+ fprintf(stdout, "%s/ (%u, %u, 0%o)\n", node->name,
+ node->uid, node->gid, node->mode & 07777);
+
+ for (n = node->data.dir->children; n != NULL; n = n->next) {
+ print_tree(level + 1, n);
+ }
+
+ if (node->data.dir->children != NULL) {
+ for (i = 0; i < level; ++i)
+ fputs("| ", stdout);
+
+ if (level)
+ fputc('\n', stdout);
+ }
+ } else {
+ fprintf(stdout, "%s (%u, %u, 0%o)\n", node->name,
+ node->uid, node->gid, node->mode & 07777);
+ }
+}
+
+
+int main(int argc, char **argv)
+{
+ options_t opt;
+ fstree_t fs;
+
+ process_command_line(&opt, argc, argv);
+
+ if (fstree_init(&fs, opt.blksz, opt.def_mtime, opt.def_mode,
+ opt.def_uid, opt.def_gid)) {
+ return EXIT_FAILURE;
+ }
+
+ if (fstree_from_file(&fs, opt.infile)) {
+ fstree_cleanup(&fs);
+ return EXIT_FAILURE;
+ }
+
+ fstree_sort(&fs);
+
+ print_tree(0, fs.root);
+
+ fstree_cleanup(&fs);
+ return EXIT_SUCCESS;
+}
diff --git a/mkfs/options.c b/mkfs/options.c
new file mode 100644
index 0000000..513e76a
--- /dev/null
+++ b/mkfs/options.c
@@ -0,0 +1,295 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+#include "squashfs.h"
+#include "options.h"
+#include "config.h"
+
+#include <stdlib.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <limits.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <stdio.h>
+
+static struct option long_opts[] = {
+ { "compressor", required_argument, NULL, 'c' },
+ { "block-size", required_argument, NULL, 'b' },
+ { "dev-block-size", required_argument, NULL, 'B' },
+ { "defaults", required_argument, NULL, 'd' },
+ { "force", no_argument, NULL, 'f' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+};
+
+static const char *short_opts = "c:b:B:d:fhV";
+
+enum {
+ DEF_UID = 0,
+ DEF_GID,
+ DEF_MODE,
+ DEF_MTIME,
+};
+
+static const char *defaults[] = {
+ [DEF_UID] = "uid",
+ [DEF_GID] = "gid",
+ [DEF_MODE] = "mode",
+ [DEF_MTIME] = "mtime",
+ NULL
+};
+
+#define LICENSE_SHORT "GPLv3+"
+#define LICENSE_LONG "GNU GPL version 3 or later"
+#define LICENSE_URL "https://gnu.org/licenses/gpl.html"
+
+extern char *__progname;
+
+static const char *version_string =
+"%s (%s) %s\n"
+"Copyright (c) 2019 David Oberhollenzer\n"
+"License " LICENSE_SHORT ": " LICENSE_LONG " <" LICENSE_URL ">.\n"
+"This is free software: you are free to change and redistribute it.\n"
+"There is NO WARRANTY, to the extent permitted by law.\n"
+"\n"
+"Written by David Oberhollenzer.\n";
+
+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"
+"\n"
+"Possible options:\n"
+"\n"
+" --compressor, -c <name> Select the compressor to use.\n"
+" directories (defaults to 'xz').\n"
+" --block-size, -b <size> Block size to use for Squashfs image.\n"
+" Defaults to %u.\n"
+" --dev-block-size, -B <size> Device block size to padd the image to.\n"
+" Defaults to %u.\n"
+" --defaults, -d <options> A comma seperated list of default values for\n"
+" implicitly created directories.\n"
+"\n"
+" Possible options:\n"
+" uid=<value> 0 if not set.\n"
+" gid=<value> 0 if not set.\n"
+" mode=<value> 0755 if not set.\n"
+" mtime=<value> 0 if not set.\n"
+"\n"
+" --force, -f Overwrite the output file if it exists.\n"
+" --help, -h Print help text and exit.\n"
+" --version, -V Print version information and exit.\n"
+"\n";
+
+static const char *compressors[] = {
+ [SQFS_COMP_GZIP] = "gzip",
+ [SQFS_COMP_LZMA] = "lzma",
+ [SQFS_COMP_LZO] = "lzo",
+ [SQFS_COMP_XZ] = "xz",
+ [SQFS_COMP_LZ4] = "lz4",
+ [SQFS_COMP_ZSTD] = "zstd",
+};
+
+static long read_number(const char *name, const char *str, long min, long max)
+{
+ long base = 10, result = 0;
+ int x;
+
+ if (str[0] == '0') {
+ if (str[1] == 'x' || str[1] == 'X') {
+ base = 16;
+ str += 2;
+ } else {
+ base = 8;
+ }
+ }
+
+ if (!isxdigit(*str))
+ goto fail_num;
+
+ while (isxdigit(*str)) {
+ x = *(str++);
+
+ if (isupper(x)) {
+ x = x - 'A' + 10;
+ } else if (islower(x)) {
+ x = x - 'a' + 10;
+ } else {
+ x -= '0';
+ }
+
+ if (x >= base)
+ goto fail_num;
+
+ if (result > (LONG_MAX - x) / base)
+ goto fail_ov;
+
+ result = result * base + x;
+ }
+
+ if (result < min)
+ goto fail_uf;
+
+ if (result > max)
+ goto fail_ov;
+
+ return result;
+fail_num:
+ fprintf(stderr, "%s: expected numeric value > 0\n", name);
+ goto fail;
+fail_uf:
+ fprintf(stderr, "%s: number to small\n", name);
+ goto fail;
+fail_ov:
+ fprintf(stderr, "%s: number to large\n", name);
+ goto fail;
+fail:
+ exit(EXIT_FAILURE);
+}
+
+static void process_defaults(options_t *opt, char *subopts)
+{
+ char *value;
+ int i;
+
+ while (*subopts != '\0') {
+ i = getsubopt(&subopts, (char *const *)defaults, &value);
+
+ if (value == NULL) {
+ fprintf(stderr, "Missing value for option %s\n",
+ defaults[i]);
+ exit(EXIT_FAILURE);
+ }
+
+ switch (i) {
+ case DEF_UID:
+ opt->def_uid = read_number("Default user ID", value,
+ 0, 0xFFFFFFFF);
+ break;
+ case DEF_GID:
+ opt->def_gid = read_number("Default group ID", value,
+ 0, 0xFFFFFFFF);
+ break;
+ case DEF_MODE:
+ opt->def_mode = read_number("Default permissions",
+ value, 0, 0xFFFFFFFF);
+ break;
+ case DEF_MTIME:
+ opt->def_mtime = read_number("Default mtime", value,
+ 0, 0xFFFFFFFF);
+ break;
+ default:
+ fprintf(stderr, "Unknown option '%s'\n", value);
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+void process_command_line(options_t *opt, int argc, char **argv)
+{
+ int i;
+
+ opt->def_uid = 0;
+ opt->def_gid = 0;
+ opt->def_mode = 0755;
+ opt->def_mtime = 0;
+ opt->outmode = O_WRONLY | O_CREAT | O_EXCL;
+ opt->compressor = SQFS_COMP_XZ;
+ opt->blksz = SQFS_DEFAULT_BLOCK_SIZE;
+ opt->devblksz = SQFS_DEVBLK_SIZE;
+ opt->infile = NULL;
+ opt->outfile = NULL;
+
+ for (;;) {
+ i = getopt_long(argc, argv, short_opts, long_opts, NULL);
+ if (i == -1)
+ break;
+
+ switch (i) {
+ case 'c':
+ for (i = SQFS_COMP_MIN; i <= SQFS_COMP_MAX; ++i) {
+ if (strcmp(compressors[i], optarg) == 0) {
+ opt->compressor = i;
+ break;
+ }
+ }
+ break;
+ case 'b':
+ opt->blksz = read_number("Block size", optarg,
+ 1024, 0xFFFFFFFF);
+ break;
+ case 'B':
+ opt->devblksz = read_number("Device block size", optarg,
+ 4096, 0xFFFFFFFF);
+ break;
+ case 'd':
+ process_defaults(opt, optarg);
+ break;
+ case 'f':
+ opt->outmode = O_WRONLY | O_CREAT | O_TRUNC;
+ break;
+ case 'h':
+ printf(help_string, __progname,
+ SQFS_DEFAULT_BLOCK_SIZE, SQFS_DEVBLK_SIZE);
+
+ fputs("Available compressors:\n", stdout);
+
+ for (i = SQFS_COMP_MIN; i <= SQFS_COMP_MAX; ++i)
+ printf("\t%s\n", compressors[i]);
+
+ exit(EXIT_SUCCESS);
+ case 'V':
+ printf(version_string, __progname,
+ PACKAGE_NAME, PACKAGE_VERSION);
+ exit(EXIT_SUCCESS);
+ default:
+ goto fail_arg;
+ }
+ }
+
+ if ((optind + 1) >= argc) {
+ fputs("Missing arguments: input and output files.\n", stderr);
+ goto fail_arg;
+ }
+
+ opt->infile = argv[optind++];
+ opt->outfile = argv[optind++];
+ return;
+fail_arg:
+ fprintf(stderr, "Try `%s --help' for more information.\n", __progname);
+ exit(EXIT_FAILURE);
+}
diff --git a/mkfs/options.h b/mkfs/options.h
new file mode 100644
index 0000000..dc11250
--- /dev/null
+++ b/mkfs/options.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+typedef struct {
+ unsigned int def_uid;
+ unsigned int def_gid;
+ unsigned int def_mode;
+ unsigned int def_mtime;
+ int outmode;
+ int compressor;
+ int blksz;
+ int devblksz;
+ const char *infile;
+ const char *outfile;
+} options_t;
+
+void process_command_line(options_t *opt, int argc, char **argv);
+
+#endif /* OPTIONS_H */