diff --git a/src/cmdline.rs b/src/cmdline.rs index ea2c4a4a4..aa02904af 100644 --- a/src/cmdline.rs +++ b/src/cmdline.rs @@ -43,6 +43,8 @@ pub struct InstallConfig { pub ignition_hash: Option, pub platform: Option, pub firstboot_kargs: Option, + pub append_kargs: Option>, + pub delete_kargs: Option>, pub insecure: bool, pub preserve_on_error: bool, pub network_config: Option, @@ -193,6 +195,24 @@ pub fn parse_args() -> Result { // for now though. It's used at least by `coreos-installer.service`. .hidden(true), ) + .arg( + Arg::with_name("append-karg") + .long("append-karg") + .value_name("arg") + .help("Append default kernel arg") + .takes_value(true) + .number_of_values(1) + .multiple(true), + ) + .arg( + Arg::with_name("delete-karg") + .long("delete-karg") + .value_name("arg") + .help("Delete default kernel arg") + .takes_value(true) + .number_of_values(1) + .multiple(true), + ) .arg( Arg::with_name("copy-network") .short("n") @@ -644,6 +664,12 @@ fn parse_install(matches: &ArgMatches) -> Result { .chain_err(|| "parsing Ignition config hash")?, platform: matches.value_of("platform").map(String::from), firstboot_kargs: matches.value_of("firstboot-kargs").map(String::from), + append_kargs: matches + .values_of("append-karg") + .map(|v| v.map(String::from).collect()), + delete_kargs: matches + .values_of("delete-karg") + .map(|v| v.map(String::from).collect()), insecure: matches.is_present("insecure"), preserve_on_error: matches.is_present("preserve-on-error"), network_config, diff --git a/src/install.rs b/src/install.rs index 0e79eb1f9..5212c8302 100644 --- a/src/install.rs +++ b/src/install.rs @@ -152,6 +152,8 @@ fn write_disk(config: &InstallConfig, source: &mut ImageSource, dest: &mut File) // postprocess if config.ignition.is_some() || config.firstboot_kargs.is_some() + || config.append_kargs.is_some() + || config.delete_kargs.is_some() || config.platform.is_some() || config.network_config.is_some() { @@ -164,6 +166,18 @@ fn write_disk(config: &InstallConfig, source: &mut ImageSource, dest: &mut File) write_firstboot_kargs(mount.mountpoint(), firstboot_kargs) .chain_err(|| "writing firstboot kargs")?; } + if config.append_kargs.is_some() || config.delete_kargs.is_some() { + eprintln!("Modifying kernel arguments"); + + edit_bls_entries(mount.mountpoint(), |orig_contents: &str| { + bls_entry_delete_and_append_kargs( + orig_contents, + config.delete_kargs.as_ref(), + config.append_kargs.as_ref(), + ) + }) + .chain_err(|| "deleting and appending kargs")?; + } if let Some(platform) = config.platform.as_ref() { write_platform(mount.mountpoint(), platform).chain_err(|| "writing platform ID")?; } @@ -236,6 +250,56 @@ fn write_firstboot_kargs(mountpoint: &Path, args: &str) -> Result<()> { Ok(()) } +// This is split out so that we can unit test it. +fn bls_entry_delete_and_append_kargs( + orig_contents: &str, + delete_args: Option<&Vec>, + append_args: Option<&Vec>, +) -> Result { + let mut new_contents = String::with_capacity(orig_contents.len()); + let mut found_options = false; + for line in orig_contents.lines() { + if !line.starts_with("options ") { + new_contents.push_str(line.trim_end()); + } else if found_options { + bail!("Multiple 'options' lines found"); + } else { + // XXX: Need a proper parser here and share it with afterburn. The approach we use here + // is to just do a dumb substring search and replace. This is naive (e.g. doesn't + // handle occurrences in quoted args) but will work for now (one thing that saves us is + // that we're acting on our baked configs, which have straight-forward kargs). + new_contents.push_str("options "); + let mut line: String = add_whitespaces(&line["options ".len()..]); + if let Some(args) = delete_args { + for arg in args { + let arg = add_whitespaces(&arg); + line = line.replace(&arg, " "); + } + } + new_contents.push_str(line.trim_start().trim_end()); + if let Some(args) = append_args { + for arg in args { + new_contents.push(' '); + new_contents.push_str(&arg); + } + } + found_options = true; + } + new_contents.push('\n'); + } + if !found_options { + bail!("Couldn't locate 'options' line"); + } + Ok(new_contents) +} + +fn add_whitespaces(s: &str) -> String { + let mut r: String = s.into(); + r.insert(0, ' '); + r.push(' '); + r +} + /// Override the platform ID. fn write_platform(mountpoint: &Path, platform: &str) -> Result<()> { // early return if setting the platform to the default value, since @@ -406,4 +470,60 @@ mod tests { "options foo bar ignition.platform.id=openstack" ); } + + #[test] + fn test_options_edit() { + let orig_content = "options foo bar foobar"; + + let delete_kargs = vec!["foo".into()]; + let new_content = + bls_entry_delete_and_append_kargs(orig_content, Some(&delete_kargs), None).unwrap(); + assert_eq!(new_content, "options bar foobar\n"); + + let delete_kargs = vec!["bar".into()]; + let new_content = + bls_entry_delete_and_append_kargs(orig_content, Some(&delete_kargs), None).unwrap(); + assert_eq!(new_content, "options foo foobar\n"); + + let delete_kargs = vec!["foobar".into()]; + let new_content = + bls_entry_delete_and_append_kargs(orig_content, Some(&delete_kargs), None).unwrap(); + assert_eq!(new_content, "options foo bar\n"); + + let delete_kargs = vec!["bar".into(), "foo".into()]; + let new_content = + bls_entry_delete_and_append_kargs(orig_content, Some(&delete_kargs), None).unwrap(); + assert_eq!(new_content, "options foobar\n"); + + let orig_content = "options foo=val bar baz=val"; + + let delete_kargs = vec!["foo=val".into()]; + let new_content = + bls_entry_delete_and_append_kargs(orig_content, Some(&delete_kargs), None).unwrap(); + assert_eq!(new_content, "options bar baz=val\n"); + + let delete_kargs = vec!["baz=val".into()]; + let new_content = + bls_entry_delete_and_append_kargs(orig_content, Some(&delete_kargs), None).unwrap(); + assert_eq!(new_content, "options foo=val bar\n"); + + let orig_content = + "options foo mitigations=auto,nosmt console=tty0 bar console=ttyS0,115200n8 baz"; + + let delete_kargs = vec![ + "mitigations=auto,nosmt".into(), + "console=ttyS0,115200n8".into(), + ]; + let append_kargs = vec!["console=ttyS1,115200n8".into()]; + let new_content = bls_entry_delete_and_append_kargs( + orig_content, + Some(&delete_kargs), + Some(&append_kargs), + ) + .unwrap(); + assert_eq!( + new_content, + "options foo console=tty0 bar baz console=ttyS1,115200n8\n" + ); + } }