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