1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <sys/types.h>
30 #include "specifier.h"
31 #include "path-util.h"
34 #include "conf-files.h"
38 typedef enum ItemType {
61 static char *arg_root = NULL;
63 static const char conf_file_dirs[] =
64 "/usr/local/lib/sysusers.d\0"
65 "/usr/lib/sysusers.d\0"
71 static Hashmap *users = NULL, *groups = NULL;
72 static Hashmap *todo_uids = NULL, *todo_gids = NULL;
73 static Hashmap *members = NULL;
75 static Hashmap *database_uid = NULL, *database_user = NULL;
76 static Hashmap *database_gid = NULL, *database_group = NULL;
78 static uid_t search_uid = SYSTEM_UID_MAX;
79 static gid_t search_gid = SYSTEM_GID_MAX;
81 #define UID_TO_PTR(u) (ULONG_TO_PTR(u+1))
82 #define PTR_TO_UID(u) ((uid_t) (PTR_TO_ULONG(u)-1))
84 #define GID_TO_PTR(g) (ULONG_TO_PTR(g+1))
85 #define PTR_TO_GID(g) ((gid_t) (PTR_TO_ULONG(g)-1))
87 #define fix_root(x) (arg_root ? strappenda(arg_root, x) : x)
89 static int load_user_database(void) {
90 _cleanup_fclose_ FILE *f = NULL;
91 const char *passwd_path;
95 passwd_path = fix_root("/etc/passwd");
96 f = fopen(passwd_path, "re");
98 return errno == ENOENT ? 0 : -errno;
100 r = hashmap_ensure_allocated(&database_user, string_hash_func, string_compare_func);
104 r = hashmap_ensure_allocated(&database_uid, trivial_hash_func, trivial_compare_func);
109 while ((pw = fgetpwent(f))) {
113 n = strdup(pw->pw_name);
117 k = hashmap_put(database_user, n, UID_TO_PTR(pw->pw_uid));
118 if (k < 0 && k != -EEXIST) {
123 q = hashmap_put(database_uid, UID_TO_PTR(pw->pw_uid), n);
124 if (q < 0 && q != -EEXIST) {
135 if (!IN_SET(errno, 0, ENOENT))
141 static int load_group_database(void) {
142 _cleanup_fclose_ FILE *f = NULL;
143 const char *group_path;
147 group_path = fix_root("/etc/group");
148 f = fopen(group_path, "re");
150 return errno == ENOENT ? 0 : -errno;
152 r = hashmap_ensure_allocated(&database_group, string_hash_func, string_compare_func);
156 r = hashmap_ensure_allocated(&database_gid, trivial_hash_func, trivial_compare_func);
161 while ((gr = fgetgrent(f))) {
165 n = strdup(gr->gr_name);
169 k = hashmap_put(database_group, n, GID_TO_PTR(gr->gr_gid));
170 if (k < 0 && k != -EEXIST) {
175 q = hashmap_put(database_gid, GID_TO_PTR(gr->gr_gid), n);
176 if (q < 0 && q != -EEXIST) {
187 if (!IN_SET(errno, 0, ENOENT))
193 static int make_backup(const char *x) {
194 _cleanup_close_ int src = -1, dst = -1;
196 struct timespec ts[2];
200 src = open(x, O_RDONLY|O_CLOEXEC|O_NOCTTY);
202 if (errno == ENOENT) /* No backup necessary... */
208 if (fstat(src, &st) < 0)
211 temp = strappenda(x, ".XXXXXX");
212 dst = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC|O_NOCTTY);
216 r = copy_bytes(src, dst, (off_t) -1);
220 /* Copy over the access mask */
221 if (fchmod(dst, st.st_mode & 07777) < 0) {
226 /* Don't fail on chmod(). If it stays owned by us, then it
227 * isn't too bad... */
228 fchown(dst, st.st_uid, st.st_gid);
234 backup = strappenda(x, "-");
235 if (rename(temp, backup) < 0)
245 static int putgrent_with_members(const struct group *gr, FILE *group) {
251 a = hashmap_get(members, gr->gr_name);
253 _cleanup_strv_free_ char **l = NULL;
257 l = strv_copy(gr->gr_mem);
262 if (strv_find(l, *i))
265 if (strv_extend(&l, *i) < 0)
281 if (putgrent(&t, group) != 0)
282 return errno ? -errno : -EIO;
289 if (putgrent(gr, group) != 0)
290 return errno ? -errno : -EIO;
295 static int write_files(void) {
297 _cleanup_fclose_ FILE *passwd = NULL, *group = NULL;
298 _cleanup_free_ char *passwd_tmp = NULL, *group_tmp = NULL;
299 const char *passwd_path = NULL, *group_path = NULL;
300 bool group_changed = false;
305 /* We don't patch /etc/shadow or /etc/gshadow here, since we
306 * only create user accounts without passwords anyway. */
308 if (hashmap_size(todo_gids) > 0 || hashmap_size(members) > 0) {
309 _cleanup_fclose_ FILE *original = NULL;
311 group_path = fix_root("/etc/group");
312 r = fopen_temporary(group_path, &group, &group_tmp);
316 if (fchmod(fileno(group), 0644) < 0) {
321 original = fopen(group_path, "re");
326 while ((gr = fgetgrent(original))) {
327 /* Safety checks against name and GID
328 * collisions. Normally, this should
329 * be unnecessary, but given that we
330 * look at the entries anyway here,
331 * let's make an extra verification
332 * step that we don't generate
333 * duplicate entries. */
335 i = hashmap_get(groups, gr->gr_name);
336 if (i && i->todo_group) {
341 if (hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid))) {
346 r = putgrent_with_members(gr, group);
351 group_changed = true;
355 if (!IN_SET(errno, 0, ENOENT)) {
360 } else if (errno != ENOENT) {
365 HASHMAP_FOREACH(i, todo_gids, iterator) {
369 .gr_passwd = (char*) "x",
372 r = putgrent_with_members(&n, group);
376 group_changed = true;
379 r = fflush_and_check(group);
384 if (hashmap_size(todo_uids) > 0) {
385 _cleanup_fclose_ FILE *original = NULL;
387 passwd_path = fix_root("/etc/passwd");
388 r = fopen_temporary(passwd_path, &passwd, &passwd_tmp);
392 if (fchmod(fileno(passwd), 0644) < 0) {
397 original = fopen(passwd_path, "re");
402 while ((pw = fgetpwent(original))) {
404 i = hashmap_get(users, pw->pw_name);
405 if (i && i->todo_user) {
410 if (hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid))) {
416 if (putpwent(pw, passwd) < 0) {
417 r = errno ? -errno : -EIO;
423 if (!IN_SET(errno, 0, ENOENT)) {
428 } else if (errno != ENOENT) {
433 HASHMAP_FOREACH(i, todo_uids, iterator) {
438 .pw_gecos = i->description,
439 .pw_passwd = (char*) "x",
442 /* Initialize the home directory and the shell
443 * to nologin, with one exception: for root we
444 * patch in something special */
446 n.pw_shell = (char*) "/bin/sh";
447 n.pw_dir = (char*) "/root";
449 n.pw_shell = (char*) "/sbin/nologin";
450 n.pw_dir = (char*) "/";
454 if (putpwent(&n, passwd) != 0) {
455 r = errno ? -errno : -EIO;
460 r = fflush_and_check(passwd);
465 /* Make a backup of the old files */
466 if (group && group_changed) {
467 r = make_backup(group_path);
473 r = make_backup(passwd_path);
478 /* And make the new files count */
479 if (group && group_changed) {
480 if (rename(group_tmp, group_path) < 0) {
490 if (rename(passwd_tmp, passwd_path) < 0) {
510 static int uid_is_ok(uid_t uid, const char *name) {
516 /* Let's see if we already have assigned the UID a second time */
517 if (hashmap_get(todo_uids, UID_TO_PTR(uid)))
520 /* Try to avoid using uids that are already used by a group
521 * that doesn't have the same name as our new user. */
522 i = hashmap_get(todo_gids, GID_TO_PTR(uid));
523 if (i && !streq(i->name, name))
526 /* Let's check the files directly */
527 if (hashmap_contains(database_uid, UID_TO_PTR(uid)))
530 n = hashmap_get(database_gid, GID_TO_PTR(uid));
531 if (n && !streq(n, name))
534 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
540 if (!IN_SET(errno, 0, ENOENT))
544 g = getgrgid((gid_t) uid);
546 if (!streq(g->gr_name, name))
548 } else if (!IN_SET(errno, 0, ENOENT))
555 static int root_stat(const char *p, struct stat *st) {
559 if (stat(fix, st) < 0)
565 static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) {
567 bool found_uid = false, found_gid = false;
573 /* First, try to get the gid directly */
574 if (_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) {
579 /* Then, try to get the uid directly */
580 if ((_uid || (_gid && !found_gid))
582 && root_stat(i->uid_path, &st) >= 0) {
587 /* If we need the gid, but had no success yet, also derive it from the uid path */
588 if (_gid && !found_gid) {
594 /* If that didn't work yet, then let's reuse the gid as uid */
595 if (_uid && !found_uid && i->gid_path) {
600 } else if (root_stat(i->gid_path, &st) >= 0) {
601 uid = (uid_t) st.st_gid;
623 static int add_user(Item *i) {
629 /* Check the database directly */
630 z = hashmap_get(database_user, i->name);
632 log_debug("User %s already exists.", i->name);
633 i->uid = PTR_TO_UID(z);
644 p = getpwnam(i->name);
646 log_debug("User %s already exists.", i->name);
650 free(i->description);
651 i->description = strdup(p->pw_gecos);
654 if (!IN_SET(errno, 0, ENOENT)) {
655 log_error("Failed to check if user %s already exists: %m", i->name);
659 /* And shadow too, just to be sure */
661 sp = getspnam(i->name);
663 log_error("User %s already exists in shadow database, but not in user database.", i->name);
666 if (!IN_SET(errno, 0, ENOENT)) {
667 log_error("Failed to check if user %s already exists in shadow database: %m", i->name);
672 /* Try to use the suggested numeric uid */
674 r = uid_is_ok(i->uid, i->name);
676 log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
680 log_debug("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name);
685 /* If that didn't work, try to read it from the specified path */
689 if (read_id_from_file(i, &c, NULL) > 0) {
691 if (c <= 0 || c > SYSTEM_UID_MAX)
692 log_debug("User ID " UID_FMT " of file not suitable for %s.", c, i->name);
694 r = uid_is_ok(c, i->name);
696 log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
702 log_debug("User ID " UID_FMT " of file for %s is already used.", c, i->name);
707 /* Otherwise try to reuse the group ID */
708 if (!i->uid_set && i->gid_set) {
709 r = uid_is_ok((uid_t) i->gid, i->name);
711 log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
715 i->uid = (uid_t) i->gid;
720 /* And if that didn't work either, let's try to find a free one */
722 for (; search_uid > 0; search_uid--) {
724 r = uid_is_ok(search_uid, i->name);
726 log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
732 if (search_uid <= 0) {
733 log_error("No free user ID available for %s.", i->name);
743 r = hashmap_ensure_allocated(&todo_uids, trivial_hash_func, trivial_compare_func);
747 r = hashmap_put(todo_uids, UID_TO_PTR(i->uid), i);
752 log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid);
757 static int gid_is_ok(gid_t gid) {
761 if (hashmap_get(todo_gids, GID_TO_PTR(gid)))
764 /* Avoid reusing gids that are already used by a different user */
765 if (hashmap_get(todo_uids, UID_TO_PTR(gid)))
768 if (hashmap_contains(database_gid, GID_TO_PTR(gid)))
771 if (hashmap_contains(database_uid, UID_TO_PTR(gid)))
779 if (!IN_SET(errno, 0, ENOENT))
783 p = getpwuid((uid_t) gid);
786 if (!IN_SET(errno, 0, ENOENT))
793 static int add_group(Item *i) {
799 /* Check the database directly */
800 z = hashmap_get(database_group, i->name);
802 log_debug("Group %s already exists.", i->name);
803 i->gid = PTR_TO_GID(z);
813 g = getgrnam(i->name);
815 log_debug("Group %s already exists.", i->name);
820 if (!IN_SET(errno, 0, ENOENT)) {
821 log_error("Failed to check if group %s already exists: %m", i->name);
826 /* Try to use the suggested numeric gid */
828 r = gid_is_ok(i->gid);
830 log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
834 log_debug("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name);
839 /* Try to reuse the numeric uid, if there's one */
840 if (!i->gid_set && i->uid_set) {
841 r = gid_is_ok((gid_t) i->uid);
843 log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
847 i->gid = (gid_t) i->uid;
852 /* If that didn't work, try to read it from the specified path */
856 if (read_id_from_file(i, NULL, &c) > 0) {
858 if (c <= 0 || c > SYSTEM_GID_MAX)
859 log_debug("Group ID " GID_FMT " of file not suitable for %s.", c, i->name);
863 log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
869 log_debug("Group ID " GID_FMT " of file for %s already used.", c, i->name);
874 /* And if that didn't work either, let's try to find a free one */
876 for (; search_gid > 0; search_gid--) {
877 r = gid_is_ok(search_gid);
879 log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
885 if (search_gid <= 0) {
886 log_error("No free group ID available for %s.", i->name);
896 r = hashmap_ensure_allocated(&todo_gids, trivial_hash_func, trivial_compare_func);
900 r = hashmap_put(todo_gids, GID_TO_PTR(i->gid), i);
904 i->todo_group = true;
905 log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid);
910 static int process_item(Item *i) {
927 j = hashmap_get(users, i->name);
929 /* There's already user to be created for this
930 * name, let's process that in one step */
939 j->gid_path = strdup(i->gid_path);
951 assert_not_reached("Unknown item type");
955 static void item_free(Item *i) {
963 free(i->description);
967 DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
969 static int add_implicit(void) {
974 /* Implicitly create additional users and groups, if they were listed in "m" lines */
976 HASHMAP_FOREACH_KEY(l, g, members, iterator) {
980 i = hashmap_get(groups, g);
982 _cleanup_(item_freep) Item *j = NULL;
984 r = hashmap_ensure_allocated(&groups, string_hash_func, string_compare_func);
997 r = hashmap_put(groups, j->name, j);
1001 log_debug("Adding implicit group '%s' due to m line", j->name);
1005 STRV_FOREACH(m, l) {
1007 i = hashmap_get(users, *m);
1009 _cleanup_(item_freep) Item *j = NULL;
1011 r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
1020 j->name = strdup(*m);
1024 r = hashmap_put(users, j->name, j);
1028 log_debug("Adding implicit user '%s' due to m line", j->name);
1037 static bool item_equal(Item *a, Item *b) {
1041 if (a->type != b->type)
1044 if (!streq_ptr(a->name, b->name))
1047 if (!streq_ptr(a->uid_path, b->uid_path))
1050 if (!streq_ptr(a->gid_path, b->gid_path))
1053 if (!streq_ptr(a->description, b->description))
1056 if (a->uid_set != b->uid_set)
1059 if (a->uid_set && a->uid != b->uid)
1062 if (a->gid_set != b->gid_set)
1065 if (a->gid_set && a->gid != b->gid)
1071 static bool valid_user_group_name(const char *u) {
1078 if (!(u[0] >= 'a' && u[0] <= 'z') &&
1079 !(u[0] >= 'A' && u[0] <= 'Z') &&
1083 for (i = u+1; *i; i++) {
1084 if (!(*i >= 'a' && *i <= 'z') &&
1085 !(*i >= 'A' && *i <= 'Z') &&
1086 !(*i >= '0' && *i <= '9') &&
1092 sz = sysconf(_SC_LOGIN_NAME_MAX);
1095 if ((size_t) (i-u) > (size_t) sz)
1101 static bool valid_gecos(const char *d) {
1103 if (!utf8_is_valid(d))
1106 if (strpbrk(d, ":\n"))
1112 static int parse_line(const char *fname, unsigned line, const char *buffer) {
1114 static const Specifier specifier_table[] = {
1115 { 'm', specifier_machine_id, NULL },
1116 { 'b', specifier_boot_id, NULL },
1117 { 'H', specifier_host_name, NULL },
1118 { 'v', specifier_kernel_release, NULL },
1122 _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL;
1123 _cleanup_(item_freep) Item *i = NULL;
1139 log_error("[%s:%u] Syntax error.", fname, line);
1143 if (strlen(action) != 1) {
1144 log_error("[%s:%u] Unknown modifier '%s'", fname, line, action);
1148 if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER)) {
1149 log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]);
1153 r = specifier_printf(name, specifier_table, NULL, &resolved_name);
1155 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
1159 if (!valid_user_group_name(resolved_name)) {
1160 log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name);
1165 n += strspn(buffer+n, WHITESPACE);
1167 if (STR_IN_SET(buffer + n, "", "-"))
1171 switch (action[0]) {
1174 _cleanup_free_ char *resolved_id = NULL;
1177 r = hashmap_ensure_allocated(&members, string_hash_func, string_compare_func);
1181 /* Try to extend an existing member or group item */
1183 if (!id || streq(id, "-")) {
1184 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line);
1188 r = specifier_printf(id, specifier_table, NULL, &resolved_id);
1190 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
1194 if (!valid_user_group_name(resolved_id)) {
1195 log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id);
1200 log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line);
1204 l = hashmap_get(members, resolved_id);
1206 /* A list for this group name already exists, let's append to it */
1207 r = strv_push(&l, resolved_name);
1211 resolved_name = NULL;
1213 assert_se(hashmap_update(members, resolved_id, l) >= 0);
1215 /* No list for this group name exists yet, create one */
1217 l = new0(char *, 2);
1221 l[0] = resolved_name;
1224 r = hashmap_put(members, resolved_id, l);
1230 resolved_id = resolved_name = NULL;
1237 r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
1245 if (id && !streq(id, "-")) {
1247 if (path_is_absolute(id)) {
1248 i->uid_path = strdup(id);
1252 path_kill_slashes(i->uid_path);
1255 r = parse_uid(id, &i->uid);
1257 log_error("Failed to parse UID: %s", id);
1266 i->description = unquote(buffer+n, "\"");
1267 if (!i->description)
1270 if (!valid_gecos(i->description)) {
1271 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, i->description);
1280 r = hashmap_ensure_allocated(&groups, string_hash_func, string_compare_func);
1285 log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line);
1293 if (id && !streq(id, "-")) {
1295 if (path_is_absolute(id)) {
1296 i->gid_path = strdup(id);
1300 path_kill_slashes(i->gid_path);
1302 r = parse_gid(id, &i->gid);
1304 log_error("Failed to parse GID: %s", id);
1317 i->type = action[0];
1318 i->name = resolved_name;
1319 resolved_name = NULL;
1321 existing = hashmap_get(h, i->name);
1324 /* Two identical items are fine */
1325 if (!item_equal(existing, i))
1326 log_warning("Two or more conflicting lines for %s configured, ignoring.", i->name);
1331 r = hashmap_put(h, i->name, i);
1339 static int read_config_file(const char *fn, bool ignore_enoent) {
1340 _cleanup_fclose_ FILE *f = NULL;
1341 char line[LINE_MAX];
1347 r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &f);
1349 if (ignore_enoent && r == -ENOENT)
1352 log_error("Failed to open '%s', ignoring: %s", fn, strerror(-r));
1356 FOREACH_LINE(line, f, break) {
1363 if (*l == '#' || *l == 0)
1366 k = parse_line(fn, v, l);
1367 if (k < 0 && r == 0)
1372 log_error("Failed to read from file %s: %m", fn);
1380 static int take_lock(void) {
1382 struct flock flock = {
1384 .l_whence = SEEK_SET,
1392 /* This is roughly the same as lckpwdf(), but not as awful. We
1393 * don't want to use alarm() and signals, hence we implement
1394 * our own trivial version of this.
1396 * Note that shadow-utils also takes per-database locks in
1397 * addition to lckpwdf(). However, we don't given that they
1398 * are redundant as they they invoke lckpwdf() first and keep
1399 * it during everything they do. The per-database locks are
1400 * awfully racy, and thus we just won't do them. */
1402 path = fix_root("/etc/.pwd.lock");
1403 fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
1407 r = fcntl(fd, F_SETLKW, &flock);
1416 static void free_database(Hashmap *by_name, Hashmap *by_id) {
1420 name = hashmap_first(by_id);
1424 hashmap_remove(by_name, name);
1426 hashmap_steal_first_key(by_id);
1430 while ((name = hashmap_steal_first_key(by_name)))
1433 hashmap_free(by_name);
1434 hashmap_free(by_id);
1437 static int help(void) {
1439 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1440 "Creates system user accounts.\n\n"
1441 " -h --help Show this help\n"
1442 " --version Show package version\n"
1443 " --root=PATH Operate on an alternate filesystem root\n",
1444 program_invocation_short_name);
1449 static int parse_argv(int argc, char *argv[]) {
1452 ARG_VERSION = 0x100,
1456 static const struct option options[] = {
1457 { "help", no_argument, NULL, 'h' },
1458 { "version", no_argument, NULL, ARG_VERSION },
1459 { "root", required_argument, NULL, ARG_ROOT },
1468 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
1476 puts(PACKAGE_STRING);
1477 puts(SYSTEMD_FEATURES);
1482 arg_root = path_make_absolute_cwd(optarg);
1486 path_kill_slashes(arg_root);
1493 assert_not_reached("Unhandled option");
1500 int main(int argc, char *argv[]) {
1502 _cleanup_close_ int lock = -1;
1508 r = parse_argv(argc, argv);
1512 log_set_target(LOG_TARGET_AUTO);
1513 log_parse_environment();
1520 if (optind < argc) {
1523 for (j = optind; j < argc; j++) {
1524 k = read_config_file(argv[j], false);
1525 if (k < 0 && r == 0)
1529 _cleanup_strv_free_ char **files = NULL;
1532 r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs);
1534 log_error("Failed to enumerate sysusers.d files: %s", strerror(-r));
1538 STRV_FOREACH(f, files) {
1539 k = read_config_file(*f, true);
1540 if (k < 0 && r == 0)
1551 log_error("Failed to take lock: %s", strerror(-lock));
1555 r = load_user_database();
1557 log_error("Failed to load user database: %s", strerror(-r));
1561 r = load_group_database();
1563 log_error("Failed to read group database: %s", strerror(-r));
1567 HASHMAP_FOREACH(i, groups, iterator)
1570 HASHMAP_FOREACH(i, users, iterator)
1575 log_error("Failed to write files: %s", strerror(-r));
1578 while ((i = hashmap_steal_first(groups)))
1581 while ((i = hashmap_steal_first(users)))
1584 while ((n = hashmap_first_key(members))) {
1585 strv_free(hashmap_steal_first(members));
1589 hashmap_free(groups);
1590 hashmap_free(users);
1591 hashmap_free(members);
1592 hashmap_free(todo_uids);
1593 hashmap_free(todo_gids);
1595 free_database(database_user, database_uid);
1596 free_database(database_group, database_gid);
1600 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;