#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>
#include <limits.h>
#include <dirent.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
#ifndef 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
#endif
#define APPLICATION_NAME "Rlfe"
#ifndef errno
extern int errno;
#endif
extern int optind;
extern char *optarg;
static char *progname;
static char *progversion;
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;
static FILE *logfile = NULL;
#ifdef DEBUG
FILE *debugfile = NULL;
#define DPRINT0(FMT) (fprintf(debugfile, FMT), fflush(debugfile))
#define DPRINT1(FMT, V1) (fprintf(debugfile, FMT, V1), fflush(debugfile))
#define DPRINT2(FMT, V1, V2) (fprintf(debugfile, FMT, V1, V2), fflush(debugfile))
#else
#define DPRINT0(FMT)
#define DPRINT1(FMT, V1)
#define DPRINT2(FMT, V1, V2)
#endif
struct termios orig_term;
static int rlfe_directory_completion_hook __P((char **));
static int rlfe_directory_rewrite_hook __P((char **));
static char *rlfe_filename_completion_function __P((const char *, int));
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;
}
static void
usage()
{
fprintf (stderr, "%s: usage: %s [-l filename] [-a] [-n appname] [-hv] [command [arguments...]]\n",
progname, progname);
}
int
main(int argc, char** argv)
{
char *path;
int i, append;
int master;
char *name, *logfname, *appname;
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;
if ((progname = strrchr (argv[0], '/')) == 0)
progname = argv[0];
else
progname++;
progversion = RL_LIBRARY_VERSION;
append = 0;
appname = APPLICATION_NAME;
logfname = (char *)NULL;
while ((i = getopt (argc, argv, "ahl:n:v")) != EOF)
{
switch (i)
{
case 'l':
logfname = optarg;
break;
case 'n':
appname = optarg;
break;
case 'a':
append = 1;
break;
case 'h':
usage ();
exit (0);
case 'v':
fprintf (stderr, "%s version %s\n", progname, progversion);
exit (0);
default:
usage ();
exit (2);
}
}
argc -= optind;
argv += optind;
if (logfname)
{
logfile = fopen (logfname, append ? "a" : "w");
if (logfile == 0)
fprintf (stderr, "%s: warning: could not open log file %s: %s\n",
progname, logfname, strerror (errno));
}
rl_readline_name = appname;
#ifdef DEBUG
debugfile = fopen("LOG", "w");
#endif
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[0], &argv[0]);
}
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);
#if 1
rl_directory_completion_hook = rlfe_directory_completion_hook;
rl_completion_entry_function = rlfe_filename_completion_function;
#else
rl_directory_rewrite_hook = rlfe_directory_rewrite_hook;
#endif
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;
if (logfile)
{
#ifndef __GNUC__
char *b;
#else
char b[count + 1];
#endif
int i, j;
#ifndef __GNUC__
b = malloc (count + 1);
if (b) {
#endif
for (i = 0; i < count; i++)
b[i] = buf[buf_count + i];
b[i] = '\0';
for (i = j = 0; i <= count; i++)
{
if (b[i] == '\r')
{
if (b[i+1] != '\n')
b[j++] = '\n';
}
else if (b[i] == '\b')
{
if (i)
j--;
}
else
b[j++] = b[i];
}
fprintf (logfile, "%s", b);
#ifndef __GNUC__
free (b);
}
#endif
}
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);
}
}
}
#ifndef PATH_MAX
# define PATH_MAX 1024
#endif
#define DIRSEP '/'
#define ISDIRSEP(x) ((x) == '/')
#define PATHSEP(x) (ISDIRSEP(x) || (x) == 0)
#define DOT_OR_DOTDOT(x) \
((x)[0] == '.' && (PATHSEP((x)[1]) || \
((x)[1] == '.' && PATHSEP((x)[2]))))
#define FREE(x) if (x) free(x)
#define STRDUP(s, x) do { \
s = strdup (x);\
if (s == 0) \
return ((char *)NULL); \
} while (0)
static int
get_inferior_cwd (path, psize)
char *path;
size_t psize;
{
int n;
static char procfsbuf[PATH_MAX] = { '\0' };
if (procfsbuf[0] == '\0')
sprintf (procfsbuf, "/proc/%d/cwd", (int)child);
n = readlink (procfsbuf, path, psize);
if (n < 0)
return n;
if (n > psize)
return -1;
path[n] = '\0';
return n;
}
static int
rlfe_directory_rewrite_hook (dirnamep)
char **dirnamep;
{
char *ldirname, cwd[PATH_MAX], *retdir, *ld;
int n, ldlen;
ldirname = *dirnamep;
if (*ldirname == '/')
return 0;
n = get_inferior_cwd (cwd, sizeof(cwd) - 1);
if (n < 0)
return 0;
if (n == 0)
{
cwd[0] = '.';
cwd[1] = '\0';
n = 1;
}
for (ld = ldirname; *ld; )
{
if (ISDIRSEP (ld[0]))
ld++;
else if (ld[0] == '.' && PATHSEP(ld[1]))
ld++;
else
break;
}
ldlen = (ld && *ld) ? strlen (ld) : 0;
retdir = (char *)malloc (n + ldlen + 3);
if (retdir == 0)
return 0;
if (ldlen)
sprintf (retdir, "%s/%s", cwd, ld);
else
strcpy (retdir, cwd);
free (ldirname);
*dirnamep = retdir;
DPRINT1("rl_directory_rewrite_hook returns %s\n", retdir);
return 1;
}
static int
rlfe_directory_completion_hook (dirnamep)
char **dirnamep;
{
char *ldirname, *retdir;
int n, ldlen;
ldirname = *dirnamep;
if (*ldirname == '/')
return 0;
n = rlfe_directory_rewrite_hook (dirnamep);
if (n == 0)
return 0;
ldirname = *dirnamep;
ldlen = (ldirname && *ldirname) ? strlen (ldirname) : 0;
if (ldlen == 0 || ldirname[ldlen - 1] != '/')
{
retdir = (char *)malloc (ldlen + 3);
if (retdir == 0)
return 0;
if (ldlen)
strcpy (retdir, ldirname);
else
retdir[ldlen++] = '.';
retdir[ldlen] = '/';
retdir[ldlen+1] = '\0';
free (ldirname);
*dirnamep = retdir;
}
DPRINT1("rl_directory_completion_hook returns %s\n", retdir);
return 1;
}
static char *
rlfe_filename_completion_function (text, state)
const char *text;
int state;
{
static DIR *directory;
static char *filename = (char *)NULL;
static char *dirname = (char *)NULL, *ud = (char *)NULL;
static int flen, udlen;
char *temp;
struct dirent *dentry;
if (state == 0)
{
if (directory)
{
closedir (directory);
directory = 0;
}
FREE (dirname);
FREE (filename);
FREE (ud);
if (text && *text)
STRDUP (filename, text);
else
{
filename = malloc(1);
if (filename == 0)
return ((char *)NULL);
filename[0] = '\0';
}
dirname = (text && *text) ? strdup (text) : strdup (".");
if (dirname == 0)
return ((char *)NULL);
temp = strrchr (dirname, '/');
if (temp)
{
strcpy (filename, ++temp);
*temp = '\0';
}
else
{
dirname[0] = '.';
dirname[1] = '\0';
}
STRDUP (ud, dirname);
udlen = strlen (ud);
rlfe_directory_completion_hook (&dirname);
directory = opendir (dirname);
flen = strlen (filename);
rl_filename_completion_desired = 1;
}
dentry = 0;
while (directory && (dentry = readdir (directory)))
{
if (flen == 0)
{
if (DOT_OR_DOTDOT(dentry->d_name) == 0)
break;
}
else
{
if ((dentry->d_name[0] == filename[0]) &&
(strlen (dentry->d_name) >= flen) &&
(strncmp (filename, dentry->d_name, flen) == 0))
break;
}
}
if (dentry == 0)
{
if (directory)
{
closedir (directory);
directory = 0;
}
FREE (dirname);
FREE (filename);
FREE (ud);
dirname = filename = ud = 0;
return ((char *)NULL);
}
if (ud == 0 || (ud[0] == '.' && ud[1] == '\0'))
temp = strdup (dentry->d_name);
else
{
temp = malloc (1 + udlen + strlen (dentry->d_name));
strcpy (temp, ud);
strcpy (temp + udlen, dentry->d_name);
}
return (temp);
}