diff --git a/include/boost/corosio/host_name.hpp b/include/boost/corosio/host_name.hpp new file mode 100644 index 00000000..d3372b46 --- /dev/null +++ b/include/boost/corosio/host_name.hpp @@ -0,0 +1,48 @@ +// +// Copyright (c) 2026 Steve Gerbino +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +#ifndef BOOST_COROSIO_HOST_NAME_HPP +#define BOOST_COROSIO_HOST_NAME_HPP + +#include + +#include + +namespace boost::corosio { + +/** Return the local machine's hostname. + + On POSIX systems this calls `gethostname(2)`. On Windows this + calls `GetComputerNameExW(ComputerNameDnsHostname, ...)` and + converts the result from UTF-16 to UTF-8. + + The function is synchronous and does not require an + `io_context`. On Windows it does not require winsock to have + been initialized. + + @par Exception Safety + Strong guarantee. + + @par Example + @code + std::string h = boost::corosio::host_name(); + std::cout << "running on " << h << "\n"; + @endcode + + @return The hostname as a UTF-8 string. + + @throws std::runtime_error If the underlying system call fails. +*/ +BOOST_COROSIO_DECL +std::string +host_name(); + +} // namespace boost::corosio + +#endif diff --git a/src/corosio/src/host_name.cpp b/src/corosio/src/host_name.cpp new file mode 100644 index 00000000..5c6b6e80 --- /dev/null +++ b/src/corosio/src/host_name.cpp @@ -0,0 +1,108 @@ +// +// Copyright (c) 2026 Steve Gerbino +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +#include +#include + +#include +#include + +#if BOOST_COROSIO_POSIX +#include +#include +#include +#elif BOOST_COROSIO_HAS_IOCP +#include +#endif + +namespace boost::corosio { + +#if BOOST_COROSIO_POSIX + +std::string +host_name() +{ + // 256 exceeds POSIX's _POSIX_HOST_NAME_MAX floor of 255 and + // every mainstream OS's actual cap (Linux 64, macOS/BSD 255). + char buf[256]; + if (::gethostname(buf, sizeof(buf)) != 0) + { + int e = errno; + throw std::runtime_error( + std::string("gethostname failed: ") + std::strerror(e)); + } + + // POSIX does not guarantee NUL termination on truncation. + if (std::memchr(buf, '\0', sizeof(buf)) == nullptr) + throw std::runtime_error("gethostname: hostname truncated"); + + return std::string(buf); +} + +#elif BOOST_COROSIO_HAS_IOCP + +std::string +host_name() +{ + // Size query: returns ERROR_MORE_DATA and writes the required + // wide-char count (including the trailing NUL) into `size`. + DWORD size = 0; + BOOL ok = ::GetComputerNameExW( + ComputerNameDnsHostname, nullptr, &size); + DWORD err = ::GetLastError(); + if (ok) + { + throw std::runtime_error( + "GetComputerNameExW (size query) unexpectedly succeeded"); + } + if (err != ERROR_MORE_DATA) + { + throw std::runtime_error( + "GetComputerNameExW (size query) failed: error " + + std::to_string(err)); + } + + // On success, GetComputerNameExW rewrites `size` to the count + // without the NUL, so resize(size) below trims to the hostname. + std::wstring wide(size, L'\0'); + if (!::GetComputerNameExW( + ComputerNameDnsHostname, wide.data(), &size)) + { + throw std::runtime_error( + "GetComputerNameExW failed: error " + + std::to_string(::GetLastError())); + } + wide.resize(size); + + int needed = ::WideCharToMultiByte( + CP_UTF8, 0, wide.data(), static_cast(wide.size()), + nullptr, 0, nullptr, nullptr); + if (needed <= 0) + { + throw std::runtime_error( + "WideCharToMultiByte (size query) failed: error " + + std::to_string(::GetLastError())); + } + + std::string out(static_cast(needed), '\0'); + int written = ::WideCharToMultiByte( + CP_UTF8, 0, wide.data(), static_cast(wide.size()), + out.data(), needed, nullptr, nullptr); + if (written != needed) + { + throw std::runtime_error( + "WideCharToMultiByte failed: error " + + std::to_string(::GetLastError())); + } + return out; +} + +#endif + +} // namespace boost::corosio diff --git a/test/unit/host_name.cpp b/test/unit/host_name.cpp new file mode 100644 index 00000000..c996b0b9 --- /dev/null +++ b/test/unit/host_name.cpp @@ -0,0 +1,80 @@ +// +// Copyright (c) 2026 Steve Gerbino +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Test that header file is self-contained. +#include + +#include + +#include "test_suite.hpp" + +namespace boost::corosio { + +struct host_name_test +{ + // Every configured machine has a hostname. + void testReturnsNonEmpty() + { + std::string h = host_name(); + BOOST_TEST(!h.empty()); + } + + // Catches buffer or string-lifetime bugs across calls. + void testStable() + { + std::string a = host_name(); + std::string b = host_name(); + BOOST_TEST_EQ(a, b); + } + + // 255 is the DNS hostname ceiling; anything longer is garbage + // from a miscounted buffer. + void testReasonableLength() + { + std::string h = host_name(); + BOOST_TEST(h.size() > 0); + BOOST_TEST(h.size() <= 255); + } + + // Regression guard for the Windows implementation choice: a + // switch to winsock gethostname() would fail here because + // corosio's WSAStartup is lazy (inside io_context). + void testNoIoContextNeeded() + { + std::string h = host_name(); + BOOST_TEST(!h.empty()); + } + + // Catches encoding regressions, especially the Windows + // UTF-16 -> UTF-8 conversion. Non-ASCII hostnames are valid, so + // accept any printable ASCII byte or high-bit byte. + void testCharsetSanity() + { + std::string h = host_name(); + for (unsigned char c : h) + { + bool printable_ascii = (c >= 0x20 && c <= 0x7E); + bool high_bit = (c >= 0x80); + BOOST_TEST(printable_ascii || high_bit); + } + } + + void run() + { + testReturnsNonEmpty(); + testStable(); + testReasonableLength(); + testNoIoContextNeeded(); + testCharsetSanity(); + } +}; + +TEST_SUITE(host_name_test, "boost.corosio.host_name"); + +} // namespace boost::corosio