diff options
Diffstat (limited to 'libtecla-1.4.1/enhance.c')
-rw-r--r-- | libtecla-1.4.1/enhance.c | 689 |
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 -} |