Skip to content

Commit 0e568b9

Browse files
committed
feat(host_name): add free function returning the local hostname
Adds boost::corosio::host_name(), a synchronous free function returning the local machine's hostname as a UTF-8 std::string. Common uses: logging, default Host: headers, cluster registration, identifying a node. A single overload that throws std::runtime_error on failure. No std::error_code variant: failures of the underlying syscalls are pathological on a configured machine, and the throwing form is sufficient. POSIX path uses gethostname(2) with a 256-byte stack buffer, which comfortably exceeds the _POSIX_HOST_NAME_MAX floor of 255 and every mainstream OS's actual cap. errno is captured immediately on failure. POSIX does not guarantee NUL termination on truncation, so the implementation checks for a NUL anywhere in the buffer and throws otherwise. Windows path uses GetComputerNameExW(ComputerNameDnsHostname, ...) followed by WideCharToMultiByte(CP_UTF8, ...) to produce UTF-8. Chosen over winsock gethostname() so the function works without WSAStartup(); in corosio winsock is initialized lazily inside io_context, so a winsock-based implementation would tie host_name() to io_context lifetime, contradicting the "no io_context needed" design goal. Adds five unit tests covering: non-empty result, stability across calls, reasonable length (<= 255 octets), no-io_context-needed (load-bearing regression guard for the Windows implementation choice), and charset sanity (catches UTF-16 -> UTF-8 regressions).
1 parent 550d2ba commit 0e568b9

3 files changed

Lines changed: 236 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// Copyright (c) 2026 Steve Gerbino
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/corosio
8+
//
9+
10+
#ifndef BOOST_COROSIO_HOST_NAME_HPP
11+
#define BOOST_COROSIO_HOST_NAME_HPP
12+
13+
#include <boost/corosio/detail/config.hpp>
14+
15+
#include <string>
16+
17+
namespace boost::corosio {
18+
19+
/** Return the local machine's hostname.
20+
21+
On POSIX systems this calls `gethostname(2)`. On Windows this
22+
calls `GetComputerNameExW(ComputerNameDnsHostname, ...)` and
23+
converts the result from UTF-16 to UTF-8.
24+
25+
The function is synchronous and does not require an
26+
`io_context`. On Windows it does not require winsock to have
27+
been initialized.
28+
29+
@par Exception Safety
30+
Strong guarantee.
31+
32+
@par Example
33+
@code
34+
std::string h = boost::corosio::host_name();
35+
std::cout << "running on " << h << "\n";
36+
@endcode
37+
38+
@return The hostname as a UTF-8 string.
39+
40+
@throws std::runtime_error If the underlying system call fails.
41+
*/
42+
BOOST_COROSIO_DECL
43+
std::string
44+
host_name();
45+
46+
} // namespace boost::corosio
47+
48+
#endif

