Skip to content

Commit

Permalink
Implemented printing and changing of host name.
Browse files Browse the repository at this point in the history
  • Loading branch information
koutheir committed Feb 24, 2025
1 parent 88bd47b commit 7cd7bbf
Show file tree
Hide file tree
Showing 16 changed files with 1,638 additions and 166 deletions.
295 changes: 163 additions & 132 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ xattr = "1.3.1"
tempfile = "3.10.1"
rand = { version = "0.9.0", features = ["small_rng"] }
utmpx = "0.2"
hostname = "0.4"
windows-sys = { version = "0.59", features = [
"Win32_Globalization",
"Win32_Networking_WinSock",
"Win32_NetworkManagement_IpHelper",
"Win32_NetworkManagement_Ndis",
"Win32_System_SystemInformation",
"Win32_Foundation",
] }

[dependencies]
clap = { workspace = true }
Expand Down
24 changes: 14 additions & 10 deletions src/uu/hostname/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
[package]
name = "uu_hostname"
version = "0.0.1"
edition = "2021"
authors = ["uutils developers"]
license = "MIT"
name = "uu_hostname"
version = "0.0.1"
edition = "2021"
authors = ["uutils developers"]
license = "MIT"
description = "hostname ~ (uutils) Execute a program periodically, showing output fullscreen"

homepage = "https://github.com/uutils/hostname"
homepage = "https://github.com/uutils/hostname"
repository = "https://github.com/uutils/hostname/tree/main/src/uu/hostname"
keywords = ["acl", "uutils", "cross-platform", "cli", "utility"]
keywords = ["acl", "uutils", "cross-platform", "cli", "utility"]
categories = ["command-line-utilities"]

[dependencies]
uucore = { workspace = true }
clap = { workspace = true }
libc = { workspace = true }
clap = { workspace = true }

[target.'cfg(target_os = "windows")'.dependencies]
windows-sys = { workspace = true }

[target.'cfg(not(target_os = "windows"))'.dependencies]
errno = { workspace = true }
hostname = { workspace = true }
libc = { workspace = true }

[lib]
path = "src/hostname.rs"
Expand Down
5 changes: 3 additions & 2 deletions src/uu/hostname/hostname.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
```
hostname [-a|--alias|-d|--domain|-f|--fqdn|--long|-A|--all-fqdns|-i|--ip-address|-I|--all-ip-addresses|-s|--short|-y|--yp|--nis]
hostname [-b|--boot] {-F filename|--file filename|hostname}
hostname [-h|--help] [-V|--version]
hostname {-h|--help}
hostname {-V|--version}
```

Show or set the system's host name
show or set the system's host name
14 changes: 14 additions & 0 deletions src/uu/hostname/src/change.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

#[cfg(not(target_family = "windows"))]
pub(crate) mod unix;
#[cfg(target_family = "windows")]
pub(crate) mod windows;

#[cfg(not(target_family = "windows"))]
pub(crate) use unix::{from_argument, from_file};
#[cfg(target_family = "windows")]
pub(crate) use windows::{from_argument, from_file};
84 changes: 84 additions & 0 deletions src/uu/hostname/src/change/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use std::borrow::Cow;
use std::ffi::{CString, OsStr};
use std::path::Path;

use uucore::error::UResult;

use crate::errors::HostNameError;
use crate::net::set_host_name;
use crate::utils::parse_host_name_file;

pub(crate) fn from_file(path: &Path) -> UResult<()> {
parse_host_name_file(path).map(Cow::Owned).and_then(run)
}

pub(crate) fn from_argument(host_name: &OsStr) -> UResult<()> {
#[cfg(target_family = "unix")]
let host_name = {
use std::os::unix::ffi::OsStrExt;
Cow::Borrowed(host_name.as_bytes())
};

#[cfg(target_family = "wasm")]
let host_name = {
use std::os::wasm::ffi::OsStrExt;
Cow::Borrowed(host_name.as_bytes())
};

run(host_name)
}

fn run(mut host_name: Cow<[u8]>) -> UResult<()> {
// Trim white space.
match &mut host_name {
Cow::Borrowed(name) => *name = name.trim_ascii(),

Cow::Owned(name) => {
while name.first().is_some_and(u8::is_ascii_whitespace) {
name.remove(0);
}

while name.last().is_some_and(u8::is_ascii_whitespace) {
name.pop();
}
}
};

let host_name = validate_host_name(host_name)?;

set_host_name(&host_name)
}

