From d854bdec0aa318fb1db33c96a7f2e52976452bc4 Mon Sep 17 00:00:00 2001 From: zhangyao2022 Date: Tue, 20 Jun 2023 17:26:52 +0800 Subject: [PATCH] feature: support hostname setup --- Cargo.toml | 1 + exts/hostname_setup/Cargo.toml | 11 ++ exts/hostname_setup/src/main.rs | 235 +++++++++++++++++++++++ install.sh | 1 + libs/basic/src/lib.rs | 1 + libs/basic/src/os_release.rs | 109 +++++++++++ 9 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 exts/hostname_setup/Cargo.toml create mode 100644 exts/hostname_setup/src/main.rs create mode 100644 libs/basic/src/os_release.rs diff --git a/Cargo.toml b/Cargo.toml index b96ec26..a375070 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,6 +120,7 @@ members = [ # external binaries "exts/init", "exts/sctl", + "exts/hostname_setup", #internal libraries crates "libs/cmdproto", #external libraries crates diff --git a/exts/hostname_setup/Cargo.toml b/exts/hostname_setup/Cargo.toml new file mode 100644 index 0000000..b2cbdcf --- /dev/null +++ b/exts/hostname_setup/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "hostname_setup" +version = "0.1.0" +edition = "2021" + +[dependencies] +nix = "0.24" +log = "0.4" +libc = "0.2.*" +constants = { version = "0.1.0", path = "../../libs/constants" } +basic = { path = "../../libs/basic" } diff --git a/exts/hostname_setup/src/main.rs b/exts/hostname_setup/src/main.rs new file mode 100644 index 0000000..4dc5a1b --- /dev/null +++ b/exts/hostname_setup/src/main.rs @@ -0,0 +1,235 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +//! hostname setup + +use basic::logger; +use basic::os_release; +use basic::proc_cmdline; +use nix::Result; +use std::fmt; +use std::fmt::Display; + +const SYSTEMD_HOSTNAME_KEY: &str = "systemd.hostname"; +const SYSMASTER_HOSTNAME_KEY: &str = "sysmaster.hostname"; +const DEFAULT_HOSTNAME: &str = "localhost"; + +#[derive(Debug)] +struct Hostname { + hostname: String, +} + +impl Hostname { + fn from_string(str_hostname: &str) -> Option { + let hostname = Hostname::new(str_hostname.trim()); + if hostname.valid() { + Some(hostname) + } else { + None + } + } + + fn from_cmdline() -> Option { + let systemd_hostname = match proc_cmdline::cmdline_get_value(SYSTEMD_HOSTNAME_KEY) { + Err(e) => { + log::warn!( + "Failed to get proc cmdline by key {}: {}", + SYSTEMD_HOSTNAME_KEY, + e + ); + None + } + Ok(hostname) => hostname, + }; + + systemd_hostname.map_or_else( + || match proc_cmdline::cmdline_get_value(SYSMASTER_HOSTNAME_KEY) { + Err(e) => { + log::warn!( + "Failed to get proc cmdline by key {}: {}", + SYSMASTER_HOSTNAME_KEY, + e + ); + None + } + Ok(hostname) => hostname.and_then(|hostname| Hostname::from_string(&hostname)), + }, + |hostname| Hostname::from_string(&hostname), + ) + } + + fn from_etc_hostname() -> Option { + match std::fs::read_to_string("/etc/hostname") { + Err(e) => { + log::warn!("Failed to get /etc/hostname: {}", e); + None + } + Ok(hostname) => Hostname::from_string(&hostname), + } + } + + fn new(hostname: &str) -> Self { + Hostname { + hostname: hostname.trim().to_string(), + } + } + + fn valid(&self) -> bool { + if self.hostname.is_empty() { + return false; + } + + if self.hostname.len() > 64 { + return false; + } + + let mut dot = true; + let mut hyphen = true; + + for c in self.hostname.chars() { + if c == '.' { + if dot || hyphen { + return false; + } + dot = true; + hyphen = false; + } else if c == '-' { + if dot { + return false; + } + dot = false; + hyphen = true; + } else { + if !c.is_ascii_alphanumeric() { + return false; + } + dot = false; + hyphen = false; + } + } + + if dot { + return false; + } + + if hyphen { + return false; + } + true + } + + fn setup(hostname: &Self) -> Result<()> { + if !hostname.valid() { + return Err(nix::errno::Errno::EINVAL); + } + + let local_hostname = Hostname::local_hostname()?; + if local_hostname.eq(hostname) { + log::info!("Hostname has already been set: {hostname}."); + return Ok(()); + } + nix::unistd::sethostname(&hostname.hostname) + } + + fn local_hostname() -> Result { + match nix::sys::utsname::uname() { + Err(e) => { + log::warn!("Failed to get uname: {}", e); + Err(e) + } + Ok(uts_name) => Ok(uts_name + .nodename() + .to_str() + .map_or(Hostname::new(""), Hostname::new)), + } + } + + fn hostname_is_set() -> bool { + Hostname::local_hostname().map_or(false, |hostname| hostname != Hostname::new("(none)")) + } +} + +impl PartialEq for Hostname { + fn eq(&self, other: &Self) -> bool { + self.hostname.eq(&other.hostname) + } +} + +impl Display for Hostname { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.hostname) + } +} + +impl Default for Hostname { + fn default() -> Self { + if let Ok(Some(str)) = os_release::get_os_release("DEFAULT_HOSTNAME") { + return Hostname::from_string(&str).unwrap_or_else(|| Hostname::new(DEFAULT_HOSTNAME)); + } + Hostname::new(DEFAULT_HOSTNAME) + } +} + +fn main() { + logger::init_log_to_console("hostname-setup", log::LevelFilter::Info); + let mut op_hostname = Hostname::from_cmdline(); + if op_hostname.is_none() { + op_hostname = Hostname::from_etc_hostname(); + } + + if op_hostname.is_none() && Hostname::hostname_is_set() { + log::info!("Hostname has already been set, skipping."); + return; + } + + let hostname = op_hostname.unwrap_or_default(); + + log::info!("Hostname set to: {}.", hostname); + if let Err(e) = Hostname::setup(&hostname) { + log::error!("Failed to set hostname: {}.", e); + std::process::exit(e as i32); + } + std::process::exit(0); +} + +#[cfg(test)] +mod test { + use crate::Hostname; + + #[test] + fn test_hostname_valid() { + assert!(Hostname::new("foobar").valid()); + assert!(Hostname::new("foobar.com").valid()); + assert!(!Hostname::new("foobar.com.").valid()); + assert!(Hostname::new("fooBAR").valid()); + assert!(Hostname::new("fooBAR.com").valid()); + assert!(!Hostname::new("fooBAR.").valid()); + assert!(!Hostname::new("fooBAR.com.").valid()); + assert!(!Hostname::new("fööbar").valid()); + assert!(!Hostname::new("").valid()); + assert!(!Hostname::new(".").valid()); + assert!(!Hostname::new("..").valid()); + assert!(!Hostname::new("foobar.").valid()); + assert!(!Hostname::new(".foobar").valid()); + assert!(!Hostname::new("foo..bar").valid()); + assert!(!Hostname::new("foo.bar..").valid()); + assert!(!Hostname::new("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx").valid()); + assert!(!Hostname::new( + "au-xph5-rvgrdsb5hcxc-47et3a5vvkrc-server-wyoz4elpdpe3.openstack.local" + ) + .valid()); + assert!(Hostname::new("local--host.localdomain").valid()); + assert!(!Hostname::new("localhost-.localdomain").valid()); + assert!(!Hostname::new("localhost.-localdomain").valid()); + assert!(Hostname::new("localhost.localdomain").valid()); + } +} diff --git a/install.sh b/install.sh index eb1b43e..675c543 100644 --- a/install.sh +++ b/install.sh @@ -14,6 +14,7 @@ install -Dm0755 -t ${install_dir} ${target_dir}/fstab || exit 1 install -Dm0755 -t ${install_dir} ${target_dir}/sysmonitor || exit 1 install -Dm0755 -t ${install_dir} ${target_dir}/random_seed || exit 1 install -Dm0755 -t ${install_dir} ${target_dir}/rc-local-generator || exit 1 +install -Dm0755 -t ${install_dir} ${target_dir}/hostname_setup || exit 1 strip ${target_dir}/lib*.so diff --git a/libs/basic/src/lib.rs b/libs/basic/src/lib.rs index 8678b58..db80efb 100644 --- a/libs/basic/src/lib.rs +++ b/libs/basic/src/lib.rs @@ -44,3 +44,4 @@ pub mod user_group_util; pub mod uuid; pub mod virtualize; pub use error::*; +pub mod os_release; diff --git a/libs/basic/src/os_release.rs b/libs/basic/src/os_release.rs new file mode 100644 index 0000000..26e291d --- /dev/null +++ b/libs/basic/src/os_release.rs @@ -0,0 +1,109 @@ +// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. +// +// sysMaster is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +//! Parse info about /etc/os-release +use std::{ + fs::File, + io::{BufRead, BufReader}, +}; + +use crate::error::*; + +/// get value by key in /etc/os-release +pub fn get_os_release(key: &str) -> Result> { + let reader = BufReader::new(File::open("/etc/os-release").context(IoSnafu)?); + for line in reader.lines().map(std::result::Result::unwrap_or_default) { + match parse_line(key, &line) { + Some(value) => return Ok(Some(value.to_string())), + None => continue, + } + } + Ok(None) +} + +fn parse_line<'a>(key: &str, line: &'a str) -> Option<&'a str> { + if let Some(pos) = line.chars().position(|c| c == '=') { + let line_key = line[..pos].trim(); + if key == line_key { + let s = trim_quotes(line[pos + 1..].trim()); + if !s.is_empty() { + return Some(s); + } + } + None + } else { + None + } +} + +fn trim_quotes(str: &str) -> &str { + if ["\"", "'"] + .iter() + .any(|s| str.starts_with(s) && str.ends_with(s)) + { + str[1..str.len() - 1].trim() + } else { + str.trim() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_trim_quotes() { + let str1 = "\"aaaaa\""; + assert_eq!(&str1[1..str1.len() - 1], trim_quotes(str1)); + + let str2 = "'aaaaa'"; + assert_eq!(&str2[1..str2.len() - 1], trim_quotes(str2)); + + let str3 = "\"aaaaa"; + assert_eq!(str3, trim_quotes(str3)); + + let str4 = "'aaaaa"; + assert_eq!(str4, trim_quotes(str4)); + } + + #[test] + fn test_parse_line() { + let line = "ID=\"openEuler\""; + assert_eq!(parse_line("ID", line), Some("openEuler")); + assert_eq!(parse_line("NONE", line), None); + + let line = "ID='openEuler'"; + assert_eq!(parse_line("ID", line), Some("openEuler")); + assert_eq!(parse_line("NONE", line), None); + + let line = "IDopenEuler"; + assert_eq!(parse_line("ID", line), None); + + let line = "ID="; + assert_eq!(parse_line("ID", line), None); + + let line = "ID=\"openEuler \""; + assert_eq!(parse_line("ID", line), Some("openEuler")); + + let line = "ID=\"openEuler \""; + assert_eq!(parse_line("ID", line), Some("openEuler")); + + let line = "ID=\"openEuler\" "; + assert_eq!(parse_line("ID", line), Some("openEuler")); + + let line = "ID=openEuler"; + assert_eq!(parse_line("ID", line), Some("openEuler")); + + let line = "ID=openEuler "; + assert_eq!(parse_line("ID", line), Some("openEuler")); + } +} -- 2.33.0