Add timeouts to 'tinc join'
[tinc] / src / event_select.c
1 /*
2     event_select.c -- select(2) support
3     Copyright (C) 2012-2022 Guus Sliepen <guus@tinc-vpn.org>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "system.h"
21
22 #include "event.h"
23 #include "utils.h"
24
25 static bool running = false;
26 static fd_set readfds;
27 static fd_set writefds;
28
29 void io_add(io_t *io, io_cb_t cb, void *data, int fd, int flags) {
30         if(io->cb) {
31                 return;
32         }
33
34         io->fd = fd;
35         io->cb = cb;
36         io->data = data;
37         io->node.data = io;
38
39         io_set(io, flags);
40
41         if(!splay_insert_node(&io_tree, &io->node)) {
42                 abort();
43         }
44 }
45
46 void io_set(io_t *io, int flags) {
47         if(flags == io->flags) {
48                 return;
49         }
50
51         io->flags = flags;
52
53         if(io->fd == -1) {
54                 return;
55         }
56
57         if(flags & IO_READ) {
58                 FD_SET(io->fd, &readfds);
59         } else {
60                 FD_CLR(io->fd, &readfds);
61         }
62
63         if(flags & IO_WRITE) {
64                 FD_SET(io->fd, &writefds);
65         } else {
66                 FD_CLR(io->fd, &writefds);
67         }
68 }
69
70 void io_del(io_t *io) {
71         if(io->cb) {
72                 io_set(io, 0);
73                 splay_unlink_node(&io_tree, &io->node);
74                 io->cb = NULL;
75         }
76 }
77
78 bool event_loop(void) {
79         running = true;
80
81         fd_set readable;
82         fd_set writable;
83
84         while(running) {
85                 struct timeval diff;
86                 struct timeval *tv = timeout_execute(&diff);
87
88                 memcpy(&readable, &readfds, sizeof(readable));
89                 memcpy(&writable, &writefds, sizeof(writable));
90
91                 int maxfds =  0;
92
93                 if(io_tree.tail) {
94                         io_t *last = io_tree.tail->data;
95                         maxfds = last->fd + 1;
96                 }
97
98                 int n = select(maxfds, &readable, &writable, NULL, tv);
99
100                 if(n < 0) {
101                         if(sockwouldblock(sockerrno)) {
102                                 continue;
103                         } else {
104                                 return false;
105                         }
106                 }
107
108                 if(!n) {
109                         continue;
110                 }
111
112                 unsigned int curgen = io_tree.generation;
113
114                 for splay_each(io_t, io, &io_tree) {
115                         if(FD_ISSET(io->fd, &writable)) {
116                                 io->cb(io->data, IO_WRITE);
117                         } else if(FD_ISSET(io->fd, &readable)) {
118                                 io->cb(io->data, IO_READ);
119                         } else {
120                                 continue;
121                         }
122
123                         /*
124                             There are scenarios in which the callback will remove another io_t from the tree
125                             (e.g. closing a double connection). Since splay_each does not support that, we
126                             need to exit the loop if that happens. That's okay, since any remaining events will
127                             get picked up by the next select() call.
128                         */
129                         if(curgen != io_tree.generation) {
130                                 break;
131                         }
132                 }
133         }
134
135         return true;
136 }
137
138 void event_exit(void) {
139         running = false;
140 }