[systemd-commits] 4 commits - .gitignore Makefile.am configure.ac src/machine src/shared src/sysusers src/tmpfiles sysusers.d/Makefile sysusers.d/systemd.conf

Lennart Poettering lennart at kemper.freedesktop.org
Thu Jun 12 14:10:38 PDT 2014


 .gitignore              |    1 
 Makefile.am             |   24 
 configure.ac            |    9 
 src/machine/machine.c   |   15 
 src/shared/copy.c       |    6 
 src/shared/copy.h       |    1 
 src/shared/util.c       |   21 
 src/shared/util.h       |    2 
 src/sysusers/Makefile   |    1 
 src/sysusers/sysusers.c | 1378 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/tmpfiles/tmpfiles.c |   12 
 sysusers.d/Makefile     |    1 
 sysusers.d/systemd.conf |   45 +
 13 files changed, 1495 insertions(+), 21 deletions(-)

New commits:
commit 7ec9fb4be987164b1622a4de2c6c96c52bd87b35
Author: Lennart Poettering <lennart at poettering.net>
Date:   Thu Jun 12 23:08:51 2014 +0200

    sysusers: add new input group to default snippet

diff --git a/sysusers.d/systemd.conf b/sysusers.d/systemd.conf
index c44eaac..76d6a37 100644
--- a/sysusers.d/systemd.conf
+++ b/sysusers.d/systemd.conf
@@ -28,6 +28,7 @@ g audio                         -               -
 g cdrom                         -               -
 g dialout                       -               -
 g disk                          -               -
+g input                         -               -
 g lp                            -               -
 g tape                          -               -
 g video                         -               -

commit 753615e85d990077c303ae4a42a53e792ffb12ca
Author: Lennart Poettering <lennart at poettering.net>
Date:   Thu Jun 12 23:07:17 2014 +0200

    tmpfiles: minor modernizations

diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index bb12dd0..f515fe8 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -1021,7 +1021,9 @@ static int process_item(Item *i) {
 }
 
 static void item_free(Item *i) {
-        assert(i);
+
+        if (!i)
+                return;
 
         free(i->path);
         free(i->argument);
@@ -1150,7 +1152,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                 }
         }
 
-        switch(type) {
+        switch (type) {
 
         case CREATE_FILE:
         case TRUNCATE_FILE:
@@ -1214,7 +1216,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
         }
 
         default:
-                log_error("[%s:%u] Unknown file type '%c'.", fname, line, type);
+                log_error("[%s:%u] Unknown command type '%c'.", fname, line, type);
                 return -EBADMSG;
         }
 
@@ -1413,9 +1415,11 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_ROOT:
+                        free(arg_root);
                         arg_root = path_make_absolute_cwd(optarg);
                         if (!arg_root)
                                 return log_oom();
+
                         path_kill_slashes(arg_root);
                         break;
 
