diff --git a/src/cli.c b/src/cli.c new file mode 100644 index 0000000..16d0fbd --- /dev/null +++ b/src/cli.c @@ -0,0 +1,166 @@ +/* + * Copyright 2017-2019 Gentoo Foundation + * Copyright 2017-2019 The Chromium OS Authors + * Released under the 2-clause BSD license. + */ + +#include "tmpfiles.h" + +bool verbose = false; +bool dryrun = false; +bool do_default = true; +bool do_boot = false; +bool do_clean = false; +bool do_create = false; +bool do_remove = false; + +ordered_set_t cli_files = {}; +ordered_set_t rules_prefix = {}; +ordered_set_t rules_exclude_prefix = {}; + +/* Order here doesn't matter. All must have a value of 256+ though. */ +typedef enum { + OPT_BOOT = 0x100, + OPT_CLEAN, + OPT_CREATE, + OPT_DRY_RUN, + OPT_EXCLUDE_PREFIX, + OPT_PREFIX, + OPT_REMOVE, + OPT_VERBOSE, + OPT_VERSION, +} options; + +/* + * Order here reflects output order in --help output, so things are loosely + * grouped by relationship. Otherwise, it doesn't matter. + */ +#define PARSE_FLAGS "h" +#define a_argument required_argument +static struct option const opts[] = { + {"boot", no_argument, NULL, OPT_BOOT}, + {"create", no_argument, NULL, OPT_CREATE}, + {"clean", no_argument, NULL, OPT_CLEAN}, + {"remove", no_argument, NULL, OPT_REMOVE}, + {"prefix", a_argument, NULL, OPT_PREFIX}, + {"exclude-prefix", a_argument, NULL, OPT_EXCLUDE_PREFIX}, + {"dry-run", no_argument, NULL, OPT_DRY_RUN}, + {"dryrun", no_argument, NULL, OPT_DRY_RUN}, + {"verbose", no_argument, NULL, OPT_VERBOSE}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, OPT_VERSION}, + {NULL, no_argument, NULL, 0x0} +}; + +/* Order here must match opts array above. */ +static const char * const opts_help[] = { + "Process all boot entries (have a ! in the command)", + "Process all f, F, w, d, D, v, p, L, c, b, m and z, Z, t, T, a, A commands", + "All paths with an age setting will be cleaned", + "Remove directories marked with D or R, and paths marked with r or R", + "Only process entries that match the specified paths", + "Ignore all entries that match the specified paths", + "Don't make any changes; just show what would be done", + "Alias to --dry-run", + "Run in verbose mode", + "Print this help and exit", + "Print version and exit", + NULL, +}; + +attribute_noreturn +void usage(int status) +{ + /* Increase this if a new --longer-option is added. */ + const int padding = 25; + size_t i; + int ret; + FILE *fp = status == 0 ? stdout : stderr; + + fprintf(fp, + "Usage: tmpfiles [options] [files]\n" + "\n" + "If [files] are specified, only those will be processed.\n" + "Basenames are searched, while non-basenames are read directly.\n" + "\n" + "Options:\n" + ); + + for (i = 0; opts[i].name; ++i) { + if (opts[i].val > '~' || opts[i].val < ' ') + fprintf(fp, " "); + else + fprintf(fp, " -%c, ", opts[i].val); + + ret = fprintf(fp, "--%s ", opts[i].name); + fprintf(fp, "%-*s", padding - ret, + opts[i].has_arg == no_argument ? "" : ""); + fprintf(fp, " %s\n", opts_help[i]); + } + + exit(status); +} + +#ifndef VERSION +#define VERSION "git" +#endif +attribute_noreturn +static void version(void) +{ + printf("opentmpfiles %s\n", VERSION); + exit(0); +} + +void parseargs(int argc, char *argv[]) +{ + int i; + + ordered_set_init(&cli_files); + ordered_set_init(&rules_prefix); + ordered_set_init(&rules_exclude_prefix); + + while ((i = getopt_long(argc, argv, PARSE_FLAGS, opts, NULL)) != -1) { + switch (i) { + case OPT_BOOT: + do_default = false; + do_boot = true; + break; + case OPT_CLEAN: + do_default = false; + do_clean = true; + break; + case OPT_CREATE: + do_default = false; + do_create = true; + break; + case OPT_REMOVE: + do_default = false; + do_remove = true; + break; + + case OPT_EXCLUDE_PREFIX: + ordered_set_add(&rules_exclude_prefix, optarg); + break; + case OPT_PREFIX: + ordered_set_add(&rules_prefix, optarg); + break; + + case OPT_DRY_RUN: + dryrun = true; + break; + case OPT_VERBOSE: + verbose = true; + break; + case OPT_VERSION: + version(); + break; + case 'h': + usage(0); + default: + usage(1); + } + } + + for (i = optind; i < argc; ++i) + ordered_set_add(&cli_files, argv[i]); +} diff --git a/src/cli.h b/src/cli.h new file mode 100644 index 0000000..88c78cd --- /dev/null +++ b/src/cli.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017-2019 Gentoo Foundation + * Copyright 2017-2019 The Chromium OS Authors + * Released under the 2-clause BSD license. + */ + +#ifndef TMPFILES_CLI_H +#define TMPFILES_CLI_H + +#include "set.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* State variables for the program. */ +extern bool verbose; +extern bool dryrun; +extern bool do_boot; +extern bool do_default; +extern bool do_create; +extern bool do_clean; +extern bool do_remove; +extern ordered_set_t cli_files; +extern ordered_set_t rules_prefix; +extern ordered_set_t rules_exclude_prefix; + +/* Parse the command line arguments. */ +extern void parseargs(int argc, char *argv[]); + +/* Show usage details and exit. */ +attribute_noreturn void usage(int status); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/headers.h b/src/headers.h index 2863ead..b0e39cf 100644 --- a/src/headers.h +++ b/src/headers.h @@ -23,4 +23,7 @@ #include #include +/* Some compiler attributes we find useful. */ +#define attribute_noreturn __attribute__((__noreturn__)) + #endif diff --git a/src/main.c b/src/main.c index 28ab1eb..9d95b19 100644 --- a/src/main.c +++ b/src/main.c @@ -11,8 +11,56 @@ #include "tmpfiles.h" +static const char *flatten_set(ordered_set_t *set) +{ + char *ret = NULL; + size_t i; + size_t len = 0; + + for (i = 0; i < set->count; ++i) + len += strlen(set->elements[i]) + 1; + + if (len == 0) + return ""; + + char *p = ret = xmalloc(len); + for (i = 0; i < set->count; ++i) { + char *ele = set->elements[i]; + size_t len = strlen(ele); + memcpy(p, ele, len); + p[len] = ' '; + p += len + 1; + } + ret[len - 1] = '\0'; + return ret; +} + +static void reset_env(void) +{ + setenv("BOOT", do_boot ? "1" : "0", 1); + setenv("CLEAN", do_clean ? "1" : "0", 1); + setenv("CREATE", do_create ? "1" : "0", 1); + setenv("DRYRUN", dryrun ? "1" : "0", 1); + setenv("REMOVE", do_remove ? "1" : "0", 1); + setenv("VERBOSE", verbose ? "1" : "0", 1); + setenv("EXCLUDE", flatten_set(&rules_exclude_prefix), 1); + setenv("PREFIX", flatten_set(&rules_prefix), 1); + setenv("FILES", flatten_set(&cli_files), 1); +} + int main(int argc, char *argv[]) { - int ret = execvp("tmpfiles.sh", argv); + parseargs(argc, argv); + + if (do_clean) + errx(1, "--clean is not yet implemented"); + + if (do_create && do_remove) + errx(1, "--create and --remove are mutually exclusive"); + + /* Clear out env vars the shell script expects. */ + reset_env(); + + int ret = execlp("tmpfiles.sh", "tmpfiles.sh", "--run-by-tmpfiles-wrapper", NULL); err(ret, "Could not execute tmpfiles.sh"); } diff --git a/src/module.mk b/src/module.mk index 72fa2fa..487c659 100644 --- a/src/module.mk +++ b/src/module.mk @@ -1,4 +1,5 @@ SRC-tmpfiles := \ + cli.c \ main.c \ SRC-libtmpfiles.so := \ diff --git a/src/tmpfiles.h b/src/tmpfiles.h index 6d9f171..e8eb4a6 100644 --- a/src/tmpfiles.h +++ b/src/tmpfiles.h @@ -20,5 +20,6 @@ #include "xfuncs.h" #include "set.h" +#include "cli.h" #endif diff --git a/test/cli_test.cc b/test/cli_test.cc new file mode 100644 index 0000000..4205b90 --- /dev/null +++ b/test/cli_test.cc @@ -0,0 +1,9 @@ +/* + * Copyright 2017-2019 Gentoo Foundation + * Copyright 2017-2019 The Chromium OS Authors + * Released under the 2-clause BSD license. + */ + +/* Tests for the cli module. */ + +#include "test.h" diff --git a/test/module.mk b/test/module.mk index 9d66ebd..eba6518 100644 --- a/test/module.mk +++ b/test/module.mk @@ -2,6 +2,7 @@ LDFLAGS-tmpfiles_test = -pthread LDLIBS-tmpfiles_test = -lgtest SRC-tmpfiles_test := \ + cli_test.cc \ set_test.cc \ xfuncs_test.cc \ diff --git a/tmpfiles.sh b/tmpfiles.sh index 3619b1f..56e5c5c 100755 --- a/tmpfiles.sh +++ b/tmpfiles.sh @@ -16,7 +16,16 @@ # as of 2012/03/12 and also implements some more recent features # -DRYRUN=0 +# Any errors we've accumulated from processing. +error=0 +# XXX: No idea why this is here. +LINENO=0 + +# Make sure we aren't run directly anymore. +if [ $# -ne 1 ] || [ "$1" != "--run-by-tmpfiles-wrapper" ]; then + echo "tmpfiles.sh: this must not be run directly; use tmpfiles instead" >&2 + exit 1 +fi checkprefix() { n=$1 @@ -34,11 +43,6 @@ warninvalid() { error=$(( error+1 )) } >&2 -invalid_option() { - printf "tmpfiles: invalid option '%s'\n" "$1" >&2 - exit 1 -} - dryrun_or_real() { local dryrun= if [ $DRYRUN -eq 1 ]; then @@ -429,49 +433,6 @@ _Z() { CHOPTS=-R relabel "$@" } -usage() { - printf 'usage: %s [--exclude-prefix=path] [--prefix=path] [--boot] [--create] [--remove] [--clean] [--verbose] [--dry-run]\n' "${0##*/}" - exit ${1:-0} -} - -version() { - # We don't record the version info anywhere currently. - echo "opentmpfiles" - exit 0 -} - -BOOT=0 CREATE=0 REMOVE=0 CLEAN=0 VERBOSE=0 DRYRUN=0 error=0 LINENO=0 -EXCLUDE= -PREFIX= -FILES= - -while [ $# -gt 0 ]; do - case $1 in - --boot) BOOT=1 ;; - --create) CREATE=1 ;; - --remove) REMOVE=1 ;; - --clean) CLEAN=1 ;; # TODO: Not implemented - --verbose) VERBOSE=1 ;; - --dryrun|--dry-run) DRYRUN=1 ;; - --exclude-prefix=*) EXCLUDE="${EXCLUDE}${1##--exclude-prefix=} " ;; - --prefix=*) PREFIX="${PREFIX}${1##--prefix=} " ;; - -h|--help) usage ;; - --version) version ;; - -*) invalid_option "$1" ;; - *) FILES="${FILES} $1" - esac - shift -done - -if [ $(( CLEAN )) -eq 1 ] ; then - printf '%s clean mode is not implemented\n' "${0##*/}" - exit 1 -fi - -if [ "$CREATE$REMOVE" = '00' ]; then - usage 1 >&2 -fi - # XXX: The harcoding of /usr/lib/ is an explicit choice by upstream tmpfiles_dirs='/usr/lib/tmpfiles.d /run/tmpfiles.d /etc/tmpfiles.d' tmpfiles_basenames='' @@ -510,8 +471,6 @@ for b in ${FILES} ; do fi done -error=0 - # loop through the gathered fragments, sorted globally by filename. # `/run/tmpfiles/foo.conf' will always be read after `/etc/tmpfiles.d/bar.conf' FILE=