fn validate_host_name(host_name: Cow<[u8]>) -> Result<CString, HostNameError> {
// Rules:
// - The only allowed prefix and suffix characters are alphanumeric.
// - The only allowed characters inside are alphanumeric, '-' and '.'.
// - The following sequences are disallowed: "..", ".-" and "-.".
//
// Reference: RFC 1035: Domain Names - Implementation And Specification,
// section 2.3.1. Preferred name syntax.

let (Some(first_byte), Some(last_byte)) = (host_name.first(), host_name.last()) else {
return Err(HostNameError::InvalidHostName); // Empty name.
};

let is_disallowed_byte = move |b: &u8| !b.is_ascii_alphanumeric() && *b != b'-' && *b != b'.';
let is_disallowed_seq = move |seq: &[u8]| seq == b".." || seq == b".-" || seq == b"-.";

if !first_byte.is_ascii_alphanumeric()
|| !last_byte.is_ascii_alphanumeric()
|| host_name.iter().any(is_disallowed_byte)
|| host_name.windows(2).any(is_disallowed_seq)
{
return Err(HostNameError::InvalidHostName);
}

let mut host_name = host_name.into_owned();
host_name.push(0_u8);
Ok(unsafe { CString::from_vec_with_nul_unchecked(host_name) })
}
81 changes: 81 additions & 0 deletions src/uu/hostname/src/change/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;

use uucore::error::UResult;

use crate::errors::HostNameError;
use crate::net::set_host_name;
use crate::utils::parse_host_name_file;

pub(crate) fn from_file(path: &Path) -> UResult<()> {
let host_name = parse_host_name_file(path)?;
let host_name = std::str::from_utf8(&host_name).map_err(|_r| HostNameError::InvalidHostName)?;
run(host_name.encode_utf16().collect())
}

pub(crate) fn from_argument(host_name: &OsStr) -> UResult<()> {
run(host_name.encode_wide().collect())
}

fn run(mut host_name: Vec<u16>) -> UResult<()> {
// Trim white space.
while host_name.first().is_some_and(u16_is_ascii_whitespace) {
host_name.remove(0);
}

while host_name.last().is_some_and(u16_is_ascii_whitespace) {
host_name.pop();
}

validate_host_name(&host_name)?;

host_name.push(0); // Null-terminate.
set_host_name(&host_name)
}

fn u16_is_ascii_whitespace(ch: &u16) -> bool {
u8::try_from(*ch).is_ok_and(|b| b.is_ascii_whitespace())
}

fn u16_is_ascii_alphanumeric(ch: &u16) -> bool {
u8::try_from(*ch).is_ok_and(|b| b.is_ascii_alphanumeric())
}

fn validate_host_name(host_name: &[u16]) -> Result<(), HostNameError> {
// Rules:
// - The only allowed prefix and suffix characters are alphanumeric.
// - The only allowed characters inside are alphanumeric, '-' and '.'.
// - The following sequences are disallowed: "..", ".-" and "-.".
//
// Reference: RFC 1035: Domain Names - Implementation And Specification,
// section 2.3.1. Preferred name syntax.

const DOT_DOT: [u16; 2] = [b'.' as u16, b'.' as u16];
const DOT_DASH: [u16; 2] = [b'.' as u16, b'-' as u16];
const DASH_DOT: [u16; 2] = [b'-' as u16, b'.' as u16];

let (Some(first_byte), Some(last_byte)) = (host_name.first(), host_name.last()) else {
return Err(HostNameError::InvalidHostName); // Empty name.
};

let is_disallowed_byte = move |ch: &u16| {
!u16_is_ascii_alphanumeric(ch) && *ch != (b'-' as u16) && *ch != (b'.' as u16)
};
let is_disallowed_seq = move |seq: &[u16]| seq == DOT_DOT || seq == DOT_DASH || seq == DASH_DOT;

if !u16_is_ascii_alphanumeric(first_byte)
|| !u16_is_ascii_alphanumeric(last_byte)
|| host_name.iter().any(is_disallowed_byte)
|| host_name.windows(2).any(is_disallowed_seq)
{
Err(HostNameError::InvalidHostName)
} else {
Ok(())
}
}
67 changes: 67 additions & 0 deletions src/uu/hostname/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use std::fmt;

