Skip to content

Commit 1f6fcae

Browse files
committed
Reach the network at run time: its names are the C library's names
⚠️⚠️ THIS SYSTEM'S NETWORK LIBRARY EXPORTS THE BSD NAMES, AND SO DOES THE C LIBRARY ABOVE THIS IMPLEMENTATION. openkal-musl compiles musl's own `src/network/*.c', which define `bind', `listen', `accept' and `connect' and route them through this port. Naming `-lws2_32' on the link line put both definitions in one program: ld.exe: libws2_32.a(libws2_32s00165.o): multiple definition of `connect'; musl/src/network/connect.o: first defined here Measured on the GNU/PE row of the C library's own continuous integration, on the first run of this change. It is not an ordering problem: an import library's member defines the thunk AND the `__imp_' pointer together, so reaching for either brings both. ⭐ THE NAMES ARE THEREFORE REACHED THROUGH THE SYSTEM'S OWN LOADER --- `LoadLibraryW' and `GetProcAddress', both of which this package already links --- into a table resolved once. Nothing of ws2_32 enters the program's symbol table, so the C library above keeps its `bind' and this implementation still reaches the system's. `port/ws2_32.def', the `-lws2_32' flag and the `#pragma comment' all come out. The table is resolved ENTIRELY OR NOT AT ALL: a table with one null entry would make the operations that resolved work and the one that did not call through zero, which is the failure clause 6.1 exists to turn into a link error and this arrangement cannot. Measured here after the change: `examples/cross-hello' links and produces `cross-hello.exe' over this implementation and that C library; 16 objects; exported surface complete and conforming at 88 names; no undefined symbol outside the permitted set; and NO BSD NAME DEFINED in this package's own objects, which is the property the collision was about.
1 parent 0fea697 commit 1f6fcae

10 files changed

Lines changed: 208 additions & 133 deletions

File tree

