/* 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 . */ #define PROGRAM_VERSION_STRING "2" #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "hashmap.h" #include "util.h" #include "strv.h" #define _asprintf(strp, fmt, ...) \ do { \ if (dracut_asprintf(strp, fmt, __VA_ARGS__) < 0) { \ log_error("Out of memory\n"); \ exit(EXIT_FAILURE); \ } \ } while (0) static bool arg_hmac = false; static bool arg_createdir = false; static int arg_loglevel = -1; static bool arg_optional = false; static bool arg_silent = false; static bool arg_all = false; static bool arg_module = false; static bool arg_modalias = false; static bool arg_resolvelazy = false; static bool arg_resolvedeps = false; static bool arg_hostonly = false; static bool no_xattr = false; static char *destrootdir = NULL; static char *sysrootdir = NULL; static size_t sysrootdirlen = 0; static char *kerneldir = NULL; static size_t kerneldirlen = 0; static char **firmwaredirs = NULL; static char **pathdirs; static char *ldd = NULL; static char *logdir = NULL; static char *logfile = NULL; FILE *logfile_f = NULL; static Hashmap *items = NULL; static Hashmap *items_failed = NULL; static Hashmap *modules_loaded = NULL; static Hashmap *modules_suppliers = NULL; static Hashmap *processed_suppliers = NULL; static regex_t mod_filter_path; static regex_t mod_filter_nopath; static regex_t mod_filter_symbol; static regex_t mod_filter_nosymbol; static regex_t mod_filter_noname; static bool arg_mod_filter_path = false; static bool arg_mod_filter_nopath = false; static bool arg_mod_filter_symbol = false; static bool arg_mod_filter_nosymbol = false; static bool arg_mod_filter_noname = false; static int dracut_install(const char *src, const char *dst, bool isdir, bool resolvedeps, bool hashdst); static int install_dependent_modules(struct kmod_ctx *ctx, struct kmod_list *modlist, Hashmap *suppliers_paths); static void item_free(char *i) { assert(i); free(i); } static inline void kmod_module_unrefp(struct kmod_module **p) { if (*p) kmod_module_unref(*p); } #define _cleanup_kmod_module_unref_ _cleanup_(kmod_module_unrefp) static inline void kmod_module_unref_listp(struct kmod_list **p) { if (*p) kmod_module_unref_list(*p); } #define _cleanup_kmod_module_unref_list_ _cleanup_(kmod_module_unref_listp) static inline void kmod_module_info_free_listp(struct kmod_list **p) { if (*p) kmod_module_info_free_list(*p); } #define _cleanup_kmod_module_info_free_list_ _cleanup_(kmod_module_info_free_listp) static inline void kmod_unrefp(struct kmod_ctx **p) { kmod_unref(*p); } #define _cleanup_kmod_unref_ _cleanup_(kmod_unrefp) static inline void kmod_module_dependency_symbols_free_listp(struct kmod_list **p) { if (*p) kmod_module_dependency_symbols_free_list(*p); } #define _cleanup_kmod_module_dependency_symbols_free_list_ _cleanup_(kmod_module_dependency_symbols_free_listp) static inline void fts_closep(FTS **p) { if (*p) fts_close(*p); } #define _cleanup_fts_close_ _cleanup_(fts_closep) #define _cleanup_globfree_ _cleanup_(globfree) static inline void destroy_hashmap(Hashmap **hashmap) { void *i = NULL; while ((i = hashmap_steal_first(*hashmap))) item_free(i); hashmap_free(*hashmap); } #define _cleanup_destroy_hashmap_ _cleanup_(destroy_hashmap) 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 buf[MAXPATHLEN * 4]; _cleanup_free_ char *realtarget = NULL, *realfrom = NULL, *from_dir_p = NULL; _cleanup_free_ char *target_dir_p = NULL; size_t level = 0, fromlevel = 0, targetlevel = 0; int l; size_t i, rl, dirlen; dirlen = dir_len(from); from_dir_p = strndup(from, dirlen); if (!from_dir_p) return strdup(from + strlen(destrootdir)); if (realpath(from_dir_p, buf) == NULL) { log_warning("convert_abs_rel(): from '%s' directory has no realpath: %m", from); return strdup(from + strlen(destrootdir)); } /* dir_len() skips double /'s e.g. //lib64, so we can't skip just one * character - need to skip all leading /'s */ for (i = dirlen + 1; from[i] == '/'; ++i) ; _asprintf(&realfrom, "%s/%s", buf, from + i); dirlen = dir_len(target); target_dir_p = strndup(target, dirlen); if (!target_dir_p) return strdup(from + strlen(destrootdir)); if (realpath(target_dir_p, buf) == NULL) { log_warning("convert_abs_rel(): target '%s' directory has no realpath: %m", target); return strdup(from + strlen(destrootdir)); } for (i = dirlen + 1; target[i] == '/'; ++i) ; _asprintf(&realtarget, "%s/%s", buf, target + i); /* now calculate the relative path from to and store it in */ 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 buf path, until the common pathname is reached */ for (i = level; i < targetlevel; i++) { if (i != level) buf[rl++] = '/'; buf[rl++] = '.'; buf[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 buf path */ for (i = level; i <= fromlevel; i++) { if (rl) buf[rl++] = '/'; while (realfrom[l] && realfrom[l] != '/') buf[rl++] = realfrom[l++]; l++; } buf[rl] = 0; return strdup(buf); } 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) { if (geteuid() == 0) log_error("Failed to chown %s: %m", dst); else log_info("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(); const char *preservation = (geteuid() == 0 && no_xattr == false) ? "--preserve=mode,xattr,timestamps,ownership" : "--preserve=mode,timestamps,ownership"; if (pid == 0) { execlp("cp", "cp", "--reflink=auto", "--sparse=auto", preservation, "-fL", src, dst, NULL); _exit(errno == ENOENT ? 127 : 126); } while (waitpid(pid, &ret, 0) == -1) { if (errno != EINTR) { log_error("ERROR: waitpid() failed: %m"); return 1; } } ret = WIFSIGNALED(ret) ? 128 + WTERMSIG(ret) : WEXITSTATUS(ret); if (ret != 0) log_error("ERROR: 'cp --reflink=auto --sparse=auto %s -fL %s %s' failed with %d", preservation, src, dst, ret); 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, *pppdir = NULL, *clib = NULL; char *q, *clibdir; int r, ret = 0; r = dracut_install(lib, lib, false, false, true); if (r != 0) log_error("ERROR: failed to install '%s' for '%s'", lib, src); else log_debug("Lib install: '%s'", lib); ret += r; /* also install lib.so for lib.so.* files */ q = strstr(lib, ".so."); if (q) { p = strndup(lib, q - lib + 3); /* ignore errors for base lib symlink */ if (dracut_install(p, p, false, false, true) == 0) log_debug("Lib install: '%s'", p); free(p); } /* Also try to install the same library from one directory above * or from one directory above glibc-hwcaps. This fixes the case, where only the HWCAP lib would be installed # ldconfig -p|grep -F 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 */ p = strdup(lib); pdir = dirname_malloc(p); if (!pdir) return ret; ppdir = dirname_malloc(pdir); /* only one parent directory, not HWCAP library */ if (!ppdir || streq(ppdir, "/")) return ret; pppdir = dirname_malloc(ppdir); if (!pppdir) return ret; clibdir = streq(basename(ppdir), "glibc-hwcaps") ? pppdir : ppdir; clib = strjoin(clibdir, "/", 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 char *get_real_file(const char *src, bool fullyresolve) { struct stat sb; ssize_t linksz; char linktarget[PATH_MAX + 1]; _cleanup_free_ char *fullsrcpath_a = NULL; const char *fullsrcpath; _cleanup_free_ char *abspath = NULL; if (sysrootdirlen) { if (strncmp(src, sysrootdir, sysrootdirlen) == 0) { fullsrcpath = src; } else { _asprintf(&fullsrcpath_a, "%s/%s", (sysrootdirlen ? sysrootdir : ""), (src[0] == '/' ? src + 1 : src)); fullsrcpath = fullsrcpath_a; } } else { fullsrcpath = src; } log_debug("get_real_file('%s')", fullsrcpath); if (lstat(fullsrcpath, &sb) < 0) return NULL; switch (sb.st_mode & S_IFMT) { case S_IFDIR: case S_IFREG: return strdup(fullsrcpath); case S_IFLNK: break; default: return NULL; } linksz = readlink(fullsrcpath, linktarget, sizeof(linktarget)); if (linksz < 0) return NULL; linktarget[linksz] = '\0'; log_debug("get_real_file: readlink('%s') returns '%s'", fullsrcpath, linktarget); if (streq(fullsrcpath, linktarget)) { log_error("ERROR: '%s' is pointing to itself", fullsrcpath); return NULL; } if (linktarget[0] == '/') { _asprintf(&abspath, "%s%s", (sysrootdirlen ? sysrootdir : ""), linktarget); } else { _asprintf(&abspath, "%.*s/%s", (int)dir_len(fullsrcpath), fullsrcpath, linktarget); } if (fullyresolve) { struct stat st; if (lstat(abspath, &st) < 0) { if (errno != ENOENT) { return NULL; } } if (S_ISLNK(st.st_mode)) { return get_real_file(abspath, fullyresolve); } } log_debug("get_real_file('%s') => '%s'", src, abspath); return TAKE_PTR(abspath); } static int resolve_deps(const char *src) { int ret = 0, err; _cleanup_free_ char *buf = NULL; size_t linesize = LINE_MAX + 1; _cleanup_free_ char *fullsrcpath = NULL; fullsrcpath = get_real_file(src, true); log_debug("resolve_deps('%s') -> get_real_file('%s', true) = '%s'", src, src, fullsrcpath); if (!fullsrcpath) return 0; buf = malloc(linesize); if (buf == NULL) return -errno; if (strstr(src, ".so") == NULL) { _cleanup_close_ int fd = -1; fd = open(fullsrcpath, O_RDONLY | O_CLOEXEC); if (fd < 0) return -errno; ret = read(fd, buf, linesize - 1); if (ret == -1) return -errno; buf[ret] = '\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; } } int fds[2]; FILE *fptr; if (pipe2(fds, O_CLOEXEC) == -1 || (fptr = fdopen(fds[0], "r")) == NULL) { log_error("ERROR: pipe stream initialization for '%s' failed: %m", ldd); exit(EXIT_FAILURE); } log_debug("%s %s", ldd, fullsrcpath); pid_t ldd_pid; if ((ldd_pid = fork()) == 0) { dup2(fds[1], 1); dup2(fds[1], 2); putenv("LC_ALL=C"); execlp(ldd, ldd, fullsrcpath, (char *)NULL); _exit(errno == ENOENT ? 127 : 126); } close(fds[1]); ret = 0; while (getline(&buf, &linesize, fptr) >= 0) { char *p; log_debug("ldd: '%s'", buf); if (strstr(buf, "you do not have execution permission")) { log_error("%s", buf); ret += 1; break; } /* errors from cross-compiler-ldd */ if (strstr(buf, "unable to find sysroot")) { log_error("%s", buf); ret += 1; break; } /* musl ldd */ if (strstr(buf, "Not a valid dynamic program")) break; /* glibc */ if (strstr(buf, "cannot execute binary file")) continue; 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, "cannot be preloaded")) break; if (strstr(buf, destrootdir)) break; p = buf; if (strchr(p, '$')) { /* take ldd variable expansion into account */ p = strstr(p, "=>"); 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); } } fclose(fptr); while (waitpid(ldd_pid, &err, 0) == -1) { if (errno != EINTR) { log_error("ERROR: waitpid() failed: %m"); return 1; } } err = WIFSIGNALED(err) ? 128 + WTERMSIG(err) : WEXITSTATUS(err); /* ldd has error conditions we largely don't care about ("not a dynamic executable", &c.): only error out on hard errors (ENOENT, ENOEXEC, signals) */ if (err >= 126) { log_error("ERROR: '%s %s' failed with %d", ldd, fullsrcpath, err); return err; } else return ret; } /* Install "..hmac" file for FIPS self-checks */ static int hmac_install(const char *src, const char *dst, const char *hmacpath) { _cleanup_free_ char *srchmacname = NULL; _cleanup_free_ 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"); hmac_install(src, dst, "/lib/hmaccalc"); hmac_install(src, dst, "/lib64/hmaccalc"); } 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", (int)dlen, src, &src[dlen + 1]); _asprintf(&dsthmacname, "%.*s/.%s.hmac", (int)dir_len(dst), dst, &src[dlen + 1]); } 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; _asprintf(&fulldstpath, "%s/lib/dracut/hostonly-files", destrootdir); 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 bool check_hashmap(Hashmap *hm, const char *item) { char *existing; existing = hashmap_get(hm, item); if (existing) { if (strcmp(existing, item) == 0) { return true; } } return false; } static int dracut_mkdir(const char *src) { _cleanup_free_ char *parent = NULL; char *path; struct stat sb; parent = strdup(src); if (!parent) return 1; path = parent[0] == '/' ? parent + 1 : parent; while (path) { path = strstr(path, "/"); if (path) *path = '\0'; if (stat(parent, &sb) == 0) { if (!S_ISDIR(sb.st_mode)) { log_error("%s exists but is not a directory!", parent); return 1; } } else if (errno != ENOENT) { log_error("ERROR: stat '%s': %m", parent); return 1; } else { if (mkdir(parent, 0755) < 0) { log_error("ERROR: mkdir '%s': %m", parent); return 1; } } if (path) { *path = '/'; path++; } } return 0; } static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir, bool resolvedeps, bool hashdst) { struct stat sb; _cleanup_free_ char *fullsrcpath = NULL; _cleanup_free_ char *fulldstpath = NULL; _cleanup_free_ char *fulldstdir = NULL; int ret; bool src_islink = false; bool src_isdir = false; mode_t src_mode = 0; bool dst_exists = true; char *i = NULL; const char *src, *dst; if (sysrootdirlen) { if (strncmp(orig_src, sysrootdir, sysrootdirlen) == 0) { src = orig_src + sysrootdirlen; fullsrcpath = strdup(orig_src); } else { src = orig_src; _asprintf(&fullsrcpath, "%s%s", sysrootdir, src); } if (strncmp(orig_dst, sysrootdir, sysrootdirlen) == 0) dst = orig_dst + sysrootdirlen; else dst = orig_dst; } else { src = orig_src; fullsrcpath = strdup(src); dst = orig_dst; } log_debug("dracut_install('%s', '%s', %d, %d, %d)", src, dst, isdir, resolvedeps, hashdst); if (check_hashmap(items_failed, src)) { log_debug("hash hit items_failed for '%s'", src); return 1; } if (hashdst && check_hashmap(items, dst)) { log_debug("hash hit items for '%s'", dst); return 0; } if (lstat(fullsrcpath, &sb) < 0) { if (!isdir) { i = strdup(src); hashmap_put(items_failed, i, i); /* src does not exist */ return 1; } } else { src_islink = S_ISLNK(sb.st_mode); src_isdir = S_ISDIR(sb.st_mode); src_mode = sb.st_mode; } _asprintf(&fulldstpath, "%s/%s", destrootdir, (dst[0] == '/' ? (dst + 1) : dst)); ret = stat(fulldstpath, &sb); if (ret != 0) { dst_exists = false; if (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(fulldstpath + sysrootdirlen); } else log_debug("'%s' already exists", fulldstpath); /* dst does already exist */ } else { /* check destination directory */ fulldstdir = strndup(fulldstpath, dir_len(fulldstpath)); if (!fulldstdir) { log_error("Out of memory!"); return 1; } ret = access(fulldstdir, F_OK); 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 = strndup(dst, dir_len(dst)); if (!dname) return 1; ret = dracut_install(dname, dname, true, false, true); if (ret != 0) { log_error("ERROR: failed to create directory '%s'", fulldstdir); return 1; } } if (src_isdir) { if (dst_exists) { if (S_ISDIR(sb.st_mode)) { log_debug("dest dir '%s' already exists", fulldstpath); return 0; } log_error("dest dir '%s' already exists but is not a directory", fulldstpath); return 1; } log_info("mkdir '%s'", fulldstpath); ret = dracut_mkdir(fulldstpath); if (ret == 0) { i = strdup(dst); if (!i) return -ENOMEM; hashmap_put(items, i, i); } return ret; } /* ready to install src */ if (src_islink) { _cleanup_free_ char *abspath = NULL; abspath = get_real_file(src, false); if (abspath == NULL) return 1; if (dracut_install(abspath, abspath, false, resolvedeps, hashdst)) { log_debug("'%s' install error", abspath); return 1; } if (faccessat(AT_FDCWD, abspath, F_OK, AT_SYMLINK_NOFOLLOW) != 0) { log_debug("lstat '%s': %m", abspath); return 1; } if (faccessat(AT_FDCWD, fulldstpath, F_OK, AT_SYMLINK_NOFOLLOW) != 0) { _cleanup_free_ char *absdestpath = NULL; _asprintf(&absdestpath, "%s/%s", destrootdir, (abspath[0] == '/' ? (abspath + 1) : abspath) + sysrootdirlen); ln_r(absdestpath, fulldstpath); } if (arg_hmac) { /* copy .hmac files also */ hmac_install(src, dst, NULL); } return 0; } if (src_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { if (resolvedeps) ret += resolve_deps(fullsrcpath + sysrootdirlen); if (arg_hmac) { /* copy .hmac files also */ hmac_install(src, dst, NULL); } } log_debug("dracut_install ret = %d", ret); if (arg_hostonly && !arg_module) mark_hostonly(dst); if (isdir) { log_info("mkdir '%s'", fulldstpath); ret += dracut_mkdir(fulldstpath); } else { log_info("cp '%s' '%s'", fullsrcpath, fulldstpath); ret += cp(fullsrcpath, fulldstpath); } } if (ret == 0) { i = strdup(dst); if (!i) return -ENOMEM; hashmap_put(items, i, i); if (logfile_f) dracut_log_cp(src); } log_debug("dracut_install ret = %d", ret); return ret; } static void usage(int status) { /* */ printf("Usage: %s -D DESTROOTDIR [-r SYSROOTDIR] [OPTION]... -a SOURCE...\n" "or: %s -D DESTROOTDIR [-r SYSROOTDIR] [OPTION]... SOURCE DEST\n" "or: %s -D DESTROOTDIR [-r SYSROOTDIR] [OPTION]... -m KERNELMODULE [KERNELMODULE …]\n" "\n" "Install SOURCE (from rootfs or SYSROOTDIR) to DEST in DESTROOTDIR with all needed dependencies.\n" "\n" " KERNELMODULE can have the format:\n" " with a leading /\n" " =[/…] like '=drivers/hid'\n" " \n" "\n" " -D --destrootdir Install all files to DESTROOTDIR as the root\n" " -r --sysrootdir Install all files from SYSROOTDIR\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 Log files, which were installed from the host to \n" " -R --resolvelazy Only install shebang executables and libraries\n" " for all SOURCE files\n" " -H --hostonly Mark all SOURCE files as hostonly\n\n" " -f --fips Also install all '.SOURCE.hmac' files\n" "\n" " --module,-m Install kernel modules, instead of files\n" " --kerneldir Specify the kernel module directory\n" " (default: /lib/modules/$(uname -r))\n" " --firmwaredirs Specify the firmware directory search path with : separation\n" " (default: $DRACUT_FIRMWARE_PATH, otherwise kernel-compatible\n" " $(= 0) { log_set_max_level(arg_loglevel); } struct utsname buf = {0}; if (!kerneldir) { uname(&buf); _asprintf(&kerneldir, "/lib/modules/%s", buf.release); } if (arg_modalias) { return 1; } if (arg_module) { if (!firmwaredirs) { char *path = getenv("DRACUT_FIRMWARE_PATH"); if (path) { log_debug("DRACUT_FIRMWARE_PATH=%s", path); firmwaredirs = strv_split(path, ":"); } else { if (!*buf.release) uname(&buf); char fw_path_para[PATH_MAX + 1] = ""; int path = open("/sys/module/firmware_class/parameters/path", O_RDONLY | O_CLOEXEC); if (path != -1) { ssize_t rd = read(path, fw_path_para, PATH_MAX); if (rd != -1) fw_path_para[rd - 1] = '\0'; close(path); } char uk[22 + sizeof(buf.release)], fk[14 + sizeof(buf.release)]; sprintf(uk, "/lib/firmware/updates/%s", buf.release); sprintf(fk, "/lib/firmware/%s", buf.release); firmwaredirs = strv_new(STRV_IFNOTNULL(*fw_path_para ? fw_path_para : NULL), uk, "/lib/firmware/updates", fk, "/lib/firmware", NULL); } } } if (!optind || optind == argc) { if (!arg_optional) { log_error("No SOURCE argument given"); usage(EXIT_FAILURE); } else { exit(EXIT_SUCCESS); } } 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]; log_debug("resolve_deps('%s')", src); if (strstr(src, destrootdir)) { p = &argv[i][destrootdirlen]; } if (check_hashmap(items, p)) { continue; } item = strdup(p); hashmap_put(items, item, item); ret += resolve_deps(src); } return ret; } static char **find_binary(const char *src) { char **ret = NULL; char **q; char *newsrc = NULL; STRV_FOREACH(q, pathdirs) { char *fullsrcpath; _asprintf(&newsrc, "%s/%s", *q, src); fullsrcpath = get_real_file(newsrc, false); if (!fullsrcpath) { log_debug("get_real_file(%s) not found", newsrc); free(newsrc); newsrc = NULL; continue; } if (faccessat(AT_FDCWD, fullsrcpath, F_OK, AT_SYMLINK_NOFOLLOW) != 0) { log_debug("lstat(%s) != 0", fullsrcpath); free(newsrc); newsrc = NULL; free(fullsrcpath); fullsrcpath = NULL; continue; } strv_push(&ret, newsrc); free(fullsrcpath); fullsrcpath = NULL; }; 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 { if (strchr(argv[i], '*') == NULL) { ret = dracut_install(argv[i], argv[i], arg_createdir, arg_resolvedeps, true); } else { _cleanup_free_ char *realsrc = NULL; _cleanup_globfree_ glob_t globbuf; _asprintf(&realsrc, "%s%s", sysrootdir ? sysrootdir : "", argv[i]); ret = glob(realsrc, 0, NULL, &globbuf); if (ret == 0) { size_t j; for (j = 0; j < globbuf.gl_pathc; j++) { ret |= dracut_install(globbuf.gl_pathv[j] + sysrootdirlen, globbuf.gl_pathv[j] + sysrootdirlen, arg_createdir, arg_resolvedeps, true); } } } } if ((ret != 0) && (!arg_optional)) { log_error("ERROR: installing '%s'", argv[i]); r = EXIT_FAILURE; } } return r; } static int install_firmware_fullpath(const char *fwpath) { const char *fw = fwpath; _cleanup_free_ char *fwpath_compressed = NULL; int ret; if (access(fwpath, F_OK) != 0) { _asprintf(&fwpath_compressed, "%s.zst", fwpath); if (access(fwpath_compressed, F_OK) != 0) { strcpy(fwpath_compressed + strlen(fwpath) + 1, "xz"); if (access(fwpath_compressed, F_OK) != 0) { log_debug("stat(%s) != 0", fwpath); return 1; } } fw = fwpath_compressed; } ret = dracut_install(fw, fw, false, false, true); if (ret == 0) { log_debug("dracut_install '%s' OK", fwpath); } return ret; } static int install_firmware(struct kmod_module *mod) { struct kmod_list *l = NULL; _cleanup_kmod_module_info_free_list_ struct kmod_list *list = NULL; int ret; char **q; ret = kmod_module_get_info(mod, &list); if (ret < 0) { log_error("could not get modinfo from '%s': %s\n", kmod_module_get_name(mod), strerror(-ret)); return ret; } kmod_list_foreach(l, list) { const char *key = kmod_module_info_get_key(l); const char *value = NULL; bool found_this = false; if (!streq("firmware", key)) continue; value = kmod_module_info_get_value(l); log_debug("Firmware %s", value); ret = -1; STRV_FOREACH(q, firmwaredirs) { _cleanup_free_ char *fwpath = NULL; _asprintf(&fwpath, "%s/%s", *q, value); if (strpbrk(value, "*?[") != NULL && access(fwpath, F_OK) != 0) { size_t i; _cleanup_globfree_ glob_t globbuf; glob(fwpath, 0, NULL, &globbuf); for (i = 0; i < globbuf.gl_pathc; i++) { ret = install_firmware_fullpath(globbuf.gl_pathv[i]); if (ret == 0) found_this = true; } } else { ret = install_firmware_fullpath(fwpath); if (ret == 0) found_this = true; } } if (!found_this) { /* firmware path was not found in any firmwaredirs */ log_info("Missing firmware %s for kernel module %s", value, kmod_module_get_name(mod)); } } return 0; } static bool check_module_symbols(struct kmod_module *mod) { struct kmod_list *itr = NULL; _cleanup_kmod_module_dependency_symbols_free_list_ struct kmod_list *deplist = NULL; if (!arg_mod_filter_symbol && !arg_mod_filter_nosymbol) return true; if (kmod_module_get_dependency_symbols(mod, &deplist) < 0) { log_debug("kmod_module_get_dependency_symbols failed"); if (arg_mod_filter_symbol) return false; return true; } if (arg_mod_filter_nosymbol) { kmod_list_foreach(itr, deplist) { const char *symbol = kmod_module_symbol_get_symbol(itr); // log_debug("Checking symbol %s", symbol); if (regexec(&mod_filter_nosymbol, symbol, 0, NULL, 0) == 0) { log_debug("Module %s: symbol %s matched exclusion filter", kmod_module_get_name(mod), symbol); return false; } } } if (arg_mod_filter_symbol) { kmod_list_foreach(itr, deplist) { const char *symbol = kmod_module_dependency_symbol_get_symbol(itr); // log_debug("Checking symbol %s", symbol); if (regexec(&mod_filter_symbol, symbol, 0, NULL, 0) == 0) { log_debug("Module %s: symbol %s matched inclusion filter", kmod_module_get_name(mod), symbol); return true; } } return false; } return true; } static bool check_module_path(const char *path) { if (arg_mod_filter_nopath && (regexec(&mod_filter_nopath, path, 0, NULL, 0) == 0)) { log_debug("Path %s matched exclusion filter", path); return false; } if (arg_mod_filter_path && (regexec(&mod_filter_path, path, 0, NULL, 0) != 0)) { log_debug("Path %s matched inclusion filter", path); return false; } return true; } static int find_kmod_module_from_sysfs_node(struct kmod_ctx *ctx, const char *sysfs_node, int sysfs_node_len, struct kmod_list **modules) { char modalias_path[PATH_MAX]; if (snprintf(modalias_path, sizeof(modalias_path), "%.*s/modalias", sysfs_node_len, sysfs_node) >= sizeof(modalias_path)) return -1; _cleanup_close_ int modalias_file = -1; if ((modalias_file = open(modalias_path, O_RDONLY | O_CLOEXEC)) == -1) return 0; char alias[page_size()]; ssize_t len = read(modalias_file, alias, sizeof(alias)); alias[len - 1] = '\0'; return kmod_module_new_from_lookup(ctx, alias, modules); } static int find_modules_from_sysfs_node(struct kmod_ctx *ctx, const char *sysfs_node, Hashmap *modules) { _cleanup_kmod_module_unref_list_ struct kmod_list *list = NULL; struct kmod_list *l = NULL; if (find_kmod_module_from_sysfs_node(ctx, sysfs_node, strlen(sysfs_node), &list) >= 0) { kmod_list_foreach(l, list) { struct kmod_module *mod = kmod_module_get_module(l); char *module = strdup(kmod_module_get_name(mod)); kmod_module_unref(mod); if (hashmap_put(modules, module, module) < 0) free(module); } } return 0; } static void find_suppliers_for_sys_node(struct kmod_ctx *ctx, Hashmap *suppliers, const char *node_path_raw, size_t node_path_len) { char node_path[PATH_MAX]; char real_path[PATH_MAX]; memcpy(node_path, node_path_raw, node_path_len); node_path[node_path_len] = '\0'; DIR *d; struct dirent *dir; while (realpath(node_path, real_path) != NULL && strcmp(real_path, "/sys/devices")) { d = opendir(node_path); if (d) { size_t real_path_len = strlen(real_path); while ((dir = readdir(d)) != NULL) { if (strstr(dir->d_name, "supplier:platform") != NULL) { if (snprintf(real_path + real_path_len, sizeof(real_path) - real_path_len, "/%s/supplier", dir->d_name) < sizeof(real_path) - real_path_len) { char *real_supplier_path = realpath(real_path, NULL); if (real_supplier_path != NULL) if (hashmap_put(suppliers, real_supplier_path, real_supplier_path) < 0) free(real_supplier_path); } } } closedir(d); } strncat(node_path, "/..", 3); // Also find suppliers of parents } } static void find_suppliers(struct kmod_ctx *ctx) { _cleanup_fts_close_ FTS *fts; char *paths[] = { "/sys/devices/platform", NULL }; fts = fts_open(paths, FTS_NOSTAT | FTS_PHYSICAL, NULL); for (FTSENT *ftsent = fts_read(fts); ftsent != NULL; ftsent = fts_read(fts)) { if (strcmp(ftsent->fts_name, "modalias") == 0) { _cleanup_kmod_module_unref_list_ struct kmod_list *list = NULL; struct kmod_list *l; if (find_kmod_module_from_sysfs_node(ctx, ftsent->fts_parent->fts_path, ftsent->fts_parent->fts_pathlen, &list) < 0) continue; kmod_list_foreach(l, list) { _cleanup_kmod_module_unref_ struct kmod_module *mod = kmod_module_get_module(l); const char *name = kmod_module_get_name(mod); Hashmap *suppliers = hashmap_get(modules_suppliers, name); if (suppliers == NULL) { suppliers = hashmap_new(string_hash_func, string_compare_func); hashmap_put(modules_suppliers, strdup(name), suppliers); } find_suppliers_for_sys_node(ctx, suppliers, ftsent->fts_parent->fts_path, ftsent->fts_parent->fts_pathlen); } } } } static Hashmap *find_suppliers_paths_for_module(const char *module) { return hashmap_get(modules_suppliers, module); } static int install_dependent_module(struct kmod_ctx *ctx, struct kmod_module *mod, Hashmap *suppliers_paths, int *err) { const char *path = NULL; const char *name = NULL; path = kmod_module_get_path(mod); if (path == NULL) return 0; if (check_hashmap(items_failed, path)) return -1; if (check_hashmap(items, &path[kerneldirlen])) { return 0; } name = kmod_module_get_name(mod); if (arg_mod_filter_noname && (regexec(&mod_filter_noname, name, 0, NULL, 0) == 0)) { return 0; } *err = dracut_install(path, &path[kerneldirlen], false, false, true); if (*err == 0) { _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL; _cleanup_kmod_module_unref_list_ struct kmod_list *modpre = NULL; _cleanup_kmod_module_unref_list_ struct kmod_list *modpost = NULL; log_debug("dracut_install '%s' '%s' OK", path, &path[kerneldirlen]); install_firmware(mod); modlist = kmod_module_get_dependencies(mod); *err = install_dependent_modules(ctx, modlist, suppliers_paths); if (*err == 0) { *err = kmod_module_get_softdeps(mod, &modpre, &modpost); if (*err == 0) { int r; *err = install_dependent_modules(ctx, modpre, NULL); r = install_dependent_modules(ctx, modpost, NULL); *err = *err ? : r; } } } else { log_error("dracut_install '%s' '%s' ERROR", path, &path[kerneldirlen]); } return 0; } static int install_dependent_modules(struct kmod_ctx *ctx, struct kmod_list *modlist, Hashmap *suppliers_paths) { struct kmod_list *itr = NULL; int ret = 0; kmod_list_foreach(itr, modlist) { _cleanup_kmod_module_unref_ struct kmod_module *mod = NULL; mod = kmod_module_get_module(itr); if (install_dependent_module(ctx, mod, find_suppliers_paths_for_module(kmod_module_get_name(mod)), &ret)) return -1; } const char *supplier_path; Iterator i; HASHMAP_FOREACH(supplier_path, suppliers_paths, i) { if (check_hashmap(processed_suppliers, supplier_path)) continue; char *path = strdup(supplier_path); hashmap_put(processed_suppliers, path, path); _cleanup_destroy_hashmap_ Hashmap *modules = hashmap_new(string_hash_func, string_compare_func); find_modules_from_sysfs_node(ctx, supplier_path, modules); _cleanup_destroy_hashmap_ Hashmap *suppliers = hashmap_new(string_hash_func, string_compare_func); find_suppliers_for_sys_node(ctx, suppliers, supplier_path, strlen(supplier_path)); if (!hashmap_isempty(modules)) { // Supplier is a module const char *module; Iterator j; HASHMAP_FOREACH(module, modules, j) { _cleanup_kmod_module_unref_ struct kmod_module *mod = NULL; if (!kmod_module_new_from_name(ctx, module, &mod)) { if (install_dependent_module(ctx, mod, suppliers, &ret)) return -1; } } } else { // Supplier is builtin install_dependent_modules(ctx, NULL, suppliers); } } return ret; } static int install_module(struct kmod_ctx *ctx, struct kmod_module *mod) { int ret = 0; _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL; _cleanup_kmod_module_unref_list_ struct kmod_list *modpre = NULL; _cleanup_kmod_module_unref_list_ struct kmod_list *modpost = NULL; const char *path = NULL; const char *name = NULL; name = kmod_module_get_name(mod); path = kmod_module_get_path(mod); if (!path) { log_debug("dracut_install '%s' is a builtin kernel module", name); return 0; } if (arg_mod_filter_noname && (regexec(&mod_filter_noname, name, 0, NULL, 0) == 0)) { log_debug("dracut_install '%s' is excluded", name); return 0; } if (arg_hostonly && !check_hashmap(modules_loaded, name)) { log_debug("dracut_install '%s' not hostonly", name); return 0; } if (check_hashmap(items_failed, path)) return -1; if (check_hashmap(items, path)) return 0; if (!check_module_path(path) || !check_module_symbols(mod)) { log_debug("No symbol or path match for '%s'", path); return 1; } log_debug("dracut_install '%s' '%s'", path, &path[kerneldirlen]); ret = dracut_install(path, &path[kerneldirlen], false, false, true); if (ret == 0) { log_debug("dracut_install '%s' OK", kmod_module_get_name(mod)); } else if (!arg_optional) { if (!arg_silent) log_error("dracut_install '%s' ERROR", kmod_module_get_name(mod)); return ret; } install_firmware(mod); Hashmap *suppliers = find_suppliers_paths_for_module(name); modlist = kmod_module_get_dependencies(mod); ret = install_dependent_modules(ctx, modlist, suppliers); if (ret == 0) { ret = kmod_module_get_softdeps(mod, &modpre, &modpost); if (ret == 0) { int r; ret = install_dependent_modules(ctx, modpre, NULL); r = install_dependent_modules(ctx, modpost, NULL); ret = ret ? : r; } } return ret; } static int modalias_list(struct kmod_ctx *ctx) { int err; struct kmod_list *loaded_list = NULL; struct kmod_list *l = NULL; struct kmod_list *itr = NULL; _cleanup_fts_close_ FTS *fts = NULL; { char *paths[] = { "/sys/devices", NULL }; fts = fts_open(paths, FTS_NOCHDIR | FTS_NOSTAT, NULL); } for (FTSENT *ftsent = fts_read(fts); ftsent != NULL; ftsent = fts_read(fts)) { _cleanup_fclose_ FILE *f = NULL; _cleanup_kmod_module_unref_list_ struct kmod_list *list = NULL; int err; char alias[2048] = {0}; size_t len; if (strncmp("modalias", ftsent->fts_name, 8) != 0) continue; if (!(f = fopen(ftsent->fts_accpath, "r"))) continue; if (!fgets(alias, sizeof(alias), f)) continue; len = strlen(alias); if (len == 0) continue; if (alias[len - 1] == '\n') alias[len - 1] = 0; err = kmod_module_new_from_lookup(ctx, alias, &list); if (err < 0) continue; kmod_list_foreach(l, list) { struct kmod_module *mod = kmod_module_get_module(l); char *name = strdup(kmod_module_get_name(mod)); kmod_module_unref(mod); hashmap_put(modules_loaded, name, name); } } err = kmod_module_new_from_loaded(ctx, &loaded_list); if (err < 0) { errno = err; log_error("Could not get list of loaded modules: %m. Switching to non-hostonly mode."); arg_hostonly = false; } else { kmod_list_foreach(itr, loaded_list) { _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL; struct kmod_module *mod = kmod_module_get_module(itr); char *name = strdup(kmod_module_get_name(mod)); hashmap_put(modules_loaded, name, name); kmod_module_unref(mod); /* also put the modules from the new kernel in the hashmap, * which resolve the name as an alias, in case a kernel module is * renamed. */ err = kmod_module_new_from_lookup(ctx, name, &modlist); if (err < 0) continue; if (!modlist) continue; kmod_list_foreach(l, modlist) { mod = kmod_module_get_module(l); char *name = strdup(kmod_module_get_name(mod)); hashmap_put(modules_loaded, name, name); kmod_module_unref(mod); } } kmod_module_unref_list(loaded_list); } return 0; } static int install_modules(int argc, char **argv) { _cleanup_kmod_unref_ struct kmod_ctx *ctx = NULL; struct kmod_list *itr = NULL; struct kmod_module *mod = NULL, *mod_o = NULL; const char *abskpath = NULL; char *p; int i; int modinst = 0; ctx = kmod_new(kerneldir, NULL); abskpath = kmod_get_dirname(ctx); p = strstr(abskpath, "/lib/modules/"); if (p != NULL) kerneldirlen = p - abskpath; modules_suppliers = hashmap_new(string_hash_func, string_compare_func); find_suppliers(ctx); if (arg_hostonly) { char *modalias_file; modalias_file = getenv("DRACUT_KERNEL_MODALIASES"); if (modalias_file == NULL) { modalias_list(ctx); } else { _cleanup_fclose_ FILE *f = NULL; if ((f = fopen(modalias_file, "r"))) { char name[2048]; while (!feof(f)) { size_t len; char *dupname = NULL; if (!(fgets(name, sizeof(name), f))) continue; len = strlen(name); if (len == 0) continue; if (name[len - 1] == '\n') name[len - 1] = 0; log_debug("Adding module '%s' to hostonly module list", name); dupname = strdup(name); hashmap_put(modules_loaded, dupname, dupname); } } } } for (i = 0; i < argc; i++) { int r = 0; int ret = -1; log_debug("Handle module '%s'", argv[i]); if (argv[i][0] == '/') { _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL; _cleanup_free_ const char *modname = NULL; r = kmod_module_new_from_path(ctx, argv[i], &mod_o); if (r < 0) { log_debug("Failed to lookup modules path '%s': %m", argv[i]); if (!arg_optional) return -ENOENT; continue; } /* Check, if we have to load another module with that name instead */ modname = strdup(kmod_module_get_name(mod_o)); if (!modname) { if (!arg_optional) { if (!arg_silent) log_error("Failed to get name for module '%s'", argv[i]); return -ENOENT; } log_info("Failed to get name for module '%s'", argv[i]); continue; } r = kmod_module_new_from_lookup(ctx, modname, &modlist); kmod_module_unref(mod_o); mod_o = NULL; if (r < 0) { if (!arg_optional) { if (!arg_silent) log_error("3 Failed to lookup alias '%s': %d", modname, r); return -ENOENT; } log_info("3 Failed to lookup alias '%s': %d", modname, r); continue; } if (!modlist) { if (!arg_optional) { if (!arg_silent) log_error("Failed to find module '%s' %s", modname, argv[i]); return -ENOENT; } log_info("Failed to find module '%s' %s", modname, argv[i]); continue; } kmod_list_foreach(itr, modlist) { mod = kmod_module_get_module(itr); r = install_module(ctx, mod); kmod_module_unref(mod); if ((r < 0) && !arg_optional) { if (!arg_silent) log_error("ERROR: installing module '%s'", modname); return -ENOENT; }; ret = (ret == 0 ? 0 : r); modinst = 1; } } else if (argv[i][0] == '=') { _cleanup_free_ char *path1 = NULL, *path2 = NULL, *path3 = NULL; _cleanup_fts_close_ FTS *fts = NULL; log_debug("Handling =%s", &argv[i][1]); /* FIXME and add more paths */ _asprintf(&path2, "%s/kernel/%s", kerneldir, &argv[i][1]); _asprintf(&path1, "%s/extra/%s", kerneldir, &argv[i][1]); _asprintf(&path3, "%s/updates/%s", kerneldir, &argv[i][1]); { char *paths[] = { path1, path2, path3, NULL }; fts = fts_open(paths, FTS_COMFOLLOW | FTS_NOCHDIR | FTS_NOSTAT | FTS_LOGICAL, NULL); } for (FTSENT *ftsent = fts_read(fts); ftsent != NULL; ftsent = fts_read(fts)) { _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL; _cleanup_free_ const char *modname = NULL; if ((ftsent->fts_info == FTS_D) && !check_module_path(ftsent->fts_accpath)) { fts_set(fts, ftsent, FTS_SKIP); log_debug("Skipping %s", ftsent->fts_accpath); continue; } if ((ftsent->fts_info != FTS_F) && (ftsent->fts_info != FTS_SL)) { log_debug("Ignoring %s", ftsent->fts_accpath); continue; } log_debug("Handling %s", ftsent->fts_accpath); r = kmod_module_new_from_path(ctx, ftsent->fts_accpath, &mod_o); if (r < 0) { log_debug("Failed to lookup modules path '%s': %m", ftsent->fts_accpath); if (!arg_optional) { return -ENOENT; } continue; } /* Check, if we have to load another module with that name instead */ modname = strdup(kmod_module_get_name(mod_o)); if (!modname) { log_error("Failed to get name for module '%s'", ftsent->fts_accpath); if (!arg_optional) { return -ENOENT; } continue; } r = kmod_module_new_from_lookup(ctx, modname, &modlist); kmod_module_unref(mod_o); mod_o = NULL; if (r < 0) { log_error("Failed to lookup alias '%s': %m", modname); if (!arg_optional) { return -ENOENT; } continue; } if (!modlist) { log_error("Failed to find module '%s' %s", modname, ftsent->fts_accpath); if (!arg_optional) { return -ENOENT; } continue; } kmod_list_foreach(itr, modlist) { mod = kmod_module_get_module(itr); r = install_module(ctx, mod); kmod_module_unref(mod); if ((r < 0) && !arg_optional) { if (!arg_silent) log_error("ERROR: installing module '%s'", modname); return -ENOENT; }; ret = (ret == 0 ? 0 : r); modinst = 1; } } if (errno) { log_error("FTS ERROR: %m"); } } else { _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL; char *modname = argv[i]; if (endswith(modname, ".ko")) { int len = strlen(modname); modname[len - 3] = 0; } if (endswith(modname, ".ko.xz") || endswith(modname, ".ko.gz")) { int len = strlen(modname); modname[len - 6] = 0; } if (endswith(modname, ".ko.zst")) { int len = strlen(modname); modname[len - 7] = 0; } r = kmod_module_new_from_lookup(ctx, modname, &modlist); if (r < 0) { if (!arg_optional) { if (!arg_silent) log_error("Failed to lookup alias '%s': %m", modname); return -ENOENT; } log_info("Failed to lookup alias '%s': %m", modname); continue; } if (!modlist) { if (!arg_optional) { if (!arg_silent) log_error("Failed to find module '%s'", modname); return -ENOENT; } log_info("Failed to find module '%s'", modname); continue; } kmod_list_foreach(itr, modlist) { mod = kmod_module_get_module(itr); r = install_module(ctx, mod); kmod_module_unref(mod); if ((r < 0) && !arg_optional) { if (!arg_silent) log_error("ERROR: installing '%s'", argv[i]); return -ENOENT; }; ret = (ret == 0 ? 0 : r); modinst = 1; } } if ((modinst != 0) && (ret != 0) && (!arg_optional)) { if (!arg_silent) log_error("ERROR: installing '%s'", argv[i]); return EXIT_FAILURE; } } return EXIT_SUCCESS; } int main(int argc, char **argv) { int r; char *i; char *path = NULL; char *env_no_xattr = NULL; log_set_target(LOG_TARGET_CONSOLE); log_parse_environment(); log_open(); r = parse_argv(argc, argv); if (r <= 0) return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; modules_loaded = hashmap_new(string_hash_func, string_compare_func); if (arg_modalias) { Iterator i; char *name; _cleanup_kmod_unref_ struct kmod_ctx *ctx = NULL; ctx = kmod_new(kerneldir, NULL); modalias_list(ctx); HASHMAP_FOREACH(name, modules_loaded, i) { printf("%s\n", name); } exit(0); } log_debug("Program arguments:"); for (r = 0; r < argc; r++) log_debug("%s", argv[r]); path = getenv("DRACUT_INSTALL_PATH"); if (path == NULL) path = getenv("PATH"); if (path == NULL) { log_error("PATH is not set"); exit(EXIT_FAILURE); } log_debug("PATH=%s", path); ldd = getenv("DRACUT_LDD"); if (ldd == NULL) ldd = "ldd"; log_debug("LDD=%s", ldd); env_no_xattr = getenv("DRACUT_NO_XATTR"); if (env_no_xattr != NULL) no_xattr = true; pathdirs = strv_split(path, ":"); 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); } } if (strcmp(destrootdir, "/") == 0) { log_error("Environment DESTROOTDIR or argument -D is set to '/'!"); usage(EXIT_FAILURE); } i = destrootdir; if (!(destrootdir = realpath(i, NULL))) { log_error("Environment DESTROOTDIR or argument -D is set to '%s': %m", i); r = EXIT_FAILURE; goto finish2; } items = hashmap_new(string_hash_func, string_compare_func); items_failed = hashmap_new(string_hash_func, string_compare_func); processed_suppliers = hashmap_new(string_hash_func, string_compare_func); if (!items || !items_failed || !processed_suppliers || !modules_loaded) { log_error("Out of memory"); r = EXIT_FAILURE; goto finish1; } if (logdir) { _asprintf(&logfile, "%s/%d.log", logdir, getpid()); logfile_f = fopen(logfile, "a"); if (logfile_f == NULL) { log_error("Could not open %s for logging: %m", logfile); r = EXIT_FAILURE; goto finish1; } } 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_module) { r = install_modules(argc - optind, &argv[optind]); } else 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; finish1: free(destrootdir); finish2: if (logfile_f) fclose(logfile_f); while ((i = hashmap_steal_first(modules_loaded))) item_free(i); while ((i = hashmap_steal_first(items))) item_free(i); while ((i = hashmap_steal_first(items_failed))) item_free(i); Hashmap *h; while ((h = hashmap_steal_first(modules_suppliers))) { while ((i = hashmap_steal_first(h))) { item_free(i); } hashmap_free(h); } while ((i = hashmap_steal_first(processed_suppliers))) item_free(i); hashmap_free(items); hashmap_free(items_failed); hashmap_free(modules_loaded); hashmap_free(modules_suppliers); hashmap_free(processed_suppliers); strv_free(firmwaredirs); strv_free(pathdirs); return r; }