mirror of https://github.com/martanne/abduco
269 lines
6.3 KiB
C
269 lines
6.3 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 void server_mark_socket_exec(bool exec, bool usr) {
|
|
struct stat sb;
|
|
if (stat(sockaddr.sun_path, &sb) == -1)
|
|
return;
|
|
mode_t mode = sb.st_mode;
|
|
mode_t flag = usr ? S_IXUSR : S_IXGRP;
|
|
if (exec)
|
|
mode |= flag;
|
|
else
|
|
mode &= ~flag;
|
|
chmod(sockaddr.sun_path, mode);
|
|
}
|
|
|
|
static int server_create_socket(const char *name) {
|
|
if (!set_socket_name(&sockaddr, name))
|
|
return -1;
|
|
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd == -1)
|
|
return -1;
|
|
socklen_t socklen = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path) + 1;
|
|
mode_t mode = S_IRUSR|S_IWUSR;
|
|
if (fchmod(fd, mode) == -1)
|
|
goto error1;
|
|
if (bind(fd, (struct sockaddr*)&sockaddr, socklen) == -1)
|
|
goto error1;
|
|
if (fchmod(fd, mode) == -1 || chmod(sockaddr.sun_path, mode) == -1)
|
|
goto error2;
|
|
if (listen(fd, 5) == -1)
|
|
goto error2;
|
|
return fd;
|
|
error2:
|
|
unlink(sockaddr.sun_path);
|
|
error1:
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
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(void) {
|
|
int newfd = accept(server.socket, NULL, NULL);
|
|
if (newfd == -1 || server_set_socket_non_blocking(newfd) == -1)
|
|
goto error;
|
|
Client *c = client_malloc(newfd);
|
|
if (!c)
|
|
goto error;
|
|
if (!server.clients)
|
|
server_mark_socket_exec(true, true);
|
|
c->socket = newfd;
|
|
c->state = STATE_CONNECTED;
|
|
c->next = server.clients;
|
|
server.clients = c;
|
|
server.read_pty = true;
|
|
return c;
|
|
error:
|
|
if (newfd != -1)
|
|
close(newfd);
|
|
return NULL;
|
|
}
|
|
|
|
static bool server_read_pty(Packet *pkt) {
|
|
pkt->type = MSG_CONTENT;
|
|
ssize_t len = read(server.pty, pkt->u.msg, sizeof(pkt->u.msg));
|
|
if (len != -1)
|
|
pkt->len = len;
|
|
else if (errno != EAGAIN && errno != EINTR)
|
|
server.running = false;
|
|
print_packet("server-read-pty:", pkt);
|
|
return len > 0;
|
|
}
|
|
|
|
static bool server_write_pty(Packet *pkt) {
|
|
print_packet("server-write-pty:", pkt);
|
|
size_t size = pkt->len;
|
|
if (write_all(server.pty, pkt->u.msg, size) == size)
|
|
return true;
|
|
debug("FAILED\n");
|
|
server.running = false;
|
|
return false;
|
|
}
|
|
|
|
static bool server_recv_packet(Client *c, Packet *pkt) {
|
|
if (recv_packet(c->socket, pkt)) {
|
|
print_packet("server-recv:", pkt);
|
|
return true;
|
|
}
|
|
debug("server-recv: FAILED\n");
|
|
c->state = STATE_DISCONNECTED;
|
|
return false;
|
|
}
|
|
|
|
static bool server_send_packet(Client *c, Packet *pkt) {
|
|
print_packet("server-send:", pkt);
|
|
if (send_packet(c->socket, pkt))
|
|
return true;
|
|
debug("FAILED\n");
|
|
c->state = STATE_DISCONNECTED;
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
server_mark_socket_exec(true, false);
|
|
}
|
|
|
|
debug("server pty died: %d\n", server.exit_status);
|
|
errno = errsv;
|
|
}
|
|
|
|
static void server_sigterm_handler(int sig) {
|
|
exit(EXIT_FAILURE); /* invoke atexit handler */
|
|
}
|
|
|
|
static void server_sigusr1_handler(int sig) {
|
|
int socket = server_create_socket(server.session_name);
|
|
if (socket != -1) {
|
|
if (server.socket)
|
|
close(server.socket);
|
|
server.socket = socket;
|
|
}
|
|
}
|
|
|
|
static void server_atexit_handler(void) {
|
|
unlink(sockaddr.sun_path);
|
|
}
|
|
|
|
static void server_mainloop(void) {
|
|
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;
|
|
bool exit_packet_delivered = false;
|
|
|
|
if (server.read_pty)
|
|
FD_SET_MAX(server.pty, &new_readfds, new_fdmax);
|
|
|
|
while (server.clients || !exit_packet_delivered) {
|
|
int fdmax = new_fdmax;
|
|
fd_set readfds = new_readfds;
|
|
fd_set writefds = new_writefds;
|
|
FD_SET_MAX(server.socket, &readfds, fdmax);
|
|
|
|
if (select(fdmax+1, &readfds, &writefds, NULL, NULL) == -1) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
die("server-mainloop");
|
|
}
|
|
|
|
FD_ZERO(&new_readfds);
|
|
FD_ZERO(&new_writefds);
|
|
new_fdmax = server.socket;
|
|
|
|
bool pty_data = false;
|
|
|
|
Packet server_packet, client_packet;
|
|
|
|
if (FD_ISSET(server.socket, &readfds))
|
|
server_accept_client();
|
|
|
|
if (FD_ISSET(server.pty, &readfds))
|
|
pty_data = server_read_pty(&server_packet);
|
|
|
|
for (Client **prev_next = &server.clients, *c = server.clients; c;) {
|
|
if (FD_ISSET(c->socket, &readfds) && server_recv_packet(c, &client_packet)) {
|
|
switch (client_packet.type) {
|
|
case MSG_CONTENT:
|
|
server_write_pty(&client_packet);
|
|
break;
|
|
case MSG_ATTACH:
|
|
c->readonly = client_packet.u.b;
|
|
break;
|
|
case MSG_RESIZE:
|
|
c->state = STATE_ATTACHED;
|
|
case MSG_REDRAW:
|
|
if (!c->readonly && (client_packet.type == MSG_REDRAW || c == server.clients)) {
|
|
debug("server-ioct: TIOCSWINSZ\n");
|
|
ioctl(server.pty, TIOCSWINSZ, &client_packet.u.ws);
|
|
}
|
|
kill(-server.pid, SIGWINCH);
|
|
break;
|
|
case MSG_DETACH:
|
|
case MSG_EXIT:
|
|
c->state = STATE_DISCONNECTED;
|
|
break;
|
|
default: /* ignore package */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (c->state == STATE_DISCONNECTED) {
|
|
bool first = (c == server.clients);
|
|
Client *t = c->next;
|
|
client_free(c);
|
|
*prev_next = c = t;
|
|
if (first && server.clients) {
|
|
Packet pkt = {
|
|
.type = MSG_RESIZE,
|
|
.len = 0,
|
|
};
|
|
server_send_packet(server.clients, &pkt);
|
|
} else if (!server.clients) {
|
|
server_mark_socket_exec(false, true);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
FD_SET_MAX(c->socket, &new_readfds, new_fdmax);
|
|
|
|
if (pty_data)
|
|
server_send_packet(c, &server_packet);
|
|
if (!server.running) {
|
|
if (server.exit_status != -1) {
|
|
Packet pkt = {
|
|
.type = MSG_EXIT,
|
|
.u.i = server.exit_status,
|
|
.len = sizeof(pkt.u.i),
|
|
};
|
|
if (server_send_packet(c, &pkt))
|
|
exit_packet_delivered = true;
|
|
else
|
|
FD_SET_MAX(c->socket, &new_writefds, new_fdmax);
|
|
} else {
|
|
FD_SET_MAX(c->socket, &new_writefds, new_fdmax);
|
|
}
|
|
}
|
|
prev_next = &c->next;
|
|
c = c->next;
|
|
}
|
|
|
|
if (server.running && server.read_pty)
|
|
FD_SET_MAX(server.pty, &new_readfds, new_fdmax);
|
|
}
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|