Merge pull request #8058 from keszybz/sysusers-inline

Extend sysusers for package installation scripts
This commit is contained in:
Yu Watanabe 2018-02-05 16:50:51 +09:00 committed by GitHub
commit 24c2c5689d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 480 additions and 123 deletions

View File

@ -69,15 +69,18 @@
<citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para>
<para>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
<citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
are searched for a matching file. If the string
<literal>-</literal> is specified instead of a filename, entries from the
standard input of the process are read.</para>
<para>If invoked with no arguments, it applies all directives from all files
found in the directories specified by
<citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
When invoked with positional arguments, if option
<option>--replace=<replaceable>PATH</replaceable></option> is specified, arguments
specified on the command line are used instead of the configuration file
<replaceable>PATH</replaceable>. Otherwise, just the configuration specified by
the command line arguments is executed. The string <literal>-</literal> may be
specified instead of a filename to instruct <command>systemd-sysusers</command>
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.</para>
</refsect1>
<refsect1>
@ -94,6 +97,46 @@
paths. </para></listitem>
</varlistentry>
<varlistentry>
<term><option>--replace=<replaceable>PATH</replaceable></option></term>
<listitem><para>When this option is given, one ore more positional arguments
must be specified. All configuration files found in the directories listed in
<citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
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
<replaceable>PATH</replaceable>.</para>
<para>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.
</para>
<example>
<title>RPM installation script for radvd</title>
<programlisting>echo 'u radvd - "radvd daemon"' | \
systemd-sysusers --replace=/usr/lib/sysusers.d/radvd.conf -</programlisting>
<para>This will create the radvd user as if
<filename>/usr/lib/sysusers.d/radvd.conf</filename> was already on disk.
An admin might override the configuration specified on the command line by
placing <filename>/etc/sysusers.d/radvd.conf</filename> or even
<filename>/etc/sysusers.d/00-overrides.conf</filename>.</para>
<para>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.</para>
</example>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--inline</option></term>
<listitem><para>Treat each positional argument as a separate configuration
line instead of a file name.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>

View File

