]> git.ipfire.org Git - thirdparty/dracut.git/blame - install/dracut-install.c
dracut.sh: for --include copy also the symbolic links
[thirdparty/dracut.git] / install / dracut-install.c
CommitLineData
026b81e9
HH
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/* dracut-install.c -- install files and executables
4
5 Copyright (C) 2012 Harald Hoyer
6 Copyright (C) 2012 Red Hat, Inc. All rights reserved.
7
8 This program is free software: you can redistribute it and/or modify
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 This program 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 this program; If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#define PROGRAM_VERSION_STRING "1"
23
24#ifndef _GNU_SOURCE
25#define _GNU_SOURCE
26#endif
27
28#include <ctype.h>
29#include <errno.h>
30#include <fcntl.h>
31#include <getopt.h>
32#include <libgen.h>
33#include <limits.h>
34#include <stdbool.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <sys/stat.h>
39#include <sys/types.h>
40#include <sys/wait.h>
41#include <unistd.h>
85854b24 42#include <sys/ioctl.h>
026b81e9
HH
43
44#include "log.h"
45#include "hashmap.h"
46#include "util.h"
47
48static bool arg_hmac = false;
49static bool arg_createdir = false;
50static int arg_loglevel = -1;
51static bool arg_optional = false;
52static bool arg_all = false;
53static bool arg_resolvelazy = false;
54static bool arg_resolvedeps = false;
55static char *destrootdir = NULL;
56
57static Hashmap *items = NULL;
58static Hashmap *items_failed = NULL;
59
e74944ee 60static int dracut_install(const char *src, const char *dst, bool isdir, bool resolvedeps, bool hashdst);
026b81e9
HH
61
62static size_t dir_len(char const *file)
63{
64 size_t length;
65 /* Strip the basename and any redundant slashes before it. */
66 for (length = strlen(file); 0 < length; length--)
67 if (file[length] == '/')
68 break;
69 return length;
70}
71
72static char *convert_abs_rel(const char *from, const char *target)
73{
74 /* we use the 4*MAXPATHLEN, which should not overrun */
75 char relative_from[MAXPATHLEN * 4];
76 char *realtarget = NULL;
77 char *p, *q;
78 const char *realfrom = from;
79 int level = 0, fromlevel = 0, targetlevel = 0;
80 int l, i, rl;
81 int dirlen;
82
83 p = strdup(target);
84 dirlen = dir_len(p);
85 p[dirlen] = '\0';
86 q = realpath(p, NULL);
87
88 if (q == NULL) {
89 free(p);
90 log_warning("convert_abs_rel(): target '%s' directory has no realpath.", target);
91 return strdup(from);
92 }
93
94 asprintf(&realtarget, "%s/%s", q, &p[dirlen + 1]);
95 free(p);
96 free(q);
97
98 /* now calculate the relative path from <from> to <target> and
99 store it in <relative_from>
100 */
101 relative_from[0] = 0;
102 rl = 0;
103
104 /* count the pathname elements of realtarget */
105 for (targetlevel = 0, i = 0; realtarget[i]; i++)
106 if (realtarget[i] == '/')
107 targetlevel++;
108
109 /* count the pathname elements of realfrom */
110 for (fromlevel = 0, i = 0; realfrom[i]; i++)
111 if (realfrom[i] == '/')
112 fromlevel++;
113
114 /* count the pathname elements, which are common for both paths */
115 for (level = 0, i = 0; realtarget[i] && (realtarget[i] == realfrom[i]); i++)
116 if (realtarget[i] == '/')
117 level++;
118
119 free(realtarget);
120
121 /* add "../" to the relative_from path, until the common pathname is
122 reached */
123 for (i = level; i < targetlevel; i++) {
124 if (i != level)
125 relative_from[rl++] = '/';
126 relative_from[rl++] = '.';
127 relative_from[rl++] = '.';
128 }
129
130 /* set l to the next uncommon pathname element in realfrom */
131 for (l = 1, i = 1; i < level; i++)
132 for (l++; realfrom[l] && realfrom[l] != '/'; l++) ;
133 /* skip next '/' */
134 l++;
135
136 /* append the uncommon rest of realfrom to the relative_from path */
137 for (i = level; i <= fromlevel; i++) {
138 if (rl)
139 relative_from[rl++] = '/';
140 while (realfrom[l] && realfrom[l] != '/')
141 relative_from[rl++] = realfrom[l++];
142 l++;
143 }
144
145 relative_from[rl] = 0;
146 return strdup(relative_from);
147}
148
149static int ln_r(const char *src, const char *dst)
150{
151 int ret;
152 const char *points_to = convert_abs_rel(src, dst);
153 log_info("ln -s '%s' '%s'", points_to, dst);
154 ret = symlink(points_to, dst);
155
156 if (ret != 0) {
157 log_error("ERROR: ln -s '%s' '%s': %m", points_to, dst);
158 free((char *)points_to);
159 return 1;
160 }
161
162 free((char *)points_to);
163
164 return 0;
165}
166
85854b24
HH
167/* Perform the O(1) btrfs clone operation, if possible.
168 Upon success, return 0. Otherwise, return -1 and set errno. */
169static inline int clone_file(int dest_fd, int src_fd)
170{
171#undef BTRFS_IOCTL_MAGIC
172#define BTRFS_IOCTL_MAGIC 0x94
173#undef BTRFS_IOC_CLONE
174#define BTRFS_IOC_CLONE _IOW (BTRFS_IOCTL_MAGIC, 9, int)
175 return ioctl(dest_fd, BTRFS_IOC_CLONE, src_fd);
176}
177
178static bool use_clone = true;
179
026b81e9
HH
180static int cp(const char *src, const char *dst)
181{
182 int pid;
85854b24
HH
183 int ret;
184
185 if(use_clone) {
186 struct stat sb;
187 int dest_desc, source_desc;
188
189 if (lstat(src, &sb) != 0)
190 goto normal_copy;
191
192 if (S_ISLNK(sb.st_mode))
193 goto normal_copy;
194
195 source_desc = open(src, O_RDONLY | O_CLOEXEC);
196 if (source_desc < 0)
197 goto normal_copy;
026b81e9 198
85854b24
HH
199 dest_desc =
200 open(dst, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC,
201 (sb.st_mode) & (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO));
202
203 if (dest_desc < 0) {
204 close(source_desc);
205 goto normal_copy;
206 }
207
208 ret = clone_file(dest_desc, source_desc);
209 close(source_desc);
210 if (ret == 0) {
211 if (fchown(dest_desc, sb.st_uid, sb.st_gid) != 0)
212 fchown(dest_desc, -1, sb.st_gid);
213 close(dest_desc);
214 return ret;
215 }
216 close(dest_desc);
217
218 /* clone did not work, remove the file */
219 unlink(dst);
220 /* do not try clone again */
221 use_clone = false;
222 }
223
224 normal_copy:
026b81e9
HH
225 pid = fork();
226 if (pid == 0) {
227 execlp("cp", "cp", "--reflink=auto", "--sparse=auto", "--preserve=mode", "-fL", src, dst, NULL);
228 _exit(EXIT_FAILURE);
229 }
230
85854b24 231 while (waitpid(pid, &ret, 0) < 0) {
026b81e9 232 if (errno != EINTR) {
85854b24 233 ret = -1;
026b81e9
HH
234 break;
235 }
236 }
237
85854b24 238 return ret;
026b81e9
HH
239}
240
241static int resolve_deps(const char *src)
242{
243 int ret = 0;
244
245 char *buf = malloc(LINE_MAX);
246 size_t linesize = LINE_MAX;
247 FILE *fptr;
248 char *cmd;
249
250 if (strstr(src, ".so") == 0) {
251 int fd;
252 fd = open(src, O_RDONLY | O_CLOEXEC);
253 read(fd, buf, LINE_MAX);
254 buf[LINE_MAX - 1] = '\0';
255 close(fd);
256 if (buf[0] == '#' && buf[1] == '!') {
257 /* we have a shebang */
258 char *p, *q;
259 for (p = &buf[2]; *p && isspace(*p); p++) ;
260 for (q = p; *q && (!isspace(*q)); q++) ;
261 *q = '\0';
262 log_debug("Script install: '%s'", p);
e74944ee 263 ret = dracut_install(p, p, false, true, false);
026b81e9
HH
264 if (ret != 0)
265 log_error("ERROR: failed to install '%s'", p);
266 return ret;
267 }
268 }
269
270 /* run ldd */
a9231107 271 asprintf(&cmd, "ldd %s 2>&1", src);
026b81e9
HH
272 fptr = popen(cmd, "r");
273
274 while (!feof(fptr)) {
275 char *p, *q;
276
277 if (getline(&buf, &linesize, fptr) <= 0)
278 continue;
279
280 log_debug("ldd: '%s'", buf);
281
282 if (strstr(buf, "not a dynamic executable"))
283 break;
284
a9231107
HH
285 if (strstr(buf, "loader cannot load itself"))
286 break;
287
026b81e9
HH
288 p = strstr(buf, "/");
289 if (p) {
290 int r;
291 for (q = p; *q && *q != ' ' && *q != '\n'; q++) ;
292 *q = '\0';
e74944ee 293 r = dracut_install(p, p, false, false, true);
026b81e9
HH
294 if (r != 0)
295 log_error("ERROR: failed to install '%s' for '%s'", p, src);
296 else
297 log_debug("Lib install: '%s'", p);
298 ret += r;
299
300 /* also install lib.so for lib.so.* files */
301 q = strstr(p, ".so.");
302 if (q) {
303 q += 3;
304 *q = '\0';
305
306 /* ignore errors for base lib symlink */
e74944ee 307 if (dracut_install(p, p, false, false, true) == 0)
026b81e9
HH
308 log_debug("Lib install: '%s'", p);
309 }
310 }
311 }
312 pclose(fptr);
313
314 return ret;
315}
316
317/* Install ".<filename>.hmac" file for FIPS self-checks */
318static int hmac_install(const char *src, const char *dst)
319{
320 char *srcpath = strdup(src);
321 char *dstpath = strdup(dst);
322 char *srchmacname = NULL;
323 char *dsthmacname = NULL;
324 size_t dlen = dir_len(src);
325
326 if (endswith(src, ".hmac"))
327 return 0;
328
329 srcpath[dlen] = '\0';
330 dstpath[dir_len(dst)] = '\0';
331 asprintf(&srchmacname, "%s/.%s.hmac", srcpath, &src[dlen + 1]);
332 asprintf(&dsthmacname, "%s/.%s.hmac", dstpath, &src[dlen + 1]);
333 log_debug("hmac cp '%s' '%s')", srchmacname, dsthmacname);
e74944ee 334 dracut_install(srchmacname, dsthmacname, false, false, true);
026b81e9
HH
335 free(dsthmacname);
336 free(srchmacname);
337 free(srcpath);
338 free(dstpath);
339 return 0;
340}
341
e74944ee 342static int dracut_install(const char *src, const char *dst, bool isdir, bool resolvedeps, bool hashdst)
026b81e9
HH
343{
344 struct stat sb, db;
345 char *dname = NULL;
346 char *fulldstpath = NULL;
347 char *fulldstdir = NULL;
348 int ret;
349 bool src_exists = true;
350 char *i, *existing;
351
352 log_debug("dracut_install('%s', '%s')", src, dst);
353
354 existing = hashmap_get(items_failed, src);
355 if (existing) {
356 if (strcmp(existing, src) == 0) {
357 log_debug("hash hit items_failed for '%s'", src);
358 return 1;
359 }
360 }
361
e74944ee
HH
362 if (hashdst) {
363 existing = hashmap_get(items, dst);
364 if (existing) {
365 if (strcmp(existing, dst) == 0) {
366 log_debug("hash hit items for '%s'", dst);
367 return 0;
368 }
026b81e9
HH
369 }
370 }
371
372 if (lstat(src, &sb) < 0) {
373 src_exists = false;
374 if (!isdir) {
375 i = strdup(src);
376 hashmap_put(items_failed, i, i);
377 /* src does not exist */
378 return 1;
379 }
380 }
381
e74944ee 382
026b81e9
HH
383 i = strdup(dst);
384 hashmap_put(items, i, i);
385
386 asprintf(&fulldstpath, "%s%s", destrootdir, dst);
387
388 ret = stat(fulldstpath, &sb);
389
390 if (ret != 0 && (errno != ENOENT)) {
391 log_error("ERROR: stat '%s': %m", fulldstpath);
392 return 1;
393 }
394
395 if (ret == 0) {
396 log_debug("'%s' already exists", fulldstpath);
397 free(fulldstpath);
398 /* dst does already exist */
399 return 0;
400 }
401
402 /* check destination directory */
403 fulldstdir = strdup(fulldstpath);
404 fulldstdir[dir_len(fulldstdir)] = '\0';
405
406 ret = stat(fulldstdir, &db);
407
408 if (ret < 0) {
409 if (errno != ENOENT) {
410 log_error("ERROR: stat '%s': %m", fulldstdir);
411 return 1;
412 }
413 /* create destination directory */
414 log_debug("dest dir '%s' does not exist", fulldstdir);
415 dname = strdup(dst);
416 dname[dir_len(dname)] = '\0';
e74944ee 417 ret = dracut_install(dname, dname, true, false, true);
026b81e9
HH
418
419 free(dname);
420
421 if (ret != 0) {
422 log_error("ERROR: failed to create directory '%s'", fulldstdir);
423 free(fulldstdir);
424 return 1;
425 }
426 }
427
428 free(fulldstdir);
429
430 if (isdir && !src_exists) {
431 log_info("mkdir '%s'", fulldstpath);
432 return mkdir(fulldstpath, 0755);
433 }
434
435 /* ready to install src */
436
437 if (S_ISDIR(sb.st_mode)) {
438 log_info("mkdir '%s'", fulldstpath);
439 return mkdir(fulldstpath, sb.st_mode | S_IWUSR);
440 }
441
442 if (S_ISLNK(sb.st_mode)) {
443 char *abspath;
444 char *absdestpath = NULL;
445
446 abspath = realpath(src, NULL);
447
448 if (abspath == NULL)
449 return 1;
450
e74944ee 451 if (dracut_install(abspath, abspath, false, resolvedeps, hashdst)) {
026b81e9
HH
452 log_debug("'%s' install error", abspath);
453 return 1;
454 }
455
456 if (lstat(abspath, &sb) != 0) {
457 log_debug("lstat '%s': %m", abspath);
458 return 1;
459 }
460
461 if (lstat(fulldstpath, &sb) != 0) {
462
463 asprintf(&absdestpath, "%s%s", destrootdir, abspath);
464
465 ln_r(absdestpath, fulldstpath);
466
467 free(absdestpath);
468 }
469
470 free(abspath);
471 if (arg_hmac) {
472 /* copy .hmac files also */
473 hmac_install(src, dst);
474 }
475
476 return 0;
477 }
478
479 if (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
480 if (resolvedeps)
481 ret += resolve_deps(src);
482 if (arg_hmac) {
483 /* copy .hmac files also */
484 hmac_install(src, dst);
485 }
486 }
487
488 log_info("cp '%s' '%s'", src, fulldstpath);
489 ret += cp(src, fulldstpath);
490 return ret;
491}
492
493static void item_free(char *i)
494{
495 assert(i);
496 free(i);
497}
498
499static void usage(int status)
500{
501 /* */
502 printf("\
503Usage: %s -D DESTROOTDIR [OPTION]... -a SOURCE...\n\
504 or: %s -D DESTROOTDIR [OPTION]... SOURCE DEST\n\
505\n\
506Install SOURCE to DEST in DESTROOTDIR with all needed dependencies.\n\
507\n\
508 -D --destrootdir Install all files to DESTROOTDIR as the root\n\
509 -a --all Install all SOURCE arguments to DESTROOTDIR\n\
510 -o --optional If SOURCE does not exist, do not fail\n\
511 -d --dir SOURCE is a directory\n\
512 -l --ldd Also install shebang executables and libraries\n\
513 -R --resolvelazy Only install shebang executables and libraries for all SOURCE files\n\
514 -f --fips Also install all '.SOURCE.hmac' files\n\
515 -v --verbose Show more output\n\
516 --debug Show debug output\n\
517 --version Show package version\n\
518 -h --help Show this help\n\
519\n\
520Example:\n\
521# %s -D /var/tmp/test-root --ldd -a sh tr\n\
522# tree /var/tmp/test-root\n\
523/var/tmp/test-root\n\
524|-- lib64 -> usr/lib64\n\
525`-- usr\n\
526 |-- bin\n\
527 | |-- bash\n\
528 | |-- sh -> bash\n\
529 | `-- tr\n\
530 `-- lib64\n\
531 |-- ld-2.15.90.so\n\
532 |-- ld-linux-x86-64.so.2 -> ld-2.15.90.so\n\
533 |-- libc-2.15.90.so\n\
534 |-- libc.so\n\
535 |-- libc.so.6 -> libc-2.15.90.so\n\
536 |-- libdl-2.15.90.so\n\
537 |-- libdl.so -> libdl-2.15.90.so\n\
538 |-- libdl.so.2 -> libdl-2.15.90.so\n\
539 |-- libtinfo.so.5 -> libtinfo.so.5.9\n\
540 `-- libtinfo.so.5.9\n\
541", program_invocation_short_name, program_invocation_short_name, program_invocation_short_name);
542 exit(status);
543}
544
545static int parse_argv(int argc, char *argv[])
546{
547 int c;
548
549 enum {
550 ARG_VERSION = 0x100,
551 ARG_DEBUG
552 };
553
554 static const struct option const options[] = {
555 {"help", no_argument, NULL, 'h'},
556 {"version", no_argument, NULL, ARG_VERSION},
557 {"dir", no_argument, NULL, 'd'},
558 {"debug", no_argument, NULL, ARG_DEBUG},
559 {"verbose", no_argument, NULL, 'v'},
560 {"ldd", no_argument, NULL, 'l'},
561 {"resolvelazy", no_argument, NULL, 'R'},
562 {"optional", no_argument, NULL, 'o'},
563 {"all", no_argument, NULL, 'a'},
564 {"fips", no_argument, NULL, 'H'},
565 {"destrootdir", required_argument, NULL, 'D'},
566 {NULL, 0, NULL, 0}
567 };
568
569 while ((c = getopt_long(argc, argv, "adhloD:DHILR", options, NULL)) != -1) {
570 switch (c) {
571 case ARG_VERSION:
572 puts(PROGRAM_VERSION_STRING);
573 return 0;
574 case 'd':
575 arg_createdir = true;
576 break;
577 case ARG_DEBUG:
578 arg_loglevel = LOG_DEBUG;
579 break;
580 case 'v':
581 arg_loglevel = LOG_INFO;
582 break;
583 case 'o':
584 arg_optional = true;
585 break;
586 case 'l':
587 arg_resolvedeps = true;
588 break;
589 case 'R':
590 arg_resolvelazy = true;
591 break;
592 case 'a':
593 arg_all = true;
594 break;
595 case 'D':
596 destrootdir = strdup(optarg);
597 break;
598 case 'H':
599 arg_hmac = true;
600 break;
601 case 'h':
602 usage(EXIT_SUCCESS);
603 break;
604 default:
605 usage(EXIT_FAILURE);
606 }
607 }
608
609 if (!optind || optind == argc) {
34e43ceb 610 log_error("No SOURCE argument given");
026b81e9
HH
611 usage(EXIT_FAILURE);
612 }
613
614 return 1;
615}
616
617static int resolve_lazy(int argc, char **argv)
618{
619 int i;
620 int destrootdirlen = strlen(destrootdir);
621 int ret = 0;
622 char *item;
623 for (i = 0; i < argc; i++) {
624 const char *src = argv[i];
625 char *p = argv[i];
626 char *existing;
627
628 log_debug("resolve_deps('%s')", src);
629
630 if (strstr(src, destrootdir)) {
631 p = &argv[i][destrootdirlen];
632 }
633
634 existing = hashmap_get(items, p);
635 if (existing) {
636 if (strcmp(existing, p) == 0)
637 continue;
638 }
639
640 item = strdup(p);
641 hashmap_put(items, item, item);
642
643 ret += resolve_deps(src);
644 }
645 return ret;
646}
647
648static int install_all(int argc, char **argv)
649{
650 int r = 0;
651 int i;
652 for (i = 0; i < argc; i++) {
653 int ret;
654 log_debug("Handle '%s'", argv[i]);
655
656 if (strchr(argv[i], '/') == NULL) {
657 char *path;
658 char *p, *q;
659 bool end = false;
660 path = getenv("PATH");
661 if (path == NULL) {
662 log_error("PATH is not set");
663 exit(EXIT_FAILURE);
664 }
665 path = strdup(path);
666 p = path;
667 log_debug("PATH=%s", path);
668 do {
669 char *newsrc = NULL;
670 char *dest;
671 struct stat sb;
672
673 for (q = p; *q && *q != ':'; q++) ;
674
675 if (*q == '\0')
676 end = true;
677 else
678 *q = '\0';
679
680 asprintf(&newsrc, "%s/%s", p, argv[i]);
681 p = q + 1;
682
683 if (stat(newsrc, &sb) != 0) {
684 free(newsrc);
685 ret = -1;
686 continue;
687 }
688
689 dest = strdup(newsrc);
690
691 log_debug("dracut_install '%s'", newsrc);
e74944ee 692 ret = dracut_install(newsrc, dest, arg_createdir, arg_resolvedeps, true);
026b81e9
HH
693 if (ret == 0) {
694 end = true;
695 log_debug("dracut_install '%s' OK", newsrc);
696 }
697 free(newsrc);
698 free(dest);
699 } while (!end);
700 free(path);
701 } else {
702 char *dest = strdup(argv[i]);
e74944ee 703 ret = dracut_install(argv[i], dest, arg_createdir, arg_resolvedeps, true);
026b81e9
HH
704 free(dest);
705 }
706
85854b24 707 if ((ret != 0) && (!arg_optional)) {
026b81e9
HH
708 log_error("ERROR: installing '%s'", argv[i]);
709 r = EXIT_FAILURE;
710 }
711 }
712 return r;
713}
714
715int main(int argc, char **argv)
716{
717 int r;
718 char *i;
719
720 r = parse_argv(argc, argv);
721 if (r <= 0)
722 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
723
724 log_set_target(LOG_TARGET_CONSOLE);
725 log_parse_environment();
726
727 if (arg_loglevel >= 0)
728 log_set_max_level(arg_loglevel);
729
730 log_open();
731
732 umask(0022);
733
734 if (destrootdir == NULL) {
735 destrootdir = getenv("DESTROOTDIR");
736 if (destrootdir == NULL) {
737 log_error("Environment DESTROOTDIR or argument -D is not set!");
738 usage(EXIT_FAILURE);
739 }
740 destrootdir = strdup(destrootdir);
741 }
742
743 items = hashmap_new(string_hash_func, string_compare_func);
744 items_failed = hashmap_new(string_hash_func, string_compare_func);
745
746 if (!items || !items_failed) {
747 log_error("Out of memory");
748 r = EXIT_FAILURE;
749 goto finish;
750 }
751
752 r = EXIT_SUCCESS;
753
754 if (((optind + 1) < argc) && (strcmp(argv[optind + 1], destrootdir) == 0)) {
755 /* ugly hack for compat mode "inst src $destrootdir" */
756 if ((optind + 2) == argc) {
757 argc--;
758 } else {
759 /* ugly hack for compat mode "inst src $destrootdir dst" */
760 if ((optind + 3) == argc) {
761 argc--;
762 argv[optind + 1] = argv[optind + 2];
763 }
764 }
765 }
766
767 if (arg_resolvelazy) {
768 r = resolve_lazy(argc - optind, &argv[optind]);
769 } else if (arg_all || (argc - optind > 2) || ((argc - optind) == 1)) {
770 r = install_all(argc - optind, &argv[optind]);
771 } else {
772 /* simple "inst src dst" */
e74944ee 773 r = dracut_install(argv[optind], argv[optind + 1], arg_createdir, arg_resolvedeps, true);
026b81e9
HH
774 if ((r != 0) && (!arg_optional)) {
775 log_error("ERROR: installing '%s' to '%s'", argv[optind], argv[optind + 1]);
776 r = EXIT_FAILURE;
777 }
778 }
779
780 if (arg_optional)
781 r = EXIT_SUCCESS;
782
783 finish:
784
785 while ((i = hashmap_steal_first(items)))
786 item_free(i);
787
788 while ((i = hashmap_steal_first(items_failed)))
789 item_free(i);
790
791 hashmap_free(items);
792 hashmap_free(items_failed);
793
794 free(destrootdir);
795
796 return r;
797}