chiark / gitweb /
systemd-verify: a simple tool for offline unit verification
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Sun, 20 Jul 2014 21:58:35 +0000 (17:58 -0400)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Sun, 20 Jul 2014 23:48:16 +0000 (19:48 -0400)
This tool will warn about misspelt directives, unknown sections, and
non-executable commands. It will also catch the common mistake of
using Accept=yes with a non-template unit and vice versa.

https://bugs.freedesktop.org/show_bug.cgi?id=56607

.gitignore
Makefile.am
src/core/socket.c
src/core/socket.h
src/verify/Makefile [new symlink]
src/verify/verify.c [new file with mode: 0644]

index 41fff4f0c8a9170ef9515406fe4153d4ab961e84..81bcc1605737f83ae168f9e324fa3f9b5cde12e3 100644 (file)
 /systemd-update-utmp
 /systemd-user-sessions
 /systemd-vconsole-setup
 /systemd-update-utmp
 /systemd-user-sessions
 /systemd-vconsole-setup
+/systemd-verify
 /tags
 /test-architecture
 /test-async
 /tags
 /test-architecture
 /test-async
index 91ecbe4a0bc1d39688cecbbf015a21ed4e835a8b..1e4cfb31f1b03302137f7373b8477e6f03870cb7 100644 (file)
@@ -350,7 +350,8 @@ bin_PROGRAMS = \
        systemd-delta \
        systemd-analyze \
        systemd-run \
        systemd-delta \
        systemd-analyze \
        systemd-run \
-       systemd-path
+       systemd-path \
+       systemd-verify
 
 dist_bin_SCRIPTS = \
        src/kernel-install/kernel-install
 
 dist_bin_SCRIPTS = \
        src/kernel-install/kernel-install
@@ -1224,6 +1225,20 @@ CLEANFILES += \
        src/core/org.freedesktop.systemd1.policy.in
 
 # ------------------------------------------------------------------------------
        src/core/org.freedesktop.systemd1.policy.in
 
 # ------------------------------------------------------------------------------
+
+systemd_verify_SOURCES = \
+       src/verify/verify.c
+
+systemd_verify_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(SECCOMP_CFLAGS)
+
+systemd_verify_LDADD = \
+       libsystemd-core.la \
+       $(RT_LIBS)
+
+# ------------------------------------------------------------------------------
+
 manual_tests += \
        test-ns \
        test-loopback \
 manual_tests += \
        test-ns \
        test-loopback \
index c58a7f03ee9336260a7b2a884cb5547edf9d0a2e..646887d803702fac8c495a27fc233ca44a5e7767 100644 (file)
@@ -180,9 +180,8 @@ static int socket_arm_timer(Socket *s) {
                         socket_dispatch_timer, s);
 }
 
                         socket_dispatch_timer, s);
 }
 
