]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/sysusers/sysusers.c
util-lib: don't include fileio.h from fileio-label.h
[thirdparty/systemd.git] / src / sysusers / sysusers.c
index 43952e5f194d76b66aeb96150766d0fdf5278d5c..d0ab5a9b6ce686cc497166ebbdb9b46d0850f531 100644 (file)
@@ -1,28 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2014 Lennart Poettering
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
 
 #include <getopt.h>
-#include <grp.h>
-#include <gshadow.h>
-#include <pwd.h>
-#include <shadow.h>
 #include <utmp.h>
 
 #include "alloc-util.h"
 #include "copy.h"
 #include "def.h"
 #include "fd-util.h"
-#include "fs-util.h"
-#include "fileio-label.h"
+#include "fileio.h"
 #include "format-util.h"
+#include "fs-util.h"
 #include "hashmap.h"
+#include "pager.h"
 #include "path-util.h"
+#include "pretty-print.h"
 #include "selinux-util.h"
 #include "smack-util.h"
 #include "specifier.h"
@@ -51,6 +31,7 @@ typedef enum ItemType {
         ADD_MEMBER = 'm',
         ADD_RANGE = 'r',
 } ItemType;
+
 typedef struct Item {
         ItemType type;
 
@@ -78,10 +59,10 @@ typedef struct Item {
 } Item;
 
 static char *arg_root = NULL;
+static bool arg_cat_config = false;
 static const char *arg_replace = NULL;
 static bool arg_inline = false;
-
-static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysusers.d");
+static PagerFlags arg_pager_flags = 0;
 
 static OrderedHashmap *users = NULL, *groups = NULL;
 static OrderedHashmap *todo_uids = NULL, *todo_gids = NULL;
@@ -113,8 +94,7 @@ static int load_user_database(void) {
         if (r < 0)
                 return r;
 
-        errno = 0;
-        while ((pw = fgetpwent(f))) {
+        while ((r = fgetpwent_sane(f, &pw)) > 0) {
                 char *n;
                 int k, q;
 
@@ -130,20 +110,15 @@ static int load_user_database(void) {
 
                 q = hashmap_put(database_uid, UID_TO_PTR(pw->pw_uid), n);
                 if (q < 0 && q != -EEXIST) {
-                        if (k < 0)
+                        if (k <= 0)
                                 free(n);
                         return q;
                 }
 
-                if (q < 0 && k < 0)
+                if (k <= 0 && q <= 0)
                         free(n);
-
-                errno = 0;
         }
-        if (!IN_SET(errno, 0, ENOENT))
-                return -errno;
-
-        return 0;
+        return r;
 }
 
 static int load_group_database(void) {
@@ -182,12 +157,12 @@ static int load_group_database(void) {
 
                 q = hashmap_put(database_gid, GID_TO_PTR(gr->gr_gid), n);
                 if (q < 0 && q != -EEXIST) {
-                        if (k < 0)
+                        if (k <= 0)
                                 free(n);
                         return q;
                 }
 
-                if (q < 0 && k < 0)
+                if (k <= 0 && q <= 0)
                         free(n);
 
                 errno = 0;
@@ -232,11 +207,9 @@ static int make_backup(const char *target, const char *x) {
         backup = strjoina(x, "-");
 
         /* Copy over the access mask */
-        if (fchmod(fileno(dst), st.st_mode & 07777) < 0)
-                log_warning_errno(errno, "Failed to change mode on %s: %m", backup);
-
-        if (fchown(fileno(dst), st.st_uid, st.st_gid)< 0)
-                log_warning_errno(errno, "Failed to change ownership of %s: %m", backup);
+        r = fchmod_and_chown(fileno(dst), st.st_mode & 07777, st.st_uid, st.st_gid);
+        if (r < 0)
+                log_warning_errno(r, "Failed to change access mode or ownership of %s: %m", backup);
 
         ts[0] = st.st_atim;
         ts[1] = st.st_mtim;
@@ -287,6 +260,7 @@ static int putgrent_with_members(const struct group *gr, FILE *group) {
 
                 if (added) {
                         struct group t;
+                        int r;
 
                         strv_uniq(l);
                         strv_sort(l);
@@ -294,19 +268,12 @@ static int putgrent_with_members(const struct group *gr, FILE *group) {
                         t = *gr;
                         t.gr_mem = l;
 
-                        errno = 0;
-                        if (putgrent(&t, group) != 0)
-                                return errno > 0 ? -errno : -EIO;
-
-                        return 1;
+                        r = putgrent_sane(&t, group);
+                        return r < 0 ? r : 1;
                 }
         }
 
-        errno = 0;
-        if (putgrent(gr, group) != 0)
-                return errno > 0 ? -errno : -EIO;
-
-        return 0;
+        return putgrent_sane(gr, group);
 }
 
 #if ENABLE_GSHADOW
@@ -338,6 +305,7 @@ static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) {
 
                 if (added) {
                         struct sgrp t;
+                        int r;
 
                         strv_uniq(l);
                         strv_sort(l);
@@ -345,19 +313,12 @@ static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) {
                         t = *sg;
                         t.sg_mem = l;
 
-                        errno = 0;
-                        if (putsgent(&t, gshadow) != 0)
-                                return errno > 0 ? -errno : -EIO;
-
-                        return 1;
+                        r = putsgent_sane(&t, gshadow);
+                        return r < 0 ? r : 1;
                 }
         }
 
-        errno = 0;
-        if (putsgent(sg, gshadow) != 0)
-                return errno > 0 ? -errno : -EIO;
-
-        return 0;
+        return putsgent_sane(sg, gshadow);
 }
 #endif
 
@@ -367,13 +328,7 @@ static int sync_rights(FILE *from, FILE *to) {
         if (fstat(fileno(from), &st) < 0)
                 return -errno;
 
-        if (fchmod(fileno(to), st.st_mode & 07777) < 0)
-                return -errno;
-
-        if (fchown(fileno(to), st.st_uid, st.st_gid) < 0)
-                return -errno;
-
-        return 0;
+        return fchmod_and_chown(fileno(to), st.st_mode & 07777, st.st_uid, st.st_gid);
 }
 
 static int rename_and_apply_smack(const char *temp_path, const char *dest_path) {
@@ -415,8 +370,7 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
                 if (r < 0)
                         return r;
 
-                errno = 0;
-                while ((pw = fgetpwent(original))) {
+                while ((r = fgetpwent_sane(original, &pw)) > 0) {
 
                         i = ordered_hashmap_get(users, pw->pw_name);
                         if (i && i->todo_user) {
@@ -429,19 +383,16 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
                                 return -EEXIST;
                         }
 
-                        errno = 0;
-
                         /* Make sure we keep the NIS entries (if any) at the end. */
                         if (IN_SET(pw->pw_name[0], '+', '-'))
                                 break;
 
-                        if (putpwent(pw, passwd) < 0)
-                                return errno ? -errno : -EIO;
-
-                        errno = 0;
+                        r = putpwent_sane(pw, passwd);
+                        if (r < 0)
+                                return r;
                 }
-                if (!IN_SET(errno, 0, ENOENT))
-                        return -errno;
+                if (r < 0)
+                        return r;
 
         } else {
                 if (errno != ENOENT)
@@ -468,32 +419,31 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
                         .pw_shell = i->shell ?: (char*) default_shell(i->uid),
                 };
 
-                errno = 0;
-                if (putpwent(&n, passwd) != 0)
-                        return errno ? -errno : -EIO;
+                r = putpwent_sane(&n, passwd);
+                if (r < 0)
+                        return r;
         }
-        errno = 0;
 
         /* Append the remaining NIS entries if any */
         while (pw) {
-                errno = 0;
-                if (putpwent(pw, passwd) < 0)
-                        return errno ? -errno : -EIO;
+                r = putpwent_sane(pw, passwd);
+                if (r < 0)
+                        return r;
 
-                errno = 0;
-                pw = fgetpwent(original);
+                r = fgetpwent_sane(original, &pw);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
         }
-        if (!IN_SET(errno, 0, ENOENT))
-                return -errno;
 
         r = fflush_and_check(passwd);
         if (r < 0)
                 return r;
 
-        *tmpfile = passwd;
-        *tmpfile_path = passwd_tmp;
-        passwd = NULL;
-        passwd_tmp = NULL;
+        *tmpfile = TAKE_PTR(passwd);
+        *tmpfile_path = TAKE_PTR(passwd_tmp);
+
         return 0;
 }
 
@@ -522,8 +472,7 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
                 if (r < 0)
                         return r;
 
-                errno = 0;
-                while ((sp = fgetspent(original))) {
+                while ((r = fgetspent_sane(original, &sp)) > 0) {
 
                         i = ordered_hashmap_get(users, sp->sp_namp);
                         if (i && i->todo_user) {
@@ -536,19 +485,16 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
                                 ordered_hashmap_remove(todo_uids, UID_TO_PTR(i->uid));
                         }
 
-                        errno = 0;
-
                         /* Make sure we keep the NIS entries (if any) at the end. */
                         if (IN_SET(sp->sp_namp[0], '+', '-'))
                                 break;
 
-                        if (putspent(sp, shadow) < 0)
-                                return errno ? -errno : -EIO;
-
-                        errno = 0;
+                        r = putspent_sane(sp, shadow);
+                        if (r < 0)
+                                return r;
                 }
-                if (!IN_SET(errno, 0, ENOENT))
-                        return -errno;
+                if (r < 0)
+                        return r;
 
         } else {
                 if (errno != ENOENT)
@@ -570,20 +516,22 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
                         .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
                 };
 
-                errno = 0;
-                if (putspent(&n, shadow) != 0)
-                        return errno ? -errno : -EIO;
+                r = putspent_sane(&n, shadow);
+                if (r < 0)
+                        return r;
         }
-        errno = 0;
 
         /* Append the remaining NIS entries if any */
         while (sp) {
-                errno = 0;
-                if (putspent(sp, shadow) < 0)
-                        return errno ? -errno : -EIO;
+                r = putspent_sane(sp, shadow);
+                if (r < 0)
+                        return r;
 
-                errno = 0;
-                sp = fgetspent(original);
+                r = fgetspent_sane(original, &sp);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
         }
         if (!IN_SET(errno, 0, ENOENT))
                 return -errno;
@@ -592,10 +540,9 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
         if (r < 0)
                 return r;
 
-        *tmpfile = shadow;
-        *tmpfile_path = shadow_tmp;
-        shadow = NULL;
-        shadow_tmp = NULL;
+        *tmpfile = TAKE_PTR(shadow);
+        *tmpfile_path = TAKE_PTR(shadow_tmp);
+
         return 0;
 }
 
@@ -622,8 +569,7 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
                 if (r < 0)
                         return r;
 
-                errno = 0;
-                while ((gr = fgetgrent(original))) {
+                while ((r = fgetgrent_sane(original, &gr)) > 0) {
                         /* Safety checks against name and GID collisions. Normally,
                          * this should be unnecessary, but given that we look at the
                          * entries anyway here, let's make an extra verification
@@ -640,8 +586,6 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
                                 return  -EEXIST;
                         }
 
-                        errno = 0;
-
                         /* Make sure we keep the NIS entries (if any) at the end. */
                         if (IN_SET(gr->gr_name[0], '+', '-'))
                                 break;
@@ -651,11 +595,9 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
                                 return r;
                         if (r > 0)
                                 group_changed = true;
-
-                        errno = 0;
                 }
-                if (!IN_SET(errno, 0, ENOENT))
-                        return -errno;
+                if (r < 0)
+                        return r;
 
         } else {
                 if (errno != ENOENT)
@@ -677,29 +619,27 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
 
                 group_changed = true;
         }
-        errno = 0;
 
         /* Append the remaining NIS entries if any */
         while (gr) {
-                errno = 0;
-                if (putgrent(gr, group) != 0)
-                        return errno > 0 ? -errno : -EIO;
+                r = putgrent_sane(gr, group);
+                if (r < 0)
+                        return r;
 
-                errno = 0;
-                gr = fgetgrent(original);
+                r = fgetgrent_sane(original, &gr);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
         }
-        if (!IN_SET(errno, 0, ENOENT))
-                return -errno;
 
         r = fflush_sync_and_check(group);
         if (r < 0)
                 return r;
 
         if (group_changed) {
-                *tmpfile = group;
-                *tmpfile_path = group_tmp;
-                group = NULL;
-                group_tmp = NULL;
+                *tmpfile = TAKE_PTR(group);
+                *tmpfile_path = TAKE_PTR(group_tmp);
         }
         return 0;
 }
