]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #18982 from keszybz/test-nss-users
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 31 Mar 2021 08:32:09 +0000 (10:32 +0200)
committerGitHub <noreply@github.com>
Wed, 31 Mar 2021 08:32:09 +0000 (10:32 +0200)
Add a new test for user/group resolution in nss modules

docs/ENVIRONMENT.md
src/basic/nss-util.h
src/test/meson.build
src/test/nss-test-util.c [new file with mode: 0644]
src/test/nss-test-util.h [new file with mode: 0644]
src/test/test-nss-hosts.c [moved from src/test/test-nss.c with 87% similarity]
src/test/test-nss-users.c [new file with mode: 0644]

index ad2d3ad84b8edef0d3bd2c41a73586bfd9c3bc4a..2cec3bdc1666d6344eb6ba39a1f18afcda9e4eb3 100644 (file)
@@ -198,11 +198,6 @@ All tools:
   prefixed with `:` in which case the kernel command line option takes
   precedence, if it is specified as well.
 
-installed systemd tests:
-
-* `$SYSTEMD_TEST_DATA` — override the location of test data. This is useful if
-  a test executable is moved to an arbitrary location.
-
 `nss-systemd`:
 
 * `$SYSTEMD_NSS_BYPASS_SYNTHETIC=1` — if set, `nss-systemd` won't synthesize
@@ -302,6 +297,14 @@ installed systemd tests:
 * `$SYSTEMD_SYSVRCND_PATH` — Controls where `systemd-sysv-generator` looks for
   SysV init script runlevel link farms.
 
+systemd tests:
+
+* `$SYSTEMD_TEST_DATA` — override the location of test data. This is useful if
+  a test executable is moved to an arbitrary location.
+
+* `$SYSTEMD_TEST_NSS_BUFSIZE` — size of scratch buffers for "reentrant"
+  functions exported by the nss modules.
+
 fuzzers:
 
 * `$SYSTEMD_FUZZ_OUTPUT` — A boolean that specifies whether to write output to
index dfc0d3fb200e64bd4e06d59d1eeed4f09348d385..3c59dcc03c363b5106c4a8624c9b3d9aef65171b 100644 (file)
@@ -213,3 +213,25 @@ typedef enum nss_status (*_nss_gethostbyaddr_r_t)(
                 struct hostent *host,
                 char *buffer, size_t buflen,
                 int *errnop, int *h_errnop);