build.mcpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ int main() {
117117
mcpp::rerun_if_changed("port/shell32.def");
118118
mcpp::rerun_if_changed("port/synchronization.def");
119119
mcpp::rerun_if_changed("port/bcrypt.def");
120-
mcpp::rerun_if_changed("port/ws2_32.def");
121120

122121
if (host_is_windows()) return 0;
123122

@@ -140,7 +139,7 @@ int main() {
140139
const std::string tool = dlltool();
141140

142141
for (auto name : { "kernel32", "ntdll", "shell32", "synchronization",
143-
"bcrypt", "ws2_32" }) {
142+
"bcrypt" }) {
144143
const auto def = std::format("{}/port/{}.def", root, name);
145144
const auto lib = std::format("{}/lib{}.a", out, name);
146145
// ⚠️ `-m i386:x86-64` is stated. See port/README.md: the 32-bit ABI

mcpp.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ openkal = "0.8.0"
4949
# predicate the second one linked with none of these libraries and failed on
5050
# `GetStdHandle`.
5151
[target.'cfg(all(windows, not(env = "msvc")))'.build]
52-
ldflags = ["-lntdll", "-lsynchronization", "-lshell32", "-lkernel32", "-lbcrypt",
53-
"-lws2_32"]
52+
ldflags = ["-lntdll", "-lsynchronization", "-lshell32", "-lkernel32", "-lbcrypt"]
5453

5554
# Exceptions and run-time type information, on the one ABI where their absence
5655
# is asserted.

port/kernel32.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ GetFileType
2323
GetFinalPathNameByHandleW
2424
GetLastError
2525
GetLogicalDriveStringsW
26+
GetProcAddress
2627
GetProcessHeap
2728
GetStdHandle
2829
GetSystemTimePreciseAsFileTime
2930
HeapAlloc
3031
HeapFree
32+
LoadLibraryW
3133
LocalFree
3234
MultiByteToWideChar
3335
QueryPerformanceCounter

port/ws2_32.def

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/datagram.cpp

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ namespace {
1414

1515
SOCKET socket_of(kal_datagram d) { return okw::unpack_socket(d.h); }
1616

17+
okw::network_calls* net() { return okw::net_or_null(); }
18+
1719
bool bad(SOCKET s) { return s == INVALID_SOCKET; }
1820

1921
// The largest transfer one call accepts. This system states a count as an
@@ -38,26 +40,27 @@ int kal_datagram_open(const kal_endpoint* local, kal_datagram* out) {
3840
if (family < 0) return kal_err_invalid;
3941
}
4042

41-
okw::ensure_network();
42-
const SOCKET s = WSASocketW(family, SOCK_DGRAM_, IPPROTO_UDP_, nullptr, 0, 0);
43+
auto* n = net();
44+
if (n == nullptr) return kal_err_io;
45+
const SOCKET s = n->socket(family, SOCK_DGRAM_, IPPROTO_UDP_, nullptr, 0, 0);
4346
if (bad(s)) return okw::last_socket_error();
4447

4548
if (local != nullptr) {
4649
ksockaddr_storage ss{};
4750
int len = 0;
4851
if (const int rc = okw::to_system(*local, ss, len); rc != kal_ok) {
49-
closesocket(s);
52+
n->close(s);
5053
return rc;
5154
}
52-
if (bind(s, &ss, len) != 0) {
55+
if (n->bind(s, &ss, len) != 0) {
5356
const int e = okw::last_socket_error();
54-
closesocket(s);
57+
n->close(s);
5558
return e;
5659
}
5760
}
5861

5962
out->h = okw::pack_socket(s);
60-
if (out->h == 0) { closesocket(s); return kal_err_no_memory; }
63+
if (out->h == 0) { n->close(s); return kal_err_no_memory; }
6164
return kal_ok;
6265
}
6366

@@ -66,9 +69,11 @@ int kal_datagram_local(kal_datagram d, kal_endpoint* out) {
6669
const SOCKET s = socket_of(d);
6770
if (bad(s)) return kal_err_invalid;
6871

72+
auto* n = net();
73+
if (n == nullptr) return kal_err_io;
6974
ksockaddr_storage ss{};
7075
int len = static_cast<int>(sizeof ss);
71-
if (getsockname(s, &ss, &len) != 0) return okw::last_socket_error();
76+
if (n->sockname(s, &ss, &len) != 0) return okw::last_socket_error();
7277
return okw::from_system(ss, *out);
7378
}
7479

@@ -78,13 +83,15 @@ kal_io_result kal_datagram_send_to(kal_datagram d, const void* buf, kal_uintptr
7883
if (bad(s) || to == nullptr) return { 0, kal_err_invalid };
7984
if (len > kMaxOne) return { 0, kal_err_invalid };
8085

86+
auto* n = net();
87+
if (n == nullptr) return { 0, kal_err_io };
8188
ksockaddr_storage ss{};
8289
int addrlen = 0;
8390
if (const int rc = okw::to_system(*to, ss, addrlen); rc != kal_ok)
8491
return { 0, rc };
8592

86-
const int r = sendto(s, static_cast<const char*>(buf), static_cast<int>(len),
87-
0, &ss, addrlen);
93+
const int r = n->send_to(s, static_cast<const char*>(buf), static_cast<int>(len),
94+
0, &ss, addrlen);
8895
if (r < 0) return { 0, okw::last_socket_error() };
8996

9097
// A MESSAGE IS SENT WHOLE OR NOT AT ALL, which is what this interface
@@ -93,8 +100,8 @@ kal_io_result kal_datagram_send_to(kal_datagram d, const void* buf, kal_uintptr
93100
// it does not do. Reporting the short count as success would give a caller a
94101
// partial send this interface says cannot occur, so it is reported as a
95102
// failure of the medium instead.
96-
const kal_uintptr n = static_cast<kal_uintptr>(r);
97-
return { n, n == len ? kal_ok : kal_err_io };
103+
const kal_uintptr sent = static_cast<kal_uintptr>(r);
104+
return { sent, sent == len ? kal_ok : kal_err_io };
98105
}
99106

100107
kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
@@ -103,11 +110,13 @@ kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
103110
if (bad(s)) return { 0, kal_err_invalid };
104111
if (len > kMaxOne) len = kMaxOne;
105112

113+
auto* n = net();
114+
if (n == nullptr) return { 0, kal_err_io };
106115
ksockaddr_storage ss{};
107116
int addrlen = static_cast<int>(sizeof ss);
108117

109-
const int r = recvfrom(s, static_cast<char*>(buf), static_cast<int>(len),
110-
0, &ss, &addrlen);
118+
const int r = n->recv_from(s, static_cast<char*>(buf), static_cast<int>(len),
119+
0, &ss, &addrlen);
111120
if (r < 0) {
112121
// ⚠️ THE ONE FAILURE THIS SYSTEM REPORTS THAT THE OTHER TWO DO NOT.
113122
//
@@ -122,7 +131,7 @@ kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
122131
//
123132
// The count is not recoverable from this call, so what is reported is
124133
// the whole of the buffer, which is what was filled.
125-
if (WSAGetLastError() == okw::WSAEMSGSIZE) {
134+
if (n->last_error() == okw::WSAEMSGSIZE) {
126135
if (from != nullptr && okw::from_system(ss, *from) != kal_ok) {
127136
for (auto& b : from->addr) b = 0;
128137
from->addr_len = 0;
@@ -149,7 +158,7 @@ kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
149158
void kal_datagram_close(kal_datagram d) {
150159
const SOCKET s = socket_of(d);
151160
if (bad(s)) return;
152-
closesocket(s);
161+
if (auto* n = net()) n->close(s);
153162
okw::retire(d.h);
154163
}
155164

src/endpoint.h

Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -57,32 +57,115 @@ inline int translate_wsa(int e) {
5757
}
5858
}
5959

60-
inline int last_socket_error() { return translate_wsa(WSAGetLastError()); }
61-
62-
// ── starting this system's network interface ────────────────────────────────
60+
// ── reaching this system's network interface ────────────────────────────────
61+
//
62+
// ⚠️⚠️ RESOLVED AT RUN TIME RATHER THAN LINKED, AND src/win32.h RECORDS THE
63+
// MEASUREMENT: this library's names are the BSD names, the C library above this
64+
// implementation defines the same names, and an import library puts both
65+
// definitions in one program. Nothing of ws2_32 enters this program's symbol
66+
// table now.
6367
//
64-
// ⚠️ IT HAS TO BE STARTED, AND THERE IS NO OTHER PLACE TO DO IT. Every socket
65-
// call fails with `WSANOTINITIALISED' until `WSAStartup' has been called in
66-
// this image, and openkal has no operation a program calls first.
68+
// ⚠️ IT ALSO HAS TO BE STARTED. Every socket call fails with
69+
// `WSANOTINITIALISED' until `WSAStartup' has been called in this image, and
70+
// openkal has no operation a program calls first --- so the first operation
71+
// that needs the interface starts it.
6772
//
6873
// ⭐ NOT A FUNCTION-LOCAL STATIC WITH A RUNTIME INITIALISER, AND THE MANIFEST
6974
// SAYS WHY: every static in this package is initialised by a constant, so no
7075
// guard variable is emitted. One with a runtime initialiser would emit a call
7176
// to `__cxa_guard_acquire' --- a C runtime symbol, in the one package whose
7277
// continuous integration asserts it references none.
7378
//
74-
// ⚠️ THE FLAG IS READ AND WRITTEN WITHOUT SYNCHRONISATION, AND THAT IS SAFE
75-
// HERE RATHER THAN OVERLOOKED. Two contexts racing produce a second
76-
// `WSAStartup', which this system reference-counts and which is documented as
77-
// callable more than once. This implementation never calls `WSACleanup' --- the
78-
// interface stays started for the life of the image, which is what a program
79-
// that opened a socket wants --- so the count never reaches zero and the
80-
// duplicate costs nothing.
81-
inline void ensure_network() {
82-
static int started = 0;
83-
if (started) return;
79+
// ⚠️ THE TABLE IS READ AND WRITTEN WITHOUT SYNCHRONISATION, AND THAT IS SAFE
80+
// HERE RATHER THAN OVERLOOKED. Two contexts racing resolve the same pointers
81+
// from the same library to the same values and perform a second `WSAStartup',
82+
// which this system reference-counts and documents as callable more than once.
83+
// This implementation never calls `WSACleanup' --- the interface stays
84+
// available for the life of the image, which is what a program that opened a
85+
// socket wants --- so the count never reaches zero.
86+
struct network_calls {
87+
int ready; // 0 not tried, 1 available, -1 absent
88+
pfn_WSAGetLastError last_error;
89+
pfn_WSASocketW socket;
90+
pfn_closesocket close;
91+
pfn_bind bind;
92+
pfn_listen listen;
93+
pfn_accept accept;
94+
pfn_connect connect;
95+
pfn_shutdown shutdown;
96+
pfn_getsockname sockname;
97+
pfn_getpeername peername;
98+
pfn_sendto send_to;
99+
pfn_recvfrom recv_from;
100+
pfn_WSAPoll poll;
101+
};
102+
103+
inline network_calls& net_calls() {
104+
static network_calls c = {}; // constant-initialised: no guard is emitted
105+
return c;
106+
}
107+
108+
// ⚠️ THE LIBRARY'S NAME IS WRITTEN AS WIDE CHARACTERS BY HAND. This package has
109+
// no C library to take a literal converter from, and `L"ws2_32.dll"' is the
110+
// language's own; it is spelled out so that no header is needed for it.
111+
inline bool ensure_network() {
112+
network_calls& c = net_calls();
113+
if (c.ready != 0) return c.ready > 0;
114+
115+
static const wchar_t name[] = { L'w', L's', L'2', L'_', L'3', L'2', L'.',
116+
L'd', L'l', L'l', L'\0' };
117+
HANDLE lib = LoadLibraryW(name);
118+
if (lib == nullptr) { c.ready = -1; return false; }
119+
120+
auto at = [lib](const char* n) { return GetProcAddress(lib, n); };
121+
auto start = reinterpret_cast<pfn_WSAStartup>(at("WSAStartup"));
122+
c.last_error = reinterpret_cast<pfn_WSAGetLastError>(at("WSAGetLastError"));
123+
c.socket = reinterpret_cast<pfn_WSASocketW>(at("WSASocketW"));
124+
c.close = reinterpret_cast<pfn_closesocket>(at("closesocket"));
125+
c.bind = reinterpret_cast<pfn_bind>(at("bind"));
126+
c.listen = reinterpret_cast<pfn_listen>(at("listen"));
127+
c.accept = reinterpret_cast<pfn_accept>(at("accept"));
128+
c.connect = reinterpret_cast<pfn_connect>(at("connect"));
129+
c.shutdown = reinterpret_cast<pfn_shutdown>(at("shutdown"));
130+
c.sockname = reinterpret_cast<pfn_getsockname>(at("getsockname"));
131+
c.peername = reinterpret_cast<pfn_getpeername>(at("getpeername"));
132+
c.send_to = reinterpret_cast<pfn_sendto>(at("sendto"));
133+
c.recv_from = reinterpret_cast<pfn_recvfrom>(at("recvfrom"));
134+
c.poll = reinterpret_cast<pfn_WSAPoll>(at("WSAPoll"));
135+
136+
// ⚠️ EVERY ONE OF THEM, OR NONE. A table with one null entry is worse than
137+
// no table: the operations that resolved would work and the one that did
138+
// not would call through zero, which is the failure clause 6.1 exists to
139+
// turn into a link error and this arrangement cannot.
140+
if (!start || !c.last_error || !c.socket || !c.close || !c.bind ||
141+
!c.listen || !c.accept || !c.connect || !c.shutdown || !c.sockname ||
142+
!c.peername || !c.send_to || !c.recv_from || !c.poll) {
143+
c.ready = -1;
144+
return false;
145+
}
146+
84147
unsigned char record[1024]; // larger than the documented layout; see win32.h
85-
if (WSAStartup(0x0202 /* version 2.2 */, record) == 0) started = 1;
148+
if (start(0x0202 /* version 2.2 */, record) != 0) { c.ready = -1; return false; }
149+
c.ready = 1;
150+
return true;
151+
}
152+
153+
// The error this system last reported for a socket operation. ⚠️ Reached through
154+
// the table, so a caller that failed BEFORE the table was built --- which is the
155+
// only way `ensure_network' returns false --- is told `kal_err_io' rather than
156+
// calling through a null pointer.
157+
inline int last_socket_error() {
158+
network_calls& c = net_calls();
159+
if (c.ready <= 0 || c.last_error == nullptr) return kal_err_io;
160+
return translate_wsa(c.last_error());
161+
}
162+
163+
// The table, or a null pointer when this system's network interface could not
164+
// be reached at all. ⚠️ Every operation of both interfaces begins here, so a
165+
// system without `ws2_32.dll' --- which is not a system this package expects to
166+
// meet --- reports `kal_err_io' rather than calling through zero.
167+
inline network_calls* net_or_null() {
168+
return ensure_network() ? &net_calls() : nullptr;
86169
}
87170

88171
// ── addresses ───────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)