diff --git a/man/systemd-sysusers.xml b/man/systemd-sysusers.xml index 73ba4e4a840..a55d9f6a757 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,46 @@ 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 + line instead of a file name. + + diff --git a/man/sysusers.d.xml b/man/sysusers.d.xml index c0d8a1682a8..47f018f4020 100644 --- a/man/sysusers.d.xml +++ b/man/sysusers.d.xml @@ -57,11 +57,14 @@ Description - systemd-sysusers uses the files from sysusers.d 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 - /etc/passwd and /etc/group directly, bypassing any more complex user - databases, for example any database involving NIS or LDAP. + systemd-sysusers uses the files from + sysusers.d 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 + /etc/passwd and /etc/group directly, + bypassing any more complex user databases, for example any database involving NIS + or LDAP. @@ -100,15 +103,16 @@ Configuration File Format - The file format is one line per user or group containing - name, ID, GECOS field description and home directory: + The file format is one line per user or group containing name, ID, GECOS + field description, home directory, and login shell: - #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 + #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 Empty lines and lines beginning with the # character are ignored, and may be used for commenting. @@ -122,14 +126,10 @@ u root 0 "Superuser" /root u - 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 - /sbin/nologin, the home directory to - the specified home directory, or / if - none is given. The account will be created disabled, so that - logins are not allowed. + 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. @@ -187,7 +187,8 @@ u root 0 "Superuser" /root numeric 32-bit UID or GID of the user/group. Do not use IDs 65535 or 4294967295, as they have special placeholder meanings. Specify - 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 GECOS - A short, descriptive string for users to be created, - enclosed in quotation marks. Note that this field may not - contain colons. + A short, descriptive string for users to be created, enclosed in + quotation marks. Note that this field may not contain colons. - Only applies to lines of type u and - should otherwise be left unset, or be set to - -. + Only applies to lines of type u and should otherwise + be left unset (or -). Home Directory - 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. + The home directory for a new system user. If omitted, defaults to the + root directory. - Only applies to lines of type u and - should otherwise be left unset, or be set to - -. + Only applies to lines of type u and should otherwise + be left unset (or -). It is recommended to omit this, unless + software strictly requires a home directory to be set. + + + + Shell + + The login shell of the user. If not specified, this will be set to + /sbin/nologin, except if the UID of the user is 0, in + which case /bin/sh will be used. + + Only applies to lines of type u and should otherwise + be left unset (or -). It is recommended to omit this, unless + a shell different /sbin/nologin must be used. Idempotence - Note that systemd-sysusers will do - nothing if the specified users or groups already exist, so - normally, there is no reason to override - sysusers.d vendor configuration, except to - block certain users or groups from being created. + Note that systemd-sysusers 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 + sysusers.d vendor configuration, except to block certain + users or groups from being created. diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index c0ac202f57f..08ede2c766a 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 75dfd05e7c1..ddee7278260 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/basic/strv.c b/src/basic/strv.c index 1103245a366..68e2e874b43 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -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); diff --git a/src/basic/strv.h b/src/basic/strv.h index 75369d29cb3..44fe1f279c9 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -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); diff --git a/src/basic/user-util.c b/src/basic/user-util.c index 17a9b5a8f13..db18ee31c00 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -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; diff --git a/src/basic/user-util.h b/src/basic/user-util.h index 5f0391f2b85..e1259a15826 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -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); diff --git a/src/core/macros.systemd.in b/src/core/macros.systemd.in index 940f1e895af..cee9b898703 100644 --- a/src/core/macros.systemd.in +++ b/src/core/macros.systemd.in @@ -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} diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 510d5fa59eb..cf16d2b55bc 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -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; } diff --git a/src/test/test-strv.c b/src/test/test-strv.c index f78b1bcd464..76cd551eeb2 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -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(); diff --git a/test/TEST-21-SYSUSERS/inline.expected-group b/test/TEST-21-SYSUSERS/inline.expected-group new file mode 100644 index 00000000000..cc9093f8079 --- /dev/null +++ b/test/TEST-21-SYSUSERS/inline.expected-group @@ -0,0 +1,2 @@ +g1:x:111: +u1:x:222: diff --git a/test/TEST-21-SYSUSERS/inline.expected-passwd b/test/TEST-21-SYSUSERS/inline.expected-passwd new file mode 100644 index 00000000000..f50f25c7d71 --- /dev/null +++ b/test/TEST-21-SYSUSERS/inline.expected-passwd @@ -0,0 +1 @@ +u1:x:222:222::/:/bin/zsh diff --git a/test/TEST-21-SYSUSERS/test-2.expected-group b/test/TEST-21-SYSUSERS/test-2.expected-group index 4d90426075c..8fcc03f4e9f 100644 --- a/test/TEST-21-SYSUSERS/test-2.expected-group +++ b/test/TEST-21-SYSUSERS/test-2.expected-group @@ -1 +1,4 @@ u1:x:SYSTEM_UID_MAX: +u2:x:777: +u3:x:778: +u4:x:779: diff --git a/test/TEST-21-SYSUSERS/test-2.expected-passwd b/test/TEST-21-SYSUSERS/test-2.expected-passwd index f438ed137ca..9eeee5d3877 100644 --- a/test/TEST-21-SYSUSERS/test-2.expected-passwd +++ b/test/TEST-21-SYSUSERS/test-2.expected-passwd @@ -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 diff --git a/test/TEST-21-SYSUSERS/test-2.input b/test/TEST-21-SYSUSERS/test-2.input index bc3e182227b..cedea9e4019 100644 --- a/test/TEST-21-SYSUSERS/test-2.input +++ b/test/TEST-21-SYSUSERS/test-2.input @@ -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 diff --git a/test/TEST-21-SYSUSERS/test.sh b/test/TEST-21-SYSUSERS/test.sh index f69d27748d1..bebbab9d237 100755 --- a/test/TEST-21-SYSUSERS/test.sh +++ b/test/TEST-21-SYSUSERS/test.sh @@ -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