+
+typedef enum nss_status (*_nss_getpwnam_r_t)(
+                const char *name,
+                struct passwd *pwd,
+                char *buffer, size_t buflen,
+                int *errnop);
+typedef enum nss_status (*_nss_getpwuid_r_t)(
+                uid_t uid,
+                struct passwd *pwd,
+                char *buffer, size_t buflen,
+                int *errnop);
+
+typedef enum nss_status (*_nss_getgrnam_r_t)(
+                const char *name,
+                struct group *gr,
+                char *buffer, size_t buflen,
+                int *errnop);
+typedef enum nss_status (*_nss_getgrgid_r_t)(
+                gid_t gid,
+                struct group *gr,
+                char *buffer, size_t buflen,
+                int *errnop);
index ff40a8d10ddd0452db642b953d53b2aecc4d0485..0488baba82090e61a798311003f5ad4621f72bb6 100644 (file)
@@ -540,7 +540,16 @@ tests += [
         [['src/test/test-gcrypt-util.c'],
          [], [], [], 'HAVE_GCRYPT'],
 
-        [['src/test/test-nss.c'],
+        [['src/test/test-nss-hosts.c',
+          'src/test/nss-test-util.c',
+          'src/test/nss-test-util.h'],
+         [],
+         [libdl],
+         [], 'ENABLE_NSS', 'manual'],
+
+        [['src/test/test-nss-users.c',
+          'src/test/nss-test-util.c',
+          'src/test/nss-test-util.h'],
          [],
          [libdl],
          [], 'ENABLE_NSS', 'manual'],
diff --git a/src/test/nss-test-util.c b/src/test/nss-test-util.c
new file mode 100644 (file)
index 0000000..fc1d724
--- /dev/null
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "nss-test-util.h"
+#include "string-util.h"
+
+const char* nss_status_to_string(enum nss_status status, char *buf, size_t buf_len) {
+        switch (status) {
+        case NSS_STATUS_TRYAGAIN:
+                return "NSS_STATUS_TRYAGAIN";
+        case NSS_STATUS_UNAVAIL:
+                return "NSS_STATUS_UNAVAIL";
+        case NSS_STATUS_NOTFOUND:
+                return "NSS_STATUS_NOTFOUND";
+        case NSS_STATUS_SUCCESS:
+                return "NSS_STATUS_SUCCESS";
+        case NSS_STATUS_RETURN:
+                return "NSS_STATUS_RETURN";
+        default:
+                snprintf(buf, buf_len, "%i", status);
+                return buf;
+        }
+};
+
+void* nss_open_handle(const char *dir, const char *module, int flags) {
+        const char *path = NULL;
+        void *handle;
+
+        if (dir)
+                path = strjoina(dir, "/libnss_", module, ".so.2");
+        if (!path || access(path, F_OK) < 0)
+                path = strjoina("libnss_", module, ".so.2");
+
+        log_debug("Using %s", path);
+        handle = dlopen(path, flags);
+        if (!handle)
+                log_error("Failed to load module %s: %s", module, dlerror());
+        return handle;
+}
diff --git a/src/test/nss-test-util.h b/src/test/nss-test-util.h
new file mode 100644 (file)
index 0000000..f081e64
--- /dev/null
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <nss.h>
+#include <stdint.h>
+
+const char* nss_status_to_string(enum nss_status status, char *buf, size_t buf_len);
+void* nss_open_handle(const char *dir, const char *module, int flags);
similarity index 87%
rename from src/test/test-nss.c
rename to src/test/test-nss-hosts.c
index 2e9414d16d54eea127f5baf5dd34a743bc3f65f7..e9bc6ecce61b6c01d253fb091b9379e39a09b89d 100644 (file)
@@ -1,12 +1,12 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include <dlfcn.h>
 #include <net/if.h>
 #include <stdlib.h>
 #include <unistd.h>
 
 #include "af-list.h"
 #include "alloc-util.h"
+#include "dlfcn-util.h"
 #include "errno-list.h"
 #include "format-util.h"
 #include "hexdecoct.h"
 #include "local-addresses.h"
 #include "log.h"
 #include "main-func.h"
+#include "nss-test-util.h"
 #include "nss-util.h"
+#include "parse-util.h"
 #include "path-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "tests.h"
 
-static const char* nss_status_to_string(enum nss_status status, char *buf, size_t buf_len) {
-        switch (status) {
-        case NSS_STATUS_TRYAGAIN:
-                return "NSS_STATUS_TRYAGAIN";
-        case NSS_STATUS_UNAVAIL:
-                return "NSS_STATUS_UNAVAIL";
-        case NSS_STATUS_NOTFOUND:
-                return "NSS_STATUS_NOTFOUND";
-        case NSS_STATUS_SUCCESS:
-                return "NSS_STATUS_SUCCESS";
-        case NSS_STATUS_RETURN:
-                return "NSS_STATUS_RETURN";
-        default:
-                snprintf(buf, buf_len, "%i", status);
-                return buf;
-        }
-};
+static size_t arg_bufsize = 1024;
 
 static const char* af_to_string(int family, char *buf, size_t buf_len) {
         const char *name;
@@ -54,22 +40,6 @@ static const char* af_to_string(int family, char *buf, size_t buf_len) {
         return buf;
 }
 
-static void* open_handle(const char *dir, const char *module, int flags) {
-        const char *path = NULL;
-        void *handle;
-
-        if (dir)
-                path = strjoina(dir, "/libnss_", module, ".so.2");
-        if (!path || access(path, F_OK) < 0)
-                path = strjoina("libnss_", module, ".so.2");
-
-        log_debug("Using %s", path);
-        handle = dlopen(path, flags);
-        if (!handle)
-                log_error("Failed to load module %s: %s", module, dlerror());
-        return handle;
-}
-
 static int print_gaih_addrtuples(const struct gaih_addrtuple *tuples) {
         int n = 0;
 
@@ -132,7 +102,7 @@ static void print_struct_hostent(struct hostent *host, const char *canon) {
 static void test_gethostbyname4_r(void *handle, const char *module, const char *name) {
         const char *fname;
         _nss_gethostbyname4_r_t f;
-        char buffer[2000];
+        char buffer[arg_bufsize];
         struct gaih_addrtuple *pat = NULL;
         int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */
         int32_t ttl = INT32_MAX; /* nss-dns wants to return the lowest ttl,
@@ -184,7 +154,7 @@ static void test_gethostbyname4_r(void *handle, const char *module, const char *
 static void test_gethostbyname3_r(void *handle, const char *module, const char *name, int af) {
         const char *fname;
         _nss_gethostbyname3_r_t f;
-        char buffer[2000];
+        char buffer[arg_bufsize];
         int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */
         int32_t ttl = INT32_MAX; /* nss-dns wants to return the lowest ttl,
                                     and will access this variable through *ttlp,
@@ -219,7 +189,7 @@ static void test_gethostbyname3_r(void *handle, const char *module, const char *
 static void test_gethostbyname2_r(void *handle, const char *module, const char *name, int af) {
         const char *fname;
         _nss_gethostbyname2_r_t f;
-        char buffer[2000];
+        char buffer[arg_bufsize];
         int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */
         enum nss_status status;
         char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
@@ -247,7 +217,7 @@ static void test_gethostbyname2_r(void *handle, const char *module, const char *
 static void test_gethostbyname_r(void *handle, const char *module, const char *name) {
         const char *fname;
         _nss_gethostbyname_r_t f;
-        char buffer[2000];
+        char buffer[arg_bufsize];
         int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */
         enum nss_status status;
         char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
@@ -278,7 +248,7 @@ static void test_gethostbyaddr2_r(void *handle,
 
         const char *fname;
         _nss_gethostbyaddr2_r_t f;
-        char buffer[2000];
+        char buffer[arg_bufsize];
         int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */
         enum nss_status status;
         char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
@@ -316,7 +286,7 @@ static void test_gethostbyaddr_r(void *handle,
 
         const char *fname;
         _nss_gethostbyaddr_r_t f;
-        char buffer[2000];
+        char buffer[arg_bufsize];
         int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */
         enum nss_status status;
         char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
@@ -392,8 +362,7 @@ static int make_addresses(struct local_address **addresses) {
                 log_info_errno(n, "Failed to query local addresses: %m");
 
         n_alloc = n; /* we _can_ do that */
-        if (!GREEDY_REALLOC(addrs, n_alloc, n + 3))
-                return log_oom();
+        assert_se(GREEDY_REALLOC(addrs, n_alloc, n + 3));
 
         addrs[n++] = (struct local_address) { .family = AF_INET,
                                               .address.in = { htobe32(0x7F000001) } };
@@ -409,15 +378,14 @@ static int test_one_module(const char *dir,
                            char **names,
                            struct local_address *addresses,
                            int n_addresses) {
-        void *handle;
-        char **name;
 
         log_info("======== %s ========", module);
 
-        handle = open_handle(dir, module, RTLD_LAZY|RTLD_NODELETE);
+        _cleanup_(dlclosep) void *handle = nss_open_handle(dir, module, RTLD_LAZY|RTLD_NODELETE);
         if (!handle)
                 return -EINVAL;
 
+        char **name;
         STRV_FOREACH(name, names)
                 test_byname(handle, module, *name);
 
@@ -428,7 +396,6 @@ static int test_one_module(const char *dir,
                             addresses[i].family);
 
         log_info(" ");
-        dlclose(handle);
         return 0;
 }
 
@@ -437,10 +404,18 @@ static int parse_argv(int argc, char **argv,
                       char ***the_names,
                       struct local_address **the_addresses, int *n_addresses) {
 
-        int r, n = 0;
         _cleanup_strv_free_ char **modules = NULL, **names = NULL;
         _cleanup_free_ struct local_address *addrs = NULL;
         size_t n_allocated = 0;
+        const char *p;
+        int r, n = 0;
+
+        p = getenv("SYSTEMD_TEST_NSS_BUFSIZE");
+        if (p) {
+                r = safe_atozu(p, &arg_bufsize);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse $SYSTEMD_TEST_NSS_BUFSIZE");
+        }
 
         if (argc > 1)
                 modules = strv_new(argv[1]);
@@ -456,8 +431,7 @@ static int parse_argv(int argc, char **argv,
                                 "mymachines",
 #endif
                                 "dns");
-        if (!modules)
-                return -ENOMEM;
+        assert_se(modules);
 
         if (argc > 2) {
                 char **name;
@@ -472,8 +446,7 @@ static int parse_argv(int argc, char **argv,
                                 if (r < 0)
                                         return r;
                         } else {
-                                if (!GREEDY_REALLOC0(addrs, n_allocated, n + 1))
-                                        return -ENOMEM;
+                                assert_se(GREEDY_REALLOC0(addrs, n_allocated, n + 1));
 
                                 addrs[n++] = (struct local_address) { .family = family,
                                                                       .address = address };
@@ -481,26 +454,18 @@ static int parse_argv(int argc, char **argv,
                 }
         } else {
                 _cleanup_free_ char *hostname;
+                assert_se(hostname = gethostname_malloc());
 
-                hostname = gethostname_malloc();
-                if (!hostname)
-                        return -ENOMEM;
-
-                names = strv_new("localhost", "_gateway", "foo_no_such_host", hostname);
-                if (!names)
-                        return -ENOMEM;
+                assert_se(names = strv_new("localhost", "_gateway", "foo_no_such_host", hostname));
 
                 n = make_addresses(&addrs);
-                if (n < 0)
-                        return n;
+                assert_se(n >= 0);
         }
 
-        *the_modules = modules;
-        *the_names = names;
-        modules = names = NULL;
-        *the_addresses = addrs;
+        *the_modules = TAKE_PTR(modules);
+        *the_names = TAKE_PTR(names);
+        *the_addresses = TAKE_PTR(addrs);
         *n_addresses = n;
-        addrs = NULL;
         return 0;
 }
 
@@ -515,14 +480,10 @@ static int run(int argc, char **argv) {
         test_setup_logging(LOG_INFO);
 
         r = parse_argv(argc, argv, &modules, &names, &addresses, &n_addresses);
-        if (r < 0) {
-                log_error_errno(r, "Failed to parse arguments: %m");
-                return EXIT_FAILURE;
-        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse arguments: %m");
 
-        dir = dirname_malloc(argv[0]);
-        if (!dir)
-                return log_oom();
+        assert_se(path_extract_directory(argv[0], &dir) >= 0);
 
         STRV_FOREACH(module, modules) {
                 r = test_one_module(dir, *module, names, addresses, n_addresses);
diff --git a/src/test/test-nss-users.c b/src/test/test-nss-users.c
new file mode 100644 (file)
index 0000000..c415c0c
--- /dev/null
@@ -0,0 +1,258 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <pwd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "dlfcn-util.h"
+#include "errno-list.h"
+#include "format-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "nss-test-util.h"
+#include "nss-util.h"
+#include "path-util.h"
+#include "parse-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "tests.h"
+#include "user-util.h"
+
+static size_t arg_bufsize = 1024;
+
+static void print_struct_passwd(const struct passwd *pwd) {
+        log_info("        \"%s\" / "UID_FMT":"GID_FMT,
+                 pwd->pw_name, pwd->pw_uid, pwd->pw_gid);
+        log_info("        passwd=\"%s\"", pwd->pw_passwd);
+        log_info("        gecos=\"%s\"", pwd->pw_gecos);
+        log_info("        dir=\"%s\"", pwd->pw_dir);
+        log_info("        shell=\"%s\"", pwd->pw_shell);
+}
+
+static void print_struct_group(const struct group *gr) {
+        _cleanup_free_ char *members = NULL;
+
+        log_info("        \"%s\" / "GID_FMT,
+                 gr->gr_name, gr->gr_gid);
+        log_info("        passwd=\"%s\"", gr->gr_passwd);
+
+        assert_se(members = strv_join(gr->gr_mem, ", "));
+        // FIXME: use shell_maybe_quote(SHELL_ESCAPE_EMPTY) when it becomes available
+        log_info("        members=%s", members);
+}
+
+static void test_getpwnam_r(void *handle, const char *module, const char *name) {
+        const char *fname;
+        _nss_getpwnam_r_t f;
+        char buffer[arg_bufsize];
+        int errno1 = 999; /* nss-dns doesn't set those */
+        enum nss_status status;
+        char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
+        struct passwd pwd;
+
+        fname = strjoina("_nss_", module, "_getpwnam_r");
+        f = dlsym(handle, fname);
+        log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f);
+        if (!f) {
+                log_info("%s not defined", fname);
+                return;
+        }
+
+        status = f(name, &pwd, buffer, sizeof buffer, &errno1);
+        log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s",
+                 fname, name,
+                 nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n",
+                 errno1, errno_to_name(errno1) ?: "---");
+        if (status == NSS_STATUS_SUCCESS)
+                print_struct_passwd(&pwd);
+}
+
+static void test_getgrnam_r(void *handle, const char *module, const char *name) {
+        const char *fname;
+        _nss_getgrnam_r_t f;
+        char buffer[arg_bufsize];
+        int errno1 = 999; /* nss-dns doesn't set those */
+        enum nss_status status;
+        char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
+        struct group gr;
+
+        fname = strjoina("_nss_", module, "_getgrnam_r");
+        f = dlsym(handle, fname);
+        log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f);
+        if (!f) {
+                log_info("%s not defined", fname);
+                return;
+        }
+
+        status = f(name, &gr, buffer, sizeof buffer, &errno1);
+        log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s",
+                 fname, name,
+                 nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n",
+                 errno1, errno_to_name(errno1) ?: "---");
+        if (status == NSS_STATUS_SUCCESS)
+                print_struct_group(&gr);
+}
+
+static void test_getpwuid_r(void *handle, const char *module, uid_t uid) {
+        const char *fname;
+        _nss_getpwuid_r_t f;
+        char buffer[arg_bufsize];
+        int errno1 = 999; /* nss-dns doesn't set those */
+        enum nss_status status;
+        char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
+        struct passwd pwd;
+
+        fname = strjoina("_nss_", module, "_getpwuid_r");
+        f = dlsym(handle, fname);
+        log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f);
+        if (!f) {
+                log_info("%s not defined", fname);
+                return;
+        }
+
+        status = f(uid, &pwd, buffer, sizeof buffer, &errno1);
+        log_info("%s("UID_FMT") → status=%s%-20serrno=%d/%s",
+                 fname, uid,
+                 nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n",
+                 errno1, errno_to_name(errno1) ?: "---");
+        if (status == NSS_STATUS_SUCCESS)
+                print_struct_passwd(&pwd);
+}
+
+static void test_getgrgid_r(void *handle, const char *module, gid_t gid) {
+        const char *fname;
+        _nss_getgrgid_r_t f;
+        char buffer[arg_bufsize];
+        int errno1 = 999; /* nss-dns doesn't set those */
+        enum nss_status status;
+        char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
+        struct group gr;
+
+        fname = strjoina("_nss_", module, "_getgrgid_r");
+        f = dlsym(handle, fname);
+        log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f);
+        if (!f) {
+                log_info("%s not defined", fname);
+                return;
+        }
+
+        status = f(gid, &gr, buffer, sizeof buffer, &errno1);
+        log_info("%s("GID_FMT") → status=%s%-20serrno=%d/%s",
+                 fname, gid,
+                 nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n",
+                 errno1, errno_to_name(errno1) ?: "---");
+        if (status == NSS_STATUS_SUCCESS)
+                print_struct_group(&gr);
+}
+
+static void test_byname(void *handle, const char *module, const char *name) {
+        test_getpwnam_r(handle, module, name);
+        test_getgrnam_r(handle, module, name);
+        puts("");
+}
+
+static void test_byuid(void *handle, const char *module, uid_t uid) {
+        test_getpwuid_r(handle, module, uid);
+        test_getgrgid_r(handle, module, uid);
+        puts("");
+}
+
+static int test_one_module(const char *dir,
+                           const char *module,
+                           char **names) {
+
+        log_info("======== %s ========", module);
+
+        _cleanup_(dlclosep) void *handle = nss_open_handle(dir, module, RTLD_LAZY|RTLD_NODELETE);
+        if (!handle)
+                return -EINVAL;
+
+        char **name;
+        STRV_FOREACH(name, names)
+                test_byname(handle, module, *name);
+
+        STRV_FOREACH(name, names) {
+                uid_t uid;
+
+                assert_cc(sizeof(uid_t) == sizeof(uint32_t));
+                /* We use safe_atou32 because we don't want to refuse invalid uids. */
+                if (safe_atou32(*name, &uid) < 0)
+                        continue;
+
+                test_byuid(handle, module, uid);
+        }
+
+        log_info(" ");
+        return 0;
+}
+
+static int parse_argv(int argc, char **argv,
+                      char ***the_modules,
+                      char ***the_names) {
+
+        _cleanup_strv_free_ char **modules = NULL, **names = NULL;
+        const char *p;
+        int r;
+
+        p = getenv("SYSTEMD_TEST_NSS_BUFSIZE");
+        if (p) {
+                r = safe_atozu(p, &arg_bufsize);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse $SYSTEMD_TEST_NSS_BUFSIZE");
+        }
+
+        if (argc > 1)
+                modules = strv_new(argv[1]);
+        else
+                modules = strv_new(
+#if ENABLE_NSS_SYSTEMD
+                                "systemd",
+#endif
+#if ENABLE_NSS_MYMACHINES
+                                "mymachines",
+#endif
+                                "files");
+        assert_se(modules);
+
+        if (argc > 2)
+                names = strv_copy(strv_skip(argv, 2));
+        else
+                names = strv_new("root",
+                                 NOBODY_USER_NAME,
+                                 "foo_no_such_user",
+                                 "0",
+                                 "65534");
+        assert_se(names);
+
+        *the_modules = TAKE_PTR(modules);
+        *the_names = TAKE_PTR(names);
+        return 0;
+}
+
+static int run(int argc, char **argv) {
+        _cleanup_free_ char *dir = NULL;
+        _cleanup_strv_free_ char **modules = NULL, **names = NULL;
+        char **module;
+        int r;
+
+        test_setup_logging(LOG_INFO);
+
+        r = parse_argv(argc, argv, &modules, &names);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse arguments: %m");
+
+        assert_se(path_extract_directory(argv[0], &dir) >= 0);
+
+        STRV_FOREACH(module, modules) {
+                r = test_one_module(dir, *module, names);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);