|
|
|
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
|
|
|
|
|
|
|
/* 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"
|
|
|
|
|
|
|
|
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 char *destrootdir = 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;
|
|
|
|
/* 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];
|
|
|
|
char *realtarget = NULL;
|
|
|
|
char *p, *q;
|
|
|
|
const char *realfrom = from;
|
|
|
|
int level = 0, fromlevel = 0, targetlevel = 0;
|
|
|
|
int l, i, rl;
|
|
|
|
int dirlen;
|
|
|
|
|
|
|
|
p = strdup(target);
|
|
|
|
dirlen = dir_len(p);
|
|
|
|
p[dirlen] = '\0';
|
|
|
|
q = realpath(p, NULL);
|
|
|
|
|
|
|
|
if (q == NULL) {
|
|
|
|
free(p);
|
|
|
|
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 (p[i] != '/')
|
|
|
|
break;
|
|
|
|
asprintf(&realtarget, "%s/%s", q, &p[i]);
|
|
|
|
free(p);
|
|
|
|
free(q);
|
|
|
|
|
|
|
|
/* 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++;
|
|
|
|
|
|
|
|
free(realtarget);
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
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);
|
|
|
|
free((char *)points_to);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
free((char *)points_to);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
if (use_clone) {
|
|
|
|
struct stat sb;
|
|
|
|
int dest_desc, source_desc;
|
|
|
|
|
|
|
|
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) {
|
|
|
|
close(source_desc);
|
|
|
|
goto normal_copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = clone_file(dest_desc, source_desc);
|
|
|
|
close(source_desc);
|
|
|
|
if (ret == 0) {
|
|
|
|
if (fchown(dest_desc, sb.st_uid, sb.st_gid) != 0)
|
|
|
|
fchown(dest_desc, -1, sb.st_gid);
|
|
|
|
close(dest_desc);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
close(dest_desc);
|
|
|
|
|
|
|
|
/* 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", "-fL", src, dst, NULL);
|
|
|
|
_exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (waitpid(pid, &ret, 0) < 0) {
|
|
|
|
if (errno != EINTR) {
|
|
|
|
ret = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int resolve_deps(const char *src)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
char *buf = malloc(LINE_MAX);
|
|
|
|
size_t linesize = LINE_MAX;
|
|
|
|
FILE *fptr;
|
|
|
|
char *cmd;
|
|
|
|
|
|
|
|
if (strstr(src, ".so") == 0) {
|
|
|
|
int fd;
|
|
|
|
fd = open(src, O_RDONLY | O_CLOEXEC);
|
|
|
|
read(fd, buf, LINE_MAX);
|
|
|
|
buf[LINE_MAX - 1] = '\0';
|
|
|
|
close(fd);
|
|
|
|
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 */
|
|
|
|
asprintf(&cmd, "ldd %s 2>&1", src);
|
|
|
|
fptr = popen(cmd, "r");
|
|
|
|
|
|
|
|
while (!feof(fptr)) {
|
|
|
|
char *p, *q;
|
|
|
|
|
|
|
|
if (getline(&buf, &linesize, fptr) <= 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
log_debug("ldd: '%s'", buf);
|
|
|
|
|
|
|
|
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) {
|
|
|
|
int r;
|
|
|
|
for (q = p; *q && *q != ' ' && *q != '\n'; q++) ;
|
|
|
|
*q = '\0';
|
|
|
|
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;
|
|
|
|
*q = '\0';
|
|
|
|
|
|
|
|
/* ignore errors for base lib symlink */
|
|
|
|
if (dracut_install(p, p, false, false, true) == 0)
|
|
|
|
log_debug("Lib install: '%s'", p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pclose(fptr);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Install ".<filename>.hmac" file for FIPS self-checks */
|
|
|
|
static int hmac_install(const char *src, const char *dst, const char *hmacpath)
|
|
|
|
{
|
|
|
|
char *srcpath = strdup(src);
|
|
|
|
char *dstpath = strdup(dst);
|
|
|
|
char *srchmacname = NULL;
|
|
|
|
char *dsthmacname = NULL;
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
|
|
|
srcpath[dlen] = '\0';
|
|
|
|
dstpath[dir_len(dst)] = '\0';
|
|
|
|
if (hmacpath) {
|
|
|
|
asprintf(&srchmacname, "%s/%s.hmac", hmacpath, &src[dlen + 1]);
|
|
|
|
asprintf(&dsthmacname, "%s/%s.hmac", hmacpath, &src[dlen + 1]);
|
|
|
|
} else {
|
|
|
|
asprintf(&srchmacname, "%s/.%s.hmac", srcpath, &src[dlen + 1]);
|
|
|
|
asprintf(&dsthmacname, "%s/.%s.hmac", dstpath, &src[dlen + 1]);
|
|
|
|
}
|
|
|
|
log_debug("hmac cp '%s' '%s')", srchmacname, dsthmacname);
|
|
|
|
dracut_install(srchmacname, dsthmacname, false, false, true);
|
|
|
|
free(dsthmacname);
|
|
|
|
free(srchmacname);
|
|
|
|
free(srcpath);
|
|
|
|
free(dstpath);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dracut_install(const char *src, const char *dst, bool isdir, bool resolvedeps, bool hashdst)
|
|
|
|
{
|
|
|
|
struct stat sb, db;
|
|
|
|
char *dname = NULL;
|
|
|
|
char *fulldstpath = NULL;
|
|
|
|
char *fulldstdir = NULL;
|
|
|
|
int ret;
|
|
|
|
bool src_exists = true;
|
|
|
|
char *i, *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);
|
|
|
|
hashmap_put(items, i, i);
|
|
|
|
|
|
|
|
asprintf(&fulldstpath, "%s%s", destrootdir, dst);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
free(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) {
|
|
|
|
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);
|
|
|
|
dname[dir_len(dname)] = '\0';
|
|
|
|
ret = dracut_install(dname, dname, true, false, true);
|
|
|
|
|
|
|
|
free(dname);
|
|
|
|
|
|
|
|
if (ret != 0) {
|
|
|
|
log_error("ERROR: failed to create directory '%s'", fulldstdir);
|
|
|
|
free(fulldstdir);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
free(fulldstdir);
|
|
|
|
|
|
|
|
if (isdir && !src_exists) {
|
|
|
|
log_info("mkdir '%s'", fulldstpath);
|
|
|
|
return mkdir(fulldstpath, 0755);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ready to install src */
|
|
|
|
|
|
|
|
if (S_ISDIR(sb.st_mode)) {
|
|
|
|
log_info("mkdir '%s'", fulldstpath);
|
|
|
|
return mkdir(fulldstpath, sb.st_mode | S_IWUSR);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (S_ISLNK(sb.st_mode)) {
|
|
|
|
char *abspath;
|
|
|
|
char *absdestpath = 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) {
|
|
|
|
|
|
|
|
asprintf(&absdestpath, "%s%s", destrootdir, abspath);
|
|
|
|
|
|
|
|
ln_r(absdestpath, fulldstpath);
|
|
|
|
|
|
|
|
free(absdestpath);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(abspath);
|
|
|
|
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_info("cp '%s' '%s'", src, fulldstpath);
|
|
|
|
ret += cp(src, fulldstpath);
|
|
|
|
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\
|
|
|
|
-R --resolvelazy Only install shebang executables and libraries 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 const 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'},
|
|
|
|
{"all", no_argument, NULL, 'a'},
|
|
|
|
{"fips", no_argument, NULL, 'H'},
|
|
|
|
{"destrootdir", required_argument, NULL, 'D'},
|
|
|
|
{NULL, 0, NULL, 0}
|
|
|
|
};
|
|
|
|
|
|
|
|
while ((c = getopt_long(argc, argv, "adhloD:DHILR", 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 'H':
|
|
|
|
arg_hmac = 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;
|
|
|
|
int 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;
|
|
|
|
char *p, *q;
|
|
|
|
bool end = false;
|
|
|
|
char *newsrc = NULL;
|
|
|
|
path = getenv("PATH");
|
|
|
|
|
|
|
|
if (path == NULL) {
|
|
|
|
log_error("PATH is not set");
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
path = strdup(path);
|
|
|
|
p = path;
|
|
|
|
log_debug("PATH=%s", path);
|
|
|
|
|
|
|
|
do {
|
|
|
|
struct stat sb;
|
|
|
|
|
|
|
|
for (q = p; *q && *q != ':'; q++) ;
|
|
|
|
|
|
|
|
if (*q == '\0')
|
|
|
|
end = true;
|
|
|
|
else
|
|
|
|
*q = '\0';
|
|
|
|
|
|
|
|
asprintf(&newsrc, "%s/%s", p, src);
|
|
|
|
p = q + 1;
|
|
|
|
|
|
|
|
if (stat(newsrc, &sb) != 0) {
|
|
|
|
log_debug("stat(%s) != 0", newsrc);
|
|
|
|
free(newsrc);
|
|
|
|
newsrc = NULL;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
end = true;
|
|
|
|
|
|
|
|
} while (!end);
|
|
|
|
|
|
|
|
free(path);
|
|
|
|
if (newsrc)
|
|
|
|
log_debug("find_binary(%s) == %s", src, newsrc);
|
|
|
|
return newsrc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int install_one(const char *src, const char *dst)
|
|
|
|
{
|
|
|
|
int r = 0;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (strchr(src, '/') == NULL) {
|
|
|
|
char *newsrc = find_binary(src);
|
|
|
|
if (newsrc) {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
free(newsrc);
|
|
|
|
} 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 = 0;
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
|
|
int ret;
|
|
|
|
log_debug("Handle '%s'", argv[i]);
|
|
|
|
|
|
|
|
if (strchr(argv[i], '/') == NULL) {
|
|
|
|
char *newsrc = find_binary(argv[i]);
|
|
|
|
if (newsrc) {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
free(newsrc);
|
|
|
|
} else {
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
char *dest = strdup(argv[i]);
|
|
|
|
ret = dracut_install(argv[i], dest, arg_createdir, arg_resolvedeps, true);
|
|
|
|
free(dest);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|