diff --git a/man/systemd-sysusers.xml b/man/systemd-sysusers.xml index 7816356889..a55d9f6a75 100644 --- a/man/systemd-sysusers.xml +++ b/man/systemd-sysusers.xml @@ -69,15 +69,18 @@ sysusers.d5. - If invoked with no arguments, it applies all directives from - all files found. If one or more filenames are passed on the - command line, only the directives in these files are applied. If - only the basename of a file is specified, all directories as - specified in - sysusers.d5 - are searched for a matching file. If the string - - is specified instead of a filename, entries from the - standard input of the process are read. + If invoked with no arguments, it applies all directives from all files + found in the directories specified by + sysusers.d5. + When invoked with positional arguments, if option + is specified, arguments + specified on the command line are used instead of the configuration file + PATH. Otherwise, just the configuration specified by + the command line arguments is executed. The string - may be + specified instead of a filename to instruct systemd-sysusers + to read the configuration from standard input. If only the basename of a file is + specified, all configuration directories are searched for a matching file and + the file found that has the highest priority is executed. @@ -94,6 +97,40 @@ paths. + + + When this option is given, one ore more positional arguments + must be specified. All configuration files found in the directories listed in + sysusers.d5 + will be read, and the configuration given on the command line will be + handled instead of and with the same priority as the configuration file + PATH. + + This option is intended to be used when package installation scripts + are running and files belonging to that package are not yet available on + disk, so their contents must be given on the command line, but the admin + configuration might already exist and should be given higher priority. + + + + RPM installation script for radvd + + echo 'u radvd - "radvd daemon"' | \ + systemd-sysusers --replace=/usr/lib/sysusers.d/radvd.conf - + + This will create the radvd user as if + /usr/lib/sysusers.d/radvd.conf was already on disk. + An admin might override the configuration specified on the command line by + placing /etc/sysusers.d/radvd.conf or even + /etc/sysusers.d/00-overrides.conf. + + Note that this is the expanded from, and when used in a package, this + would be written using a macro with "radvd" and a file containing the + configuration line as arguments. + + + + Treat each positional argument as a separate configuration diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index c0ac202f57..08ede2c766 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -154,6 +154,70 @@ static int conf_files_list_strv_internal(char ***strv, const char *suffix, const return 0; } +int conf_files_insert(char ***strv, const char *root, const char *dirs, const char *path) { + /* Insert a path into strv, at the place honouring the usual sorting rules: + * - we first compare by the basename + * - and then we compare by dirname, allowing just one file with the given + * basename. + * This means that we will + * - add a new entry if basename(path) was not on the list, + * - do nothing if an entry with higher priority was already present, + * - do nothing if our new entry matches the existing entry, + * - replace the existing entry if our new entry has higher priority. + */ + char *t; + unsigned i; + int r; + + for (i = 0; i < strv_length(*strv); i++) { + int c; + + c = base_cmp(*strv + i, &path); + if (c == 0) { + const char *dir; + + /* Oh, we found our spot and it already contains something. */ + NULSTR_FOREACH(dir, dirs) { + char *p1, *p2; + + p1 = path_startswith((*strv)[i], root); + if (p1) + /* Skip "/" in dir, because p1 is without "/" too */ + p1 = path_startswith(p1, dir + 1); + if (p1) + /* Existing entry with higher priority + * or same priority, no need to do anything. */ + return 0; + + p2 = path_startswith(path, dir); + if (p2) { + /* Our new entry has higher priority */ + t = path_join(root, path, NULL); + if (!t) + return log_oom(); + + return free_and_replace((*strv)[i], t); + } + } + + } else if (c > 0) + /* Following files have lower priority, let's go insert our + * new entry. */ + break; + + /* … we are not there yet, let's continue */ + } + + t = path_join(root, path, NULL); + if (!t) + return log_oom(); + + r = strv_insert(strv, i, t); + if (r < 0) + free(t); + return r; +} + int conf_files_list_strv(char ***strv, const char *suffix, const char *root, unsigned flags, const char* const* dirs) { _cleanup_strv_free_ char **copy = NULL; diff --git a/src/basic/conf-files.h b/src/basic/conf-files.h index 75dfd05e7c..ddee727826 100644 --- a/src/basic/conf-files.h +++ b/src/basic/conf-files.h @@ -28,3 +28,4 @@ enum { int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir, ...); int conf_files_list_strv(char ***ret, const char *suffix, const char *root, unsigned flags, const char* const* dirs); int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs); +int conf_files_insert(char ***strv, const char *root, const char *dirs, const char *path); diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index e2a3b9968c..af21e3b854 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -75,6 +75,7 @@ typedef struct Item { } Item; static char *arg_root = NULL; +static const char *arg_replace = NULL; static bool arg_inline = false; static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysusers.d"); @@ -1746,6 +1747,7 @@ static void help(void) { " -h --help Show this help\n" " --version Show package version\n" " --root=PATH Operate on an alternate filesystem root\n" + " --replace=PATH Treat arguments as replacement for PATH\n" " --inline Treat arguments as configuration lines\n" , program_invocation_short_name); } @@ -1755,6 +1757,7 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_ROOT, + ARG_REPLACE, ARG_INLINE, }; @@ -1762,6 +1765,7 @@ static int parse_argv(int argc, char *argv[]) { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "root", required_argument, NULL, ARG_ROOT }, + { "replace", required_argument, NULL, ARG_REPLACE }, { "inline", no_argument, NULL, ARG_INLINE }, {} }; @@ -1788,6 +1792,16 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_REPLACE: + if (!path_is_absolute(optarg) || + !endswith(optarg, ".conf")) { + log_error("The argument to --replace= must an absolute path to a config file"); + return -EINVAL; + } + + arg_replace = optarg; + break; + case ARG_INLINE: arg_inline = true; break; @@ -1799,14 +1813,76 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached("Unhandled option"); } + if (arg_replace && optind >= argc) { + log_error("When --replace= is given, some configuration items must be specified"); + return -EINVAL; + } + return 1; } +static int parse_arguments(char **args) { + char **arg; + unsigned pos = 1; + int r; + + STRV_FOREACH(arg, args) { + if (arg_inline) + /* Use (argument):n, where n==1 for the first positional arg */ + r = parse_line("(argument)", pos, *arg); + else + r = read_config_file(*arg, false); + if (r < 0) + return r; + + pos++; + } + + return 0; +} + +static int read_config_files(const char* dirs, char **args) { + _cleanup_strv_free_ char **files = NULL; + _cleanup_free_ char *p = NULL; + char **f; + int r; + + r = conf_files_list_nulstr(&files, ".conf", arg_root, 0, dirs); + if (r < 0) + return log_error_errno(r, "Failed to enumerate sysusers.d files: %m"); + + if (arg_replace) { + r = conf_files_insert(&files, arg_root, dirs, arg_replace); + if (r < 0) + return log_error_errno(r, "Failed to extend sysusers.d file list: %m"); + + p = path_join(arg_root, arg_replace, NULL); + if (!p) + return log_oom(); + } + + STRV_FOREACH(f, files) + if (p && path_equal(*f, p)) { + log_debug("Parsing arguments at position \"%s\"…", *f); + + r = parse_arguments(args); + if (r < 0) + return r; + } else { + log_debug("Reading config file \"%s\"…", *f); + + /* Just warn, ignore result otherwise */ + (void) read_config_file(*f, true); + } + + return 0; +} + int main(int argc, char *argv[]) { _cleanup_close_ int lock = -1; Iterator iterator; - int r, k; + int r; Item *i; char *n; @@ -1826,34 +1902,18 @@ int main(int argc, char *argv[]) { goto finish; } - if (optind < argc) { - int j; - - for (j = optind; j < argc; j++) { - if (arg_inline) - /* Use (argument):n, where n==1 for the first positional arg */ - r = parse_line("(argument)", j - optind + 1, argv[j]); - else - r = read_config_file(argv[j], false); - if (r < 0) - goto finish; - } - } else { - _cleanup_strv_free_ char **files = NULL; - char **f; - - r = conf_files_list_nulstr(&files, ".conf", arg_root, 0, conf_file_dirs); - if (r < 0) { - log_error_errno(r, "Failed to enumerate sysusers.d files: %m"); - goto finish; - } - - STRV_FOREACH(f, files) { - k = read_config_file(*f, true); - if (k < 0 && r == 0) - r = k; - } - } + /* If command line arguments are specified along with --replace, read all + * configuration files and insert the positional arguments at the specified + * place. Otherwise, if command line arguments are specified, execute just + * them, and finally, without --replace= or any positional arguments, just + * read configuration and execute it. + */ + if (arg_replace || optind >= argc) + r = read_config_files(conf_file_dirs, argv + optind); + else + r = parse_arguments(argv + optind); + if (r < 0) + goto finish; /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though