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 20, 2025
1 parent 88bd47b commit 47f165b
Show file tree
Hide file tree
Showing 8 changed files with 851 additions and 156 deletions.
294 changes: 162 additions & 132 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ xattr = "1.3.1"
tempfile = "3.10.1"
rand = { version = "0.9.0", features = ["small_rng"] }
utmpx = "0.2"
hostname = "0.4"

[dependencies]
clap = { workspace = true }
Expand Down
1 change: 0 additions & 1 deletion src/uu/hostname/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ uucore = { workspace = true }
clap = { workspace = true }
libc = { workspace = true }
errno = { workspace = true }
hostname = { workspace = true }

[lib]
path = "src/hostname.rs"
Expand Down
112 changes: 112 additions & 0 deletions src/uu/hostname/src/change.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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::io::{BufRead, BufReader, Read};
use std::path::Path;

use uucore::error::UResult;

use crate::errors::HostNameError;
use crate::utils::bytes_from_os_str;

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<()> {
run(bytes_from_os_str(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)?;

if unsafe { libc::sethostname(host_name.as_ptr(), host_name.count_bytes()) } == -1 {
let err = std::io::Error::last_os_error();
match err.kind() {
std::io::ErrorKind::PermissionDenied => Err(Box::new(HostNameError::SetHostNameDenied)),
std::io::ErrorKind::InvalidInput => Err(Box::new(HostNameError::HostNameTooLong)),
_ => Err(err.into()),
}
} else {
Ok(())
}
}

fn parse_host_name_file(path: &Path) -> UResult<Vec<u8>> {
let mut file = std::fs::File::open(path).map(BufReader::new)?;

let first_byte = loop {
let mut first_byte = [0_u8; 1];
if let Err(err) = file.read_exact(&mut first_byte) {
if err.kind() == std::io::ErrorKind::UnexpectedEof {
return Ok(Vec::default()); // Empty name.
}
}

match first_byte[0] {
b'\r' | b'\n' => {} // Empty line. Skip.

b'#' => {
file.skip_until(b'\n')?; // Comment line. Skip.
}

first_byte => break first_byte,
}
};

let mut buffer = Vec::with_capacity(256);
buffer.push(first_byte);
file.read_until(b'\n', &mut buffer)?;
while matches!(buffer.last().copied(), Some(b'\r' | b'\n')) {
buffer.pop();
}
Ok(buffer)
}

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) })
}
63 changes: 63 additions & 0 deletions src/uu/hostname/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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 core::ffi::{c_int, CStr};
use std::fmt::Display;

use uucore::error::UError;

#[derive(Debug, PartialEq, Eq)]
pub enum HostNameError {
InvalidHostName,
HostNameTooLong,
NoLocalDomainName,
GetNameOrAddrInfo(GetNameOrAddrInfoError),
SetHostNameDenied,
}

impl 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::GetNameOrAddrInfo(r) => write!(f, "{r}"),
Self::SetHostNameDenied => write!(f, "you must be root to change the host name"),
}
}
}

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

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

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

#[derive(Debug, PartialEq, Eq)]
pub struct GetNameOrAddrInfoError(pub(crate) c_int);

impl 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 { CStr::from_ptr(message.cast()) };
if let Ok(message) = message.to_str() {
write!(f, "{message}")
} else {
write!(f, "{message:?}")
}
}
}
}

impl std::error::Error for GetNameOrAddrInfoError {}
63 changes: 41 additions & 22 deletions src/uu/hostname/src/hostname.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
// 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 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 +34,43 @@ 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!()
}

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

fn set_host_name(_args: ArgMatches) -> UResult<()> {
todo!()
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
let args = uu_app().try_get_matches_from(args)?;

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");

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 47f165b

Please sign in to comment.