mirror of https://github.com/martanne/abduco
281 lines
6.5 KiB
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);
|
|
}
|