use uucore::error::UError;

#[derive(Debug, PartialEq, Eq)]
pub enum HostNameError {
InvalidHostName,
HostNameTooLong,
NoLocalDomainName,
SetHostNameDenied,
#[cfg(not(target_family = "windows"))]
GetNameOrAddrInfo(GetNameOrAddrInfoError),
}

impl fmt::Display for HostNameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidHostName => write!(f, "the specified hostname is invalid"),
Self::HostNameTooLong => write!(f, "name too long"),
Self::NoLocalDomainName => write!(f, "local domain name not set"),
Self::SetHostNameDenied => write!(f, "you must be root to change the host name"),
#[cfg(not(target_family = "windows"))]
Self::GetNameOrAddrInfo(r) => write!(f, "{r}"),
}
}
}

impl UError for HostNameError {
fn code(&self) -> i32 {
1
}

fn usage(&self) -> bool {
false
}
}

impl std::error::Error for HostNameError {}

#[cfg(not(target_family = "windows"))]
#[derive(Debug, PartialEq, Eq)]
pub struct GetNameOrAddrInfoError(pub(crate) std::ffi::c_int);

#[cfg(not(target_family = "windows"))]
impl fmt::Display for GetNameOrAddrInfoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let message = unsafe { libc::gai_strerror(self.0) };
if message.is_null() {
write!(f, "domain name resolution failed: error {}", self.0)
} else {
let message = unsafe { std::ffi::CStr::from_ptr(message.cast()) };
if let Ok(message) = message.to_str() {
write!(f, "{message}")
} else {
write!(f, "{message:?}")
}
}
}
}

#[cfg(not(target_family = "windows"))]
impl std::error::Error for GetNameOrAddrInfoError {}
64 changes: 43 additions & 21 deletions src/uu/hostname/src/hostname.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use std::ffi::OsString;
mod change;
mod errors;
mod net;
mod print;
mod utils;

use clap::{crate_version, Arg, ArgAction, ArgGroup, ArgMatches, Command};
use std::ffi::OsString;
use std::path::PathBuf;

use clap::{crate_version, value_parser, Arg, ArgAction, ArgGroup, Command};
use uucore::{error::UResult, format_usage, help_about, help_usage};

const ABOUT: &str = help_about!("hostname.md");
Expand All @@ -29,29 +35,45 @@ pub mod options {
pub static YP: &str = "yp";
}

/// Retrieve and display the host name.
fn query_host_name(args: ArgMatches) -> UResult<()> {
if args.args_present() {
todo!()
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = uu_app().try_get_matches_from(args)?;

let hostname = hostname::get().unwrap_or(OsString::from(""));
let hostname = hostname.to_string_lossy();
println!("{}", hostname);
Ok(())
}
let _net_lib_guard = net::LibraryGuard::load()?;

fn set_host_name(_args: ArgMatches) -> UResult<()> {
todo!()
}
if args.contains_id("set-group") {
if let Some(path) = args.get_one::<PathBuf>(options::FILE) {
change::from_file(path)
} else {
let host_name = args
.get_one::<OsString>(options::HOSTNAME)
.expect("hostname must be specified");

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
change::from_argument(host_name)
}
} else {
let host_name: &mut dyn print::PrintHostName = if args.get_flag(options::ALIAS) {
&mut print::AliasHostName
} else if args.get_flag(options::DOMAIN) {
&mut print::DomainHostName
} else if args.get_flag(options::FQDN) {
&mut print::FqdnHostName
} else if args.get_flag(options::ALL_FQDNS) {
&mut print::AllFqdnHostName
} else if args.get_flag(options::IP_ADDRESS) {
&mut print::IpAddressHostName
} else if args.get_flag(options::ALL_IP_ADDRESSES) {
&mut print::AllIpAddressesHostName
} else if args.get_flag(options::SHORT) {
&mut print::ShortHostName
} else if args.get_flag(options::NIS) {
&mut print::NisHostName
} else {
&mut print::DefaultHostName
};

match (matches.args_present(), matches.contains_id("set-group")) {
(false, _) | (true, false) => query_host_name(matches),
(true, true) => set_host_name(matches),
let mut stdout = std::io::stdout();
host_name.print_host_name(&mut stdout)
}
}

Expand Down
Loading

0 comments on commit 7cd7bbf

Please sign in to comment.