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