#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <grp.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <termios.h>
#ifdef READLINE_LIBRARY
# include "readline.h"
# include "history.h"
#else
# include <readline/readline.h>
# include <readline/history.h>
#endif
#ifndef COMMAND
#define COMMAND "/bin/sh"
#endif
#ifndef COMMAND_ARGS
#define COMMAND_ARGS COMMAND
#endif
#ifndef HAVE_MEMMOVE
# if __GNUC__ > 1
# define memmove(d, s, n) __builtin_memcpy(d, s, n)
# else
# define memmove(d, s, n) memcpy(d, s, n)
# endif
#else
# define memmove(d, s, n) memcpy(d, s, n)
#endif
#define APPLICATION_NAME "Fep"
static int in_from_inferior_fd;
static int out_to_inferior_fd;
#define ECHO_SUPPRESS_MAX 1024
char echo_suppress_buffer[ECHO_SUPPRESS_MAX];
int echo_suppress_start = 0;
int echo_suppress_limit = 0;
#define DEBUG
#ifdef DEBUG
FILE *logfile = NULL;
#define DPRINT0(FMT) (fprintf(logfile, FMT), fflush(logfile))
#define DPRINT1(FMT, V1) (fprintf(logfile, FMT, V1), fflush(logfile))
#define DPRINT2(FMT, V1, V2) (fprintf(logfile, FMT, V1, V2), fflush(logfile))
#else
#define DPRINT0(FMT)
#define DPRINT1(FMT, V1)
#define DPRINT2(FMT, V1, V2)
#endif
struct termios orig_term;
static pid_t child = -1;
static void
sig_child (int signo)
{
int status;
wait (&status);
DPRINT0 ("(Child process died.)\n");
tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
exit (0);
}
volatile int propagate_sigwinch = 0;
void sigwinch_handler(int signal) {
propagate_sigwinch = 1;
}
int get_master_pty(char **name) {
int i, j;
int master = -1;
*name = strdup("/dev/ptyXX");
for (i=0; i<16 && master <= 0; i++) {
for (j=0; j<16 && master <= 0; j++) {
(*name)[5] = 'p';
(*name)[8] = "pqrstuvwxyzPQRST"[i];
(*name)[9] = "0123456789abcdef"[j];
if ((master = open(*name, O_RDWR)) < 0) {
if (errno == ENOENT) {
free (*name);
return (master);
}
}
else {
(*name)[5] = 't';
if (access(*name, R_OK|W_OK) != 0)
{
close(master);
master = -1;
}
}
}
}
if ((master < 0) && (i == 16) && (j == 16)) {
free (*name);
return (master);
}
(*name)[5] = 't';
return (master);
}
int get_slave_pty(char *name) {
struct group *gptr;
gid_t gid;
int slave = -1;
if ((gptr = getgrnam("tty")) != 0) {
gid = gptr->gr_gid;
} else {
gid = -1;
}
chown(name, getuid(), gid);
chmod(name, S_IRUSR|S_IWUSR);
slave = open(name, O_RDWR);
return (slave);
}
static char special_chars[20];
static int special_chars_count;
static void
add_special_char(int ch)
{
if (ch != 0)
special_chars[special_chars_count++] = ch;
}
static int eof_char;
static int
is_special_char(int ch)
{
int i;
#if 0
if (ch == eof_char && rl_point == rl_end)
return 1;
#endif
for (i = special_chars_count; --i >= 0; )
if (special_chars[i] == ch)
return 1;
return 0;
}
static char buf[1024];
static int buf_count = 0;
int num_keys = 0;
static void
null_prep_terminal (int meta)
{
}
static void
null_deprep_terminal ()
{
}
char pending_special_char;
static void
line_handler (char *line)
{
if (line == NULL)
{
char buf[1];
DPRINT0("saw eof!\n");
buf[0] = '\004';
write (out_to_inferior_fd, buf, 1);
}
else
{
static char enter[] = "\r";
int length = strlen (line);
if (length > ECHO_SUPPRESS_MAX-2)
{
echo_suppress_start = 0;
echo_suppress_limit = 0;
}
else
{
if (echo_suppress_limit + length > ECHO_SUPPRESS_MAX - 2)
{
if (echo_suppress_limit - echo_suppress_start + length
<= ECHO_SUPPRESS_MAX - 2)
{
memmove (echo_suppress_buffer,
echo_suppress_buffer + echo_suppress_start,
echo_suppress_limit - echo_suppress_start);
echo_suppress_limit -= echo_suppress_start;
echo_suppress_start = 0;
}
else
{
echo_suppress_limit = 0;
}
echo_suppress_start = 0;
}
memcpy (echo_suppress_buffer + echo_suppress_limit,
line, length);
echo_suppress_limit += length;
echo_suppress_buffer[echo_suppress_limit++] = '\r';
echo_suppress_buffer[echo_suppress_limit++] = '\n';
}
write (out_to_inferior_fd, line, length);
if (pending_special_char == 0)
{
write (out_to_inferior_fd, enter, sizeof(enter)-1);
if (*line)
add_history (line);
}
free (line);
}
rl_callback_handler_remove ();
buf_count = 0;
num_keys = 0;
if (pending_special_char != 0)
{
write (out_to_inferior_fd, &pending_special_char, 1);
pending_special_char = 0;
}
}
int
my_rl_getc (FILE *dummy)
{
int ch = rl_getc (stdin);
if (is_special_char (ch))
{
pending_special_char = ch;
return '\r';
}
return ch;
}
int
main(int argc, char** argv)
{
char *path;
int i;
int master;
char *name;
int in_from_tty_fd;
struct sigaction act;
struct winsize ws;
struct termios t;
int maxfd;
fd_set in_set;
static char empty_string[1] = "";
char *prompt = empty_string;
int ioctl_err = 0;
#ifdef DEBUG
logfile = fopen("LOG", "w");
#endif
rl_readline_name = APPLICATION_NAME;
if ((master = get_master_pty(&name)) < 0)
{
perror("ptypair: could not open master pty");
exit(1);
}
DPRINT1("pty name: '%s'\n", name);
act.sa_handler = sigwinch_handler;
sigemptyset(&(act.sa_mask));
act.sa_flags = 0;
if (sigaction(SIGWINCH, &act, NULL) < 0)
{
perror("ptypair: could not handle SIGWINCH ");
exit(1);
}
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
{
perror("ptypair: could not get window size");
exit(1);
}
if ((child = fork()) < 0)
{
perror("cannot fork");
exit(1);
}
if (child == 0)
{
int slave;
close(master);
#ifdef TIOCSCTTY
if ((slave = get_slave_pty(name)) < 0)
{
perror("ptypair: could not open slave pty");
exit(1);
}
free(name);
#endif
if (setsid() < 0)
{
perror("could not set session leader");
}
#ifdef TIOCSCTTY
if (ioctl(slave, TIOCSCTTY, NULL))
{
perror("could not set new controlling tty");
}
#else
if ((slave = get_slave_pty(name)) < 0)
{
perror("ptypair: could not open slave pty");
exit(1);
}
free(name);
#endif
dup2(slave, STDIN_FILENO);
dup2(slave, STDOUT_FILENO);
dup2(slave, STDERR_FILENO);
if (slave > 2)
{
close(slave);
}
if (ioctl(STDOUT_FILENO, TIOCSWINSZ, &ws) < 0)
{
perror("could not restore window size");
}
{
static char* command_args[] = { COMMAND_ARGS, NULL };
if (argc <= 1)
execvp(COMMAND, command_args);
else
execvp(argv[1], &argv[1]);
}
exit(1);
}
signal (SIGCHLD, sig_child);
free(name);
tcgetattr(STDIN_FILENO, &orig_term);
t = orig_term;
eof_char = t.c_cc[VEOF];
add_special_char(t.c_cc[VINTR]);
add_special_char(t.c_cc[VQUIT]);
add_special_char(t.c_cc[VSUSP]);
#if defined (VDISCARD)
add_special_char(t.c_cc[VDISCARD]);
#endif
#if 0
t.c_lflag |= (ICANON | ISIG | ECHO | ECHOCTL | ECHOE | \
ECHOK | ECHOKE | ECHONL | ECHOPRT );
#else
t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | \
ECHOK | ECHOKE | ECHONL | ECHOPRT );
#endif
t.c_iflag |= IGNBRK;
t.c_cc[VMIN] = 1;
t.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &t);
in_from_inferior_fd = master;
out_to_inferior_fd = master;
rl_instream = fdopen (master, "r");
rl_getc_function = my_rl_getc;
rl_prep_term_function = null_prep_terminal;
rl_deprep_term_function = null_deprep_terminal;
rl_callback_handler_install (prompt, line_handler);
in_from_tty_fd = STDIN_FILENO;
FD_ZERO (&in_set);
maxfd = in_from_inferior_fd > in_from_tty_fd ? in_from_inferior_fd
: in_from_tty_fd;
for (;;)
{
int num;
FD_SET (in_from_inferior_fd, &in_set);
FD_SET (in_from_tty_fd, &in_set);
num = select(maxfd+1, &in_set, NULL, NULL, NULL);
if (propagate_sigwinch)
{
struct winsize ws;
if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
{
ioctl (master, TIOCSWINSZ, &ws);
}
propagate_sigwinch = 0;
continue;
}
if (num <= 0)
{
perror ("select");
exit (-1);
}
if (FD_ISSET (in_from_tty_fd, &in_set))
{
extern int readline_echoing_p;
struct termios term_master;
int do_canon = 1;
int ioctl_ret;
DPRINT1("[tty avail num_keys:%d]\n", num_keys);
ioctl_ret = tcgetattr(master, &term_master);
if (ioctl_ret >= 0)
{
DPRINT2 ("echo:%d, canon:%d\n",
(term_master.c_lflag & ECHO) != 0,
(term_master.c_lflag & ICANON) != 0);
do_canon = (term_master.c_lflag & ICANON) != 0;
readline_echoing_p = (term_master.c_lflag & ECHO) != 0;
}
else
{
if (ioctl_err == 0)
DPRINT1("tcgetattr on master fd failed: errno = %d\n", errno);
ioctl_err = 1;
}
if (do_canon == 0 && num_keys == 0)
{
char ch[10];
int count = read (STDIN_FILENO, ch, sizeof(ch));
write (out_to_inferior_fd, ch, count);
}
else
{
if (num_keys == 0)
{
int i;
if (prompt != empty_string)
free (prompt);
prompt = malloc (buf_count + 1);
if (prompt == NULL)
prompt = empty_string;
else
{
memcpy (prompt, buf, buf_count);
prompt[buf_count] = '\0';
DPRINT1("New prompt '%s'\n", prompt);
#if 0
rl_already_prompted = buf_count > 0;
#else
if (buf_count > 0)
write (1, "\r", 1);
#endif
}
rl_callback_handler_install (prompt, line_handler);
}
num_keys++;
rl_callback_read_char ();
}
}
else
{
int i;
int count;
int old_count;
if (buf_count > (sizeof(buf) >> 2))
buf_count = 0;
count = read (in_from_inferior_fd, buf+buf_count,
sizeof(buf) - buf_count);
if (count <= 0)
{
DPRINT0 ("(Connection closed by foreign host.)\n");
tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
exit (0);
}
old_count = buf_count;
while (echo_suppress_start < echo_suppress_limit
&& count > 0
&& buf[buf_count] == echo_suppress_buffer[echo_suppress_start])
{
count--;
buf_count++;
echo_suppress_start++;
}
if (count > 0)
write (1, buf + buf_count, count);
buf_count += count;
#if 1
for (i = buf_count; --i >= old_count; )
#else
for (i = buf_count - 1; i-- >= buf_count - count; )
#endif
{
if (buf[i] == '\n' || buf[i] == '\r')
{
i++;
memmove (buf, buf+i, buf_count - i);
buf_count -= i;
break;
}
}
DPRINT2("-> i: %d, buf_count: %d\n", i, buf_count);
}
}
}