@@ -728,8 +668,7 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
                 if (r < 0)
                         return r;
 
-                errno = 0;
-                while ((sg = fgetsgent(original))) {
+                while ((r = fgetsgent_sane(original, &sg)) > 0) {
 
                         i = ordered_hashmap_get(groups, sg->sg_namp);
                         if (i && i->todo_group) {
@@ -742,11 +681,9 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
                                 return r;
                         if (r > 0)
                                 group_changed = true;
-
-                        errno = 0;
                 }
-                if (!IN_SET(errno, 0, ENOENT))
-                        return -errno;
+                if (r < 0)
+                        return r;
 
         } else {
                 if (errno != ENOENT)
@@ -773,10 +710,8 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
                 return r;
 
         if (group_changed) {
-                *tmpfile = gshadow;
-                *tmpfile_path = gshadow_tmp;
-                gshadow = NULL;
-                gshadow_tmp = NULL;
+                *tmpfile = TAKE_PTR(gshadow);
+                *tmpfile_path = TAKE_PTR(gshadow_tmp);
         }
         return 0;
 #else
@@ -1177,10 +1112,10 @@ static int add_group(Item *i) {
                          * r > 0: means the gid does not exist -> fail
                          * r == 0: means the gid exists -> nothing more to do.
                          */
-                        if (r > 0) {
-                                log_error("Failed to create %s: please create GID %d", i->name, i->gid);
-                                return -EINVAL;
-                        }
+                        if (r > 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Failed to create %s: please create GID %d",
+                                                       i->name, i->gid);
                         if (r == 0)
                                 return 0;
                 }
@@ -1414,18 +1349,20 @@ static bool item_equal(Item *a, Item *b) {
 static int parse_line(const char *fname, unsigned line, const char *buffer) {
 
         static const Specifier specifier_table[] = {
-                { 'm', specifier_machine_id, NULL },
-                { 'b', specifier_boot_id, NULL },
-                { 'H', specifier_host_name, NULL },
+                { 'm', specifier_machine_id,     NULL },
+                { 'b', specifier_boot_id,        NULL },
+                { 'H', specifier_host_name,      NULL },
                 { 'v', specifier_kernel_release, NULL },
+                { 'T', specifier_tmp_dir,        NULL },
+                { 'V', specifier_var_tmp_dir,    NULL },
                 {}
         };
 
         _cleanup_free_ char *action = NULL,
                 *name = NULL, *resolved_name = NULL,
                 *id = NULL, *resolved_id = NULL,
-                *description = NULL,
-                *home = NULL,
+                *description = NULL, *resolved_description = NULL,
+                *home = NULL, *resolved_home = NULL,
                 *shell, *resolved_shell = NULL;
         _cleanup_(item_freep) Item *i = NULL;
         Item *existing;
@@ -1499,8 +1436,14 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                 description = mfree(description);
 
         if (description) {
-                if (!valid_gecos(description)) {
-                        log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, description);
+                r = specifier_printf(description, specifier_table, NULL, &resolved_description);
+                if (r < 0) {
+                        log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, description);
+                        return r;
+                }
+
+                if (!valid_gecos(resolved_description)) {
+                        log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, resolved_description);
                         return -EINVAL;
                 }
         }
@@ -1510,8 +1453,14 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                 home = mfree(home);
 
         if (home) {
-                if (!valid_home(home)) {
-                        log_error("[%s:%u] '%s' is not a valid home directory field.", fname, line, home);
+                r = specifier_printf(home, specifier_table, NULL, &resolved_home);
+                if (r < 0) {
+                        log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, home);
+                        return r;
+                }
+
+                if (!valid_home(resolved_home)) {
+                        log_error("[%s:%u] '%s' is not a valid home directory field.", fname, line, resolved_home);
                         return -EINVAL;
                 }
         }
@@ -1533,7 +1482,6 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                 }
         }
 
