]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
635d059f LP |
2 | |
3 | #include <fcntl.h> | |
4 | #include <poll.h> | |
5 | #include <pthread.h> | |
6 | ||
7 | #include "sd-event.h" | |
8 | ||
7947dbe3 | 9 | #include "data-fd-util.h" |
635d059f LP |
10 | #include "fd-util.h" |
11 | #include "json.h" | |
12 | #include "rm-rf.h" | |
13 | #include "strv.h" | |
14 | #include "tmpfile-util.h" | |
15 | #include "user-util.h" | |
16 | #include "varlink.h" | |
17 | ||
18 | /* Let's pick some high value, that is higher than the largest listen() backlog, but leaves enough room below | |
19 | the typical RLIMIT_NOFILE value of 1024 so that we can process both sides of each socket in our | |
20 | process. Or in other words: "OVERLOAD_CONNECTIONS * 2 + x < 1024" should hold, for some small x that | |
21 | should cover any auxiliary fds, the listener server fds, stdin/stdout/stderr and whatever else. */ | |
22 | #define OVERLOAD_CONNECTIONS 333 | |
23 | ||
24 | static int n_done = 0; | |
254d1313 | 25 | static int block_write_fd = -EBADF; |
635d059f LP |
26 | |
27 | static int method_something(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
28 | _cleanup_(json_variant_unrefp) JsonVariant *ret = NULL; | |
29 | JsonVariant *a, *b; | |
718ca772 | 30 | int64_t x, y; |
635d059f LP |
31 | int r; |
32 | ||
33 | a = json_variant_by_key(parameters, "a"); | |
34 | if (!a) | |
35 | return varlink_error(link, "io.test.BadParameters", NULL); | |
36 | ||
37 | x = json_variant_integer(a); | |
38 | ||
39 | b = json_variant_by_key(parameters, "b"); | |
40 | if (!b) | |
41 | return varlink_error(link, "io.test.BadParameters", NULL); | |
42 | ||
43 | y = json_variant_integer(b); | |
44 | ||
45 | r = json_build(&ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("sum", JSON_BUILD_INTEGER(x + y)))); | |
46 | if (r < 0) | |
47 | return r; | |
48 | ||
49 | return varlink_reply(link, ret); | |
50 | } | |
51 | ||
1bd0b9c0 AS |
52 | static int method_something_more(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { |
53 | _cleanup_(json_variant_unrefp) JsonVariant *ret = NULL; | |
54 | int r; | |
55 | ||
56 | struct Something { | |
57 | int x; | |
58 | int y; | |
59 | }; | |
60 | ||
61 | static const JsonDispatch dispatch_table[] = { | |
f1b622a0 | 62 | { "a", JSON_VARIANT_INTEGER, json_dispatch_int, offsetof(struct Something, x), JSON_MANDATORY }, |
1bd0b9c0 AS |
63 | { "b", JSON_VARIANT_INTEGER, json_dispatch_int, offsetof(struct Something, y), JSON_MANDATORY}, |
64 | {} | |
65 | }; | |
66 | struct Something s = {}; | |
67 | ||
f1b622a0 LP |
68 | r = varlink_dispatch(link, parameters, dispatch_table, &s); |
69 | if (r != 0) | |
70 | return r; | |
1bd0b9c0 AS |
71 | |
72 | for (int i = 0; i < 5; i++) { | |
73 | _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; | |
74 | ||
75 | r = json_build(&w, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("sum", JSON_BUILD_INTEGER(s.x + (s.y * i))))); | |
76 | if (r < 0) | |
77 | return r; | |
78 | ||
79 | r = varlink_notify(link, w); | |
80 | if (r < 0) | |
81 | return r; | |
82 | } | |
83 | ||
84 | r = json_build(&ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("sum", JSON_BUILD_INTEGER(s.x + (s.y * 5))))); | |
85 | if (r < 0) | |
86 | return r; | |
87 | ||
88 | return varlink_reply(link, ret); | |
89 | } | |
90 | ||
7947dbe3 LP |
91 | static void test_fd(int fd, const void *buf, size_t n) { |
92 | char rbuf[n + 1]; | |
93 | ssize_t m; | |
94 | ||
95 | m = read(fd, rbuf, n + 1); | |
85ba4ca8 | 96 | assert_se(m >= 0); |
7947dbe3 LP |
97 | assert_se(memcmp_nn(buf, n, rbuf, m) == 0); |
98 | } | |
99 | ||
100 | static int method_passfd(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
101 | _cleanup_(json_variant_unrefp) JsonVariant *ret = NULL; | |
102 | JsonVariant *a; | |
103 | int r; | |
104 | ||
105 | a = json_variant_by_key(parameters, "fd"); | |
106 | if (!a) | |
107 | return varlink_error(link, "io.test.BadParameters", NULL); | |
108 | ||
109 | assert_se(streq_ptr(json_variant_string(a), "whoop")); | |
110 | ||
111 | int xx = varlink_peek_fd(link, 0), | |
112 | yy = varlink_peek_fd(link, 1), | |
113 | zz = varlink_peek_fd(link, 2); | |
114 | ||
115 | log_info("%i %i %i", xx, yy, zz); | |
116 | ||
85ba4ca8 YW |
117 | assert_se(xx >= 0); |
118 | assert_se(yy >= 0); | |
119 | assert_se(zz >= 0); | |
120 | ||
7947dbe3 LP |
121 | test_fd(xx, "foo", 3); |
122 | test_fd(yy, "bar", 3); | |
123 | test_fd(zz, "quux", 4); | |
124 | ||
125 | _cleanup_close_ int vv = acquire_data_fd("miau", 4, 0); | |
126 | _cleanup_close_ int ww = acquire_data_fd("wuff", 4, 0); | |
127 | ||
128 | assert_se(vv >= 0); | |
129 | assert_se(ww >= 0); | |
130 | ||
131 | r = json_build(&ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("yo", JSON_BUILD_INTEGER(88)))); | |
132 | if (r < 0) | |
133 | return r; | |
134 | ||
135 | assert_se(varlink_push_fd(link, vv) == 0); | |
136 | assert_se(varlink_push_fd(link, ww) == 1); | |
137 | ||
138 | TAKE_FD(vv); | |
139 | TAKE_FD(ww); | |
140 | ||
141 | return varlink_reply(link, ret); | |
142 | } | |
143 | ||
635d059f LP |
144 | static int method_done(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { |
145 | ||
146 | if (++n_done == 2) | |
147 | sd_event_exit(varlink_get_event(link), EXIT_FAILURE); | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
152 | static int reply(Varlink *link, JsonVariant *parameters, const char *error_id, VarlinkReplyFlags flags, void *userdata) { | |
153 | JsonVariant *sum; | |
154 | ||
155 | sum = json_variant_by_key(parameters, "sum"); | |
156 | ||
157 | assert_se(json_variant_integer(sum) == 7+22); | |
158 | ||
159 | if (++n_done == 2) | |
160 | sd_event_exit(varlink_get_event(link), EXIT_FAILURE); | |
161 | ||
162 | return 0; | |
163 | } | |
164 | ||
165 | static int on_connect(VarlinkServer *s, Varlink *link, void *userdata) { | |
166 | uid_t uid = UID_INVALID; | |
167 | ||
f21b863e YW |
168 | assert_se(s); |
169 | assert_se(link); | |
635d059f LP |
170 | |
171 | assert_se(varlink_get_peer_uid(link, &uid) >= 0); | |
172 | assert_se(getuid() == uid); | |
7947dbe3 LP |
173 | assert_se(varlink_set_allow_fd_passing_input(link, true) >= 0); |
174 | assert_se(varlink_set_allow_fd_passing_output(link, true) >= 0); | |
635d059f LP |
175 | |
176 | return 0; | |
177 | } | |
178 | ||
179 | static int overload_reply(Varlink *link, JsonVariant *parameters, const char *error_id, VarlinkReplyFlags flags, void *userdata) { | |
180 | ||
181 | /* This method call reply should always be called with a disconnection, since the method call should | |
182 | * be talking to an overloaded server */ | |
183 | ||
184 | log_debug("Over reply triggered with error: %s", strna(error_id)); | |
185 | assert_se(streq(error_id, VARLINK_ERROR_DISCONNECTED)); | |
186 | sd_event_exit(varlink_get_event(link), 0); | |
187 | ||
188 | return 0; | |
189 | } | |
190 | ||
191 | static void flood_test(const char *address) { | |
192 | _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL; | |
193 | _cleanup_(sd_event_unrefp) sd_event *e = NULL; | |
194 | _cleanup_free_ Varlink **connections = NULL; | |
195 | size_t k; | |
196 | char x = 'x'; | |
197 | ||
198 | log_debug("Flooding server..."); | |
199 | ||
200 | /* Block the main event loop while we flood */ | |
201 | assert_se(write(block_write_fd, &x, sizeof(x)) == sizeof(x)); | |
202 | ||
203 | assert_se(sd_event_default(&e) >= 0); | |
204 | ||
205 | /* Flood the server with connections */ | |
206 | assert_se(connections = new0(Varlink*, OVERLOAD_CONNECTIONS)); | |
207 | for (k = 0; k < OVERLOAD_CONNECTIONS; k++) { | |
208 | _cleanup_free_ char *t = NULL; | |
209 | log_debug("connection %zu", k); | |
210 | assert_se(varlink_connect_address(connections + k, address) >= 0); | |
211 | ||
212 | assert_se(asprintf(&t, "flood-%zu", k) >= 0); | |
213 | assert_se(varlink_set_description(connections[k], t) >= 0); | |
214 | assert_se(varlink_attach_event(connections[k], e, k) >= 0); | |
215 | assert_se(varlink_sendb(connections[k], "io.test.Rubbish", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("id", JSON_BUILD_INTEGER(k)))) >= 0); | |
216 | } | |
217 | ||
218 | /* Then, create one more, which should fail */ | |
219 | log_debug("Creating overload connection..."); | |
220 | assert_se(varlink_connect_address(&c, address) >= 0); | |
221 | assert_se(varlink_set_description(c, "overload-client") >= 0); | |
222 | assert_se(varlink_attach_event(c, e, k) >= 0); | |
223 | assert_se(varlink_bind_reply(c, overload_reply) >= 0); | |
0cdf6b14 | 224 | assert_se(varlink_invokeb(c, "io.test.Overload", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("foo", JSON_BUILD_CONST_STRING("bar")))) >= 0); |
635d059f LP |
225 | |
226 | /* Unblock it */ | |
227 | log_debug("Unblocking server..."); | |
228 | block_write_fd = safe_close(block_write_fd); | |
229 | ||
230 | /* This loop will terminate as soon as the overload reply callback is called */ | |
231 | assert_se(sd_event_loop(e) >= 0); | |
232 | ||
233 | /* And close all connections again */ | |
234 | for (k = 0; k < OVERLOAD_CONNECTIONS; k++) | |
235 | connections[k] = varlink_unref(connections[k]); | |
236 | } | |
237 | ||
238 | static void *thread(void *arg) { | |
239 | _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL; | |
1bd0b9c0 AS |
240 | _cleanup_(json_variant_unrefp) JsonVariant *i = NULL, *j = NULL; |
241 | JsonVariant *o = NULL, *k = NULL; | |
242 | const char *error_id; | |
243 | VarlinkReplyFlags flags = 0; | |
635d059f | 244 | const char *e; |
1bd0b9c0 | 245 | int x = 0; |
635d059f LP |
246 | |
247 | assert_se(json_build(&i, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("a", JSON_BUILD_INTEGER(88)), | |
248 | JSON_BUILD_PAIR("b", JSON_BUILD_INTEGER(99)))) >= 0); | |
249 | ||
250 | assert_se(varlink_connect_address(&c, arg) >= 0); | |
251 | assert_se(varlink_set_description(c, "thread-client") >= 0); | |
7947dbe3 LP |
252 | assert_se(varlink_set_allow_fd_passing_input(c, true) >= 0); |
253 | assert_se(varlink_set_allow_fd_passing_output(c, true) >= 0); | |
635d059f | 254 | |
1bd0b9c0 AS |
255 | assert_se(varlink_collect(c, "io.test.DoSomethingMore", i, &j, &error_id, &flags) >= 0); |
256 | ||
257 | assert_se(!error_id); | |
258 | assert_se(!flags); | |
259 | assert_se(json_variant_is_array(j) && !json_variant_is_blank_array(j)); | |
260 | ||
261 | JSON_VARIANT_ARRAY_FOREACH(k, j) { | |
262 | assert_se(json_variant_integer(json_variant_by_key(k, "sum")) == 88 + (99 * x)); | |
263 | x++; | |
264 | } | |
265 | assert_se(x == 6); | |
266 | ||
635d059f LP |
267 | assert_se(varlink_call(c, "io.test.DoSomething", i, &o, &e, NULL) >= 0); |
268 | assert_se(json_variant_integer(json_variant_by_key(o, "sum")) == 88 + 99); | |
269 | assert_se(!e); | |
270 | ||
7947dbe3 LP |
271 | int fd1 = acquire_data_fd("foo", 3, 0); |
272 | int fd2 = acquire_data_fd("bar", 3, 0); | |
273 | int fd3 = acquire_data_fd("quux", 4, 0); | |
274 | ||
275 | assert_se(fd1 >= 0); | |
276 | assert_se(fd2 >= 0); | |
277 | assert_se(fd3 >= 0); | |
278 | ||
279 | assert_se(varlink_push_fd(c, fd1) == 0); | |
280 | assert_se(varlink_push_fd(c, fd2) == 1); | |
281 | assert_se(varlink_push_fd(c, fd3) == 2); | |
282 | ||
283 | assert_se(varlink_callb(c, "io.test.PassFD", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("fd", JSON_BUILD_STRING("whoop")))) >= 0); | |
284 | ||
285 | int fd4 = varlink_peek_fd(c, 0); | |
286 | int fd5 = varlink_peek_fd(c, 1); | |
287 | ||
288 | assert_se(fd4 >= 0); | |
289 | assert_se(fd5 >= 0); | |
290 | ||
291 | test_fd(fd4, "miau", 4); | |
292 | test_fd(fd5, "wuff", 4); | |
293 | ||
635d059f LP |
294 | assert_se(varlink_callb(c, "io.test.IDontExist", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_REAL(5.5)))) >= 0); |
295 | assert_se(streq_ptr(json_variant_string(json_variant_by_key(o, "method")), "io.test.IDontExist")); | |
296 | assert_se(streq(e, VARLINK_ERROR_METHOD_NOT_FOUND)); | |
297 | ||
298 | flood_test(arg); | |
299 | ||
300 | assert_se(varlink_send(c, "io.test.Done", NULL) >= 0); | |
301 | ||
302 | return NULL; | |
303 | } | |
304 | ||
305 | static int block_fd_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { | |
306 | char c; | |
307 | ||
308 | assert_se(fd_nonblock(fd, false) >= 0); | |
309 | ||
310 | assert_se(read(fd, &c, sizeof(c)) == sizeof(c)); | |
311 | /* When a character is written to this pipe we'll block until the pipe is closed. */ | |
312 | ||
313 | assert_se(read(fd, &c, sizeof(c)) == 0); | |
314 | ||
315 | assert_se(fd_nonblock(fd, true) >= 0); | |
316 | ||
317 | assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); | |
318 | ||
319 | return 0; | |
320 | } | |
321 | ||
322 | int main(int argc, char *argv[]) { | |
323 | _cleanup_(sd_event_source_unrefp) sd_event_source *block_event = NULL; | |
324 | _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; | |
325 | _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL; | |
326 | _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; | |
327 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; | |
328 | _cleanup_(sd_event_unrefp) sd_event *e = NULL; | |
71136404 | 329 | _cleanup_close_pair_ int block_fds[2] = EBADF_PAIR; |
635d059f LP |
330 | pthread_t t; |
331 | const char *sp; | |
332 | ||
333 | log_set_max_level(LOG_DEBUG); | |
334 | log_open(); | |
335 | ||
336 | assert_se(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir) >= 0); | |
337 | sp = strjoina(tmpdir, "/socket"); | |
338 | ||
339 | assert_se(sd_event_default(&e) >= 0); | |
340 | ||
341 | assert_se(pipe2(block_fds, O_NONBLOCK|O_CLOEXEC) >= 0); | |
342 | assert_se(sd_event_add_io(e, &block_event, block_fds[0], EPOLLIN, block_fd_handler, NULL) >= 0); | |
343 | assert_se(sd_event_source_set_priority(block_event, SD_EVENT_PRIORITY_IMPORTANT) >= 0); | |
344 | block_write_fd = TAKE_FD(block_fds[1]); | |
345 | ||
346 | assert_se(varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID) >= 0); | |
347 | assert_se(varlink_server_set_description(s, "our-server") >= 0); | |
348 | ||
7947dbe3 | 349 | assert_se(varlink_server_bind_method(s, "io.test.PassFD", method_passfd) >= 0); |
635d059f | 350 | assert_se(varlink_server_bind_method(s, "io.test.DoSomething", method_something) >= 0); |
1bd0b9c0 | 351 | assert_se(varlink_server_bind_method(s, "io.test.DoSomethingMore", method_something_more) >= 0); |
635d059f LP |
352 | assert_se(varlink_server_bind_method(s, "io.test.Done", method_done) >= 0); |
353 | assert_se(varlink_server_bind_connect(s, on_connect) >= 0); | |
354 | assert_se(varlink_server_listen_address(s, sp, 0600) >= 0); | |
355 | assert_se(varlink_server_attach_event(s, e, 0) >= 0); | |
356 | assert_se(varlink_server_set_connections_max(s, OVERLOAD_CONNECTIONS) >= 0); | |
357 | ||
1bd0b9c0 AS |
358 | assert_se(json_build(&v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("a", JSON_BUILD_INTEGER(7)), |
359 | JSON_BUILD_PAIR("b", JSON_BUILD_INTEGER(22)))) >= 0); | |
360 | ||
635d059f LP |
361 | assert_se(varlink_connect_address(&c, sp) >= 0); |
362 | assert_se(varlink_set_description(c, "main-client") >= 0); | |
363 | assert_se(varlink_bind_reply(c, reply) >= 0); | |
364 | ||
635d059f LP |
365 | assert_se(varlink_invoke(c, "io.test.DoSomething", v) >= 0); |
366 | ||
367 | assert_se(varlink_attach_event(c, e, 0) >= 0); | |
368 | ||
369 | assert_se(pthread_create(&t, NULL, thread, (void*) sp) == 0); | |
370 | ||
371 | assert_se(sd_event_loop(e) >= 0); | |
372 | ||
373 | assert_se(pthread_join(t, NULL) == 0); | |
374 | ||
375 | return 0; | |
376 | } |