sysmaster/backport-feature-support-hostname-setup.patch
huyubiao 8936fa02c5 sync patches from upstream,change the path of the unit,modify permissions for some directories and files
(cherry picked from commit ce9ff469b57f60130621bc293783bd3ac1fc92f2)
2023-08-05 18:15:53 +08:00

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