16static void require(
int condition,
const char* operation) {
18 printf(
"SOCKET-PATH-CONTRACT: FAIL %s errno=%d\n", operation, errno);
23static socklen_t abstract_socket_address(
struct sockaddr_un* address,
char discriminator) {
24 memset(address, 0,
sizeof(*address));
25 address->sun_family = AF_UNIX;
27 const int prefix_length =
28 snprintf(address->sun_path + 1,
sizeof(address->sun_path) - 1,
"pedigree-%c", discriminator);
29 require(prefix_length > 0,
"format abstract socket prefix");
30 const size_t suffix_offset = 1 + (size_t)prefix_length + 1;
31 const int suffix_length =
32 snprintf(address->sun_path + suffix_offset,
sizeof(address->sun_path) - suffix_offset,
"%ld",
34 require(suffix_length > 0,
"format abstract socket suffix");
36 return offsetof(
struct sockaddr_un, sun_path) + suffix_offset + (size_t)suffix_length;
39static void abstract_socket_contracts(
void) {
40 struct sockaddr_un stream_address;
41 const socklen_t stream_length = abstract_socket_address(&stream_address,
's');
42 int listener = socket(AF_UNIX, SOCK_STREAM, 0);
43 int replacement = socket(AF_UNIX, SOCK_STREAM, 0);
44 int client = socket(AF_UNIX, SOCK_STREAM, 0);
45 require(listener >= 0 && replacement >= 0 && client >= 0 &&
46 bind(listener, (
struct sockaddr*)&stream_address, stream_length) == 0,
47 "bind abstract stream socket");
49 struct sockaddr_un reported = {};
50 socklen_t reported_length =
sizeof(reported);
51 require(getsockname(listener, (
struct sockaddr*)&reported, &reported_length) == 0 &&
52 reported_length == stream_length &&
53 memcmp(&reported, &stream_address, stream_length) == 0,
54 "report length-sensitive abstract address");
56 require(bind(replacement, (
struct sockaddr*)&stream_address, stream_length) == -1 &&
58 "reject duplicate abstract stream bind");
59 require(listen(listener, 1) == 0 &&
60 connect(client, (
struct sockaddr*)&stream_address, stream_length) == 0,
61 "connect abstract stream socket");
62 int accepted = accept(listener, NULL, NULL);
63 require(accepted >= 0,
"accept abstract stream socket");
65 int duplicate = dup(listener);
66 require(duplicate >= 0 && close(listener) == 0,
"duplicate abstract listener");
68 require(bind(replacement, (
struct sockaddr*)&stream_address, stream_length) == -1 &&
70 "duplicate retains abstract address");
71 require(close(duplicate) == 0 &&
72 bind(replacement, (
struct sockaddr*)&stream_address, stream_length) == 0,
73 "final listener close releases abstract address");
76 require(send(client,
"c", 1, 0) == 1 && recv(accepted, &
byte, 1, 0) == 1 &&
byte ==
'c' &&
77 send(accepted,
"a", 1, 0) == 1 && recv(client, &
byte, 1, 0) == 1 &&
byte ==
'a',
78 "accepted connection survives abstract rebind");
79 require(close(client) == 0 && close(accepted) == 0 && close(replacement) == 0,
80 "close abstract stream sockets");
82 struct sockaddr_un receiver_address;
83 struct sockaddr_un sender_address;
84 const socklen_t receiver_length = abstract_socket_address(&receiver_address,
'r');
85 const socklen_t sender_length = abstract_socket_address(&sender_address,
'd');
86 int receiver = socket(AF_UNIX, SOCK_DGRAM, 0);
87 int sender = socket(AF_UNIX, SOCK_DGRAM, 0);
88 require(receiver >= 0 && sender >= 0 &&
89 bind(receiver, (
struct sockaddr*)&receiver_address, receiver_length) == 0 &&
90 bind(sender, (
struct sockaddr*)&sender_address, sender_length) == 0 &&
91 sendto(sender,
"d", 1, 0, (
struct sockaddr*)&receiver_address, receiver_length) == 1,
92 "send abstract datagram");
94 memset(&reported, 0,
sizeof(reported));
95 reported_length =
sizeof(reported);
96 require(recvfrom(receiver, &
byte, 1, 0, (
struct sockaddr*)&reported, &reported_length) == 1 &&
97 byte ==
'd' && reported_length == sender_length &&
98 memcmp(&reported, &sender_address, sender_length) == 0,
99 "report abstract datagram sender");
100 require(close(receiver) == 0 && close(sender) == 0,
"close abstract datagram sockets");
103static void rename_socket_paths(
const char* directory) {
104 struct sockaddr_un source_path = {.sun_family = AF_UNIX};
105 struct sockaddr_un destination = {.sun_family = AF_UNIX};
106 char regular_path[108];
107 snprintf(source_path.sun_path,
sizeof(source_path.sun_path),
"%s/socket-source-%ld", directory,
109 snprintf(destination.sun_path,
sizeof(destination.sun_path),
"%s/socket-destination-%ld",
110 directory, (
long)getpid());
111 snprintf(regular_path,
sizeof(regular_path),
"%s/socket-regular-%ld", directory, (
long)getpid());
112 const socklen_t source_length =
113 offsetof(
struct sockaddr_un, sun_path) + strlen(source_path.sun_path) + 1;
114 const socklen_t destination_length =
115 offsetof(
struct sockaddr_un, sun_path) + strlen(destination.sun_path) + 1;
116 int source = socket(AF_UNIX, SOCK_DGRAM, 0);
117 int victim = socket(AF_UNIX, SOCK_DGRAM, 0);
118 int source_peer = socket(AF_UNIX, SOCK_DGRAM, 0);
119 int victim_peer = socket(AF_UNIX, SOCK_DGRAM, 0);
120 require(source >= 0 && victim >= 0 && source_peer >= 0 && victim_peer >= 0 &&
121 bind(source, (
struct sockaddr*)&source_path, source_length) == 0 &&
122 bind(victim, (
struct sockaddr*)&destination, destination_length) == 0 &&
123 connect(source_peer, (
struct sockaddr*)&source_path, source_length) == 0 &&
124 connect(victim_peer, (
struct sockaddr*)&destination, destination_length) == 0,
125 "prepare socket rename endpoints");
126 require(rename(source_path.sun_path, destination.sun_path) == 0,
127 "rename replaces an open socket pathname");
128 struct stat metadata;
130 require(lstat(source_path.sun_path, &metadata) == -1 && errno == ENOENT,
131 "rename removes old socket name");
133 require(send(source_peer,
"s", 1, 0) == 1 && recv(source, &
byte, 1, 0) == 1 &&
byte ==
's' &&
134 send(victim_peer,
"v", 1, 0) == 1 && recv(victim, &
byte, 1, 0) == 1 &&
byte ==
'v',
135 "rename preserves both existing endpoint references");
136 require(connect(victim_peer, (
struct sockaddr*)&destination, destination_length) == 0 &&
137 send(victim_peer,
"n", 1, 0) == 1 && recv(source, &
byte, 1, 0) == 1 &&
byte ==
'n' &&
139 "new connection follows renamed endpoint");
141 int regular = open(regular_path, O_CREAT | O_EXCL | O_RDWR, 0600);
142 require(regular >= 0 && write(regular,
"r", 1) == 1 &&
143 rename(destination.sun_path, regular_path) == 0 && pread(regular, &
byte, 1, 0) == 1 &&
144 byte ==
'r' && lstat(regular_path, &metadata) == 0 && S_ISSOCK(metadata.st_mode),
145 "socket replaces backing file while old descriptor survives");
146 int backing = open(source_path.sun_path, O_CREAT | O_EXCL | O_RDWR, 0600);
147 require(backing >= 0 && write(backing,
"f", 1) == 1 &&
148 rename(source_path.sun_path, regular_path) == 0 &&
149 stat(regular_path, &metadata) == 0 && S_ISREG(metadata.st_mode) &&
150 send(source_peer,
"u", 1, 0) == 1 && recv(source, &
byte, 1, 0) == 1 &&
byte ==
'u',
151 "backing file replaces pathname without closing socket endpoint");
152 require(close(source) == 0 && stat(regular_path, &metadata) == 0 && S_ISREG(metadata.st_mode) &&
153 close(source_peer) == 0 && close(victim_peer) == 0 && close(regular) == 0 &&
154 close(backing) == 0 && unlink(regular_path) == 0,
155 "close preserves the renamed backing replacement");
158void test_socket_path_contracts(
void) {
159 const int types[] = {SOCK_STREAM, SOCK_DGRAM};
160 for (
size_t i = 0; i <
sizeof(types) /
sizeof(types[0]); ++i) {
161 struct sockaddr_un address = {.sun_family = AF_UNIX};
162 snprintf(address.sun_path,
sizeof(address.sun_path),
"/tmp/socket-life-%ld-%d", (
long)getpid(),
164 const socklen_t length = offsetof(
struct sockaddr_un, sun_path) + strlen(address.sun_path) + 1;
165 int bound = socket(AF_UNIX, types[i], 0);
166 require(bound >= 0 && bind(bound, (
struct sockaddr*)&address, length) == 0,
167 "bind socket lifetime fixture");
168 if (types[i] == SOCK_STREAM) {
169 require(listen(bound, 1) == 0,
"listen lifetime fixture");
171 int duplicate = dup(bound);
172 require(duplicate >= 0 && close(bound) == 0,
"close duplicated bound descriptor");
173 struct stat metadata;
174 require(lstat(address.sun_path, &metadata) == 0 && S_ISSOCK(metadata.st_mode),
175 "duplicated socket retains pathname");
176 int replacement = socket(AF_UNIX, types[i], 0);
177 require(replacement >= 0,
"replacement socket");
179 require(bind(replacement, (
struct sockaddr*)&address, length) == -1 && errno == EADDRINUSE,
180 "bound name cannot be replaced by bind");
181 require(close(duplicate) == 0 && lstat(address.sun_path, &metadata) == 0 &&
182 S_ISSOCK(metadata.st_mode),
183 "final close preserves socket pathname");
185 require(connect(replacement, (
struct sockaddr*)&address, length) == -1 && errno == ECONNREFUSED,
186 "closed pathname refuses connections");
187 require(unlink(address.sun_path) == 0 &&
188 bind(replacement, (
struct sockaddr*)&address, length) == 0 &&
189 close(replacement) == 0 && unlink(address.sun_path) == 0,
190 "explicit unlink permits rebinding");
193 struct sockaddr_un address = {.sun_family = AF_UNIX};
194 snprintf(address.sun_path,
sizeof(address.sun_path),
"/tmp/socket-rebind-%ld", (
long)getpid());
195 const socklen_t length = offsetof(
struct sockaddr_un, sun_path) + strlen(address.sun_path) + 1;
196 int original = socket(AF_UNIX, SOCK_DGRAM, 0);
197 int sender = socket(AF_UNIX, SOCK_DGRAM, 0);
198 require(original >= 0 && sender >= 0 && bind(original, (
struct sockaddr*)&address, length) == 0 &&
199 connect(sender, (
struct sockaddr*)&address, length) == 0 &&
200 unlink(address.sun_path) == 0,
201 "unlink a connected datagram endpoint");
202 int replacement = socket(AF_UNIX, SOCK_DGRAM, 0);
203 require(replacement >= 0 && bind(replacement, (
struct sockaddr*)&address, length) == 0,
204 "reuse unlinked pathname for a new endpoint");
206 require(sendto(sender,
"n", 1, 0, (
struct sockaddr*)&address, length) == 1 &&
207 recv(replacement, &
byte, 1, 0) == 1 &&
byte ==
'n' && send(sender,
"o", 1, 0) == 1,
208 "explicit datagram destination preserves the default peer");
209 require(recv(original, &
byte, 1, 0) == 1 &&
byte ==
'o' && close(original) == 0,
210 "unlinked endpoint remains usable until close");
212 require(send(sender,
"x", 1, 0) == -1 && errno == ECONNREFUSED,
213 "old connection cannot reach replacement endpoint");
214 require(connect(sender, (
struct sockaddr*)&address, length) == 0 &&
215 send(sender,
"n", 1, 0) == 1 && recv(replacement, &
byte, 1, 0) == 1 &&
byte ==
'n',
216 "closing old endpoint preserves replacement pathname");
217 require(close(sender) == 0 && close(replacement) == 0 && unlink(address.sun_path) == 0,
218 "remove replacement endpoint");
219 puts(
"SOCKET-PATH-CONTRACT: PASS close-unlink-rebind");
220 rename_socket_paths(
"/tmp");
221 puts(
"SOCKET-PATH-CONTRACT: PASS ramfs-rename-replacement");
222 rename_socket_paths(
"");
223 puts(
"SOCKET-PATH-CONTRACT: PASS ext2-rename-replacement");
224 abstract_socket_contracts();
225 puts(
"SOCKET-PATH-CONTRACT: PASS abstract-namespace");