/* SPDX-License-Identifier: GPL-3.0-or-later */ #include "mksquashfs.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) { bool have_compressor; 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': have_compressor = false; for (i = SQFS_COMP_MIN; i <= SQFS_COMP_MAX; ++i) { if (strcmp(compressors[i], optarg) == 0) { if (compressor_exists(i)) { have_compressor = true; opt->compressor = i; break; } } } if (!have_compressor) { fprintf(stderr, "Unsupported compressor '%s'\n", optarg); exit(EXIT_FAILURE); } break; case 'b': opt->blksz = read_number("Block size", optarg, SQFS_META_BLOCK_SIZE, 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) { if (compressor_exists(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); }