-
         switch (action[0]) {
 
         case ADD_RANGE:
@@ -1640,10 +1588,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
 
                 if (resolved_id) {
                         if (path_is_absolute(resolved_id)) {
-                                i->uid_path = resolved_id;
-                                resolved_id = NULL;
-
-                                path_kill_slashes(i->uid_path);
+                                i->uid_path = TAKE_PTR(resolved_id);
+                                path_simplify(i->uid_path, false);
                         } else {
                                 _cleanup_free_ char *uid = NULL, *gid = NULL;
                                 if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
@@ -1663,14 +1609,9 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                         }
                 }
 
-                i->description = description;
-                description = NULL;
-
-                i->home = home;
-                home = NULL;
-
-                i->shell = resolved_shell;
-                resolved_shell = NULL;
+                i->description = TAKE_PTR(resolved_description);
+                i->home = TAKE_PTR(resolved_home);
+                i->shell = TAKE_PTR(resolved_shell);
 
                 h = users;
                 break;
@@ -1698,10 +1639,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
 
                 if (resolved_id) {
                         if (path_is_absolute(resolved_id)) {
-                                i->gid_path = resolved_id;
-                                resolved_id = NULL;
-
-                                path_kill_slashes(i->gid_path);
+                                i->gid_path = TAKE_PTR(resolved_id);
+                                path_simplify(i->gid_path, false);
                         } else {
                                 r = parse_gid(resolved_id, &i->gid);
                                 if (r < 0)
@@ -1719,8 +1658,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
         }
 
         i->type = action[0];
-        i->name = resolved_name;
-        resolved_name = NULL;
+        i->name = TAKE_PTR(resolved_name);
 
         existing = ordered_hashmap_get(h, i->name);
         if (existing) {
@@ -1743,7 +1681,6 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
 static int read_config_file(const char *fn, bool ignore_enoent) {
         _cleanup_fclose_ FILE *rf = NULL;
         FILE *f = NULL;
-        char line[LINE_MAX];
         unsigned v = 0;
         int r = 0;
 
@@ -1752,7 +1689,7 @@ static int read_config_file(const char *fn, bool ignore_enoent) {
         if (streq(fn, "-"))
                 f = stdin;
         else {
-                r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &rf);
+                r = search_and_fopen(fn, "re", arg_root, (const char**) CONF_PATHS_STRV("sysusers.d"), &rf);
                 if (r < 0) {
                         if (ignore_enoent && r == -ENOENT)
                                 return 0;
@@ -1763,10 +1700,17 @@ static int read_config_file(const char *fn, bool ignore_enoent) {
                 f = rf;
         }
 
-        FOREACH_LINE(line, f, break) {
+        for (;;) {
+                _cleanup_free_ char *line = NULL;
                 char *l;
                 int k;
 
+                k = read_line(f, LONG_LINE_MAX, &line);
+                if (k < 0)
+                        return log_error_errno(k, "Failed to read '%s': %m", fn);
+                if (k == 0)
+                        break;
+
                 v++;
 
                 l = strstrip(line);
@@ -1808,32 +1752,63 @@ static void free_database(Hashmap *by_name, Hashmap *by_id) {
         hashmap_free(by_id);
 }
 
-static void help(void) {
+static int cat_config(void) {
+        _cleanup_strv_free_ char **files = NULL;
+        int r;
+
+        r = conf_files_list_with_replacement(arg_root, CONF_PATHS_STRV("sysusers.d"), arg_replace, &files, NULL);
+        if (r < 0)
+                return r;
+
+        (void) pager_open(arg_pager_flags);
+
+        return cat_files(NULL, files, 0);
+}
+
+static int help(void) {
+        _cleanup_free_ char *link = NULL;
+        int r;
+
+        r = terminal_urlify_man("systemd-sysusers.service", "8", &link);
+        if (r < 0)
+                return log_oom();
+
         printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
                "Creates system user accounts.\n\n"
                "  -h --help                 Show this help\n"
                "     --version              Show package version\n"
+               "     --cat-config           Show configuration files\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);
+               "     --no-pager             Do not pipe output into a pager\n"
+               "\nSee the %s for details.\n"
+               , program_invocation_short_name
+               , link
+        );
+
+        return 0;
 }
 
 static int parse_argv(int argc, char *argv[]) {
 
         enum {
                 ARG_VERSION = 0x100,
+                ARG_CAT_CONFIG,
                 ARG_ROOT,
                 ARG_REPLACE,
                 ARG_INLINE,
+                ARG_NO_PAGER,
         };
 
         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  },
+                { "help",       no_argument,       NULL, 'h'            },
+                { "version",    no_argument,       NULL, ARG_VERSION    },
+                { "cat-config", no_argument,       NULL, ARG_CAT_CONFIG },
+                { "root",       required_argument, NULL, ARG_ROOT       },
+                { "replace",    required_argument, NULL, ARG_REPLACE    },
+                { "inline",     no_argument,       NULL, ARG_INLINE     },
+                { "no-pager",   no_argument,       NULL, ARG_NO_PAGER   },
                 {}
         };
 
@@ -1847,12 +1822,15 @@ static int parse_argv(int argc, char *argv[]) {
                 switch (c) {
 
                 case 'h':
-                        help();
-                        return 0;
+                        return help();
 
                 case ARG_VERSION:
                         return version();
 
+                case ARG_CAT_CONFIG:
+                        arg_cat_config = true;
+                        break;
+
                 case ARG_ROOT:
                         r = parse_path_argument_and_warn(optarg, true, &arg_root);
                         if (r < 0)
@@ -1861,10 +1839,9 @@ static int parse_argv(int argc, char *argv[]) {
 
                 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;
-                        }
+                            !endswith(optarg, ".conf"))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "The argument to --replace= must an absolute path to a config file");
 
                         arg_replace = optarg;
                         break;
@@ -1873,6 +1850,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_inline = true;
                         break;
 
+                case ARG_NO_PAGER:
+                        arg_pager_flags |= PAGER_DISABLE;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -1880,10 +1861,13 @@ 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;
-        }
+        if (arg_replace && arg_cat_config)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Option --replace= is not supported with --cat-config");
+
+        if (arg_replace && optind >= argc)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "When --replace= is given, some configuration items must be specified");
 
         return 1;
 }
@@ -1908,25 +1892,15 @@ static int parse_arguments(char **args) {
         return 0;
 }
 
-static int read_config_files(const char* dirs, char **args) {
+static int read_config_files(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);
+        r = conf_files_list_with_replacement(arg_root, CONF_PATHS_STRV("sysusers.d"), arg_replace, &files, &p);
         if (r < 0)
-                return log_error_errno(r, "Failed to enumerate sysusers.d files: %m");
-
-        if (arg_replace) {
-                r = conf_files_insert_nulstr(&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();
-        }
+                return r;
 
         STRV_FOREACH(f, files)
                 if (p && path_equal(*f, p)) {
@@ -1946,20 +1920,22 @@ static int read_config_files(const char* dirs, char **args) {
 }
 
 int main(int argc, char *argv[]) {
-
         _cleanup_close_ int lock = -1;
         Iterator iterator;
-        int r;
+        char *n, **v;
         Item *i;
-        char *n;
+        int r;
 
         r = parse_argv(argc, argv);
         if (r <= 0)
                 goto finish;
 
-        log_set_target(LOG_TARGET_AUTO);
-        log_parse_environment();
-        log_open();
+        log_setup_service();
+
+        if (arg_cat_config) {
+                r = cat_config();
+                goto finish;
+        }
 
         umask(0022);
 
@@ -1976,7 +1952,7 @@ int main(int argc, char *argv[]) {
          * read configuration and execute it.
          */
         if (arg_replace || optind >= argc)
-                r = read_config_files(conf_file_dirs, argv + optind);
+                r = read_config_files(argv + optind);
         else
                 r = parse_arguments(argv + optind);
         if (r < 0)
@@ -2024,21 +2000,23 @@ int main(int argc, char *argv[]) {
         }
 
         ORDERED_HASHMAP_FOREACH(i, groups, iterator)
-                process_item(i);
+                (void) process_item(i);
 
         ORDERED_HASHMAP_FOREACH(i, users, iterator)
-                process_item(i);
+                (void) process_item(i);
 
         r = write_files();
         if (r < 0)
                 log_error_errno(r, "Failed to write files: %m");
 
 finish:
+        pager_close();
+
         ordered_hashmap_free_with_destructor(groups, item_free);
         ordered_hashmap_free_with_destructor(users, item_free);
 
-        while ((n = ordered_hashmap_first_key(members))) {
-                strv_free(ordered_hashmap_steal_first(members));
+        while ((v = ordered_hashmap_steal_first_key_and_value(members, (void **) &n))) {
+                strv_free(v);
                 free(n);
         }
         ordered_hashmap_free(members);
@@ -2049,6 +2027,8 @@ finish:
         free_database(database_user, database_uid);
         free_database(database_group, database_gid);
 
+        free(uid_range);
+
         free(arg_root);
 
         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;