summaryrefslogtreecommitdiff
path: root/mkfs/options.c
diff options
context:
space:
mode:
Diffstat (limited to 'mkfs/options.c')
-rw-r--r--mkfs/options.c295
1 files changed, 295 insertions, 0 deletions
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);
+}