From bf1ea4e7996357c55fea2bde18daf2a20abd1591 Mon Sep 17 00:00:00 2001 From: Josh Megnauth <jo.sh@tutanota.com> Date: Sat, 2 Nov 2024 12:21:47 +0000 Subject: [PATCH] Set `h_errno` for gethostbyname/addr --- include/bits/netdb.h | 9 +++ src/header/netdb/mod.rs | 118 ++++++++++++++++++++++++++++++++++++---- tests/netdb/netdb.c | 29 ++++++++++ 3 files changed, 146 insertions(+), 10 deletions(-) diff --git a/include/bits/netdb.h b/include/bits/netdb.h index 698e0e216..811097a42 100644 --- a/include/bits/netdb.h +++ b/include/bits/netdb.h @@ -1,6 +1,15 @@ #ifndef _BITS_NETDB_H #define _BITS_NETDB_H +#ifdef __cplusplus +extern "C" { +#endif + +#define h_errno (*__h_errno_location()) # define h_addr h_addr_list[0] /* Address, for backward compatibility.*/ +#ifdef __cplusplus +} // extern "C" +#endif + #endif /* _BITS_NETDB_H */ diff --git a/src/header/netdb/mod.rs b/src/header/netdb/mod.rs index d5ee94a52..c01579af9 100644 --- a/src/header/netdb/mod.rs +++ b/src/header/netdb/mod.rs @@ -3,6 +3,8 @@ mod dns; use core::{ + cell::Cell, + fmt::Write, mem, ptr, slice, str::{self, FromStr}, }; @@ -132,14 +134,18 @@ pub static mut NET_ADDR: Option<u32> = None; static mut N_POS: usize = 0; static mut NET_STAYOPEN: c_int = 0; -#[allow(non_upper_case_globals)] -#[no_mangle] -pub static mut h_errno: c_int = 0; +#[thread_local] +pub static H_ERRNO: Cell<c_int> = Cell::new(0); +const H_UNSET: c_int = 0; pub const HOST_NOT_FOUND: c_int = 1; pub const NO_DATA: c_int = 2; pub const NO_RECOVERY: c_int = 3; pub const TRY_AGAIN: c_int = 4; +// Expected length of addresses +const SOCKLEN_AF_INET4: socklen_t = 4; +const SOCKLEN_AF_INET6: socklen_t = 16; + static mut PROTODB: c_int = 0; static mut PROTO_ENTRY: protoent = protoent { p_name: ptr::null_mut(), @@ -188,12 +194,44 @@ pub unsafe extern "C" fn endservent() { SERVDB = 0; } +/// Resolve a host name from a given network address. +/// +/// # Arguments +/// * `v` - Address to resolve as a non-null [`in_addr`] +/// * `length` - +/// * `format` - AF_INET or AF_INET6 +/// +/// # Safety +/// * `v` must be a valid pointer. +/// * `length` must correctly match the size of `v` as expected by `format` (usually 4 or 16). +/// * This function is not reentrant and may modify static data. +/// +/// # Panics +/// Panics if `v` is a null pointer. +/// +/// # Deprecation +/// Deprecated as of POSIX.1-2001 and removed in POSIX.1-2008. +/// New code should use [`getaddrinfo`] instead. #[no_mangle] +#[deprecated] pub unsafe extern "C" fn gethostbyaddr( v: *const c_void, length: socklen_t, format: c_int, ) -> *mut hostent { + assert!( + !v.is_null(), + "`gethostbyaddr()` called with null `v` (in_addr)" + ); + // Uncomment if optional IPv6 support is added + // if length != SOCKLEN_AF_INET4 || length != SOCKLEN_AF_INET6 { + // H_ERRNO.set(NO_RECOVERY); + // return ptr::null_mut(); + // } + if length != SOCKLEN_AF_INET4 { + H_ERRNO.set(NO_RECOVERY); + return ptr::null_mut(); + } let addr: in_addr = *(v as *mut in_addr); // check the hosts file first @@ -246,24 +284,39 @@ pub unsafe extern "C" fn gethostbyaddr( // `glibc` sets errno if an address doesn't have a host name // `musl` uses the address as the host name in said case Ok(None) => { - // TODO: Set h_errno instead to match spec - platform::ERRNO.set(EIO); + H_ERRNO.set(HOST_NOT_FOUND); ptr::null_mut() } Err(e) => { - platform::ERRNO.set(e); + // TODO: Better error separation in lookup_addr + H_ERRNO.set(NO_RECOVERY); ptr::null_mut() } } } +/// Resolve host information by name or IP address. +/// +/// # Arguments +/// * `name` - Host name or IP address. +/// +/// # Safety +/// `name` must be a valid string. +/// This function is not reentrant and may modify static data. +/// +/// # Panics +/// Panics if `name` is a null pointer. +/// +/// # Deprecation +/// Deprecated as of POSIX.1-2001 and removed in POSIX.1-2008. +/// New code should use [`getaddrinfo`] instead. #[no_mangle] +#[deprecated] pub unsafe extern "C" fn gethostbyname(name: *const c_char) -> *mut hostent { let name_cstr = CStr::from_nullable_ptr(name).expect("gethostbyname() called with a NULL pointer"); let Ok(name_str) = str::from_utf8(name_cstr.to_bytes()) else { - // TODO Set h_errno instead of errno (spec) - platform::ERRNO.set(EINVAL); + H_ERRNO.set(NO_RECOVERY); return ptr::null_mut(); }; @@ -321,14 +374,14 @@ pub unsafe extern "C" fn gethostbyname(name: *const c_char) -> *mut hostent { let mut host = match lookup_host(name_str) { Ok(lookuphost) => lookuphost, Err(e) => { - platform::ERRNO.set(e); + H_ERRNO.set(NO_RECOVERY); return ptr::null_mut(); } }; let host_addr = match host.next() { Some(result) => result, None => { - platform::ERRNO.set(ENOENT); + H_ERRNO.set(HOST_NOT_FOUND); return ptr::null_mut(); } }; @@ -902,9 +955,18 @@ pub extern "C" fn gai_strerror(errcode: c_int) -> *const c_char { .as_ptr() } +/// Provide a pointer to relibc's internal [`H_ERRNO`]. +#[no_mangle] +#[deprecated] +pub extern "C" fn __h_errno_location() -> *mut c_int { + H_ERRNO.as_ptr() +} + #[no_mangle] +#[deprecated] pub extern "C" fn hstrerror(errcode: c_int) -> *const c_char { match errcode { + H_UNSET => c_str!("Resolver error unset"), HOST_NOT_FOUND => c_str!("Unknown hostname"), NO_DATA => c_str!("No address for hostname"), NO_RECOVERY => c_str!("Unknown server error"), @@ -913,3 +975,39 @@ pub extern "C" fn hstrerror(errcode: c_int) -> *const c_char { } .as_ptr() } + +/// Print error message associated with [`H_ERRNO`] to stderr. +/// +/// # Arguments +/// * `prefix` - An optional prefix to prepend to the error message. May be null or an empty +/// (`""`) C string. +/// +/// # Safety +/// Like [`crate::header::stdio::perror`], `prefix` should be a valid, NUL terminated C string if +/// used. The caller may safely call this function with a null pointer. +/// +/// # Deprecation +/// [`H_ERRNO`], [`hstrerror`], [`herror`], and other functions are deprecated as of +/// POSIX.1-2001 and removed as of POSIX.1-2008. These functions are provided for backwards +/// compatibility but should not be used by new code. +#[no_mangle] +#[deprecated] +pub extern "C" fn herror(prefix: *const c_char) { + let code = H_ERRNO.get(); + // Safety: `hstrerror` handles every error code case and always returns a valid C string + let error = unsafe { + let msg_cstr = CStr::from_ptr(hstrerror(code)); + str::from_utf8_unchecked(msg_cstr.to_bytes()) + }; + + let mut writer = platform::FileWriter(2); + // Prefix is optional + match unsafe { CStr::from_nullable_ptr(prefix) } + .and_then(|prefix| str::from_utf8(prefix.to_bytes()).ok()) + { + Some(prefix) if !prefix.is_empty() => writer + .write_fmt(format_args!("{prefix}: {error}\n")) + .unwrap(), + _ => writer.write_fmt(format_args!("{error}\n")).unwrap(), + } +} diff --git a/tests/netdb/netdb.c b/tests/netdb/netdb.c index d675fc010..2ccf70ec0 100644 --- a/tests/netdb/netdb.c +++ b/tests/netdb/netdb.c @@ -26,6 +26,7 @@ #include <netdb.h> #include <stdio.h> +#include <stdint.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> @@ -37,6 +38,8 @@ #include "test_helpers.h" +#define ADDR_SIZE sizeof(uint8_t) * 4 + int error_count; static void output_servent (const char *call, struct servent *sptr) @@ -229,12 +232,38 @@ test_network (void) while (nptr != NULL); setnetent (0); } +static void +test_h_errno (void) { + const uint8_t addr[] = {0, 0, 0, 0}; + struct hostent *host = gethostbyaddr(addr, ADDR_SIZE, AF_INET); + if (host) { + ++error_count; + } + + int err = h_errno; + herror("Prefix test"); + if (err != HOST_NOT_FOUND) { + ++error_count; + } + + host = gethostbyname(""); + if (host) { + ++error_count; + } + + err = h_errno; + herror(""); + if (err != HOST_NOT_FOUND) { + ++error_count; + } +} static int do_test (void) { /* setdb ("db"); */ + test_h_errno (); test_hosts (); test_network (); test_protocols (); -- GitLab