diff --git a/meson.build b/meson.build index 45efde9..240b6e6 100644 --- a/meson.build +++ b/meson.build @@ -258,7 +258,7 @@ endif if enable_depmod vx_sources += depmod_src - vx_dependencies += [dependency('zlib'), dependency('libzstd')] + vx_dependencies += [dependency('zlib'), dependency('libzstd'), dependency('liblzma')] endif vx = executable('vx', diff --git a/src/depmod/depmod.cpp b/src/depmod/depmod.cpp index 8db74c0..1fd60fa 100644 --- a/src/depmod/depmod.cpp +++ b/src/depmod/depmod.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include #include +#include #include namespace { @@ -42,10 +44,8 @@ constexpr uint32_t INDEX_VERSION = (0x0002u << 16) | 0x0001u; constexpr uint32_t INDEX_NODE_PREFIX = 0x80000000u; constexpr uint32_t INDEX_NODE_VALUES = 0x40000000u; constexpr uint32_t INDEX_NODE_CHILDS = 0x20000000u; -constexpr uint32_t INDEX_NODE_MASK = 0x0fffffffu; struct Options { - bool all = false; bool quick = false; bool dry_run = false; bool errsyms = false; @@ -54,12 +54,17 @@ struct Options { std::string basedir = "/"; std::string moduledir = "/lib/modules"; std::string outdir; - std::string config; + std::vector config_paths; std::string symvers; std::string filesyms; char symbol_prefix = '\0'; std::string version; std::vector files; + std::set external_symbols; +}; + +struct DepmodConfig { + std::set excluded_dirs; }; struct Module { @@ -74,7 +79,6 @@ struct Module { std::vector exports; std::vector undefined; int order = 0x3fffffff; - size_t scan_index = 0; }; static void warnx(const char *fmt, ...) @@ -140,6 +144,8 @@ static std::string strip_module_suffix(std::string name) { if (has_suffix(name, ".zst")) name.resize(name.size() - 4); + else if (has_suffix(name, ".xz")) + name.resize(name.size() - 3); else if (has_suffix(name, ".gz")) name.resize(name.size() - 3); @@ -155,6 +161,9 @@ static std::string normalize_modname(std::string name) if (c == '-') c = '_'; } + size_t dot = name.find('.'); + if (dot != std::string::npos) + name.resize(dot); return name; } @@ -244,45 +253,75 @@ static bool decompress_gzip(const std::vector &in, std::vector static bool decompress_zstd(const std::vector &in, std::vector &out) { - unsigned long long frame_size = ZSTD_getFrameContentSize(in.data(), in.size()); - if (frame_size != ZSTD_CONTENTSIZE_ERROR && frame_size != ZSTD_CONTENTSIZE_UNKNOWN) { - out.resize(static_cast(frame_size)); - size_t ret = ZSTD_decompress(out.data(), out.size(), in.data(), in.size()); - if (ZSTD_isError(ret)) - return false; - out.resize(ret); - return true; - } - ZSTD_DStream *ds = ZSTD_createDStream(); if (!ds) return false; - ZSTD_initDStream(ds); + size_t init = ZSTD_initDStream(ds); + if (ZSTD_isError(init)) { + ZSTD_freeDStream(ds); + return false; + } ZSTD_inBuffer input{in.data(), in.size(), 0}; std::vector buf(ZSTD_DStreamOutSize()); - while (input.pos < input.size) { + size_t ret = 1; + while (ret != 0 || input.pos < input.size) { ZSTD_outBuffer output{buf.data(), buf.size(), 0}; - size_t ret = ZSTD_decompressStream(ds, &output, &input); + ret = ZSTD_decompressStream(ds, &output, &input); if (ZSTD_isError(ret)) { ZSTD_freeDStream(ds); return false; } out.insert(out.end(), buf.begin(), buf.begin() + output.pos); + if (ret == 0 && input.pos < input.size) { + ret = ZSTD_initDStream(ds); + if (ZSTD_isError(ret)) { + ZSTD_freeDStream(ds); + return false; + } + } else if (input.pos == input.size && output.pos == 0 && ret != 0) { + ZSTD_freeDStream(ds); + return false; + } } ZSTD_freeDStream(ds); return true; } +static bool decompress_xz(const std::vector &in, std::vector &out) +{ + lzma_stream stream = LZMA_STREAM_INIT; + if (lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED) != LZMA_OK) + return false; + + stream.next_in = in.data(); + stream.avail_in = in.size(); + std::vector buf(256 * 1024); + lzma_ret ret = LZMA_OK; + while (ret == LZMA_OK) { + stream.next_out = buf.data(); + stream.avail_out = buf.size(); + ret = lzma_code(&stream, LZMA_FINISH); + size_t have = buf.size() - stream.avail_out; + out.insert(out.end(), buf.begin(), buf.begin() + have); + } + lzma_end(&stream); + return ret == LZMA_STREAM_END; +} + static bool read_module_image(const std::string &path, std::vector &out) { std::vector raw; if (!read_file_raw(path, raw)) return false; - if (has_suffix(path, ".ko.gz")) - return decompress_gzip(raw, out); - if (has_suffix(path, ".ko.zst")) + if (raw.size() >= 4 && raw[0] == 0x28 && raw[1] == 0xb5 && + raw[2] == 0x2f && raw[3] == 0xfd) return decompress_zstd(raw, out); + if (raw.size() >= 6 && raw[0] == 0xfd && raw[1] == '7' && + raw[2] == 'z' && raw[3] == 'X' && raw[4] == 'Z' && raw[5] == 0x00) + return decompress_xz(raw, out); + if (raw.size() >= 2 && raw[0] == 0x1f && raw[1] == 0x8b) + return decompress_gzip(raw, out); out = std::move(raw); return true; @@ -333,7 +372,8 @@ struct ElfReader { bool parse() { - if (data.size() < 64 || data[0] != 0x7f || data[1] != 'E' || data[2] != 'L' || data[3] != 'F') + if (data.size() < EI_NIDENT || data[0] != ELFMAG0 || data[1] != ELFMAG1 || + data[2] != ELFMAG2 || data[3] != ELFMAG3) return false; is64 = data[4] == 2; if (data[4] != 1 && data[4] != 2) @@ -345,6 +385,11 @@ struct ElfReader { else return false; + const size_t header_size = is64 ? sizeof(Elf64_Ehdr) : sizeof(Elf32_Ehdr); + const size_t section_size = is64 ? sizeof(Elf64_Shdr) : sizeof(Elf32_Shdr); + if (data.size() < header_size) + return false; + uint64_t shoff = 0; uint16_t shentsize = 0, shnum = 0, shstrndx = 0; if (is64) { @@ -358,9 +403,9 @@ struct ElfReader { shnum = rd(0x30); shstrndx = rd(0x32); } - if (shoff == 0 || shentsize == 0 || shnum == 0 || shstrndx >= shnum) + if (shoff == 0 || shentsize != section_size || shnum == 0 || shstrndx >= shnum) return false; - if (shoff + static_cast(shentsize) * shnum > data.size()) + if (shoff > data.size() || static_cast(shentsize) * shnum > data.size() - shoff) return false; std::vector name_offsets(shnum); @@ -383,7 +428,7 @@ struct ElfReader { s.link = rd(off + 0x18); s.entsize = rd(off + 0x24); } - if (s.offset + s.size > data.size()) + if (s.offset > data.size() || s.size > data.size() - s.offset) return false; sections[i] = std::move(s); } @@ -395,8 +440,11 @@ struct ElfReader { continue; size_t start = static_cast(shstr.offset + noff); size_t end = start; - while (end < data.size() && data[end] != '\0') + const size_t shstr_end = static_cast(shstr.offset + shstr.size); + while (end < shstr_end && data[end] != '\0') end++; + if (end == shstr_end) + continue; sections[i].name.assign(reinterpret_cast(data.data() + start), end - start); } return true; @@ -417,8 +465,11 @@ struct ElfReader { return {}; size_t start = static_cast(s.offset + off); size_t end = start; - while (end < data.size() && data[end] != '\0') + const size_t section_end = static_cast(s.offset + s.size); + while (end < section_end && data[end] != '\0') end++; + if (end == section_end) + return {}; return std::string(reinterpret_cast(data.data() + start), end - start); } @@ -456,8 +507,6 @@ struct ElfReader { mod.softdeps.push_back(val); } else if (key == "weakdep") { mod.weakdeps.push_back(val); - } else if (key == "name" && !val.empty()) { - mod.modname = normalize_modname(val); } } } @@ -467,8 +516,6 @@ struct ElfReader { void read_symbols(Module &mod, char prefix) const { - constexpr uint32_t SHT_SYMTAB = 2; - constexpr uint16_t SHN_UNDEF = 0; static const char *export_prefixes[] = { "__ksymtab_unused_gpl_", "__ksymtab_gpl_future_", @@ -478,6 +525,21 @@ struct ElfReader { "__ksymtab_strings+", }; + const Section *ksymtab_strings = find_section("__ksymtab_strings"); + if (ksymtab_strings) { + size_t pos = static_cast(ksymtab_strings->offset); + size_t end = static_cast(ksymtab_strings->offset + ksymtab_strings->size); + while (pos < end) { + size_t next = pos; + while (next < end && data[next] != '\0') + next++; + if (next > pos) + mod.exports.push_back(strip_known_symbol_prefix( + std::string(reinterpret_cast(data.data() + pos), next - pos), prefix)); + pos = next + 1; + } + } + for (size_t si = 0; si < sections.size(); si++) { const Section &symsec = sections[si]; if (symsec.type != SHT_SYMTAB || symsec.entsize == 0 || symsec.link >= sections.size()) @@ -487,23 +549,19 @@ struct ElfReader { for (size_t i = 0; i < count; i++) { size_t off = static_cast(symsec.offset + i * symsec.entsize); uint32_t st_name = 0; - uint8_t st_info = 0; uint16_t st_shndx = 0; if (is64) { st_name = rd(off + 0x00); - st_info = rd(off + 0x04); st_shndx = rd(off + 0x06); } else { st_name = rd(off + 0x00); - st_info = rd(off + 0x0c); st_shndx = rd(off + 0x0e); } std::string name = str_at(strsec, st_name); if (name.empty()) continue; - unsigned bind = st_info >> 4; - if (st_shndx == SHN_UNDEF && (bind == 1 || bind == 2)) { + if (st_shndx == SHN_UNDEF) { mod.undefined.push_back(strip_known_symbol_prefix(name, prefix)); continue; } @@ -517,6 +575,26 @@ struct ElfReader { } } } + + const Section *versions = find_section("__versions"); + if (versions) { + const size_t crc_size = is64 ? sizeof(uint64_t) : sizeof(uint32_t); + constexpr size_t record_size = 64; + for (size_t pos = static_cast(versions->offset); + pos + record_size <= versions->offset + versions->size; + pos += record_size) { + const size_t name_start = pos + crc_size; + size_t name_end = name_start; + const size_t section_end = static_cast(versions->offset + versions->size); + while (name_end < std::min(section_end, name_start + record_size - crc_size) && + data[name_end] != '\0') + name_end++; + if (name_end > name_start) + mod.undefined.push_back(strip_known_symbol_prefix( + std::string(reinterpret_cast(data.data() + name_start), + name_end - name_start), prefix)); + } + } std::sort(mod.exports.begin(), mod.exports.end()); mod.exports.erase(std::unique(mod.exports.begin(), mod.exports.end()), mod.exports.end()); std::sort(mod.undefined.begin(), mod.undefined.end()); @@ -545,23 +623,31 @@ static bool parse_module(Module &mod, const Options &opts) static bool is_module_file(const std::string &path) { - return has_suffix(path, ".ko") || has_suffix(path, ".ko.gz") || has_suffix(path, ".ko.zst"); + return has_suffix(path, ".ko") || has_suffix(path, ".ko.gz") || + has_suffix(path, ".ko.xz") || has_suffix(path, ".ko.zst"); } -static void scan_modules_recursive(const std::string &root, const std::string &dir, std::vector &out) +static bool excluded_module_dir(const char *name) +{ + return strcmp(name, ".") == 0 || strcmp(name, "..") == 0 || + strcmp(name, "build") == 0 || strcmp(name, "source") == 0; +} + +static void scan_modules_recursive(const std::string &dir, const DepmodConfig &config, + std::vector &out) { DIR *d = opendir(dir.c_str()); if (!d) return; while (dirent *de = readdir(d)) { - if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + if (excluded_module_dir(de->d_name) || config.excluded_dirs.count(de->d_name) != 0) continue; std::string p = path_join(dir, de->d_name); struct stat st{}; - if (lstat(p.c_str(), &st) != 0) + if (stat(p.c_str(), &st) != 0) continue; if (S_ISDIR(st.st_mode)) { - scan_modules_recursive(root, p, out); + scan_modules_recursive(p, config, out); } else if (S_ISREG(st.st_mode) && is_module_file(p)) { out.push_back(p); } @@ -569,6 +655,131 @@ static void scan_modules_recursive(const std::string &root, const std::string &d closedir(d); } +static void parse_config_file(const std::string &path, const std::string &version, + DepmodConfig &config) +{ + FILE *f = fopen(path.c_str(), "r"); + if (!f) + return; + + char *line = nullptr; + size_t cap = 0; + while (getline(&line, &cap, f) >= 0) { + std::string text(line); + size_t comment = text.find('#'); + if (comment != std::string::npos) + text.resize(comment); + std::vector words; + size_t pos = 0; + while (pos < text.size()) { + while (pos < text.size() && std::isspace(static_cast(text[pos]))) + pos++; + if (pos == text.size()) + break; + size_t end = pos; + while (end < text.size() && !std::isspace(static_cast(text[end]))) + end++; + words.emplace_back(text, pos, end - pos); + pos = end; + } + if (words.empty()) + continue; + + if (words[0] == "exclude") { + for (size_t i = 1; i < words.size(); i++) + config.excluded_dirs.insert(words[i]); + } + } + free(line); + fclose(f); + (void)version; +} + +static std::vector config_files_in(const std::string &path) +{ + std::vector files; + struct stat st{}; + if (stat(path.c_str(), &st) != 0) + return files; + if (S_ISREG(st.st_mode)) { + files.push_back(path); + return files; + } + if (!S_ISDIR(st.st_mode)) + return files; + + DIR *dir = opendir(path.c_str()); + if (!dir) + return files; + while (dirent *de = readdir(dir)) { + if (de->d_name[0] == '.' || !has_suffix(de->d_name, ".conf")) + continue; + std::string candidate = path_join(path, de->d_name); + if (stat(candidate.c_str(), &st) == 0 && S_ISREG(st.st_mode)) + files.push_back(std::move(candidate)); + } + closedir(dir); + std::sort(files.begin(), files.end()); + return files; +} + +static DepmodConfig load_depmod_config(const Options &opts) +{ + DepmodConfig config; + std::vector paths = opts.config_paths; + if (paths.empty()) { + paths = { + path_join(opts.basedir, "etc/depmod.d"), + path_join(opts.basedir, "run/depmod.d"), + path_join(opts.basedir, "usr/local/lib/depmod.d"), + path_join(opts.basedir, "usr/lib/depmod.d"), + path_join(opts.basedir, "lib/depmod.d"), + }; + } + + for (const auto &path : paths) { + for (const auto &file : config_files_in(path)) + parse_config_file(file, opts.version, config); + } + return config; +} + +static void load_external_symbols(const Options &opts, std::set &symbols) +{ + const std::string &path = opts.symvers.empty() ? opts.filesyms : opts.symvers; + if (path.empty()) + return; + + FILE *f = fopen(path.c_str(), "r"); + if (!f) + return; + char line[10240]; + while (fgets(line, sizeof(line), f)) { + if (!opts.symvers.empty()) { + char *save = nullptr; + char *crc = strtok_r(line, " \t\r\n", &save); + char *name = strtok_r(nullptr, " \t\r\n", &save); + char *owner = strtok_r(nullptr, " \t\r\n", &save); + if (crc && name && owner && strcmp(owner, "vmlinux") == 0) + symbols.insert(strip_known_symbol_prefix(name, opts.symbol_prefix)); + } else { + char *name = strrchr(line, ' '); + if (!name) + continue; + name++; + while (*name == ' ' || *name == '\t') + name++; + char *end = name + strcspn(name, " \t\r\n"); + *end = '\0'; + static const char prefix[] = "__ksymtab_"; + if (strncmp(name, prefix, sizeof(prefix) - 1) == 0) + symbols.insert(strip_known_symbol_prefix(name + sizeof(prefix) - 1, + opts.symbol_prefix)); + } + } + fclose(f); +} + static std::string rel_to_root(const std::string &root, const std::string &path) { std::string r = trim_slashes_right(root); @@ -617,7 +828,7 @@ static bool module_less(const Module &a, const Module &b) } -static void resolve_dependencies(std::vector &mods, const Options &opts) +static bool resolve_dependencies(std::vector &mods, const Options &opts) { std::unordered_map by_name; std::unordered_map export_owner; @@ -642,31 +853,55 @@ static void resolve_dependencies(std::vector &mods, const Options &opts) auto it = export_owner.find(sym); if (it != export_owner.end() && it->second != i) direct[i].insert(it->second); - else if (opts.errsyms) + else if (opts.errsyms && opts.external_symbols.count(sym) == 0) warnx("%s needs unknown symbol %s", mods[i].relpath.c_str(), sym.c_str()); } } + bool cycle = false; for (size_t i = 0; i < mods.size(); i++) { std::set seen; std::vector flat; + std::vector state(mods.size(), 0); + std::vector stack; std::function dfs = [&](size_t n) { + if (state[n] == 2) + return; + if (state[n] == 1) { + std::string cycle_line; + for (size_t node : stack) { + if (!cycle_line.empty()) + cycle_line += " -> "; + cycle_line += mods[node].modname; + } + if (!cycle_line.empty()) + cycle_line += " -> "; + cycle_line += mods[n].modname; + warnx("Cycle detected: %s", cycle_line.c_str()); + cycle = true; + return; + } + state[n] = 1; + stack.push_back(n); std::vector children(direct[n].begin(), direct[n].end()); std::sort(children.begin(), children.end(), [&](size_t a, size_t b) { return module_less(mods[a], mods[b]); }); for (size_t d : children) { - if (d == i || seen.count(d)) + if (seen.count(d)) continue; seen.insert(d); dfs(d); flat.push_back(d); } + stack.pop_back(); + state[n] = 2; }; dfs(i); for (size_t d : flat) mods[i].deps_paths.push_back(mods[d].relpath); } + return !cycle; } static std::string dep_line(const Module &m) @@ -677,28 +912,30 @@ static std::string dep_line(const Module &m) return line; } -static std::string alias_normalize(const std::string &alias) +static bool alias_normalize(const std::string &alias, std::string &out) { - // Match kmod's alias index normalization: dashes are stored as - // underscores, except inside bracket ranges such as [0-9]. - std::string out; + out.clear(); out.reserve(alias.size()); bool in_range = false; for (char c : alias) { if (c == '[') in_range = true; - if (c == ']' && in_range) + if (c == ']') { + if (!in_range) + return false; in_range = false; + } if (c == '-' && !in_range) out.push_back('_'); else out.push_back(c); } - return out; + return !in_range; } class BinaryIndex { struct Node { + std::string prefix; std::map> children; std::vector> values; uint32_t size = 0; @@ -721,6 +958,8 @@ class BinaryIndex { uint32_t m = 0; if (!n.children.empty()) m |= INDEX_NODE_CHILDS; + if (!n.prefix.empty()) + m |= INDEX_NODE_PREFIX; if (!n.values.empty()) m |= INDEX_NODE_VALUES; return m; @@ -730,6 +969,8 @@ class BinaryIndex { { n.size = 0; n.total = 0; + if (!n.prefix.empty()) + n.size += static_cast(n.prefix.size() + 1); if (!n.children.empty()) { uint8_t first = n.children.begin()->first; uint8_t last = n.children.rbegin()->first; @@ -758,6 +999,8 @@ class BinaryIndex { static bool write_node(FILE *f, const Node &n, uint32_t offset) { std::map child_offsets; + if (!n.prefix.empty() && fwrite(n.prefix.c_str(), 1, n.prefix.size() + 1, f) != n.prefix.size() + 1) + return false; if (!n.children.empty()) { uint32_t child_base = offset + n.size; uint32_t accum = 0; @@ -795,28 +1038,52 @@ class BinaryIndex { return true; } - public: - void insert(const std::string &key, const std::string &value, uint32_t priority) + public: + bool insert(const std::string &key, const std::string &value, uint32_t priority) { if (!ascii_ok(key) || !ascii_ok(value)) - return; + return false; Node *n = &root_; - for (unsigned char c : key) { - auto &child = n->children[c]; - if (!child) - child = std::make_unique(); - n = child.get(); + size_t pos = 0; + for (;;) { + size_t common = 0; + while (common < n->prefix.size() && pos + common < key.size() && + n->prefix[common] == key[pos + common]) + common++; + if (common != n->prefix.size()) { + const std::string original = n->prefix; + auto old = std::make_unique(std::move(*n)); + const uint8_t edge = static_cast(original[common]); + old->prefix.erase(0, common + 1); + n->prefix = original.substr(0, common); + n->children.clear(); + n->values.clear(); + n->children.emplace(edge, std::move(old)); + } + + pos += n->prefix.size(); + if (pos == key.size()) { + const bool duplicate = std::find_if(n->values.begin(), n->values.end(), + [&](const auto &entry) { return entry.second == value; }) != n->values.end(); + auto it = n->values.begin(); + while (it != n->values.end() && it->first < priority) + ++it; + n->values.insert(it, {priority, value}); + return duplicate; + } + + const uint8_t edge = static_cast(key[pos]); + auto it = n->children.find(edge); + if (it == n->children.end()) { + auto child = std::make_unique(); + child->prefix = key.substr(pos + 1); + child->values.emplace_back(priority, value); + n->children.emplace(edge, std::move(child)); + return false; + } + n = it->second.get(); + pos++; } - auto dup = std::find_if(n->values.begin(), n->values.end(), [&](const auto &p) { - return p.second == value; - }); - if (dup == n->values.end()) - n->values.emplace_back(priority, value); - std::sort(n->values.begin(), n->values.end(), [](const auto &a, const auto &b) { - if (a.first != b.first) - return a.first < b.first; - return a.second < b.second; - }); } bool write(FILE *f) @@ -946,8 +1213,6 @@ static std::string build_modules_devname(const std::vector &mods) out += buf; } } - if (out.empty()) - out = "# Device nodes to trigger on-demand module loading.\n"; return out; } @@ -991,8 +1256,11 @@ static void add_builtin_alias_index(const std::string &module_root, BinaryIndex std::string mod = e.substr(0, dot); std::string key = e.substr(dot + 1, eq - dot - 1); std::string val = e.substr(eq + 1); - if (key == "alias") - idx.insert(alias_normalize(val), normalize_modname(mod), prio++); + if (key == "alias") { + std::string normalized; + if (alias_normalize(val, normalized)) + idx.insert(normalized, normalize_modname(mod), prio++); + } } } pos = next + 1; @@ -1034,11 +1302,16 @@ static bool output_all(const std::string &module_root, const std::string &out_ro BinaryIndex builtinaliasidx; for (uint32_t i = 0; i < mods.size(); i++) { const Module &m = mods[i]; - depidx.insert(m.modname, dep_line(m), i); - for (const auto &a : m.aliases) - aliasidx.insert(alias_normalize(a), m.modname, i); + if (depidx.insert(m.modname, dep_line(m), i) && opts.warn) + warnx("duplicate module deps:\n%s", dep_line(m).c_str()); + for (const auto &a : m.aliases) { + std::string normalized; + if (alias_normalize(a, normalized) && aliasidx.insert(normalized, m.modname, i) && opts.warn) + warnx("duplicate module alias:\n%s %s", normalized.c_str(), m.modname.c_str()); + } for (const auto &s : m.exports) - symidx.insert("symbol:" + s, m.modname, i); + if (symidx.insert("symbol:" + s, m.modname, i) && opts.warn) + warnx("duplicate module syms:\n%s %s", s.c_str(), m.modname.c_str()); } add_builtin_index(module_root, builtinidx); add_builtin_alias_index(module_root, builtinaliasidx); @@ -1046,8 +1319,10 @@ static bool output_all(const std::string &module_root, const std::string &out_ro ok = write_index_file(path_join(out_root, "modules.dep.bin"), depidx) && ok; ok = write_index_file(path_join(out_root, "modules.alias.bin"), aliasidx) && ok; ok = write_index_file(path_join(out_root, "modules.symbols.bin"), symidx) && ok; - ok = write_index_file(path_join(out_root, "modules.builtin.bin"), builtinidx) && ok; - ok = write_index_file(path_join(out_root, "modules.builtin.alias.bin"), builtinaliasidx) && ok; + if (access(path_join(module_root, "modules.builtin").c_str(), F_OK) == 0) + ok = write_index_file(path_join(out_root, "modules.builtin.bin"), builtinidx) && ok; + if (access(path_join(module_root, "modules.builtin.modinfo").c_str(), F_OK) == 0) + ok = write_index_file(path_join(out_root, "modules.builtin.alias.bin"), builtinaliasidx) && ok; return ok; } @@ -1078,24 +1353,29 @@ static void usage(FILE *out) { fputs("Usage:\n" "\tdepmod -[aA] [options] [forced_version]\n" - "\tdepmod [options] [forced_version] [modules...]\n" + "\n" + "If no arguments (except options) are given, \\\"depmod -a\\\" is assumed\n" + "\n" + "depmod will output a dependency list suitable for the modprobe utility.\n" "\n" "Options:\n" "\t-a, --all Probe all modules\n" - "\t-A, --quick Skip if modules.dep is newer than modules\n" - "\t-b, --basedir=DIR Input root path (default: /)\n" - "\t-m, --moduledir=DIR Module directory (default: /lib/modules)\n" - "\t-o, --outdir=DIR Output root path (default: basedir)\n" - "\t-C, --config=PATH Accepted for compatibility\n" - "\t-e, --errsyms Warn about unresolved module symbols\n" - "\t-F, --filesyms=FILE Accepted for compatibility\n" - "\t-E, --symvers=FILE Accepted for compatibility\n" - "\t-n, --show Write text output to stdout only\n" - "\t-P, --symbol-prefix Ignore architecture symbol prefix\n" - "\t-v, --verbose Print scanned modules\n" - "\t-w, --warn Enable duplicate warnings\n" + "\t-A, --quick Only does the work if there's a new module\n" + "\t-e, --errsyms Report not supplied symbols\n" + "\t-n, --show Write the dependency file on stdout only\n" + "\t-P, --symbol-prefix Architecture symbol prefix\n" + "\t-C, --config=PATH Read configuration from PATH\n" + "\t-v, --verbose Enable verbose mode\n" + "\t-w, --warn Warn on duplicates\n" "\t-V, --version Show version\n" - "\t-h, --help Show this help\n", out); + "\t-h, --help Show this help\n" + "\n" + "The following options are useful for people managing distributions:\n" + "\t-b, --basedir=DIR Root path (default: /).\n" + "\t-m, --moduledir=DIR Module directory (default: /lib/modules).\n" + "\t-o, --outdir=DIR Output root path (default: same as ).\n" + "\t-F, --filesyms=FILE Use the file instead of the current kernel symbols.\n" + "\t-E, --symvers=FILE Use Module.symvers to check symbol versions.\n", out); } static bool looks_like_version(const std::string &s) @@ -1105,6 +1385,17 @@ static bool looks_like_version(const std::string &s) return true; } +static bool is_version_number(const std::string &s) +{ + size_t dot = s.find('.'); + if (dot == std::string::npos || dot == 0 || dot + 1 == s.size()) + return false; + for (size_t i = 0; i < dot; i++) + if (!std::isdigit(static_cast(s[i]))) + return false; + return std::isdigit(static_cast(s[dot + 1])); +} + static int parse_args(int argc, char **argv, Options &opts) { static const option long_opts[] = { @@ -1131,20 +1422,26 @@ static int parse_args(int argc, char **argv, Options &opts) int c; while ((c = getopt_long(argc, argv, "aAb:m:o:C:E:F:evnP:wVh", long_opts, nullptr)) != -1) { switch (c) { - case 'a': opts.all = true; break; + case 'a': break; case 'A': opts.quick = true; break; case 'b': opts.basedir = optarg; break; case 'm': opts.moduledir = optarg; break; case 'o': opts.outdir = optarg; break; - case 'C': opts.config = optarg; break; + case 'C': opts.config_paths.emplace_back(optarg); break; case 'E': opts.symvers = optarg; break; case 'F': opts.filesyms = optarg; break; case 'e': opts.errsyms = true; break; case 'v': opts.verbose = true; break; case 'n': opts.dry_run = true; break; - case 'P': opts.symbol_prefix = optarg && optarg[0] ? optarg[0] : '\0'; break; + case 'P': + if (!optarg || strlen(optarg) != 1) { + warnx("-P only takes a single char"); + return -1; + } + opts.symbol_prefix = optarg[0]; + break; case 'w': opts.warn = true; break; - case 'V': puts("vx depmod 0.1"); return 1; + case 'V': puts("kmod version 34.2"); return 1; case 'h': usage(stdout); return 1; default: usage(stderr); return -1; } @@ -1168,6 +1465,10 @@ static int parse_args(int argc, char **argv, Options &opts) warnx("could not determine kernel release"); return -1; } + if (!is_version_number(opts.version)) { + warnx("Bad version passed %s", opts.version.c_str()); + return -1; + } if (opts.outdir.empty()) opts.outdir = opts.basedir; @@ -1192,7 +1493,14 @@ extern "C" int depmod_main(int argc, char **argv) std::string out_parent = path_join(opts.outdir, opts.moduledir); std::string out_root = path_join(out_parent, opts.version); + if (opts.errsyms && opts.symvers.empty() && opts.filesyms.empty()) { + warnx("-e needs -E or -F"); + opts.errsyms = false; + } + load_external_symbols(opts, opts.external_symbols); + std::vector paths; + DepmodConfig config = load_depmod_config(opts); if (!opts.files.empty()) { for (const auto &f : opts.files) { if (f.empty()) @@ -1203,8 +1511,7 @@ extern "C" int depmod_main(int argc, char **argv) paths.push_back(path_join(module_root, f)); } } else { - opts.all = true; - scan_modules_recursive(module_root, module_root, paths); + scan_modules_recursive(module_root, config, paths); } std::sort(paths.begin(), paths.end()); @@ -1228,7 +1535,6 @@ extern "C" int depmod_main(int argc, char **argv) m.path = p; m.relpath = rel_to_root(module_root, p); m.modname = normalize_modname(m.relpath); - m.scan_index = mods.size(); if (parse_module(m, opts)) { if (opts.verbose) fprintf(stdout, "%s\n", m.relpath.c_str()); @@ -1238,9 +1544,8 @@ extern "C" int depmod_main(int argc, char **argv) load_modules_order(module_root, mods); std::sort(mods.begin(), mods.end(), module_less); - for (size_t i = 0; i < mods.size(); i++) - mods[i].scan_index = i; - resolve_dependencies(mods, opts); + if (!resolve_dependencies(mods, opts)) + return 1; if (!output_all(module_root, out_root, mods, opts)) { warnx("failed to write dependency files under %s", out_root.c_str()); diff --git a/tests/test_depmod.sh b/tests/test_depmod.sh index d777c4a..0e80073 100644 --- a/tests/test_depmod.sh +++ b/tests/test_depmod.sh @@ -2,3 +2,27 @@ set -eu "$VX" depmod --help >/dev/null "$VX" depmod --version >/dev/null + +if command -v zstd >/dev/null 2>&1 && command -v xz >/dev/null 2>&1; then + _test_dir=$(mktemp -d) + trap 'rm -rf "$_test_dir"' EXIT + _module_dir="$_test_dir/root/lib/modules/7.1.3-vertex/kernel" + mkdir -p "$_module_dir/build" "$_module_dir/source" + cp /bin/true "$_module_dir/build/ignored.ko" + cp /bin/true "$_module_dir/source/ignored.ko" + + cp /bin/true "$_module_dir/acpi_pad.ko" + zstd -q -f "$_module_dir/acpi_pad.ko" -o "$_module_dir/acpi_pad.ko.zst" + rm "$_module_dir/acpi_pad.ko" + cp /bin/true "$_module_dir/p4-clockmod.ko" + xz -f "$_module_dir/p4-clockmod.ko" + cp /bin/true "$_module_dir/soundwire-bus.ko" + + "$VX" depmod -b "$_test_dir/root" 7.1.3-vertex 2>"$_test_dir/stderr" + test ! -s "$_test_dir/stderr" + grep -Fx 'kernel/acpi_pad.ko.zst:' "$_test_dir/root/lib/modules/7.1.3-vertex/modules.dep" + grep -Fx 'kernel/p4-clockmod.ko.xz:' "$_test_dir/root/lib/modules/7.1.3-vertex/modules.dep" + grep -Fx 'kernel/soundwire-bus.ko:' "$_test_dir/root/lib/modules/7.1.3-vertex/modules.dep" + test "$(wc -l < "$_test_dir/root/lib/modules/7.1.3-vertex/modules.dep")" -eq 3 + test ! -s "$_test_dir/root/lib/modules/7.1.3-vertex/modules.devname" +fi