]> git.proxmox.com Git - mirror_frr.git/blob - pimd/mtracebis_netlink.c
pimd: standard library usage (PVS-Studio)
[mirror_frr.git] / pimd / mtracebis_netlink.c
1 /*
2 * libnetlink.c RTnetlink service routines.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version
7 * 2 of the License, or (at your option) any later version.
8 *
9 * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
10 *
11 */
12
13 #ifdef __linux__
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <syslog.h>
19 #include <fcntl.h>
20 #include <net/if_arp.h>
21 #include <sys/socket.h>
22 #include <netinet/in.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <time.h>
26 #include <sys/uio.h>
27 #include <assert.h>
28
29 #include "mtracebis_netlink.h"
30
31 int rcvbuf = 1024 * 1024;
32
33 void rtnl_close(struct rtnl_handle *rth)
34 {
35 if (rth->fd >= 0) {
36 close(rth->fd);
37 rth->fd = -1;
38 }
39 }
40
41 int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions,
42 int protocol)
43 {
44 socklen_t addr_len;
45 int sndbuf = 32768;
46
47 memset(rth, 0, sizeof(*rth));
48
49 rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol);
50 if (rth->fd < 0) {
51 perror("Cannot open netlink socket");
52 return -1;
53 }
54
55 if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))
56 < 0) {
57 perror("SO_SNDBUF");
58 return -1;
59 }
60
61 if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf))
62 < 0) {
63 perror("SO_RCVBUF");
64 return -1;
65 }
66
67 memset(&rth->local, 0, sizeof(rth->local));
68 rth->local.nl_family = AF_NETLINK;
69 rth->local.nl_groups = subscriptions;
70
71 if (bind(rth->fd, (struct sockaddr *)&rth->local, sizeof(rth->local))
72 < 0) {
73 perror("Cannot bind netlink socket");
74 return -1;
75 }
76 addr_len = sizeof(rth->local);
77 if (getsockname(rth->fd, (struct sockaddr *)&rth->local, &addr_len)
78 < 0) {
79 perror("Cannot getsockname");
80 return -1;
81 }
82 if (addr_len != sizeof(rth->local)) {
83 fprintf(stderr, "Wrong address length %d\n", addr_len);
84 return -1;
85 }
86 if (rth->local.nl_family != AF_NETLINK) {
87 fprintf(stderr, "Wrong address family %d\n",
88 rth->local.nl_family);
89 return -1;
90 }
91 rth->seq = time(NULL);
92 return 0;
93 }
94
95 int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions)
96 {
97 return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
98 }
99
100 int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type)
101 {
102 struct {
103 struct nlmsghdr nlh;
104 struct rtgenmsg g;
105 } req;
106
107 memset(&req, 0, sizeof(req));
108 req.nlh.nlmsg_len = sizeof(req);
109 req.nlh.nlmsg_type = type;
110 req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
111 req.nlh.nlmsg_pid = 0;
112 req.nlh.nlmsg_seq = rth->dump = ++rth->seq;
113 req.g.rtgen_family = family;
114
115 return send(rth->fd, (void *)&req, sizeof(req), 0);
116 }
117
118 int rtnl_send(struct rtnl_handle *rth, const char *buf, int len)
119 {
120 return send(rth->fd, buf, len, 0);
121 }
122
123 int rtnl_send_check(struct rtnl_handle *rth, const char *buf, int len)
124 {
125 struct nlmsghdr *h;
126 int status;
127 char resp[1024];
128
129 status = send(rth->fd, buf, len, 0);
130 if (status < 0)
131 return status;
132
133 /* Check for immediate errors */
134 status = recv(rth->fd, resp, sizeof(resp), MSG_DONTWAIT | MSG_PEEK);
135 if (status < 0) {
136 if (errno == EAGAIN)
137 return 0;
138 return -1;
139 }
140
141 for (h = (struct nlmsghdr *)resp; NLMSG_OK(h, (uint32_t)status);
142 h = NLMSG_NEXT(h, status)) {
143 if (h->nlmsg_type == NLMSG_ERROR) {
144 struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h);
145 if (h->nlmsg_len
146 < NLMSG_LENGTH(sizeof(struct nlmsgerr)))
147 fprintf(stderr, "ERROR truncated\n");
148 else
149 errno = -err->error;
150 return -1;
151 }
152 }
153
154 return 0;
155 }
156
157 int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len)
158 {
159 struct nlmsghdr nlh;
160 struct sockaddr_nl nladdr;
161 struct iovec iov[2] = {{.iov_base = &nlh, .iov_len = sizeof(nlh)},
162 {.iov_base = req, .iov_len = len}};
163 struct msghdr msg = {
164 .msg_name = &nladdr,
165 .msg_namelen = sizeof(nladdr),
166 .msg_iov = iov,
167 .msg_iovlen = 2,
168 };
169
170 memset(&nladdr, 0, sizeof(nladdr));
171 nladdr.nl_family = AF_NETLINK;
172
173 nlh.nlmsg_len = NLMSG_LENGTH(len);
174 nlh.nlmsg_type = type;
175 nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
176 nlh.nlmsg_pid = 0;
177 nlh.nlmsg_seq = rth->dump = ++rth->seq;
178
179 return sendmsg(rth->fd, &msg, 0);
180 }
181
182 int rtnl_dump_filter_l(struct rtnl_handle *rth,
183 const struct rtnl_dump_filter_arg *arg)
184 {
185 struct sockaddr_nl nladdr;
186 struct iovec iov;
187 struct msghdr msg = {
188 .msg_name = &nladdr,
189 .msg_namelen = sizeof(nladdr),
190 .msg_iov = &iov,
191 .msg_iovlen = 1,
192 };
193 char buf[16384];
194
195 iov.iov_base = buf;
196 while (1) {
197 int status;
198 const struct rtnl_dump_filter_arg *a;
199 int found_done = 0;
200 int msglen = 0;
201
202 iov.iov_len = sizeof(buf);
203 status = recvmsg(rth->fd, &msg, 0);
204
205 if (status < 0) {
206 if (errno == EINTR || errno == EAGAIN)
207 continue;
208 fprintf(stderr, "netlink receive error %s (%d)\n",
209 strerror(errno), errno);
210 return -1;
211 }
212
213 if (status == 0) {
214 fprintf(stderr, "EOF on netlink\n");
215 return -1;
216 }
217
218 for (a = arg; a->filter; a++) {
219 struct nlmsghdr *h = (struct nlmsghdr *)buf;
220 msglen = status;
221
222 while (NLMSG_OK(h, (uint32_t)msglen)) {
223 int err;
224
225 if (nladdr.nl_pid != 0
226 || h->nlmsg_pid != rth->local.nl_pid
227 || h->nlmsg_seq != rth->dump) {
228 if (a->junk) {
229 err = a->junk(&nladdr, h,
230 a->arg2);
231 if (err < 0)
232 return err;
233 }
234 goto skip_it;
235 }
236
237 if (h->nlmsg_type == NLMSG_DONE) {
238 found_done = 1;
239 break; /* process next filter */
240 }
241 if (h->nlmsg_type == NLMSG_ERROR) {
242 struct nlmsgerr *err =
243 (struct nlmsgerr *)NLMSG_DATA(
244 h);
245 if (h->nlmsg_len
246 < NLMSG_LENGTH(sizeof(
247 struct nlmsgerr))) {
248 fprintf(stderr,
249 "ERROR truncated\n");
250 } else {
251 errno = -err->error;
252 perror("RTNETLINK answers");
253 }
254 return -1;
255 }
256 err = a->filter(&nladdr, h, a->arg1);
257 if (err < 0)
258 return err;
259
260 skip_it:
261 h = NLMSG_NEXT(h, msglen);
262 }
263 }
264
265 if (found_done)
266 return 0;
267
268 if (msg.msg_flags & MSG_TRUNC) {
269 fprintf(stderr, "Message truncated\n");
270 continue;
271 }
272 if (msglen) {
273 fprintf(stderr, "!!!Remnant of size %d\n", msglen);
274 exit(1);
275 }
276 }
277 }
278
279 int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, void *arg1,
280 rtnl_filter_t junk, void *arg2)
281 {
282 const struct rtnl_dump_filter_arg a[2] = {
283 {.filter = filter, .arg1 = arg1, .junk = junk, .arg2 = arg2},
284 {.filter = NULL, .arg1 = NULL, .junk = NULL, .arg2 = NULL}};
285
286 return rtnl_dump_filter_l(rth, a);
287 }
288
289 int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer,
290 unsigned groups, struct nlmsghdr *answer, rtnl_filter_t junk,
291 void *jarg)
292 {
293 int status;
294 unsigned seq;
295 struct nlmsghdr *h;
296 struct sockaddr_nl nladdr;
297 struct iovec iov = {.iov_base = (void *)n, .iov_len = n->nlmsg_len};
298 struct msghdr msg = {
299 .msg_name = &nladdr,
300 .msg_namelen = sizeof(nladdr),
301 .msg_iov = &iov,
302 .msg_iovlen = 1,
303 };
304 char buf[16384];
305
306 memset(&nladdr, 0, sizeof(nladdr));
307 nladdr.nl_family = AF_NETLINK;
308 nladdr.nl_pid = peer;
309 nladdr.nl_groups = groups;
310
311 n->nlmsg_seq = seq = ++rtnl->seq;
312
313 if (answer == NULL)
314 n->nlmsg_flags |= NLM_F_ACK;
315
316 status = sendmsg(rtnl->fd, &msg, 0);
317
318 if (status < 0) {
319 perror("Cannot talk to rtnetlink");
320 return -1;
321 }
322
323 memset(buf, 0, sizeof(buf));
324
325 iov.iov_base = buf;
326
327 while (1) {
328 iov.iov_len = sizeof(buf);
329 status = recvmsg(rtnl->fd, &msg, 0);
330
331 if (status < 0) {
332 if (errno == EINTR || errno == EAGAIN)
333 continue;
334 fprintf(stderr, "netlink receive error %s (%d)\n",
335 strerror(errno), errno);
336 return -1;
337 }
338 if (status == 0) {
339 fprintf(stderr, "EOF on netlink\n");
340 return -1;
341 }
342 if (msg.msg_namelen != sizeof(nladdr)) {
343 fprintf(stderr, "sender address length == %d\n",
344 msg.msg_namelen);
345 exit(1);
346 }
347 for (h = (struct nlmsghdr *)buf; status >= (int)sizeof(*h);) {
348 int err;
349 int len = h->nlmsg_len;
350 int l = len - sizeof(*h);
351
352 if (l < 0 || len > status) {
353 if (msg.msg_flags & MSG_TRUNC) {
354 fprintf(stderr, "Truncated message\n");
355 return -1;
356 }
357 fprintf(stderr,
358 "!!!malformed message: len=%d\n", len);
359 exit(1);
360 }
361
362 if ((int)nladdr.nl_pid != peer
363 || h->nlmsg_pid != rtnl->local.nl_pid
364 || h->nlmsg_seq != seq) {
365 if (junk) {
366 err = junk(&nladdr, h, jarg);
367 if (err < 0)
368 return err;
369 }
370 /* Don't forget to skip that message. */
371 status -= NLMSG_ALIGN(len);
372 h = (struct nlmsghdr *)((char *)h
373 + NLMSG_ALIGN(len));
374 continue;
375 }
376
377 if (h->nlmsg_type == NLMSG_ERROR) {
378 struct nlmsgerr *err =
379 (struct nlmsgerr *)NLMSG_DATA(h);
380 if (l < (int)sizeof(struct nlmsgerr)) {
381 fprintf(stderr, "ERROR truncated\n");
382 } else {
383 errno = -err->error;
384 if (errno == 0) {
385 if (answer)
386 memcpy(answer, h,
387 h->nlmsg_len);
388 return 0;
389 }
390 perror("RTNETLINK answers");
391 }
392 return -1;
393 }
394 if (answer) {
395 memcpy(answer, h, h->nlmsg_len);
396 return 0;
397 }
398
399 fprintf(stderr, "Unexpected reply!!!\n");
400
401 status -= NLMSG_ALIGN(len);
402 h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
403 }
404 if (msg.msg_flags & MSG_TRUNC) {
405 fprintf(stderr, "Message truncated\n");
406 continue;
407 }
408 if (status) {
409 fprintf(stderr, "!!!Remnant of size %d\n", status);
410 exit(1);
411 }
412 }
413 }
414
415 int rtnl_listen(struct rtnl_handle *rtnl, rtnl_filter_t handler, void *jarg)
416 {
417 int status;
418 struct nlmsghdr *h;
419 struct sockaddr_nl nladdr;
420 struct iovec iov;
421 struct msghdr msg = {
422 .msg_name = &nladdr,
423 .msg_namelen = sizeof(nladdr),
424 .msg_iov = &iov,
425 .msg_iovlen = 1,
426 };
427 char buf[8192];
428
429 memset(&nladdr, 0, sizeof(nladdr));
430 nladdr.nl_family = AF_NETLINK;
431 nladdr.nl_pid = 0;
432 nladdr.nl_groups = 0;
433
434 iov.iov_base = buf;
435 while (1) {
436 iov.iov_len = sizeof(buf);
437 status = recvmsg(rtnl->fd, &msg, 0);
438
439 if (status < 0) {
440 if (errno == EINTR || errno == EAGAIN)
441 continue;
442 fprintf(stderr, "netlink receive error %s (%d)\n",
443 strerror(errno), errno);
444 if (errno == ENOBUFS)
445 continue;
446 return -1;
447 }
448 if (status == 0) {
449 fprintf(stderr, "EOF on netlink\n");
450 return -1;
451 }
452 if (msg.msg_namelen != sizeof(nladdr)) {
453 fprintf(stderr, "Sender address length == %d\n",
454 msg.msg_namelen);
455 exit(1);
456 }
457 for (h = (struct nlmsghdr *)buf; status >= (int)sizeof(*h);) {
458 int err;
459 int len = h->nlmsg_len;
460 int l = len - sizeof(*h);
461
462 if (l < 0 || len > status) {
463 if (msg.msg_flags & MSG_TRUNC) {
464 fprintf(stderr, "Truncated message\n");
465 return -1;
466 }
467 fprintf(stderr,
468 "!!!malformed message: len=%d\n", len);
469 exit(1);
470 }
471
472 err = handler(&nladdr, h, jarg);
473 if (err < 0)
474 return err;
475
476 status -= NLMSG_ALIGN(len);
477 h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
478 }
479 if (msg.msg_flags & MSG_TRUNC) {
480 fprintf(stderr, "Message truncated\n");
481 continue;
482 }
483 if (status) {
484 fprintf(stderr, "!!!Remnant of size %d\n", status);
485 exit(1);
486 }
487 }
488 }
489
490 int rtnl_from_file(FILE *rtnl, rtnl_filter_t handler, void *jarg)
491 {
492 struct sockaddr_nl nladdr;
493 char buf[8192];
494 struct nlmsghdr *h = (void *)buf;
495
496 memset(&nladdr, 0, sizeof(nladdr));
497 nladdr.nl_family = AF_NETLINK;
498 nladdr.nl_pid = 0;
499 nladdr.nl_groups = 0;
500
501 while (1) {
502 int err;
503 size_t l, rl, arl;
504
505 rl = sizeof(*h);
506 arl = fread(&buf, 1, rl, rtnl);
507
508 if (arl != rl) {
509 if (arl == 0)
510 return 0;
511
512 if (ferror(rtnl))
513 fprintf(stderr, "%s: header read failed\n",
514 __func__);
515 else
516 fprintf(stderr, "%s: truncated header\n",
517 __func__);
518 return -1;
519 }
520
521 l = h->nlmsg_len > rl ? h->nlmsg_len - rl : 0;
522
523 if (l == 0 || (l + (size_t)NLMSG_HDRLEN) > sizeof(buf)) {
524 fprintf(stderr, "%s: malformed message: len=%zu @%lu\n",
525 __func__, (size_t)h->nlmsg_len, ftell(rtnl));
526 return -1;
527 }
528
529 rl = NLMSG_ALIGN(l);
530 arl = fread(NLMSG_DATA(h), 1, rl, rtnl);
531
532 if (arl != rl) {
533 if (ferror(rtnl))
534 fprintf(stderr, "%s: msg read failed\n",
535 __func__);
536 else
537 fprintf(stderr, "%s: truncated message\n",
538 __func__);
539 return -1;
540 }
541
542 err = handler(&nladdr, h, jarg);
543 if (err < 0)
544 return err;
545 }
546 }
547
548 int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data)
549 {
550 int len = RTA_LENGTH(4);
551 struct rtattr *rta;
552 if ((int)(NLMSG_ALIGN(n->nlmsg_len) + len) > maxlen) {
553 fprintf(stderr,
554 "addattr32: Error! max allowed bound %d exceeded\n",
555 maxlen);
556 return -1;
557 }
558 rta = NLMSG_TAIL(n);
559 rta->rta_type = type;
560 rta->rta_len = len;
561 memcpy(RTA_DATA(rta), &data, 4);
562 n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
563 return 0;
564 }
565
566 int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data,
567 int alen)
568 {
569 int len = RTA_LENGTH(alen);
570 struct rtattr *rta;
571
572 if ((int)(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen) {
573 fprintf(stderr,
574 "addattr_l ERROR: message exceeded bound of %d\n",
575 maxlen);
576 return -1;
577 }
578 rta = NLMSG_TAIL(n);
579 rta->rta_type = type;
580 rta->rta_len = len;
581
582 if (data)
583 memcpy(RTA_DATA(rta), data, alen);
584 else
585 assert(alen == 0);
586
587 n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
588 return 0;
589 }
590
591 int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len)
592 {
593 if ((int)(NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len)) > maxlen) {
594 fprintf(stderr,
595 "addraw_l ERROR: message exceeded bound of %d\n",
596 maxlen);
597 return -1;
598 }
599
600 memcpy(NLMSG_TAIL(n), data, len);
601 memset((uint8_t *)NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len);
602 n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len);
603 return 0;
604 }
605
606 struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type)
607 {
608 struct rtattr *nest = NLMSG_TAIL(n);
609
610 addattr_l(n, maxlen, type, NULL, 0);
611 return nest;
612 }
613
614 int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest)
615 {
616 nest->rta_len = (uint8_t *)NLMSG_TAIL(n) - (uint8_t *)nest;
617 return n->nlmsg_len;
618 }
619
620 struct rtattr *addattr_nest_compat(struct nlmsghdr *n, int maxlen, int type,
621 const void *data, int len)
622 {
623 struct rtattr *start = NLMSG_TAIL(n);
624
625 addattr_l(n, maxlen, type, data, len);
626 addattr_nest(n, maxlen, type);
627 return start;
628 }
629
630 int addattr_nest_compat_end(struct nlmsghdr *n, struct rtattr *start)
631 {
632 struct rtattr *nest = start + NLMSG_ALIGN(start->rta_len);
633
634 start->rta_len = (uint8_t *)NLMSG_TAIL(n) - (uint8_t *)start;
635 addattr_nest_end(n, nest);
636 return n->nlmsg_len;
637 }
638
639 int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data)
640 {
641 int len = RTA_LENGTH(4);
642 struct rtattr *subrta;
643
644 if ((int)(RTA_ALIGN(rta->rta_len) + len) > maxlen) {
645 fprintf(stderr,
646 "rta_addattr32: Error! max allowed bound %d exceeded\n",
647 maxlen);
648 return -1;
649 }
650 subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len));
651 subrta->rta_type = type;
652 subrta->rta_len = len;
653 memcpy(RTA_DATA(subrta), &data, 4);
654 rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len;
655 return 0;
656 }
657
658 int rta_addattr_l(struct rtattr *rta, int maxlen, int type, const void *data,
659 int alen)
660 {
661 struct rtattr *subrta;
662 int len = RTA_LENGTH(alen);
663
664 if ((int)(RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len)) > maxlen) {
665 fprintf(stderr,
666 "rta_addattr_l: Error! max allowed bound %d exceeded\n",
667 maxlen);
668 return -1;
669 }
670 subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len));
671 subrta->rta_type = type;
672 subrta->rta_len = len;
673 memcpy(RTA_DATA(subrta), data, alen);
674 rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len);
675 return 0;
676 }
677
678 int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
679 {
680 memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
681 while (RTA_OK(rta, len)) {
682 if ((rta->rta_type <= max) && (!tb[rta->rta_type]))
683 tb[rta->rta_type] = rta;
684 rta = RTA_NEXT(rta, len);
685 }
686 if (len)
687 fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len,
688 rta->rta_len);
689 return 0;
690 }
691
692 int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta,
693 int len)
694 {
695 int i = 0;
696
697 memset(tb, 0, sizeof(struct rtattr *) * max);
698 while (RTA_OK(rta, len)) {
699 if (rta->rta_type <= max && i < max)
700 tb[i++] = rta;
701 rta = RTA_NEXT(rta, len);
702 }
703 if (len)
704 fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len,
705 rta->rta_len);
706 return i;
707 }
708
709 int __parse_rtattr_nested_compat(struct rtattr *tb[], int max,
710 struct rtattr *rta, int len)
711 {
712 if ((int)RTA_PAYLOAD(rta) < len)
713 return -1;
714 if (RTA_PAYLOAD(rta) >= RTA_ALIGN(len) + sizeof(struct rtattr)) {
715 rta = (struct rtattr *)(uint8_t *)RTA_DATA(rta)
716 + RTA_ALIGN(len);
717 return parse_rtattr_nested(tb, max, rta);
718 }
719 memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
720 return 0;
721 }
722
723 #endif /* __linux__ */