summaryrefslogtreecommitdiff
path: root/libtecla-1.4.1/enhance.c
diff options
context:
space:
mode:
Diffstat (limited to 'libtecla-1.4.1/enhance.c')
-rw-r--r--libtecla-1.4.1/enhance.c689
1 files changed, 0 insertions, 689 deletions
diff --git a/libtecla-1.4.1/enhance.c b/libtecla-1.4.1/enhance.c
deleted file mode 100644
index 72f5061..0000000
--- a/libtecla-1.4.1/enhance.c
+++ /dev/null
@@ -1,689 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <signal.h>
-#include <locale.h>
-
-#include <unistd.h>
-#include <termios.h>
-
-#include <fcntl.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <dirent.h>
-
-#if HAVE_SYSV_PTY
-#include <stropts.h> /* System-V stream I/O */
-char *ptsname(int fd);
-int grantpt(int fd);
-int unlockpt(int fd);
-#endif
-
-#include "libtecla.h"
-
-/*
- * Pseudo-terminal devices are found in the following directory.
- */
-#define PTY_DEV_DIR "/dev/"
-
-/*
- * Pseudo-terminal controller device file names start with the following
- * prefix.
- */
-#define PTY_CNTRL "pty"
-
-/*
- * Pseudo-terminal slave device file names start with the following
- * prefix.
- */
-#define PTY_SLAVE "tty"
-
-/*
- * Specify the maximum suffix length for the control and slave device
- * names.
- */
-#define PTY_MAX_SUFFIX 10
-
-/*
- * Set the maximum length of the master and slave terminal device filenames,
- * including space for a terminating '\0'.
- */
-#define PTY_MAX_NAME (sizeof(PTY_DEV_DIR)-1 + \
- (sizeof(PTY_SLAVE) > sizeof(PTY_CNTRL) ? \
- sizeof(PTY_SLAVE) : sizeof(PTY_CNTRL))-1 \
- + PTY_MAX_SUFFIX + 1)
-/*
- * Set the maximum length of an input line.
- */
-#define PTY_MAX_LINE 4096
-
-/*
- * Set the size of the buffer used for accumulating bytes written by the
- * user's terminal to its stdout.
- */
-#define PTY_MAX_READ 1000
-
-/*
- * Set the amount of memory used to record history.
- */
-#define PTY_HIST_SIZE 10000
-
-/*
- * Set the timeout delay used to check for quickly arriving
- * sequential output from the application.
- */
-#define PTY_READ_TIMEOUT 100000 /* micro-seconds */
-
-static int pty_open_master(const char *prog, int *cntrl, char *slave_name);
-static int pty_open_slave(const char *prog, char *slave_name);
-static int pty_child(const char *prog, int slave, char *argv[]);
-static int pty_parent(const char *prog, int cntrl);
-static int pty_stop_parent(int waserr, int cntrl, GetLine *gl, char *rbuff);
-static GL_FD_EVENT_FN(pty_read_from_program);
-static int pty_write_to_fd(int fd, const char *string, int n);
-static void pty_child_exited(int sig);
-static int pty_master_readable(int fd, long usec);
-
-/*.......................................................................
- * Run a program with enhanced terminal editing facilities.
- *
- * Usage:
- * enhance program [args...]
- */
-int main(int argc, char *argv[])
-{
- int cntrl = -1; /* The fd of the pseudo-terminal controller device */
- int slave = -1; /* The fd of the pseudo-terminal slave device */
- pid_t pid; /* The return value of fork() */
- int status; /* The return statuses of the parent and child functions */
- char slave_name[PTY_MAX_NAME]; /* The filename of the slave end of the */
- /* pseudo-terminal. */
- char *prog; /* The name of the program (ie. argv[0]) */
-/*
- * Check the arguments.
- */
- if(argc < 2) {
- fprintf(stderr, "Usage: %s <program> [arguments...]\n", argv[0]);
- return 1;
- };
-/*
- * Get the name of the program.
- */
- prog = argv[0];
-/*
- * If the user has the LC_CTYPE or LC_ALL environment variables set,
- * enable display of characters corresponding to the specified locale.
- */
- (void) setlocale(LC_CTYPE, "");
-/*
- * If the program is taking its input from a pipe or a file, or
- * sending its output to something other than a terminal, run the
- * program without tecla.
- */
- if(!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {
- if(execvp(argv[1], argv + 1) < 0) {
- fprintf(stderr, "%s: Unable to execute %s (%s).\n", prog, argv[1],
- strerror(errno));
- fflush(stderr);
- _exit(1);
- };
- };
-/*
- * Open the master side of a pseudo-terminal pair, and return
- * the corresponding file descriptor and the filename of the
- * slave end of the pseudo-terminal.
- */
- if(pty_open_master(prog, &cntrl, slave_name))
- return 1;
-/*
- * Set up a signal handler to watch for the child process exiting.
- */
- signal(SIGCHLD, pty_child_exited);
-/*
- * The above signal handler sends the parent process a SIGINT signal.
- * This signal is caught by gl_get_line(), which resets the terminal
- * settings, and if the application signal handler for this signal
- * doesn't abort the process, gl_get_line() returns NULL with errno
- * set to EINTR. Arrange to ignore the signal, so that gl_get_line()
- * returns and we have a chance to cleanup.
- */
- signal(SIGINT, SIG_IGN);
-/*
- * We will read user input in one process, and run the user's program
- * in a child process.
- */
- pid = fork();
- if(pid < 0) {
- fprintf(stderr, "%s: Unable to fork child process (%s).\n", prog,
- strerror(errno));
- return 1;
- };
-/*
- * Are we the parent?
- */
- if(pid!=0) {
- status = pty_parent(prog, cntrl);
- close(cntrl);
- } else {
- close(cntrl); /* The child doesn't use the slave device */
- signal(SIGCHLD, pty_child_exited);
- if((slave = pty_open_slave(prog, slave_name)) >= 0) {
- status = pty_child(prog, slave, argv + 1);
- close(slave);
- } else {
- status = 1;
- };
- };
- return status;
-}
-
-/*.......................................................................
- * Open the master side of a pseudo-terminal pair, and return
- * the corresponding file descriptor and the filename of the
- * slave end of the pseudo-terminal.
- *
- * Input/Output:
- * prog const char * The name of this program.
- * cntrl int * The file descriptor of the pseudo-terminal
- * controller device will be assigned tp *cntrl.
- * slave_name char * The file-name of the pseudo-terminal slave device
- * will be recorded in slave_name[], which must have
- * at least PTY_MAX_NAME elements.
- * Output:
- * return int 0 - OK.
- * 1 - Error.
- */
-static int pty_open_master(const char *prog, int *cntrl, char *slave_name)
-{
- char master_name[PTY_MAX_NAME]; /* The filename of the master device */
- DIR *dir; /* The directory iterator */
- struct dirent *file; /* A file in "/dev" */
-/*
- * Mark the controller device as not opened yet.
- */
- *cntrl = -1;
-/*
- * On systems with the Sys-V pseudo-terminal interface, we don't
- * have to search for a free master terminal. We just open /dev/ptmx,
- * and if there is a free master terminal device, we are given a file
- * descriptor connected to it.
- */
-#if HAVE_SYSV_PTY
- *cntrl = open("/dev/ptmx", O_RDWR);
- if(*cntrl >= 0) {
-/*
- * Get the filename of the slave side of the pseudo-terminal.
- */
- char *name = ptsname(*cntrl);
- if(name) {
- if(strlen(name)+1 > PTY_MAX_NAME) {
- fprintf(stderr, "%s: Slave pty filename too long.\n", prog);
- return 1;
- };
- strcpy(slave_name, name);
-/*
- * If unable to get the slave name, discard the controller file descriptor,
- * ready to try a search instead.
- */
- } else {
- close(*cntrl);
- *cntrl = -1;
- };
- } else {
-#endif
-/*
- * On systems without /dev/ptmx, or if opening /dev/ptmx failed,
- * we open one master terminal after another, until one that isn't
- * in use by another program is found.
- *
- * Open the devices directory.
- */
- dir = opendir(PTY_DEV_DIR);
- if(!dir) {
- fprintf(stderr, "%s: Couldn't open %s (%s)\n", prog, PTY_DEV_DIR,
- strerror(errno));
- return 1;
- };
-/*
- * Look for pseudo-terminal controller device files in the devices
- * directory.
- */
- while(*cntrl < 0 && (file = readdir(dir))) {
- if(strncmp(file->d_name, PTY_CNTRL, sizeof(PTY_CNTRL)-1) == 0) {
-/*
- * Get the common extension of the control and slave filenames.
- */
- const char *ext = file->d_name + sizeof(PTY_CNTRL)-1;
- if(strlen(ext) > PTY_MAX_SUFFIX)
- continue;
-/*
- * Attempt to open the control file.
- */
- strcpy(master_name, PTY_DEV_DIR);
- strcat(master_name, PTY_CNTRL);
- strcat(master_name, ext);
- *cntrl = open(master_name, O_RDWR);
- if(*cntrl < 0)
- continue;
-/*
- * Attempt to open the matching slave file.
- */
- strcpy(slave_name, PTY_DEV_DIR);
- strcat(slave_name, PTY_SLAVE);
- strcat(slave_name, ext);
- };
- };
- closedir(dir);
-#if HAVE_SYSV_PTY
- };
-#endif
-/*
- * Did we fail to find a pseudo-terminal pair that we could open?
- */
- if(*cntrl < 0) {
- fprintf(stderr, "%s: Unable to find a free pseudo-terminal.\n", prog);
- return 1;
- };
-/*
- * System V systems require the program that opens the master to
- * grant access to the slave side of the pseudo-terminal.
- */
-#ifdef HAVE_SYSV_PTY
- if(grantpt(*cntrl) < 0 ||
- unlockpt(*cntrl) < 0) {
- fprintf(stderr, "%s: Unable to unlock terminal (%s).\n", prog,
- strerror(errno));
- return 1;
- };
-#endif
-/*
- * Success.
- */
- return 0;
-}
-
-/*.......................................................................
- * Open the slave end of a pseudo-terminal.
- *
- * Input:
- * prog const char * The name of this program.
- * slave_name char * The filename of the slave device.
- * Output:
- * return int The file descriptor of the successfully opened
- * slave device, or < 0 on error.
- */
-static int pty_open_slave(const char *prog, char *slave_name)
-{
- int fd; /* The file descriptor of the slave device */
-/*
- * Place the process in its own process group. In system-V based
- * OS's, this ensures that when the pseudo-terminal is opened, it
- * becomes the controlling terminal of the process.
- */
- if(setsid() < 0) {
- fprintf(stderr, "%s: Unable to form new process group (%s).\n", prog,
- strerror(errno));
- return -1;
- };
-/*
- * Attempt to open the specified device.
- */
- fd = open(slave_name, O_RDWR);
- if(fd < 0) {
- fprintf(stderr, "%s: Unable to open pseudo-terminal slave device (%s).\n",
- prog, strerror(errno));
- return -1;
- };
-/*
- * On system-V streams based systems, we need to push the stream modules
- * that implement pseudo-terminal and termio interfaces. At least on
- * Solaris, which pushes these automatically when a slave is opened,
- * this is redundant, so ignore errors when pushing the modules.
- */
-#if HAVE_SYSV_PTY
- (void) ioctl(fd, I_PUSH, "ptem");
- (void) ioctl(fd, I_PUSH, "ldterm");
-/*
- * On BSD based systems other than SunOS 4.x, the following makes the
- * pseudo-terminal the controlling terminal of the child process.
- * According to the pseudo-terminal example code in Steven's
- * Advanced programming in the unix environment, the !defined(CIBAUD)
- * part of the clause prevents this from being used under SunOS. Since
- * I only have his code with me, and won't have access to the book,
- * I don't know why this is necessary.
- */
-#elif defined(TIOCSCTTY) && !defined(CIBAUD)
- if(ioctl(fd, TIOCSCTTY, (char *) 0) < 0) {
- fprintf(stderr, "%s: Unable to establish controlling terminal (%s).\n",
- prog, strerror(errno));
- close(fd);
- return -1;
- };
-#endif
- return fd;
-}
-
-/*.......................................................................
- * Read input from the controlling terminal of the program, using
- * gl_get_line(), and feed it to the user's program running in a child
- * process, via the controller side of the pseudo-terminal. Also pass
- * data received from the user's program via the conroller end of
- * the pseudo-terminal, to stdout.
- *
- * Input:
- * prog const char * The name of this program.
- * cntrl int The file descriptor of the controller end of the
- * pseudo-terminal.
- * Output:
- * return int 0 - OK.
- * 1 - Error.
- */
-static int pty_parent(const char *prog, int cntrl)
-{
- GetLine *gl = NULL; /* The gl_get_line() resource object */
- char *line; /* An input line read from the user */
- char *rbuff=NULL; /* A buffer for reading from the pseudo terminal */
-/*
- * Allocate the gl_get_line() resource object.
- */
- gl = new_GetLine(PTY_MAX_LINE, PTY_HIST_SIZE);
- if(!gl)
- return pty_stop_parent(1, cntrl, gl, rbuff);
-/*
- * Allocate a buffer to use to accumulate bytes read from the
- * pseudo-terminal.
- */
- rbuff = (char *) malloc(PTY_MAX_READ+1);
- if(!rbuff)
- return pty_stop_parent(1, cntrl, gl, rbuff);
- rbuff[0] = '\0';
-/*
- * Register an event handler to watch for data appearing from the
- * user's program on the controller end of the pseudo terminal.
- */
- if(gl_watch_fd(gl, cntrl, GLFD_READ, pty_read_from_program, rbuff))
- return pty_stop_parent(1, cntrl, gl, rbuff);
-/*
- * Read input lines from the user and pass them on to the user's program,
- * by writing to the controller end of the pseudo-terminal.
- */
- while((line=gl_get_line(gl, rbuff, NULL, 0))) {
- if(pty_write_to_fd(cntrl, line, strlen(line)))
- return pty_stop_parent(1, cntrl, gl, rbuff);
- rbuff[0] = '\0';
- };
- return pty_stop_parent(0, cntrl, gl, rbuff);
-}
-
-/*.......................................................................
- * This is a private return function of pty_parent(), used to release
- * dynamically allocated resources, close the controller end of the
- * pseudo-terminal, and wait for the child to exit. It returns the
- * exit status of the child process, unless the caller reports an
- * error itself, in which case the caller's error status is returned.
- *
- * Input:
- * waserr int True if the caller is calling this function because
- * an error occured.
- * cntrl int The file descriptor of the controller end of the
- * pseudo-terminal.
- * gl GetLine * The resource object of gl_get_line().
- * rbuff char * The buffer used to accumulate bytes read from
- * the pseudo-terminal.
- * Output:
- * return int The desired exit status of the program.
- */
-static int pty_stop_parent(int waserr, int cntrl, GetLine *gl, char *rbuff)
-{
- int status; /* The return status of the child process */
-/*
- * Close the controller end of the terminal.
- */
- close(cntrl);
-/*
- * Delete the resource object.
- */
- gl = del_GetLine(gl);
-/*
- * Delete the read buffer.
- */
- if(rbuff)
- free(rbuff);
-/*
- * Wait for the user's program to end.
- */
- (void) wait(&status);
-/*
- * Return either our error status, or the return status of the child
- * program.
- */
- return waserr ? 1 : status;
-}
-
-/*.......................................................................
- * Run the user's program, with its stdin and stdout connected to the
- * slave end of the psuedo-terminal.
- *
- * Input:
- * prog const char * The name of this program.
- * slave int The file descriptor of the slave end of the
- * pseudo terminal.
- * argv char *[] The argument vector to pass to the user's program,
- * where argv[0] is the name of the user's program,
- * and the last argument is followed by a pointer
- * to NULL.
- * Output:
- * return int If this function returns at all, an error must
- * have occured when trying to overlay the process
- * with the user's program. In this case 1 is
- * returned.
- */
-static int pty_child(const char *prog, int slave, char *argv[])
-{
- struct termios attr; /* The terminal attributes */
-/*
- * We need to stop the pseudo-terminal from echoing everything that we send it.
- */
- if(tcgetattr(slave, &attr)) {
- fprintf(stderr, "%s: Can't get pseudo-terminal attributes (%s).\n", prog,
- strerror(errno));
- return 1;
- };
- attr.c_lflag &= ~(ECHO);
- while(tcsetattr(slave, TCSADRAIN, &attr)) {
- if(errno != EINTR) {
- fprintf(stderr, "%s: tcsetattr error: %s\n", prog, strerror(errno));
- return 1;
- };
- };
-/*
- * Arrange for stdin, stdout and stderr to be connected to the slave device,
- * ignoring errors that imply that either stdin or stdout is closed.
- */
- while(dup2(slave, STDIN_FILENO) < 0 && errno==EINTR)
- ;
- while(dup2(slave, STDOUT_FILENO) < 0 && errno==EINTR)
- ;
- while(dup2(slave, STDERR_FILENO) < 0 && errno==EINTR)
- ;
-/*
- * Run the user's program.
- */
- if(execvp(argv[0], argv) < 0) {
- fprintf(stderr, "%s: Unable to execute %s (%s).\n", prog, argv[0],
- strerror(errno));
- fflush(stderr);
- _exit(1);
- };
- return 0; /* This should never be reached */
-}
-
-/*.......................................................................
- * This is the event-handler that is called by gl_get_line() whenever
- * there is tet waiting to be read from the user's program, via the
- * controller end of the pseudo-terminal. See libtecla.h for details
- * about its arguments.
- */
-static GL_FD_EVENT_FN(pty_read_from_program)
-{
- char *nlptr; /* A pointer to the last newline in the accumulated string */
- char *crptr; /* A pointer to the last '\r' in the accumulated string */
- char *nextp; /* A pointer to the next unprocessed character */
-/*
- * Get the read buffer in which we are accumulating a line to be
- * forwarded to stdout.
- */
- char *rbuff = (char *) data;
-/*
- * New data may arrive while we are processing the current read, and
- * it is more efficient to display this here than to keep returning to
- * gl_get_line() and have it display the latest prefix as a prompt,
- * followed by the current input line, so we loop, delaying a bit at
- * the end of each iteration to check for more data arriving from
- * the application, before finally returning to gl_get_line() when
- * no more input is available.
- */
- do {
-/*
- * Get the current length of the output string.
- */
- int len = strlen(rbuff);
-/*
- * Read the text from the program.
- */
- int nnew = read(fd, rbuff + len, PTY_MAX_READ - len);
- if(nnew < 0)
- return GLFD_ABORT;
- len += nnew;
-/*
- * Nul terminate the accumulated string.
- */
- rbuff[len] = '\0';
-/*
- * Find the last newline and last carriage return in the buffer, if any.
- */
- nlptr = strrchr(rbuff, '\n');
- crptr = strrchr(rbuff, '\r');
-/*
- * We want to output up to just before the last newline or carriage
- * return. If there are no newlines of carriage returns in the line,
- * and the buffer is full, then we should output the whole line. In
- * all cases a new output line will be started after the latest text
- * has been output. The intention is to leave any incomplete line
- * in the buffer, for (perhaps temporary) use as the current prompt.
- */
- if(nlptr) {
- nextp = crptr && crptr < nlptr ? crptr : nlptr;
- } else if(crptr) {
- nextp = crptr;
- } else if(len >= PTY_MAX_READ) {
- nextp = rbuff + len;
- } else {
- nextp = NULL;
- };
-/*
- * Do we have any text to output yet?
- */
- if(nextp) {
-/*
- * If there was already some text in rbuff before this function
- * was called, then it will have been used as a prompt. Arrange
- * to rewrite this prefix, plus the new suffix, by moving back to
- * the start of the line.
- */
- if(len > 0)
- (void) pty_write_to_fd(STDOUT_FILENO, "\r", 1);
-/*
- * Write everything up to the last newline to stdout.
- */
- (void) pty_write_to_fd(STDOUT_FILENO, rbuff, nextp - rbuff);
-/*
- * Start a new line.
- */
- (void) pty_write_to_fd(STDOUT_FILENO, "\r\n", 2);
-/*
- * Skip trailing carriage returns and newlines.
- */
- while(*nextp=='\n' || *nextp=='\r')
- nextp++;
-/*
- * Move any unwritten text following the newline, to the start of the
- * buffer.
- */
- memmove(rbuff, nextp, len - (nextp - rbuff) + 1);
- };
- } while(pty_master_readable(fd, PTY_READ_TIMEOUT));
-/*
- * Make the incomplete line in the output buffer the current prompt.
- */
- gl_replace_prompt(gl, rbuff);
- return GLFD_REFRESH;
-}
-
-/*.......................................................................
- * Write a given string to a specified file descriptor.
- *
- * Input:
- * fd int The file descriptor to write to.
- * string const char * The string to write (of at least 'n' characters).
- * n int The number of characters to write.
- * Output:
- * return int 0 - OK.
- * 1 - Error.
- */
-static int pty_write_to_fd(int fd, const char *string, int n)
-{
- int ndone = 0; /* The number of characters written so far */
-/*
- * Do as many writes as are needed to write the whole string.
- */
- while(ndone < n) {
- int nnew = write(fd, string + ndone, n - ndone);
- if(nnew > 0)
- ndone += nnew;
- else if(errno != EINTR)
- return 1;
- };
- return 0;
-}
-
-/*.......................................................................
- * This is the signal handler that is called when the child process
- * that is running the user's program exits for any reason. It closes
- * the slave end of the terminal, so that gl_get_line() in the parent
- * process sees an end of file.
- */
-static void pty_child_exited(int sig)
-{
- raise(SIGINT);
-}
-
-/*.......................................................................
- * Return non-zero after a given amount of time if there is data waiting
- * to be read from a given file descriptor.
- *
- * Input:
- * fd int The descriptor to watch.
- * usec long The number of micro-seconds to wait for input to
- * arrive before giving up.
- * Output:
- * return int 0 - No data is waiting to be read (or select isn't
- * available).
- * 1 - Data is waiting to be read.
- */
-static int pty_master_readable(int fd, long usec)
-{
-#if HAVE_SELECT
- fd_set rfds; /* The set of file descriptors to check */
- struct timeval timeout; /* The timeout */
- FD_ZERO(&rfds);
- FD_SET(fd, &rfds);
- timeout.tv_sec = 0;
- timeout.tv_usec = usec;
- return select(fd+1, &rfds, NULL, NULL, &timeout) == 1;
-#else
- return 0;
-#endif
-}