/* io.c: Network routines. */ #define _POSIX_SOURCE #include <ctype.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <unistd.h> #include "x.tab.h" #include "io.h" #include "net.h" #include "execute.h" #include "memory.h" #include "grammar.h" #include "cmstring.h" #include "data.h" #include "util.h" #include "ident.h" #define BUF_SIZE 1024 static void connection_read(Connection *conn); static void connection_write(Connection *conn); static Connection *connection_add(int fd, long dbref); static void connection_discard(Connection *conn); static void pend_discard(Pending *pend); static void server_discard(Server *serv); static Connection *connections; /* List of client connections. */ static Server *servers; /* List of server sockets. */ static Pending *pendings; /* List of pending connections. */ /* Notify the system object of any dead connections and delete them. */ void flush_defunct(void) { Connection **connp, *conn; Server **servp, *serv; Pending **pendp, *pend; connp = &connections; while (*connp) { conn = *connp; if (conn->flags.dead && conn->write_buf->len == 0) { *connp = conn->next; connection_discard(conn); } else { connp = &conn->next; } } servp = &servers; while (*servp) { serv = *servp; if (serv->dead) { *servp = serv->next; server_discard(serv); } else { servp = &serv->next; } } pendp = &pendings; while (*pendp) { pend = *pendp; if (pend->finished) { *pendp = pend->next; pend_discard(pend); } else { pendp = &pend->next; } } } /* Handle any I/O events. sec is the number of seconds we get to wait for * something to happen before the timer wants the thread back, or -1 if we can * wait forever. */ void handle_io_events(long sec) { Connection *conn; Server *serv; Pending *pend; String *str; Data d1, d2; /* Call io_event_wait() to wait for something to happen. The return value * is nonzero if an I/O event occurred. If thre is a new connection, then * *fd will be set to the descriptor of the new connection; otherwise, it * is set to -1. */ if (!io_event_wait(sec, connections, servers, pendings)) return; /* Deal with any events on our existing connections. */ for (conn = connections; conn; conn = conn->next) { if (conn->flags.readable && !conn->flags.dead) connection_read(conn); if (conn->flags.writable) connection_write(conn); } /* Look for new connections on the server sockets. */ for (serv = servers; serv; serv = serv->next) { if (serv->client_socket == -1) continue; conn = connection_add(serv->client_socket, serv->dbref); serv->client_socket = -1; str = string_from_chars(serv->client_addr, strlen(serv->client_addr)); d1.type = STRING; d1.u.str = str; d2.type = INTEGER; d2.u.val = serv->client_port; task(conn, conn->dbref, connect_id, 2, &d1, &d2); string_discard(str); } /* Look for pending connections succeeding or failing. */ for (pend = pendings; pend; pend = pend->next) { if (pend->finished) { if (pend->error == NOT_AN_IDENT) { conn = connection_add(pend->fd, pend->dbref); d1.type = INTEGER; d1.u.val = pend->task_id; task(conn, conn->dbref, connect_id, 1, &d1); } else { close(pend->fd); d1.type = INTEGER; d1.u.val = pend->task_id; d2.type = ERROR; d2.u.error = pend->error; task(NULL, pend->dbref, failed_id, 2, &d1, &d2); } } } } void tell(long dbref, Buffer *buf) { Connection *conn; for (conn = connections; conn; conn = conn->next) { if (conn->dbref == dbref && !conn->flags.dead) conn->write_buf = buffer_append(conn->write_buf, buf); } } int boot(long dbref) { Connection *conn; int count = 0; for (conn = connections; conn; conn = conn->next) { if (conn->dbref == dbref) { conn->flags.dead = 1; count++; } } return count; } int add_server(int port, long dbref) { Server *cnew; int server_socket; /* Check if a server already exists for this port. */ for (cnew = servers; cnew; cnew = cnew->next) { if (cnew->port == port) { cnew->dbref = dbref; cnew->dead = 0; return 1; } } /* Get a server socket for the port. */ server_socket = get_server_socket(port); if (server_socket < 0) return 0; cnew = EMALLOC(Server, 1); cnew->server_socket = server_socket; cnew->client_socket = -1; cnew->port = port; cnew->dbref = dbref; cnew->dead = 0; cnew->next = servers; servers = cnew; return 1; } int remove_server(int port) { Server **servp; for (servp = &servers; *servp; servp = &((*servp)->next)) { if ((*servp)->port == port) { (*servp)->dead = 1; return 1; } } return 0; } static void connection_read(Connection *conn) { unsigned char temp[BUF_SIZE]; int len; Buffer *buf; Data d; len = read(conn->fd, (char *) temp, BUF_SIZE); if (len < 0 && errno == EINTR) { /* We were interrupted; deal with this next time around. */ return; } conn->flags.readable = 0; if (len <= 0) { /* The connection closed. */ conn->flags.dead = 1; return; } /* We successfully read some data. Handle it. */ buf = buffer_new(len); MEMCPY(buf->s, temp, len); d.type = BUFFER; d.u.buffer = buf; task(conn, conn->dbref, parse_id, 1, &d); buffer_discard(buf); } static void connection_write(Connection *conn) { Buffer *buf = conn->write_buf; int r; r = write(conn->fd, buf->s, buf->len); conn->flags.writable = 0; if (r <= 0) { /* We lost the connection. */ conn->flags.dead = 1; buf = buffer_truncate(buf, 0); } else { MEMMOVE(buf->s, buf->s + r, buf->len - r); buf = buffer_truncate(buf, buf->len - r); } conn->write_buf = buf; } static Connection *connection_add(int fd, long dbref) { Connection *conn; conn = EMALLOC(Connection, 1); conn->fd = fd; conn->write_buf = buffer_new(0); conn->dbref = dbref; conn->flags.readable = 0; conn->flags.writable = 0; conn->flags.dead = 0; conn->next = connections; connections = conn; return conn; } static void connection_discard(Connection *conn) { /* Notify system object that the connection is gone. */ task(conn, conn->dbref, disconnect_id, 0); /* Free the data associated with the connection. */ close(conn->fd); buffer_discard(conn->write_buf); free(conn); } static void pend_discard(Pending *pend) { free(pend); } static void server_discard(Server *serv) { close(serv->server_socket); } long make_connection(char *addr, int port, Dbref receiver) { Pending *cnew; int socket; long result; result = non_blocking_connect(addr, port, &socket); if (result == address_id || result == socket_id) return result; cnew = TMALLOC(Pending, 1); cnew->fd = socket; cnew->task_id = task_id; cnew->dbref = receiver; cnew->finished = 0; cnew->error = result; cnew->next = pendings; pendings = cnew; return NOT_AN_IDENT; } /* Write out everything in connections' write buffers. Called by main() * before exiting; does not modify the write buffers to reflect writing. */ void flush_output(void) { Connection *conn; unsigned char *s; int len, r; for (conn = connections; conn; conn = conn->next) { s = conn->write_buf->s; len = conn->write_buf->len; while (len) { r = write(conn->fd, s, len); if (r <= 0) break; len -= r; s += r; } } }