abduco/server.c

281 lines
6.5 KiB
C

#define FD_SET_MAX(fd, set, maxfd) do { \
FD_SET(fd, set); \
if (fd > maxfd) \
maxfd = fd; \
} while (0)
static Client *client_malloc(int socket) {
Client *c = calloc(1, sizeof(Client));
if (!c)
return NULL;
c->socket = socket;
return c;
}
static void client_free(Client *c) {
if (c && c->socket > 0)
close(c->socket);
free(c);
}
static int server_set_socket_non_blocking(int sock) {
int flags;
if ((flags = fcntl(sock, F_GETFL, 0)) == -1)
flags = 0;
return fcntl(sock, F_SETFL, flags | O_NONBLOCK);
}
static Client *server_accept_client(time_t now) {
int newfd = accept(server.socket, NULL, NULL);
if (newfd == -1)
return NULL;
Client *c = client_malloc(newfd);
if (!c)
return NULL;
server_set_socket_non_blocking(newfd);
c->socket = newfd;
c->state = STATE_CONNECTED;
c->last_activity = now;
c->next = server.clients;
server.clients = c;
server.client_count++;
return c;
}
static bool server_read_pty(ServerPacket *pkt) {
ssize_t len = read(server.pty, pkt->buf, sizeof(pkt->buf));
if (len != -1)
pkt->len = len;
else if (errno != EAGAIN && errno != EINTR)
server.running = false;
print_server_packet("server-read-pty:", pkt);
return len > 0;
}
static bool server_write_pty(ClientPacketState *pkt) {
int count = pkt->pkt.len - pkt->off;
ssize_t len = write(server.pty, pkt->pkt.u.msg + pkt->off, count);
if (len == -1) {
if (errno != EAGAIN && errno != EINTR)
server.running = false;
} else {
pkt->off += len;
}
print_client_packet_state("server-write-pty:", pkt);
return len == count;
}
static void server_place_packet(Client *c, ServerPacket *pkt) {
c->output.pkt = pkt;
c->output.off = 0;
}
static bool server_recv_packet(Client *c) {
ClientPacketState *pkt = &c->input;
if (is_client_packet_complete(pkt))
return true;
int count = sizeof(ClientPacket) - pkt->off;
ssize_t len = recv(c->socket, ((char *)&pkt->pkt) + pkt->off, count, 0);
switch (len) {
case -1:
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
case 0:
c->state = STATE_DISCONNECTED;
}
break;
default:
pkt->off += len;
break;
}
print_client_packet_state("server-recv:", pkt);
return len == count;
}
static bool server_send_packet(Client *c) {
ServerPacketState *pkt = &c->output;
if (is_server_packet_complete(pkt))
return true;
int count = pkt->pkt->len - pkt->off;
ssize_t len = send(c->socket, pkt->pkt->buf + pkt->off, count, 0);
switch (len) {
case -1:
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
case 0:
c->state = STATE_DISCONNECTED;
}
break;
default:
pkt->off += len;
break;
}
print_server_packet_state("server-send:", pkt);
return len == count;
}
static void server_pty_died_handler(int sig) {
int errsv = errno;
pid_t pid;
while ((pid = waitpid(-1, &server.exit_status, WNOHANG)) != 0) {
if (pid == -1)
break;
server.exit_status = WEXITSTATUS(server.exit_status);
}
debug("server pty died: %d\n", server.exit_status);
errno = errsv;
}
static void server_atexit_handler() {
unlink(sockaddr.sun_path);
}
static bool server_queue_empty() {
return server.queue_count == 0;
}
static bool server_queue_packet(ClientPacket *pkt) {
if (server.queue_count >= countof(server.queue))
return false;
server.queue[server.queue_insert] = *pkt;
server.queue_insert++;
server.queue_insert %= countof(server.queue);
server.queue_count++;
return true;
}
static ClientPacket *server_peek_packet() {
return &server.queue[server.queue_remove];
}
static void server_dequeue_packet() {
server.queue_remove++;
server.queue_remove %= countof(server.queue);
server.queue_count--;
}
static void server_mainloop() {
atexit(server_atexit_handler);
fd_set new_readfds, new_writefds;
FD_ZERO(&new_readfds);
FD_ZERO(&new_writefds);
FD_SET(server.socket, &new_readfds);
int new_fdmax = server.socket;
for (;;) {
int fdmax = new_fdmax;
fd_set readfds = new_readfds;
fd_set writefds = new_writefds;
if (select(fdmax+1, &readfds, &writefds, NULL, NULL) == -1) {
if (errno == EINTR)
continue;
die("server-mainloop");
}
FD_ZERO(&new_readfds);
FD_SET(server.socket, &new_readfds);
FD_ZERO(&new_writefds);
new_fdmax = server.socket;
time_t now = time(NULL);
time_t timeout = now - CLIENT_TIMEOUT;
bool pty_data = false, clients_ready = true;
if (FD_ISSET(server.socket, &readfds))
server_accept_client(now);
if (FD_ISSET(server.pty, &readfds)) {
pty_data = server_read_pty(&server.pty_output);
clients_ready = !pty_data;
}
for (Client **prev_next = &server.clients, *c = server.clients; c;) {
if (c->state == STATE_DISCONNECTED) {
Client *t = c->next;
client_free(c);
*prev_next = c = t;
server.client_count--;
continue;
}
if (FD_ISSET(c->socket, &readfds))
server_recv_packet(c);
if (is_client_packet_complete(&c->input)) {
bool packet_handled = true;
switch (c->input.pkt.type) {
case MSG_CONTENT:
packet_handled = server_queue_packet(&c->input.pkt);
break;
case MSG_ATTACH:
case MSG_RESIZE:
c->state = STATE_ATTACHED;
ioctl(server.pty, TIOCSWINSZ, &c->input.pkt.u.ws);
case MSG_REDRAW:
kill(-server.pid, SIGWINCH);
break;
case MSG_DETACH:
c->state = STATE_DETACHED;
break;
default: /* ignore package */
break;
}
if (packet_handled) {
c->input.off = 0;
FD_SET_MAX(c->socket, &new_readfds, new_fdmax);
}
} else {
FD_SET_MAX(c->socket, &new_readfds, new_fdmax);
}
if (pty_data) {
server_place_packet(c, &server.pty_output);
c->last_activity = now;
}
if (FD_ISSET(c->socket, &writefds)) {
server_send_packet(c);
c->last_activity = now;
}
if (!is_server_packet_complete(&c->output)) {
if (c->last_activity < timeout) {
c->state = STATE_DISCONNECTED;
} else if (is_server_packet_nonempty(&c->output)) {
clients_ready = false;
FD_SET_MAX(c->socket, &new_writefds, new_fdmax);
}
}
if (c->state != STATE_ATTACHED)
clients_ready = false;
prev_next = &c->next;
c = c->next;
}
if (clients_ready && server.clients) {
if (server.running)
FD_SET_MAX(server.pty, &new_readfds, new_fdmax);
else
break;
}
if (FD_ISSET(server.pty, &writefds)) {
while (!server_queue_empty()) {
if (!server.pty_input.off)
server.pty_input.pkt = *server_peek_packet();
if (!server_write_pty(&server.pty_input))
break;
server_dequeue_packet();
server.pty_input.off = 0;
}
}
if (!server_queue_empty())
FD_SET_MAX(server.pty, &new_writefds, new_fdmax);
}
exit(EXIT_SUCCESS);
}