dtach/master.c

328 lines
7.1 KiB
C

/*
dtach - A simple program that emulates the detach feature of screen.
Copyright (C) 2001 Ned T. Crigler
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "detach.h"
// The pty struct - The pty information is stored here.
struct pty
{
// File descriptor of the pty
int fd;
// The terminal parameters of the pty. Old and new for comparision
// purposes.
struct termios term;
// The current window size of the pty.
struct winsize ws;
};
// The poll structures
static struct pollfd *polls;
// The number of active poll slots
static int num_polls;
// Boolean array for whether a particular connection is attached.
static int *attached;
// The highest file descriptor possible, as returned by getrlimit.
static int highest_fd;
// The number of fixed slots in the poll structures
#define FIXED_SLOTS 2
// Unlink the socket
static void
unlink_socket(void)
{
unlink(sockname);
}
// Signal
static RETSIGTYPE
die(int sig)
{
// Well, the child died.
if (sig == SIGCHLD)
return;
exit(1);
}
// Initialize the pty structure.
static int
init_pty(struct pty *pty, char **argv)
{
pid_t pid;
// Use the original terminal's settings. We don't have to set the
// window size here, because the attacher will send it in a packet.
pty->term = orig_term;
// Create the pty process
pid = forkpty(&pty->fd, NULL, &pty->term, NULL);
if (pid < 0)
return -1;
else if (pid == 0)
{
int i;
// Child.. Close some file descriptors and execute the program.
for (i = highest_fd; i > 2; --i)
close(i);
execvp(*argv, argv);
exit(127);
}
// Parent.. Finish up and return
return 0;
}
// Creates a new unix domain socket.
static int
create_socket(char *name)
{
int s;
struct sockaddr_un sun;
s = socket(PF_UNIX, SOCK_STREAM, 0);
if (s < 0)
return -1;
sun.sun_family = AF_UNIX;
strcpy(sun.sun_path, name);
if (bind(s, (struct sockaddr*)&sun, sizeof(sun)) < 0)
return -1;
if (listen(s, 128) < 0)
return -1;
// chmod it to prevent any suprises
if (chmod(name, 0600) < 0)
return -1;
return s;
}
// Process activity on a pty - Input and terminal changes are sent out to
// the attached clients. If the pty goes away, we die.
static void
pty_activity(struct pty *pty)
{
int i, len;
unsigned char buf[BUFSIZE];
// Read the pty activity
len = read(pty->fd, buf, sizeof(buf));
// Error -> die
if (len <= 0)
exit(1);
// Get the current terminal settings.
if (tcgetattr(pty->fd, &pty->term) < 0)
exit(1);
// Send it out to the clients.
for (i = FIXED_SLOTS; i < num_polls; ++i)
{
if (attached[polls[i].fd])
write(polls[i].fd, buf, len);
}
}
// Process activity on the control socket
static void
control_activity(int s)
{
int fd;
// Accept the new client and link it in.
fd = accept(s, 0, 0);
if (fd < 0)
return;
// Link it in.
polls[num_polls].fd = fd;
polls[num_polls].events = POLLIN;
polls[num_polls].revents = 0;
attached[fd] = 1;
++num_polls;
}
// Process activity from a client.
static void
client_activity(int i, struct pty *pty)
{
int len;
struct packet pkt;
// Read the activity.
len = read(polls[i].fd, &pkt, sizeof(pkt));
if (len <= 0)
{
// Close the socket and go bye bye
attached[polls[i].fd]=0;
close(polls[i].fd);
memcpy(polls + i, polls + i + 1, num_polls - i);
--num_polls;
return;
}
// Okay, check the command byte. Push out data if we need to.
if (pkt.type == MSG_PUSH)
write(pty->fd, pkt.u.buf, pkt.len);
// Window size change.
else if (pkt.type == MSG_WINCH)
{
pty->ws = pkt.u.ws;
ioctl(pty->fd, TIOCSWINSZ, &pty->ws);
}
// Redraw request?
else if (pkt.type == MSG_REDRAW)
{
char c = '\f';
// Guess that ^L might work under certain conditions.
if (((pty->term.c_lflag & (ECHO|ICANON)) == 0) &&
(pty->term.c_cc[VMIN] == 1))
{
write(pty->fd, &c, sizeof(c));
}
}
// Attach request?
else if (pkt.type == MSG_ATTACH)
attached[polls[i].fd] = 1;
else if (pkt.type == MSG_DETACH)
attached[polls[i].fd] = 0;
}
// The master process - It watches over the pty process and the attached
// clients.
static void
master_process(int s, char **argv)
{
struct pty pty;
int i;
#ifdef HAVE_GETRLIMIT
struct rlimit rlim;
// Dynamically allocate structures based on the number of file
// descriptors.
if (getrlimit(RLIMIT_NOFILE, &rlim) < 0)
{
printf("%s: getrlimit: %s\n", progname, strerror(errno));
exit(1);
}
highest_fd = rlim.rlim_cur;
#else
// We can't query the OS for the number of file descriptors, so
// we pull a number out of the air.
highest_fd = 1024;
#endif
polls = (struct pollfd*)malloc(highest_fd * sizeof(struct pollfd));
attached = (int*)malloc(highest_fd * sizeof(int));
// Okay, disassociate ourselves from the original terminal, as we
// don't care what happens to it.
setsid();
// Create a pty in which the process is running.
if (init_pty(&pty, argv) < 0)
{
printf("%s: init_pty: %s\n", progname, strerror(errno));
exit(1);
}
// Set up some signals.
signal(SIGPIPE, SIG_IGN);
signal(SIGXFSZ, SIG_IGN);
signal(SIGHUP, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
signal(SIGINT, die);
signal(SIGTERM, die);
signal(SIGCHLD, die);
// Close the original terminal. We are now a daemon.
fclose(stdin);
fclose(stdout);
fclose(stderr);
// Set a trap to unlink the socket when we die
atexit(unlink_socket);
// Set up the poll structures. Slot 0 is the control socket, slot 1
// is the pty, and slot 2 .. n is the connected clients.
polls[0].fd = s;
polls[0].events = POLLIN;
polls[0].revents = 0;
polls[1].fd = pty.fd;
polls[1].events = POLLIN;
polls[1].revents = 0;
num_polls = FIXED_SLOTS;
// Loop forever.
while (1)
{
// Wait for something to happen.
if (poll(polls, num_polls, -1) < 0)
{
if (errno == EINTR || errno == EAGAIN)
continue;
exit(1);
}
// pty activity?
if (polls[1].revents != 0)
pty_activity(&pty);
// New client?
if (polls[0].revents != 0)
control_activity(s);
// Activity on a client?
for (i = 2; i < num_polls; ++i)
{
if (polls[i].revents != 0)
client_activity(i, &pty);
}
}
}
int
master_main(char **argv)
{
int s;
pid_t pid;
// Create the unix domain socket.
s = create_socket(sockname);
if (s < 0)
{
printf("%s: %s: %s\n", progname, sockname, strerror(errno));
return 1;
}
// Fork off so we can daemonize and such
pid = fork();
if (pid < 0)
{
printf("%s: fork: %s\n", progname, strerror(errno));
return 1;
}
else if (pid == 0)
{
// Child - this becomes the master
master_process(s, argv);
return 0;
}
// Parent - just return.
return 0;
}