chiark / gitweb /
systemd-verify: a simple tool for offline unit verification
[elogind.git] / src / verify / verify.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 Zbigniew JÄ™drzejewski-Szmek
7
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.
12
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.
17
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/>.
20 ***/
21
22 #include <stdlib.h>
23 #include <getopt.h>
24
25 #include "manager.h"
26 #include "bus-util.h"
27 #include "log.h"
28 #include "strv.h"
29 #include "build.h"
30
31 SystemdRunningAs arg_running_as = SYSTEMD_SYSTEM;
32
33 static int generate_path(char **var, char **filenames) {
34         char **filename;
35
36         _cleanup_strv_free_ char **ans = NULL;
37         int r;
38
39         STRV_FOREACH(filename, filenames) {
40                 char *t;
41
42                 t = dirname_malloc(*filename);
43                 if (!t)
44                         return -ENOMEM;
45
46                 r = strv_consume(&ans, t);
47                 if (r < 0)
48                         return r;
49         }
50
51         assert_se(strv_uniq(ans));
52
53         r = strv_extend(&ans, "");
54         if (r < 0)
55                 return r;
56
57         *var = strv_join(ans, ":");
58         if (!*var)
59                 return -ENOMEM;
60
61         return 0;
62 }
63
64 static int verify_socket(Unit *u) {
65         int r;
66
67         assert(u);
68
69         if (u->type != UNIT_SOCKET)
70                 return 0;
71
72         /* Cannot run this without the service being around */
73
74         /* This makes sure instance is created if necessary. */
75         r = socket_instantiate_service(SOCKET(u));
76         if (r < 0) {
77                 log_error_unit(u->id, "Socket %s cannot be started, failed to create instance.",
78                                u->id);
79                 return r;
80         }
81
82         /* This checks both type of sockets */
83         if (UNIT_ISSET(SOCKET(u)->service)) {
84                 Service *service;
85
86                 service = SERVICE(UNIT_DEREF(SOCKET(u)->service));
87                 log_debug_unit(u->id, "%s uses %s", u->id, UNIT(service)->id);
88
89                 if (UNIT(service)->load_state != UNIT_LOADED) {
90                         log_error_unit(u->id, "Service %s not loaded, %s cannot be started.",
91                                        UNIT(service)->id, u->id);
92                         return -ENOENT;
93                 }
94         }
95
96         return 0;
97 }
98
99 static int verify_executable(Unit *u, ExecCommand *exec) {
100         if (exec == NULL)
101                 return 0;
102
103         if (access(exec->path, X_OK) < 0) {
104                 log_error_unit(u->id, "%s: command %s is not executable: %m",
105                                u->id, exec->path);
106                 return -errno;
107         }
108
109         return 0;
110 }
111
112 static int verify_executables(Unit *u) {
113         ExecCommand *exec;
114         int r = 0, k;
115         unsigned i;
116
117         assert(u);
118
119         exec =  u->type == UNIT_SOCKET ? SOCKET(u)->control_command :
120                 u->type == UNIT_MOUNT ? MOUNT(u)->control_command :
121                 u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL;
122         k = verify_executable(u, exec);
123         if (k < 0 && r == 0)
124                 r = k;
125
126         if (u->type == UNIT_SERVICE)
127                 for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) {
128                         k = verify_executable(u, SERVICE(u)->exec_command[i]);
129                         if (k < 0 && r == 0)
130                                 r = k;
131                 }
132
133         if (u->type == UNIT_SOCKET)
134                 for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) {
135                         k = verify_executable(u, SOCKET(u)->exec_command[i]);
136                         if (k < 0 && r == 0)
137                                 r = k;
138                 }
139
140         return r;
141 }
142
143 static int test_unit(Unit *u) {
144         _cleanup_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
145         Job *j;
146         int r, k;
147
148         assert(u);
149
150         if (log_get_max_level() >= LOG_DEBUG)
151                 unit_dump(u, stdout, "\t");
152
153         log_debug_unit(u->id, "Creating %s/start job", u->id);
154         r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, false, &err, &j);
155         if (sd_bus_error_is_set(&err))
156                 log_error_unit(u->id, "Error: %s: %s",
157                                err.name, err.message);
158         if (r < 0)
159                 log_error_unit(u->id, "Failed to create %s/start: %s",
160                                u->id, strerror(-r));
161
162         k = verify_socket(u);
163         if (k < 0 && r == 0)
164                 r = k;
165
166         k = verify_executables(u);
167         if (k < 0 && r == 0)
168                 r = k;
169
170         return r;
171 }
172
173 static int test_units(char **filenames) {
174         _cleanup_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
175         Manager *m = NULL;
176         FILE *serial = NULL;
177         FDSet *fdset = NULL;
178
179         _cleanup_free_ char *var;
180
181         char **filename;
182         int r = 0, k;
183
184         Unit *units[strv_length(filenames)];
185         int i, count = 0;
186
187         /* set the path */
188         r = generate_path(&var, filenames);
189         if (r < 0) {
190                 log_error("Failed to generate unit load path: %s", strerror(-r));
191                 return r;
192         }
193
194         assert_se(set_unit_path(var) >= 0);
195
196         r = manager_new(arg_running_as, true, &m);
197         if (r < 0) {
198                 log_error("Failed to initalize manager: %s", strerror(-r));
199                 return r;
200         }
201
202         log_debug("Starting manager...");
203
204         r = manager_startup(m, serial, fdset);
205         if (r < 0) {
206                 log_error("Failed to start manager: %s", strerror(-r));
207                 goto finish;
208         }
209
210         manager_clear_jobs(m);
211
212         log_debug("Loading remaining units from the command line...");
213
214         STRV_FOREACH(filename, filenames) {
215                 log_debug("Handling %s...", *filename);
216
217                 k = manager_load_unit(m, NULL, *filename, &err, &units[count]);
218                 if (k < 0) {
219                         log_error("Failed to load %s: %s", *filename, strerror(-r));
220                         if (r == 0)
221                                 r = k;
222                 }
223
224                 count ++;
225         }
226
227         for (i = 0; i < count; i++) {
228                 k = test_unit(units[i]);
229                 if (k < 0 && r == 0)
230                         r = k;
231         }
232
233 finish:
234         manager_free(m);
235
236         return r;
237 }
238
239 static void help(void) {
240         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
241                "Check if unit files can be correctly loaded.\n\n"
242                "  -h --help           Show this help\n"
243                "     --version        Show package version\n"
244                "     --system         Connect to system manager\n"
245                "     --user           Connect to user service manager\n",
246                program_invocation_short_name);
247 }
248
249 static int parse_argv(int argc, char *argv[]) {
250         enum {
251                 ARG_VERSION = 0x100,
252                 ARG_USER,
253                 ARG_SYSTEM,
254         };
255
256         static const struct option options[] = {
257                 { "help",                no_argument,       NULL, 'h'                     },
258                 { "version",             no_argument,       NULL, ARG_VERSION             },
259                 { "user",                no_argument,       NULL, ARG_USER                },
260                 { "system",              no_argument,       NULL, ARG_SYSTEM              },
261                 {}
262         };
263
264         int c;
265
266         assert(argc >= 1);
267         assert(argv);
268
269         opterr = 0;
270
271         while ((c = getopt_long(argc, argv, ":h", options, NULL)) >= 0)
272                 switch (c) {
273
274                 case 'h':
275                         help();
276                         return 0;
277
278                 case ARG_VERSION:
279                         puts(PACKAGE_STRING);
280                         puts(SYSTEMD_FEATURES);
281                         return 0;
282
283                 case ARG_USER:
284                         arg_running_as = SYSTEMD_USER;
285                         break;
286
287                 case ARG_SYSTEM:
288                         arg_running_as = SYSTEMD_SYSTEM;
289                         break;
290
291                 case '?':
292                         log_error("Unknown option %s.", argv[optind-1]);
293                         return -EINVAL;
294
295                 case ':':
296                         log_error("Missing argument to %s.", argv[optind-1]);
297                         return -EINVAL;
298
299                 default:
300                         assert_not_reached("Unhandled option code.");
301                 }
302
303         return 1; /* work to do */
304 }
305
306 int main(int argc, char *argv[]) {
307         int r;
308
309         log_parse_environment();
310         log_open();
311
312         r = parse_argv(argc, argv);
313         if (r <= 0)
314                 goto finish;
315
316         r = test_units(argv + optind);
317
318 finish:
319         return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
320 }