|
| 1 | +use anyhow::Ok; |
| 2 | +use anyhow::Result; |
| 3 | + |
| 4 | +use crate::deploy::ImageState; |
| 5 | +use ostree::gio; |
| 6 | +use ostree_ext::ostree; |
| 7 | +use ostree_ext::ostree::Deployment; |
| 8 | +use ostree_ext::prelude::Cast; |
| 9 | +use ostree_ext::prelude::FileEnumeratorExt; |
| 10 | +use ostree_ext::prelude::FileExt; |
| 11 | + |
| 12 | +use serde::Deserialize; |
| 13 | + |
| 14 | +#[derive(Deserialize)] |
| 15 | +#[serde(rename_all = "kebab-case", deny_unknown_fields)] |
| 16 | +struct Config { |
| 17 | + kargs: Vec<String>, |
| 18 | + match_architectures: Option<Vec<String>>, |
| 19 | +} |
| 20 | + |
| 21 | +/// Compute the kernel arguments for the new deployment. This starts from the booted |
| 22 | +/// karg, but applies the diff between the bootc karg files in /usr/lib/bootc/kargs.d |
| 23 | +/// between the booted deployment and the new one. |
| 24 | +pub(crate) fn get_kargs( |
| 25 | + repo: &ostree::Repo, |
| 26 | + booted_deployment: &Deployment, |
| 27 | + fetched: &ImageState, |
| 28 | +) -> Result<Vec<String>> { |
| 29 | + let cancellable = gio::Cancellable::NONE; |
| 30 | + let mut kargs: Vec<String> = vec![]; |
| 31 | + let sys_arch = std::env::consts::ARCH.to_string(); |
| 32 | + |
| 33 | + // Get the running kargs of the booted system |
| 34 | + if let Some(bootconfig) = ostree::Deployment::bootconfig(booted_deployment) { |
| 35 | + if let Some(options) = ostree::BootconfigParser::get(&bootconfig, "options") { |
| 36 | + let options: Vec<&str> = options.split_whitespace().collect(); |
| 37 | + let mut options: Vec<String> = options.into_iter().map(|s| s.to_string()).collect(); |
| 38 | + kargs.append(&mut options); |
| 39 | + } |
| 40 | + }; |
| 41 | + |
| 42 | + // Get the kargs in kargs.d of the booted system |
| 43 | + let mut existing_kargs: Vec<String> = vec![]; |
| 44 | + let fragments = liboverdrop::scan(&["/usr/lib"], "bootc/kargs.d", &["toml"], true); |
| 45 | + for (_name, path) in fragments { |
| 46 | + let s = std::fs::read_to_string(&path)?; |
| 47 | + let mut parsed_kargs = parse_file(s.clone(), sys_arch.clone())?; |
| 48 | + existing_kargs.append(&mut parsed_kargs); |
| 49 | + } |
| 50 | + |
| 51 | + // Get the kargs in kargs.d of the pending image |
| 52 | + let mut remote_kargs: Vec<String> = vec![]; |
| 53 | + let (fetched_tree, _) = repo.read_commit(fetched.ostree_commit.as_str(), cancellable)?; |
| 54 | + let fetched_tree = fetched_tree.resolve_relative_path("/usr/lib/bootc/kargs.d"); |
| 55 | + let fetched_tree = fetched_tree |
| 56 | + .downcast::<ostree::RepoFile>() |
| 57 | + .expect("downcast"); |
| 58 | + if !fetched_tree.query_exists(cancellable) { |
| 59 | + // if the kargs.d directory does not exist in the fetched image, return the existing kargs |
| 60 | + kargs.append(&mut existing_kargs); |
| 61 | + return Ok(kargs); |
| 62 | + } |
| 63 | + let queryattrs = "standard::name,standard::type"; |
| 64 | + let queryflags = gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS; |
| 65 | + let fetched_iter = fetched_tree.enumerate_children(queryattrs, queryflags, cancellable)?; |
| 66 | + while let Some(fetched_info) = fetched_iter.next_file(cancellable)? { |
| 67 | + // only read and parse the file if it is a toml file |
| 68 | + let name = fetched_info.name(); |
| 69 | + if let Some(name) = name.to_str() { |
| 70 | + if name.ends_with(".toml") { |
| 71 | + let fetched_child = fetched_iter.child(&fetched_info); |
| 72 | + let fetched_child = fetched_child |
| 73 | + .downcast::<ostree::RepoFile>() |
| 74 | + .expect("downcast"); |
| 75 | + fetched_child.ensure_resolved()?; |
| 76 | + let fetched_contents_checksum = fetched_child.checksum(); |
| 77 | + let f = |
| 78 | + ostree::Repo::load_file(repo, fetched_contents_checksum.as_str(), cancellable)?; |
| 79 | + let file_content = f.0; |
| 80 | + let mut reader = |
| 81 | + ostree_ext::prelude::InputStreamExtManual::into_read(file_content.unwrap()); |
| 82 | + let s = std::io::read_to_string(&mut reader)?; |
| 83 | + let mut parsed_kargs = parse_file(s.clone(), sys_arch.clone())?; |
| 84 | + remote_kargs.append(&mut parsed_kargs); |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + // get the diff between the existing and remote kargs |
| 90 | + let mut added_kargs: Vec<String> = remote_kargs |
| 91 | + .clone() |
| 92 | + .into_iter() |
| 93 | + .filter(|item| !existing_kargs.contains(item)) |
| 94 | + .collect(); |
| 95 | + let removed_kargs: Vec<String> = existing_kargs |
| 96 | + .clone() |
| 97 | + .into_iter() |
| 98 | + .filter(|item| !remote_kargs.contains(item)) |
| 99 | + .collect(); |
| 100 | + |
| 101 | + tracing::debug!( |
| 102 | + "kargs: added={:?} removed={:?}", |
| 103 | + &added_kargs, |
| 104 | + removed_kargs |
| 105 | + ); |
| 106 | + |
| 107 | + // apply the diff to the system kargs |
| 108 | + kargs.retain(|x| !removed_kargs.contains(x)); |
| 109 | + kargs.append(&mut added_kargs); |
| 110 | + |
| 111 | + Ok(kargs) |
| 112 | +} |
| 113 | + |
| 114 | +pub fn parse_file(file_content: String, sys_arch: String) -> Result<Vec<String>> { |
| 115 | + let mut de: Config = toml::from_str(&file_content)?; |
| 116 | + let mut parsed_kargs: Vec<String> = vec![]; |
| 117 | + // if arch specified, apply kargs only if the arch matches |
| 118 | + // if arch not specified, apply kargs unconditionally |
| 119 | + match de.match_architectures { |
| 120 | + None => parsed_kargs = de.kargs, |
| 121 | + Some(match_architectures) => { |
| 122 | + if match_architectures.contains(&sys_arch) { |
| 123 | + parsed_kargs.append(&mut de.kargs); |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + Ok(parsed_kargs) |
| 128 | +} |
| 129 | + |
| 130 | +#[test] |
| 131 | +/// Verify that kargs are only applied to supported architectures |
| 132 | +fn test_arch() { |
| 133 | + // no arch specified, kargs ensure that kargs are applied unconditionally |
| 134 | + let sys_arch = "x86_64".to_string(); |
| 135 | + let file_content = r##"kargs = ["console=tty0", "nosmt"]"##.to_string(); |
| 136 | + let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap(); |
| 137 | + assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]); |
| 138 | + let sys_arch = "aarch64".to_string(); |
| 139 | + let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap(); |
| 140 | + assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]); |
| 141 | + |
| 142 | + // one arch matches and one doesn't, ensure that kargs are only applied for the matching arch |
| 143 | + let sys_arch = "aarch64".to_string(); |
| 144 | + let file_content = r##"kargs = ["console=tty0", "nosmt"] |
| 145 | +match-architectures = ["x86_64"] |
| 146 | +"## |
| 147 | + .to_string(); |
| 148 | + let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap(); |
| 149 | + assert_eq!(parsed_kargs, [] as [String; 0]); |
| 150 | + let file_content = r##"kargs = ["console=tty0", "nosmt"] |
| 151 | +match-architectures = ["aarch64"] |
| 152 | +"## |
| 153 | + .to_string(); |
| 154 | + let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap(); |
| 155 | + assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]); |
| 156 | + |
| 157 | + // multiple arch specified, ensure that kargs are applied to both archs |
| 158 | + let sys_arch = "x86_64".to_string(); |
| 159 | + let file_content = r##"kargs = ["console=tty0", "nosmt"] |
| 160 | +match-architectures = ["x86_64", "aarch64"] |
| 161 | +"## |
| 162 | + .to_string(); |
| 163 | + let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap(); |
| 164 | + assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]); |
| 165 | + std::env::set_var("ARCH", "aarch64"); |
| 166 | + let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap(); |
| 167 | + assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]); |
| 168 | +} |
0 commit comments