@ -57,11 +57,14 @@
<refsect1>
<title>Description</title>
<para><command>systemd-sysusers</command> uses the files from <filename>sysusers.d</filename> directory to create
system users and groups at package installation or boot time. This tool may be used to allocate system users and
groups only, it is not useful for creating non-system (i.e. regular, "human") users and groups, as it accesses
<filename>/etc/passwd</filename> and <filename>/etc/group</filename> directly, bypassing any more complex user
databases, for example any database involving NIS or LDAP.</para>
<para><command>systemd-sysusers</command> uses the files from
<filename>sysusers.d</filename> directory to create system users and groups and
to add users to groups, at package installation or boot time. This tool may be
used to allocate system users and groups only, it is not useful for creating
non-system (i.e. regular, "human") users and groups, as it accesses
<filename>/etc/passwd</filename> and <filename>/etc/group</filename> directly,
bypassing any more complex user databases, for example any database involving NIS
or LDAP.</para>
</refsect1>
<refsect1>
@ -100,15 +103,16 @@
<refsect1>
<title>Configuration File Format</title>
<para>The file format is one line per user or group containing
name, ID, GECOS field description and home directory:</para>
<para>The file format is one line per user or group containing name, ID, GECOS
field description, home directory, and login shell:</para>
<programlisting>#Type Name ID GECOS Home directory
u httpd 440 "HTTP User"
u authd /usr/bin/authd "Authorization user"
g input - -
m authd input
u root 0 "Superuser" /root</programlisting>
<programlisting>#Type Name ID GECOS Home directory Shell
u httpd 404 "HTTP User"
u authd /usr/bin/authd "Authorization user"
u postgres - "Postgresql Database" /var/lib/pgsql /usr/libexec/postgresdb
g input - -
m authd input
u root 0 "Superuser" /root /bin/zsh</programlisting>
<para>Empty lines and lines beginning with the <literal>#</literal> character are ignored, and may be used for
commenting.</para>
@ -122,14 +126,10 @@ u root 0 "Superuser" /root</programlisting>
<variablelist>
<varlistentry>
<term><varname>u</varname></term>
<listitem><para>Create a system user and group of the
specified name should they not exist yet. The user's primary
group will be set to the group bearing the same name. The
user's shell will be set to
<filename>/sbin/nologin</filename>, the home directory to
the specified home directory, or <filename>/</filename> if
none is given. The account will be created disabled, so that
logins are not allowed.</para></listitem>
<listitem><para>Create a system user and group of the specified name should
they not exist yet. The user's primary group will be set to the group
bearing the same name. The account will be created disabled, so that logins
are not allowed.</para></listitem>
</varlistentry>
<varlistentry>
@ -187,7 +187,8 @@ u root 0 "Superuser" /root</programlisting>
numeric 32-bit UID or GID of the user/group. Do not use IDs 65535
or 4294967295, as they have special placeholder meanings.
Specify <literal>-</literal> for automatic UID/GID allocation
for the user or group. Alternatively, specify an absolute path
for the user or group (this is strongly recommended unless it is strictly
necessary to use a specific UID or GID). Alternatively, specify an absolute path
in the file system. In this case, the UID/GID is read from the
path's owner/group. This is useful to create users whose UID/GID
match the owners of pre-existing files (such as SUID or SGID
@ -209,37 +210,45 @@ u root 0 "Superuser" /root</programlisting>
<refsect2>
<title>GECOS</title>
<para>A short, descriptive string for users to be created,
enclosed in quotation marks. Note that this field may not
contain colons.</para>
<para>A short, descriptive string for users to be created, enclosed in
quotation marks. Note that this field may not contain colons.</para>
<para>Only applies to lines of type <varname>u</varname> and
should otherwise be left unset, or be set to
<literal>-</literal>.</para>
<para>Only applies to lines of type <varname>u</varname> and should otherwise
be left unset (or <literal>-</literal>).</para>
</refsect2>
<refsect2>
<title>Home Directory</title>
<para>The home directory for a new system user. If omitted,
defaults to the root directory. It is recommended to not
unnecessarily specify home directories for system users, unless
software strictly requires one to be set.</para>
<para>The home directory for a new system user. If omitted, defaults to the
root directory.</para>
<para>Only applies to lines of type <varname>u</varname> and
should otherwise be left unset, or be set to
<literal>-</literal>.</para>
<para>Only applies to lines of type <varname>u</varname> and should otherwise
be left unset (or <literal>-</literal>). It is recommended to omit this, unless
software strictly requires a home directory to be set.</para>
</refsect2>
<refsect2>
<title>Shell</title>
<para>The login shell of the user. If not specified, this will be set to
<filename>/sbin/nologin</filename>, except if the UID of the user is 0, in
which case <filename>/bin/sh</filename> will be used.</para>
<para>Only applies to lines of type <varname>u</varname> and should otherwise
be left unset (or <literal>-</literal>). It is recommended to omit this, unless
a shell different <filename>/sbin/nologin</filename> must be used.</para>
</refsect2>
</refsect1>
<refsect1>
<title>Idempotence</title>
<para>Note that <command>systemd-sysusers</command> will do
nothing if the specified users or groups already exist, so
normally, there is no reason to override
<filename>sysusers.d</filename> vendor configuration, except to
block certain users or groups from being created.</para>
<para>Note that <command>systemd-sysusers</command> will do nothing if the
specified users or groups already exist or the users are members of specified
groups, so normally there is no reason to override
<filename>sysusers.d</filename> vendor configuration, except to block certain
users or groups from being created.</para>
</refsect1>
<refsect1>

View File

@ -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;

View File

@ -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);

View File

