#include "irc.h"
#include <locale.h>
+static int is_running = 1;
+
#ifndef _WIN32
#include <signal.h>
-static sig_atomic_t is_running = 1;
+static volatile sig_atomic_t is_siginted = 0;
static void on_sigint(int signum)
{
(void)signum;
- is_running = 0;
+ is_siginted = 1;
}
static void set_handler()
{
perror("sigaction");
}
#else
-static int is_running = 1;
+static int is_siginted_once = 0;
+static volatile int is_siginted = 0;
static SOCKET self_sock = INVALID_SOCKET;
static BOOL WINAPI on_sigint(DWORD dwCtrlType)
{
- if ((dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_CLOSE_EVENT) && is_running) {
- is_running = 0;
+ if ((dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_CLOSE_EVENT) && !is_siginted_once) {
+ is_siginted_once = 1;
+ is_siginted = 1;
char x = 0;
if (self_sock != INVALID_SOCKET) {
send(self_sock, &x, 0, 0); // FIXME: this could EAGAIN in theory
#endif
// Client linked list
-struct client_data *clients = NULL;
+struct client_data *clients = NULL, *dyingclients = NULL;
// FDSET List
fd_set readfds, writefds;
}
// Main loop
- while(is_running){
+ while (is_running || dyingclients) {
+ if (is_siginted) {
+ fprintf(stderr, "[PatchyIRCD]: Cleaning up...\n");
+ for (int i = 0; i < listener_count; i++)
+ closesocket(listeners[i].sock);
+ listener_count = 0;
+ for (struct client_data *cl = clients; cl;) {
+ struct client_data *next = cl->next;
+ killConnection(cl, 0);
+ cl = next;
+ }
+ is_running = 0;
+ is_siginted = 0;
+ if (!dyingclients)
+ break;
+ }
FD_ZERO(&readfds);
FD_ZERO(&writefds);
// Add listening SOCKETS to FDSET
FD_SET(self_sock, &readfds);
#endif
for (struct client_data *cl = clients; cl; cl = cl->next) {
- if (!cl->want_write_for_close)
+ if (!cl->want_write_for_read || cl->want_read_for_write)
FD_SET(cl->FD, &readfds);
- if (cl->want_write_for_read || cl->want_write_for_write || cl->want_write_for_close)
+ if (cl->want_write_for_read || cl->want_write_for_write)
+ FD_SET(cl->FD, &writefds);
+ }
+ for (struct client_data *cl = dyingclients; cl; cl = cl->next) {
+ if (cl->want_read_for_write || cl->want_read_for_close)
+ FD_SET(cl->FD, &readfds);
+ if (cl->want_write_for_write || cl->want_write_for_close)
FD_SET(cl->FD, &writefds);
}
for (struct client_data *cl = clients; cl; cl = cl->next)
if (highestFD < cl->FD)
highestFD = cl->FD;
+ for (struct client_data *cl = dyingclients; cl; cl = cl->next)
+ if (highestFD < cl->FD)
+ highestFD = cl->FD;
#endif
// Wait until activity on sockets
if(select(highestFD+1,&readfds,&writefds,NULL,NULL) == -1) {
}
#endif
- // Accept new connections
+ /* Accept new connections */
for (int i = 0; i < listener_count; i++)
if (FD_ISSET(listeners[i].sock, &readfds))
accept_from_sock(&listeners[i]);
- // Respond to already connected clients
+ /* Respond to already connected clients */
for (struct client_data *cl = clients; cl;) {
struct client_data *next = cl->next;
int isr = FD_ISSET(cl->FD, &readfds);
int isw = FD_ISSET(cl->FD, &writefds);
- if (!cl->want_read_for_close && !cl->want_write_for_close)
- if (isr || (cl->want_write_for_read && isw))
- recvProc(cl);
+ /* NOTE: recvProc can kill the client, so writing should be done first. */
if ((cl->want_read_for_write && isr) || (cl->want_write_for_write && isw))
sendClient(cl, 0, 0);
- if ((cl->want_read_for_close && isr) || (cl->want_write_for_close && isw))
- killConnectionPart2(cl);
+ if (cl->write_error)
+ killConnection(cl, 0);
+ else if (cl->want_write_for_read ? isw : isr)
+ recvProc(cl);
+ cl = next;
+ }
+ /* Close dying clients */
+ for (struct client_data *cl = dyingclients; cl;) {
+ struct client_data *next = cl->next;
+ int isr = FD_ISSET(cl->FD, &readfds);
+ int isw = FD_ISSET(cl->FD, &writefds);
+ if (cl->wbufLen)
+ if ((cl->want_read_for_write && isr) || (cl->want_write_for_write && isw))
+ sendClient(cl, 0, 0);
+ if (!cl->wbufLen) {
+ if (!cl->want_read_for_close && !cl->want_write_for_close)
+ killConnectionPart2(cl);
+ else if ((cl->want_read_for_close && isr) || (cl->want_write_for_close && isw))
+ killConnectionPart2(cl);
+ }
cl = next;
}
}
- fprintf(stderr, "[PatchyIRCD]: Cleaning up...\n");
- for (struct client_data *cl = clients; cl;) {
- struct client_data *next = cl->next;
- killConnection(cl, 0);
- /* FIXME: who is going to wait for these connections to actually get
- * killed? */
- cl = next;
- }
- for (int i = 0; i < listener_count; i++)
- closesocket(listeners[i].sock);
net_cleanup();
#ifdef HAS_TLS
tls_free(tls_ctx);
void killConnection(struct client_data *cl, int byChoice)
{
- if (!cl) return;
+ if (!cl)
+ return;
+ if (cl->dying)
+ return;
+ cl->dying = 1; /* NOTE: this recursion guard isn't necessary, but it's there just in case */
if (cl->peername)
fprintf(stderr, "Client disconnected from Socket: %"PRISocket", IP: %s PORT: %d\n", cl->FD, cl->peername, cl->peerport);
- if(!byChoice && cl->nick && cl->username) {
+ if (!byChoice && cl->nick && cl->username) {
msg_quit(cl, "", "Has left the building (Forced)");
sendbuf_visible(cl);
}
- // Send error to client informing them that they will be disconnected
+ /* Send error to client informing them that they will be disconnected */
msg_error("Closing Link");
sendbuf(cl);
+ /* Move the client to the dyingclients chain */
+ for (struct client_data **next = &clients; *next; next = &(*next)->next) {
+ if (*next == cl) {
+ *next = cl->next;
+ break;
+ }
+ }
+ cl->next = dyingclients;
+ dyingclients = cl;
+ /* Part all channels */
+ struct trie_find *f = NULL;
+ if (trie_findfirst(&f, &cl->channels) == 1)
+ do {
+ struct channel *chan = f->n->value;
+ channel_part(cl, chan, NULL, NULL);
+ } while(trie_findfirst(&f, &cl->channels) == 1);
+ /* Get rid of the pending invites */
+ if (cl->key) {
+ trie_set(&client_map, cl->key, NULL, NULL);
+ struct trie_find *f = NULL;
+ if (trie_findfirst(&f, &cl->invites) == 1)
+ do {
+ struct channel *chan = f->n->value;
+ trie_set(&chan->invitees, cl->key, NULL, NULL);
+ } while(trie_findnext(&f) == 1);
+ }
killConnectionPart2(cl);
}
void killConnectionPart2(struct client_data *cl)
{
- /* FIXME: we have to flush existing data somehow */
- /* A better implementation would be to keep a separate list of disconnected
- * clients. This way we can do all PARTing right away and only concern
- * ourselves with tls_write/tls_close. */
+ if (cl->want_read_for_write || cl->want_write_for_write) {
+ /* We're still waiting for a pending write.
+ * This could be the case when this is called from killConnection. */
+ return;
+ }
cl->want_read_for_close = 0;
cl->want_write_for_close = 0;
if (cl->FD != INVALID_SOCKET) {
shutdown(cl->FD, SHUT_RDWR);
closesocket(cl->FD);
}
- for (struct client_data **next = &clients; *next; next = &(*next)->next) {
+ /* Remove the client from the dyingclients chain */
+ for (struct client_data **next = &dyingclients; *next; next = &(*next)->next) {
if (*next == cl) {
*next = cl->next;
break;
}
}
- struct trie_find *f = NULL;
- if (trie_findfirst(&f, &cl->channels) == 1)
- do {
- struct channel *chan = f->n->value;
- channel_part(cl, chan, NULL, NULL);
- } while(trie_findfirst(&f, &cl->channels) == 1);
- free(f);
- if (cl->key) {
- trie_set(&client_map, cl->key, NULL, NULL);
- struct trie_find *f = NULL;
- if (trie_findfirst(&f, &cl->invites) == 1)
- do {
- struct channel *chan = f->n->value;
- trie_set(&chan->invitees, cl->key, NULL, NULL);
- } while(trie_findnext(&f) == 1);
- }
+ /* Free all the remaining data */
free(cl->nick);
free(cl->key);
free(cl->username);
if(!x) break;
packetReturn = parsePacket(cl);
// Client QUIT
- if(packetReturn == 1) {
+ if (packetReturn == 1 || cl->write_error)
return;
- }
memmove(cl->buf, x+1, cl->buf + cl->bufLen-(x+1));
cl->bufLen -= x+1 - cl->buf;
}
}
}
-/* returns the count of bytes which were not sent */
+/* returns the count of bytes which were not sent, or -1 on error.
+ * updates want_read_for_write and want_write_for_write */
static int sendClientRaw(struct client_data *cl, const char *buf, size_t len)
{
cl->want_read_for_write = 0;
if (cl->FD == (SOCKET)INVALID_SOCKET)
return 0;
fprintf(stderr, "SERVER %"PRISocket": %.*s", cl->FD, (int)len, (char*)buf);
- /* FIXME: error handling */
#ifdef HAS_TLS
if (cl->cctx) {
while (len) {
int rv = tls_write(cl->cctx, buf, len);
if (rv == 0 || rv == -1) {
- break;
+ return -1;
} else if (rv == TLS_WANT_POLLIN) {
cl->want_read_for_write = 1;
return len;
while (len) {
int rv = send(cl->FD, buf, len, 0);
if (!rv)
- break;
+ return -1;
if (rv == -1) {
if (net_wouldblock()) {
cl->want_write_for_write = 1;
return len;
} else {
- break;
+ return -1;
}
}
buf += rv;
void sendClient(struct client_data *cl, const char *buf, size_t len)
{
+ if (cl->write_error)
+ return;
if (cl->wbufLen + len > sizeof(cl->wbuf)) {
/* Buffer overrun */
/* FIXME: probably should terminate the connection at this point */
if (!cl->wbufLen) {
if (len) {
int left = sendClientRaw(cl, buf, len);
+ if (left == -1) {
+ /* Write error happened, discard the buffer and close the
+ * connection. sendClient is used from a ton of places,
+ * including killConnection and processPacket. The former will
+ * recurse into this function, and the latter assumes that this
+ * function will not cause the client to go away. For this
+ * reason we can't just use killConnection. Instead we just
+ * mark the client with a flag and make all the following
+ * writes instantly succeed.
+ */
+ cl->write_error = 1;
+ return;
+ }
memcpy(cl->wbuf, buf+len-left, left);
cl->wbufLen = left;
}
cl->wbufLen += len;
}
int left = sendClientRaw(cl, cl->wbuf, cl->wbufLen);
+ if (left == -1) {
+ cl->wbufLen = 0;
+ cl->write_error = 1;
+ return;
+ }
memmove(cl->wbuf, cl->wbuf+cl->wbufLen-left, left);
cl->wbufLen = left;
}