diff --git a/src/shared/offline-passwd.c b/src/shared/offline-passwd.c new file mode 100644 index 0000000000..3f8220d9ac --- /dev/null +++ b/src/shared/offline-passwd.c @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "fd-util.h" +#include "offline-passwd.h" +#include "path-util.h" +#include "user-util.h" + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(uid_gid_hash_ops, char, string_hash_func, string_compare_func, free); + +static int open_passwd_file(const char *root, const char *fname, FILE **ret_file) { + const char *p = prefix_roota(root, fname); + if (!p) + return -ENOMEM; + + FILE *f = fopen(p, "re"); + if (!f) + return -errno; + + log_debug("Reading %s entries from %s...", basename(fname), p); + + *ret_file = f; + return 0; +} + +static int populate_uid_cache(const char *root, Hashmap **ret) { + _cleanup_(hashmap_freep) Hashmap *cache = NULL; + int r; + + cache = hashmap_new(&uid_gid_hash_ops); + if (!cache) + return -ENOMEM; + + /* The directory list is harcoded here: /etc is the standard, and rpm-ostree uses /usr/lib. This + * could be made configurable, but I don't see the point right now. */ + + const char *fname; + FOREACH_STRING(fname, "/etc/passwd", "/usr/lib/passwd") { + _cleanup_fclose_ FILE *f = NULL; + + r = open_passwd_file(root, fname, &f); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + + struct passwd *pw; + while ((r = fgetpwent_sane(f, &pw)) > 0) { + _cleanup_free_ char *n = NULL; + + n = strdup(pw->pw_name); + if (!n) + return -ENOMEM; + + r = hashmap_put(cache, n, UID_TO_PTR(pw->pw_uid)); + if (IN_SET(r, 0 -EEXIST)) + continue; + if (r < 0) + return r; + TAKE_PTR(n); + } + } + + *ret = TAKE_PTR(cache); + return 0; +} + +static int populate_gid_cache(const char *root, Hashmap **ret) { + _cleanup_(hashmap_freep) Hashmap *cache = NULL; + int r; + + cache = hashmap_new(&uid_gid_hash_ops); + if (!cache) + return -ENOMEM; + + const char *fname; + FOREACH_STRING(fname, "/etc/group", "/usr/lib/group") { + _cleanup_fclose_ FILE *f = NULL; + + r = open_passwd_file(root, fname, &f); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + + struct group *gr; + while ((r = fgetgrent_sane(f, &gr)) > 0) { + _cleanup_free_ char *n = NULL; + + n = strdup(gr->gr_name); + if (!n) + return -ENOMEM; + + r = hashmap_put(cache, n, GID_TO_PTR(gr->gr_gid)); + if (IN_SET(r, 0, -EEXIST)) + continue; + if (r < 0) + return r; + TAKE_PTR(n); + } + } + + *ret = TAKE_PTR(cache); + return 0; +} + +int name_to_uid_offline( + const char *root, + const char *user, + uid_t *ret_uid, + Hashmap **cache) { + + void *found; + int r; + + assert(user); + assert(ret_uid); + assert(cache); + + if (!*cache) { + r = populate_uid_cache(root, cache); + if (r < 0) + return r; + } + + found = hashmap_get(*cache, user); + if (!found) + return -ESRCH; + + *ret_uid = PTR_TO_UID(found); + return 0; +} + +int name_to_gid_offline( + const char *root, + const char *group, + gid_t *ret_gid, + Hashmap **cache) { + + void *found; + int r; + + assert(group); + assert(ret_gid); + assert(cache); + + if (!*cache) { + r = populate_gid_cache(root, cache); + if (r < 0) + return r; + } + + found = hashmap_get(*cache, group); + if (!found) + return -ESRCH; + + *ret_gid = PTR_TO_GID(found); + return 0; +} diff --git a/src/tmpfiles/offline-passwd.h b/src/shared/offline-passwd.h similarity index 100% rename from src/tmpfiles/offline-passwd.h rename to src/shared/offline-passwd.h diff --git a/src/test/meson.build b/src/test/meson.build index d3fc803088..4affcd647f 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -301,6 +301,12 @@ tests += [ [], []], + [['src/test/test-offline-passwd.c', + 'src/shared/offline-passwd.c', + 'src/shared/offline-passwd.h'], + [], + []], + [['src/test/test-escape.c'], [], []], diff --git a/src/test/test-offline-passwd.c b/src/test/test-offline-passwd.c new file mode 100644 index 0000000000..5933ec28a3 --- /dev/null +++ b/src/test/test-offline-passwd.c @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include + +#include "offline-passwd.h" +#include "user-util.h" +#include "format-util.h" +#include "tests.h" + +static char *arg_root = NULL; + +static void test_resolve_one(const char *name) { + bool relaxed = name || arg_root; + + if (!name) + name = "root"; + + log_info("/* %s(\"%s\") */", __func__, name); + + _cleanup_(hashmap_freep) Hashmap *uid_cache = NULL, *gid_cache = NULL; + uid_t uid = UID_INVALID; + gid_t gid = GID_INVALID; + int r; + + r = name_to_uid_offline(arg_root, name, &uid, &uid_cache); + log_info_errno(r, "name_to_uid_offline: %s → "UID_FMT": %m", name, uid); + assert_se(relaxed || r == 0); + + r = name_to_uid_offline(arg_root, name, &uid, &uid_cache); + log_info_errno(r, "name_to_uid_offline: %s → "UID_FMT": %m", name, uid); + assert_se(relaxed || r == 0); + + r = name_to_gid_offline(arg_root, name, &gid, &gid_cache); + log_info_errno(r, "name_to_gid_offline: %s → "GID_FMT": %m", name, gid); + assert_se(relaxed || r == 0); + + r = name_to_gid_offline(arg_root, name, &gid, &gid_cache); + log_info_errno(r, "name_to_gid_offline: %s → "GID_FMT": %m", name, gid); + assert_se(relaxed || r == 0); +} + +static int parse_argv(int argc, char *argv[]) { + static const struct option options[] = { + { "root", required_argument, NULL, 'r' }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "r:", options, NULL)) >= 0) + switch(c) { + case 'r': + arg_root = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 0; +} + +int main(int argc, char **argv) { + int r; + + test_setup_logging(LOG_DEBUG); + + r = parse_argv(argc, argv); + if (r < 0) + return r; + + if (optind >= argc) + test_resolve_one(NULL); + else + while (optind < argc) + test_resolve_one(argv[optind++]); + + return 0; +} diff --git a/src/tmpfiles/meson.build b/src/tmpfiles/meson.build index 2f8fb29bd9..434dcf800d 100644 --- a/src/tmpfiles/meson.build +++ b/src/tmpfiles/meson.build @@ -2,6 +2,6 @@ systemd_tmpfiles_sources = [ 'src/tmpfiles/tmpfiles.c', - 'src/tmpfiles/offline-passwd.c', - 'src/tmpfiles/offline-passwd.h', + 'src/shared/offline-passwd.c', + 'src/shared/offline-passwd.h', ] diff --git a/src/tmpfiles/offline-passwd.c b/src/tmpfiles/offline-passwd.c deleted file mode 100644 index 8ac5431c66..0000000000 --- a/src/tmpfiles/offline-passwd.c +++ /dev/null @@ -1,122 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1+ */ - -#include "fd-util.h" -#include "offline-passwd.h" -#include "path-util.h" -#include "user-util.h" - -DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(uid_gid_hash_ops, char, string_hash_func, string_compare_func, free); - -int name_to_uid_offline( - const char *root, - const char *user, - uid_t *ret_uid, - Hashmap **cache) { - - void *found; - int r; - - assert(user); - assert(ret_uid); - assert(cache); - - if (!*cache) { - _cleanup_(hashmap_freep) Hashmap *uid_by_name = NULL; - _cleanup_fclose_ FILE *f = NULL; - struct passwd *pw; - const char *passwd_path; - - passwd_path = prefix_roota(root, "/etc/passwd"); - f = fopen(passwd_path, "re"); - if (!f) - return errno == ENOENT ? -ESRCH : -errno; - - uid_by_name = hashmap_new(&uid_gid_hash_ops); - if (!uid_by_name) - return -ENOMEM; - - while ((r = fgetpwent_sane(f, &pw)) > 0) { - _cleanup_free_ char *n = NULL; - - n = strdup(pw->pw_name); - if (!n) - return -ENOMEM; - - r = hashmap_put(uid_by_name, n, UID_TO_PTR(pw->pw_uid)); - if (r == -EEXIST) { - log_warning_errno(r, "Duplicate entry in %s for %s: %m", passwd_path, pw->pw_name); - continue; - } - if (r < 0) - return r; - - TAKE_PTR(n); - } - - *cache = TAKE_PTR(uid_by_name); - } - - found = hashmap_get(*cache, user); - if (!found) - return -ESRCH; - - *ret_uid = PTR_TO_UID(found); - return 0; -} - -int name_to_gid_offline( - const char *root, - const char *group, - gid_t *ret_gid, - Hashmap **cache) { - - void *found; - int r; - - assert(group); - assert(ret_gid); - assert(cache); - - if (!*cache) { - _cleanup_(hashmap_freep) Hashmap *gid_by_name = NULL; - _cleanup_fclose_ FILE *f = NULL; - struct group *gr; - const char *group_path; - - group_path = prefix_roota(root, "/etc/group"); - f = fopen(group_path, "re"); - if (!f) - return errno == ENOENT ? -ESRCH : -errno; - - gid_by_name = hashmap_new(&uid_gid_hash_ops); - if (!gid_by_name) - return -ENOMEM; - - while ((r = fgetgrent_sane(f, &gr)) > 0) { - _cleanup_free_ char *n = NULL; - - n = strdup(gr->gr_name); - if (!n) - return -ENOMEM; - - r = hashmap_put(gid_by_name, n, GID_TO_PTR(gr->gr_gid)); - if (r == -EEXIST) { - log_warning_errno(r, "Duplicate entry in %s for %s: %m", group_path, gr->gr_name); - continue; - } - if (r < 0) - return r; - - TAKE_PTR(n); - } - - *cache = TAKE_PTR(gid_by_name); - } - - found = hashmap_get(*cache, group); - if (!found) - return -ESRCH; - - *ret_gid = PTR_TO_GID(found); - return 0; -}