chiark / gitweb /
mount: fix confirm spawn setting
[elogind.git] / src / install.c
1 /*-*- Mode: C; c-basic-offset: 8 -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 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   General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <sys/stat.h>
23 #include <stdio.h>
24 #include <getopt.h>
25 #include <errno.h>
26 #include <unistd.h>
27
28 #include "log.h"
29 #include "path-lookup.h"
30 #include "util.h"
31 #include "macro.h"
32 #include "strv.h"
33 #include "conf-parser.h"
34 #include "dbus-common.h"
35
36 static bool arg_force = false;
37
38 static enum {
39         WHERE_SYSTEM,
40         WHERE_SESSION,
41         WHERE_GLOBAL,
42 } arg_where = WHERE_SYSTEM;
43
44 static enum {
45         ACTION_INVALID,
46         ACTION_ENABLE,
47         ACTION_DISABLE,
48         ACTION_TEST
49 } arg_action = ACTION_INVALID;
50
51 static enum {
52         START_NO,        /* Don't start/stop or anything */
53         START_MINIMAL,   /* Only shutdown/restart if running. */
54         START_MAYBE,     /* Start if WantedBy= suggests */
55         START_YES        /* Start unconditionally */
56 } arg_start = START_NO;
57
58 typedef struct {
59         char *name;
60         char *path;
61
62         char **aliases;
63         char **wanted_by;
64 } InstallInfo;
65
66 Hashmap *will_install = NULL, *have_installed = NULL;
67
68 static int help(void) {
69
70         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
71                "Install init system units.\n\n"
72                "  -h --help         Show this help\n"
73                "     --force        Override existing links\n"
74                "     --system       Install into system\n"
75                "     --session      Install into session\n"
76                "     --global       Install into all sessions\n"
77                "     --start[=MODE] Start/stop/restart unit after installation\n"
78                "                    Takes 'no', 'minimal', 'maybe' or 'yes'\n\n"
79                "Commands:\n"
80                "  enable [NAME...]    Enable one or more units\n"
81                "  disable [NAME...]   Disable one or more units\n"
82                "  test [NAME...]      Test whether any of the specified units are enabled\n",
83                program_invocation_short_name);
84
85         return 0;
86 }
87
88 static int parse_argv(int argc, char *argv[]) {
89
90         enum {
91                 ARG_SESSION = 0x100,
92                 ARG_SYSTEM,
93                 ARG_GLOBAL,
94                 ARG_FORCE,
95                 ARG_START
96         };
97
98         static const struct option options[] = {
99                 { "help",      no_argument,       NULL, 'h'         },
100                 { "session",   no_argument,       NULL, ARG_SESSION },
101                 { "system",    no_argument,       NULL, ARG_SYSTEM  },
102                 { "global",    no_argument,       NULL, ARG_GLOBAL  },
103                 { "force",     no_argument,       NULL, ARG_FORCE   },
104                 { "start",     optional_argument, NULL, ARG_START   },
105                 { NULL,        0,                 NULL, 0           }
106         };
107
108         int c;
109
110         assert(argc >= 1);
111         assert(argv);
112
113         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
114
115                 switch (c) {
116
117                 case 'h':
118                         help();
119                         return 0;
120
121                 case ARG_SESSION:
122                         arg_where = WHERE_SESSION;
123                         break;
124
125                 case ARG_SYSTEM:
126                         arg_where = WHERE_SYSTEM;
127                         break;
128
129                 case ARG_GLOBAL:
130                         arg_where = WHERE_GLOBAL;
131                         break;
132
133                 case ARG_FORCE:
134                         arg_force = true;
135                         break;
136
137                 case ARG_START:
138
139                         if (!optarg)
140                                 arg_start = START_MAYBE;
141                         else if (streq(optarg, "no"))
142                                 arg_start = START_NO;
143                         else if (streq(optarg, "minimal"))
144                                 arg_start = START_MINIMAL;
145                         else if (streq(optarg, "maybe"))
146                                 arg_start = START_MAYBE;
147                         else if (streq(optarg, "yes"))
148                                 arg_start = START_YES;
149                         else {
150                                 log_error("Invalid --start argument %s", optarg);
151                                 return -EINVAL;
152                         }
153
154                         break;
155
156                 case '?':
157                         return -EINVAL;
158
159                 default:
160                         log_error("Unknown option code %c", c);
161                         return -EINVAL;
162                 }
163         }
164
165         if (optind >= argc) {
166                 help();
167                 return -EINVAL;
168         }
169
170         if (streq(argv[optind], "enable"))
171                 arg_action = ACTION_ENABLE;
172         else if (streq(argv[optind], "disable"))
173                 arg_action = ACTION_DISABLE;
174         else if (streq(argv[optind], "test"))
175                 arg_action = ACTION_TEST;
176         else {
177                 log_error("Unknown verb %s.", argv[optind]);
178                 return -EINVAL;
179         }
180
181         optind++;
182
183         if (optind >= argc) {
184                 log_error("Missing unit name.");
185                 return -EINVAL;
186         }
187
188
189         return 1;
190 }
191
192 static void install_info_free(InstallInfo *i) {
193         assert(i);
194
195         free(i->name);
196         free(i->path);
197         strv_free(i->aliases);
198         strv_free(i->wanted_by);
199         free(i);
200 }
201
202 static void install_info_hashmap_free(Hashmap *m) {
203         InstallInfo *i;
204
205         while ((i = hashmap_steal_first(m)))
206                 install_info_free(i);
207
208         hashmap_free(m);
209 }
210
211 static bool unit_name_valid(const char *name) {
212
213         /* This is a minimal version of unit_name_valid() from
214          * unit-name.c */
215
216         if (!*name)
217                 return false;
218
219         if (ignore_file(name))
220                 return false;
221
222         return true;
223 }
224
225 static int install_info_add(const char *name) {
226         InstallInfo *i;
227         int r;
228
229         if (!unit_name_valid(name))
230                 return -EINVAL;
231
232         if (hashmap_get(have_installed, name) ||
233             hashmap_get(will_install, name))
234                 return 0;
235
236         if (!(i = new0(InstallInfo, 1))) {
237                 r = -ENOMEM;
238                 goto fail;
239         }
240
241         if (!(i->name = strdup(name))) {
242                 r = -ENOMEM;
243                 goto fail;
244         }
245
246         if ((r = hashmap_put(will_install, i->name, i)) < 0)
247                 goto fail;
248
249         return 0;
250
251 fail:
252         if (i)
253                 install_info_free(i);
254
255         return r;
256 }
257
258 static int daemon_reload(DBusConnection *bus) {
259         DBusMessage *m = NULL, *reply = NULL;
260         DBusError error;
261         int r;
262
263         assert(bus);
264
265         dbus_error_init(&error);
266
267         if (!(m = dbus_message_new_method_call(
268                               "org.freedesktop.systemd1",
269                               "/org/freedesktop/systemd1",
270                               "org.freedesktop.systemd1.Manager",
271                               "Reload"))) {
272                 log_error("Could not allocate message.");
273                 return -ENOMEM;
274         }
275
276         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
277                 log_error("Failed to reload configuration: %s", error.message);
278                 r = -EIO;
279                 goto finish;
280         }
281
282         r = 0;
283
284 finish:
285         if (m)
286                 dbus_message_unref(m);
287
288         if (reply)
289                 dbus_message_unref(reply);
290
291         dbus_error_free(&error);
292
293         return r;
294 }
295
296 static int install_info_run(DBusConnection *bus, InstallInfo *i) {
297         DBusMessage *m = NULL, *reply = NULL;
298         DBusError error;
299         int r;
300         const char *mode = "replace";
301
302         assert(bus);
303         assert(i);
304
305         dbus_error_init(&error);
306
307         if (arg_action == ACTION_ENABLE) {
308
309                 if (arg_start == START_MAYBE) {
310                         char **k;
311                         bool yes_please = false;
312
313                         STRV_FOREACH(k, i->wanted_by) {
314                                 DBusMessageIter sub, iter;
315
316                                 const char *path, *state;
317                                 const char *interface = "org.freedesktop.systemd1.Unit";
318                                 const char *property = "ActiveState";
319
320                                 if (!(m = dbus_message_new_method_call(
321                                                       "org.freedesktop.systemd1",
322                                                       "/org/freedesktop/systemd1",
323                                                       "org.freedesktop.systemd1.Manager",
324                                                       "GetUnit"))) {
325                                         log_error("Could not allocate message.");
326                                         r = -ENOMEM;
327                                         goto finish;
328                                 }
329
330                                 if (!dbus_message_append_args(m,
331                                                               DBUS_TYPE_STRING, k,
332                                                               DBUS_TYPE_INVALID)) {
333                                         log_error("Could not append arguments to message.");
334                                         r = -ENOMEM;
335                                         goto finish;
336                                 }
337
338                                 if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
339                                         /* Hmm, this unit doesn't exist, let's try the next one */
340                                         dbus_message_unref(m);
341                                         m = NULL;
342                                         continue;
343                                 }
344
345                                 if (!dbus_message_get_args(reply, &error,
346                                                            DBUS_TYPE_OBJECT_PATH, &path,
347                                                            DBUS_TYPE_INVALID)) {
348                                         log_error("Failed to parse reply: %s", error.message);
349                                         r = -EIO;
350                                         goto finish;
351                                 }
352
353                                 dbus_message_unref(m);
354                                 if (!(m = dbus_message_new_method_call(
355                                                       "org.freedesktop.systemd1",
356                                                       path,
357                                                       "org.freedesktop.DBus.Properties",
358                                                       "Get"))) {
359                                         log_error("Could not allocate message.");
360                                         r = -ENOMEM;
361                                         goto finish;
362                                 }
363
364                                 if (!dbus_message_append_args(m,
365                                                               DBUS_TYPE_STRING, &interface,
366                                                               DBUS_TYPE_STRING, &property,
367                                                               DBUS_TYPE_INVALID)) {
368                                         log_error("Could not append arguments to message.");
369                                         r = -ENOMEM;
370                                         goto finish;
371                                 }
372
373                                 dbus_message_unref(reply);
374                                 if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
375                                         log_error("Failed to issue method call: %s", error.message);
376                                         r = -EIO;
377                                         goto finish;
378                                 }
379
380                                 if (!dbus_message_iter_init(reply, &iter) ||
381                                     dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
382                                         log_error("Failed to parse reply.");
383                                         r = -EIO;
384                                         goto finish;
385                                 }
386
387                                 dbus_message_iter_recurse(&iter, &sub);
388
389                                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)  {
390                                         log_error("Failed to parse reply.");
391                                         r = -EIO;
392                                         goto finish;
393                                 }
394
395                                 dbus_message_iter_get_basic(&sub, &state);
396
397                                 dbus_message_unref(m);
398                                 dbus_message_unref(reply);
399                                 m = reply = NULL;
400
401                                 if (streq(state, "active") ||
402                                     startswith(state, "reloading") ||
403                                     startswith(state, "activating")) {
404                                         yes_please = true;
405                                         break;
406                                 }
407                         }
408
409                         if (!yes_please) {
410                                 r = 0;
411                                 goto finish;
412                         }
413                 }
414
415                 if (!(m = dbus_message_new_method_call(
416                                       "org.freedesktop.systemd1",
417                                       "/org/freedesktop/systemd1",
418                                       "org.freedesktop.systemd1.Manager",
419                                       arg_start == START_MINIMAL ? "TryRestartUnit" : "RestartUnit"))) {
420                         log_error("Could not allocate message.");
421                         r = -ENOMEM;
422                         goto finish;
423                 }
424
425                 if (!dbus_message_append_args(m,
426                                               DBUS_TYPE_STRING, &i->name,
427                                               DBUS_TYPE_STRING, &mode,
428                                               DBUS_TYPE_INVALID)) {
429                         log_error("Could not append arguments to message.");
430                         r = -ENOMEM;
431                         goto finish;
432                 }
433
434
435         } else if (arg_action == ACTION_DISABLE) {
436
437                 if (!(m = dbus_message_new_method_call(
438                                       "org.freedesktop.systemd1",
439                                       "/org/freedesktop/systemd1",
440                                       "org.freedesktop.systemd1.Manager",
441                                       "StopUnit"))) {
442                         log_error("Could not allocate message.");
443                         r = -ENOMEM;
444                         goto finish;
445                 }
446
447                 if (!dbus_message_append_args(m,
448                                               DBUS_TYPE_STRING, &i->name,
449                                               DBUS_TYPE_STRING, &mode,
450                                               DBUS_TYPE_INVALID)) {
451                         log_error("Could not append arguments to message.");
452                         r = -ENOMEM;
453                         goto finish;
454                 }
455         }
456
457         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
458                 log_error("Failed to reload configuration: %s", error.message);
459                 r = -EIO;
460                 goto finish;
461         }
462
463         r = 0;
464
465 finish:
466         if (m)
467                 dbus_message_unref(m);
468
469         if (reply)
470                 dbus_message_unref(reply);
471
472         dbus_error_free(&error);
473
474         return r;
475 }
476
477 static int config_parse_also(
478                 const char *filename,
479                 unsigned line,
480                 const char *section,
481                 const char *lvalue,
482                 const char *rvalue,
483                 void *data,
484                 void *userdata) {
485
486         char *w;
487         size_t l;
488         char *state;
489
490         assert(filename);
491         assert(lvalue);
492         assert(rvalue);
493
494         FOREACH_WORD_QUOTED(w, l, rvalue, state) {
495                 char *n;
496                 int r;
497
498                 if (!(n = strndup(w, l)))
499                         return -ENOMEM;
500
501                 r = install_info_add(n);
502                 free(n);
503
504                 if (r < 0)
505                         return r;
506         }
507
508         return 0;
509 }
510
511 static int create_symlink(const char *old_path, const char *new_path) {
512         int r;
513
514         assert(old_path);
515         assert(new_path);
516
517         if (arg_action == ACTION_ENABLE) {
518                 char *dest;
519
520                 mkdir_parents(new_path, 0755);
521
522                 if (symlink(old_path, new_path) >= 0)
523                         return 0;
524
525                 if (errno != EEXIST) {
526                         log_error("Cannot link %s to %s: %m", old_path, new_path);
527                         return -errno;
528                 }
529
530                 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
531
532                         if (errno == EINVAL) {
533                                 log_error("Cannot link %s to %s, file exists already and is not a symlink.", old_path, new_path);
534                                 return -EEXIST;
535                         }
536
537                         log_error("readlink() failed: %s", strerror(-r));
538                         return r;
539                 }
540
541                 if (streq(dest, old_path)) {
542                         free(dest);
543                         return 0;
544                 }
545
546                 if (!arg_force) {
547                         log_error("Cannot link %s to %s, symlink exists already and points to %s.", old_path, new_path, dest);
548                         free(dest);
549                         return -EEXIST;
550                 }
551
552                 free(dest);
553                 unlink(new_path);
554
555                 if (symlink(old_path, new_path) >= 0)
556                         return 0;
557
558                 log_error("Cannot link %s to %s: %m", old_path, new_path);
559                 return -errno;
560
561         } else if (arg_action == ACTION_DISABLE) {
562                 char *dest;
563
564                 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
565                         if (errno == ENOENT)
566                                 return 0;
567
568                         if (errno == EINVAL) {
569                                 log_warning("File %s not a symlink, ignoring.", old_path);
570                                 return 0;
571                         }
572
573                         log_error("readlink() failed: %s", strerror(-r));
574                         return r;
575                 }
576
577                 if (!streq(dest, old_path)) {
578                         log_warning("File %s not a symlink to %s but points to %s, ignoring.", new_path, old_path, dest);
579                         free(dest);
580                         return 0;
581                 }
582
583                 free(dest);
584                 if (unlink(new_path) >= 0)
585                         return 0;
586
587                 log_error("Cannot unlink %s: %m", new_path);
588                 return -errno;
589
590         } else if (arg_action == ACTION_TEST) {
591                 char *dest;
592
593                 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
594
595                         if (errno == ENOENT || errno == EINVAL)
596                                 return 0;
597
598                         log_error("readlink() failed: %s", strerror(-r));
599                         return r;
600                 }
601
602                 if (streq(dest, old_path)) {
603                         free(dest);
604                         return 1;
605                 }
606
607                 return 0;
608         }
609
610         assert_not_reached("Unknown action.");
611 }
612
613 static int install_info_symlink_alias(InstallInfo *i, const char *config_path) {
614         char **s;
615         char *alias_path = NULL;
616         int r;
617
618         assert(i);
619
620         STRV_FOREACH(s, i->aliases) {
621
622                 if (!unit_name_valid(*s)) {
623                         log_error("Invalid name %s.", *s);
624                         r = -EINVAL;
625                         goto finish;
626                 }
627
628                 free(alias_path);
629                 if (!(alias_path = path_make_absolute(*s, config_path))) {
630                         log_error("Out of memory");
631                         r = -ENOMEM;
632                         goto finish;
633                 }
634
635                 if ((r = create_symlink(i->path, alias_path)) != 0)
636                         goto finish;
637
638                 if (arg_action == ACTION_DISABLE)
639                         rmdir_parents(alias_path, config_path);
640         }
641
642         r = 0;
643
644 finish:
645         free(alias_path);
646
647         return r;
648 }
649
650 static int install_info_symlink_wants(InstallInfo *i, const char *config_path) {
651         char **s;
652         char *alias_path = NULL;
653         int r;
654
655         assert(i);
656
657         STRV_FOREACH(s, i->wanted_by) {
658                 if (!unit_name_valid(*s)) {
659                         log_error("Invalid name %s.", *s);
660                         r = -EINVAL;
661                         goto finish;
662                 }
663
664                 free(alias_path);
665                 alias_path = NULL;
666
667                 if (asprintf(&alias_path, "%s/%s.wants/%s", config_path, *s, i->name) < 0) {
668                         log_error("Out of memory");
669                         r = -ENOMEM;
670                         goto finish;
671                 }
672
673                 if ((r = create_symlink(i->path, alias_path)) != 0)
674                         goto finish;
675
676                 if (arg_action == ACTION_DISABLE)
677                         rmdir_parents(alias_path, config_path);
678         }
679
680         r = 0;
681
682 finish:
683         free(alias_path);
684
685         return r;
686 }
687
688 static int install_info_apply(LookupPaths *paths, InstallInfo *i, const char *config_path) {
689
690         const ConfigItem items[] = {
691                 { "Alias",    config_parse_strv, &i->aliases,   "Install" },
692                 { "WantedBy", config_parse_strv, &i->wanted_by, "Install" },
693                 { "Also",     config_parse_also, NULL,          "Install" },
694
695                 { NULL, NULL, NULL, NULL }
696         };
697
698         char **p;
699         char *filename = NULL;
700         FILE *f = NULL;
701         int r;
702
703         assert(paths);
704         assert(i);
705
706         STRV_FOREACH(p, paths->unit_path) {
707
708                 if (!(filename = path_make_absolute(i->name, *p))) {
709                         log_error("Out of memory");
710                         return -ENOMEM;
711                 }
712
713                 if ((f = fopen(filename, "re")))
714                         break;
715
716                 free(filename);
717                 filename = NULL;
718
719                 if (errno != ENOENT) {
720                         log_error("Failed to open %s: %m", filename);
721                         return -errno;
722                 }
723         }
724
725         if (!f) {
726                 log_error("Couldn't find %s.", i->name);
727                 return -ENOENT;
728         }
729
730         i->path = filename;
731
732         if ((r = config_parse(filename, f, NULL, items, true, i)) < 0) {
733                 fclose(f);
734                 return r;
735         }
736
737         fclose(f);
738
739         if ((r = install_info_symlink_alias(i, config_path)) != 0)
740                 return r;
741
742         if ((r = install_info_symlink_wants(i, config_path)) != 0)
743                 return r;
744
745         return 0;
746 }
747
748 static char *get_config_path(void) {
749
750         switch (arg_where) {
751
752         case WHERE_SYSTEM:
753                 return strdup(SYSTEM_CONFIG_UNIT_PATH);
754
755         case WHERE_GLOBAL:
756                 return strdup(SESSION_CONFIG_UNIT_PATH);
757
758         case WHERE_SESSION: {
759                 char *p;
760
761                 if (session_config_home(&p) < 0)
762                         return NULL;
763
764                 return p;
765         }
766
767         default:
768                 assert_not_reached("Unknown config path.");
769         }
770 }
771
772 static int do_run(void) {
773         DBusConnection *bus = NULL;
774         DBusError error;
775         int r, q;
776         Iterator i;
777         InstallInfo *j;
778
779         dbus_error_init(&error);
780
781         if (arg_start == START_NO)
782                 return 0;
783
784         if (arg_where == WHERE_GLOBAL) {
785                 log_warning("Warning: --start has no effect with --global.");
786                 return 0;
787         }
788
789         if (arg_action != ACTION_ENABLE && arg_action != ACTION_DISABLE) {
790                 log_warning("Warning: --start has no effect with test.");
791                 return 0;
792         }
793
794         if ((r = bus_connect(arg_where == WHERE_SESSION ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, NULL, &error)) < 0) {
795                 log_error("Failed to get D-Bus connection: %s", error.message);
796                 goto finish;
797         }
798
799         r = 0;
800
801         if (arg_action == ACTION_ENABLE)
802                 if ((r = daemon_reload(bus)) < 0)
803                         goto finish;
804
805         HASHMAP_FOREACH(j, have_installed, i)
806                 if ((q = install_info_run(bus, j)) < 0)
807                         r = q;
808
809         if (arg_action == ACTION_DISABLE)
810                 if ((q = daemon_reload(bus)) < 0)
811                         r = q;
812
813 finish:
814         if (bus)
815                 dbus_connection_unref(bus);
816
817         dbus_error_free(&error);
818
819         dbus_shutdown();
820         return r;
821 }
822
823 int main(int argc, char *argv[]) {
824         int r, retval = 1, j;
825         LookupPaths paths;
826         InstallInfo *i;
827         char *config_path = NULL;
828
829         zero(paths);
830
831         log_parse_environment();
832
833         if ((r = parse_argv(argc, argv)) < 0)
834                 goto finish;
835         else if (r == 0) {
836                 retval = 0;
837                 goto finish;
838         }
839
840         if ((r = lookup_paths_init(&paths, arg_where == WHERE_SYSTEM ? MANAGER_SYSTEM : MANAGER_SESSION)) < 0) {
841                 log_error("Failed to determine lookup paths: %s", strerror(-r));
842                 goto finish;
843         }
844
845         if (!(config_path = get_config_path())) {
846                 log_error("Failed to determine config path");
847                 goto finish;
848         }
849
850         will_install = hashmap_new(string_hash_func, string_compare_func);
851         have_installed = hashmap_new(string_hash_func, string_compare_func);
852
853         if (!will_install || !have_installed) {
854                 log_error("Failed to allocate unit sets.");
855                 goto finish;
856         }
857
858         for (j = optind; j < argc; j++)
859                 if ((r = install_info_add(argv[j])) < 0)
860                         goto finish;
861
862         while ((i = hashmap_first(will_install))) {
863                 assert_se(hashmap_move_one(have_installed, will_install, i->name) == 0);
864
865                 if ((r = install_info_apply(&paths, i, config_path)) != 0) {
866
867                         if (r < 0)
868                                 goto finish;
869
870                         /* In test mode and found something */
871                         retval = 0;
872                         goto finish;
873                 }
874         }
875
876         if (do_run() < 0)
877                 goto finish;
878
879         retval = arg_action == ACTION_TEST ? 1 : 0;
880
881 finish:
882         install_info_hashmap_free(will_install);
883         install_info_hashmap_free(have_installed);
884
885         lookup_paths_free(&paths);
886
887         free(config_path);
888
889         return retval;
890 }