/
githubmirror
/
coreos-installer
Обзор
Документация
Войти
/
githubmirror
/
coreos-installer
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/cmdline/console.rs
345 строк
11 KB
Benjamin Gilbert
Inline variables into format strings
19 фев 2023, 09:06
19 фев 2023, 09:06
7e906d0
Код
Авторство
О чём код?
// Copyright 2022 Red Hat, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Helper types for console argument. use anyhow::{bail, Context, Error, Result}; use lazy_static::lazy_static; use regex::Regex; use serde_with::{DeserializeFromStr, SerializeDisplay}; use std::fmt; use std::str::FromStr; const KARG_PREFIX: &str = "console="; #[derive(Clone, Debug, DeserializeFromStr, SerializeDisplay, PartialEq, Eq)] pub enum Console { Graphical(GraphicalConsole), Serial(SerialConsole), } #[derive(Clone, Debug, PartialEq, Eq)] pub struct GraphicalConsole { device: String, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SerialConsole { prefix: String, port: u8, speed: u32, data_bits: u8, parity: Parity, // Linux console doesn't support stop bits // GRUB doesn't support RTS/CTS flow control } #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum Parity { None, Odd, Even, } impl Parity { fn for_grub(&self) -> &'static str { match self { Self::None => "no", Self::Odd => "odd", Self::Even => "even", } } fn for_karg(&self) -> &'static str { match self { Self::None => "n", Self::Odd => "o", Self::Even => "e", } } } impl Console { pub fn grub_terminal(&self) -> &'static str { match self { Self::Graphical(_) => "console", Self::Serial(_) => "serial", } } pub fn grub_command(&self) -> Option<String> { match self { Self::Graphical(_) => None, Self::Serial(c) => Some(format!( "serial --unit={} --speed={} --word={} --parity={}", c.port, c.speed, c.data_bits, c.parity.for_grub() )), } } pub fn karg(&self) -> String { format!("{KARG_PREFIX}{self}") } /// Write a warning message to stdout if kargs contains "console=" /// arguments we can parse and no "console=" arguments we can't. The /// warning suggests that users use console_option instead of /// karg_option to specify the desired console. pub fn maybe_warn_on_kargs(kargs: &[String], karg_option: &str, console_option: &str) { use textwrap::{fill, Options, WordSplitter}; if let Some(args) = Self::maybe_console_args_from_kargs(kargs) { // automatically wrap the message, but use Unicode non-breaking // spaces to avoid wrapping in the middle of the argument // strings, and then replace the non-breaking spaces afterward const NBSP: &str = "\u{a0}"; let msg = format!( "Note: consider using \"{}\" instead of \"{}\" to configure both kernel and bootloader consoles.", args.iter() .map(|a| format!("{console_option}{NBSP}{a}")) .collect::<Vec<String>>() .join(NBSP), args.iter() .map(|a| format!("{karg_option}{NBSP}console={a}")) .collect::<Vec<String>>() .join(NBSP), ); let wrapped = fill( &msg, Options::new(80) .break_words(false) .word_splitter(WordSplitter::NoHyphenation), ) .replace(NBSP, " "); eprintln!("\n{wrapped}\n"); } } /// If kargs contains at least one console argument and all console /// arguments are parseable as consoles, return a vector of verbatim /// (unparsed) console arguments with the console= prefixes removed. fn maybe_console_args_from_kargs(kargs: &[String]) -> Option<Vec<&str>> { let (parseable, unparseable): (Vec<&str>, Vec<&str>) = kargs .iter() .filter(|a| a.starts_with(KARG_PREFIX)) .map(|a| &a[KARG_PREFIX.len()..]) .partition(|a| Console::from_str(a).is_ok()); if !parseable.is_empty() && unparseable.is_empty() { Some(parseable) } else { None } } } impl FromStr for Console { type Err = Error; fn from_str(s: &str) -> Result<Self, Self::Err> { // help the user with possible misunderstandings for prefix in [KARG_PREFIX, "/dev/"] { if s.starts_with(prefix) { bail!(r#"spec should not start with "{prefix}""#); } } // first, parse serial console parameters lazy_static! { static ref SERIAL_REGEX: Regex = Regex::new("^(?P<prefix>ttyS|ttyAMA)(?P<port>[0-9]+)(?:,(?P<speed>[0-9]+)(?:(?P<parity>n|o|e)(?P<data_bits>[5-8])?)?)?$").expect("compiling console regex"); } if let Some(c) = SERIAL_REGEX.captures(s) { return Ok(Console::Serial(SerialConsole { prefix: c .name("prefix") .expect("prefix is mandatory") .as_str() .to_string(), port: c .name("port") .expect("port is mandatory") .as_str() .parse() .context("couldn't parse port")?, speed: c .name("speed") .map(|v| v.as_str().parse().context("couldn't parse speed")) .unwrap_or(Ok(9600))?, data_bits: c .name("data_bits") .map(|v| v.as_str().parse().expect("unexpected data bits")) .unwrap_or(8), parity: match c.name("parity").map(|v| v.as_str()) { // default None => Parity::None, Some("n") => Parity::None, Some("e") => Parity::Even, Some("o") => Parity::Odd, _ => unreachable!(), }, })); } // then try hardcoded strings for graphical consoles match s { "tty0" | "hvc0" | "ttysclp0" => Ok(Console::Graphical(GraphicalConsole { device: s.to_string(), })), _ => bail!("invalid or unsupported console argument"), } } } impl fmt::Display for Console { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Graphical(c) => write!(f, "{}", c.device), Self::Serial(c) => write!( f, "{}{},{}{}{}", c.prefix, c.port, c.speed, c.parity.for_karg(), c.data_bits ), } } } #[cfg(test)] mod tests { use super::*; #[test] fn valid_console_args() { let cases = vec![ ("tty0", "console=tty0", "console", None), ("hvc0", "console=hvc0", "console", None), ("ttysclp0", "console=ttysclp0", "console", None), ( "ttyS1", "console=ttyS1,9600n8", "serial", Some("serial --unit=1 --speed=9600 --word=8 --parity=no"), ), ( "ttyAMA1", "console=ttyAMA1,9600n8", "serial", Some("serial --unit=1 --speed=9600 --word=8 --parity=no"), ), ( "ttyS1,1234567e5", "console=ttyS1,1234567e5", "serial", Some("serial --unit=1 --speed=1234567 --word=5 --parity=even"), ), ( "ttyS2,5o", "console=ttyS2,5o8", "serial", Some("serial --unit=2 --speed=5 --word=8 --parity=odd"), ), ( "ttyS3,17", "console=ttyS3,17n8", "serial", Some("serial --unit=3 --speed=17 --word=8 --parity=no"), ), ]; for (input, karg, grub_terminal, grub_command) in cases { let console = Console::from_str(input).unwrap(); assert_eq!( console.grub_terminal(), grub_terminal, "GRUB terminal for {input}" ); assert_eq!( console.grub_command().as_deref(), grub_command, "GRUB command for {input}" ); assert_eq!(console.karg(), karg, "karg for {input}"); } } #[test] fn invalid_console_args() { let cases = vec![ "foo", "/dev/tty0", "/dev/ttyS0", "console=tty0", "console=ttyS0", "ztty0", "zttyS0", "tty0z", "ttyS0z", "tty1", "hvc1", "ttysclp1", "ttyS0,", "ttyS0,z", "ttyS0,115200p8", "ttyS0,115200n4", "ttyS0,115200n8r", "ttyB0", "ttyS9999999999999999999", "ttyS0,999999999999999999999", ]; for input in cases { Console::from_str(input).unwrap_err(); } } #[test] fn maybe_console_args_from_kargs() { assert_eq!( Console::maybe_console_args_from_kargs(&[ "foo".into(), "console=ttyS0".into(), "bar".into() ]), Some(vec!["ttyS0"]) ); assert_eq!( Console::maybe_console_args_from_kargs(&[ "foo".into(), "console=ttyS0".into(), "console=tty0".into(), "console=tty0".into(), "console=ttyAMA1,115200n8".into(), "bar".into() ]), Some(vec!["ttyS0", "tty0", "tty0", "ttyAMA1,115200n8"]) ); assert_eq!( Console::maybe_console_args_from_kargs(&[ "foo".into(), "console=ttyS0".into(), "console=ttyS1z".into(), "console=tty0".into(), "bar".into() ]), None ); assert_eq!( Console::maybe_console_args_from_kargs(&["foo".into(), "bar".into()]), None ); assert_eq!(Console::maybe_console_args_from_kargs(&[]), None); } }