]>
Commit | Line | Data |
---|---|---|
65f568bb TG |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2013 Tom Gundersen <teg@jklm.no> | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #include <sys/socket.h> | |
23 | #include <poll.h> | |
24 | ||
66269b05 | 25 | #include "missing.h" |
65f568bb TG |
26 | #include "macro.h" |
27 | #include "util.h" | |
e16bcf98 | 28 | #include "hashmap.h" |
65f568bb TG |
29 | |
30 | #include "sd-rtnl.h" | |
31 | #include "rtnl-internal.h" | |
a33dece5 | 32 | #include "rtnl-util.h" |
65f568bb TG |
33 | |
34 | static int sd_rtnl_new(sd_rtnl **ret) { | |
689703f6 | 35 | _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL; |
65f568bb TG |
36 | |
37 | assert_return(ret, -EINVAL); | |
38 | ||
39 | rtnl = new0(sd_rtnl, 1); | |
40 | if (!rtnl) | |
41 | return -ENOMEM; | |
42 | ||
43 | rtnl->n_ref = REFCNT_INIT; | |
44 | ||
45 | rtnl->fd = -1; | |
46 | ||
47 | rtnl->sockaddr.nl.nl_family = AF_NETLINK; | |
48 | ||
adf412b9 TG |
49 | rtnl->original_pid = getpid(); |
50 | ||
8cec01b9 TG |
51 | LIST_HEAD_INIT(rtnl->match_callbacks); |
52 | ||
4555ec72 TG |
53 | /* We guarantee that wqueue always has space for at least |
54 | * one entry */ | |
77768cba | 55 | if (!GREEDY_REALLOC(rtnl->wqueue, rtnl->wqueue_allocated, 1)) |
4555ec72 | 56 | return -ENOMEM; |
4555ec72 | 57 | |
a88f77c4 TG |
58 | /* We guarantee that the read buffer has at least space for |
59 | * a message header */ | |
60 | if (!greedy_realloc((void**)&rtnl->rbuffer, &rtnl->rbuffer_allocated, | |
61 | sizeof(struct nlmsghdr), sizeof(uint8_t))) | |
62 | return -ENOMEM; | |
63 | ||
65f568bb | 64 | *ret = rtnl; |
689703f6 TG |
65 | rtnl = NULL; |
66 | ||
65f568bb TG |
67 | return 0; |
68 | } | |
69 | ||
adf412b9 TG |
70 | static bool rtnl_pid_changed(sd_rtnl *rtnl) { |
71 | assert(rtnl); | |
72 | ||
73 | /* We don't support people creating an rtnl connection and | |
74 | * keeping it around over a fork(). Let's complain. */ | |
75 | ||
76 | return rtnl->original_pid != getpid(); | |
77 | } | |
78 | ||
897e184c TG |
79 | static int rtnl_compute_groups_ap(uint32_t *_groups, unsigned n_groups, va_list ap) { |
80 | uint32_t groups = 0; | |
81 | unsigned i; | |
82 | ||
83 | for (i = 0; i < n_groups; i++) { | |
84 | unsigned group; | |
85 | ||
86 | group = va_arg(ap, unsigned); | |
87 | assert_return(group < 32, -EINVAL); | |
88 | ||
89 | groups |= group ? (1 << (group - 1)) : 0; | |
90 | } | |
91 | ||
92 | *_groups = groups; | |
93 | ||
94 | return 0; | |
95 | } | |
96 | ||
6d0b55c2 | 97 | static int rtnl_open_fd_ap(sd_rtnl **ret, int fd, unsigned n_groups, va_list ap) { |
cf6a8911 | 98 | _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL; |
d4bbdb77 | 99 | socklen_t addrlen; |
bc078e71 | 100 | int r, one = 1; |
65f568bb | 101 | |
9d0db178 | 102 | assert_return(ret, -EINVAL); |
6d0b55c2 | 103 | assert_return(fd >= 0, -EINVAL); |
9d0db178 | 104 | |
65f568bb TG |
105 | r = sd_rtnl_new(&rtnl); |
106 | if (r < 0) | |
107 | return r; | |
108 | ||
6d0b55c2 | 109 | r = setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); |
66269b05 TG |
110 | if (r < 0) |
111 | return -errno; | |
112 | ||
6d0b55c2 | 113 | r = setsockopt(fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)); |
66269b05 | 114 | if (r < 0) |
bc078e71 TG |
115 | return -errno; |
116 | ||
897e184c | 117 | r = rtnl_compute_groups_ap(&rtnl->sockaddr.nl.nl_groups, n_groups, ap); |
897e184c TG |
118 | if (r < 0) |
119 | return r; | |
65f568bb | 120 | |
d4bbdb77 TG |
121 | addrlen = sizeof(rtnl->sockaddr); |
122 | ||
6d0b55c2 | 123 | r = bind(fd, &rtnl->sockaddr.sa, addrlen); |
fe5c4e49 TG |
124 | if (r < 0) |
125 | return -errno; | |
65f568bb | 126 | |
6d0b55c2 | 127 | r = getsockname(fd, &rtnl->sockaddr.sa, &addrlen); |
d4bbdb77 TG |
128 | if (r < 0) |
129 | return r; | |
130 | ||
6d0b55c2 LP |
131 | rtnl->fd = fd; |
132 | ||
65f568bb | 133 | *ret = rtnl; |
fe5c4e49 | 134 | rtnl = NULL; |
65f568bb TG |
135 | |
136 | return 0; | |
137 | } | |
138 | ||
6d0b55c2 LP |
139 | int sd_rtnl_open_fd(sd_rtnl **ret, int fd, unsigned n_groups, ...) { |
140 | va_list ap; | |
141 | int r; | |
142 | ||
143 | va_start(ap, n_groups); | |
144 | r = rtnl_open_fd_ap(ret, fd, n_groups, ap); | |
145 | va_end(ap); | |
146 | ||
147 | return r; | |
148 | } | |
149 | ||
150 | int sd_rtnl_open(sd_rtnl **ret, unsigned n_groups, ...) { | |
151 | va_list ap; | |
152 | int fd, r; | |
153 | ||
154 | fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE); | |
155 | if (fd < 0) | |
156 | return -errno; | |
157 | ||
158 | va_start(ap, n_groups); | |
159 | r = rtnl_open_fd_ap(ret, fd, n_groups, ap); | |
160 | va_end(ap); | |
161 | ||
162 | if (r < 0) { | |
163 | safe_close(fd); | |
164 | return r; | |
165 | } | |
166 | ||
167 | return 0; | |
168 | } | |
169 | ||
be660c37 AR |
170 | int sd_rtnl_inc_rcvbuf(const sd_rtnl *const rtnl, const int size) { |
171 | return fd_inc_rcvbuf(rtnl->fd, size); | |
172 | } | |
173 | ||
65f568bb | 174 | sd_rtnl *sd_rtnl_ref(sd_rtnl *rtnl) { |
8c578303 TG |
175 | assert_return(rtnl, NULL); |
176 | assert_return(!rtnl_pid_changed(rtnl), NULL); | |
177 | ||
65f568bb TG |
178 | if (rtnl) |
179 | assert_se(REFCNT_INC(rtnl->n_ref) >= 2); | |
180 | ||
181 | return rtnl; | |
182 | } | |
183 | ||
184 | sd_rtnl *sd_rtnl_unref(sd_rtnl *rtnl) { | |
22fdeadc DH |
185 | if (!rtnl) |
186 | return NULL; | |
187 | ||
8c578303 | 188 | assert_return(!rtnl_pid_changed(rtnl), NULL); |
22fdeadc | 189 | |
f0c4b1c3 | 190 | if (REFCNT_DEC(rtnl->n_ref) == 0) { |
8cec01b9 | 191 | struct match_callback *f; |
4555ec72 TG |
192 | unsigned i; |
193 | ||
8c578303 TG |
194 | for (i = 0; i < rtnl->rqueue_size; i++) |
195 | sd_rtnl_message_unref(rtnl->rqueue[i]); | |
196 | free(rtnl->rqueue); | |
8cec01b9 | 197 | |
4e996881 TG |
198 | for (i = 0; i < rtnl->rqueue_partial_size; i++) |
199 | sd_rtnl_message_unref(rtnl->rqueue_partial[i]); | |
200 | free(rtnl->rqueue_partial); | |
201 | ||
8c578303 TG |
202 | for (i = 0; i < rtnl->wqueue_size; i++) |
203 | sd_rtnl_message_unref(rtnl->wqueue[i]); | |
204 | free(rtnl->wqueue); | |
22fdeadc | 205 | |
a88f77c4 TG |
206 | free(rtnl->rbuffer); |
207 | ||
8c578303 TG |
208 | hashmap_free_free(rtnl->reply_callbacks); |
209 | prioq_free(rtnl->reply_callbacks_prioq); | |
22fdeadc | 210 | |
8c578303 TG |
211 | sd_event_source_unref(rtnl->io_event_source); |
212 | sd_event_source_unref(rtnl->time_event_source); | |
213 | sd_event_source_unref(rtnl->exit_event_source); | |
214 | sd_event_unref(rtnl->event); | |
22fdeadc | 215 | |
8c578303 TG |
216 | while ((f = rtnl->match_callbacks)) { |
217 | LIST_REMOVE(match_callbacks, rtnl->match_callbacks, f); | |
218 | free(f); | |
22fdeadc | 219 | } |
65f568bb | 220 | |
8c578303 TG |
221 | safe_close(rtnl->fd); |
222 | free(rtnl); | |
223 | } | |
22fdeadc | 224 | |
65f568bb TG |
225 | return NULL; |
226 | } | |
227 | ||
3dd215e0 TG |
228 | static void rtnl_seal_message(sd_rtnl *rtnl, sd_rtnl_message *m) { |
229 | assert(rtnl); | |
230 | assert(!rtnl_pid_changed(rtnl)); | |
231 | assert(m); | |
232 | assert(m->hdr); | |
233 | ||
234 | m->hdr->nlmsg_seq = rtnl->serial++; | |
235 | ||
236 | rtnl_message_seal(m); | |
237 | ||
238 | return; | |
239 | } | |
240 | ||
4555ec72 TG |
241 | int sd_rtnl_send(sd_rtnl *nl, |
242 | sd_rtnl_message *message, | |
243 | uint32_t *serial) { | |
244 | int r; | |
65f568bb TG |
245 | |
246 | assert_return(nl, -EINVAL); | |
adf412b9 | 247 | assert_return(!rtnl_pid_changed(nl), -ECHILD); |
65f568bb | 248 | assert_return(message, -EINVAL); |
3dd215e0 | 249 | assert_return(!message->sealed, -EPERM); |
65f568bb | 250 | |
3dd215e0 | 251 | rtnl_seal_message(nl, message); |
65f568bb | 252 | |
4555ec72 TG |
253 | if (nl->wqueue_size <= 0) { |
254 | /* send directly */ | |
255 | r = socket_write_message(nl, message); | |
256 | if (r < 0) | |
257 | return r; | |
258 | else if (r == 0) { | |
259 | /* nothing was sent, so let's put it on | |
260 | * the queue */ | |
261 | nl->wqueue[0] = sd_rtnl_message_ref(message); | |
262 | nl->wqueue_size = 1; | |
263 | } | |
264 | } else { | |
4555ec72 | 265 | /* append to queue */ |
6190b9f9 TG |
266 | if (nl->wqueue_size >= RTNL_WQUEUE_MAX) { |
267 | log_debug("rtnl: exhausted the write queue size (%d)", RTNL_WQUEUE_MAX); | |
4555ec72 | 268 | return -ENOBUFS; |
6190b9f9 | 269 | } |
65f568bb | 270 | |
77768cba | 271 | if (!GREEDY_REALLOC(nl->wqueue, nl->wqueue_allocated, nl->wqueue_size + 1)) |
4555ec72 | 272 | return -ENOMEM; |
65f568bb | 273 | |
77768cba | 274 | nl->wqueue[nl->wqueue_size ++] = sd_rtnl_message_ref(message); |
4555ec72 | 275 | } |
65f568bb | 276 | |
4555ec72 | 277 | if (serial) |
3815f36f | 278 | *serial = rtnl_message_get_serial(message); |
65f568bb | 279 | |
4555ec72 TG |
280 | return 1; |
281 | } | |
65f568bb | 282 | |
1b89cf56 TG |
283 | int rtnl_rqueue_make_room(sd_rtnl *rtnl) { |
284 | assert(rtnl); | |
285 | ||
6190b9f9 TG |
286 | if (rtnl->rqueue_size >= RTNL_RQUEUE_MAX) { |
287 | log_debug("rtnl: exhausted the read queue size (%d)", RTNL_RQUEUE_MAX); | |
1b89cf56 | 288 | return -ENOBUFS; |
6190b9f9 | 289 | } |
1b89cf56 TG |
290 | |
291 | if (!GREEDY_REALLOC(rtnl->rqueue, rtnl->rqueue_allocated, rtnl->rqueue_size + 1)) | |
292 | return -ENOMEM; | |
293 | ||
294 | return 0; | |
295 | } | |
296 | ||
4e996881 TG |
297 | int rtnl_rqueue_partial_make_room(sd_rtnl *rtnl) { |
298 | assert(rtnl); | |
299 | ||
6190b9f9 TG |
300 | if (rtnl->rqueue_partial_size >= RTNL_RQUEUE_MAX) { |
301 | log_debug("rtnl: exhausted the partial read queue size (%d)", RTNL_RQUEUE_MAX); | |
4e996881 | 302 | return -ENOBUFS; |
6190b9f9 | 303 | } |
4e996881 TG |
304 | |
305 | if (!GREEDY_REALLOC(rtnl->rqueue_partial, rtnl->rqueue_partial_allocated, | |
306 | rtnl->rqueue_partial_size + 1)) | |
307 | return -ENOMEM; | |
308 | ||
309 | return 0; | |
310 | } | |
311 | ||
4555ec72 | 312 | static int dispatch_rqueue(sd_rtnl *rtnl, sd_rtnl_message **message) { |
4555ec72 | 313 | int r; |
65f568bb | 314 | |
4555ec72 TG |
315 | assert(rtnl); |
316 | assert(message); | |
317 | ||
1b89cf56 TG |
318 | if (rtnl->rqueue_size <= 0) { |
319 | /* Try to read a new message */ | |
320 | r = socket_read_message(rtnl); | |
321 | if (r <= 0) | |
322 | return r; | |
4555ec72 TG |
323 | } |
324 | ||
1b89cf56 TG |
325 | /* Dispatch a queued message */ |
326 | *message = rtnl->rqueue[0]; | |
327 | rtnl->rqueue_size --; | |
328 | memmove(rtnl->rqueue, rtnl->rqueue + 1, sizeof(sd_rtnl_message*) * rtnl->rqueue_size); | |
4555ec72 TG |
329 | |
330 | return 1; | |
331 | } | |
332 | ||
333 | static int dispatch_wqueue(sd_rtnl *rtnl) { | |
334 | int r, ret = 0; | |
335 | ||
336 | assert(rtnl); | |
337 | ||
338 | while (rtnl->wqueue_size > 0) { | |
339 | r = socket_write_message(rtnl, rtnl->wqueue[0]); | |
65f568bb TG |
340 | if (r < 0) |
341 | return r; | |
4555ec72 TG |
342 | else if (r == 0) |
343 | /* Didn't do anything this time */ | |
344 | return ret; | |
345 | else { | |
346 | /* see equivalent in sd-bus.c */ | |
347 | sd_rtnl_message_unref(rtnl->wqueue[0]); | |
348 | rtnl->wqueue_size --; | |
349 | memmove(rtnl->wqueue, rtnl->wqueue + 1, sizeof(sd_rtnl_message*) * rtnl->wqueue_size); | |
350 | ||
351 | ret = 1; | |
65f568bb TG |
352 | } |
353 | } | |
354 | ||
4555ec72 TG |
355 | return ret; |
356 | } | |
357 | ||
e16bcf98 | 358 | static int process_timeout(sd_rtnl *rtnl) { |
cf6a8911 | 359 | _cleanup_rtnl_message_unref_ sd_rtnl_message *m = NULL; |
e16bcf98 TG |
360 | struct reply_callback *c; |
361 | usec_t n; | |
362 | int r; | |
363 | ||
364 | assert(rtnl); | |
365 | ||
366 | c = prioq_peek(rtnl->reply_callbacks_prioq); | |
367 | if (!c) | |
368 | return 0; | |
369 | ||
370 | n = now(CLOCK_MONOTONIC); | |
371 | if (c->timeout > n) | |
372 | return 0; | |
373 | ||
3815f36f | 374 | r = rtnl_message_new_synthetic_error(-ETIMEDOUT, c->serial, &m); |
e16bcf98 TG |
375 | if (r < 0) |
376 | return r; | |
377 | ||
378 | assert_se(prioq_pop(rtnl->reply_callbacks_prioq) == c); | |
379 | hashmap_remove(rtnl->reply_callbacks, &c->serial); | |
380 | ||
381 | r = c->callback(rtnl, m, c->userdata); | |
382 | free(c); | |
383 | ||
384 | return r < 0 ? r : 1; | |
385 | } | |
386 | ||
387 | static int process_reply(sd_rtnl *rtnl, sd_rtnl_message *m) { | |
388 | struct reply_callback *c; | |
389 | uint64_t serial; | |
390 | int r; | |
391 | ||
392 | assert(rtnl); | |
393 | assert(m); | |
394 | ||
1f0db3ed TG |
395 | if (sd_rtnl_message_is_broadcast(m)) |
396 | return 0; | |
397 | ||
3815f36f | 398 | serial = rtnl_message_get_serial(m); |
e16bcf98 TG |
399 | c = hashmap_remove(rtnl->reply_callbacks, &serial); |
400 | if (!c) | |
401 | return 0; | |
402 | ||
403 | if (c->timeout != 0) | |
404 | prioq_remove(rtnl->reply_callbacks_prioq, c, &c->prioq_idx); | |
405 | ||
406 | r = c->callback(rtnl, m, c->userdata); | |
407 | free(c); | |
408 | ||
409 | return r; | |
410 | } | |
411 | ||
8cec01b9 TG |
412 | static int process_match(sd_rtnl *rtnl, sd_rtnl_message *m) { |
413 | struct match_callback *c; | |
414 | uint16_t type; | |
415 | int r; | |
416 | ||
417 | assert(rtnl); | |
418 | assert(m); | |
419 | ||
420 | r = sd_rtnl_message_get_type(m, &type); | |
421 | if (r < 0) | |
422 | return r; | |
423 | ||
424 | LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) { | |
23a7f0f7 | 425 | if (type == c->type) { |
8cec01b9 TG |
426 | r = c->callback(rtnl, m, c->userdata); |
427 | if (r != 0) | |
428 | return r; | |
429 | } | |
430 | } | |
431 | ||
432 | return 0; | |
433 | } | |
434 | ||
4555ec72 | 435 | static int process_running(sd_rtnl *rtnl, sd_rtnl_message **ret) { |
cf6a8911 | 436 | _cleanup_rtnl_message_unref_ sd_rtnl_message *m = NULL; |
4555ec72 TG |
437 | int r; |
438 | ||
9d0db178 TG |
439 | assert(rtnl); |
440 | ||
e16bcf98 TG |
441 | r = process_timeout(rtnl); |
442 | if (r != 0) | |
443 | goto null_message; | |
444 | ||
4555ec72 TG |
445 | r = dispatch_wqueue(rtnl); |
446 | if (r != 0) | |
447 | goto null_message; | |
448 | ||
449 | r = dispatch_rqueue(rtnl, &m); | |
450 | if (r < 0) | |
451 | return r; | |
452 | if (!m) | |
453 | goto null_message; | |
454 | ||
e16bcf98 TG |
455 | r = process_reply(rtnl, m); |
456 | if (r != 0) | |
457 | goto null_message; | |
458 | ||
8cec01b9 TG |
459 | r = process_match(rtnl, m); |
460 | if (r != 0) | |
461 | goto null_message; | |
462 | ||
4555ec72 TG |
463 | if (ret) { |
464 | *ret = m; | |
465 | m = NULL; | |
466 | ||
467 | return 1; | |
468 | } | |
469 | ||
470 | return 1; | |
471 | ||
472 | null_message: | |
473 | if (r >= 0 && ret) | |
474 | *ret = NULL; | |
475 | ||
476 | return r; | |
477 | } | |
e16bcf98 | 478 | |
4555ec72 | 479 | int sd_rtnl_process(sd_rtnl *rtnl, sd_rtnl_message **ret) { |
e16bcf98 | 480 | RTNL_DONT_DESTROY(rtnl); |
4555ec72 TG |
481 | int r; |
482 | ||
483 | assert_return(rtnl, -EINVAL); | |
484 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
485 | assert_return(!rtnl->processing, -EBUSY); | |
486 | ||
487 | rtnl->processing = true; | |
488 | r = process_running(rtnl, ret); | |
489 | rtnl->processing = false; | |
490 | ||
491 | return r; | |
492 | } | |
493 | ||
494 | static usec_t calc_elapse(uint64_t usec) { | |
495 | if (usec == (uint64_t) -1) | |
496 | return 0; | |
497 | ||
498 | if (usec == 0) | |
499 | usec = RTNL_DEFAULT_TIMEOUT; | |
500 | ||
501 | return now(CLOCK_MONOTONIC) + usec; | |
502 | } | |
503 | ||
b4f2a5b1 | 504 | static int rtnl_poll(sd_rtnl *rtnl, bool need_more, uint64_t timeout_usec) { |
4555ec72 TG |
505 | struct pollfd p[1] = {}; |
506 | struct timespec ts; | |
3a43da28 | 507 | usec_t m = USEC_INFINITY; |
b4f2a5b1 TG |
508 | int r, e; |
509 | ||
510 | assert(rtnl); | |
4555ec72 | 511 | |
b4f2a5b1 TG |
512 | e = sd_rtnl_get_events(rtnl); |
513 | if (e < 0) | |
514 | return e; | |
4555ec72 | 515 | |
b4f2a5b1 TG |
516 | if (need_more) |
517 | /* Caller wants more data, and doesn't care about | |
518 | * what's been read or any other timeouts. */ | |
f55dc7c9 | 519 | e |= POLLIN; |
b4f2a5b1 TG |
520 | else { |
521 | usec_t until; | |
522 | /* Caller wants to process if there is something to | |
523 | * process, but doesn't care otherwise */ | |
524 | ||
525 | r = sd_rtnl_get_timeout(rtnl, &until); | |
526 | if (r < 0) | |
527 | return r; | |
528 | if (r > 0) { | |
529 | usec_t nw; | |
530 | nw = now(CLOCK_MONOTONIC); | |
531 | m = until > nw ? until - nw : 0; | |
532 | } | |
533 | } | |
65f568bb | 534 | |
b4f2a5b1 TG |
535 | if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) |
536 | m = timeout_usec; | |
537 | ||
538 | p[0].fd = rtnl->fd; | |
539 | p[0].events = e; | |
540 | ||
541 | r = ppoll(p, 1, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); | |
4555ec72 TG |
542 | if (r < 0) |
543 | return -errno; | |
544 | ||
545 | return r > 0 ? 1 : 0; | |
546 | } | |
547 | ||
548 | int sd_rtnl_wait(sd_rtnl *nl, uint64_t timeout_usec) { | |
549 | assert_return(nl, -EINVAL); | |
550 | assert_return(!rtnl_pid_changed(nl), -ECHILD); | |
551 | ||
552 | if (nl->rqueue_size > 0) | |
553 | return 0; | |
554 | ||
b4f2a5b1 | 555 | return rtnl_poll(nl, false, timeout_usec); |
4555ec72 TG |
556 | } |
557 | ||
e16bcf98 TG |
558 | static int timeout_compare(const void *a, const void *b) { |
559 | const struct reply_callback *x = a, *y = b; | |
560 | ||
561 | if (x->timeout != 0 && y->timeout == 0) | |
562 | return -1; | |
563 | ||
564 | if (x->timeout == 0 && y->timeout != 0) | |
565 | return 1; | |
566 | ||
567 | if (x->timeout < y->timeout) | |
568 | return -1; | |
569 | ||
570 | if (x->timeout > y->timeout) | |
571 | return 1; | |
572 | ||
573 | return 0; | |
574 | } | |
575 | ||
576 | int sd_rtnl_call_async(sd_rtnl *nl, | |
577 | sd_rtnl_message *m, | |
578 | sd_rtnl_message_handler_t callback, | |
579 | void *userdata, | |
580 | uint64_t usec, | |
581 | uint32_t *serial) { | |
582 | struct reply_callback *c; | |
583 | uint32_t s; | |
584 | int r, k; | |
585 | ||
586 | assert_return(nl, -EINVAL); | |
587 | assert_return(m, -EINVAL); | |
588 | assert_return(callback, -EINVAL); | |
589 | assert_return(!rtnl_pid_changed(nl), -ECHILD); | |
590 | ||
d5099efc | 591 | r = hashmap_ensure_allocated(&nl->reply_callbacks, &uint64_hash_ops); |
e16bcf98 TG |
592 | if (r < 0) |
593 | return r; | |
594 | ||
595 | if (usec != (uint64_t) -1) { | |
596 | r = prioq_ensure_allocated(&nl->reply_callbacks_prioq, timeout_compare); | |
597 | if (r < 0) | |
598 | return r; | |
599 | } | |
600 | ||
601 | c = new0(struct reply_callback, 1); | |
602 | if (!c) | |
603 | return -ENOMEM; | |
604 | ||
605 | c->callback = callback; | |
606 | c->userdata = userdata; | |
607 | c->timeout = calc_elapse(usec); | |
608 | ||
609 | k = sd_rtnl_send(nl, m, &s); | |
610 | if (k < 0) { | |
611 | free(c); | |
612 | return k; | |
613 | } | |
614 | ||
615 | c->serial = s; | |
616 | ||
617 | r = hashmap_put(nl->reply_callbacks, &c->serial, c); | |
618 | if (r < 0) { | |
619 | free(c); | |
620 | return r; | |
621 | } | |
622 | ||
623 | if (c->timeout != 0) { | |
624 | r = prioq_put(nl->reply_callbacks_prioq, c, &c->prioq_idx); | |
625 | if (r > 0) { | |
626 | c->timeout = 0; | |
627 | sd_rtnl_call_async_cancel(nl, c->serial); | |
628 | return r; | |
629 | } | |
630 | } | |
631 | ||
632 | if (serial) | |
633 | *serial = s; | |
634 | ||
635 | return k; | |
636 | } | |
637 | ||
638 | int sd_rtnl_call_async_cancel(sd_rtnl *nl, uint32_t serial) { | |
639 | struct reply_callback *c; | |
640 | uint64_t s = serial; | |
641 | ||
642 | assert_return(nl, -EINVAL); | |
643 | assert_return(serial != 0, -EINVAL); | |
644 | assert_return(!rtnl_pid_changed(nl), -ECHILD); | |
645 | ||
646 | c = hashmap_remove(nl->reply_callbacks, &s); | |
647 | if (!c) | |
648 | return 0; | |
649 | ||
650 | if (c->timeout != 0) | |
651 | prioq_remove(nl->reply_callbacks_prioq, c, &c->prioq_idx); | |
652 | ||
653 | free(c); | |
654 | return 1; | |
655 | } | |
656 | ||
1b89cf56 | 657 | int sd_rtnl_call(sd_rtnl *rtnl, |
4555ec72 TG |
658 | sd_rtnl_message *message, |
659 | uint64_t usec, | |
660 | sd_rtnl_message **ret) { | |
661 | usec_t timeout; | |
662 | uint32_t serial; | |
1b89cf56 | 663 | unsigned i = 0; |
4555ec72 TG |
664 | int r; |
665 | ||
1b89cf56 TG |
666 | assert_return(rtnl, -EINVAL); |
667 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
4555ec72 TG |
668 | assert_return(message, -EINVAL); |
669 | ||
1b89cf56 | 670 | r = sd_rtnl_send(rtnl, message, &serial); |
4555ec72 TG |
671 | if (r < 0) |
672 | return r; | |
673 | ||
674 | timeout = calc_elapse(usec); | |
675 | ||
65f568bb | 676 | for (;;) { |
4555ec72 | 677 | usec_t left; |
65f568bb | 678 | |
1b89cf56 TG |
679 | while (i < rtnl->rqueue_size) { |
680 | sd_rtnl_message *incoming; | |
681 | uint32_t received_serial; | |
65f568bb | 682 | |
1b89cf56 TG |
683 | incoming = rtnl->rqueue[i]; |
684 | received_serial = rtnl_message_get_serial(incoming); | |
65f568bb TG |
685 | |
686 | if (received_serial == serial) { | |
1b89cf56 TG |
687 | /* found a match, remove from rqueue and return it */ |
688 | memmove(rtnl->rqueue + i,rtnl->rqueue + i + 1, | |
689 | sizeof(sd_rtnl_message*) * (rtnl->rqueue_size - i - 1)); | |
690 | rtnl->rqueue_size--; | |
691 | ||
e16bcf98 | 692 | r = sd_rtnl_message_get_errno(incoming); |
1b89cf56 TG |
693 | if (r < 0) { |
694 | sd_rtnl_message_unref(incoming); | |
65f568bb | 695 | return r; |
1b89cf56 | 696 | } |
65f568bb | 697 | |
fe5c4e49 | 698 | if (ret) { |
4555ec72 | 699 | *ret = incoming; |
1b89cf56 TG |
700 | } else |
701 | sd_rtnl_message_unref(incoming); | |
65f568bb | 702 | |
4555ec72 | 703 | return 1; |
65f568bb | 704 | } |
4555ec72 | 705 | |
4555ec72 | 706 | /* Try to read more, right away */ |
1b89cf56 | 707 | i ++; |
65f568bb | 708 | } |
1b89cf56 TG |
709 | |
710 | r = socket_read_message(rtnl); | |
711 | if (r < 0) | |
712 | return r; | |
713 | if (r > 0) | |
6ff8806e | 714 | /* received message, so try to process straight away */ |
4555ec72 | 715 | continue; |
65f568bb | 716 | |
4555ec72 TG |
717 | if (timeout > 0) { |
718 | usec_t n; | |
719 | ||
720 | n = now(CLOCK_MONOTONIC); | |
721 | if (n >= timeout) | |
722 | return -ETIMEDOUT; | |
723 | ||
724 | left = timeout - n; | |
725 | } else | |
726 | left = (uint64_t) -1; | |
727 | ||
1b89cf56 | 728 | r = rtnl_poll(rtnl, true, left); |
4555ec72 TG |
729 | if (r < 0) |
730 | return r; | |
b551ddd3 TG |
731 | else if (r == 0) |
732 | return -ETIMEDOUT; | |
4555ec72 | 733 | |
1b89cf56 | 734 | r = dispatch_wqueue(rtnl); |
4555ec72 TG |
735 | if (r < 0) |
736 | return r; | |
737 | } | |
65f568bb | 738 | } |
b4f2a5b1 TG |
739 | |
740 | int sd_rtnl_flush(sd_rtnl *rtnl) { | |
741 | int r; | |
742 | ||
743 | assert_return(rtnl, -EINVAL); | |
744 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
745 | ||
746 | if (rtnl->wqueue_size <= 0) | |
747 | return 0; | |
748 | ||
749 | for (;;) { | |
750 | r = dispatch_wqueue(rtnl); | |
751 | if (r < 0) | |
752 | return r; | |
753 | ||
754 | if (rtnl->wqueue_size <= 0) | |
755 | return 0; | |
756 | ||
757 | r = rtnl_poll(rtnl, false, (uint64_t) -1); | |
758 | if (r < 0) | |
759 | return r; | |
760 | } | |
761 | } | |
762 | ||
763 | int sd_rtnl_get_events(sd_rtnl *rtnl) { | |
764 | int flags = 0; | |
765 | ||
766 | assert_return(rtnl, -EINVAL); | |
767 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
768 | ||
769 | if (rtnl->rqueue_size <= 0) | |
770 | flags |= POLLIN; | |
771 | if (rtnl->wqueue_size > 0) | |
772 | flags |= POLLOUT; | |
773 | ||
774 | return flags; | |
775 | } | |
776 | ||
777 | int sd_rtnl_get_timeout(sd_rtnl *rtnl, uint64_t *timeout_usec) { | |
778 | struct reply_callback *c; | |
779 | ||
780 | assert_return(rtnl, -EINVAL); | |
781 | assert_return(timeout_usec, -EINVAL); | |
782 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
783 | ||
784 | if (rtnl->rqueue_size > 0) { | |
785 | *timeout_usec = 0; | |
786 | return 1; | |
787 | } | |
788 | ||
789 | c = prioq_peek(rtnl->reply_callbacks_prioq); | |
790 | if (!c) { | |
791 | *timeout_usec = (uint64_t) -1; | |
792 | return 0; | |
793 | } | |
794 | ||
795 | *timeout_usec = c->timeout; | |
796 | ||
797 | return 1; | |
798 | } | |
799 | ||
800 | static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { | |
801 | sd_rtnl *rtnl = userdata; | |
802 | int r; | |
803 | ||
804 | assert(rtnl); | |
805 | ||
806 | r = sd_rtnl_process(rtnl, NULL); | |
807 | if (r < 0) | |
808 | return r; | |
809 | ||
810 | return 1; | |
811 | } | |
812 | ||
813 | static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { | |
814 | sd_rtnl *rtnl = userdata; | |
815 | int r; | |
816 | ||
817 | assert(rtnl); | |
818 | ||
819 | r = sd_rtnl_process(rtnl, NULL); | |
820 | if (r < 0) | |
821 | return r; | |
822 | ||
823 | return 1; | |
824 | } | |
825 | ||
826 | static int prepare_callback(sd_event_source *s, void *userdata) { | |
827 | sd_rtnl *rtnl = userdata; | |
828 | int r, e; | |
829 | usec_t until; | |
830 | ||
831 | assert(s); | |
832 | assert(rtnl); | |
833 | ||
834 | e = sd_rtnl_get_events(rtnl); | |
835 | if (e < 0) | |
836 | return e; | |
837 | ||
838 | r = sd_event_source_set_io_events(rtnl->io_event_source, e); | |
839 | if (r < 0) | |
840 | return r; | |
841 | ||
842 | r = sd_rtnl_get_timeout(rtnl, &until); | |
843 | if (r < 0) | |
844 | return r; | |
845 | if (r > 0) { | |
846 | int j; | |
847 | ||
848 | j = sd_event_source_set_time(rtnl->time_event_source, until); | |
849 | if (j < 0) | |
850 | return j; | |
851 | } | |
852 | ||
853 | r = sd_event_source_set_enabled(rtnl->time_event_source, r > 0); | |
854 | if (r < 0) | |
855 | return r; | |
856 | ||
857 | return 1; | |
858 | } | |
859 | ||
6203e07a | 860 | static int exit_callback(sd_event_source *event, void *userdata) { |
b4f2a5b1 TG |
861 | sd_rtnl *rtnl = userdata; |
862 | ||
863 | assert(event); | |
864 | ||
865 | sd_rtnl_flush(rtnl); | |
866 | ||
867 | return 1; | |
868 | } | |
869 | ||
870 | int sd_rtnl_attach_event(sd_rtnl *rtnl, sd_event *event, int priority) { | |
871 | int r; | |
872 | ||
873 | assert_return(rtnl, -EINVAL); | |
874 | assert_return(!rtnl->event, -EBUSY); | |
875 | ||
876 | assert(!rtnl->io_event_source); | |
877 | assert(!rtnl->time_event_source); | |
878 | ||
879 | if (event) | |
880 | rtnl->event = sd_event_ref(event); | |
881 | else { | |
882 | r = sd_event_default(&rtnl->event); | |
883 | if (r < 0) | |
884 | return r; | |
885 | } | |
886 | ||
151b9b96 | 887 | r = sd_event_add_io(rtnl->event, &rtnl->io_event_source, rtnl->fd, 0, io_callback, rtnl); |
b4f2a5b1 TG |
888 | if (r < 0) |
889 | goto fail; | |
890 | ||
891 | r = sd_event_source_set_priority(rtnl->io_event_source, priority); | |
892 | if (r < 0) | |
893 | goto fail; | |
894 | ||
356779df | 895 | r = sd_event_source_set_description(rtnl->io_event_source, "rtnl-receive-message"); |
9021bb9f TG |
896 | if (r < 0) |
897 | goto fail; | |
898 | ||
b4f2a5b1 TG |
899 | r = sd_event_source_set_prepare(rtnl->io_event_source, prepare_callback); |
900 | if (r < 0) | |
901 | goto fail; | |
902 | ||
6a0f1f6d | 903 | r = sd_event_add_time(rtnl->event, &rtnl->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, rtnl); |
b4f2a5b1 TG |
904 | if (r < 0) |
905 | goto fail; | |
906 | ||
907 | r = sd_event_source_set_priority(rtnl->time_event_source, priority); | |
908 | if (r < 0) | |
909 | goto fail; | |
910 | ||
356779df | 911 | r = sd_event_source_set_description(rtnl->time_event_source, "rtnl-timer"); |
9021bb9f TG |
912 | if (r < 0) |
913 | goto fail; | |
914 | ||
151b9b96 | 915 | r = sd_event_add_exit(rtnl->event, &rtnl->exit_event_source, exit_callback, rtnl); |
b4f2a5b1 TG |
916 | if (r < 0) |
917 | goto fail; | |
918 | ||
356779df | 919 | r = sd_event_source_set_description(rtnl->exit_event_source, "rtnl-exit"); |
9021bb9f TG |
920 | if (r < 0) |
921 | goto fail; | |
922 | ||
b4f2a5b1 TG |
923 | return 0; |
924 | ||
925 | fail: | |
926 | sd_rtnl_detach_event(rtnl); | |
927 | return r; | |
928 | } | |
929 | ||
930 | int sd_rtnl_detach_event(sd_rtnl *rtnl) { | |
931 | assert_return(rtnl, -EINVAL); | |
932 | assert_return(rtnl->event, -ENXIO); | |
933 | ||
934 | if (rtnl->io_event_source) | |
935 | rtnl->io_event_source = sd_event_source_unref(rtnl->io_event_source); | |
936 | ||
937 | if (rtnl->time_event_source) | |
938 | rtnl->time_event_source = sd_event_source_unref(rtnl->time_event_source); | |
939 | ||
6203e07a LP |
940 | if (rtnl->exit_event_source) |
941 | rtnl->exit_event_source = sd_event_source_unref(rtnl->exit_event_source); | |
b4f2a5b1 TG |
942 | |
943 | if (rtnl->event) | |
944 | rtnl->event = sd_event_unref(rtnl->event); | |
945 | ||
946 | return 0; | |
947 | } | |
8cec01b9 TG |
948 | |
949 | int sd_rtnl_add_match(sd_rtnl *rtnl, | |
23a7f0f7 | 950 | uint16_t type, |
8cec01b9 TG |
951 | sd_rtnl_message_handler_t callback, |
952 | void *userdata) { | |
953 | struct match_callback *c; | |
954 | ||
955 | assert_return(rtnl, -EINVAL); | |
956 | assert_return(callback, -EINVAL); | |
8cec01b9 | 957 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); |
3815f36f TG |
958 | assert_return(rtnl_message_type_is_link(type) || |
959 | rtnl_message_type_is_addr(type) || | |
960 | rtnl_message_type_is_route(type), -ENOTSUP); | |
8cec01b9 TG |
961 | |
962 | c = new0(struct match_callback, 1); | |
963 | if (!c) | |
964 | return -ENOMEM; | |
965 | ||
966 | c->callback = callback; | |
23a7f0f7 | 967 | c->type = type; |
8cec01b9 TG |
968 | c->userdata = userdata; |
969 | ||
970 | LIST_PREPEND(match_callbacks, rtnl->match_callbacks, c); | |
971 | ||
972 | return 0; | |
973 | } | |
974 | ||
975 | int sd_rtnl_remove_match(sd_rtnl *rtnl, | |
23a7f0f7 | 976 | uint16_t type, |
8cec01b9 TG |
977 | sd_rtnl_message_handler_t callback, |
978 | void *userdata) { | |
979 | struct match_callback *c; | |
980 | ||
981 | assert_return(rtnl, -EINVAL); | |
982 | assert_return(callback, -EINVAL); | |
983 | assert_return(!rtnl_pid_changed(rtnl), -ECHILD); | |
984 | ||
985 | LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) | |
23a7f0f7 | 986 | if (c->callback == callback && c->type == type && c->userdata == userdata) { |
8cec01b9 TG |
987 | LIST_REMOVE(match_callbacks, rtnl->match_callbacks, c); |
988 | free(c); | |
989 | ||
990 | return 1; | |
991 | } | |
992 | ||
993 | return 0; | |
994 | } |