You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1081 lines
34 KiB
1081 lines
34 KiB
/* dracut-install.c -- install files and executables |
|
|
|
Copyright (C) 2012 Harald Hoyer |
|
Copyright (C) 2012 Red Hat, Inc. All rights reserved. |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
under the terms of the GNU Lesser General Public License as published by |
|
the Free Software Foundation; either version 2.1 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, but |
|
WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
Lesser General Public License for more details. |
|
|
|
You should have received a copy of the GNU Lesser General Public License |
|
along with this program; If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
#define PROGRAM_VERSION_STRING "1" |
|
|
|
#ifndef _GNU_SOURCE |
|
#define _GNU_SOURCE |
|
#endif |
|
|
|
#include <ctype.h> |
|
#include <errno.h> |
|
#include <fcntl.h> |
|
#include <getopt.h> |
|
#include <libgen.h> |
|
#include <limits.h> |
|
#include <stdbool.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <sys/stat.h> |
|
#include <sys/types.h> |
|
#include <sys/wait.h> |
|
#include <unistd.h> |
|
#include <sys/ioctl.h> |
|
|
|
#include "log.h" |
|
#include "hashmap.h" |
|
#include "util.h" |
|
#include "strv.h" |
|
|
|
static bool arg_hmac = false; |
|
static bool arg_createdir = false; |
|
static int arg_loglevel = -1; |
|
static bool arg_optional = false; |
|
static bool arg_all = false; |
|
static bool arg_resolvelazy = false; |
|
static bool arg_resolvedeps = false; |
|
static bool arg_hostonly = false; |
|
static char *destrootdir = NULL; |
|
static char *logdir = NULL; |
|
static char *logfile = NULL; |
|
FILE *logfile_f = NULL; |
|
static Hashmap *items = NULL; |
|
static Hashmap *items_failed = NULL; |
|
|
|
static int dracut_install(const char *src, const char *dst, bool isdir, bool resolvedeps, bool hashdst); |
|
|
|
static size_t dir_len(char const *file) |
|
{ |
|
size_t length; |
|
|
|
if (!file) |
|
return 0; |
|
|
|
/* Strip the basename and any redundant slashes before it. */ |
|
for (length = strlen(file) - 1; 0 < length; length--) |
|
if (file[length] == '/' && file[length - 1] != '/') |
|
break; |
|
return length; |
|
} |
|
|
|
static char *convert_abs_rel(const char *from, const char *target) |
|
{ |
|
/* we use the 4*MAXPATHLEN, which should not overrun */ |
|
char relative_from[MAXPATHLEN * 4]; |
|
_cleanup_free_ char *realtarget = NULL; |
|
_cleanup_free_ char *target_dir_p = NULL, *realpath_p = NULL; |
|
const char *realfrom = from; |
|
size_t level = 0, fromlevel = 0, targetlevel = 0; |
|
int l; |
|
size_t i, rl, dirlen; |
|
int ret; |
|
|
|
target_dir_p = strdup(target); |
|
if (!target_dir_p) |
|
return strdup(from); |
|
|
|
dirlen = dir_len(target_dir_p); |
|
target_dir_p[dirlen] = '\0'; |
|
realpath_p = realpath(target_dir_p, NULL); |
|
|
|
if (realpath_p == NULL) { |
|
log_warning("convert_abs_rel(): target '%s' directory has no realpath.", target); |
|
return strdup(from); |
|
} |
|
|
|
/* dir_len() skips double /'s e.g. //lib64, so we can't skip just one |
|
* character - need to skip all leading /'s */ |
|
rl = strlen(target); |
|
for (i = dirlen + 1; i < rl; ++i) |
|
if (target_dir_p[i] != '/') |
|
break; |
|
ret = asprintf(&realtarget, "%s/%s", realpath_p, &target_dir_p[i]); |
|
if (ret < 0) { |
|
log_error("Out of memory!"); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
/* now calculate the relative path from <from> to <target> and |
|
store it in <relative_from> |
|
*/ |
|
relative_from[0] = 0; |
|
rl = 0; |
|
|
|
/* count the pathname elements of realtarget */ |
|
for (targetlevel = 0, i = 0; realtarget[i]; i++) |
|
if (realtarget[i] == '/') |
|
targetlevel++; |
|
|
|
/* count the pathname elements of realfrom */ |
|
for (fromlevel = 0, i = 0; realfrom[i]; i++) |
|
if (realfrom[i] == '/') |
|
fromlevel++; |
|
|
|
/* count the pathname elements, which are common for both paths */ |
|
for (level = 0, i = 0; realtarget[i] && (realtarget[i] == realfrom[i]); i++) |
|
if (realtarget[i] == '/') |
|
level++; |
|
|
|
/* add "../" to the relative_from path, until the common pathname is |
|
reached */ |
|
for (i = level; i < targetlevel; i++) { |
|
if (i != level) |
|
relative_from[rl++] = '/'; |
|
relative_from[rl++] = '.'; |
|
relative_from[rl++] = '.'; |
|
} |
|
|
|
/* set l to the next uncommon pathname element in realfrom */ |
|
for (l = 1, i = 1; i < level; i++) |
|
for (l++; realfrom[l] && realfrom[l] != '/'; l++) ; |
|
/* skip next '/' */ |
|
l++; |
|
|
|
/* append the uncommon rest of realfrom to the relative_from path */ |
|
for (i = level; i <= fromlevel; i++) { |
|
if (rl) |
|
relative_from[rl++] = '/'; |
|
while (realfrom[l] && realfrom[l] != '/') |
|
relative_from[rl++] = realfrom[l++]; |
|
l++; |
|
} |
|
|
|
relative_from[rl] = 0; |
|
return strdup(relative_from); |
|
} |
|
|
|
static int ln_r(const char *src, const char *dst) |
|
{ |
|
int ret; |
|
_cleanup_free_ const char *points_to = convert_abs_rel(src, dst); |
|
|
|
log_info("ln -s '%s' '%s'", points_to, dst); |
|
ret = symlink(points_to, dst); |
|
|
|
if (ret != 0) { |
|
log_error("ERROR: ln -s '%s' '%s': %m", points_to, dst); |
|
return 1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Perform the O(1) btrfs clone operation, if possible. |
|
Upon success, return 0. Otherwise, return -1 and set errno. */ |
|
static inline int clone_file(int dest_fd, int src_fd) |
|
{ |
|
#undef BTRFS_IOCTL_MAGIC |
|
#define BTRFS_IOCTL_MAGIC 0x94 |
|
#undef BTRFS_IOC_CLONE |
|
#define BTRFS_IOC_CLONE _IOW (BTRFS_IOCTL_MAGIC, 9, int) |
|
return ioctl(dest_fd, BTRFS_IOC_CLONE, src_fd); |
|
} |
|
|
|
static bool use_clone = true; |
|
|
|
static int cp(const char *src, const char *dst) |
|
{ |
|
int pid; |
|
int ret = 0; |
|
|
|
if (use_clone) { |
|
struct stat sb; |
|
_cleanup_close_ int dest_desc = -1, source_desc = -1; |
|
|
|
if (lstat(src, &sb) != 0) |
|
goto normal_copy; |
|
|
|
if (S_ISLNK(sb.st_mode)) |
|
goto normal_copy; |
|
|
|
source_desc = open(src, O_RDONLY | O_CLOEXEC); |
|
if (source_desc < 0) |
|
goto normal_copy; |
|
|
|
dest_desc = |
|
open(dst, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, |
|
(sb.st_mode) & (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)); |
|
|
|
if (dest_desc < 0) { |
|
goto normal_copy; |
|
} |
|
|
|
ret = clone_file(dest_desc, source_desc); |
|
|
|
if (ret == 0) { |
|
struct timeval tv[2]; |
|
if (fchown(dest_desc, sb.st_uid, sb.st_gid) != 0) |
|
if(fchown(dest_desc, (__uid_t) - 1, sb.st_gid) != 0) |
|
log_error("Failed to chown %s: %m", dst); |
|
tv[0].tv_sec = sb.st_atime; |
|
tv[0].tv_usec = 0; |
|
tv[1].tv_sec = sb.st_mtime; |
|
tv[1].tv_usec = 0; |
|
futimes(dest_desc, tv); |
|
return ret; |
|
} |
|
close(dest_desc); |
|
dest_desc = -1; |
|
/* clone did not work, remove the file */ |
|
unlink(dst); |
|
/* do not try clone again */ |
|
use_clone = false; |
|
} |
|
|
|
normal_copy: |
|
pid = fork(); |
|
if (pid == 0) { |
|
execlp("cp", "cp", "--reflink=auto", "--sparse=auto", "--preserve=mode,timestamps", "-fL", src, dst, |
|
NULL); |
|
_exit(EXIT_FAILURE); |
|
} |
|
|
|
while (waitpid(pid, &ret, 0) < 0) { |
|
if (errno != EINTR) { |
|
ret = -1; |
|
log_error("Failed: cp --reflink=auto --sparse=auto --preserve=mode,timestamps -fL %s %s", src, |
|
dst); |
|
break; |
|
} |
|
} |
|
log_debug("cp ret = %d", ret); |
|
return ret; |
|
} |
|
|
|
static int library_install(const char *src, const char *lib) |
|
{ |
|
_cleanup_free_ char *p = NULL; |
|
_cleanup_free_ char *pdir = NULL, *ppdir = NULL, *clib = NULL; |
|
char *q; |
|
int r, ret = 0; |
|
|
|
p = strdup(lib); |
|
|
|
r = dracut_install(p, p, false, false, true); |
|
if (r != 0) |
|
log_error("ERROR: failed to install '%s' for '%s'", p, src); |
|
else |
|
log_debug("Lib install: '%s'", p); |
|
ret += r; |
|
|
|
/* also install lib.so for lib.so.* files */ |
|
q = strstr(p, ".so."); |
|
if (q) { |
|
q[3] = '\0'; |
|
|
|
/* ignore errors for base lib symlink */ |
|
if (dracut_install(p, p, false, false, true) == 0) |
|
log_debug("Lib install: '%s'", p); |
|
} |
|
|
|
/* Also try to install the same library from one directory above. |
|
This fixes the case, where only the HWCAP lib would be installed |
|
# ldconfig -p|fgrep libc.so |
|
libc.so.6 (libc6,64bit, hwcap: 0x0000001000000000, OS ABI: Linux 2.6.32) => /lib64/power6/libc.so.6 |
|
libc.so.6 (libc6,64bit, hwcap: 0x0000000000000200, OS ABI: Linux 2.6.32) => /lib64/power6x/libc.so.6 |
|
libc.so.6 (libc6,64bit, OS ABI: Linux 2.6.32) => /lib64/libc.so.6 |
|
*/ |
|
|
|
free(p); |
|
p = strdup(lib); |
|
|
|
pdir = dirname(p); |
|
if (!pdir) |
|
return ret; |
|
|
|
pdir = strdup(pdir); |
|
ppdir = dirname(pdir); |
|
if (!ppdir) |
|
return ret; |
|
|
|
ppdir = strdup(ppdir); |
|
|
|
strcpy(p, lib); |
|
|
|
clib = strjoin(ppdir, "/", basename(p), NULL); |
|
if (dracut_install(clib, clib, false, false, true) == 0) |
|
log_debug("Lib install: '%s'", clib); |
|
/* also install lib.so for lib.so.* files */ |
|
q = strstr(clib, ".so."); |
|
if (q) { |
|
q[3] = '\0'; |
|
|
|
/* ignore errors for base lib symlink */ |
|
if (dracut_install(clib, clib, false, false, true) == 0) |
|
log_debug("Lib install: '%s'", p); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int resolve_deps(const char *src) |
|
{ |
|
int ret = 0; |
|
|
|
_cleanup_free_ char *buf = NULL; |
|
size_t linesize = LINE_MAX; |
|
_cleanup_pclose_ FILE *fptr = NULL; |
|
_cleanup_free_ char *cmd = NULL; |
|
|
|
buf = malloc(LINE_MAX); |
|
if (buf == NULL) |
|
return -errno; |
|
|
|
if (strstr(src, ".so") == 0) { |
|
_cleanup_close_ int fd = -1; |
|
fd = open(src, O_RDONLY | O_CLOEXEC); |
|
if (fd < 0) |
|
return -errno; |
|
|
|
ret = read(fd, buf, LINE_MAX); |
|
if (ret == -1) |
|
return -errno; |
|
|
|
buf[LINE_MAX - 1] = '\0'; |
|
if (buf[0] == '#' && buf[1] == '!') { |
|
/* we have a shebang */ |
|
char *p, *q; |
|
for (p = &buf[2]; *p && isspace(*p); p++) ; |
|
for (q = p; *q && (!isspace(*q)); q++) ; |
|
*q = '\0'; |
|
log_debug("Script install: '%s'", p); |
|
ret = dracut_install(p, p, false, true, false); |
|
if (ret != 0) |
|
log_error("ERROR: failed to install '%s'", p); |
|
return ret; |
|
} |
|
} |
|
|
|
/* run ldd */ |
|
ret = asprintf(&cmd, "ldd %s 2>&1", src); |
|
if (ret < 0) { |
|
log_error("Out of memory!"); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
ret = 0; |
|
|
|
fptr = popen(cmd, "r"); |
|
|
|
while (!feof(fptr)) { |
|
char *p; |
|
|
|
if (getline(&buf, &linesize, fptr) <= 0) |
|
continue; |
|
|
|
log_debug("ldd: '%s'", buf); |
|
|
|
if (strstr(buf, "you do not have execution permission")) { |
|
log_error("%s", buf); |
|
ret += 1; |
|
break; |
|
} |
|
|
|
if (strstr(buf, "not a dynamic executable")) |
|
break; |
|
|
|
if (strstr(buf, "loader cannot load itself")) |
|
break; |
|
|
|
if (strstr(buf, "not regular file")) |
|
break; |
|
|
|
if (strstr(buf, "cannot read header")) |
|
break; |
|
|
|
if (strstr(buf, destrootdir)) |
|
break; |
|
|
|
p = strstr(buf, "=>"); |
|
if (!p) |
|
p = buf; |
|
|
|
p = strchr(p, '/'); |
|
if (p) { |
|
char *q; |
|
|
|
for (q = p; *q && *q != ' ' && *q != '\n'; q++) ; |
|
*q = '\0'; |
|
|
|
ret += library_install(src, p); |
|
|
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/* Install ".<filename>.hmac" file for FIPS self-checks */ |
|
static int hmac_install(const char *src, const char *dst, const char *hmacpath) |
|
{ |
|
_cleanup_free_ char *srcpath = strdup(src); |
|
_cleanup_free_ char *dstpath = strdup(dst); |
|
_cleanup_free_ char *srchmacname = NULL; |
|
_cleanup_free_ char *dsthmacname = NULL; |
|
int ret; |
|
|
|
if (!(srcpath && dstpath)) |
|
return -ENOMEM; |
|
|
|
size_t dlen = dir_len(src); |
|
|
|
if (endswith(src, ".hmac")) |
|
return 0; |
|
|
|
if (!hmacpath) { |
|
hmac_install(src, dst, "/lib/fipscheck"); |
|
hmac_install(src, dst, "/lib64/fipscheck"); |
|
hmac_install(src, dst, "/lib/hmaccalc"); |
|
hmac_install(src, dst, "/lib64/hmaccalc"); |
|
} |
|
|
|
srcpath[dlen] = '\0'; |
|
dstpath[dir_len(dst)] = '\0'; |
|
if (hmacpath) { |
|
ret = asprintf(&srchmacname, "%s/%s.hmac", hmacpath, &src[dlen + 1]); |
|
if (ret < 0) { |
|
log_error("Out of memory!"); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
ret = asprintf(&dsthmacname, "%s/%s.hmac", hmacpath, &src[dlen + 1]); |
|
if (ret < 0) { |
|
log_error("Out of memory!"); |
|
exit(EXIT_FAILURE); |
|
} |
|
} else { |
|
ret = asprintf(&srchmacname, "%s/.%s.hmac", srcpath, &src[dlen + 1]); |
|
if (ret < 0) { |
|
log_error("Out of memory!"); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
ret = asprintf(&dsthmacname, "%s/.%s.hmac", dstpath, &src[dlen + 1]); |
|
if (ret < 0) { |
|
log_error("Out of memory!"); |
|
exit(EXIT_FAILURE); |
|
} |
|
} |
|
log_debug("hmac cp '%s' '%s')", srchmacname, dsthmacname); |
|
dracut_install(srchmacname, dsthmacname, false, false, true); |
|
return 0; |
|
} |
|
|
|
void mark_hostonly(const char *path) |
|
{ |
|
_cleanup_free_ char *fulldstpath = NULL; |
|
_cleanup_fclose_ FILE *f = NULL; |
|
int ret; |
|
|
|
ret = asprintf(&fulldstpath, "%s/lib/dracut/hostonly-files", destrootdir); |
|
if (ret < 0) { |
|
log_error("Out of memory!"); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
f = fopen(fulldstpath, "a"); |
|
|
|
if (f == NULL) { |
|
log_error("Could not open '%s' for writing.", fulldstpath); |
|
return; |
|
} |
|
|
|
fprintf(f, "%s\n", path); |
|
} |
|
|
|
void dracut_log_cp(const char *path) |
|
{ |
|
int ret; |
|
ret = fprintf(logfile_f, "%s\n", path); |
|
if (ret < 0) |
|
log_error("Could not append '%s' to logfile '%s': %m", path, logfile); |
|
} |
|
|
|
static int dracut_install(const char *src, const char *dst, bool isdir, bool resolvedeps, bool hashdst) |
|
{ |
|
struct stat sb, db; |
|
_cleanup_free_ char *fulldstpath = NULL; |
|
_cleanup_free_ char *fulldstdir = NULL; |
|
int ret; |
|
bool src_exists = true; |
|
char *i = NULL; |
|
char *existing; |
|
|
|
log_debug("dracut_install('%s', '%s')", src, dst); |
|
|
|
existing = hashmap_get(items_failed, src); |
|
if (existing) { |
|
if (strcmp(existing, src) == 0) { |
|
log_debug("hash hit items_failed for '%s'", src); |
|
return 1; |
|
} |
|
} |
|
|
|
if (hashdst) { |
|
existing = hashmap_get(items, dst); |
|
if (existing) { |
|
if (strcmp(existing, dst) == 0) { |
|
log_debug("hash hit items for '%s'", dst); |
|
return 0; |
|
} |
|
} |
|
} |
|
|
|
if (lstat(src, &sb) < 0) { |
|
src_exists = false; |
|
if (!isdir) { |
|
i = strdup(src); |
|
hashmap_put(items_failed, i, i); |
|
/* src does not exist */ |
|
return 1; |
|
} |
|
} |
|
|
|
i = strdup(dst); |
|
if (!i) |
|
return -ENOMEM; |
|
|
|
hashmap_put(items, i, i); |
|
|
|
ret = asprintf(&fulldstpath, "%s%s", destrootdir, dst); |
|
if (ret < 0) { |
|
log_error("Out of memory!"); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
ret = stat(fulldstpath, &sb); |
|
|
|
if (ret != 0 && (errno != ENOENT)) { |
|
log_error("ERROR: stat '%s': %m", fulldstpath); |
|
return 1; |
|
} |
|
|
|
if (ret == 0) { |
|
if (resolvedeps && S_ISREG(sb.st_mode) && (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { |
|
log_debug("'%s' already exists, but checking for any deps", fulldstpath); |
|
ret = resolve_deps(src); |
|
} else |
|
log_debug("'%s' already exists", fulldstpath); |
|
|
|
/* dst does already exist */ |
|
return ret; |
|
} |
|
|
|
/* check destination directory */ |
|
fulldstdir = strdup(fulldstpath); |
|
fulldstdir[dir_len(fulldstdir)] = '\0'; |
|
|
|
ret = stat(fulldstdir, &db); |
|
|
|
if (ret < 0) { |
|
_cleanup_free_ char *dname = NULL; |
|
|
|
if (errno != ENOENT) { |
|
log_error("ERROR: stat '%s': %m", fulldstdir); |
|
return 1; |
|
} |
|
/* create destination directory */ |
|
log_debug("dest dir '%s' does not exist", fulldstdir); |
|
dname = strdup(dst); |
|
if (!dname) |
|
return 1; |
|
|
|
dname[dir_len(dname)] = '\0'; |
|
ret = dracut_install(dname, dname, true, false, true); |
|
|
|
if (ret != 0) { |
|
log_error("ERROR: failed to create directory '%s'", fulldstdir); |
|
return 1; |
|
} |
|
} |
|
|
|
if (isdir && !src_exists) { |
|
log_info("mkdir '%s'", fulldstpath); |
|
ret = mkdir(fulldstpath, 0755); |
|
return ret; |
|
} |
|
|
|
/* ready to install src */ |
|
|
|
if (S_ISDIR(sb.st_mode)) { |
|
log_info("mkdir '%s'", fulldstpath); |
|
ret = mkdir(fulldstpath, sb.st_mode | S_IWUSR); |
|
return ret; |
|
} |
|
|
|
if (S_ISLNK(sb.st_mode)) { |
|
_cleanup_free_ char *abspath = NULL; |
|
|
|
abspath = realpath(src, NULL); |
|
|
|
if (abspath == NULL) |
|
return 1; |
|
|
|
if (dracut_install(abspath, abspath, false, resolvedeps, hashdst)) { |
|
log_debug("'%s' install error", abspath); |
|
return 1; |
|
} |
|
|
|
if (lstat(abspath, &sb) != 0) { |
|
log_debug("lstat '%s': %m", abspath); |
|
return 1; |
|
} |
|
|
|
if (lstat(fulldstpath, &sb) != 0) { |
|
_cleanup_free_ char *absdestpath = NULL; |
|
|
|
ret = asprintf(&absdestpath, "%s%s", destrootdir, abspath); |
|
if (ret < 0) { |
|
log_error("Out of memory!"); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
ln_r(absdestpath, fulldstpath); |
|
} |
|
|
|
if (arg_hmac) { |
|
/* copy .hmac files also */ |
|
hmac_install(src, dst, NULL); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
if (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { |
|
if (resolvedeps) |
|
ret += resolve_deps(src); |
|
if (arg_hmac) { |
|
/* copy .hmac files also */ |
|
hmac_install(src, dst, NULL); |
|
} |
|
} |
|
|
|
log_debug("dracut_install ret = %d", ret); |
|
log_info("cp '%s' '%s'", src, fulldstpath); |
|
|
|
if (arg_hostonly) |
|
mark_hostonly(dst); |
|
|
|
ret += cp(src, fulldstpath); |
|
if (ret == 0 && logfile_f) |
|
dracut_log_cp(src); |
|
|
|
log_debug("dracut_install ret = %d", ret); |
|
|
|
return ret; |
|
} |
|
|
|
static void item_free(char *i) |
|
{ |
|
assert(i); |
|
free(i); |
|
} |
|
|
|
static void usage(int status) |
|
{ |
|
/* */ |
|
printf("Usage: %s -D DESTROOTDIR [OPTION]... -a SOURCE...\n" |
|
"or: %s -D DESTROOTDIR [OPTION]... SOURCE DEST\n" |
|
"\n" |
|
"Install SOURCE to DEST in DESTROOTDIR with all needed dependencies.\n" |
|
"\n" |
|
" -D --destrootdir Install all files to DESTROOTDIR as the root\n" |
|
" -a --all Install all SOURCE arguments to DESTROOTDIR\n" |
|
" -o --optional If SOURCE does not exist, do not fail\n" |
|
" -d --dir SOURCE is a directory\n" |
|
" -l --ldd Also install shebang executables and libraries\n" |
|
" -L --logdir <DIR> Log files, which were installed from the host to <DIR>\n" |
|
" -R --resolvelazy Only install shebang executables and libraries\n" |
|
" for all SOURCE files\n" |
|
" -H --fips Also install all '.SOURCE.hmac' files\n" |
|
" -v --verbose Show more output\n" |
|
" --debug Show debug output\n" |
|
" --version Show package version\n" |
|
" -h --help Show this help\n" |
|
"\n" |
|
"Example:\n" |
|
"# mkdir -p /var/tmp/test-root\n" |
|
"# %s -D /var/tmp/test-root --ldd -a sh tr\n" |
|
"# tree /var/tmp/test-root\n" |
|
"/var/tmp/test-root\n" |
|
"|-- lib64 -> usr/lib64\n" |
|
"`-- usr\n" |
|
" |-- bin\n" |
|
" | |-- bash\n" |
|
" | |-- sh -> bash\n" |
|
" | `-- tr\n" |
|
" `-- lib64\n" |
|
" |-- ld-2.15.90.so\n" |
|
" |-- ld-linux-x86-64.so.2 -> ld-2.15.90.so\n" |
|
" |-- libc-2.15.90.so\n" |
|
" |-- libc.so\n" |
|
" |-- libc.so.6 -> libc-2.15.90.so\n" |
|
" |-- libdl-2.15.90.so\n" |
|
" |-- libdl.so -> libdl-2.15.90.so\n" |
|
" |-- libdl.so.2 -> libdl-2.15.90.so\n" |
|
" |-- libtinfo.so.5 -> libtinfo.so.5.9\n" |
|
" `-- libtinfo.so.5.9\n", program_invocation_short_name, program_invocation_short_name, |
|
program_invocation_short_name); |
|
exit(status); |
|
} |
|
|
|
static int parse_argv(int argc, char *argv[]) |
|
{ |
|
int c; |
|
|
|
enum { |
|
ARG_VERSION = 0x100, |
|
ARG_DEBUG |
|
}; |
|
|
|
static struct option const options[] = { |
|
{"help", no_argument, NULL, 'h'}, |
|
{"version", no_argument, NULL, ARG_VERSION}, |
|
{"dir", no_argument, NULL, 'd'}, |
|
{"debug", no_argument, NULL, ARG_DEBUG}, |
|
{"verbose", no_argument, NULL, 'v'}, |
|
{"ldd", no_argument, NULL, 'l'}, |
|
{"resolvelazy", no_argument, NULL, 'R'}, |
|
{"optional", no_argument, NULL, 'o'}, |
|
{"hostonly", no_argument, NULL, 'H'}, |
|
{"all", no_argument, NULL, 'a'}, |
|
{"fips", no_argument, NULL, 'f'}, |
|
{"destrootdir", required_argument, NULL, 'D'}, |
|
{"logdir", required_argument, NULL, 'L'}, |
|
{NULL, 0, NULL, 0} |
|
}; |
|
|
|
while ((c = getopt_long(argc, argv, "adfhlL:oD:HR", options, NULL)) != -1) { |
|
switch (c) { |
|
case ARG_VERSION: |
|
puts(PROGRAM_VERSION_STRING); |
|
return 0; |
|
case 'd': |
|
arg_createdir = true; |
|
break; |
|
case ARG_DEBUG: |
|
arg_loglevel = LOG_DEBUG; |
|
break; |
|
case 'v': |
|
arg_loglevel = LOG_INFO; |
|
break; |
|
case 'o': |
|
arg_optional = true; |
|
break; |
|
case 'l': |
|
arg_resolvedeps = true; |
|
break; |
|
case 'R': |
|
arg_resolvelazy = true; |
|
break; |
|
case 'a': |
|
arg_all = true; |
|
break; |
|
case 'D': |
|
destrootdir = strdup(optarg); |
|
break; |
|
case 'L': |
|
logdir = strdup(optarg); |
|
break; |
|
case 'f': |
|
arg_hmac = true; |
|
break; |
|
case 'H': |
|
arg_hostonly = true; |
|
break; |
|
case 'h': |
|
usage(EXIT_SUCCESS); |
|
break; |
|
default: |
|
usage(EXIT_FAILURE); |
|
} |
|
} |
|
|
|
if (!optind || optind == argc) { |
|
log_error("No SOURCE argument given"); |
|
usage(EXIT_FAILURE); |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
static int resolve_lazy(int argc, char **argv) |
|
{ |
|
int i; |
|
size_t destrootdirlen = strlen(destrootdir); |
|
int ret = 0; |
|
char *item; |
|
for (i = 0; i < argc; i++) { |
|
const char *src = argv[i]; |
|
char *p = argv[i]; |
|
char *existing; |
|
|
|
log_debug("resolve_deps('%s')", src); |
|
|
|
if (strstr(src, destrootdir)) { |
|
p = &argv[i][destrootdirlen]; |
|
} |
|
|
|
existing = hashmap_get(items, p); |
|
if (existing) { |
|
if (strcmp(existing, p) == 0) |
|
continue; |
|
} |
|
|
|
item = strdup(p); |
|
hashmap_put(items, item, item); |
|
|
|
ret += resolve_deps(src); |
|
} |
|
return ret; |
|
} |
|
|
|
static char **find_binary(const char *src) |
|
{ |
|
char *path = NULL; |
|
_cleanup_strv_free_ char **p = NULL; |
|
char **ret = NULL; |
|
char **q; |
|
char *newsrc = NULL; |
|
|
|
path = getenv("PATH"); |
|
|
|
if (path == NULL) { |
|
log_error("PATH is not set"); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
log_debug("PATH=%s", path); |
|
|
|
p = strv_split(path, ":"); |
|
|
|
STRV_FOREACH(q, p) { |
|
struct stat sb; |
|
int r; |
|
|
|
r = asprintf(&newsrc, "%s/%s", *q, src); |
|
if (r < 0) { |
|
log_error("Out of memory!"); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
if (stat(newsrc, &sb) != 0) { |
|
log_debug("stat(%s) != 0", newsrc); |
|
free(newsrc); |
|
newsrc = NULL; |
|
continue; |
|
} |
|
|
|
strv_push(&ret, newsrc); |
|
|
|
}; |
|
|
|
if (ret) { |
|
STRV_FOREACH(q, ret) { |
|
log_debug("find_binary(%s) == %s", src, *q); |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int install_one(const char *src, const char *dst) |
|
{ |
|
int r = EXIT_SUCCESS; |
|
int ret = 0; |
|
|
|
if (strchr(src, '/') == NULL) { |
|
char **p = find_binary(src); |
|
if (p) { |
|
char **q = NULL; |
|
STRV_FOREACH(q, p) { |
|
char *newsrc = *q; |
|
log_debug("dracut_install '%s' '%s'", newsrc, dst); |
|
ret = dracut_install(newsrc, dst, arg_createdir, arg_resolvedeps, true); |
|
if (ret == 0) { |
|
log_debug("dracut_install '%s' '%s' OK", newsrc, dst); |
|
} |
|
} |
|
strv_free(p); |
|
} else { |
|
ret = -1; |
|
} |
|
} else { |
|
ret = dracut_install(src, dst, arg_createdir, arg_resolvedeps, true); |
|
} |
|
|
|
if ((ret != 0) && (!arg_optional)) { |
|
log_error("ERROR: installing '%s' to '%s'", src, dst); |
|
r = EXIT_FAILURE; |
|
} |
|
|
|
return r; |
|
} |
|
|
|
static int install_all(int argc, char **argv) |
|
{ |
|
int r = EXIT_SUCCESS; |
|
int i; |
|
for (i = 0; i < argc; i++) { |
|
int ret = 0; |
|
log_debug("Handle '%s'", argv[i]); |
|
|
|
if (strchr(argv[i], '/') == NULL) { |
|
char **p = find_binary(argv[i]); |
|
if (p) { |
|
char **q = NULL; |
|
STRV_FOREACH(q, p) { |
|
char *newsrc = *q; |
|
log_debug("dracut_install '%s'", newsrc); |
|
ret = dracut_install(newsrc, newsrc, arg_createdir, arg_resolvedeps, true); |
|
if (ret == 0) { |
|
log_debug("dracut_install '%s' OK", newsrc); |
|
} |
|
} |
|
strv_free(p); |
|
} else { |
|
ret = -1; |
|
} |
|
|
|
} else { |
|
_cleanup_free_ char *dest = strdup(argv[i]); |
|
ret = dracut_install(argv[i], dest, arg_createdir, arg_resolvedeps, true); |
|
} |
|
|
|
if ((ret != 0) && (!arg_optional)) { |
|
log_error("ERROR: installing '%s'", argv[i]); |
|
r = EXIT_FAILURE; |
|
} |
|
} |
|
return r; |
|
} |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
int r; |
|
char *i; |
|
|
|
r = parse_argv(argc, argv); |
|
if (r <= 0) |
|
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; |
|
|
|
log_set_target(LOG_TARGET_CONSOLE); |
|
log_parse_environment(); |
|
|
|
if (arg_loglevel >= 0) |
|
log_set_max_level(arg_loglevel); |
|
|
|
log_open(); |
|
|
|
umask(0022); |
|
|
|
if (destrootdir == NULL || strlen(destrootdir) == 0) { |
|
destrootdir = getenv("DESTROOTDIR"); |
|
if (destrootdir == NULL || strlen(destrootdir) == 0) { |
|
log_error("Environment DESTROOTDIR or argument -D is not set!"); |
|
usage(EXIT_FAILURE); |
|
} |
|
destrootdir = strdup(destrootdir); |
|
} |
|
|
|
if (strcmp(destrootdir, "/") == 0) { |
|
log_error("Environment DESTROOTDIR or argument -D is set to '/'!"); |
|
usage(EXIT_FAILURE); |
|
} |
|
|
|
i = destrootdir; |
|
destrootdir = realpath(destrootdir, NULL); |
|
if (!destrootdir) { |
|
log_error("Environment DESTROOTDIR or argument -D is set to '%s': %m", i); |
|
r = EXIT_FAILURE; |
|
goto finish; |
|
} |
|
free(i); |
|
|
|
items = hashmap_new(string_hash_func, string_compare_func); |
|
items_failed = hashmap_new(string_hash_func, string_compare_func); |
|
|
|
if (!items || !items_failed) { |
|
log_error("Out of memory"); |
|
r = EXIT_FAILURE; |
|
goto finish; |
|
} |
|
|
|
if (logdir) { |
|
int ret; |
|
|
|
ret = asprintf(&logfile, "%s/%d.log", logdir, getpid()); |
|
if (ret < 0) { |
|
log_error("Out of memory!"); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
logfile_f = fopen(logfile, "a"); |
|
if (logfile_f == NULL) { |
|
log_error("Could not open %s for logging: %m", logfile); |
|
r = EXIT_FAILURE; |
|
goto finish; |
|
} |
|
} |
|
|
|
r = EXIT_SUCCESS; |
|
|
|
if (((optind + 1) < argc) && (strcmp(argv[optind + 1], destrootdir) == 0)) { |
|
/* ugly hack for compat mode "inst src $destrootdir" */ |
|
if ((optind + 2) == argc) { |
|
argc--; |
|
} else { |
|
/* ugly hack for compat mode "inst src $destrootdir dst" */ |
|
if ((optind + 3) == argc) { |
|
argc--; |
|
argv[optind + 1] = argv[optind + 2]; |
|
} |
|
} |
|
} |
|
|
|
if (arg_resolvelazy) { |
|
r = resolve_lazy(argc - optind, &argv[optind]); |
|
} else if (arg_all || (argc - optind > 2) || ((argc - optind) == 1)) { |
|
r = install_all(argc - optind, &argv[optind]); |
|
} else { |
|
/* simple "inst src dst" */ |
|
r = install_one(argv[optind], argv[optind + 1]); |
|
} |
|
|
|
if (arg_optional) |
|
r = EXIT_SUCCESS; |
|
|
|
finish: |
|
if (logfile_f) |
|
fclose(logfile_f); |
|
|
|
while ((i = hashmap_steal_first(items))) |
|
item_free(i); |
|
|
|
while ((i = hashmap_steal_first(items_failed))) |
|
item_free(i); |
|
|
|
hashmap_free(items); |
|
hashmap_free(items_failed); |
|
|
|
free(destrootdir); |
|
|
|
return r; |
|
}
|
|
|