]>
Commit | Line | Data |
---|---|---|
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 | ||
48 | static bool arg_hmac = false; | |
49 | static bool arg_createdir = false; | |
50 | static int arg_loglevel = -1; | |
51 | static bool arg_optional = false; | |
52 | static bool arg_all = false; | |
53 | static bool arg_resolvelazy = false; | |
54 | static bool arg_resolvedeps = false; | |
55 | static char *destrootdir = NULL; | |
56 | ||
57 | static Hashmap *items = NULL; | |
58 | static Hashmap *items_failed = NULL; | |
59 | ||
e74944ee | 60 | static int dracut_install(const char *src, const char *dst, bool isdir, bool resolvedeps, bool hashdst); |
026b81e9 HH |
61 | |
62 | static size_t dir_len(char const *file) | |
63 | { | |
64 | size_t length; | |
3ed08d1e HH |
65 | |
66 | if(!file) | |
67 | return 0; | |
68 | ||
026b81e9 | 69 | /* Strip the basename and any redundant slashes before it. */ |
68c49db9 | 70 | for (length = strlen(file)-1; 0 < length; length--) |
f9c7788b | 71 | if (file[length] == '/' && file[length-1] != '/') |
026b81e9 HH |
72 | break; |
73 | return length; | |
74 | } | |
75 | ||
76 | static char *convert_abs_rel(const char *from, const char *target) | |
77 | { | |
78 | /* we use the 4*MAXPATHLEN, which should not overrun */ | |
79 | char relative_from[MAXPATHLEN * 4]; | |
3ed08d1e HH |
80 | _cleanup_free_ char *realtarget = NULL; |
81 | _cleanup_free_ char *target_dir_p = NULL, *realpath_p = NULL; | |
026b81e9 | 82 | const char *realfrom = from; |
599182b1 KR |
83 | size_t level = 0, fromlevel = 0, targetlevel = 0; |
84 | int l; | |
85 | size_t i, rl, dirlen; | |
d9eff33c | 86 | int ret; |
026b81e9 | 87 | |
3ed08d1e HH |
88 | target_dir_p = strdup(target); |
89 | if (!target_dir_p) | |
90 | return strdup(from); | |
91 | ||
92 | dirlen = dir_len(target_dir_p); | |
93 | target_dir_p[dirlen] = '\0'; | |
94 | realpath_p = realpath(target_dir_p, NULL); | |
026b81e9 | 95 | |
3ed08d1e | 96 | if (realpath_p == NULL) { |
026b81e9 HH |
97 | log_warning("convert_abs_rel(): target '%s' directory has no realpath.", target); |
98 | return strdup(from); | |
99 | } | |
100 | ||
f9c7788b CG |
101 | /* dir_len() skips double /'s e.g. //lib64, so we can't skip just one |
102 | * character - need to skip all leading /'s */ | |
103 | rl = strlen(target); | |
104 | for (i = dirlen+1; i < rl; ++i) | |
3ed08d1e | 105 | if (target_dir_p[i] != '/') |
f9c7788b | 106 | break; |
d9eff33c KR |
107 | ret = asprintf(&realtarget, "%s/%s", realpath_p, &target_dir_p[i]); |
108 | if (ret < 0) { | |
109 | log_error("Out of memory!"); | |
110 | exit(EXIT_FAILURE); | |
111 | } | |
026b81e9 HH |
112 | |
113 | /* now calculate the relative path from <from> to <target> and | |
114 | store it in <relative_from> | |
115 | */ | |
116 | relative_from[0] = 0; | |
117 | rl = 0; | |
118 | ||
119 | /* count the pathname elements of realtarget */ | |
120 | for (targetlevel = 0, i = 0; realtarget[i]; i++) | |
121 | if (realtarget[i] == '/') | |
122 | targetlevel++; | |
123 | ||
124 | /* count the pathname elements of realfrom */ | |
125 | for (fromlevel = 0, i = 0; realfrom[i]; i++) | |
126 | if (realfrom[i] == '/') | |
127 | fromlevel++; | |
128 | ||
129 | /* count the pathname elements, which are common for both paths */ | |
130 | for (level = 0, i = 0; realtarget[i] && (realtarget[i] == realfrom[i]); i++) | |
131 | if (realtarget[i] == '/') | |
132 | level++; | |
133 | ||
026b81e9 HH |
134 | /* add "../" to the relative_from path, until the common pathname is |
135 | reached */ | |
136 | for (i = level; i < targetlevel; i++) { | |
137 | if (i != level) | |
138 | relative_from[rl++] = '/'; | |
139 | relative_from[rl++] = '.'; | |
140 | relative_from[rl++] = '.'; | |
141 | } | |
142 | ||
143 | /* set l to the next uncommon pathname element in realfrom */ | |
144 | for (l = 1, i = 1; i < level; i++) | |
145 | for (l++; realfrom[l] && realfrom[l] != '/'; l++) ; | |
146 | /* skip next '/' */ | |
147 | l++; | |
148 | ||
149 | /* append the uncommon rest of realfrom to the relative_from path */ | |
150 | for (i = level; i <= fromlevel; i++) { | |
151 | if (rl) | |
152 | relative_from[rl++] = '/'; | |
153 | while (realfrom[l] && realfrom[l] != '/') | |
154 | relative_from[rl++] = realfrom[l++]; | |
155 | l++; | |
156 | } | |
157 | ||
158 | relative_from[rl] = 0; | |
159 | return strdup(relative_from); | |
160 | } | |
161 | ||
162 | static int ln_r(const char *src, const char *dst) | |
163 | { | |
164 | int ret; | |
3ed08d1e HH |
165 | _cleanup_free_ const char *points_to = convert_abs_rel(src, dst); |
166 | ||
026b81e9 HH |
167 | log_info("ln -s '%s' '%s'", points_to, dst); |
168 | ret = symlink(points_to, dst); | |
169 | ||
170 | if (ret != 0) { | |
171 | log_error("ERROR: ln -s '%s' '%s': %m", points_to, dst); | |
026b81e9 HH |
172 | return 1; |
173 | } | |
174 | ||
026b81e9 HH |
175 | return 0; |
176 | } | |
177 | ||
85854b24 HH |
178 | /* Perform the O(1) btrfs clone operation, if possible. |
179 | Upon success, return 0. Otherwise, return -1 and set errno. */ | |
180 | static inline int clone_file(int dest_fd, int src_fd) | |
181 | { | |
182 | #undef BTRFS_IOCTL_MAGIC | |
183 | #define BTRFS_IOCTL_MAGIC 0x94 | |
184 | #undef BTRFS_IOC_CLONE | |
185 | #define BTRFS_IOC_CLONE _IOW (BTRFS_IOCTL_MAGIC, 9, int) | |
186 | return ioctl(dest_fd, BTRFS_IOC_CLONE, src_fd); | |
187 | } | |
188 | ||
189 | static bool use_clone = true; | |
190 | ||
026b81e9 HH |
191 | static int cp(const char *src, const char *dst) |
192 | { | |
193 | int pid; | |
3ed08d1e | 194 | int ret = 0; |
85854b24 | 195 | |
f6c2faeb | 196 | if (use_clone) { |
85854b24 | 197 | struct stat sb; |
3ed08d1e | 198 | _cleanup_close_ int dest_desc = -1, source_desc = -1; |
85854b24 HH |
199 | |
200 | if (lstat(src, &sb) != 0) | |
201 | goto normal_copy; | |
202 | ||
203 | if (S_ISLNK(sb.st_mode)) | |
204 | goto normal_copy; | |
205 | ||
206 | source_desc = open(src, O_RDONLY | O_CLOEXEC); | |
207 | if (source_desc < 0) | |
208 | goto normal_copy; | |
026b81e9 | 209 | |
85854b24 | 210 | dest_desc = |
f6c2faeb HH |
211 | open(dst, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, |
212 | (sb.st_mode) & (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)); | |
85854b24 HH |
213 | |
214 | if (dest_desc < 0) { | |
85854b24 HH |
215 | goto normal_copy; |
216 | } | |
217 | ||
218 | ret = clone_file(dest_desc, source_desc); | |
3ed08d1e | 219 | |
85854b24 | 220 | if (ret == 0) { |
6f4c2dad | 221 | struct timeval tv[2]; |
85854b24 | 222 | if (fchown(dest_desc, sb.st_uid, sb.st_gid) != 0) |
599182b1 | 223 | fchown(dest_desc, (__uid_t)-1, sb.st_gid); |
6f4c2dad HH |
224 | tv[0].tv_sec = sb.st_atime; |
225 | tv[0].tv_usec = 0; | |
226 | tv[1].tv_sec = sb.st_mtime; | |
227 | tv[1].tv_usec = 0; | |
228 | futimes(dest_desc, tv); | |
85854b24 HH |
229 | return ret; |
230 | } | |
231 | close(dest_desc); | |
3ed08d1e | 232 | dest_desc = -1; |
85854b24 HH |
233 | /* clone did not work, remove the file */ |
234 | unlink(dst); | |
235 | /* do not try clone again */ | |
236 | use_clone = false; | |
237 | } | |
238 | ||
239 | normal_copy: | |
026b81e9 HH |
240 | pid = fork(); |
241 | if (pid == 0) { | |
6f4c2dad | 242 | execlp("cp", "cp", "--reflink=auto", "--sparse=auto", "--preserve=mode,timestamps", "-fL", src, dst, NULL); |
026b81e9 HH |
243 | _exit(EXIT_FAILURE); |
244 | } | |
245 | ||
85854b24 | 246 | while (waitpid(pid, &ret, 0) < 0) { |
026b81e9 | 247 | if (errno != EINTR) { |
85854b24 | 248 | ret = -1; |
3ed08d1e | 249 | log_error("Failed: cp --reflink=auto --sparse=auto --preserve=mode,timestamps -fL %s %s", src, dst); |
026b81e9 HH |
250 | break; |
251 | } | |
252 | } | |
3ed08d1e | 253 | log_debug("cp ret = %d", ret); |
85854b24 | 254 | return ret; |
026b81e9 HH |
255 | } |
256 | ||
791532b0 HH |
257 | static int library_install(const char *src, const char *lib) |
258 | { | |
259 | _cleanup_free_ char *p = NULL; | |
260 | _cleanup_free_ char *pdir = NULL, *ppdir = NULL, *clib = NULL; | |
261 | char *q; | |
262 | int r, ret = 0; | |
263 | ||
264 | p = strdup(lib); | |
265 | ||
266 | r = dracut_install(p, p, false, false, true); | |
267 | if (r != 0) | |
268 | log_error("ERROR: failed to install '%s' for '%s'", p, src); | |
269 | else | |
270 | log_debug("Lib install: '%s'", p); | |
271 | ret += r; | |
272 | ||
273 | /* also install lib.so for lib.so.* files */ | |
274 | q = strstr(p, ".so."); | |
275 | if (q) { | |
276 | q[3] = '\0'; | |
277 | ||
278 | /* ignore errors for base lib symlink */ | |
279 | if (dracut_install(p, p, false, false, true) == 0) | |
280 | log_debug("Lib install: '%s'", p); | |
281 | } | |
282 | ||
283 | /* Also try to install the same library from one directory above. | |
284 | This fixes the case, where only the HWCAP lib would be installed | |
285 | # ldconfig -p|fgrep libc.so | |
286 | libc.so.6 (libc6,64bit, hwcap: 0x0000001000000000, OS ABI: Linux 2.6.32) => /lib64/power6/libc.so.6 | |
287 | libc.so.6 (libc6,64bit, hwcap: 0x0000000000000200, OS ABI: Linux 2.6.32) => /lib64/power6x/libc.so.6 | |
288 | libc.so.6 (libc6,64bit, OS ABI: Linux 2.6.32) => /lib64/libc.so.6 | |
289 | */ | |
290 | ||
291 | free(p); | |
292 | p = strdup(lib); | |
293 | ||
294 | pdir = dirname(p); | |
295 | if (!pdir) | |
296 | return ret; | |
297 | ||
298 | pdir = strdup(pdir); | |
299 | ppdir = dirname(pdir); | |
300 | if (!ppdir) | |
301 | return ret; | |
302 | ||
303 | ppdir = strdup(ppdir); | |
304 | ||
305 | strcpy(p, lib); | |
306 | ||
307 | clib = strjoin(ppdir, "/", basename(p), NULL); | |
308 | if (dracut_install(clib, clib, false, false, true) == 0) | |
309 | log_debug("Lib install: '%s'", clib); | |
310 | /* also install lib.so for lib.so.* files */ | |
311 | q = strstr(clib, ".so."); | |
312 | if (q) { | |
313 | q[3] = '\0'; | |
314 | ||
315 | /* ignore errors for base lib symlink */ | |
316 | if (dracut_install(clib, clib, false, false, true) == 0) | |
317 | log_debug("Lib install: '%s'", p); | |
318 | } | |
319 | ||
320 | return ret; | |
321 | } | |
322 | ||
026b81e9 HH |
323 | static int resolve_deps(const char *src) |
324 | { | |
325 | int ret = 0; | |
326 | ||
e0904b27 | 327 | _cleanup_free_ char *buf = malloc(LINE_MAX); |
026b81e9 | 328 | size_t linesize = LINE_MAX; |
3ed08d1e HH |
329 | _cleanup_pclose_ FILE *fptr = NULL; |
330 | _cleanup_free_ char *cmd = NULL; | |
026b81e9 HH |
331 | |
332 | if (strstr(src, ".so") == 0) { | |
3ed08d1e | 333 | _cleanup_close_ int fd = -1; |
026b81e9 | 334 | fd = open(src, O_RDONLY | O_CLOEXEC); |
3ed08d1e HH |
335 | if (fd < 0) |
336 | return -errno; | |
337 | ||
026b81e9 HH |
338 | read(fd, buf, LINE_MAX); |
339 | buf[LINE_MAX - 1] = '\0'; | |
026b81e9 HH |
340 | if (buf[0] == '#' && buf[1] == '!') { |
341 | /* we have a shebang */ | |
342 | char *p, *q; | |
343 | for (p = &buf[2]; *p && isspace(*p); p++) ; | |
344 | for (q = p; *q && (!isspace(*q)); q++) ; | |
345 | *q = '\0'; | |
346 | log_debug("Script install: '%s'", p); | |
e74944ee | 347 | ret = dracut_install(p, p, false, true, false); |
026b81e9 HH |
348 | if (ret != 0) |
349 | log_error("ERROR: failed to install '%s'", p); | |
350 | return ret; | |
351 | } | |
352 | } | |
353 | ||
354 | /* run ldd */ | |
3ed08d1e | 355 | ret = asprintf(&cmd, "ldd %s 2>&1", src); |
d9eff33c KR |
356 | if (ret < 0) { |
357 | log_error("Out of memory!"); | |
358 | exit(EXIT_FAILURE); | |
359 | } | |
360 | ||
3ed08d1e HH |
361 | ret = 0; |
362 | ||
026b81e9 HH |
363 | fptr = popen(cmd, "r"); |
364 | ||
365 | while (!feof(fptr)) { | |
366 | char *p, *q; | |
367 | ||
368 | if (getline(&buf, &linesize, fptr) <= 0) | |
369 | continue; | |
370 | ||
371 | log_debug("ldd: '%s'", buf); | |
372 | ||
b4dc22ca | 373 | if (strstr(buf, "you do not have execution permission")) { |
599182b1 | 374 | log_error("%s", buf); |
b4dc22ca HH |
375 | ret+=1; |
376 | break; | |
377 | } | |
378 | ||
026b81e9 HH |
379 | if (strstr(buf, "not a dynamic executable")) |
380 | break; | |
381 | ||
a9231107 HH |
382 | if (strstr(buf, "loader cannot load itself")) |
383 | break; | |
384 | ||
43a050e5 HH |
385 | if (strstr(buf, "not regular file")) |
386 | break; | |
87dc81a1 HH |
387 | |
388 | if (strstr(buf, "cannot read header")) | |
389 | break; | |
390 | ||
391 | if (strstr(buf, destrootdir)) | |
392 | break; | |
43a050e5 | 393 | |
791532b0 | 394 | p = strchr(buf, '/'); |
026b81e9 | 395 | if (p) { |
026b81e9 HH |
396 | for (q = p; *q && *q != ' ' && *q != '\n'; q++) ; |
397 | *q = '\0'; | |
791532b0 HH |
398 | |
399 | ret += library_install(src, p); | |
400 | ||
026b81e9 HH |
401 | } |
402 | } | |
026b81e9 HH |
403 | |
404 | return ret; | |
405 | } | |
406 | ||
407 | /* Install ".<filename>.hmac" file for FIPS self-checks */ | |
cce471c9 | 408 | static int hmac_install(const char *src, const char *dst, const char *hmacpath) |
026b81e9 | 409 | { |
3ed08d1e HH |
410 | _cleanup_free_ char *srcpath = strdup(src); |
411 | _cleanup_free_ char *dstpath = strdup(dst); | |
412 | _cleanup_free_ char *srchmacname = NULL; | |
413 | _cleanup_free_ char *dsthmacname = NULL; | |
d9eff33c | 414 | int ret; |
3ed08d1e HH |
415 | |
416 | if (!(srcpath && dstpath)) | |
417 | return -ENOMEM; | |
418 | ||
026b81e9 HH |
419 | size_t dlen = dir_len(src); |
420 | ||
421 | if (endswith(src, ".hmac")) | |
422 | return 0; | |
423 | ||
cce471c9 MB |
424 | if (!hmacpath) { |
425 | hmac_install(src, dst, "/lib/fipscheck"); | |
426 | hmac_install(src, dst, "/lib64/fipscheck"); | |
6f4c2dad HH |
427 | hmac_install(src, dst, "/lib/hmaccalc"); |
428 | hmac_install(src, dst, "/lib64/hmaccalc"); | |
cce471c9 MB |
429 | } |
430 | ||
026b81e9 HH |
431 | srcpath[dlen] = '\0'; |
432 | dstpath[dir_len(dst)] = '\0'; | |
cce471c9 | 433 | if (hmacpath) { |
d9eff33c KR |
434 | ret = asprintf(&srchmacname, "%s/%s.hmac", hmacpath, &src[dlen + 1]); |
435 | if (ret < 0) { | |
436 | log_error("Out of memory!"); | |
437 | exit(EXIT_FAILURE); | |
438 | } | |
439 | ||
440 | ret = asprintf(&dsthmacname, "%s/%s.hmac", hmacpath, &src[dlen + 1]); | |
441 | if (ret < 0) { | |
442 | log_error("Out of memory!"); | |
443 | exit(EXIT_FAILURE); | |
444 | } | |
cce471c9 | 445 | } else { |
d9eff33c KR |
446 | ret = asprintf(&srchmacname, "%s/.%s.hmac", srcpath, &src[dlen + 1]); |
447 | if (ret < 0) { | |
448 | log_error("Out of memory!"); | |
449 | exit(EXIT_FAILURE); | |
450 | } | |
451 | ||
452 | ret = asprintf(&dsthmacname, "%s/.%s.hmac", dstpath, &src[dlen + 1]); | |
453 | if (ret < 0) { | |
454 | log_error("Out of memory!"); | |
455 | exit(EXIT_FAILURE); | |
456 | } | |
cce471c9 | 457 | } |
026b81e9 | 458 | log_debug("hmac cp '%s' '%s')", srchmacname, dsthmacname); |
e74944ee | 459 | dracut_install(srchmacname, dsthmacname, false, false, true); |
026b81e9 HH |
460 | return 0; |
461 | } | |
462 | ||
e74944ee | 463 | static int dracut_install(const char *src, const char *dst, bool isdir, bool resolvedeps, bool hashdst) |
026b81e9 HH |
464 | { |
465 | struct stat sb, db; | |
3ed08d1e HH |
466 | _cleanup_free_ char *fulldstpath = NULL; |
467 | _cleanup_free_ char *fulldstdir = NULL; | |
026b81e9 HH |
468 | int ret; |
469 | bool src_exists = true; | |
3ed08d1e HH |
470 | char *i = NULL; |
471 | char *existing; | |
026b81e9 HH |
472 | |
473 | log_debug("dracut_install('%s', '%s')", src, dst); | |
474 | ||
475 | existing = hashmap_get(items_failed, src); | |
476 | if (existing) { | |
477 | if (strcmp(existing, src) == 0) { | |
478 | log_debug("hash hit items_failed for '%s'", src); | |
479 | return 1; | |
480 | } | |
481 | } | |
482 | ||
e74944ee HH |
483 | if (hashdst) { |
484 | existing = hashmap_get(items, dst); | |
485 | if (existing) { | |
486 | if (strcmp(existing, dst) == 0) { | |
487 | log_debug("hash hit items for '%s'", dst); | |
488 | return 0; | |
489 | } | |
026b81e9 HH |
490 | } |
491 | } | |
492 | ||
493 | if (lstat(src, &sb) < 0) { | |
494 | src_exists = false; | |
495 | if (!isdir) { | |
496 | i = strdup(src); | |
497 | hashmap_put(items_failed, i, i); | |
498 | /* src does not exist */ | |
499 | return 1; | |
500 | } | |
501 | } | |
502 | ||
503 | i = strdup(dst); | |
3ed08d1e HH |
504 | if (!i) |
505 | return -ENOMEM; | |
506 | ||
026b81e9 HH |
507 | hashmap_put(items, i, i); |
508 | ||
d9eff33c KR |
509 | ret = asprintf(&fulldstpath, "%s%s", destrootdir, dst); |
510 | if (ret < 0) { | |
511 | log_error("Out of memory!"); | |
512 | exit(EXIT_FAILURE); | |
513 | } | |
026b81e9 HH |
514 | |
515 | ret = stat(fulldstpath, &sb); | |
516 | ||
517 | if (ret != 0 && (errno != ENOENT)) { | |
518 | log_error("ERROR: stat '%s': %m", fulldstpath); | |
519 | return 1; | |
520 | } | |
521 | ||
522 | if (ret == 0) { | |
43a050e5 | 523 | if (resolvedeps && S_ISREG(sb.st_mode) && (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { |
68318328 CG |
524 | log_debug("'%s' already exists, but checking for any deps", fulldstpath); |
525 | ret = resolve_deps(src); | |
526 | } else | |
527 | log_debug("'%s' already exists", fulldstpath); | |
528 | ||
026b81e9 | 529 | /* dst does already exist */ |
68318328 | 530 | return ret; |
026b81e9 HH |
531 | } |
532 | ||
533 | /* check destination directory */ | |
534 | fulldstdir = strdup(fulldstpath); | |
535 | fulldstdir[dir_len(fulldstdir)] = '\0'; | |
536 | ||
537 | ret = stat(fulldstdir, &db); | |
538 | ||
539 | if (ret < 0) { | |
3ed08d1e HH |
540 | _cleanup_free_ char *dname = NULL; |
541 | ||
026b81e9 HH |
542 | if (errno != ENOENT) { |
543 | log_error("ERROR: stat '%s': %m", fulldstdir); | |
544 | return 1; | |
545 | } | |
546 | /* create destination directory */ | |
547 | log_debug("dest dir '%s' does not exist", fulldstdir); | |
548 | dname = strdup(dst); | |
3ed08d1e HH |
549 | if (!dname) |
550 | return 1; | |
551 | ||
026b81e9 | 552 | dname[dir_len(dname)] = '\0'; |
e74944ee | 553 | ret = dracut_install(dname, dname, true, false, true); |
026b81e9 | 554 | |
026b81e9 HH |
555 | if (ret != 0) { |
556 | log_error("ERROR: failed to create directory '%s'", fulldstdir); | |
026b81e9 HH |
557 | return 1; |
558 | } | |
559 | } | |
560 | ||
026b81e9 HH |
561 | if (isdir && !src_exists) { |
562 | log_info("mkdir '%s'", fulldstpath); | |
3ed08d1e HH |
563 | ret = mkdir(fulldstpath, 0755); |
564 | return ret; | |
026b81e9 HH |
565 | } |
566 | ||
567 | /* ready to install src */ | |
568 | ||
569 | if (S_ISDIR(sb.st_mode)) { | |
570 | log_info("mkdir '%s'", fulldstpath); | |
3ed08d1e HH |
571 | ret = mkdir(fulldstpath, sb.st_mode | S_IWUSR); |
572 | return ret; | |
026b81e9 HH |
573 | } |
574 | ||
575 | if (S_ISLNK(sb.st_mode)) { | |
3ed08d1e | 576 | _cleanup_free_ char *abspath = NULL; |
026b81e9 HH |
577 | |
578 | abspath = realpath(src, NULL); | |
579 | ||
580 | if (abspath == NULL) | |
581 | return 1; | |
582 | ||
e74944ee | 583 | if (dracut_install(abspath, abspath, false, resolvedeps, hashdst)) { |
026b81e9 HH |
584 | log_debug("'%s' install error", abspath); |
585 | return 1; | |
586 | } | |
587 | ||
588 | if (lstat(abspath, &sb) != 0) { | |
589 | log_debug("lstat '%s': %m", abspath); | |
590 | return 1; | |
591 | } | |
592 | ||
593 | if (lstat(fulldstpath, &sb) != 0) { | |
3ed08d1e | 594 | _cleanup_free_ char *absdestpath = NULL; |
026b81e9 | 595 | |
d9eff33c KR |
596 | ret = asprintf(&absdestpath, "%s%s", destrootdir, abspath); |
597 | if (ret < 0) { | |
598 | log_error("Out of memory!"); | |
599 | exit(EXIT_FAILURE); | |
600 | } | |
026b81e9 HH |
601 | |
602 | ln_r(absdestpath, fulldstpath); | |
026b81e9 HH |
603 | } |
604 | ||
026b81e9 HH |
605 | if (arg_hmac) { |
606 | /* copy .hmac files also */ | |
cce471c9 | 607 | hmac_install(src, dst, NULL); |
026b81e9 HH |
608 | } |
609 | ||
610 | return 0; | |
611 | } | |
612 | ||
613 | if (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { | |
614 | if (resolvedeps) | |
615 | ret += resolve_deps(src); | |
616 | if (arg_hmac) { | |
617 | /* copy .hmac files also */ | |
cce471c9 | 618 | hmac_install(src, dst, NULL); |
026b81e9 HH |
619 | } |
620 | } | |
621 | ||
3ed08d1e | 622 | log_debug("dracut_install ret = %d", ret); |
026b81e9 HH |
623 | log_info("cp '%s' '%s'", src, fulldstpath); |
624 | ret += cp(src, fulldstpath); | |
3ed08d1e HH |
625 | |
626 | log_debug("dracut_install ret = %d", ret); | |
627 | ||
026b81e9 HH |
628 | return ret; |
629 | } | |
630 | ||
631 | static void item_free(char *i) | |
632 | { | |
633 | assert(i); | |
634 | free(i); | |
635 | } | |
636 | ||
637 | static void usage(int status) | |
3ed08d1e HH |
638 | { |
639 | /* */ | |
640 | printf("Usage: %s -D DESTROOTDIR [OPTION]... -a SOURCE...\n" | |
641 | "or: %s -D DESTROOTDIR [OPTION]... SOURCE DEST\n" | |
642 | "\n" | |
643 | "Install SOURCE to DEST in DESTROOTDIR with all needed dependencies.\n" | |
644 | "\n" | |
645 | " -D --destrootdir Install all files to DESTROOTDIR as the root\n" | |
646 | " -a --all Install all SOURCE arguments to DESTROOTDIR\n" | |
647 | " -o --optional If SOURCE does not exist, do not fail\n" | |
648 | " -d --dir SOURCE is a directory\n" | |
649 | " -l --ldd Also install shebang executables and libraries\n" | |
650 | " -R --resolvelazy Only install shebang executables and libraries\n" | |
651 | " for all SOURCE files\n" | |
652 | " -H --fips Also install all '.SOURCE.hmac' files\n" | |
653 | " -v --verbose Show more output\n" | |
654 | " --debug Show debug output\n" | |
655 | " --version Show package version\n" | |
656 | " -h --help Show this help\n" | |
657 | "\n" | |
658 | "Example:\n" | |
659 | "# mkdir -p /var/tmp/test-root\n" | |
660 | "# %s -D /var/tmp/test-root --ldd -a sh tr\n" | |
661 | "# tree /var/tmp/test-root\n" | |
662 | "/var/tmp/test-root\n" | |
663 | "|-- lib64 -> usr/lib64\n" | |
664 | "`-- usr\n" | |
665 | " |-- bin\n" | |
666 | " | |-- bash\n" | |
667 | " | |-- sh -> bash\n" | |
668 | " | `-- tr\n" | |
669 | " `-- lib64\n" | |
670 | " |-- ld-2.15.90.so\n" | |
671 | " |-- ld-linux-x86-64.so.2 -> ld-2.15.90.so\n" | |
672 | " |-- libc-2.15.90.so\n" | |
673 | " |-- libc.so\n" | |
674 | " |-- libc.so.6 -> libc-2.15.90.so\n" | |
675 | " |-- libdl-2.15.90.so\n" | |
676 | " |-- libdl.so -> libdl-2.15.90.so\n" | |
677 | " |-- libdl.so.2 -> libdl-2.15.90.so\n" | |
678 | " |-- libtinfo.so.5 -> libtinfo.so.5.9\n" | |
679 | " `-- libtinfo.so.5.9\n" | |
680 | , program_invocation_short_name, program_invocation_short_name, program_invocation_short_name); | |
026b81e9 HH |
681 | exit(status); |
682 | } | |
683 | ||
684 | static int parse_argv(int argc, char *argv[]) | |
685 | { | |
686 | int c; | |
687 | ||
688 | enum { | |
689 | ARG_VERSION = 0x100, | |
690 | ARG_DEBUG | |
691 | }; | |
692 | ||
3ed08d1e | 693 | static struct option const options[] = { |
026b81e9 HH |
694 | {"help", no_argument, NULL, 'h'}, |
695 | {"version", no_argument, NULL, ARG_VERSION}, | |
696 | {"dir", no_argument, NULL, 'd'}, | |
697 | {"debug", no_argument, NULL, ARG_DEBUG}, | |
698 | {"verbose", no_argument, NULL, 'v'}, | |
699 | {"ldd", no_argument, NULL, 'l'}, | |
700 | {"resolvelazy", no_argument, NULL, 'R'}, | |
701 | {"optional", no_argument, NULL, 'o'}, | |
702 | {"all", no_argument, NULL, 'a'}, | |
703 | {"fips", no_argument, NULL, 'H'}, | |
704 | {"destrootdir", required_argument, NULL, 'D'}, | |
705 | {NULL, 0, NULL, 0} | |
706 | }; | |
707 | ||
6a736cc1 | 708 | while ((c = getopt_long(argc, argv, "adhloD:HR", options, NULL)) != -1) { |
026b81e9 HH |
709 | switch (c) { |
710 | case ARG_VERSION: | |
711 | puts(PROGRAM_VERSION_STRING); | |
712 | return 0; | |
713 | case 'd': | |
714 | arg_createdir = true; | |
715 | break; | |
716 | case ARG_DEBUG: | |
717 | arg_loglevel = LOG_DEBUG; | |
718 | break; | |
719 | case 'v': | |
720 | arg_loglevel = LOG_INFO; | |
721 | break; | |
722 | case 'o': | |
723 | arg_optional = true; | |
724 | break; | |
725 | case 'l': | |
726 | arg_resolvedeps = true; | |
727 | break; | |
728 | case 'R': | |
729 | arg_resolvelazy = true; | |
730 | break; | |
731 | case 'a': | |
732 | arg_all = true; | |
733 | break; | |
734 | case 'D': | |
735 | destrootdir = strdup(optarg); | |
736 | break; | |
737 | case 'H': | |
738 | arg_hmac = true; | |
739 | break; | |
740 | case 'h': | |
741 | usage(EXIT_SUCCESS); | |
742 | break; | |
743 | default: | |
744 | usage(EXIT_FAILURE); | |
745 | } | |
746 | } | |
747 | ||
748 | if (!optind || optind == argc) { | |
34e43ceb | 749 | log_error("No SOURCE argument given"); |
026b81e9 HH |
750 | usage(EXIT_FAILURE); |
751 | } | |
752 | ||
753 | return 1; | |
754 | } | |
755 | ||
756 | static int resolve_lazy(int argc, char **argv) | |
757 | { | |
758 | int i; | |
599182b1 | 759 | size_t destrootdirlen = strlen(destrootdir); |
026b81e9 HH |
760 | int ret = 0; |
761 | char *item; | |
762 | for (i = 0; i < argc; i++) { | |
763 | const char *src = argv[i]; | |
764 | char *p = argv[i]; | |
765 | char *existing; | |
766 | ||
767 | log_debug("resolve_deps('%s')", src); | |
768 | ||
769 | if (strstr(src, destrootdir)) { | |
770 | p = &argv[i][destrootdirlen]; | |
771 | } | |
772 | ||
773 | existing = hashmap_get(items, p); | |
774 | if (existing) { | |
775 | if (strcmp(existing, p) == 0) | |
776 | continue; | |
777 | } | |
778 | ||
779 | item = strdup(p); | |
780 | hashmap_put(items, item, item); | |
781 | ||
782 | ret += resolve_deps(src); | |
783 | } | |
784 | return ret; | |
785 | } | |
786 | ||
868eba13 HH |
787 | static char *find_binary(const char *src) |
788 | { | |
3ed08d1e | 789 | _cleanup_free_ char *path = NULL; |
868eba13 HH |
790 | char *p, *q; |
791 | bool end = false; | |
792 | char *newsrc = NULL; | |
d9eff33c KR |
793 | int ret; |
794 | ||
868eba13 HH |
795 | path = getenv("PATH"); |
796 | ||
797 | if (path == NULL) { | |
798 | log_error("PATH is not set"); | |
799 | exit(EXIT_FAILURE); | |
800 | } | |
801 | path = strdup(path); | |
802 | p = path; | |
3ed08d1e HH |
803 | |
804 | if (path == NULL) { | |
805 | log_error("Out of memory!"); | |
806 | exit(EXIT_FAILURE); | |
807 | } | |
808 | ||
868eba13 HH |
809 | log_debug("PATH=%s", path); |
810 | ||
811 | do { | |
812 | struct stat sb; | |
813 | ||
814 | for (q = p; *q && *q != ':'; q++) ; | |
815 | ||
816 | if (*q == '\0') | |
817 | end = true; | |
818 | else | |
819 | *q = '\0'; | |
820 | ||
d9eff33c KR |
821 | ret = asprintf(&newsrc, "%s/%s", p, src); |
822 | if (ret < 0) { | |
3ed08d1e HH |
823 | log_error("Out of memory!"); |
824 | exit(EXIT_FAILURE); | |
825 | } | |
826 | ||
868eba13 HH |
827 | p = q + 1; |
828 | ||
829 | if (stat(newsrc, &sb) != 0) { | |
830 | log_debug("stat(%s) != 0", newsrc); | |
831 | free(newsrc); | |
832 | newsrc = NULL; | |
833 | continue; | |
834 | } | |
835 | ||
836 | end = true; | |
837 | ||
838 | } while (!end); | |
839 | ||
868eba13 HH |
840 | if (newsrc) |
841 | log_debug("find_binary(%s) == %s", src, newsrc); | |
3ed08d1e | 842 | |
868eba13 HH |
843 | return newsrc; |
844 | } | |
845 | ||
846 | static int install_one(const char *src, const char *dst) | |
847 | { | |
8974102f | 848 | int r = EXIT_SUCCESS; |
868eba13 HH |
849 | int ret; |
850 | ||
851 | if (strchr(src, '/') == NULL) { | |
852 | char *newsrc = find_binary(src); | |
853 | if (newsrc) { | |
854 | log_debug("dracut_install '%s' '%s'", newsrc, dst); | |
855 | ret = dracut_install(newsrc, dst, arg_createdir, arg_resolvedeps, true); | |
856 | if (ret == 0) { | |
857 | log_debug("dracut_install '%s' '%s' OK", newsrc, dst); | |
858 | } | |
859 | free(newsrc); | |
860 | } else { | |
861 | ret = -1; | |
862 | } | |
863 | } else { | |
864 | ret = dracut_install(src, dst, arg_createdir, arg_resolvedeps, true); | |
865 | } | |
866 | ||
867 | if ((ret != 0) && (!arg_optional)) { | |
868 | log_error("ERROR: installing '%s' to '%s'", src, dst); | |
869 | r = EXIT_FAILURE; | |
870 | } | |
871 | ||
872 | return r; | |
873 | } | |
874 | ||
026b81e9 HH |
875 | static int install_all(int argc, char **argv) |
876 | { | |
8974102f | 877 | int r = EXIT_SUCCESS; |
026b81e9 HH |
878 | int i; |
879 | for (i = 0; i < argc; i++) { | |
880 | int ret; | |
881 | log_debug("Handle '%s'", argv[i]); | |
882 | ||
883 | if (strchr(argv[i], '/') == NULL) { | |
3ed08d1e | 884 | _cleanup_free_ char *newsrc = find_binary(argv[i]); |
868eba13 | 885 | if (newsrc) { |
026b81e9 | 886 | log_debug("dracut_install '%s'", newsrc); |
868eba13 | 887 | ret = dracut_install(newsrc, newsrc, arg_createdir, arg_resolvedeps, true); |
026b81e9 | 888 | if (ret == 0) { |
026b81e9 HH |
889 | log_debug("dracut_install '%s' OK", newsrc); |
890 | } | |
868eba13 HH |
891 | } else { |
892 | ret = -1; | |
893 | } | |
894 | ||
026b81e9 | 895 | } else { |
3ed08d1e | 896 | _cleanup_free_ char *dest = strdup(argv[i]); |
e74944ee | 897 | ret = dracut_install(argv[i], dest, arg_createdir, arg_resolvedeps, true); |
026b81e9 HH |
898 | } |
899 | ||
85854b24 | 900 | if ((ret != 0) && (!arg_optional)) { |
026b81e9 HH |
901 | log_error("ERROR: installing '%s'", argv[i]); |
902 | r = EXIT_FAILURE; | |
903 | } | |
904 | } | |
905 | return r; | |
906 | } | |
907 | ||
908 | int main(int argc, char **argv) | |
909 | { | |
910 | int r; | |
911 | char *i; | |
912 | ||
913 | r = parse_argv(argc, argv); | |
914 | if (r <= 0) | |
915 | return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; | |
916 | ||
917 | log_set_target(LOG_TARGET_CONSOLE); | |
918 | log_parse_environment(); | |
919 | ||
920 | if (arg_loglevel >= 0) | |
921 | log_set_max_level(arg_loglevel); | |
922 | ||
923 | log_open(); | |
924 | ||
925 | umask(0022); | |
926 | ||
f6c2faeb | 927 | if (destrootdir == NULL || strlen(destrootdir) == 0) { |
026b81e9 | 928 | destrootdir = getenv("DESTROOTDIR"); |
f6c2faeb | 929 | if (destrootdir == NULL || strlen(destrootdir) == 0) { |
026b81e9 HH |
930 | log_error("Environment DESTROOTDIR or argument -D is not set!"); |
931 | usage(EXIT_FAILURE); | |
932 | } | |
933 | destrootdir = strdup(destrootdir); | |
934 | } | |
935 | ||
f6c2faeb HH |
936 | if (strcmp(destrootdir, "/") == 0) { |
937 | log_error("Environment DESTROOTDIR or argument -D is set to '/'!"); | |
938 | usage(EXIT_FAILURE); | |
78021eac | 939 | } |
f6c2faeb | 940 | |
78021eac HH |
941 | i = destrootdir; |
942 | destrootdir = realpath(destrootdir, NULL); | |
943 | if (!destrootdir) { | |
944 | log_error("Environment DESTROOTDIR or argument -D is set to '%s': %m", i); | |
945 | r = EXIT_FAILURE; | |
946 | goto finish; | |
f6c2faeb | 947 | } |
78021eac | 948 | free(i); |
f6c2faeb | 949 | |
026b81e9 HH |
950 | items = hashmap_new(string_hash_func, string_compare_func); |
951 | items_failed = hashmap_new(string_hash_func, string_compare_func); | |
952 | ||
953 | if (!items || !items_failed) { | |
954 | log_error("Out of memory"); | |
955 | r = EXIT_FAILURE; | |
956 | goto finish; | |
957 | } | |
958 | ||
959 | r = EXIT_SUCCESS; | |
960 | ||
961 | if (((optind + 1) < argc) && (strcmp(argv[optind + 1], destrootdir) == 0)) { | |
962 | /* ugly hack for compat mode "inst src $destrootdir" */ | |
963 | if ((optind + 2) == argc) { | |
964 | argc--; | |
965 | } else { | |
966 | /* ugly hack for compat mode "inst src $destrootdir dst" */ | |
967 | if ((optind + 3) == argc) { | |
968 | argc--; | |
969 | argv[optind + 1] = argv[optind + 2]; | |
970 | } | |
971 | } | |
972 | } | |
973 | ||
974 | if (arg_resolvelazy) { | |
975 | r = resolve_lazy(argc - optind, &argv[optind]); | |
976 | } else if (arg_all || (argc - optind > 2) || ((argc - optind) == 1)) { | |
977 | r = install_all(argc - optind, &argv[optind]); | |
978 | } else { | |
979 | /* simple "inst src dst" */ | |
868eba13 | 980 | r = install_one(argv[optind], argv[optind + 1]); |
026b81e9 HH |
981 | } |
982 | ||
983 | if (arg_optional) | |
984 | r = EXIT_SUCCESS; | |
985 | ||
986 | finish: | |
987 | ||
988 | while ((i = hashmap_steal_first(items))) | |
989 | item_free(i); | |
990 | ||
991 | while ((i = hashmap_steal_first(items_failed))) | |
992 | item_free(i); | |
993 | ||
994 | hashmap_free(items); | |
995 | hashmap_free(items_failed); | |
996 | ||
997 | free(destrootdir); | |
998 | ||
999 | return r; | |
1000 | } |