/* shellout - execute prorgams in a pty shell programmatically
* Copyright (c) 2003 Michael B. Allen <mba2000 ioplex.com>
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#if defined(__digital__) && defined(__unix__)
#include <sys/termios.h>
#include <sys/ioctl.h>
#include <strings.h>
#elif defined(__APPLE__) && defined(__MACH__)
#include <util.h>
#include <sys/ioctl.h>
#elif defined(__FreeBSD__)
#include <sys/types.h>
#include <sys/select.h>
#include <libutil.h>
#elif defined(linux)
#include <pty.h>
#endif
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <signal.h>
#include "mba/msgno.h"
#include "mba/text.h"
#include "mba/misc.h"
#include "mba/shellout.h"
#define TERM_PREPARE "\x1b[?1048h\x1b[?1047h\x1b[2J\x1b[H"
#define TERM_RESTORE "\x1b[?1047l\x1b[?1048l"
static volatile sig_atomic_t sig;
/*
static const char *signal_names[] = {
"", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGPOLL/SIGIO", "SIGPWR", "SIGSYS" };
static const char *
signalstr(int sig)
{
if (sig < 1 || sig > 31) {
return "SIGUNKNOWN";
}
return signal_names[sig];
}
*/
typedef void (*sighandler_fn)(int);
static sighandler_fn
signal_intr(int signum, sighandler_fn handler)
{
struct sigaction act, oact;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
#ifdef SA_INTERRUPT /* SunOS */
act.sa_flags |= SA_INTERRUPT;
#endif
if (sigaction(signum, &act, &oact) < 0)
return SIG_ERR;
return oact.sa_handler;
}
static void
sighandler(int s)
{
sig = s;
/* fprintf(stderr, "%s\n", signalstr(s));
*/
}
/* Read individual bytes from descriptor fd until either;
* o one of the patterns in p is encountered in which case it's index+1 is returned
* o EOF is reached in which case 0 is returned or
* o the timeout is reached, a signal is received, or error occurs in which case -1
* is returned and errno is set appropriately. If a timeout occurs errno will
* be EINTR.
*/
int
sho_expect(struct sho *sh, const unsigned char *pv[], int pn, unsigned char *dst, size_t dn, int timeout)
{
int plen, di, j, i;
ssize_t n;
const unsigned char *p;
if (sh == NULL || pv == NULL || dst == NULL) {
PMNO(errno = EINVAL);
return -1;
}
if (signal_intr(SIGALRM, sighandler) == SIG_ERR) {
PMNO(errno);
return -1;
}
alarm(timeout);
for (di = 0;; ) {
if ((n = read(sh->ptym, dst + di, 1)) < 1) {
if (n < 0) {
PMNO(errno);
}
break;
}
/*
fputc(dst[di], stderr);
*/
++di;
di = di % dn;
for (j = 0; j < pn; j++) {
p = pv[j];
plen = strlen((const char *)p);
if (di < plen) {
continue;
}
for (i = 0; i < plen && p[i] == dst[(di - plen + i) % dn]; i++) {
;
}
if (i == plen) {
dst[di] = '\0';
alarm(0);
return j + 1; /* match */
}
}
}
alarm(0);
dst[di] = '\0';
return n == 0 ? 0 : -1;
}
struct sho *
sho_open(const unsigned char *shname, const unsigned char *ps1, int flags)
{
struct sho *sh;
struct termios t1;
struct winsize win;
unsigned char buf[32], ps1env[32] = "PS1=";
size_t ps1len;
const unsigned char *pv[1];
pv[0] = ps1env + 4;
if ((sh = malloc(sizeof *sh)) == NULL) {
PMNO(errno);
return NULL;
}
sh->flags = flags;
ps1len = str_copy(ps1, ps1 + 32, ps1env + 4, ps1env + 32, -1);
if (isatty(STDIN_FILENO)) {
sh->flags |= SHO_FLAGS_ISATTY;
if ((flags & SHO_FLAGS_INTERACT)) {
if (tcgetattr(STDIN_FILENO, &sh->t0) < 0) {
PMNO(errno);
free(sh);
return NULL;
}
if (writen(STDOUT_FILENO, TERM_PREPARE, strlen(TERM_PREPARE)) < 0) {
free(sh);
return NULL;
}
t1 = sh->t0;
t1.c_lflag &= ~(ICANON | ECHO);
t1.c_cc[VTIME] = 0;
t1.c_cc[VMIN] = 1;
if (tcsetattr(STDIN_FILENO, TCSANOW, &t1)) {
PMNO(errno);
goto err;
}
if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win) < 0) {
PMNO(errno);
goto err;
}
}
}
#if !defined(_HPUX_SOURCE)
if ((sh->flags & SHO_FLAGS_ISATTY) && (sh->flags & SHO_FLAGS_INTERACT)) {
sh->pid = forkpty(&sh->ptym, NULL, &t1, &win);
} else {
sh->pid = forkpty(&sh->ptym, NULL, NULL, NULL);
}
#else
/* this will fail miserably on HP-UX */
sh->pid = 0;
#endif
if (sh->pid == -1) {
PMNO(errno);
goto err;
} else if (sh->pid == 0) { /* child */
char *args[2];
args[0] = (char *)shname;
args[1] = NULL;
if (tcgetattr(STDIN_FILENO, &t1) < 0) {
MMNO(errno);
exit(errno);
}
t1.c_lflag &= ~(ICANON | ECHO);
t1.c_cc[VTIME] = 0;
t1.c_cc[VMIN] = 1;
if (tcsetattr(STDIN_FILENO, TCSANOW, &t1) < 0 || putenv((char *)ps1env) < 0) {
MMNO(errno);
exit(errno);
}
execvp((const char *)shname, args);
MMNO(errno);
exit(errno);
}
if (sho_expect(sh, pv, 1, buf, 32, 10) <= 0) {
PMNO(errno);
goto err;
}
if ((sh->flags & SHO_FLAGS_ISATTY) &&
(flags & SHO_FLAGS_INTERACT) &&
writen(STDOUT_FILENO, ps1env + 4, ps1len) < 0) {
PMNO(errno);
goto err;
}
return sh;
err:
if ((sh->flags & SHO_FLAGS_ISATTY) && (flags & SHO_FLAGS_INTERACT)) {
writen(STDOUT_FILENO, TERM_RESTORE, strlen(TERM_RESTORE));
tcsetattr(STDIN_FILENO, TCSANOW, &sh->t0);
}
free(sh);
return NULL;
}
int
sho_close(struct sho *sh)
{
int status;
waitpid(sh->pid, &status, 0);
status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
if ((sh->flags & SHO_FLAGS_ISATTY) && (sh->flags & SHO_FLAGS_INTERACT)) {
writen(STDOUT_FILENO, TERM_RESTORE, strlen(TERM_RESTORE));
tcsetattr(STDIN_FILENO, TCSANOW, &sh->t0);
}
free(sh);
return status;
}
int
sho_loop(struct sho *sh, const unsigned char *pv[], int pn, int timeout)
{
unsigned char buf[BUFSIZ];
fd_set set0, set1;
ssize_t n;
int bi = 0;
if (sh == NULL || pv == NULL) {
PMNO(errno = EINVAL);
return -1;
}
FD_ZERO(&set0);
FD_SET(sh->ptym, &set0);
FD_SET(STDIN_FILENO, &set0);
for ( ;; ) {
if (signal_intr(SIGALRM, sighandler) == SIG_ERR) {
PMNO(errno);
return -1;
}
alarm(timeout);
set1 = set0;
if (select(sh->ptym + 1, &set1, NULL, NULL, NULL) < 0) {
PMNO(errno);
return -1;
}
if (FD_ISSET(STDIN_FILENO, &set1)) {
if ((n = read(STDIN_FILENO, buf, BUFSIZ)) < 0) {
PMNO(errno);
return -1;
}
if (n == 0) { /* EOF */
return 0;
}
if ((sh->flags & SHO_FLAGS_INTERACT)) {
writen(STDOUT_FILENO, buf, n);
}
if (writen(sh->ptym, buf, n) < 0) {
PMNO(errno);
return -1;
}
}
if (FD_ISSET(sh->ptym, &set1)) {
int j;
if ((n = read(sh->ptym, buf + bi, 1)) < 0) {
PMNO(errno);
return -1;
} else if (n == 0) { /* EOF */
return 0;
}
if (write(STDOUT_FILENO, buf + bi, 1) < 0) {
PMNO(errno);
return -1;
}
/*
fputc(buf[bi], stderr);
*/
++bi;
bi = bi % BUFSIZ;
for (j = 0; j < pn; j++) {
const unsigned char *p;
int plen, i;
p = pv[j];
plen = strlen((const char *)p);
if (bi < plen) {
continue;
}
for (i = 0; i < plen && p[i] == buf[(bi - plen + i) % BUFSIZ]; i++) {
;
}
if (i == plen) {
buf[bi] = '\0';
alarm(0);
return j + 1; /* match */
}
}
}
}
}
#ifdef TEST
int
main(int argc, char *argv[])
{
int ret, i, n;
struct sho *sh;
const char *pv[] = { "sh> " };
char buf[256];
errno = 0;
if (argc != 2) {
MMSG("Must provide shell command as one argument (i.e. use quotes)");
return EXIT_FAILURE;
}
sh = sho_open("sh", "sh> ", 0);
n = sprintf(buf, "%s\n", argv[1]);
fputs(buf, stderr);
writen(sh->ptym, buf, n);
if ((i = sho_expect(sh, pv, 1, buf, 256, 10)) != 1) {
MMSG("timeout occurred, writing Ctrl-C");
writen(sh->ptym, "\x03", 1); /* Ctrl-C is SIGINT */
if ((i = sho_expect(sh, pv, 1, buf, 256, 10)) != 1) {
MMSG("timeout again! Sending SIGKILL");
kill(sh->pid, SIGKILL);
sho_close(sh);
return EXIT_FAILURE;
}
}
writen(sh->ptym, "exit $?\n", 8);
fputs("exit $?\n", stderr);
ret = sho_close(sh);
if (ret) {
MMSG("Exit status: %d", ret);
}
return EXIT_SUCCESS;
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1