@@ -1513,7 +1517,7 @@ int main(int argc, char *argv[]) {
 
         r = parse_argv(argc, argv);
         if (r <= 0)
-                return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+                goto finish;
 
         log_set_target(LOG_TARGET_AUTO);
         log_parse_environment();

commit 034753ac13a9d4b308eee1e8d7c3285f6646c0d8
Author: Lennart Poettering <lennart at poettering.net>
Date:   Thu Jun 12 23:06:56 2014 +0200

    machine: minor modernizations

diff --git a/src/machine/machine.c b/src/machine/machine.c
index a49cf81..0b0d45b 100644
--- a/src/machine/machine.c
+++ b/src/machine/machine.c
@@ -176,12 +176,13 @@ int machine_save(Machine *m) {
                         m->timestamp.realtime,
                         m->timestamp.monotonic);
 
-        fflush(f);
+        r = fflush_and_check(f);
+        if (r < 0)
+                goto finish;
 
-        if (ferror(f) || rename(temp_path, m->state_file) < 0) {
+        if (rename(temp_path, m->state_file) < 0) {
                 r = -errno;
-                unlink(m->state_file);
-                unlink(temp_path);
+                goto finish;
         }
 
         if (m->unit) {
@@ -195,8 +196,12 @@ int machine_save(Machine *m) {
         }
 
 finish:
-        if (r < 0)
+        if (r < 0) {
+                if (temp_path)
+                        unlink(temp_path);
+
                 log_error("Failed to save machine data %s: %s", m->state_file, strerror(-r));
+        }
 
         return r;
 }

commit 1b99214789101976d6bbf75c351279584b071998
Author: Lennart Poettering <lennart at poettering.net>
Date:   Thu Jun 12 22:54:02 2014 +0200

    sysusers: add minimal tool to reconstruct /etc/passwd and /etc/group from static files
    
    systemd-sysusers is a tool to reconstruct /etc/passwd and /etc/group
    from static definition files that take a lot of inspiration from
    tmpfiles snippets. These snippets should carry information about system
    users only. To make sure it is not misused for normal users these
    snippets only allow configuring UID and gecos field for each user, but
    do not allow configuration of the home directory or shell, which is
    necessary for real login users.
    
    The purpose of this tool is to enable state-less systems that can
    populate /etc with the minimal files necessary, solely from static data
    in /usr. systemd-sysuser is additive only, and will never override
    existing users.
    
    This tool will create these files directly, and not via some user
    database abtsraction layer. This is appropriate as this tool is supposed
    to run really early at boot, and is only useful for creating system
    users, and system users cannot be stored in remote databases anyway.
    
    The tool is also useful to be invoked from RPM scriptlets, instead of
    useradd. This allows moving from imperative user descriptions in RPM to
    declarative descriptions.
    
    The UID/GID for a user/group to be created can either be chosen dynamic,
    or fixed, or be read from the owner of a file in the file system, in
    order to support reconstructing the correct IDs for files that shall be
    owned by them.
    
    This also adds a minimal user definition file, that should be
    sufficient for most basic systems. Distributions are expected to patch
    these files and augment the contents, for example with fixed UIDs for
    the users where that's necessary.

diff --git a/.gitignore b/.gitignore
index 061b4af..cdb2ac9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -101,6 +101,7 @@
 /systemd-socket-proxyd
 /systemd-sysctl
 /systemd-system-update-generator
+/systemd-sysusers
 /systemd-sysv-generator
 /systemd-timedated
 /systemd-timesyncd
diff --git a/Makefile.am b/Makefile.am
index 959c121..7338868 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -88,6 +88,7 @@ pkgsysconfdir=$(sysconfdir)/systemd
 userunitdir=$(prefix)/lib/systemd/user
 userpresetdir=$(prefix)/lib/systemd/user-preset
 tmpfilesdir=$(prefix)/lib/tmpfiles.d
+sysusersdir=$(prefix)/lib/sysusers.d
 sysctldir=$(prefix)/lib/sysctl.d
 networkdir=$(rootprefix)/lib/systemd/network
 pkgincludedir=$(includedir)/systemd
@@ -1757,6 +1758,28 @@ EXTRA_DIST += \
 	units/systemd-tmpfiles-clean.service.in
 
 # ------------------------------------------------------------------------------
+if ENABLE_SYSUSERS
+systemd_sysusers_SOURCES = \
+	src/sysusers/sysusers.c
+
+systemd_sysusers_LDADD = \
+	libsystemd-units.la \
+	libsystemd-label.la \
+	libsystemd-capability.la \
+	libsystemd-internal.la \
+	libsystemd-shared.la
+
+rootbin_PROGRAMS += \
+	systemd-sysusers
+
+dist_sysusers_DATA = \
+	sysusers.d/systemd.conf
+
+INSTALL_DIRS += \
+	$(sysusersdir)
+endif
+
+# ------------------------------------------------------------------------------
 systemd_machine_id_setup_SOURCES = \
 	src/machine-id-setup/machine-id-setup-main.c \
 	src/core/machine-id-setup.c \
@@ -4879,6 +4902,7 @@ substitutions = \
        '|udevrulesdir=$(udevrulesdir)|' \
        '|catalogdir=$(catalogdir)|' \
        '|tmpfilesdir=$(tmpfilesdir)|' \
+       '|sysusersdir=$(sysusersdir)|' \
        '|sysctldir=$(sysctldir)|' \
        '|systemgeneratordir=$(systemgeneratordir)|' \
        '|usergeneratordir=$(usergeneratordir)|' \
diff --git a/configure.ac b/configure.ac
index faf7f72..c68c759 100644
--- a/configure.ac
+++ b/configure.ac
@@ -723,6 +723,14 @@ fi
 AM_CONDITIONAL(ENABLE_TMPFILES, [test "$have_tmpfiles" = "yes"])
 
 # ------------------------------------------------------------------------------
+have_sysusers=no
+AC_ARG_ENABLE(sysusers, AS_HELP_STRING([--disable-sysusers], [disable sysusers support]))
+if test "x$enable_sysusers" != "xno"; then
+        have_sysusers=yes
+fi
+AM_CONDITIONAL(ENABLE_SYSUSERS, [test "$have_sysusers" = "yes"])
+
+# ------------------------------------------------------------------------------
 have_randomseed=no
 AC_ARG_ENABLE(randomseed, AS_HELP_STRING([--disable-randomseed], [disable randomseed tools]))
 if test "x$enable_randomseed" != "xno"; then
@@ -1166,6 +1174,7 @@ AC_MSG_RESULT([
         bootchart:               ${have_bootchart}
         quotacheck:              ${have_quotacheck}
         tmpfiles:                ${have_tmpfiles}
+        sysusers:                ${have_sysusers}
         randomseed:              ${have_randomseed}
         backlight:               ${have_backlight}
         rfkill:                  ${have_rfkill}
diff --git a/src/shared/copy.c b/src/shared/copy.c
index 4dfc2f3..4c227c8 100644
--- a/src/shared/copy.c
+++ b/src/shared/copy.c
@@ -22,7 +22,7 @@
 #include "util.h"
 #include "copy.h"
 
-static int stream_bytes(int fdf, int fdt) {
+int copy_bytes(int fdf, int fdt) {
         assert(fdf >= 0);
         assert(fdt >= 0);
 
@@ -92,7 +92,7 @@ static int fd_copy_regular(int df, const char *from, const struct stat *st, int
                 return -errno;
         }
 
-        r = stream_bytes(fdf, fdt);
+        r = copy_bytes(fdf, fdt);
         if (r < 0) {
                 unlinkat(dt, to, 0);
                 return r;
@@ -273,7 +273,7 @@ int copy_file(const char *from, const char *to, int flags, mode_t mode) {
         if (fdt < 0)
                 return -errno;
 
-        r = stream_bytes(fdf, fdt);
+        r = copy_bytes(fdf, fdt);
         if (r < 0) {
                 unlink(to);
                 return r;
diff --git a/src/shared/copy.h b/src/shared/copy.h
index 5b56954..8fb057f 100644
--- a/src/shared/copy.h
+++ b/src/shared/copy.h
@@ -23,3 +23,4 @@
 
 int copy_file(const char *from, const char *to, int flags, mode_t mode);
 int copy_tree(const char *from, const char *to);
+int copy_bytes(int fdf, int fdt);
diff --git a/src/shared/util.c b/src/shared/util.c
index 91cbf20..a7aec5c 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -4007,24 +4007,16 @@ int fd_wait_for_event(int fd, int event, usec_t t) {
 int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
         FILE *f;
         char *t;
-        const char *fn;
-        size_t k;
         int fd;
 
         assert(path);
         assert(_f);
         assert(_temp_path);
 
-        t = new(char, strlen(path) + 1 + 6 + 1);
+        t = strappend(path, ".XXXXXX");
         if (!t)
                 return -ENOMEM;
 
-        fn = basename(path);
-        k = fn - path;
-        memcpy(t, path, k);
-        t[k] = '.';
-        stpcpy(stpcpy(t+k+1, fn), "XXXXXX");
-
         fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC);
         if (fd < 0) {
                 free(t);
@@ -6665,3 +6657,14 @@ int bind_remount_recursive(const char *prefix, bool ro) {
                 }
         }
 }
+
+int fflush_and_check(FILE *f) {
+
+        errno = 0;
+        fflush(f);
+
+        if (ferror(f))
+                return errno ? -errno : -EIO;
+
+        return 0;
+}
diff --git a/src/shared/util.h b/src/shared/util.h
index 0f8c393..1796014 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -946,3 +946,5 @@ int update_reboot_param_file(const char *param);
 int umount_recursive(const char *target, int flags);
 
 int bind_remount_recursive(const char *prefix, bool ro);
+
+int fflush_and_check(FILE *f);
diff --git a/src/sysusers/Makefile b/src/sysusers/Makefile
new file mode 120000
index 0000000..d0b0e8e
--- /dev/null
+++ b/src/sysusers/Makefile
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c
new file mode 100644
index 0000000..53ff950
--- /dev/null
+++ b/src/sysusers/sysusers.c
@@ -0,0 +1,1378 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  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 <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <shadow.h>
+#include <getopt.h>
+
+#include "util.h"
+#include "hashmap.h"
+#include "specifier.h"
+#include "path-util.h"
+#include "build.h"
+#include "strv.h"
+#include "conf-files.h"
+#include "copy.h"
+#include "utf8.h"
+
+typedef enum ItemType {
+        ADD_USER = 'u',
+        ADD_GROUP = 'g',
+} ItemType;
+typedef struct Item {
+        ItemType type;
+
+        char *name;
+        char *uid_path;
+        char *gid_path;
+        char *description;
+
+        gid_t gid;
+        uid_t uid;
+
+        bool gid_set:1;
+        bool uid_set:1;
+
+        bool todo:1;
+} Item;
+
+static char *arg_root = NULL;
+
+static const char conf_file_dirs[] =
+        "/usr/local/lib/sysusers.d\0"
+        "/usr/lib/sysusers.d\0"
+#ifdef HAVE_SPLIT_USR
+        "/lib/sysusers.d\0"
+#endif
+        ;
+
+static Hashmap *users = NULL, *groups = NULL;
+static Hashmap *todo_uids = NULL, *todo_gids = NULL;
+
+static Hashmap *database_uid = NULL, *database_user = NULL;
+static Hashmap *database_gid = NULL, *database_group = NULL;
+
+static uid_t search_uid = SYSTEM_UID_MAX;
+static gid_t search_gid = SYSTEM_GID_MAX;
+
+#define UID_TO_PTR(u) (ULONG_TO_PTR(u+1))
+#define PTR_TO_UID(u) ((uid_t) (PTR_TO_ULONG(u)-1))
+
+#define GID_TO_PTR(g) (ULONG_TO_PTR(g+1))
+#define PTR_TO_GID(g) ((gid_t) (PTR_TO_ULONG(g)-1))
+
+#define fix_root(x) (arg_root ? strappenda(arg_root, x) : x)
+
+static int load_user_database(void) {
+        _cleanup_fclose_ FILE *f = NULL;
+        const char *passwd_path;
+        struct passwd *pw;
+        int r;
+
+        passwd_path = fix_root("/etc/passwd");
+        f = fopen(passwd_path, "re");
+        if (!f)
+                return errno == ENOENT ? 0 : -errno;
+
+        r = hashmap_ensure_allocated(&database_user, string_hash_func, string_compare_func);
+        if (r < 0)
+                return r;
+
+        r = hashmap_ensure_allocated(&database_uid, trivial_hash_func, trivial_compare_func);
+        if (r < 0)
+                return r;
+
+        errno = 0;
+        while ((pw = fgetpwent(f))) {
+                char *n;
+                int k, q;
+
+                n = strdup(pw->pw_name);
+                if (!n)
+                        return -ENOMEM;
+
+                k = hashmap_put(database_user, n, UID_TO_PTR(pw->pw_uid));
+                if (k < 0 && k != -EEXIST) {
+                        free(n);
+                        return k;
+                }
+
+                q = hashmap_put(database_uid, UID_TO_PTR(pw->pw_uid), n);
+                if (q < 0 && q != -EEXIST) {
+                        if (k < 0)
+                                free(n);
+                        return q;
+                }
+
+                if (q < 0 && k < 0)
+                        free(n);
+
+                errno = 0;
+        }
+        if (!IN_SET(errno, 0, ENOENT))
+                return -errno;
+
+        return 0;
+}
+
+static int load_group_database(void) {
+        _cleanup_fclose_ FILE *f = NULL;
+        const char *group_path;
+        struct group *gr;
+        int r;
+
+        group_path = fix_root("/etc/group");
+        f = fopen(group_path, "re");
+        if (!f)
+                return errno == ENOENT ? 0 : -errno;
+
+        r = hashmap_ensure_allocated(&database_group, string_hash_func, string_compare_func);
+        if (r < 0)
+                return r;
+
+        r = hashmap_ensure_allocated(&database_gid, trivial_hash_func, trivial_compare_func);
+        if (r < 0)
+                return r;
+
+        errno = 0;
+        while ((gr = fgetgrent(f))) {
+                char *n;
+                int k, q;
+
+                n = strdup(gr->gr_name);
+                if (!n)
+                        return -ENOMEM;
+
+                k = hashmap_put(database_group, n, GID_TO_PTR(gr->gr_gid));
+                if (k < 0 && k != -EEXIST) {
+                        free(n);
+                        return k;
+                }
+
+                q = hashmap_put(database_gid, GID_TO_PTR(gr->gr_gid), n);
+                if (q < 0 && q != -EEXIST) {
+                        if (k < 0)
+                                free(n);
+                        return q;
+                }
+
+                if (q < 0 && k < 0)
+                        free(n);
+
+                errno = 0;
+        }
+        if (!IN_SET(errno, 0, ENOENT))
+                return -errno;
+
+        return 0;
+}
+
+static int make_backup(const char *x) {
+        _cleanup_close_ int src = -1, dst = -1;
+        char *backup, *temp;
+        struct timespec ts[2];
+        struct stat st;
+        int r;
+
+        src = open(x, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        if (src < 0) {
+                if (errno == ENOENT) /* No backup necessary... */
+                        return 0;
+
+                return -errno;
+        }
+
+        if (fstat(src, &st) < 0)
+                return -errno;
+
+        temp = strappenda(x, ".XXXXXX");
+        dst = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC|O_NOCTTY);
+        if (dst < 0)
+                return dst;
+
+        r = copy_bytes(src, dst);
+        if (r < 0)
+                goto fail;
+
+        /* Copy over the access mask */
+        if (fchmod(dst, st.st_mode & 07777) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        /* Don't fail on chmod(). If it stays owned by us, then it
+         * isn't too bad... */
+        fchown(dst, st.st_uid, st.st_gid);
+
+        ts[0] = st.st_atim;
+        ts[1] = st.st_mtim;
+        futimens(dst, ts);
+
+        backup = strappenda(x, "-");
+        if (rename(temp, backup) < 0)
+                goto fail;
+
+        return 0;
+
+fail:
+        unlink(temp);
+        return r;
+}
+
+static int write_files(void) {
+
+        _cleanup_fclose_ FILE *passwd = NULL, *group = NULL;
+        _cleanup_free_ char *passwd_tmp = NULL, *group_tmp = NULL;
+        const char *passwd_path = NULL, *group_path = NULL;
+        Iterator iterator;
+        Item *i;
+        int r;
+
+        /* We don't patch /etc/shadow or /etc/gshadow here, since we
+         * only create user accounts without passwords anyway. */
+
+        if (hashmap_size(todo_gids) > 0) {
+                _cleanup_fclose_ FILE *original = NULL;
+
+                group_path = fix_root("/etc/group");
+                r = fopen_temporary(group_path, &group, &group_tmp);
+                if (r < 0)
+                        goto finish;
+
+                if (fchmod(fileno(group), 0644) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                original = fopen(group_path, "re");
+                if (original) {
+                        struct group *gr;
+
+                        errno = 0;
+                        while ((gr = fgetgrent(original))) {
+                                /* 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
+                                 * step that we don't generate
+                                 * duplicate entries. */
+
+                                i = hashmap_get(groups, gr->gr_name);
+                                if (i && i->todo) {
+                                        r = -EEXIST;
+                                        goto finish;
+                                }
+
+                                if (hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid))) {
+                                        r = -EEXIST;
+                                        goto finish;
+                                }
+
+                                if (putgrent(gr, group) < 0) {
+                                        r = -errno;
+                                        goto finish;
+                                }
+
+                                errno = 0;
+                        }
+                        if (!IN_SET(errno, 0, ENOENT)) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                } else if (errno != ENOENT) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                HASHMAP_FOREACH(i, todo_gids, iterator) {
+                        struct group n = {
+                                .gr_name = i->name,
+                                .gr_gid = i->gid,
+                                .gr_passwd = (char*) "x",
+                        };
+
+                        if (putgrent(&n, group) < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+                }
+
+                r = fflush_and_check(group);
+                if (r < 0)
+                        goto finish;
+        }
+
+        if (hashmap_size(todo_uids) > 0) {
+                _cleanup_fclose_ FILE *original = NULL;
+
+                passwd_path = fix_root("/etc/passwd");
+                r = fopen_temporary(passwd_path, &passwd, &passwd_tmp);
+                if (r < 0)
+                        goto finish;
+
+                if (fchmod(fileno(passwd), 0644) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                original = fopen(passwd_path, "re");
+                if (original) {
+                        struct passwd *pw;
+
+                        errno = 0;
+                        while ((pw = fgetpwent(original))) {
+
+                                i = hashmap_get(users, pw->pw_name);
+                                if (i && i->todo) {
+                                        r = -EEXIST;
+                                        goto finish;
+                                }
+
+                                if (hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid))) {
+                                        r = -EEXIST;
+                                        goto finish;
+                                }
+
+                                if (putpwent(pw, passwd) < 0) {
+                                        r = -errno;
+                                        goto finish;
+                                }
+
+                                errno = 0;
+                        }
+                        if (!IN_SET(errno, 0, ENOENT)) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                } else if (errno != ENOENT) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                HASHMAP_FOREACH(i, todo_uids, iterator) {
+                        struct passwd n = {
+                                .pw_name = i->name,
+                                .pw_uid = i->uid,
+                                .pw_gid = i->gid,
+                                .pw_gecos = i->description,
+                                .pw_passwd = (char*) "x",
+                        };
+
+                        /* Initialize the home directory and the shell
+                         * to nologin, with one exception: for root we
+                         * patch in something special */
+                        if (i->uid == 0) {
+                                n.pw_shell = (char*) "/bin/sh";
+                                n.pw_dir = (char*) "/root";
+                        } else {
+                                n.pw_shell = (char*) "/sbin/nologin";
+                                n.pw_dir = (char*) "/";
+                        }
+
+                        if (putpwent(&n, passwd) < 0) {
+                                r = -r;
+                                goto finish;
+                        }
+                }
+
+                r = fflush_and_check(passwd);
+                if (r < 0)
+                        goto finish;
+        }
+
+        /* Make a backup of the old files */
+        if (group) {
+                r = make_backup(group_path);
+                if (r < 0)
+                        goto finish;
+        }
+
+        if (passwd) {
+                r = make_backup(passwd_path);
+                if (r < 0)
+                        goto finish;
+        }
+
+        /* And make the new files count */
+        if (group) {
+                if (rename(group_tmp, group_path) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                free(group_tmp);
+                group_tmp = NULL;
+        }
+
+        if (passwd) {
+                if (rename(passwd_tmp, passwd_path) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                free(passwd_tmp);
+                passwd_tmp = NULL;
+        }
+
+        return 0;
+
+finish:
+        if (r < 0) {
+                if (passwd_tmp)
+                        unlink(passwd_tmp);
+                if (group_tmp)
+                        unlink(group_tmp);
+        }
+
+        return r;
+}
+
+static int uid_is_ok(uid_t uid, const char *name) {
+        struct passwd *p;
+        struct group *g;
+        const char *n;
+        Item *i;
+
+        /* Let's see if we already have assigned the UID a second time */
+        if (hashmap_get(todo_uids, UID_TO_PTR(uid)))
+                return 0;
+
+        /* Try to avoid using uids that are already used by a group
+         * that doesn't have the same name as our new user. */
+        i = hashmap_get(todo_gids, GID_TO_PTR(uid));
+        if (i && !streq(i->name, name))
+                return 0;
+
+        /* Let's check the files directly */
+        if (hashmap_contains(database_uid, UID_TO_PTR(uid)))
+                return 0;
+
+        n = hashmap_get(database_gid, GID_TO_PTR(uid));
+        if (n && !streq(n, name))
+                return 0;
+
+        /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
+        if (!arg_root) {
+                errno = 0;
+                p = getpwuid(uid);
+                if (p)
+                        return 0;
+                if (errno != 0)
+                        return -errno;
+
+                errno = 0;
+                g = getgrgid((gid_t) uid);
+                if (g) {
+                        if (!streq(g->gr_name, name))
+                                return 0;
+                } else if (errno != 0)
+                        return -errno;
+        }
+
+        return 1;
+}
+
+static int root_stat(const char *p, struct stat *st) {
+        const char *fix;
+
+        fix = fix_root(p);
+        if (stat(fix, st) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) {
+        struct stat st;
+        bool found_uid = false, found_gid = false;
+        uid_t uid;
+        gid_t gid;
+
+        assert(i);
+
+        /* First, try to get the gid directly */
+        if (_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) {
+                gid = st.st_gid;
+                found_gid = true;
+        }
+
+        /* Then, try to get the uid directly */
+        if ((_uid || (_gid && !found_gid))
+            && i->uid_path
+            && root_stat(i->uid_path, &st) >= 0) {
+
+                uid = st.st_uid;
+                found_uid = true;
+
+                /* If we need the gid, but had no success yet, also derive it from the uid path */
+                if (_gid && !found_gid) {
+                        gid = st.st_gid;
+                        found_gid = true;
+                }
+        }
+
+        /* If that didn't work yet, then let's reuse the gid as uid */
+        if (_uid && !found_uid && i->gid_path) {
+
+                if (found_gid) {
+                        uid = (uid_t) gid;
+                        found_uid = true;
+                } else if (root_stat(i->gid_path, &st) >= 0) {
+                        uid = (uid_t) st.st_gid;
+                        found_uid = true;
+                }
+        }
+
+        if (_uid) {
+                if (!found_uid)
+                        return 0;
+
+                *_uid = uid;
+        }
+
+        if (_gid) {
+                if (!found_gid)
+                        return 0;
+
+                *_gid = gid;
+        }
+
+        return 1;
+}
+
+static int add_user(Item *i) {
+        void *z;
+        int r;
+
+        assert(i);
+
+        /* Check the database directly */
+        z = hashmap_get(database_user, i->name);
+        if (z) {
+                log_debug("User %s already exists.", i->name);
+                i->uid = PTR_TO_GID(z);
+                i->uid_set = true;
+                return 0;
+        }
+
+        if (!arg_root) {
+                struct passwd *p;
+                struct spwd *sp;
+
+                /* Also check NSS */
+                errno = 0;
+                p = getpwnam(i->name);
+                if (p) {
+                        log_debug("User %s already exists.", i->name);
+                        i->uid = p->pw_uid;
+                        i->uid_set = true;
+
+                        free(i->description);
+                        i->description = strdup(p->pw_gecos);
+                        return 0;
+                }
+                if (errno != 0) {
+                        log_error("Failed to check if user %s already exists: %m", i->name);
+                        return -errno;
+                }
+
+                /* And shadow too, just to be sure */
+                errno = 0;
+                sp = getspnam(i->name);
+                if (sp) {
+                        log_error("User %s already exists in shadow database, but not in user database.", i->name);
+                        return -EBADMSG;
+                }
+                if (errno != 0) {
+                        log_error("Failed to check if user %s already exists in shadow database: %m", i->name);
+                        return -errno;
+                }
+        }
+
+        /* Try to use the suggested numeric uid */
+        if (i->uid_set) {
+                r = uid_is_ok(i->uid, i->name);
+                if (r < 0) {
+                        log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
+                        return r;
+                }
+                if (r == 0) {
+                        log_debug("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name);
+                        i->uid_set = false;
+                }
+        }
+
+        /* If that didn't work, try to read it from the specified path */
+        if (!i->uid_set) {
+                uid_t c;
+
+                if (read_id_from_file(i, &c, NULL) > 0) {
+
+                        if (c <= 0 || c > SYSTEM_UID_MAX)
+                                log_debug("User ID " UID_FMT " of file not suitable for %s.", c, i->name);
+                        else {
+                                r = uid_is_ok(c, i->name);
+                                if (r < 0) {
+                                        log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
+                                        return r;
+                                } else if (r > 0) {
+                                        i->uid = c;
+                                        i->uid_set = true;
+                                } else
+                                        log_debug("User ID " UID_FMT " of file for %s is already used.", c, i->name);
+                        }
+                }
+        }
+
+        /* Otherwise try to reuse the group ID */
+        if (!i->uid_set && i->gid_set) {
+                r = uid_is_ok((uid_t) i->gid, i->name);
+                if (r < 0) {
+                        log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
+                        return r;
+                }
+                if (r > 0) {
+                        i->uid = (uid_t) i->gid;
+                        i->uid_set = true;
+                }
+        }
+
+        /* And if that didn't work either, let's try to find a free one */
+        if (!i->uid_set) {
+                for (; search_uid > 0; search_uid--) {
+
+                        r = uid_is_ok(search_uid, i->name);
+                        if (r < 0) {
+                                log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
+                                return r;
+                        } else if (r > 0)
+                                break;
+                }
+
+                if (search_uid <= 0) {
+                        log_error("No free user ID available for %s.", i->name);
+                        return -E2BIG;
+                }
+
+                i->uid_set = true;
+                i->uid = search_uid;
+
+                search_uid--;
+        }
+
+        r = hashmap_ensure_allocated(&todo_uids, trivial_hash_func, trivial_compare_func);
+        if (r < 0)
+                return log_oom();
+
+        r = hashmap_put(todo_uids, UID_TO_PTR(i->uid), i);
+        if (r < 0)
+                return log_oom();
+
+        i->todo = true;
+        log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid);
+
+        return 0;
+}
+
+static int gid_is_ok(gid_t gid) {
+        struct group *g;
+        struct passwd *p;
+
+        if (hashmap_get(todo_gids, GID_TO_PTR(gid)))
+                return 0;
+
+        /* Avoid reusing gids that are already used by a different user */
+        if (hashmap_get(todo_uids, UID_TO_PTR(gid)))
+                return 0;
+
+        if (hashmap_contains(database_gid, GID_TO_PTR(gid)))
+                return 0;
+
+        if (hashmap_contains(database_uid, UID_TO_PTR(gid)))
+                return 0;
+
+        if (!arg_root) {
+                errno = 0;
+                g = getgrgid(gid);
+                if (g)
+                        return 0;
+                if (errno != 0)
+                        return -errno;
+
+                errno = 0;
+                p = getpwuid((uid_t) gid);
+                if (p)
+                        return 0;
+                if (errno != 0)
+                        return -errno;
+        }
+
+        return 1;
+}
+
+static int add_group(Item *i) {
+        void *z;
+        int r;
+
+        assert(i);
+
+        /* Check the database directly */
+        z = hashmap_get(database_group, i->name);
+        if (z) {
+                log_debug("Group %s already exists.", i->name);
+                i->gid = PTR_TO_GID(z);
+                i->gid_set = true;
+                return 0;
+        }
+
+        /* Also check NSS */
+        if (!arg_root) {
+                struct group *g;
+
+                errno = 0;
+                g = getgrnam(i->name);
+                if (g) {
+                        log_debug("Group %s already exists.", i->name);
+                        i->gid = g->gr_gid;
+                        i->gid_set = true;
+                        return 0;
+                }
+                if (errno != 0) {
+                        log_error("Failed to check if group %s already exists: %m", i->name);
+                        return -errno;
+                }
+        }
+
+        /* Try to use the suggested numeric gid */
+        if (i->gid_set) {
+                r = gid_is_ok(i->gid);
+                if (r < 0) {
+                        log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
+                        return r;
+                }
+                if (r == 0) {
+                        log_debug("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name);
+                        i->gid_set = false;
+                }
+        }
+
+        /* Try to reuse the numeric uid, if there's one */
+        if (!i->gid_set && i->uid_set) {
+                r = gid_is_ok((gid_t) i->uid);
+                if (r < 0) {
+                        log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
+                        return r;
+                }
+                if (r > 0) {
+                        i->gid = (gid_t) i->uid;
+                        i->gid_set = true;
+                }
+        }
+
+        /* If that didn't work, try to read it from the specified path */
+        if (!i->gid_set) {
+                gid_t c;
+
+                if (read_id_from_file(i, NULL, &c) > 0) {
+
+                        if (c <= 0 || c > SYSTEM_GID_MAX)
+                                log_debug("Group ID " GID_FMT " of file not suitable for %s.", c, i->name);
+                        else {
+                                r = gid_is_ok(c);
+                                if (r < 0) {
+                                        log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
+                                        return r;
+                                } else if (r > 0) {
+                                        i->gid = c;
+                                        i->gid_set = true;
+                                } else
+                                        log_debug("Group ID " GID_FMT " of file for %s already used.", c, i->name);
+                        }
+                }
+        }
+
+        /* And if that didn't work either, let's try to find a free one */
+        if (!i->gid_set) {
+                for (; search_gid > 0; search_gid--) {
+                        r = gid_is_ok(search_gid);
+                        if (r < 0) {
+                                log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
+                                return r;
+                        } else if (r > 0)
+                                break;
+                }
+
+                if (search_gid <= 0) {
+                        log_error("No free group ID available for %s.", i->name);
+                        return -E2BIG;
+                }
+
+                i->gid_set = true;
+                i->gid = search_gid;
+
+                search_gid--;
+        }
+
+        r = hashmap_ensure_allocated(&todo_gids, trivial_hash_func, trivial_compare_func);
+        if (r < 0)
+                return log_oom();
+
+        r = hashmap_put(todo_gids, GID_TO_PTR(i->gid), i);
+        if (r < 0)
+                return log_oom();
+
+        i->todo = true;
+        log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid);
+
+        return 0;
+}
+
+static int process_item(Item *i) {
+        int r;
+
+        assert(i);
+
+        switch (i->type) {
+
+        case ADD_USER:
+                r = add_group(i);
+                if (r < 0)
+                        return r;
+
+                return add_user(i);
+
+        case ADD_GROUP: {
+                Item *j;
+
+                j = hashmap_get(users, i->name);
+                if (j) {
+                        /* There's already user to be created for this
+                         * name, let's process that in one step */
+
+                        if (i->gid_set) {
+                                j->gid = i->gid;
+                                j->gid_set = true;
+                        }
+
+                        if (i->gid_path) {
+                                free(j->gid_path);
+                                j->gid_path = strdup(i->gid_path);
+                                if (!j->gid_path)
+                                        return log_oom();
+                        }
+
+                        return 0;
+                }
+
+                return add_group(i);
+        }
+        }
+
+        assert_not_reached("Unknown item type");
+}
+
+static void item_free(Item *i) {
+
+        if (!i)
+                return;
+
+        free(i->name);
+        free(i->uid_path);
+        free(i->gid_path);
+        free(i->description);
+        free(i);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
+
+static bool item_equal(Item *a, Item *b) {
+        assert(a);
+        assert(b);
+
+        if (a->type != b->type)
+                return false;
+
+        if (!streq_ptr(a->name, b->name))
+                return false;
+
+        if (!streq_ptr(a->uid_path, b->uid_path))
+                return false;
+
+        if (!streq_ptr(a->gid_path, b->gid_path))
+                return false;
+
+        if (!streq_ptr(a->description, b->description))
+                return false;
+
+        if (a->uid_set != b->uid_set)
+                return false;
+
+        if (a->uid_set && a->uid != b->uid)
+                return false;
+
+        if (a->gid_set != b->gid_set)
+                return false;
+
+        if (a->gid_set && a->gid != b->gid)
+                return false;
+
+        return true;
+}
+
+static bool valid_user_group_name(const char *u) {
+        const char *i;
+        long sz;
+
+        if (isempty(u) < 0)
+                return false;
+
+        if (!(u[0] >= 'a' && u[0] <= 'z') &&
+            !(u[0] >= 'A' && u[0] <= 'Z') &&
+            u[0] != '_')
+                return false;
+
+        for (i = u+1; *i; i++) {
+                if (!(*i >= 'a' && *i <= 'z') &&
+                    !(*i >= 'A' && *i <= 'Z') &&
+                    !(*i >= '0' && *i <= '9') &&
+                    *i != '_' &&
+                    *i != '-')
+                        return false;
+        }
+
+        sz = sysconf(_SC_LOGIN_NAME_MAX);
+        assert_se(sz > 0);
+
+        if ((size_t) (i-u) > (size_t) sz)
+                return false;
+
+        return true;
+}
+
+static bool valid_gecos(const char *d) {
+
+        if (!utf8_is_valid(d))
+                return false;
+
+        if (strpbrk(d, ":\n"))
+                return false;
+
+        return true;
+}
+
+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 },
+                { 'v', specifier_kernel_release, NULL },
+                {}
+        };
+
+        _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL;
+        _cleanup_(item_freep) Item *i = NULL;
+        Item *existing;
+        Hashmap *h;
+        int r, n = -1;
+
+        assert(fname);
+        assert(line >= 1);
+        assert(buffer);
+
+        r = sscanf(buffer,
+                   "%ms %ms %ms %n",
+                   &action,
+                   &name,
+                   &id,
+                   &n);
+        if (r < 2) {
+                log_error("[%s:%u] Syntax error.", fname, line);
+                return -EIO;
+        }
+
+        if (strlen(action) != 1) {
+                log_error("[%s:%u] Unknown modifier '%s'", fname, line, action);
+                return -EINVAL;
+        }
+
+        if (!IN_SET(action[0], ADD_USER, ADD_GROUP)) {
+                log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]);
+                return -EBADMSG;
+        }
+
+        i = new0(Item, 1);
+        if (!i)
+                return log_oom();
+
+        i->type = action[0];
+
+        r = specifier_printf(name, specifier_table, NULL, &i->name);
+        if (r < 0) {
+                log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
+                return r;
+        }
+
+        if (!valid_user_group_name(i->name)) {
+                log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, i->name);
+                return -EINVAL;
+        }
+
+        if (n >= 0) {
+                n += strspn(buffer+n, WHITESPACE);
+                if (buffer[n] != 0 && (buffer[n] != '-' || buffer[n+1] != 0)) {
+                        i->description = unquote(buffer+n, "\"");
+                        if (!i->description)
+                                return log_oom();
+
+                        if (!valid_gecos(i->description)) {
+                                log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, i->description);
+                                return -EINVAL;
+                        }
+                }
+        }
+
+        if (id && !streq(id, "-")) {
+
+                if (path_is_absolute(id)) {
+                        char *p;
+
+                        p = strdup(id);
+                        if (!p)
+                                return log_oom();
+
+                        path_kill_slashes(p);
+
+                        if (i->type == ADD_USER)
+                                i->uid_path = p;
+                        else
+                                i->gid_path = p;
+
+                } else if (i->type == ADD_USER) {
+                        r = parse_uid(id, &i->uid);
+                        if (r < 0) {
+                                log_error("Failed to parse UID: %s", id);
+                                return -EBADMSG;
+                        }
+
+                        i->uid_set = true;
+
+                } else {
+                        assert(i->type == ADD_GROUP);
+
+                        r = parse_gid(id, &i->gid);
+                        if (r < 0) {
+                                log_error("Failed to parse GID: %s", id);
+                                return -EBADMSG;
+                        }
+
+                        i->gid_set = true;
+                }
+        }
+
+        if (i->type == ADD_USER) {
+                r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
+                h = users;
+        } else {
+                assert(i->type == ADD_GROUP);
+                r = hashmap_ensure_allocated(&groups, string_hash_func, string_compare_func);
+                h = groups;
+        }
+        if (r < 0)
+                return log_oom();
+
+        existing = hashmap_get(h, i->name);
+        if (existing) {
+
+                /* Two identical items are fine */
+                if (!item_equal(existing, i))
+                        log_warning("Two or more conflicting lines for %s configured, ignoring.", i->name);
+
+                return 0;
+        }
+
+        r = hashmap_put(h, i->name, i);
+        if (r < 0) {
+                log_error("Failed to insert item %s: %s", i->name, strerror(-r));
+                return r;
+        }
+
+        i = NULL;
+        return 0;
+}
+
+static int read_config_file(const char *fn, bool ignore_enoent) {
+        _cleanup_fclose_ FILE *f = NULL;
+        char line[LINE_MAX];
+        unsigned v = 0;
+        int r;
+
+        assert(fn);
+
+        r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &f);
+        if (r < 0) {
+                if (ignore_enoent && r == -ENOENT)
+                        return 0;
+
+                log_error("Failed to open '%s', ignoring: %s", fn, strerror(-r));
+                return r;
+        }
+
+        FOREACH_LINE(line, f, break) {
+                char *l;
+                int k;
+
+                v++;
+
+                l = strstrip(line);
+                if (*l == '#' || *l == 0)
+                        continue;
+
+                k = parse_line(fn, v, l);
+                if (k < 0 && r == 0)
+                        r = k;
+        }
+
+        if (ferror(f)) {
+                log_error("Failed to read from file %s: %m", fn);
+                if (r == 0)
+                        r = -EIO;
+        }
+
+        return r;
+}
+
+static int take_lock(void) {
+
+        struct flock flock = {
+                .l_type = F_WRLCK,
+                .l_whence = SEEK_SET,
+                .l_start = 0,
+                .l_len = 0,
+        };
+
+        const char *path;
+        int fd, r;
+
+        /* This is roughly the same as lckpwdf(), but not as awful. We
+         * don't want to use alarm() and signals, hence we implement
+         * our own trivial version of this.
+         *
+         * Note that shadow-utils also takes per-database locks in
+         * addition to lckpwdf(). However, we don't given that they
+         * are redundant as they they invoke lckpwdf() first and keep
+         * it during everything they do. The per-database locks are
+         * awfully racy, and thus we just won't do them. */
+
+        path = fix_root("/etc/.pwd.lock");
+        fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
+        if (fd < 0)
+                return -errno;
+
+        r = fcntl(fd, F_SETLKW, &flock);
+        if (r < 0) {
+                safe_close(fd);
+                return -errno;
+        }
+
+        return fd;
+}
+
+static void free_database(Hashmap *by_name, Hashmap *by_id) {
+        char *name;
+
+        for (;;) {
+                name = hashmap_first(by_id);
+                if (!name)
+                        break;
+
+                hashmap_remove(by_name, name);
+
+                hashmap_steal_first_key(by_id);
+                free(name);
+        }
+
+        while ((name = hashmap_steal_first_key(by_name)))
+                free(name);
+
+        hashmap_free(by_name);
+        hashmap_free(by_id);
+}
+
+static int help(void) {
+
+        printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
+               "Creates system user accounts.\n\n"
+               "  -h --help                 Show this help\n"
+               "     --version              Show package version\n"
+               "     --root=PATH            Operate on an alternate filesystem root\n",
+               program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_ROOT,
+        };
+
+        static const struct option options[] = {
+                { "help",    no_argument,       NULL, 'h'         },
+                { "version", no_argument,       NULL, ARG_VERSION },
+                { "root",    required_argument, NULL, ARG_ROOT    },
+                {}
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+
+                switch (c) {
+
+                case 'h':
+                        return help();
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case ARG_ROOT:
+                        free(arg_root);
+                        arg_root = path_make_absolute_cwd(optarg);
+                        if (!arg_root)
+                                return log_oom();
+
+                        path_kill_slashes(arg_root);
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached("Unhandled option");
+                }
+        }
+
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+
+        _cleanup_close_ int lock = -1;
+        Iterator iterator;
+        int r, k;
+        Item *i;
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                goto finish;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        r = 0;
+
+        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, conf_file_dirs);
+                if (r < 0) {
+                        log_error("Failed to enumerate sysusers.d files: %s", strerror(-r));
+                        goto finish;
+                }
+
+                STRV_FOREACH(f, files) {
+                        k = read_config_file(*f, true);
+                        if (k < 0 && r == 0)
+                                r = k;
+                }
+        }
+
+        lock = take_lock();
+        if (lock < 0) {
+                log_error("Failed to take lock: %s", strerror(-lock));
+                goto finish;
+        }
+
+        r = load_user_database();
+        if (r < 0) {
+                log_error("Failed to load user database: %s", strerror(-r));
+                goto finish;
+        }
+
+        r = load_group_database();
+        if (r < 0) {
+                log_error("Failed to read group database: %s", strerror(-r));
+                goto finish;
+        }
+
+        HASHMAP_FOREACH(i, groups, iterator)
+                process_item(i);
+
+        HASHMAP_FOREACH(i, users, iterator)
+                process_item(i);
+
+        r = write_files();
+        if (r < 0)
+                log_error("Failed to write files: %s", strerror(-r));
+
+finish:
+        while ((i = hashmap_steal_first(groups)))
+                item_free(i);
+
+        while ((i = hashmap_steal_first(users)))
+                item_free(i);
+
+        hashmap_free(groups);
+        hashmap_free(users);
+        hashmap_free(todo_uids);
+        hashmap_free(todo_gids);
+
+        free_database(database_user, database_uid);
+        free_database(database_group, database_gid);
+
+        free(arg_root);
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/sysusers.d/Makefile b/sysusers.d/Makefile
new file mode 120000
index 0000000..bd10475
--- /dev/null
+++ b/sysusers.d/Makefile
@@ -0,0 +1 @@
+../src/Makefile
\ No newline at end of file
diff --git a/sysusers.d/systemd.conf b/sysusers.d/systemd.conf
new file mode 100644
index 0000000..c44eaac
--- /dev/null
+++ b/sysusers.d/systemd.conf
@@ -0,0 +1,44 @@
+#  This file is part of systemd.
+#
+#  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.
+
+# The superuser
+u root                          0               "Super User"
+
+# The nobody use for NFS file systems
+u nobody                        65534           "Nobody"
+
+# Administrator group: can *see* more than normal users
+g adm                           -               -
+
+# Administrator group: can *do* more than normal users
+g wheel                         -               -
+
+# Access to certain kernel and userspace facilities
+g kmem                          -               -
+g lock                          -               -
+g tty                           5               -
+g utmp                          -               -
+
+# Hardware access groups
+g audio                         -               -
+g cdrom                         -               -
+g dialout                       -               -
+g disk                          -               -
+g lp                            -               -
+g tape                          -               -
+g video                         -               -
+
+# Default group for normal users
+g users                         -               -
+
+# Users and groups for specific systemd subsystems
+g systemd-journal               -               -
+u systemd-journal-gateway       -               "systemd Journal Gateway"
+u systemd-bus-proxy             -               "systemd Bus Proxy"
+u systemd-network               -               "systemd Network Management"
+u systemd-resolve               -               "systemd Resolver"
+u systemd-timesync              -               "systemd Time Synchronization"



More information about the systemd-commits mailing list