mirror of
https://github.com/troglobit/finit.git
synced 2026-10-01 05:22:48 +07:00
Fix "broken pipe" problem for UDP inetd services
This patch fixes several issues related to the problem of launching UDP inetd services: - Make sure to disable O_NONBLOCK on the socket before passing it to the inetd service, that's what is expected. This goes for both UDP and TCP (accepted) connections -- there is no guarantee even for TCP sockets that they do *not* inherit the parent socket's flags. - Mark the UDP inetd parent's SVC as busy and stop its watcher before starting the child task to service the connection. The watcher must be stopped or multiple child tasks may be spawned! - Only copy the most relevant parts to the UDP task. In particular must *not* copy the libuEv watcher data to the task! - With the child task done and the parent inetd service unblocked we must restore O_NONBLOCK to the socket before restarting the watcher When a UDP inetd service connection is handled we create a child task to service the request, meanwhile we block the parent service. There was a problem with blocking the parent since we called inetd_stop(), which both did a shutdown() and close() of the socket ... this patch introduces separate handling of blocked (busy) inetd services in the inetd_stop() function and also make sure to handle restarting the parent watcher in inetd_start() when the child task is done. Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
This commit is contained in:
@@ -150,6 +150,15 @@ static void socket_cb(uev_t *w, void *arg, int UNUSED(events))
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure to disable O_NONBLOCK on the descriptor before
|
||||
* passing it to the inetd service, that's what is expected.
|
||||
*/
|
||||
if (fcntl(stdin, F_SETFL, fcntl(stdin, F_GETFL, 0) & ~O_NONBLOCK) < 0) {
|
||||
FLOG_ERROR("Failed disabling non-blocking on %s socket", svc->cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
task = svc_new(svc->cmd, svc->inetd.next_id++, SVC_TYPE_INETD_CONN);
|
||||
if (!task) {
|
||||
FLOG_ERROR("%s: Unable to allocate service for inetd client",
|
||||
@@ -157,9 +166,19 @@ static void socket_cb(uev_t *w, void *arg, int UNUSED(events))
|
||||
return;
|
||||
}
|
||||
|
||||
/* Copy inherited attributes from inetd */
|
||||
if (!svc->inetd.forking) {
|
||||
svc->block = SVC_BLOCK_INETD_BUSY;
|
||||
service_step(svc);
|
||||
}
|
||||
|
||||
/* Copy inherited attributes from inetd service's svc */
|
||||
task->runlevels = svc->runlevels;
|
||||
task->inetd = svc->inetd;
|
||||
|
||||
/* Only copy the most relevant parts of inetd, in particular we
|
||||
* must *not* copy the watcher data to the clone! */
|
||||
task->inetd.svc = svc;
|
||||
task->inetd.type = svc->inetd.type;
|
||||
|
||||
memcpy(task->cond, svc->cond, sizeof(task->cond));
|
||||
memcpy(task->username, svc->username, sizeof(task->username));
|
||||
memcpy(task->group, svc->group, sizeof(task->group));
|
||||
@@ -168,11 +187,6 @@ static void socket_cb(uev_t *w, void *arg, int UNUSED(events))
|
||||
|
||||
task->stdin = stdin;
|
||||
service_step(task);
|
||||
|
||||
if (!svc->inetd.forking) {
|
||||
svc->block = SVC_BLOCK_INETD_BUSY;
|
||||
service_step(svc);
|
||||
}
|
||||
}
|
||||
|
||||
/* Launch Inet socket for service.
|
||||
@@ -231,19 +245,40 @@ static int spawn_socket(inetd_t *inetd)
|
||||
|
||||
int inetd_start(inetd_t *inetd)
|
||||
{
|
||||
if (inetd->watcher.fd == -1)
|
||||
int sd;
|
||||
char buf[BUFSIZ];
|
||||
ssize_t len;
|
||||
|
||||
sd = inetd->watcher.fd;
|
||||
if (sd == -1)
|
||||
return spawn_socket(inetd);
|
||||
|
||||
return -EEXIST;
|
||||
/* Read anything lingering, or clean up socket after failure */
|
||||
len = recv(sd, buf, sizeof(buf), MSG_DONTWAIT);
|
||||
_d("Read %d lingering bytes from socket before restarting %s ...", len, inetd->svc->cmd);
|
||||
|
||||
/* Restore O_NONBLOCK for socket */
|
||||
fcntl(sd, F_SETFL, fcntl(sd, F_GETFL, 0) | O_NONBLOCK);
|
||||
|
||||
_d("Re-starting %s socket watcher ...", inetd->svc->cmd);
|
||||
uev_io_start(&inetd->watcher);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void inetd_stop(inetd_t *inetd)
|
||||
{
|
||||
if (inetd->watcher.fd != -1) {
|
||||
_d("Stopping %s socket watcher ...", inetd->svc->cmd);
|
||||
uev_io_stop(&inetd->watcher);
|
||||
shutdown(inetd->watcher.fd, SHUT_RDWR);
|
||||
close(inetd->watcher.fd);
|
||||
inetd->watcher.fd = -1;
|
||||
|
||||
/* For dgram inetd services we block the parent SVC
|
||||
* and halt the watcher, so don't close the socket! */
|
||||
if (inetd->svc->block != SVC_BLOCK_INETD_BUSY) {
|
||||
shutdown(inetd->watcher.fd, SHUT_RDWR);
|
||||
close(inetd->watcher.fd);
|
||||
inetd->watcher.fd = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user