-static int socket_instantiate_service(Socket *s) {
-        _cleanup_free_ char *prefix = NULL;
-        _cleanup_free_ char *name = NULL;
+int socket_instantiate_service(Socket *s) {
+        _cleanup_free_ char *prefix = NULL, *name = NULL;
         int r;
         Unit *u;
 
         int r;
         Unit *u;
 
@@ -193,11 +192,9 @@ static int socket_instantiate_service(Socket *s) {
          * here. For Accept=no this is mostly a NOP since the service
          * is figured out at load time anyway. */
 
          * here. For Accept=no this is mostly a NOP since the service
          * is figured out at load time anyway. */
 
-        if (UNIT_DEREF(s->service))
+        if (UNIT_DEREF(s->service) || !s->accept)
                 return 0;
 
                 return 0;
 
-        assert(s->accept);
-
         prefix = unit_name_to_prefix(UNIT(s)->id);
         if (!prefix)
                 return -ENOMEM;
         prefix = unit_name_to_prefix(UNIT(s)->id);
         if (!prefix)
                 return -ENOMEM;
index 39e00deea46339f38f578b1665bcea2ce72a3fe8..814a3bfabc0c2d65dc2c906f21b3840c614b3864 100644 (file)
@@ -183,3 +183,5 @@ const char* socket_result_to_string(SocketResult i) _const_;
 SocketResult socket_result_from_string(const char *s) _pure_;
 
 const char* socket_port_type_to_string(SocketPort *p) _pure_;
 SocketResult socket_result_from_string(const char *s) _pure_;
 
 const char* socket_port_type_to_string(SocketPort *p) _pure_;
+
+int socket_instantiate_service(Socket *s);
diff --git a/src/verify/Makefile b/src/verify/Makefile
new file mode 120000 (symlink)
index 0000000..94aaae2
--- /dev/null
@@ -0,0 +1 @@
+../../Makefile
\ No newline at end of file
diff --git a/src/verify/verify.c b/src/verify/verify.c
new file mode 100644 (file)
index 0000000..fc513b1
--- /dev/null
@@ -0,0 +1,320 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+  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 <stdlib.h>
+#include <getopt.h>
+
+#include "manager.h"
+#include "bus-util.h"
+#include "log.h"
+#include "strv.h"
+#include "build.h"
+
+SystemdRunningAs arg_running_as = SYSTEMD_SYSTEM;
+
+static int generate_path(char **var, char **filenames) {
+        char **filename;
+
+        _cleanup_strv_free_ char **ans = NULL;
+        int r;
+
+        STRV_FOREACH(filename, filenames) {
+                char *t;
+
+                t = dirname_malloc(*filename);
+                if (!t)
+                        return -ENOMEM;
+
+                r = strv_consume(&ans, t);
+                if (r < 0)
+                        return r;
+        }
+
+        assert_se(strv_uniq(ans));
+
+        r = strv_extend(&ans, "");
+        if (r < 0)
+                return r;
+
+        *var = strv_join(ans, ":");
+        if (!*var)
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int verify_socket(Unit *u) {
+        int r;
+
+        assert(u);
+
+        if (u->type != UNIT_SOCKET)
+                return 0;
+
+        /* Cannot run this without the service being around */
+
+        /* This makes sure instance is created if necessary. */
+        r = socket_instantiate_service(SOCKET(u));
+        if (r < 0) {
+                log_error_unit(u->id, "Socket %s cannot be started, failed to create instance.",
+                               u->id);
+                return r;
+        }
+
+        /* This checks both type of sockets */
+        if (UNIT_ISSET(SOCKET(u)->service)) {
+                Service *service;
+
+                service = SERVICE(UNIT_DEREF(SOCKET(u)->service));
+                log_debug_unit(u->id, "%s uses %s", u->id, UNIT(service)->id);
+
+                if (UNIT(service)->load_state != UNIT_LOADED) {
+                        log_error_unit(u->id, "Service %s not loaded, %s cannot be started.",
+                                       UNIT(service)->id, u->id);
+                        return -ENOENT;
+                }
+        }
+
+        return 0;
+}
+
+static int verify_executable(Unit *u, ExecCommand *exec) {
+        if (exec == NULL)
+                return 0;
+
+        if (access(exec->path, X_OK) < 0) {
+                log_error_unit(u->id, "%s: command %s is not executable: %m",
+                               u->id, exec->path);
+                return -errno;
+        }
+
+        return 0;
+}
+
+static int verify_executables(Unit *u) {
+        ExecCommand *exec;
+        int r = 0, k;
+        unsigned i;
+
+        assert(u);
+
+        exec =  u->type == UNIT_SOCKET ? SOCKET(u)->control_command :
+                u->type == UNIT_MOUNT ? MOUNT(u)->control_command :
+                u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL;
+        k = verify_executable(u, exec);
+        if (k < 0 && r == 0)
+                r = k;
+
+        if (u->type == UNIT_SERVICE)
+                for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) {
+                        k = verify_executable(u, SERVICE(u)->exec_command[i]);
+                        if (k < 0 && r == 0)
+                                r = k;
+                }
+
+        if (u->type == UNIT_SOCKET)
+                for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) {
+                        k = verify_executable(u, SOCKET(u)->exec_command[i]);
+                        if (k < 0 && r == 0)
+                                r = k;
+                }
+
+        return r;
+}
+
+static int test_unit(Unit *u) {
+        _cleanup_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
+        Job *j;
+        int r, k;
+
+        assert(u);
+
+        if (log_get_max_level() >= LOG_DEBUG)
+                unit_dump(u, stdout, "\t");
+
+        log_debug_unit(u->id, "Creating %s/start job", u->id);
+        r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, false, &err, &j);
+        if (sd_bus_error_is_set(&err))
+                log_error_unit(u->id, "Error: %s: %s",
+                               err.name, err.message);
+        if (r < 0)
+                log_error_unit(u->id, "Failed to create %s/start: %s",
+                               u->id, strerror(-r));
+
+        k = verify_socket(u);
+        if (k < 0 && r == 0)
+                r = k;
+
+        k = verify_executables(u);
+        if (k < 0 && r == 0)
+                r = k;
+
+        return r;
+}
+
+static int test_units(char **filenames) {
+        _cleanup_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
+        Manager *m = NULL;
+        FILE *serial = NULL;
+        FDSet *fdset = NULL;
+
+        _cleanup_free_ char *var;
+
+        char **filename;
+        int r = 0, k;
+
+        Unit *units[strv_length(filenames)];
+        int i, count = 0;
+
+        /* set the path */
+        r = generate_path(&var, filenames);
+        if (r < 0) {
+                log_error("Failed to generate unit load path: %s", strerror(-r));
+                return r;
+        }
+
+        assert_se(set_unit_path(var) >= 0);
+
+        r = manager_new(arg_running_as, true, &m);
+        if (r < 0) {
+                log_error("Failed to initalize manager: %s", strerror(-r));
+                return r;
+        }
+
+        log_debug("Starting manager...");
+
+        r = manager_startup(m, serial, fdset);
+        if (r < 0) {
+                log_error("Failed to start manager: %s", strerror(-r));
+                goto finish;
+        }
+
+        manager_clear_jobs(m);
+
+        log_debug("Loading remaining units from the command line...");
+
+        STRV_FOREACH(filename, filenames) {
+                log_debug("Handling %s...", *filename);
+
+                k = manager_load_unit(m, NULL, *filename, &err, &units[count]);
+                if (k < 0) {
+                        log_error("Failed to load %s: %s", *filename, strerror(-r));
+                        if (r == 0)
+                                r = k;
+                }
+
+                count ++;
+        }
+
+        for (i = 0; i < count; i++) {
+                k = test_unit(units[i]);
+                if (k < 0 && r == 0)
+                        r = k;
+        }
+
+finish:
+        manager_free(m);
+
+        return r;
+}
+
+static void help(void) {
+        printf("%s [OPTIONS...] {COMMAND} ...\n\n"
+               "Check if unit files can be correctly loaded.\n\n"
+               "  -h --help           Show this help\n"
+               "     --version        Show package version\n"
+               "     --system         Connect to system manager\n"
+               "     --user           Connect to user service manager\n",
+               program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_USER,
+                ARG_SYSTEM,
+        };
+
+        static const struct option options[] = {
+                { "help",                no_argument,       NULL, 'h'                     },
+                { "version",             no_argument,       NULL, ARG_VERSION             },
+                { "user",                no_argument,       NULL, ARG_USER                },
+                { "system",              no_argument,       NULL, ARG_SYSTEM              },
+                {}
+        };
+
+        int c;
+
+        assert(argc >= 1);
+        assert(argv);
+
+        opterr = 0;
+
+        while ((c = getopt_long(argc, argv, ":h", options, NULL)) >= 0)
+                switch (c) {
+
+                case 'h':
+                        help();
+                        return 0;
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case ARG_USER:
+                        arg_running_as = SYSTEMD_USER;
+                        break;
+
+                case ARG_SYSTEM:
+                        arg_running_as = SYSTEMD_SYSTEM;
+                        break;
+
+                case '?':
+                        log_error("Unknown option %s.", argv[optind-1]);
+                        return -EINVAL;
+
+                case ':':
+                        log_error("Missing argument to %s.", argv[optind-1]);
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached("Unhandled option code.");
+                }
+
+        return 1; /* work to do */
+}
+
+int main(int argc, char *argv[]) {
+        int r;
+
+        log_parse_environment();
+        log_open();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                goto finish;
+
+        r = test_units(argv + optind);
+
+finish:
+        return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}