@ -446,7 +446,7 @@ int strv_push_pair(char ***l, char *a, char *b) {
return 0;
}
int strv_push_prepend(char ***l, char *value) {
int strv_insert(char ***l, unsigned position, char *value) {
char **c;
unsigned n, m, i;
@ -454,6 +454,7 @@ int strv_push_prepend(char ***l, char *value) {
return 0;
n = strv_length(*l);
position = MIN(position, n);
/* increase and check for overflow */
m = n + 2;
@ -464,10 +465,12 @@ int strv_push_prepend(char ***l, char *value) {
if (!c)
return -ENOMEM;
for (i = 0; i < n; i++)
for (i = 0; i < position; i++)
c[i] = (*l)[i];
c[position] = value;
for (i = position; i < n; i++)
c[i+1] = (*l)[i];
c[0] = value;
c[n+1] = NULL;
free(*l);

View File

@ -54,7 +54,12 @@ int strv_extendf(char ***l, const char *format, ...) _printf_(2,0);
int strv_extend_front(char ***l, const char *value);
int strv_push(char ***l, char *value);
int strv_push_pair(char ***l, char *a, char *b);
int strv_push_prepend(char ***l, char *value);
int strv_insert(char ***l, unsigned position, char *value);
static inline int strv_push_prepend(char ***l, char *value) {
return strv_insert(l, 0, value);
}
int strv_consume(char ***l, char *value);
int strv_consume_pair(char ***l, char *a, char *b);
int strv_consume_prepend(char ***l, char *value);

View File

@ -553,18 +553,18 @@ int take_etc_passwd_lock(const char *root) {
* awfully racy, and thus we just won't do them. */
if (root)
path = prefix_roota(root, "/etc/.pwd.lock");
path = prefix_roota(root, ETC_PASSWD_LOCK_PATH);
else
path = "/etc/.pwd.lock";
path = ETC_PASSWD_LOCK_PATH;
fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
if (fd < 0)
return -errno;
return log_debug_errno(errno, "Cannot open %s: %m", path);
r = fcntl(fd, F_SETLKW, &flock);
if (r < 0) {
safe_close(fd);
return -errno;
return log_debug_errno(errno, "Locking %s failed: %m", path);
}
return fd;
@ -645,6 +645,8 @@ bool valid_gecos(const char *d) {
}
bool valid_home(const char *p) {
/* Note that this function is also called by valid_shell(), any
* changes must account for that. */
if (isempty(p))
return false;

View File

@ -63,6 +63,8 @@ int take_etc_passwd_lock(const char *root);
#define UID_NOBODY ((uid_t) 65534U)
#define GID_NOBODY ((gid_t) 65534U)
#define ETC_PASSWD_LOCK_PATH "/etc/.pwd.lock"
static inline bool uid_is_dynamic(uid_t uid) {
return DYNAMIC_UID_MIN <= uid && uid <= DYNAMIC_UID_MAX;
}
@ -96,6 +98,15 @@ bool valid_user_group_name_or_id(const char *u);
bool valid_gecos(const char *d);
bool valid_home(const char *p);
static inline bool valid_shell(const char *p) {
/* We have the same requirements, so just piggy-back on the home check.
*
* Let's ignore /etc/shells because this is only applicable to real and
* not system users. It is also incompatible with the idea of empty /etc.
*/
return valid_home(p);
}
int maybe_setgroups(size_t size, const gid_t *list);
bool synthesize_nobody(void);

View File

@ -100,6 +100,7 @@ journalctl --update-catalog >/dev/null 2>&1 || : \
systemd-tmpfiles --create %{?*} >/dev/null 2>&1 || : \
%{nil}
# Deprecated. Use %sysusers_create_package instead
%sysusers_create() \
systemd-sysusers %{?*} >/dev/null 2>&1 || : \
%{nil}
@ -108,6 +109,24 @@ systemd-sysusers %{?*} >/dev/null 2>&1 || : \
echo %{?*} | systemd-sysusers - >/dev/null 2>&1 || : \
%{nil}
# This should be used by package installation scripts which
# require users or groups to be present before the files installed
# by the package are present on disk (for example because some files
# are owned by those users or groups).
#
# Example:
# Source1: %{name}.conf
# ...
# %install
# install -Dt %{buildroot}%{sysusersdir} %SOURCE1
# %pre
# %sysusers_create_package %{name} %SOURCE1
# %files
# %{sysusersdir}/%{name}.conf
%sysusers_create_package() \
echo "%(cat %2)" | systemd-sysusers --replace=%_sysusersdir/%1.conf - >/dev/null 2>&1 || : \
%{nil}
%sysctl_apply() \
@rootlibexecdir@/systemd-sysctl %{?*} >/dev/null 2>&1 || : \
%{nil}

View File

@ -59,14 +59,18 @@ typedef struct Item {
char *gid_path;
char *description;
char *home;
char *shell;
gid_t gid;
uid_t uid;
bool gid_set:1;
// id_set_strict means that the group with the specified gid must
// exist and that the check if a uid clashes with a gid is skipped
/* When set the group with the specified gid must exist
* and the check if a uid clashes with the gid is skipped.
*/
bool id_set_strict:1;
bool uid_set:1;
bool todo_user:1;
@ -74,6 +78,8 @@ 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");
@ -383,6 +389,10 @@ static int rename_and_apply_smack(const char *temp_path, const char *dest_path)
return r;
}
static const char* default_shell(uid_t uid) {
return uid == 0 ? "/bin/sh" : "/sbin/nologin";
}
static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char **tmpfile_path) {
_cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
_cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
@ -450,7 +460,7 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
/* Initialize the shell to nologin, with one exception:
* for root we patch in something special */
.pw_shell = i->uid == 0 ? (char*) "/bin/sh" : (char*) "/sbin/nologin",
.pw_shell = i->shell ?: (char*) default_shell(i->uid),
};
errno = 0;
@ -1224,6 +1234,7 @@ static void item_free(Item *i) {
free(i->gid_path);
free(i->description);
free(i->home);
free(i->shell);
free(i);
}
@ -1331,6 +1342,9 @@ static bool item_equal(Item *a, Item *b) {
if (!streq_ptr(a->home, b->home))
return false;
if (!streq_ptr(a->shell, b->shell))
return false;
return true;
}
@ -1344,7 +1358,12 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
{}
};
_cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL, *resolved_id = NULL, *description = NULL, *home = NULL;
_cleanup_free_ char *action = NULL,
*name = NULL, *resolved_name = NULL,
*id = NULL, *resolved_id = NULL,
*description = NULL,
*home = NULL,
*shell, *resolved_shell = NULL;
_cleanup_(item_freep) Item *i = NULL;
Item *existing;
OrderedHashmap *h;
@ -1357,7 +1376,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
/* Parse columns */
p = buffer;
r = extract_many_words(&p, NULL, EXTRACT_QUOTES, &action, &name, &id, &description, &home, NULL);
r = extract_many_words(&p, NULL, EXTRACT_QUOTES,
&action, &name, &id, &description, &home, &shell, NULL);
if (r < 0) {
log_error("[%s:%u] Syntax error.", fname, line);
return r;
@ -1433,6 +1453,24 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
}
}
/* Verify shell */
if (isempty(shell) || streq(shell, "-"))
shell = mfree(shell);
if (shell) {
r = specifier_printf(shell, specifier_table, NULL, &resolved_shell);
if (r < 0) {
log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, shell);
return r;
}
if (!valid_shell(resolved_shell)) {
log_error("[%s:%u] '%s' is not a valid login shell field.", fname, line, resolved_shell);
return -EINVAL;
}
}
switch (action[0]) {
case ADD_RANGE:
@ -1446,13 +1484,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
if (description) {
log_error("[%s:%u] Lines of type 'r' don't take a GECOS field.", fname, line);
return -EINVAL;
}
if (home) {
log_error("[%s:%u] Lines of type 'r' don't take a home directory field.", fname, line);
if (description || home || shell) {
log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
fname, line, action[0],
description ? "GECOS" : home ? "home directory" : "login shell");
return -EINVAL;
}
@ -1483,13 +1518,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
if (description) {
log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line);
return -EINVAL;
}
if (home) {
log_error("[%s:%u] Lines of type 'm' don't take a home directory field.", fname, line);
if (description || home || shell) {
log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
fname, line, action[0],
description ? "GECOS" : home ? "home directory" : "login shell");
return -EINVAL;
}
@ -1573,6 +1605,9 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
i->home = home;
home = NULL;
i->shell = resolved_shell;
resolved_shell = NULL;
h = users;
break;
@ -1582,13 +1617,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
if (description) {
log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line);
return -EINVAL;
}
if (home) {
log_error("[%s:%u] Lines of type 'g' don't take a home directory field.", fname, line);
if (description || home || shell) {
log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
fname, line, action[0],
description ? "GECOS" : home ? "home directory" : "login shell");
return -EINVAL;
}
@ -1718,6 +1750,8 @@ 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);
}
@ -1726,12 +1760,16 @@ static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_ROOT,
ARG_REPLACE,
ARG_INLINE,
};
static const struct option options[] = {
{ "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 },
{}
};
@ -1757,6 +1795,20 @@ 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;
case '?':
return -EINVAL;
@ -1764,14 +1816,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;
@ -1791,30 +1905,18 @@ int main(int argc, char *argv[]) {
goto finish;
}
if (optind < argc) {
int j;
for (j = optind; j < argc; j++) {
k = read_config_file(argv[j], false);
if (k < 0 && r == 0)
r = k;
}
} 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
@ -1841,7 +1943,7 @@ int main(int argc, char *argv[]) {
lock = take_etc_passwd_lock(arg_root);
if (lock < 0) {
log_error_errno(lock, "Failed to take lock: %m");
log_error_errno(lock, "Failed to take /etc/passwd lock: %m");
goto finish;
}

View File

@ -447,6 +447,36 @@ static void test_strv_from_stdarg_alloca(void) {
test_strv_from_stdarg_alloca_one(STRV_MAKE_EMPTY, NULL);
}
static void test_strv_insert(void) {
_cleanup_strv_free_ char **a = NULL;
assert_se(strv_insert(&a, 0, strdup("first")) == 0);
assert_se(streq(a[0], "first"));
assert_se(!a[1]);
assert_se(strv_insert(&a, 0, NULL) == 0);
assert_se(streq(a[0], "first"));
assert_se(!a[1]);
assert_se(strv_insert(&a, 1, strdup("two")) == 0);
assert_se(streq(a[0], "first"));
assert_se(streq(a[1], "two"));
assert_se(!a[2]);
assert_se(strv_insert(&a, 4, strdup("tri")) == 0);
assert_se(streq(a[0], "first"));
assert_se(streq(a[1], "two"));
assert_se(streq(a[2], "tri"));
assert_se(!a[3]);
assert_se(strv_insert(&a, 1, strdup("duo")) == 0);
assert_se(streq(a[0], "first"));
assert_se(streq(a[1], "duo"));
assert_se(streq(a[2], "two"));
assert_se(streq(a[3], "tri"));
assert_se(!a[4]);
}
static void test_strv_push_prepend(void) {
_cleanup_strv_free_ char **a = NULL;
@ -723,6 +753,7 @@ int main(int argc, char *argv[]) {
test_strv_extend();
test_strv_extendf();
test_strv_from_stdarg_alloca();
test_strv_insert();
test_strv_push_prepend();
test_strv_push();
test_strv_equal();

View File

@ -0,0 +1,2 @@
g1:x:111:
u1:x:222:

View File

@ -0,0 +1 @@
u1:x:222:222::/:/bin/zsh

View File

@ -1 +1,4 @@
u1:x:SYSTEM_UID_MAX:
u2:x:777:
u3:x:778:
u4:x:779:

View File

@ -1 +1,4 @@
u1:x:SYSTEM_UID_MAX:SYSTEM_UID_MAX:some gecos:/random/dir:/sbin/nologin
u2:x:777:777:some gecos:/random/dir:/bin/zsh
u3:x:778:778::/random/dir2:/bin/bash
u4:x:779:779::/:/bin/csh

View File

@ -1,4 +1,8 @@
# Trivial smoke test that generate the ID dynamically based on SYSTEM_UID_MAX
# Test generation of ID dynamically based on SYSTEM_UID_MAX and
# replacement of all fields up to the login shell.
#
#Type Name ID GECOS HOMEDIR
u u1 - "some gecos" /random/dir
#Type Name ID GECOS homedir shell
u u1 - "some gecos" /random/dir -
u u2 777 "some gecos" /random/dir /bin/zsh
u u3 778 - /random/dir2 /bin/bash
u u4 779 - - /bin/csh

View File

@ -7,7 +7,7 @@ TEST_DESCRIPTION="Sysuser-related tests"
. $TEST_BASE_DIR/test-functions
test_setup() {
mkdir -p $TESTDIR/etc $TESTDIR/usr/lib/sysusers.d $TESTDIR/tmp
mkdir -p $TESTDIR/etc/sysusers.d $TESTDIR/usr/lib/sysusers.d $TESTDIR/tmp
}
preprocess() {
@ -20,31 +20,85 @@ preprocess() {
sed "s/SYSTEM_UID_MAX/${SYSTEM_UID_MAX}/g" "$in"
}
compare() {
if ! diff -u $TESTDIR/etc/passwd <(preprocess ${1%.*}.expected-passwd); then
echo "**** Unexpected output for $f"
exit 1
fi
if ! diff -u $TESTDIR/etc/group <(preprocess ${1%.*}.expected-group); then
echo "**** Unexpected output for $f $2"
exit 1
fi
}
test_run() {
# ensure our build of systemd-sysusers is run
PATH=${BUILD_DIR}:$PATH
rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
# happy tests
for f in test-*.input; do
echo "*** Running $f"
rm -f $TESTDIR/etc/*
rm -f $TESTDIR/etc/*{passwd,group,shadow}
cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
systemd-sysusers --root=$TESTDIR
if ! diff -u $TESTDIR/etc/passwd <(preprocess ${f%.*}.expected-passwd); then
echo "**** Unexpected output for $f"
exit 1
fi
if ! diff -u $TESTDIR/etc/group <(preprocess ${f%.*}.expected-group); then
echo "**** Unexpected output for $f"
exit 1
fi
compare $f ""
done
for f in test-*.input; do
echo "*** Running $f on stdin"
rm -f $TESTDIR/etc/*{passwd,group,shadow}
touch $TESTDIR/etc/sysusers.d/test.conf
cat $f | systemd-sysusers --root=$TESTDIR -
compare $f "on stdin"
done
for f in test-*.input; do
echo "*** Running $f on stdin with --replace"
rm -f $TESTDIR/etc/*{passwd,group,shadow}
touch $TESTDIR/etc/sysusers.d/test.conf
# this overrides test.conf which is masked on disk
cat $f | systemd-sysusers --root=$TESTDIR --replace=/etc/sysusers.d/test.conf -
# this should be ignored
cat test-1.input | systemd-sysusers --root=$TESTDIR --replace=/usr/lib/sysusers.d/test.conf -
compare $f "on stdin with --replace"
done
# test --inline
echo "*** Testing --inline"
rm -f $TESTDIR/etc/*{passwd,group,shadow}
# copy a random file to make sure it is ignored
cp $f $TESTDIR/etc/sysusers.d/confuse.conf
systemd-sysusers --root=$TESTDIR --inline \
"u u1 222 - - /bin/zsh" \
"g g1 111"
compare inline "(--inline)"
# test --replace
echo "*** Testing --inline with --replace"
rm -f $TESTDIR/etc/*{passwd,group,shadow}
# copy a random file to make sure it is ignored
cp $f $TESTDIR/etc/sysusers.d/confuse.conf
systemd-sysusers --root=$TESTDIR \
--inline \
--replace=/etc/sysusers.d/confuse.conf \
"u u1 222 - - /bin/zsh" \
"g g1 111"
compare inline "(--inline --replace=…)"
rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
# tests for error conditions
for f in unhappy-*.input; do
echo "*** Running test $f"
rm -f $TESTDIR/etc/*
rm -f $TESTDIR/etc/*{passwd,group,shadow}
cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
systemd-sysusers --root=$TESTDIR 2> /dev/null
journalctl -t systemd-sysusers -o cat | tail -n1 > $TESTDIR/tmp/err