feat: Add DKMS support for Depot-managed kernel modules
- Introduced a new `dkms` module in the installation helpers. - Updated `BuildType` enum to include `Dkms`. - Enhanced `PackageSpec` to validate DKMS-specific fields and added methods to retrieve effective DKMS names, versions, and install directories. - Implemented DKMS build and installation logic in a new `dkms.rs` file, including staging source trees and lifecycle scripts. - Added runtime support for managing installed DKMS modules, including autoinstall and removal functionalities. - Created tests to validate DKMS parsing and module management.
This commit is contained in:
@@ -54,7 +54,7 @@ pub(crate) fn build_tool_package_option(build_type: BuildType) -> Option<&'stati
|
|||||||
BuildType::Python => Some("DEPOT_PYTHON_PACKAGE"),
|
BuildType::Python => Some("DEPOT_PYTHON_PACKAGE"),
|
||||||
BuildType::Rust => Some("DEPOT_RUST_PACKAGE"),
|
BuildType::Rust => Some("DEPOT_RUST_PACKAGE"),
|
||||||
BuildType::Makefile => Some("DEPOT_MAKEFILE_PACKAGE"),
|
BuildType::Makefile => Some("DEPOT_MAKEFILE_PACKAGE"),
|
||||||
BuildType::Bin | BuildType::Meta => None,
|
BuildType::Dkms | BuildType::Bin | BuildType::Meta => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ pub(crate) fn requested_build_tool_package(build_type: BuildType) -> Option<Stri
|
|||||||
BuildType::Python => normalize_string_option(option_env!("DEPOT_PYTHON_PACKAGE")),
|
BuildType::Python => normalize_string_option(option_env!("DEPOT_PYTHON_PACKAGE")),
|
||||||
BuildType::Rust => normalize_string_option(option_env!("DEPOT_RUST_PACKAGE")),
|
BuildType::Rust => normalize_string_option(option_env!("DEPOT_RUST_PACKAGE")),
|
||||||
BuildType::Makefile => normalize_string_option(option_env!("DEPOT_MAKEFILE_PACKAGE")),
|
BuildType::Makefile => normalize_string_option(option_env!("DEPOT_MAKEFILE_PACKAGE")),
|
||||||
BuildType::Bin | BuildType::Meta => None,
|
BuildType::Dkms | BuildType::Bin | BuildType::Meta => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,378 @@
|
|||||||
|
//! Depot-managed kernel module source packaging.
|
||||||
|
|
||||||
|
use crate::cross::CrossConfig;
|
||||||
|
use crate::install::scripts;
|
||||||
|
use crate::package::{DkmsModule, PackageSpec};
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
|
const DEPOT_KMOD_MANIFEST: &str = ".depot-kmod.toml";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct DepotKmodManifest {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
pub install_dir: String,
|
||||||
|
pub make_args: Vec<String>,
|
||||||
|
pub pre_build: Vec<String>,
|
||||||
|
pub modules: Vec<DepotKmodModule>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct DepotKmodModule {
|
||||||
|
pub name: String,
|
||||||
|
pub dest_name: String,
|
||||||
|
pub build_dir: String,
|
||||||
|
pub built_location: String,
|
||||||
|
pub install_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(
|
||||||
|
spec: &PackageSpec,
|
||||||
|
src_dir: &Path,
|
||||||
|
destdir: &Path,
|
||||||
|
_cross: Option<&CrossConfig>,
|
||||||
|
_export_compiler_flags: bool,
|
||||||
|
_host_build_dir: Option<&Path>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if spec.build.flags.lib32_variant {
|
||||||
|
anyhow::bail!("build.type = \"dkms\" does not support lib32 variants");
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::create_dir_all(destdir)
|
||||||
|
.with_context(|| format!("Failed to create DESTDIR: {}", destdir.display()))?;
|
||||||
|
|
||||||
|
let source_root = resolve_dkms_source_root(spec, src_dir)?;
|
||||||
|
let source_dest = staged_source_dir(spec, destdir);
|
||||||
|
if source_dest.exists() {
|
||||||
|
fs::remove_dir_all(&source_dest).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to remove existing DKMS source staging dir: {}",
|
||||||
|
source_dest.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
crate::fs_copy::copy_tree_preserving_links(&source_root, &source_dest).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to stage DKMS source tree from {} to {}",
|
||||||
|
source_root.display(),
|
||||||
|
source_dest.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let manifest = manifest_from_spec(spec)?;
|
||||||
|
let manifest_toml =
|
||||||
|
toml::to_string_pretty(&manifest).context("Failed to serialize DKMS metadata")?;
|
||||||
|
fs::write(source_dest.join(DEPOT_KMOD_MANIFEST), manifest_toml).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to write DKMS metadata: {}",
|
||||||
|
source_dest.join(DEPOT_KMOD_MANIFEST).display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn stage_lifecycle_scripts(spec: &PackageSpec, destdir: &Path) -> Result<()> {
|
||||||
|
if !spec.build.flags.dkms_autoinstall {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let scripts_dir = scripts::staged_scripts_dir(destdir);
|
||||||
|
fs::create_dir_all(&scripts_dir)
|
||||||
|
.with_context(|| format!("Failed to create scripts dir: {}", scripts_dir.display()))?;
|
||||||
|
|
||||||
|
let source_rel = format!("/usr/src/{}", source_dir_name(spec));
|
||||||
|
let package_name = spec.effective_dkms_name();
|
||||||
|
stage_script_command(
|
||||||
|
&scripts_dir.join("post_install"),
|
||||||
|
&autoinstall_command(&source_rel),
|
||||||
|
)?;
|
||||||
|
stage_script_command(
|
||||||
|
&scripts_dir.join("post_update"),
|
||||||
|
&autoinstall_command(&source_rel),
|
||||||
|
)?;
|
||||||
|
stage_script_command(
|
||||||
|
&scripts_dir.join("pre_remove"),
|
||||||
|
&remove_command(&package_name),
|
||||||
|
)?;
|
||||||
|
stage_script_command(
|
||||||
|
&scripts_dir.join("pre_update"),
|
||||||
|
&remove_command(&package_name),
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn manifest_from_spec(spec: &PackageSpec) -> Result<DepotKmodManifest> {
|
||||||
|
let default_install_dir = spec.effective_dkms_install_dir();
|
||||||
|
let modules = spec
|
||||||
|
.build
|
||||||
|
.flags
|
||||||
|
.dkms_modules
|
||||||
|
.iter()
|
||||||
|
.map(|module| manifest_module_from_spec(spec, module, &default_install_dir))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
let mut build_dirs = BTreeSet::new();
|
||||||
|
for module in &modules {
|
||||||
|
build_dirs.insert(module.build_dir.clone());
|
||||||
|
}
|
||||||
|
if build_dirs.is_empty() {
|
||||||
|
anyhow::bail!("DKMS manifest must contain at least one module");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DepotKmodManifest {
|
||||||
|
name: spec.effective_dkms_name(),
|
||||||
|
version: spec.effective_dkms_version(),
|
||||||
|
install_dir: default_install_dir,
|
||||||
|
make_args: spec
|
||||||
|
.build
|
||||||
|
.flags
|
||||||
|
.dkms_make_args
|
||||||
|
.iter()
|
||||||
|
.map(|arg| spec.expand_vars(arg))
|
||||||
|
.collect(),
|
||||||
|
pre_build: spec
|
||||||
|
.build
|
||||||
|
.flags
|
||||||
|
.dkms_pre_build
|
||||||
|
.iter()
|
||||||
|
.map(|arg| spec.expand_vars(arg))
|
||||||
|
.collect(),
|
||||||
|
modules,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn manifest_module_from_spec(
|
||||||
|
spec: &PackageSpec,
|
||||||
|
module: &DkmsModule,
|
||||||
|
default_install_dir: &str,
|
||||||
|
) -> Result<DepotKmodModule> {
|
||||||
|
let name = spec.expand_vars(module.name.trim());
|
||||||
|
let dest_name = spec.effective_dkms_module_dest_name(module);
|
||||||
|
let build_dir = normalized_rel_string(&spec.expand_vars(module.build_dir.trim()), true)?;
|
||||||
|
let built_location = if module.built_location.trim().is_empty() {
|
||||||
|
build_dir.clone()
|
||||||
|
} else {
|
||||||
|
normalized_rel_string(&spec.expand_vars(module.built_location.trim()), true)?
|
||||||
|
};
|
||||||
|
let install_dir = if module.install_dir.trim().is_empty() {
|
||||||
|
default_install_dir.to_string()
|
||||||
|
} else {
|
||||||
|
spec.effective_dkms_module_install_dir(module)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(DepotKmodModule {
|
||||||
|
name,
|
||||||
|
dest_name,
|
||||||
|
build_dir,
|
||||||
|
built_location,
|
||||||
|
install_dir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_dkms_source_root(spec: &PackageSpec, src_dir: &Path) -> Result<PathBuf> {
|
||||||
|
let base = if spec.build.flags.source_subdir.trim().is_empty() {
|
||||||
|
src_dir.to_path_buf()
|
||||||
|
} else {
|
||||||
|
let subdir =
|
||||||
|
normalized_rel_string(&spec.expand_vars(&spec.build.flags.source_subdir), false)?;
|
||||||
|
src_dir.join(subdir)
|
||||||
|
};
|
||||||
|
let source_dir = spec.expand_vars(spec.build.flags.dkms_source_dir.trim());
|
||||||
|
let source_root = if source_dir.trim().is_empty() {
|
||||||
|
base
|
||||||
|
} else {
|
||||||
|
base.join(normalized_rel_string(&source_dir, false)?)
|
||||||
|
};
|
||||||
|
if !source_root.is_dir() {
|
||||||
|
anyhow::bail!("DKMS source directory not found: {}", source_root.display());
|
||||||
|
}
|
||||||
|
Ok(source_root)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn staged_source_dir(spec: &PackageSpec, destdir: &Path) -> PathBuf {
|
||||||
|
destdir.join("usr/src").join(source_dir_name(spec))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source_dir_name(spec: &PackageSpec) -> String {
|
||||||
|
format!(
|
||||||
|
"{}-{}",
|
||||||
|
spec.effective_dkms_name(),
|
||||||
|
spec.effective_dkms_version()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autoinstall_command(source_rel: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"depot internal dkms-autoinstall --rootfs \"$DEPOT_ROOTFS\" --source {}",
|
||||||
|
sh_quote(source_rel)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_command(name: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"depot internal dkms-remove --rootfs \"$DEPOT_ROOTFS\" --name {}",
|
||||||
|
sh_quote(name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stage_script_command(path: &Path, command: &str) -> Result<()> {
|
||||||
|
if path.exists() {
|
||||||
|
use std::io::Write;
|
||||||
|
let mut file = fs::OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.open(path)
|
||||||
|
.with_context(|| format!("Failed to open lifecycle script: {}", path.display()))?;
|
||||||
|
writeln!(file).with_context(|| format!("Failed to update {}", path.display()))?;
|
||||||
|
writeln!(file, "# Depot DKMS autoinstall").with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to append lifecycle script marker: {}",
|
||||||
|
path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
writeln!(file, "{command}")
|
||||||
|
.with_context(|| format!("Failed to append lifecycle script: {}", path.display()))?;
|
||||||
|
} else {
|
||||||
|
fs::write(path, format!("#!/bin/sh\nset -eu\n{command}\n"))
|
||||||
|
.with_context(|| format!("Failed to write lifecycle script: {}", path.display()))?;
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let mut perms = fs::metadata(path)?.permissions();
|
||||||
|
perms.set_mode(0o755);
|
||||||
|
fs::set_permissions(path, perms)
|
||||||
|
.with_context(|| format!("Failed to make script executable: {}", path.display()))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalized_rel_string(raw: &str, allow_empty: bool) -> Result<String> {
|
||||||
|
let trimmed = raw.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
if allow_empty {
|
||||||
|
return Ok(".".to_string());
|
||||||
|
}
|
||||||
|
anyhow::bail!("DKMS path cannot be empty");
|
||||||
|
}
|
||||||
|
let path = Path::new(trimmed);
|
||||||
|
if path.is_absolute() {
|
||||||
|
anyhow::bail!("DKMS path must be relative: {}", trimmed);
|
||||||
|
}
|
||||||
|
let mut out = PathBuf::new();
|
||||||
|
for component in path.components() {
|
||||||
|
match component {
|
||||||
|
Component::Normal(part) => out.push(part),
|
||||||
|
Component::CurDir => {}
|
||||||
|
_ => anyhow::bail!("DKMS path contains unsafe component: {}", trimmed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if out.as_os_str().is_empty() {
|
||||||
|
Ok(".".to_string())
|
||||||
|
} else {
|
||||||
|
Ok(out.to_string_lossy().into_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sh_quote(value: &str) -> String {
|
||||||
|
format!("'{}'", value.replace('\'', "'\\''"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::package::{
|
||||||
|
Alternatives, Build, BuildFlags, BuildType, Dependencies, PackageInfo, Source,
|
||||||
|
};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
fn mk_spec() -> PackageSpec {
|
||||||
|
let flags = BuildFlags {
|
||||||
|
dkms_modules: vec![
|
||||||
|
DkmsModule {
|
||||||
|
name: "zfs".into(),
|
||||||
|
build_dir: "module".into(),
|
||||||
|
built_location: "module/zfs".into(),
|
||||||
|
..DkmsModule::default()
|
||||||
|
},
|
||||||
|
DkmsModule {
|
||||||
|
name: "spl".into(),
|
||||||
|
dest_name: "spl_compat".into(),
|
||||||
|
build_dir: "module".into(),
|
||||||
|
built_location: "module/spl".into(),
|
||||||
|
install_dir: "updates/storage".into(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dkms_make_args: vec!["V=1".into()],
|
||||||
|
dkms_pre_build: vec!["./configure --with-linux=$kernel_build_dir".into()],
|
||||||
|
..BuildFlags::default()
|
||||||
|
};
|
||||||
|
PackageSpec {
|
||||||
|
package: PackageInfo {
|
||||||
|
name: "zfs-dkms".into(),
|
||||||
|
real_name: None,
|
||||||
|
version: "2.4.3".into(),
|
||||||
|
revision: 1,
|
||||||
|
description: "d".into(),
|
||||||
|
homepage: "h".into(),
|
||||||
|
abi_breaking: false,
|
||||||
|
built_against: Vec::new(),
|
||||||
|
license: vec!["MIT".into()],
|
||||||
|
},
|
||||||
|
packages: Vec::new(),
|
||||||
|
alternatives: Alternatives::default(),
|
||||||
|
manual_sources: Vec::new(),
|
||||||
|
source: vec![Source {
|
||||||
|
url: "https://example.test/zfs.tar.gz".into(),
|
||||||
|
sha256: "skip".into(),
|
||||||
|
extract_dir: "zfs".into(),
|
||||||
|
patches: Vec::new(),
|
||||||
|
post_extract: Vec::new(),
|
||||||
|
cherry_pick: Vec::new(),
|
||||||
|
}],
|
||||||
|
build: Build {
|
||||||
|
build_type: BuildType::Dkms,
|
||||||
|
flags,
|
||||||
|
},
|
||||||
|
dependencies: Dependencies::default(),
|
||||||
|
package_alternatives: Default::default(),
|
||||||
|
package_dependencies: Default::default(),
|
||||||
|
spec_dir: PathBuf::from("."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dkms_build_stages_source_metadata_and_scripts() -> Result<()> {
|
||||||
|
let tmp = tempdir()?;
|
||||||
|
let src = tmp.path().join("src");
|
||||||
|
let dest = tmp.path().join("dest");
|
||||||
|
fs::create_dir_all(src.join("module/zfs"))?;
|
||||||
|
fs::write(src.join("module/zfs/zfs.c"), "source")?;
|
||||||
|
|
||||||
|
let spec = mk_spec();
|
||||||
|
build(&spec, &src, &dest, None, true, None)?;
|
||||||
|
fs::create_dir_all(dest.join("scripts"))?;
|
||||||
|
fs::write(
|
||||||
|
dest.join("scripts/post_install"),
|
||||||
|
"#!/bin/sh\necho package\n",
|
||||||
|
)?;
|
||||||
|
stage_lifecycle_scripts(&spec, &dest)?;
|
||||||
|
|
||||||
|
let staged = dest.join("usr/src/zfs-dkms-2.4.3");
|
||||||
|
assert!(staged.join("module/zfs/zfs.c").exists());
|
||||||
|
let manifest: DepotKmodManifest =
|
||||||
|
toml::from_str(&fs::read_to_string(staged.join(DEPOT_KMOD_MANIFEST))?)?;
|
||||||
|
assert_eq!(manifest.name, "zfs-dkms");
|
||||||
|
assert_eq!(manifest.pre_build.len(), 1);
|
||||||
|
assert_eq!(manifest.modules.len(), 2);
|
||||||
|
assert_eq!(manifest.modules[1].dest_name, "spl_compat");
|
||||||
|
let post_install = fs::read_to_string(dest.join("scripts/post_install"))?;
|
||||||
|
assert!(post_install.contains("echo package"));
|
||||||
|
assert!(post_install.contains("dkms-autoinstall"));
|
||||||
|
assert!(dest.join("scripts/pre_update").exists());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ mod autotools;
|
|||||||
mod bin;
|
mod bin;
|
||||||
mod cmake;
|
mod cmake;
|
||||||
mod custom;
|
mod custom;
|
||||||
|
mod dkms;
|
||||||
mod makefile;
|
mod makefile;
|
||||||
mod meson;
|
mod meson;
|
||||||
mod perl;
|
mod perl;
|
||||||
@@ -489,6 +490,13 @@ pub(crate) fn requested_development_package() -> Option<String> {
|
|||||||
crate::build_options::requested_development_package()
|
crate::build_options::requested_development_package()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn stage_generated_lifecycle_scripts(spec: &PackageSpec, destdir: &Path) -> Result<()> {
|
||||||
|
match spec.build.build_type {
|
||||||
|
BuildType::Dkms => dkms::stage_lifecycle_scripts(spec, destdir),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn configured_install_dir(value: &str, default: &str) -> String {
|
fn configured_install_dir(value: &str, default: &str) -> String {
|
||||||
let trimmed = value.trim();
|
let trimmed = value.trim();
|
||||||
if trimmed.is_empty() {
|
if trimmed.is_empty() {
|
||||||
@@ -1100,6 +1108,14 @@ pub fn build(
|
|||||||
export_compiler_flags,
|
export_compiler_flags,
|
||||||
host_build_dir,
|
host_build_dir,
|
||||||
),
|
),
|
||||||
|
BuildType::Dkms => dkms::build(
|
||||||
|
spec,
|
||||||
|
src_dir,
|
||||||
|
destdir,
|
||||||
|
cross,
|
||||||
|
export_compiler_flags,
|
||||||
|
host_build_dir,
|
||||||
|
),
|
||||||
BuildType::Bin => bin::build(
|
BuildType::Bin => bin::build(
|
||||||
spec,
|
spec,
|
||||||
src_dir,
|
src_dir,
|
||||||
|
|||||||
+16
@@ -384,6 +384,22 @@ pub enum InternalCommands {
|
|||||||
#[arg(value_name = "ARG", num_args = 0.., allow_hyphen_values = true)]
|
#[arg(value_name = "ARG", num_args = 0.., allow_hyphen_values = true)]
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
},
|
},
|
||||||
|
#[command(hide = true)]
|
||||||
|
DkmsAutoinstall {
|
||||||
|
#[arg(long, default_value = "/")]
|
||||||
|
rootfs: PathBuf,
|
||||||
|
#[arg(long)]
|
||||||
|
source: PathBuf,
|
||||||
|
},
|
||||||
|
#[command(hide = true)]
|
||||||
|
DkmsRemove {
|
||||||
|
#[arg(long, default_value = "/")]
|
||||||
|
rootfs: PathBuf,
|
||||||
|
#[arg(long)]
|
||||||
|
source: Option<PathBuf>,
|
||||||
|
#[arg(long)]
|
||||||
|
name: Option<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Args)]
|
#[derive(Debug, Clone, Args)]
|
||||||
|
|||||||
@@ -2501,6 +2501,7 @@ fn run_direct_install_request(
|
|||||||
// 3.1 Copy license files into staged tree
|
// 3.1 Copy license files into staged tree
|
||||||
staging::add_licenses(&src_dir, &destdir, &pkg_spec.package.name)?;
|
staging::add_licenses(&src_dir, &destdir, &pkg_spec.package.name)?;
|
||||||
install::scripts::stage_scripts_from_spec_dir(&pkg_spec, &destdir)?;
|
install::scripts::stage_scripts_from_spec_dir(&pkg_spec, &destdir)?;
|
||||||
|
builder::stage_generated_lifecycle_scripts(&pkg_spec, &destdir)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
destdir
|
destdir
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ pub(super) fn run_build(args: BuildArgs, cli_test_deps: bool) -> Result<()> {
|
|||||||
if !lib32_only {
|
if !lib32_only {
|
||||||
staging::add_licenses(&src_dir, &destdir, &pkg_spec.package.name)?;
|
staging::add_licenses(&src_dir, &destdir, &pkg_spec.package.name)?;
|
||||||
install::scripts::stage_scripts_from_spec_dir(&pkg_spec, &destdir)?;
|
install::scripts::stage_scripts_from_spec_dir(&pkg_spec, &destdir)?;
|
||||||
|
builder::stage_generated_lifecycle_scripts(&pkg_spec, &destdir)?;
|
||||||
staging::process(&destdir, &pkg_spec)?;
|
staging::process(&destdir, &pkg_spec)?;
|
||||||
staging::stage_split_package_licenses(&src_dir, &destdir, &pkg_spec)?;
|
staging::stage_split_package_licenses(&src_dir, &destdir, &pkg_spec)?;
|
||||||
if let Some(watcher) = interrupt_watcher.as_ref() {
|
if let Some(watcher) = interrupt_watcher.as_ref() {
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ pub(crate) fn build_lib32_companion_package(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
install::scripts::stage_scripts_from_spec_dir(&lib32_pkg_spec, &lib32_destdir)?;
|
install::scripts::stage_scripts_from_spec_dir(&lib32_pkg_spec, &lib32_destdir)?;
|
||||||
|
builder::stage_generated_lifecycle_scripts(&lib32_pkg_spec, &lib32_destdir)?;
|
||||||
staging::process(&lib32_destdir, &lib32_pkg_spec)?;
|
staging::process(&lib32_destdir, &lib32_pkg_spec)?;
|
||||||
staging::symlink_package_license(
|
staging::symlink_package_license(
|
||||||
&lib32_destdir,
|
&lib32_destdir,
|
||||||
|
|||||||
@@ -233,6 +233,19 @@ pub(crate) fn run_internal_command(command: InternalCommands) -> Result<()> {
|
|||||||
&args,
|
&args,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
InternalCommands::DkmsAutoinstall { rootfs, source } => {
|
||||||
|
crate::install::dkms::autoinstall(&rootfs, &source)
|
||||||
|
}
|
||||||
|
InternalCommands::DkmsRemove {
|
||||||
|
rootfs,
|
||||||
|
source,
|
||||||
|
name,
|
||||||
|
} => {
|
||||||
|
if source.is_none() && name.as_deref().is_none_or(|value| value.trim().is_empty()) {
|
||||||
|
anyhow::bail!("internal dkms-remove requires --source or --name");
|
||||||
|
}
|
||||||
|
crate::install::dkms::remove(&rootfs, source.as_deref(), name.as_deref())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,547 @@
|
|||||||
|
//! Runtime support for Depot-managed kernel module source packages.
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
const DEPOT_KMOD_MANIFEST: &str = ".depot-kmod.toml";
|
||||||
|
const TRACKING_DIR_REL: &str = "var/lib/depot/kmods";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct DepotKmodManifest {
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
#[serde(default)]
|
||||||
|
install_dir: String,
|
||||||
|
#[serde(default)]
|
||||||
|
make_args: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pre_build: Vec<String>,
|
||||||
|
modules: Vec<DepotKmodModule>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct DepotKmodModule {
|
||||||
|
name: String,
|
||||||
|
dest_name: String,
|
||||||
|
build_dir: String,
|
||||||
|
built_location: String,
|
||||||
|
install_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct InstalledKmodManifest {
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
entries: Vec<InstalledKmodEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct InstalledKmodEntry {
|
||||||
|
kernel: String,
|
||||||
|
module: String,
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct KernelTarget {
|
||||||
|
version: String,
|
||||||
|
build_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build and install a Depot-managed kernel module source tree for all kernels.
|
||||||
|
pub(crate) fn autoinstall(rootfs: &Path, source: &Path) -> Result<()> {
|
||||||
|
let source_dir = resolve_rootfs_path(rootfs, source);
|
||||||
|
let manifest = read_source_manifest(&source_dir)?;
|
||||||
|
if manifest.modules.is_empty() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"DKMS source manifest contains no modules: {}",
|
||||||
|
source_dir.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let kernels = discover_kernels(rootfs)?;
|
||||||
|
if kernels.is_empty() {
|
||||||
|
crate::log_warn!(
|
||||||
|
"Skipping Depot DKMS autoinstall for {}/{}: no kernels under {}",
|
||||||
|
manifest.name,
|
||||||
|
manifest.version,
|
||||||
|
rootfs.join("lib/modules").display()
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let buildable: Vec<_> = kernels
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|kernel| {
|
||||||
|
if kernel.build_dir.exists() {
|
||||||
|
Some(kernel)
|
||||||
|
} else {
|
||||||
|
crate::log_warn!(
|
||||||
|
"Skipping kernel {} for {}/{}: missing build directory {}",
|
||||||
|
kernel.version,
|
||||||
|
manifest.name,
|
||||||
|
manifest.version,
|
||||||
|
kernel.build_dir.display()
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if buildable.is_empty() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"No buildable kernels found for {}/{} under {}",
|
||||||
|
manifest.name,
|
||||||
|
manifest.version,
|
||||||
|
rootfs.join("lib/modules").display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut installed = Vec::new();
|
||||||
|
for kernel in &buildable {
|
||||||
|
build_for_kernel(rootfs, &source_dir, &manifest, kernel)?;
|
||||||
|
installed.extend(install_for_kernel(rootfs, &source_dir, &manifest, kernel)?);
|
||||||
|
run_depmod(rootfs, &kernel.version)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_tracking_manifest(rootfs, &manifest, installed)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove Depot-installed kernel modules by source tree or package name.
|
||||||
|
pub(crate) fn remove(rootfs: &Path, source: Option<&Path>, name: Option<&str>) -> Result<()> {
|
||||||
|
let selected = selected_tracking_manifests(rootfs, source, name)?;
|
||||||
|
if selected.is_empty() {
|
||||||
|
if let Some(name) = name {
|
||||||
|
crate::log_warn!("No installed Depot DKMS modules found for {}", name);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut depmod_kernels = BTreeSet::new();
|
||||||
|
for (manifest_path, manifest) in selected {
|
||||||
|
for entry in &manifest.entries {
|
||||||
|
let module_path = rootfs.join(&entry.path);
|
||||||
|
if module_path.exists() {
|
||||||
|
fs::remove_file(&module_path).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to remove installed module {}",
|
||||||
|
module_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
depmod_kernels.insert(entry.kernel.clone());
|
||||||
|
}
|
||||||
|
fs::remove_file(&manifest_path).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to remove Depot DKMS tracking manifest {}",
|
||||||
|
manifest_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for kernel in depmod_kernels {
|
||||||
|
run_depmod(rootfs, &kernel)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_source_manifest(source_dir: &Path) -> Result<DepotKmodManifest> {
|
||||||
|
let manifest_path = source_dir.join(DEPOT_KMOD_MANIFEST);
|
||||||
|
let raw = fs::read_to_string(&manifest_path)
|
||||||
|
.with_context(|| format!("Failed to read {}", manifest_path.display()))?;
|
||||||
|
toml::from_str(&raw).with_context(|| format!("Failed to parse {}", manifest_path.display()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discover_kernels(rootfs: &Path) -> Result<Vec<KernelTarget>> {
|
||||||
|
let modules_root = rootfs.join("lib/modules");
|
||||||
|
if !modules_root.exists() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
if !modules_root.is_dir() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Kernel modules path is not a directory: {}",
|
||||||
|
modules_root.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut kernels = Vec::new();
|
||||||
|
for entry in fs::read_dir(&modules_root)
|
||||||
|
.with_context(|| format!("Failed to read {}", modules_root.display()))?
|
||||||
|
{
|
||||||
|
let entry = entry.with_context(|| format!("Failed to list {}", modules_root.display()))?;
|
||||||
|
let file_type = entry
|
||||||
|
.file_type()
|
||||||
|
.with_context(|| format!("Failed to inspect {}", entry.path().display()))?;
|
||||||
|
if !file_type.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let version = entry.file_name().to_string_lossy().into_owned();
|
||||||
|
if version.trim().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
kernels.push(KernelTarget {
|
||||||
|
version,
|
||||||
|
build_dir: entry.path().join("build"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
kernels.sort_by(|a, b| a.version.cmp(&b.version));
|
||||||
|
Ok(kernels)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_for_kernel(
|
||||||
|
rootfs: &Path,
|
||||||
|
source_dir: &Path,
|
||||||
|
manifest: &DepotKmodManifest,
|
||||||
|
kernel: &KernelTarget,
|
||||||
|
) -> Result<()> {
|
||||||
|
run_pre_build_commands(rootfs, source_dir, manifest, kernel)?;
|
||||||
|
let build_dirs = module_build_dirs(source_dir, manifest)?;
|
||||||
|
for build_dir in build_dirs {
|
||||||
|
crate::log_info!(
|
||||||
|
"Building Depot DKMS module {}/{} for kernel {}",
|
||||||
|
manifest.name,
|
||||||
|
manifest.version,
|
||||||
|
kernel.version
|
||||||
|
);
|
||||||
|
let mut cmd = Command::new("make");
|
||||||
|
cmd.arg("-C")
|
||||||
|
.arg(&kernel.build_dir)
|
||||||
|
.arg(format!("M={}", build_dir.display()))
|
||||||
|
.arg("modules")
|
||||||
|
.args(&manifest.make_args);
|
||||||
|
crate::builder::prepare_tool_command(&mut cmd, &Vec::new());
|
||||||
|
let status = crate::interrupts::command_status(&mut cmd).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to run kernel module build for {} on {}",
|
||||||
|
manifest.name, kernel.version
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if !status.success() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Kernel module build failed for {}/{} on {}",
|
||||||
|
manifest.name,
|
||||||
|
manifest.version,
|
||||||
|
kernel.version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_pre_build_commands(
|
||||||
|
rootfs: &Path,
|
||||||
|
source_dir: &Path,
|
||||||
|
manifest: &DepotKmodManifest,
|
||||||
|
kernel: &KernelTarget,
|
||||||
|
) -> Result<()> {
|
||||||
|
for raw in &manifest.pre_build {
|
||||||
|
let command = expand_kernel_command(raw, rootfs, source_dir, kernel);
|
||||||
|
if command.trim().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
crate::log_info!(
|
||||||
|
"Preparing Depot DKMS module {}/{} for kernel {}: {}",
|
||||||
|
manifest.name,
|
||||||
|
manifest.version,
|
||||||
|
kernel.version,
|
||||||
|
command
|
||||||
|
);
|
||||||
|
let mut cmd = Command::new("sh");
|
||||||
|
cmd.arg("-c").arg(&command).current_dir(source_dir);
|
||||||
|
crate::builder::prepare_tool_command(&mut cmd, &Vec::new());
|
||||||
|
let status = crate::interrupts::command_status(&mut cmd).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to run DKMS pre-build command for {} on {}",
|
||||||
|
manifest.name, kernel.version
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if !status.success() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"DKMS pre-build command failed for {}/{} on {}: {}",
|
||||||
|
manifest.name,
|
||||||
|
manifest.version,
|
||||||
|
kernel.version,
|
||||||
|
command
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_kernel_command(
|
||||||
|
raw: &str,
|
||||||
|
rootfs: &Path,
|
||||||
|
source_dir: &Path,
|
||||||
|
kernel: &KernelTarget,
|
||||||
|
) -> String {
|
||||||
|
raw.replace("$kernel_build_dir", &shell_path(&kernel.build_dir))
|
||||||
|
.replace("${kernel_build_dir}", &shell_path(&kernel.build_dir))
|
||||||
|
.replace("$source_dir", &shell_path(source_dir))
|
||||||
|
.replace("${source_dir}", &shell_path(source_dir))
|
||||||
|
.replace("$rootfs", &shell_path(rootfs))
|
||||||
|
.replace("${rootfs}", &shell_path(rootfs))
|
||||||
|
.replace("$kernel", &kernel.version)
|
||||||
|
.replace("${kernel}", &kernel.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shell_path(path: &Path) -> String {
|
||||||
|
path.to_string_lossy().into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_build_dirs(source_dir: &Path, manifest: &DepotKmodManifest) -> Result<Vec<PathBuf>> {
|
||||||
|
let mut seen = BTreeSet::new();
|
||||||
|
let mut dirs = Vec::new();
|
||||||
|
for module in &manifest.modules {
|
||||||
|
let rel = safe_rel_path(&module.build_dir)?;
|
||||||
|
let dir = source_dir.join(rel);
|
||||||
|
if seen.insert(dir.clone()) {
|
||||||
|
dirs.push(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(dirs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn install_for_kernel(
|
||||||
|
rootfs: &Path,
|
||||||
|
source_dir: &Path,
|
||||||
|
manifest: &DepotKmodManifest,
|
||||||
|
kernel: &KernelTarget,
|
||||||
|
) -> Result<Vec<InstalledKmodEntry>> {
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
for module in &manifest.modules {
|
||||||
|
let built_location = source_dir.join(safe_rel_path(&module.built_location)?);
|
||||||
|
let built_module = built_location.join(format!("{}.ko", module.name));
|
||||||
|
if !built_module.is_file() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Built module not found for {}/{} on {}: {}",
|
||||||
|
manifest.name,
|
||||||
|
module.name,
|
||||||
|
kernel.version,
|
||||||
|
built_module.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let install_dir = safe_rel_path(&module.install_dir)?;
|
||||||
|
let dest_rel = Path::new("lib/modules")
|
||||||
|
.join(&kernel.version)
|
||||||
|
.join(install_dir)
|
||||||
|
.join(format!("{}.ko", module.dest_name));
|
||||||
|
let dest = rootfs.join(&dest_rel);
|
||||||
|
if let Some(parent) = dest.parent() {
|
||||||
|
fs::create_dir_all(parent)
|
||||||
|
.with_context(|| format!("Failed to create {}", parent.display()))?;
|
||||||
|
}
|
||||||
|
fs::copy(&built_module, &dest).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to install module {} to {}",
|
||||||
|
built_module.display(),
|
||||||
|
dest.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
entries.push(InstalledKmodEntry {
|
||||||
|
kernel: kernel.version.clone(),
|
||||||
|
module: module.dest_name.clone(),
|
||||||
|
path: dest_rel.to_string_lossy().into_owned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_depmod(rootfs: &Path, kernel: &str) -> Result<()> {
|
||||||
|
let mut cmd = Command::new("depmod");
|
||||||
|
if rootfs == Path::new("/") {
|
||||||
|
cmd.arg("-a").arg(kernel);
|
||||||
|
} else {
|
||||||
|
cmd.arg("-b").arg(rootfs).arg(kernel);
|
||||||
|
}
|
||||||
|
let status = crate::interrupts::command_status(&mut cmd)
|
||||||
|
.with_context(|| format!("Failed to run depmod for kernel {}", kernel))?;
|
||||||
|
if !status.success() {
|
||||||
|
anyhow::bail!("depmod failed for kernel {}", kernel);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_tracking_manifest(
|
||||||
|
rootfs: &Path,
|
||||||
|
manifest: &DepotKmodManifest,
|
||||||
|
entries: Vec<InstalledKmodEntry>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let tracking_dir = rootfs.join(TRACKING_DIR_REL);
|
||||||
|
fs::create_dir_all(&tracking_dir)
|
||||||
|
.with_context(|| format!("Failed to create {}", tracking_dir.display()))?;
|
||||||
|
let installed = InstalledKmodManifest {
|
||||||
|
name: manifest.name.clone(),
|
||||||
|
version: manifest.version.clone(),
|
||||||
|
entries,
|
||||||
|
};
|
||||||
|
let path = tracking_manifest_path(rootfs, &manifest.name, &manifest.version);
|
||||||
|
let raw = toml::to_string_pretty(&installed)
|
||||||
|
.context("Failed to serialize Depot DKMS tracking manifest")?;
|
||||||
|
fs::write(&path, raw).with_context(|| format!("Failed to write {}", path.display()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_tracking_manifests(
|
||||||
|
rootfs: &Path,
|
||||||
|
source: Option<&Path>,
|
||||||
|
name: Option<&str>,
|
||||||
|
) -> Result<Vec<(PathBuf, InstalledKmodManifest)>> {
|
||||||
|
let mut selected = BTreeMap::new();
|
||||||
|
if let Some(source) = source {
|
||||||
|
let source_dir = resolve_rootfs_path(rootfs, source);
|
||||||
|
if source_dir.join(DEPOT_KMOD_MANIFEST).exists() {
|
||||||
|
let manifest = read_source_manifest(&source_dir)?;
|
||||||
|
let tracking = tracking_manifest_path(rootfs, &manifest.name, &manifest.version);
|
||||||
|
if tracking.exists() {
|
||||||
|
selected.insert(tracking.clone(), read_tracking_manifest(&tracking)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = name {
|
||||||
|
for (path, manifest) in tracking_manifests_by_name(rootfs, name)? {
|
||||||
|
selected.insert(path, manifest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(selected.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tracking_manifests_by_name(
|
||||||
|
rootfs: &Path,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Vec<(PathBuf, InstalledKmodManifest)>> {
|
||||||
|
let tracking_dir = rootfs.join(TRACKING_DIR_REL);
|
||||||
|
if !tracking_dir.exists() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
let mut manifests = Vec::new();
|
||||||
|
for entry in fs::read_dir(&tracking_dir)
|
||||||
|
.with_context(|| format!("Failed to read {}", tracking_dir.display()))?
|
||||||
|
{
|
||||||
|
let entry = entry.with_context(|| format!("Failed to list {}", tracking_dir.display()))?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path.extension().and_then(|ext| ext.to_str()) != Some("toml") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let manifest = read_tracking_manifest(&path)?;
|
||||||
|
if manifest.name == name {
|
||||||
|
manifests.push((path, manifest));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
manifests.sort_by(|left, right| left.0.cmp(&right.0));
|
||||||
|
Ok(manifests)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_tracking_manifest(path: &Path) -> Result<InstalledKmodManifest> {
|
||||||
|
let raw =
|
||||||
|
fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?;
|
||||||
|
toml::from_str(&raw).with_context(|| format!("Failed to parse {}", path.display()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tracking_manifest_path(rootfs: &Path, name: &str, version: &str) -> PathBuf {
|
||||||
|
rootfs.join(TRACKING_DIR_REL).join(format!(
|
||||||
|
"{}-{}.toml",
|
||||||
|
safe_tracking_component(name),
|
||||||
|
safe_tracking_component(version)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_tracking_component(raw: &str) -> String {
|
||||||
|
raw.chars()
|
||||||
|
.map(|ch| match ch {
|
||||||
|
'A'..='Z' | 'a'..='z' | '0'..='9' | '.' | '_' | '+' | '-' => ch,
|
||||||
|
_ => '_',
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_rootfs_path(rootfs: &Path, path: &Path) -> PathBuf {
|
||||||
|
if path.is_absolute() {
|
||||||
|
rootfs.join(path.strip_prefix(Path::new("/")).unwrap_or(path))
|
||||||
|
} else {
|
||||||
|
rootfs.join(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safe_rel_path(raw: &str) -> Result<PathBuf> {
|
||||||
|
let trimmed = raw.trim().trim_start_matches('/');
|
||||||
|
if trimmed.is_empty() || trimmed == "." {
|
||||||
|
return Ok(PathBuf::new());
|
||||||
|
}
|
||||||
|
let path = Path::new(trimmed);
|
||||||
|
if path.is_absolute() {
|
||||||
|
anyhow::bail!("Expected relative path: {}", raw);
|
||||||
|
}
|
||||||
|
let mut out = PathBuf::new();
|
||||||
|
for component in path.components() {
|
||||||
|
match component {
|
||||||
|
Component::Normal(part) => out.push(part),
|
||||||
|
Component::CurDir => {}
|
||||||
|
_ => anyhow::bail!("Unsafe path component in DKMS manifest path: {}", raw),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::test_support::TestEnv;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
fn write_tracking(rootfs: &Path) -> Result<PathBuf> {
|
||||||
|
let path = tracking_manifest_path(rootfs, "zfs", "2.4.3");
|
||||||
|
fs::create_dir_all(path.parent().unwrap())?;
|
||||||
|
let manifest = InstalledKmodManifest {
|
||||||
|
name: "zfs".into(),
|
||||||
|
version: "2.4.3".into(),
|
||||||
|
entries: vec![InstalledKmodEntry {
|
||||||
|
kernel: "6.1.0".into(),
|
||||||
|
module: "zfs".into(),
|
||||||
|
path: "lib/modules/6.1.0/updates/depot/zfs.ko".into(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
fs::write(&path, toml::to_string(&manifest)?)?;
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_uses_tracking_manifest_and_only_removes_tracked_modules() -> Result<()> {
|
||||||
|
let tmp = tempdir()?;
|
||||||
|
let rootfs = tmp.path();
|
||||||
|
let module_path = rootfs.join("lib/modules/6.1.0/updates/depot/zfs.ko");
|
||||||
|
fs::create_dir_all(module_path.parent().unwrap())?;
|
||||||
|
fs::write(&module_path, "module")?;
|
||||||
|
let tracking = write_tracking(rootfs)?;
|
||||||
|
|
||||||
|
let depmod = rootfs.join("depmod-bin");
|
||||||
|
fs::write(&depmod, "#!/bin/sh\nexit 0\n")?;
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let mut perms = fs::metadata(&depmod)?.permissions();
|
||||||
|
perms.set_mode(0o755);
|
||||||
|
fs::set_permissions(&depmod, perms)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut env = TestEnv::new();
|
||||||
|
env.set_var("PATH", rootfs);
|
||||||
|
fs::rename(&depmod, rootfs.join("depmod"))?;
|
||||||
|
remove(rootfs, None, Some("zfs"))?;
|
||||||
|
|
||||||
|
assert!(!module_path.exists());
|
||||||
|
assert!(!tracking.exists());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn safe_rel_path_rejects_traversal() {
|
||||||
|
assert!(safe_rel_path("../x").is_err());
|
||||||
|
assert!(safe_rel_path("updates/depot").is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
//! Installation helpers.
|
//! Installation helpers.
|
||||||
|
|
||||||
|
pub(crate) mod dkms;
|
||||||
pub mod hooks;
|
pub mod hooks;
|
||||||
pub mod scripts;
|
pub mod scripts;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ impl fmt::Display for BuildType {
|
|||||||
BuildType::Python => write!(f, "Python"),
|
BuildType::Python => write!(f, "Python"),
|
||||||
BuildType::Rust => write!(f, "Rust"),
|
BuildType::Rust => write!(f, "Rust"),
|
||||||
BuildType::Makefile => write!(f, "Makefile"),
|
BuildType::Makefile => write!(f, "Makefile"),
|
||||||
|
BuildType::Dkms => write!(f, "DKMS"),
|
||||||
BuildType::Bin => write!(f, "Binary installer"),
|
BuildType::Bin => write!(f, "Binary installer"),
|
||||||
BuildType::Meta => write!(f, "Metapackage"),
|
BuildType::Meta => write!(f, "Metapackage"),
|
||||||
}
|
}
|
||||||
@@ -960,6 +961,7 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
|||||||
BuildType::Python => "python",
|
BuildType::Python => "python",
|
||||||
BuildType::Rust => "rust",
|
BuildType::Rust => "rust",
|
||||||
BuildType::Makefile => "makefile",
|
BuildType::Makefile => "makefile",
|
||||||
|
BuildType::Dkms => "dkms",
|
||||||
BuildType::Bin => "bin",
|
BuildType::Bin => "bin",
|
||||||
BuildType::Meta => "meta",
|
BuildType::Meta => "meta",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ impl PackageSpec {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
spec.validate_manual_sources()?;
|
spec.validate_manual_sources()?;
|
||||||
|
spec.validate_dkms()?;
|
||||||
|
|
||||||
Ok(spec)
|
Ok(spec)
|
||||||
}
|
}
|
||||||
@@ -166,6 +167,52 @@ impl PackageSpec {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_dkms(&self) -> Result<()> {
|
||||||
|
if !matches!(self.build.build_type, BuildType::Dkms) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.build.flags.dkms_modules.is_empty() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"build.type = \"dkms\" requires at least one build.flags.dkms_modules entry"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_dkms_component("build.flags.dkms_name", &self.effective_dkms_name())?;
|
||||||
|
validate_dkms_component("build.flags.dkms_version", &self.effective_dkms_version())?;
|
||||||
|
validate_dkms_rel_path(
|
||||||
|
"build.flags.dkms_source_dir",
|
||||||
|
self.build.flags.dkms_source_dir.trim(),
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
validate_dkms_install_dir(
|
||||||
|
"build.flags.dkms_install_dir",
|
||||||
|
self.effective_dkms_install_dir().as_str(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut installed_names = HashSet::new();
|
||||||
|
for (idx, module) in self.build.flags.dkms_modules.iter().enumerate() {
|
||||||
|
let label = format!("build.flags.dkms_modules[{idx}]");
|
||||||
|
validate_dkms_component(&format!("{label}.name"), module.name.trim())?;
|
||||||
|
let dest_name = self.effective_dkms_module_dest_name(module);
|
||||||
|
validate_dkms_component(&format!("{label}.dest_name"), &dest_name)?;
|
||||||
|
validate_dkms_rel_path(&format!("{label}.build_dir"), module.build_dir.trim(), true)?;
|
||||||
|
validate_dkms_rel_path(
|
||||||
|
&format!("{label}.built_location"),
|
||||||
|
module.built_location.trim(),
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
let install_dir = self.effective_dkms_module_install_dir(module);
|
||||||
|
validate_dkms_install_dir(&format!("{label}.install_dir"), &install_dir)?;
|
||||||
|
|
||||||
|
if !installed_names.insert(dest_name.clone()) {
|
||||||
|
anyhow::bail!("Duplicate DKMS destination module name: {}", dest_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Expand variables like `$name` and `$version` in a string.
|
/// Expand variables like `$name` and `$version` in a string.
|
||||||
pub fn expand_vars(&self, input: &str) -> String {
|
pub fn expand_vars(&self, input: &str) -> String {
|
||||||
let specdir = self.spec_dir.to_string_lossy();
|
let specdir = self.spec_dir.to_string_lossy();
|
||||||
@@ -180,6 +227,56 @@ impl PackageSpec {
|
|||||||
&self.source
|
&self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the effective Depot DKMS source package name.
|
||||||
|
pub fn effective_dkms_name(&self) -> String {
|
||||||
|
let configured = self.build.flags.dkms_name.trim();
|
||||||
|
if configured.is_empty() {
|
||||||
|
self.package.name.clone()
|
||||||
|
} else {
|
||||||
|
self.expand_vars(configured)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the effective Depot DKMS source package version.
|
||||||
|
pub fn effective_dkms_version(&self) -> String {
|
||||||
|
let configured = self.build.flags.dkms_version.trim();
|
||||||
|
if configured.is_empty() {
|
||||||
|
self.package.version.clone()
|
||||||
|
} else {
|
||||||
|
self.expand_vars(configured)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the default module install directory for Depot DKMS packages.
|
||||||
|
pub fn effective_dkms_install_dir(&self) -> String {
|
||||||
|
let configured = self.build.flags.dkms_install_dir.trim();
|
||||||
|
if configured.is_empty() {
|
||||||
|
"updates/depot".to_string()
|
||||||
|
} else {
|
||||||
|
normalize_dkms_install_dir(&self.expand_vars(configured))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the effective installed module name for a DKMS module entry.
|
||||||
|
pub fn effective_dkms_module_dest_name(&self, module: &DkmsModule) -> String {
|
||||||
|
let configured = module.dest_name.trim();
|
||||||
|
if configured.is_empty() {
|
||||||
|
module.name.clone()
|
||||||
|
} else {
|
||||||
|
self.expand_vars(configured)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the effective install directory for a DKMS module entry.
|
||||||
|
pub fn effective_dkms_module_install_dir(&self, module: &DkmsModule) -> String {
|
||||||
|
let configured = module.install_dir.trim();
|
||||||
|
if configured.is_empty() {
|
||||||
|
self.effective_dkms_install_dir()
|
||||||
|
} else {
|
||||||
|
normalize_dkms_install_dir(&self.expand_vars(configured))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true when this spec is a metadata-only package that exists to pull dependencies.
|
/// Returns true when this spec is a metadata-only package that exists to pull dependencies.
|
||||||
pub fn is_metapackage(&self) -> bool {
|
pub fn is_metapackage(&self) -> bool {
|
||||||
matches!(self.build.build_type, BuildType::Meta)
|
matches!(self.build.build_type, BuildType::Meta)
|
||||||
@@ -339,6 +436,49 @@ impl PackageSpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_dkms_install_dir(raw: &str) -> String {
|
||||||
|
raw.trim().trim_start_matches('/').to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_dkms_component(label: &str, value: &str) -> Result<()> {
|
||||||
|
let value = value.trim();
|
||||||
|
if value.is_empty() {
|
||||||
|
anyhow::bail!("{label} cannot be empty");
|
||||||
|
}
|
||||||
|
if value.contains('/') || value.contains('\\') {
|
||||||
|
anyhow::bail!("{label} cannot contain path separators: {value}");
|
||||||
|
}
|
||||||
|
if value == "." || value == ".." {
|
||||||
|
anyhow::bail!("{label} cannot be '.' or '..'");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_dkms_install_dir(label: &str, value: &str) -> Result<()> {
|
||||||
|
validate_dkms_rel_path(label, value.trim_start_matches('/'), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_dkms_rel_path(label: &str, value: &str, allow_empty: bool) -> Result<()> {
|
||||||
|
let value = value.trim();
|
||||||
|
if value.is_empty() {
|
||||||
|
if allow_empty {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
anyhow::bail!("{label} cannot be empty");
|
||||||
|
}
|
||||||
|
let path = Path::new(value);
|
||||||
|
if path.is_absolute() {
|
||||||
|
anyhow::bail!("{label} must be relative: {value}");
|
||||||
|
}
|
||||||
|
for component in path.components() {
|
||||||
|
match component {
|
||||||
|
std::path::Component::Normal(_) | std::path::Component::CurDir => {}
|
||||||
|
_ => anyhow::bail!("{label} contains an unsafe path component: {value}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for PackageSpec {
|
impl fmt::Display for PackageSpec {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
writeln!(
|
writeln!(
|
||||||
|
|||||||
@@ -217,10 +217,36 @@ pub enum BuildType {
|
|||||||
Python,
|
Python,
|
||||||
Rust,
|
Rust,
|
||||||
Makefile,
|
Makefile,
|
||||||
|
Dkms,
|
||||||
Bin,
|
Bin,
|
||||||
Meta,
|
Meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One kernel module output managed by Depot's DKMS-compatible backend.
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct DkmsModule {
|
||||||
|
/// Built module name without a `.ko` suffix.
|
||||||
|
pub name: String,
|
||||||
|
/// Installed module name without a `.ko` suffix. Defaults to `name`.
|
||||||
|
#[serde(default, alias = "dest-name", alias = "dest_name")]
|
||||||
|
pub dest_name: String,
|
||||||
|
/// Source subdirectory passed as `M=` to the kernel build system.
|
||||||
|
#[serde(default, alias = "path", alias = "build-dir", alias = "build_dir")]
|
||||||
|
pub build_dir: String,
|
||||||
|
/// Directory containing the resulting `.ko`. Defaults to `build_dir`.
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
alias = "built-location",
|
||||||
|
alias = "built_location",
|
||||||
|
alias = "output-dir",
|
||||||
|
alias = "output_dir"
|
||||||
|
)]
|
||||||
|
pub built_location: String,
|
||||||
|
/// Install directory under `/lib/modules/<kernel>`. Defaults to `dkms_install_dir`.
|
||||||
|
#[serde(default, alias = "install-dir", alias = "install_dir")]
|
||||||
|
pub install_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Build flags and toolchain configuration
|
/// Build flags and toolchain configuration
|
||||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct BuildFlags {
|
pub struct BuildFlags {
|
||||||
@@ -715,6 +741,47 @@ pub struct BuildFlags {
|
|||||||
/// Binary package type when using BuildType::Bin (e.g. "deb")
|
/// Binary package type when using BuildType::Bin (e.g. "deb")
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub binary_type: String,
|
pub binary_type: String,
|
||||||
|
/// DKMS package/module name for Depot-managed kernel modules.
|
||||||
|
#[serde(default, alias = "dkms-name", alias = "dkms_name")]
|
||||||
|
pub dkms_name: String,
|
||||||
|
/// DKMS package/module version. Defaults to the package version.
|
||||||
|
#[serde(default, alias = "dkms-version", alias = "dkms_version")]
|
||||||
|
pub dkms_version: String,
|
||||||
|
/// Source subdirectory to stage into `/usr/src/<name>-<version>`.
|
||||||
|
#[serde(default, alias = "dkms-source-dir", alias = "dkms_source_dir")]
|
||||||
|
pub dkms_source_dir: String,
|
||||||
|
/// Default install directory under `/lib/modules/<kernel>` for built modules.
|
||||||
|
#[serde(default, alias = "dkms-install-dir", alias = "dkms_install_dir")]
|
||||||
|
pub dkms_install_dir: String,
|
||||||
|
/// Additional arguments appended to kernel module `make` invocations.
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
alias = "dkms-make-args",
|
||||||
|
alias = "dkms_make_args",
|
||||||
|
deserialize_with = "deserialize_string_or_array"
|
||||||
|
)]
|
||||||
|
pub dkms_make_args: Vec<String>,
|
||||||
|
/// Commands run from the staged source tree before each per-kernel module build.
|
||||||
|
///
|
||||||
|
/// Supports `$kernel`, `$kernel_build_dir`, `$source_dir`, and `$rootfs`.
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
alias = "dkms-pre-build",
|
||||||
|
alias = "dkms_pre_build",
|
||||||
|
deserialize_with = "deserialize_string_or_array_no_split"
|
||||||
|
)]
|
||||||
|
pub dkms_pre_build: Vec<String>,
|
||||||
|
/// Run Depot's kernel-module build/install hook after package install/update.
|
||||||
|
#[serde(
|
||||||
|
default = "default_dkms_autoinstall",
|
||||||
|
alias = "dkms-autoinstall",
|
||||||
|
alias = "dkms_autoinstall",
|
||||||
|
deserialize_with = "deserialize_boolish"
|
||||||
|
)]
|
||||||
|
pub dkms_autoinstall: bool,
|
||||||
|
/// Kernel modules produced by this DKMS source package.
|
||||||
|
#[serde(default, alias = "dkms-modules", alias = "dkms_modules")]
|
||||||
|
pub dkms_modules: Vec<DkmsModule>,
|
||||||
/// Internal runtime marker used to adjust builder behavior for the lib32 variant.
|
/// Internal runtime marker used to adjust builder behavior for the lib32 variant.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub lib32_variant: bool,
|
pub lib32_variant: bool,
|
||||||
@@ -819,6 +886,14 @@ impl Default for BuildFlags {
|
|||||||
source_subdir: String::new(),
|
source_subdir: String::new(),
|
||||||
build_dir: None,
|
build_dir: None,
|
||||||
binary_type: String::new(),
|
binary_type: String::new(),
|
||||||
|
dkms_name: String::new(),
|
||||||
|
dkms_version: String::new(),
|
||||||
|
dkms_source_dir: String::new(),
|
||||||
|
dkms_install_dir: String::new(),
|
||||||
|
dkms_make_args: Vec::new(),
|
||||||
|
dkms_pre_build: Vec::new(),
|
||||||
|
dkms_autoinstall: default_dkms_autoinstall(),
|
||||||
|
dkms_modules: Vec::new(),
|
||||||
lib32_variant: false,
|
lib32_variant: false,
|
||||||
host_build_dir: None,
|
host_build_dir: None,
|
||||||
}
|
}
|
||||||
@@ -958,6 +1033,10 @@ fn default_use_lto() -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_dkms_autoinstall() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn default_ar() -> String {
|
fn default_ar() -> String {
|
||||||
"ar".to_string()
|
"ar".to_string()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2379,6 +2379,102 @@ fn docs_package_for_output_derives_name_and_description() {
|
|||||||
assert_eq!(docs.version, "1.0");
|
assert_eq!(docs.version, "1.0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_dkms_build_type_and_modules() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let path = tmp.path().join("zfs.toml");
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
&path,
|
||||||
|
r#"
|
||||||
|
[package]
|
||||||
|
name = "zfs-dkms"
|
||||||
|
version = "2.4.3"
|
||||||
|
description = "d"
|
||||||
|
homepage = "h"
|
||||||
|
license = "CDDL"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/openzfs/zfs/releases/download/zfs-$version/zfs-$version.tar.gz"
|
||||||
|
sha256 = "skip"
|
||||||
|
extract_dir = "zfs-$version"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
type = "dkms"
|
||||||
|
|
||||||
|
[build.flags]
|
||||||
|
dkms_name = "zfs"
|
||||||
|
dkms_version = "$version"
|
||||||
|
dkms_source_dir = "."
|
||||||
|
dkms_install_dir = "updates/depot"
|
||||||
|
dkms_make_args = ["V=1"]
|
||||||
|
dkms_pre_build = [
|
||||||
|
"./configure --with-config=kernel --with-linux=$kernel_build_dir --with-linux-obj=$kernel_build_dir"
|
||||||
|
]
|
||||||
|
|
||||||
|
[[build.flags.dkms_modules]]
|
||||||
|
name = "zfs"
|
||||||
|
path = "module"
|
||||||
|
built_location = "module/zfs"
|
||||||
|
|
||||||
|
[[build.flags.dkms_modules]]
|
||||||
|
name = "spl"
|
||||||
|
dest_name = "spl_compat"
|
||||||
|
build_dir = "module"
|
||||||
|
built_location = "module/spl"
|
||||||
|
install_dir = "/updates/storage"
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let spec = PackageSpec::from_file(&path).unwrap();
|
||||||
|
assert!(matches!(spec.build.build_type, BuildType::Dkms));
|
||||||
|
assert_eq!(spec.effective_dkms_name(), "zfs");
|
||||||
|
assert_eq!(spec.effective_dkms_version(), "2.4.3");
|
||||||
|
assert_eq!(spec.effective_dkms_install_dir(), "updates/depot");
|
||||||
|
assert_eq!(spec.build.flags.dkms_pre_build.len(), 1);
|
||||||
|
assert_eq!(spec.build.flags.dkms_modules.len(), 2);
|
||||||
|
assert_eq!(spec.build.flags.dkms_modules[0].build_dir, "module");
|
||||||
|
assert_eq!(
|
||||||
|
spec.effective_dkms_module_install_dir(&spec.build.flags.dkms_modules[1]),
|
||||||
|
"updates/storage"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_dkms_rejects_unsafe_module_paths() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let path = tmp.path().join("bad.toml");
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
&path,
|
||||||
|
r#"
|
||||||
|
[package]
|
||||||
|
name = "bad-dkms"
|
||||||
|
version = "1.0"
|
||||||
|
description = "d"
|
||||||
|
homepage = "h"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://example.com/bad.tar.gz"
|
||||||
|
sha256 = "skip"
|
||||||
|
extract_dir = "bad"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
type = "dkms"
|
||||||
|
|
||||||
|
[[build.flags.dkms_modules]]
|
||||||
|
name = "bad"
|
||||||
|
path = "../outside"
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let err = PackageSpec::from_file(&path).unwrap_err().to_string();
|
||||||
|
assert!(err.contains("unsafe path component"), "{err}");
|
||||||
|
}
|
||||||
|
|
||||||
fn mk_spec(name: &str, version: &str) -> PackageSpec {
|
fn mk_spec(name: &str, version: &str) -> PackageSpec {
|
||||||
PackageSpec {
|
PackageSpec {
|
||||||
package: PackageInfo {
|
package: PackageInfo {
|
||||||
|
|||||||
Reference in New Issue
Block a user