Skip to content

Commit f0c6d54

Browse files
committed
Handle signals with kqueue
We used sigsetjmp() and siglogjmp() for signal handling, which is very hard to use correctly and too magical. Replace it with the kqueue(), which can notify events on sockets and signals. The get signals with kqueue, we need to block them. This has the nice property that no function in any thread will fail with EINTR when we receive a signal, which simplifies the code. We setup signal handling before we open the pidfile, so receiving a signal after the pidfile was created will always remove the pidfile. Before this change we has a small window when receiving a signal would terminate the process without removing the pidfile. To wait for connection or signal, we wait for kqueue events. If we receiving a signal we break the loop and exit normally, since termination by signal is normal. This can help programs running socket_vment that may be confused by exit code 1. Notes: - Keeping listen_fd in blocking mode to simplify the code. accept() should not block when we receive a EVFILT_READ event. We can change to non-blocking mode later if this assumption is wrong. Signed-off-by: Nir Soffer <nirsof@gmail.com>
1 parent 1a5c1d8 commit f0c6d54

File tree

1 file changed

+88
-24
lines changed

1 file changed

+88
-24
lines changed

main.c

Lines changed: 88 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
#include <errno.h>
44
#include <grp.h>
55
#include <sched.h>
6-
#include <setjmp.h>
6+
#include <signal.h>
77
#include <stdio.h>
88
#include <stdlib.h>
9+
#include <sys/event.h>
910
#include <sys/stat.h>
11+
#include <sys/time.h>
12+
#include <sys/types.h>
1013
#include <sys/uio.h>
1114
#include <sys/un.h>
1215
#include <unistd.h>
@@ -19,6 +22,8 @@
1922
#error "Requires macOS 10.15 or later"
2023
#endif
2124

25+
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
26+
2227
bool debug = false;
2328

2429
static const char *vmnet_strerror(vmnet_return_t v) {
@@ -280,12 +285,6 @@ static interface_ref start(struct state *state, struct cli_options *cliopt) {
280285
return iface;
281286
}
282287

283-
static sigjmp_buf jmpbuf;
284-
static void signalhandler(int signal) {
285-
INFOF("Received signal: %s", strsignal(signal));
286-
siglongjmp(jmpbuf, 1);
287-
}
288-
289288
static void stop(struct state *state, interface_ref iface) {
290289
if (iface == NULL) {
291290
return;
@@ -391,13 +390,55 @@ static int create_pidfile(const char *pidfile)
391390
return fd;
392391
}
393392

393+
static int setup_signals(int kq)
394+
{
395+
struct kevent changes[] = {
396+
{ .ident=SIGHUP, .filter=EVFILT_SIGNAL, .flags=EV_ADD },
397+
{ .ident=SIGINT, .filter=EVFILT_SIGNAL, .flags=EV_ADD },
398+
{ .ident=SIGTERM, .filter=EVFILT_SIGNAL, .flags=EV_ADD },
399+
};
400+
401+
// Block signals we want to receive via kqueue.
402+
sigset_t mask;
403+
sigemptyset(&mask);
404+
for (size_t i = 0; i < ARRAY_SIZE(changes); i++) {
405+
sigaddset(&mask, changes[i].ident);
406+
}
407+
if (sigprocmask(SIG_BLOCK, &mask, NULL) != 0) {
408+
ERRORN("sigprocmask");
409+
return -1;
410+
}
411+
412+
// We will receive EPIPE on the socket.
413+
signal(SIGPIPE, SIG_IGN);
414+
415+
if (kevent(kq, changes, ARRAY_SIZE(changes), NULL, 0, NULL) != 0) {
416+
ERRORN("kevent");
417+
return -1;
418+
}
419+
return 0;
420+
}
421+
422+
static int add_listen_fd(int kq, int fd)
423+
{
424+
struct kevent changes[] = {
425+
{ .ident=fd, .filter=EVFILT_READ, .flags=EV_ADD },
426+
};
427+
if (kevent(kq, changes, ARRAY_SIZE(changes), NULL, 0, NULL) != 0) {
428+
ERRORN("kevent");
429+
return -1;
430+
}
431+
return 0;
432+
}
433+
394434
static void on_accept(struct state *state, int accept_fd, interface_ref iface);
395435

396436
int main(int argc, char *argv[]) {
397437
debug = getenv("DEBUG") != NULL;
398438
int rc = 1;
399439
int listen_fd = -1;
400440
int pidfile_fd = -1;
441+
int kq = -1;
401442
__block interface_ref iface = NULL;
402443

403444
struct state state = {0};
@@ -411,6 +452,18 @@ int main(int argc, char *argv[]) {
411452
WARN("Seems running with SETUID. This is insecure and highly discouraged: See README.md");
412453
}
413454

455+
kq = kqueue();
456+
if (kq == -1) {
457+
ERRORN("kqueue");
458+
goto done;
459+
}
460+
461+
// Setup signals beofre creating the pidfile so the pidfile to ensure removal
462+
// when terminating by signal.
463+
if (setup_signals(kq)) {
464+
goto done;
465+
}
466+
414467
if (cliopt->pidfile != NULL) {
415468
pidfile_fd = create_pidfile(cliopt->pidfile);
416469
if (pidfile_fd == -1) {
@@ -426,16 +479,6 @@ int main(int argc, char *argv[]) {
426479
goto done;
427480
}
428481

429-
if (sigsetjmp(jmpbuf, 1) != 0) {
430-
goto done;
431-
}
432-
signal(SIGHUP, signalhandler);
433-
signal(SIGINT, signalhandler);
434-
signal(SIGTERM, signalhandler);
435-
436-
// We will receive EPIPE on the socket.
437-
signal(SIGPIPE, SIG_IGN);
438-
439482
state.sem = dispatch_semaphore_create(1);
440483

441484
// Queue for vm connections, allowing processing vms requests in parallel.
@@ -452,16 +495,34 @@ int main(int argc, char *argv[]) {
452495
goto done;
453496
}
454497

498+
if (add_listen_fd(kq, listen_fd)) {
499+
goto done;
500+
}
501+
455502
while (1) {
456-
int accept_fd = accept(listen_fd, NULL, NULL);
457-
if (accept_fd < 0) {
458-
ERRORN("accept");
503+
struct kevent events[1];
504+
int n = kevent(kq, NULL, 0, events, 1, NULL);
505+
if (n < 0) {
506+
ERRORN("kevent");
459507
goto done;
460508
}
461-
struct state *state_p = &state;
462-
dispatch_async(state.vms_queue, ^{
463-
on_accept(state_p, accept_fd, iface);
464-
});
509+
510+
if (events[0].filter == EVFILT_SIGNAL) {
511+
INFOF("Received signal %s", strsignal(events[0].ident));
512+
break;
513+
}
514+
515+
if (events[0].filter == EVFILT_READ) {
516+
int accept_fd = accept(listen_fd, NULL, NULL);
517+
if (accept_fd < 0) {
518+
ERRORN("accept");
519+
goto done;
520+
}
521+
struct state *state_p = &state;
522+
dispatch_async(state.vms_queue, ^{
523+
on_accept(state_p, accept_fd, iface);
524+
});
525+
}
465526
}
466527
rc = 0;
467528
done:
@@ -480,6 +541,9 @@ int main(int argc, char *argv[]) {
480541
dispatch_release(state.vms_queue);
481542
if (state.host_queue != NULL)
482543
dispatch_release(state.host_queue);
544+
if (kq != -1) {
545+
close(kq);
546+
}
483547
cli_options_destroy(cliopt);
484548
return rc;
485549
}

0 commit comments

Comments
 (0)