build: add lzma support and update dependencies in depmod

This commit is contained in:
2026-07-09 20:04:06 -05:00
parent 5de9c9864d
commit 6de58db258
3 changed files with 431 additions and 102 deletions
+1 -1
View File
@@ -258,7 +258,7 @@ endif
if enable_depmod if enable_depmod
vx_sources += depmod_src vx_sources += depmod_src
vx_dependencies += [dependency('zlib'), dependency('libzstd')] vx_dependencies += [dependency('zlib'), dependency('libzstd'), dependency('liblzma')]
endif endif
vx = executable('vx', vx = executable('vx',
+403 -98
View File
@@ -9,6 +9,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <dirent.h> #include <dirent.h>
#include <elf.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <getopt.h> #include <getopt.h>
@@ -33,6 +34,7 @@
#include <vector> #include <vector>
#include <zlib.h> #include <zlib.h>
#include <lzma.h>
#include <zstd.h> #include <zstd.h>
namespace { 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_PREFIX = 0x80000000u;
constexpr uint32_t INDEX_NODE_VALUES = 0x40000000u; constexpr uint32_t INDEX_NODE_VALUES = 0x40000000u;
constexpr uint32_t INDEX_NODE_CHILDS = 0x20000000u; constexpr uint32_t INDEX_NODE_CHILDS = 0x20000000u;
constexpr uint32_t INDEX_NODE_MASK = 0x0fffffffu;
struct Options { struct Options {
bool all = false;
bool quick = false; bool quick = false;
bool dry_run = false; bool dry_run = false;
bool errsyms = false; bool errsyms = false;
@@ -54,12 +54,17 @@ struct Options {
std::string basedir = "/"; std::string basedir = "/";
std::string moduledir = "/lib/modules"; std::string moduledir = "/lib/modules";
std::string outdir; std::string outdir;
std::string config; std::vector<std::string> config_paths;
std::string symvers; std::string symvers;
std::string filesyms; std::string filesyms;
char symbol_prefix = '\0'; char symbol_prefix = '\0';
std::string version; std::string version;
std::vector<std::string> files; std::vector<std::string> files;
std::set<std::string> external_symbols;
};
struct DepmodConfig {
std::set<std::string> excluded_dirs;
}; };
struct Module { struct Module {
@@ -74,7 +79,6 @@ struct Module {
std::vector<std::string> exports; std::vector<std::string> exports;
std::vector<std::string> undefined; std::vector<std::string> undefined;
int order = 0x3fffffff; int order = 0x3fffffff;
size_t scan_index = 0;
}; };
static void warnx(const char *fmt, ...) static void warnx(const char *fmt, ...)
@@ -140,6 +144,8 @@ static std::string strip_module_suffix(std::string name)
{ {
if (has_suffix(name, ".zst")) if (has_suffix(name, ".zst"))
name.resize(name.size() - 4); name.resize(name.size() - 4);
else if (has_suffix(name, ".xz"))
name.resize(name.size() - 3);
else if (has_suffix(name, ".gz")) else if (has_suffix(name, ".gz"))
name.resize(name.size() - 3); name.resize(name.size() - 3);
@@ -155,6 +161,9 @@ static std::string normalize_modname(std::string name)
if (c == '-') if (c == '-')
c = '_'; c = '_';
} }
size_t dot = name.find('.');
if (dot != std::string::npos)
name.resize(dot);
return name; return name;
} }
@@ -244,45 +253,75 @@ static bool decompress_gzip(const std::vector<uint8_t> &in, std::vector<uint8_t>
static bool decompress_zstd(const std::vector<uint8_t> &in, std::vector<uint8_t> &out) static bool decompress_zstd(const std::vector<uint8_t> &in, std::vector<uint8_t> &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<size_t>(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(); ZSTD_DStream *ds = ZSTD_createDStream();
if (!ds) if (!ds)
return false; 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}; ZSTD_inBuffer input{in.data(), in.size(), 0};
std::vector<uint8_t> buf(ZSTD_DStreamOutSize()); std::vector<uint8_t> 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}; 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)) { if (ZSTD_isError(ret)) {
ZSTD_freeDStream(ds); ZSTD_freeDStream(ds);
return false; return false;
} }
out.insert(out.end(), buf.begin(), buf.begin() + output.pos); 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); ZSTD_freeDStream(ds);
return true; return true;
} }
static bool decompress_xz(const std::vector<uint8_t> &in, std::vector<uint8_t> &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<uint8_t> 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<uint8_t> &out) static bool read_module_image(const std::string &path, std::vector<uint8_t> &out)
{ {
std::vector<uint8_t> raw; std::vector<uint8_t> raw;
if (!read_file_raw(path, raw)) if (!read_file_raw(path, raw))
return false; return false;
if (has_suffix(path, ".ko.gz")) if (raw.size() >= 4 && raw[0] == 0x28 && raw[1] == 0xb5 &&
return decompress_gzip(raw, out); raw[2] == 0x2f && raw[3] == 0xfd)
if (has_suffix(path, ".ko.zst"))
return decompress_zstd(raw, out); 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); out = std::move(raw);
return true; return true;
@@ -333,7 +372,8 @@ struct ElfReader {
bool parse() 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; return false;
is64 = data[4] == 2; is64 = data[4] == 2;
if (data[4] != 1 && data[4] != 2) if (data[4] != 1 && data[4] != 2)
@@ -345,6 +385,11 @@ struct ElfReader {
else else
return false; 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; uint64_t shoff = 0;
uint16_t shentsize = 0, shnum = 0, shstrndx = 0; uint16_t shentsize = 0, shnum = 0, shstrndx = 0;
if (is64) { if (is64) {
@@ -358,9 +403,9 @@ struct ElfReader {
shnum = rd<uint16_t>(0x30); shnum = rd<uint16_t>(0x30);
shstrndx = rd<uint16_t>(0x32); shstrndx = rd<uint16_t>(0x32);
} }
if (shoff == 0 || shentsize == 0 || shnum == 0 || shstrndx >= shnum) if (shoff == 0 || shentsize != section_size || shnum == 0 || shstrndx >= shnum)
return false; return false;
if (shoff + static_cast<uint64_t>(shentsize) * shnum > data.size()) if (shoff > data.size() || static_cast<uint64_t>(shentsize) * shnum > data.size() - shoff)
return false; return false;
std::vector<uint32_t> name_offsets(shnum); std::vector<uint32_t> name_offsets(shnum);
@@ -383,7 +428,7 @@ struct ElfReader {
s.link = rd<uint32_t>(off + 0x18); s.link = rd<uint32_t>(off + 0x18);
s.entsize = rd<uint32_t>(off + 0x24); s.entsize = rd<uint32_t>(off + 0x24);
} }
if (s.offset + s.size > data.size()) if (s.offset > data.size() || s.size > data.size() - s.offset)
return false; return false;
sections[i] = std::move(s); sections[i] = std::move(s);
} }
@@ -395,8 +440,11 @@ struct ElfReader {
continue; continue;
size_t start = static_cast<size_t>(shstr.offset + noff); size_t start = static_cast<size_t>(shstr.offset + noff);
size_t end = start; size_t end = start;
while (end < data.size() && data[end] != '\0') const size_t shstr_end = static_cast<size_t>(shstr.offset + shstr.size);
while (end < shstr_end && data[end] != '\0')
end++; end++;
if (end == shstr_end)
continue;
sections[i].name.assign(reinterpret_cast<const char *>(data.data() + start), end - start); sections[i].name.assign(reinterpret_cast<const char *>(data.data() + start), end - start);
} }
return true; return true;
@@ -417,8 +465,11 @@ struct ElfReader {
return {}; return {};
size_t start = static_cast<size_t>(s.offset + off); size_t start = static_cast<size_t>(s.offset + off);
size_t end = start; size_t end = start;
while (end < data.size() && data[end] != '\0') const size_t section_end = static_cast<size_t>(s.offset + s.size);
while (end < section_end && data[end] != '\0')
end++; end++;
if (end == section_end)
return {};
return std::string(reinterpret_cast<const char *>(data.data() + start), end - start); return std::string(reinterpret_cast<const char *>(data.data() + start), end - start);
} }
@@ -456,8 +507,6 @@ struct ElfReader {
mod.softdeps.push_back(val); mod.softdeps.push_back(val);
} else if (key == "weakdep") { } else if (key == "weakdep") {
mod.weakdeps.push_back(val); 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 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[] = { static const char *export_prefixes[] = {
"__ksymtab_unused_gpl_", "__ksymtab_unused_gpl_",
"__ksymtab_gpl_future_", "__ksymtab_gpl_future_",
@@ -478,6 +525,21 @@ struct ElfReader {
"__ksymtab_strings+", "__ksymtab_strings+",
}; };
const Section *ksymtab_strings = find_section("__ksymtab_strings");
if (ksymtab_strings) {
size_t pos = static_cast<size_t>(ksymtab_strings->offset);
size_t end = static_cast<size_t>(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<const char *>(data.data() + pos), next - pos), prefix));
pos = next + 1;
}
}
for (size_t si = 0; si < sections.size(); si++) { for (size_t si = 0; si < sections.size(); si++) {
const Section &symsec = sections[si]; const Section &symsec = sections[si];
if (symsec.type != SHT_SYMTAB || symsec.entsize == 0 || symsec.link >= sections.size()) 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++) { for (size_t i = 0; i < count; i++) {
size_t off = static_cast<size_t>(symsec.offset + i * symsec.entsize); size_t off = static_cast<size_t>(symsec.offset + i * symsec.entsize);
uint32_t st_name = 0; uint32_t st_name = 0;
uint8_t st_info = 0;
uint16_t st_shndx = 0; uint16_t st_shndx = 0;
if (is64) { if (is64) {
st_name = rd<uint32_t>(off + 0x00); st_name = rd<uint32_t>(off + 0x00);
st_info = rd<uint8_t>(off + 0x04);
st_shndx = rd<uint16_t>(off + 0x06); st_shndx = rd<uint16_t>(off + 0x06);
} else { } else {
st_name = rd<uint32_t>(off + 0x00); st_name = rd<uint32_t>(off + 0x00);
st_info = rd<uint8_t>(off + 0x0c);
st_shndx = rd<uint16_t>(off + 0x0e); st_shndx = rd<uint16_t>(off + 0x0e);
} }
std::string name = str_at(strsec, st_name); std::string name = str_at(strsec, st_name);
if (name.empty()) if (name.empty())
continue; continue;
unsigned bind = st_info >> 4; if (st_shndx == SHN_UNDEF) {
if (st_shndx == SHN_UNDEF && (bind == 1 || bind == 2)) {
mod.undefined.push_back(strip_known_symbol_prefix(name, prefix)); mod.undefined.push_back(strip_known_symbol_prefix(name, prefix));
continue; 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<size_t>(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<size_t>(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<const char *>(data.data() + name_start),
name_end - name_start), prefix));
}
}
std::sort(mod.exports.begin(), mod.exports.end()); std::sort(mod.exports.begin(), mod.exports.end());
mod.exports.erase(std::unique(mod.exports.begin(), mod.exports.end()), 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()); 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) 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<std::string> &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<std::string> &out)
{ {
DIR *d = opendir(dir.c_str()); DIR *d = opendir(dir.c_str());
if (!d) if (!d)
return; return;
while (dirent *de = readdir(d)) { 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; continue;
std::string p = path_join(dir, de->d_name); std::string p = path_join(dir, de->d_name);
struct stat st{}; struct stat st{};
if (lstat(p.c_str(), &st) != 0) if (stat(p.c_str(), &st) != 0)
continue; continue;
if (S_ISDIR(st.st_mode)) { 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)) { } else if (S_ISREG(st.st_mode) && is_module_file(p)) {
out.push_back(p); out.push_back(p);
} }
@@ -569,6 +655,131 @@ static void scan_modules_recursive(const std::string &root, const std::string &d
closedir(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<std::string> words;
size_t pos = 0;
while (pos < text.size()) {
while (pos < text.size() && std::isspace(static_cast<unsigned char>(text[pos])))
pos++;
if (pos == text.size())
break;
size_t end = pos;
while (end < text.size() && !std::isspace(static_cast<unsigned char>(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<std::string> config_files_in(const std::string &path)
{
std::vector<std::string> 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<std::string> 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<std::string> &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) static std::string rel_to_root(const std::string &root, const std::string &path)
{ {
std::string r = trim_slashes_right(root); 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<Module> &mods, const Options &opts) static bool resolve_dependencies(std::vector<Module> &mods, const Options &opts)
{ {
std::unordered_map<std::string, size_t> by_name; std::unordered_map<std::string, size_t> by_name;
std::unordered_map<std::string, size_t> export_owner; std::unordered_map<std::string, size_t> export_owner;
@@ -642,31 +853,55 @@ static void resolve_dependencies(std::vector<Module> &mods, const Options &opts)
auto it = export_owner.find(sym); auto it = export_owner.find(sym);
if (it != export_owner.end() && it->second != i) if (it != export_owner.end() && it->second != i)
direct[i].insert(it->second); 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()); 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++) { for (size_t i = 0; i < mods.size(); i++) {
std::set<size_t> seen; std::set<size_t> seen;
std::vector<size_t> flat; std::vector<size_t> flat;
std::vector<uint8_t> state(mods.size(), 0);
std::vector<size_t> stack;
std::function<void(size_t)> dfs = [&](size_t n) { std::function<void(size_t)> 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<size_t> children(direct[n].begin(), direct[n].end()); std::vector<size_t> children(direct[n].begin(), direct[n].end());
std::sort(children.begin(), children.end(), [&](size_t a, size_t b) { std::sort(children.begin(), children.end(), [&](size_t a, size_t b) {
return module_less(mods[a], mods[b]); return module_less(mods[a], mods[b]);
}); });
for (size_t d : children) { for (size_t d : children) {
if (d == i || seen.count(d)) if (seen.count(d))
continue; continue;
seen.insert(d); seen.insert(d);
dfs(d); dfs(d);
flat.push_back(d); flat.push_back(d);
} }
stack.pop_back();
state[n] = 2;
}; };
dfs(i); dfs(i);
for (size_t d : flat) for (size_t d : flat)
mods[i].deps_paths.push_back(mods[d].relpath); mods[i].deps_paths.push_back(mods[d].relpath);
} }
return !cycle;
} }
static std::string dep_line(const Module &m) static std::string dep_line(const Module &m)
@@ -677,28 +912,30 @@ static std::string dep_line(const Module &m)
return line; 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 out.clear();
// underscores, except inside bracket ranges such as [0-9].
std::string out;
out.reserve(alias.size()); out.reserve(alias.size());
bool in_range = false; bool in_range = false;
for (char c : alias) { for (char c : alias) {
if (c == '[') if (c == '[')
in_range = true; in_range = true;
if (c == ']' && in_range) if (c == ']') {
if (!in_range)
return false;
in_range = false; in_range = false;
}
if (c == '-' && !in_range) if (c == '-' && !in_range)
out.push_back('_'); out.push_back('_');
else else
out.push_back(c); out.push_back(c);
} }
return out; return !in_range;
} }
class BinaryIndex { class BinaryIndex {
struct Node { struct Node {
std::string prefix;
std::map<uint8_t, std::unique_ptr<Node>> children; std::map<uint8_t, std::unique_ptr<Node>> children;
std::vector<std::pair<uint32_t, std::string>> values; std::vector<std::pair<uint32_t, std::string>> values;
uint32_t size = 0; uint32_t size = 0;
@@ -721,6 +958,8 @@ class BinaryIndex {
uint32_t m = 0; uint32_t m = 0;
if (!n.children.empty()) if (!n.children.empty())
m |= INDEX_NODE_CHILDS; m |= INDEX_NODE_CHILDS;
if (!n.prefix.empty())
m |= INDEX_NODE_PREFIX;
if (!n.values.empty()) if (!n.values.empty())
m |= INDEX_NODE_VALUES; m |= INDEX_NODE_VALUES;
return m; return m;
@@ -730,6 +969,8 @@ class BinaryIndex {
{ {
n.size = 0; n.size = 0;
n.total = 0; n.total = 0;
if (!n.prefix.empty())
n.size += static_cast<uint32_t>(n.prefix.size() + 1);
if (!n.children.empty()) { if (!n.children.empty()) {
uint8_t first = n.children.begin()->first; uint8_t first = n.children.begin()->first;
uint8_t last = n.children.rbegin()->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) static bool write_node(FILE *f, const Node &n, uint32_t offset)
{ {
std::map<uint8_t, uint32_t> child_offsets; std::map<uint8_t, uint32_t> 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()) { if (!n.children.empty()) {
uint32_t child_base = offset + n.size; uint32_t child_base = offset + n.size;
uint32_t accum = 0; uint32_t accum = 0;
@@ -796,27 +1039,51 @@ class BinaryIndex {
} }
public: public:
void insert(const std::string &key, const std::string &value, uint32_t priority) bool insert(const std::string &key, const std::string &value, uint32_t priority)
{ {
if (!ascii_ok(key) || !ascii_ok(value)) if (!ascii_ok(key) || !ascii_ok(value))
return; return false;
Node *n = &root_; Node *n = &root_;
for (unsigned char c : key) { size_t pos = 0;
auto &child = n->children[c]; for (;;) {
if (!child) size_t common = 0;
child = std::make_unique<Node>(); while (common < n->prefix.size() && pos + common < key.size() &&
n = child.get(); n->prefix[common] == key[pos + common])
common++;
if (common != n->prefix.size()) {
const std::string original = n->prefix;
auto old = std::make_unique<Node>(std::move(*n));
const uint8_t edge = static_cast<uint8_t>(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<uint8_t>(key[pos]);
auto it = n->children.find(edge);
if (it == n->children.end()) {
auto child = std::make_unique<Node>();
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) bool write(FILE *f)
@@ -946,8 +1213,6 @@ static std::string build_modules_devname(const std::vector<Module> &mods)
out += buf; out += buf;
} }
} }
if (out.empty())
out = "# Device nodes to trigger on-demand module loading.\n";
return out; 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 mod = e.substr(0, dot);
std::string key = e.substr(dot + 1, eq - dot - 1); std::string key = e.substr(dot + 1, eq - dot - 1);
std::string val = e.substr(eq + 1); std::string val = e.substr(eq + 1);
if (key == "alias") if (key == "alias") {
idx.insert(alias_normalize(val), normalize_modname(mod), prio++); std::string normalized;
if (alias_normalize(val, normalized))
idx.insert(normalized, normalize_modname(mod), prio++);
}
} }
} }
pos = next + 1; pos = next + 1;
@@ -1034,11 +1302,16 @@ static bool output_all(const std::string &module_root, const std::string &out_ro
BinaryIndex builtinaliasidx; BinaryIndex builtinaliasidx;
for (uint32_t i = 0; i < mods.size(); i++) { for (uint32_t i = 0; i < mods.size(); i++) {
const Module &m = mods[i]; const Module &m = mods[i];
depidx.insert(m.modname, dep_line(m), i); if (depidx.insert(m.modname, dep_line(m), i) && opts.warn)
for (const auto &a : m.aliases) warnx("duplicate module deps:\n%s", dep_line(m).c_str());
aliasidx.insert(alias_normalize(a), m.modname, i); 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) 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_index(module_root, builtinidx);
add_builtin_alias_index(module_root, builtinaliasidx); add_builtin_alias_index(module_root, builtinaliasidx);
@@ -1046,7 +1319,9 @@ 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.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.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.symbols.bin"), symidx) && 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; 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; ok = write_index_file(path_join(out_root, "modules.builtin.alias.bin"), builtinaliasidx) && ok;
return ok; return ok;
} }
@@ -1078,24 +1353,29 @@ static void usage(FILE *out)
{ {
fputs("Usage:\n" fputs("Usage:\n"
"\tdepmod -[aA] [options] [forced_version]\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" "\n"
"Options:\n" "Options:\n"
"\t-a, --all Probe all modules\n" "\t-a, --all Probe all modules\n"
"\t-A, --quick Skip if modules.dep is newer than modules\n" "\t-A, --quick Only does the work if there's a new module\n"
"\t-b, --basedir=DIR Input root path (default: /)\n" "\t-e, --errsyms Report not supplied symbols\n"
"\t-m, --moduledir=DIR Module directory (default: /lib/modules)\n" "\t-n, --show Write the dependency file on stdout only\n"
"\t-o, --outdir=DIR Output root path (default: basedir)\n" "\t-P, --symbol-prefix Architecture symbol prefix\n"
"\t-C, --config=PATH Accepted for compatibility\n" "\t-C, --config=PATH Read configuration from PATH\n"
"\t-e, --errsyms Warn about unresolved module symbols\n" "\t-v, --verbose Enable verbose mode\n"
"\t-F, --filesyms=FILE Accepted for compatibility\n" "\t-w, --warn Warn on duplicates\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-V, --version Show version\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 <basedir>).\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) static bool looks_like_version(const std::string &s)
@@ -1105,6 +1385,17 @@ static bool looks_like_version(const std::string &s)
return true; 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<unsigned char>(s[i])))
return false;
return std::isdigit(static_cast<unsigned char>(s[dot + 1]));
}
static int parse_args(int argc, char **argv, Options &opts) static int parse_args(int argc, char **argv, Options &opts)
{ {
static const option long_opts[] = { static const option long_opts[] = {
@@ -1131,20 +1422,26 @@ static int parse_args(int argc, char **argv, Options &opts)
int c; int c;
while ((c = getopt_long(argc, argv, "aAb:m:o:C:E:F:evnP:wVh", long_opts, nullptr)) != -1) { while ((c = getopt_long(argc, argv, "aAb:m:o:C:E:F:evnP:wVh", long_opts, nullptr)) != -1) {
switch (c) { switch (c) {
case 'a': opts.all = true; break; case 'a': break;
case 'A': opts.quick = true; break; case 'A': opts.quick = true; break;
case 'b': opts.basedir = optarg; break; case 'b': opts.basedir = optarg; break;
case 'm': opts.moduledir = optarg; break; case 'm': opts.moduledir = optarg; break;
case 'o': opts.outdir = 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 'E': opts.symvers = optarg; break;
case 'F': opts.filesyms = optarg; break; case 'F': opts.filesyms = optarg; break;
case 'e': opts.errsyms = true; break; case 'e': opts.errsyms = true; break;
case 'v': opts.verbose = true; break; case 'v': opts.verbose = true; break;
case 'n': opts.dry_run = 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 '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; case 'h': usage(stdout); return 1;
default: usage(stderr); 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"); warnx("could not determine kernel release");
return -1; return -1;
} }
if (!is_version_number(opts.version)) {
warnx("Bad version passed %s", opts.version.c_str());
return -1;
}
if (opts.outdir.empty()) if (opts.outdir.empty())
opts.outdir = opts.basedir; 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_parent = path_join(opts.outdir, opts.moduledir);
std::string out_root = path_join(out_parent, opts.version); 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<std::string> paths; std::vector<std::string> paths;
DepmodConfig config = load_depmod_config(opts);
if (!opts.files.empty()) { if (!opts.files.empty()) {
for (const auto &f : opts.files) { for (const auto &f : opts.files) {
if (f.empty()) if (f.empty())
@@ -1203,8 +1511,7 @@ extern "C" int depmod_main(int argc, char **argv)
paths.push_back(path_join(module_root, f)); paths.push_back(path_join(module_root, f));
} }
} else { } else {
opts.all = true; scan_modules_recursive(module_root, config, paths);
scan_modules_recursive(module_root, module_root, paths);
} }
std::sort(paths.begin(), paths.end()); std::sort(paths.begin(), paths.end());
@@ -1228,7 +1535,6 @@ extern "C" int depmod_main(int argc, char **argv)
m.path = p; m.path = p;
m.relpath = rel_to_root(module_root, p); m.relpath = rel_to_root(module_root, p);
m.modname = normalize_modname(m.relpath); m.modname = normalize_modname(m.relpath);
m.scan_index = mods.size();
if (parse_module(m, opts)) { if (parse_module(m, opts)) {
if (opts.verbose) if (opts.verbose)
fprintf(stdout, "%s\n", m.relpath.c_str()); 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); load_modules_order(module_root, mods);
std::sort(mods.begin(), mods.end(), module_less); std::sort(mods.begin(), mods.end(), module_less);
for (size_t i = 0; i < mods.size(); i++) if (!resolve_dependencies(mods, opts))
mods[i].scan_index = i; return 1;
resolve_dependencies(mods, opts);
if (!output_all(module_root, out_root, mods, opts)) { if (!output_all(module_root, out_root, mods, opts)) {
warnx("failed to write dependency files under %s", out_root.c_str()); warnx("failed to write dependency files under %s", out_root.c_str());
+24
View File
@@ -2,3 +2,27 @@
set -eu set -eu
"$VX" depmod --help >/dev/null "$VX" depmod --help >/dev/null
"$VX" depmod --version >/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