src/corosio/src/host_name.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//
2+
// Copyright (c) 2026 Steve Gerbino
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/corosio
8+
//
9+
10+
#include <boost/corosio/host_name.hpp>
11+
#include <boost/corosio/detail/platform.hpp>
12+
13+
#include <stdexcept>
14+
#include <string>
15+
16+
#if BOOST_COROSIO_POSIX
17+
#include <cerrno>
18+
#include <cstring>
19+
#include <unistd.h>
20+
#elif BOOST_COROSIO_HAS_IOCP
21+
#include <windows.h>
22+
#endif
23+
24+
namespace boost::corosio {
25+
26+
#if BOOST_COROSIO_POSIX
27+
28+
std::string
29+
host_name()
30+
{
31+
// 256 exceeds POSIX's _POSIX_HOST_NAME_MAX floor of 255 and
32+
// every mainstream OS's actual cap (Linux 64, macOS/BSD 255).
33+
char buf[256];
34+
if (::gethostname(buf, sizeof(buf)) != 0)
35+
{
36+
int e = errno;
37+
throw std::runtime_error(
38+
std::string("gethostname failed: ") + std::strerror(e));
39+
}
40+
41+
// POSIX does not guarantee NUL termination on truncation.
42+
if (std::memchr(buf, '\0', sizeof(buf)) == nullptr)
43+
throw std::runtime_error("gethostname: hostname truncated");
44+
45+
return std::string(buf);
46+
}
47+
48+
#elif BOOST_COROSIO_HAS_IOCP
49+
50+
std::string
51+
host_name()
52+
{
53+
// Size query: returns ERROR_MORE_DATA and writes the required
54+
// wide-char count (including the trailing NUL) into `size`.
55+
DWORD size = 0;
56+
BOOL ok = ::GetComputerNameExW(
57+
ComputerNameDnsHostname, nullptr, &size);
58+
DWORD err = ::GetLastError();
59+
if (ok)
60+
{
61+
throw std::runtime_error(
62+
"GetComputerNameExW (size query) unexpectedly succeeded");
63+
}
64+
if (err != ERROR_MORE_DATA)
65+
{
66+
throw std::runtime_error(
67+
"GetComputerNameExW (size query) failed: error " +
68+
std::to_string(err));
69+
}
70+
71+
// On success, GetComputerNameExW rewrites `size` to the count
72+
// without the NUL, so resize(size) below trims to the hostname.
73+
std::wstring wide(size, L'\0');
74+
if (!::GetComputerNameExW(
75+
ComputerNameDnsHostname, wide.data(), &size))
76+
{
77+
throw std::runtime_error(
78+
"GetComputerNameExW failed: error " +
79+
std::to_string(::GetLastError()));
80+
}
81+
wide.resize(size);
82+
83+
int needed = ::WideCharToMultiByte(
84+
CP_UTF8, 0, wide.data(), static_cast<int>(wide.size()),
85+
nullptr, 0, nullptr, nullptr);
86+
if (needed <= 0)
87+
{
88+
throw std::runtime_error(
89+
"WideCharToMultiByte (size query) failed: error " +
90+
std::to_string(::GetLastError()));
91+
}
92+
93+
std::string out(static_cast<std::size_t>(needed), '\0');
94+
int written = ::WideCharToMultiByte(
95+
CP_UTF8, 0, wide.data(), static_cast<int>(wide.size()),
96+
out.data(), needed, nullptr, nullptr);
97+
if (written != needed)
98+
{
99+
throw std::runtime_error(
100+
"WideCharToMultiByte failed: error " +
101+
std::to_string(::GetLastError()));
102+
}
103+
return out;
104+
}
105+
106+
#endif
107+
108+
} // namespace boost::corosio

test/unit/host_name.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// Copyright (c) 2026 Steve Gerbino
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/corosio
8+
//
9+
10+
// Test that header file is self-contained.
11+
#include <boost/corosio/host_name.hpp>
12+
13+
#include <string>
14+
15+
#include "test_suite.hpp"
16+
17+
namespace boost::corosio {
18+
19+
struct host_name_test
20+
{
21+
// Every configured machine has a hostname.
22+
void testReturnsNonEmpty()
23+
{
24+
std::string h = host_name();
25+
BOOST_TEST(!h.empty());
26+
}
27+
28+
// Catches buffer or string-lifetime bugs across calls.
29+
void testStable()
30+
{
31+
std::string a = host_name();
32+
std::string b = host_name();
33+
BOOST_TEST_EQ(a, b);
34+
}
35+
36+
// 255 is the DNS hostname ceiling; anything longer is garbage
37+
// from a miscounted buffer.
38+
void testReasonableLength()
39+
{
40+
std::string h = host_name();
41+
BOOST_TEST(h.size() > 0);
42+
BOOST_TEST(h.size() <= 255);
43+
}
44+
45+
// Regression guard for the Windows implementation choice: a
46+
// switch to winsock gethostname() would fail here because
47+
// corosio's WSAStartup is lazy (inside io_context).
48+
void testNoIoContextNeeded()
49+
{
50+
std::string h = host_name();
51+
BOOST_TEST(!h.empty());
52+
}
53+
54+
// Catches encoding regressions, especially the Windows
55+
// UTF-16 -> UTF-8 conversion. Non-ASCII hostnames are valid, so
56+
// accept any printable ASCII byte or high-bit byte.
57+
void testCharsetSanity()
58+
{
59+
std::string h = host_name();
60+
for (unsigned char c : h)
61+
{
62+
bool printable_ascii = (c >= 0x20 && c <= 0x7E);
63+
bool high_bit = (c >= 0x80);
64+
BOOST_TEST(printable_ascii || high_bit);
65+
}
66+
}
67+
68+
void run()
69+
{
70+
testReturnsNonEmpty();
71+
testStable();
72+
testReasonableLength();
73+
testNoIoContextNeeded();
74+
testCharsetSanity();
75+
}
76+
};
77+
78+
TEST_SUITE(host_name_test, "boost.corosio.host_name");
79+
80+
} // namespace boost::corosio

0 commit comments

Comments
 (0)