427 lines
13 KiB
Diff
427 lines
13 KiB
Diff
From d854bdec0aa318fb1db33c96a7f2e52976452bc4 Mon Sep 17 00:00:00 2001
|
|
From: zhangyao2022 <zhangyao108@huawei.com>
|
|
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<Hostname> {
|
|
+ let hostname = Hostname::new(str_hostname.trim());
|
|
+ if hostname.valid() {
|
|
+ Some(hostname)
|
|
+ } else {
|
|
+ None
|
|
+ }
|
|
+ }
|
|
+
|
|
+ fn from_cmdline() -> Option<Hostname> {
|
|
+ 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<Self> {
|
|
+ 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<Hostname> {
|
|
+ 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<Option<String>> {
|
|
+ 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
|
|
|