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