1*0Sstevel@tonic-gate /*
2*0Sstevel@tonic-gate * CDDL HEADER START
3*0Sstevel@tonic-gate *
4*0Sstevel@tonic-gate * The contents of this file are subject to the terms of the
5*0Sstevel@tonic-gate * Common Development and Distribution License, Version 1.0 only
6*0Sstevel@tonic-gate * (the "License"). You may not use this file except in compliance
7*0Sstevel@tonic-gate * with the License.
8*0Sstevel@tonic-gate *
9*0Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10*0Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing.
11*0Sstevel@tonic-gate * See the License for the specific language governing permissions
12*0Sstevel@tonic-gate * and limitations under the License.
13*0Sstevel@tonic-gate *
14*0Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each
15*0Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16*0Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the
17*0Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying
18*0Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner]
19*0Sstevel@tonic-gate *
20*0Sstevel@tonic-gate * CDDL HEADER END
21*0Sstevel@tonic-gate */
22*0Sstevel@tonic-gate /*
23*0Sstevel@tonic-gate * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
24*0Sstevel@tonic-gate * Use is subject to license terms.
25*0Sstevel@tonic-gate */
26*0Sstevel@tonic-gate
27*0Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI"
28*0Sstevel@tonic-gate
29*0Sstevel@tonic-gate #include <stdio.h>
30*0Sstevel@tonic-gate #include <stdlib.h>
31*0Sstevel@tonic-gate #include <string.h>
32*0Sstevel@tonic-gate #include <errno.h>
33*0Sstevel@tonic-gate #include <signal.h>
34*0Sstevel@tonic-gate #include <locale.h>
35*0Sstevel@tonic-gate
36*0Sstevel@tonic-gate #include <unistd.h>
37*0Sstevel@tonic-gate #include <termios.h>
38*0Sstevel@tonic-gate
39*0Sstevel@tonic-gate #ifdef HAVE_SELECT
40*0Sstevel@tonic-gate #ifdef HAVE_SYS_SELECT_H
41*0Sstevel@tonic-gate #include <sys/select.h>
42*0Sstevel@tonic-gate #endif
43*0Sstevel@tonic-gate #endif
44*0Sstevel@tonic-gate
45*0Sstevel@tonic-gate #include <fcntl.h>
46*0Sstevel@tonic-gate #include <sys/time.h>
47*0Sstevel@tonic-gate #include <sys/types.h>
48*0Sstevel@tonic-gate #include <sys/wait.h>
49*0Sstevel@tonic-gate #include <dirent.h>
50*0Sstevel@tonic-gate
51*0Sstevel@tonic-gate #if HAVE_SYSV_PTY
52*0Sstevel@tonic-gate #include <stropts.h> /* System-V stream I/O */
53*0Sstevel@tonic-gate char *ptsname(int fd);
54*0Sstevel@tonic-gate int grantpt(int fd);
55*0Sstevel@tonic-gate int unlockpt(int fd);
56*0Sstevel@tonic-gate #endif
57*0Sstevel@tonic-gate
58*0Sstevel@tonic-gate #include "libtecla.h"
59*0Sstevel@tonic-gate
60*0Sstevel@tonic-gate /*
61*0Sstevel@tonic-gate * Pseudo-terminal devices are found in the following directory.
62*0Sstevel@tonic-gate */
63*0Sstevel@tonic-gate #define PTY_DEV_DIR "/dev/"
64*0Sstevel@tonic-gate
65*0Sstevel@tonic-gate /*
66*0Sstevel@tonic-gate * Pseudo-terminal controller device file names start with the following
67*0Sstevel@tonic-gate * prefix.
68*0Sstevel@tonic-gate */
69*0Sstevel@tonic-gate #define PTY_CNTRL "pty"
70*0Sstevel@tonic-gate
71*0Sstevel@tonic-gate /*
72*0Sstevel@tonic-gate * Pseudo-terminal slave device file names start with the following
73*0Sstevel@tonic-gate * prefix.
74*0Sstevel@tonic-gate */
75*0Sstevel@tonic-gate #define PTY_SLAVE "tty"
76*0Sstevel@tonic-gate
77*0Sstevel@tonic-gate /*
78*0Sstevel@tonic-gate * Specify the maximum suffix length for the control and slave device
79*0Sstevel@tonic-gate * names.
80*0Sstevel@tonic-gate */
81*0Sstevel@tonic-gate #define PTY_MAX_SUFFIX 10
82*0Sstevel@tonic-gate
83*0Sstevel@tonic-gate /*
84*0Sstevel@tonic-gate * Set the maximum length of the master and slave terminal device filenames,
85*0Sstevel@tonic-gate * including space for a terminating '\0'.
86*0Sstevel@tonic-gate */
87*0Sstevel@tonic-gate #define PTY_MAX_NAME (sizeof(PTY_DEV_DIR)-1 + \
88*0Sstevel@tonic-gate (sizeof(PTY_SLAVE) > sizeof(PTY_CNTRL) ? \
89*0Sstevel@tonic-gate sizeof(PTY_SLAVE) : sizeof(PTY_CNTRL))-1 \
90*0Sstevel@tonic-gate + PTY_MAX_SUFFIX + 1)
91*0Sstevel@tonic-gate /*
92*0Sstevel@tonic-gate * Set the maximum length of an input line.
93*0Sstevel@tonic-gate */
94*0Sstevel@tonic-gate #define PTY_MAX_LINE 4096
95*0Sstevel@tonic-gate
96*0Sstevel@tonic-gate /*
97*0Sstevel@tonic-gate * Set the size of the buffer used for accumulating bytes written by the
98*0Sstevel@tonic-gate * user's terminal to its stdout.
99*0Sstevel@tonic-gate */
100*0Sstevel@tonic-gate #define PTY_MAX_READ 1000
101*0Sstevel@tonic-gate
102*0Sstevel@tonic-gate /*
103*0Sstevel@tonic-gate * Set the amount of memory used to record history.
104*0Sstevel@tonic-gate */
105*0Sstevel@tonic-gate #define PTY_HIST_SIZE 10000
106*0Sstevel@tonic-gate
107*0Sstevel@tonic-gate /*
108*0Sstevel@tonic-gate * Set the timeout delay used to check for quickly arriving
109*0Sstevel@tonic-gate * sequential output from the application.
110*0Sstevel@tonic-gate */
111*0Sstevel@tonic-gate #define PTY_READ_TIMEOUT 100000 /* micro-seconds */
112*0Sstevel@tonic-gate
113*0Sstevel@tonic-gate static int pty_open_master(const char *prog, int *cntrl, char *slave_name);
114*0Sstevel@tonic-gate static int pty_open_slave(const char *prog, char *slave_name);
115*0Sstevel@tonic-gate static int pty_child(const char *prog, int slave, char *argv[]);
116*0Sstevel@tonic-gate static int pty_parent(const char *prog, int cntrl);
117*0Sstevel@tonic-gate static int pty_stop_parent(int waserr, int cntrl, GetLine *gl, char *rbuff);
118*0Sstevel@tonic-gate static GL_FD_EVENT_FN(pty_read_from_program);
119*0Sstevel@tonic-gate static int pty_write_to_fd(int fd, const char *string, int n);
120*0Sstevel@tonic-gate static void pty_child_exited(int sig);
121*0Sstevel@tonic-gate static int pty_master_readable(int fd, long usec);
122*0Sstevel@tonic-gate
123*0Sstevel@tonic-gate /*.......................................................................
124*0Sstevel@tonic-gate * Run a program with enhanced terminal editing facilities.
125*0Sstevel@tonic-gate *
126*0Sstevel@tonic-gate * Usage:
127*0Sstevel@tonic-gate * enhance program [args...]
128*0Sstevel@tonic-gate */
main(int argc,char * argv[])129*0Sstevel@tonic-gate int main(int argc, char *argv[])
130*0Sstevel@tonic-gate {
131*0Sstevel@tonic-gate int cntrl = -1; /* The fd of the pseudo-terminal controller device */
132*0Sstevel@tonic-gate int slave = -1; /* The fd of the pseudo-terminal slave device */
133*0Sstevel@tonic-gate pid_t pid; /* The return value of fork() */
134*0Sstevel@tonic-gate int status; /* The return statuses of the parent and child functions */
135*0Sstevel@tonic-gate char slave_name[PTY_MAX_NAME]; /* The filename of the slave end of the */
136*0Sstevel@tonic-gate /* pseudo-terminal. */
137*0Sstevel@tonic-gate char *prog; /* The name of the program (ie. argv[0]) */
138*0Sstevel@tonic-gate /*
139*0Sstevel@tonic-gate * Check the arguments.
140*0Sstevel@tonic-gate */
141*0Sstevel@tonic-gate if(argc < 2) {
142*0Sstevel@tonic-gate fprintf(stderr, "Usage: %s <program> [arguments...]\n", argv[0]);
143*0Sstevel@tonic-gate return 1;
144*0Sstevel@tonic-gate };
145*0Sstevel@tonic-gate /*
146*0Sstevel@tonic-gate * Get the name of the program.
147*0Sstevel@tonic-gate */
148*0Sstevel@tonic-gate prog = argv[0];
149*0Sstevel@tonic-gate /*
150*0Sstevel@tonic-gate * If the user has the LC_CTYPE or LC_ALL environment variables set,
151*0Sstevel@tonic-gate * enable display of characters corresponding to the specified locale.
152*0Sstevel@tonic-gate */
153*0Sstevel@tonic-gate (void) setlocale(LC_CTYPE, "");
154*0Sstevel@tonic-gate /*
155*0Sstevel@tonic-gate * If the program is taking its input from a pipe or a file, or
156*0Sstevel@tonic-gate * sending its output to something other than a terminal, run the
157*0Sstevel@tonic-gate * program without tecla.
158*0Sstevel@tonic-gate */
159*0Sstevel@tonic-gate if(!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {
160*0Sstevel@tonic-gate if(execvp(argv[1], argv + 1) < 0) {
161*0Sstevel@tonic-gate fprintf(stderr, "%s: Unable to execute %s (%s).\n", prog, argv[1],
162*0Sstevel@tonic-gate strerror(errno));
163*0Sstevel@tonic-gate fflush(stderr);
164*0Sstevel@tonic-gate _exit(1);
165*0Sstevel@tonic-gate };
166*0Sstevel@tonic-gate };
167*0Sstevel@tonic-gate /*
168*0Sstevel@tonic-gate * Open the master side of a pseudo-terminal pair, and return
169*0Sstevel@tonic-gate * the corresponding file descriptor and the filename of the
170*0Sstevel@tonic-gate * slave end of the pseudo-terminal.
171*0Sstevel@tonic-gate */
172*0Sstevel@tonic-gate if(pty_open_master(prog, &cntrl, slave_name))
173*0Sstevel@tonic-gate return 1;
174*0Sstevel@tonic-gate /*
175*0Sstevel@tonic-gate * Set up a signal handler to watch for the child process exiting.
176*0Sstevel@tonic-gate */
177*0Sstevel@tonic-gate signal(SIGCHLD, pty_child_exited);
178*0Sstevel@tonic-gate /*
179*0Sstevel@tonic-gate * The above signal handler sends the parent process a SIGINT signal.
180*0Sstevel@tonic-gate * This signal is caught by gl_get_line(), which resets the terminal
181*0Sstevel@tonic-gate * settings, and if the application signal handler for this signal
182*0Sstevel@tonic-gate * doesn't abort the process, gl_get_line() returns NULL with errno
183*0Sstevel@tonic-gate * set to EINTR. Arrange to ignore the signal, so that gl_get_line()
184*0Sstevel@tonic-gate * returns and we have a chance to cleanup.
185*0Sstevel@tonic-gate */
186*0Sstevel@tonic-gate signal(SIGINT, SIG_IGN);
187*0Sstevel@tonic-gate /*
188*0Sstevel@tonic-gate * We will read user input in one process, and run the user's program
189*0Sstevel@tonic-gate * in a child process.
190*0Sstevel@tonic-gate */
191*0Sstevel@tonic-gate pid = fork();
192*0Sstevel@tonic-gate if(pid < 0) {
193*0Sstevel@tonic-gate fprintf(stderr, "%s: Unable to fork child process (%s).\n", prog,
194*0Sstevel@tonic-gate strerror(errno));
195*0Sstevel@tonic-gate return 1;
196*0Sstevel@tonic-gate };
197*0Sstevel@tonic-gate /*
198*0Sstevel@tonic-gate * Are we the parent?
199*0Sstevel@tonic-gate */
200*0Sstevel@tonic-gate if(pid!=0) {
201*0Sstevel@tonic-gate status = pty_parent(prog, cntrl);
202*0Sstevel@tonic-gate close(cntrl);
203*0Sstevel@tonic-gate } else {
204*0Sstevel@tonic-gate close(cntrl); /* The child doesn't use the slave device */
205*0Sstevel@tonic-gate signal(SIGCHLD, pty_child_exited);
206*0Sstevel@tonic-gate if((slave = pty_open_slave(prog, slave_name)) >= 0) {
207*0Sstevel@tonic-gate status = pty_child(prog, slave, argv + 1);
208*0Sstevel@tonic-gate close(slave);
209*0Sstevel@tonic-gate } else {
210*0Sstevel@tonic-gate status = 1;
211*0Sstevel@tonic-gate };
212*0Sstevel@tonic-gate };
213*0Sstevel@tonic-gate return status;
214*0Sstevel@tonic-gate }
215*0Sstevel@tonic-gate
216*0Sstevel@tonic-gate /*.......................................................................
217*0Sstevel@tonic-gate * Open the master side of a pseudo-terminal pair, and return
218*0Sstevel@tonic-gate * the corresponding file descriptor and the filename of the
219*0Sstevel@tonic-gate * slave end of the pseudo-terminal.
220*0Sstevel@tonic-gate *
221*0Sstevel@tonic-gate * Input/Output:
222*0Sstevel@tonic-gate * prog const char * The name of this program.
223*0Sstevel@tonic-gate * cntrl int * The file descriptor of the pseudo-terminal
224*0Sstevel@tonic-gate * controller device will be assigned tp *cntrl.
225*0Sstevel@tonic-gate * slave_name char * The file-name of the pseudo-terminal slave device
226*0Sstevel@tonic-gate * will be recorded in slave_name[], which must have
227*0Sstevel@tonic-gate * at least PTY_MAX_NAME elements.
228*0Sstevel@tonic-gate * Output:
229*0Sstevel@tonic-gate * return int 0 - OK.
230*0Sstevel@tonic-gate * 1 - Error.
231*0Sstevel@tonic-gate */
pty_open_master(const char * prog,int * cntrl,char * slave_name)232*0Sstevel@tonic-gate static int pty_open_master(const char *prog, int *cntrl, char *slave_name)
233*0Sstevel@tonic-gate {
234*0Sstevel@tonic-gate char master_name[PTY_MAX_NAME]; /* The filename of the master device */
235*0Sstevel@tonic-gate DIR *dir; /* The directory iterator */
236*0Sstevel@tonic-gate struct dirent *file; /* A file in "/dev" */
237*0Sstevel@tonic-gate /*
238*0Sstevel@tonic-gate * Mark the controller device as not opened yet.
239*0Sstevel@tonic-gate */
240*0Sstevel@tonic-gate *cntrl = -1;
241*0Sstevel@tonic-gate /*
242*0Sstevel@tonic-gate * On systems with the Sys-V pseudo-terminal interface, we don't
243*0Sstevel@tonic-gate * have to search for a free master terminal. We just open /dev/ptmx,
244*0Sstevel@tonic-gate * and if there is a free master terminal device, we are given a file
245*0Sstevel@tonic-gate * descriptor connected to it.
246*0Sstevel@tonic-gate */
247*0Sstevel@tonic-gate #if HAVE_SYSV_PTY
248*0Sstevel@tonic-gate *cntrl = open("/dev/ptmx", O_RDWR);
249*0Sstevel@tonic-gate if(*cntrl >= 0) {
250*0Sstevel@tonic-gate /*
251*0Sstevel@tonic-gate * Get the filename of the slave side of the pseudo-terminal.
252*0Sstevel@tonic-gate */
253*0Sstevel@tonic-gate char *name = ptsname(*cntrl);
254*0Sstevel@tonic-gate if(name) {
255*0Sstevel@tonic-gate if(strlen(name)+1 > PTY_MAX_NAME) {
256*0Sstevel@tonic-gate fprintf(stderr, "%s: Slave pty filename too long.\n", prog);
257*0Sstevel@tonic-gate return 1;
258*0Sstevel@tonic-gate };
259*0Sstevel@tonic-gate strlcpy(slave_name, name, PTY_MAX_NAME);
260*0Sstevel@tonic-gate /*
261*0Sstevel@tonic-gate * If unable to get the slave name, discard the controller file descriptor,
262*0Sstevel@tonic-gate * ready to try a search instead.
263*0Sstevel@tonic-gate */
264*0Sstevel@tonic-gate } else {
265*0Sstevel@tonic-gate close(*cntrl);
266*0Sstevel@tonic-gate *cntrl = -1;
267*0Sstevel@tonic-gate };
268*0Sstevel@tonic-gate } else {
269*0Sstevel@tonic-gate #endif
270*0Sstevel@tonic-gate /*
271*0Sstevel@tonic-gate * On systems without /dev/ptmx, or if opening /dev/ptmx failed,
272*0Sstevel@tonic-gate * we open one master terminal after another, until one that isn't
273*0Sstevel@tonic-gate * in use by another program is found.
274*0Sstevel@tonic-gate *
275*0Sstevel@tonic-gate * Open the devices directory.
276*0Sstevel@tonic-gate */
277*0Sstevel@tonic-gate dir = opendir(PTY_DEV_DIR);
278*0Sstevel@tonic-gate if(!dir) {
279*0Sstevel@tonic-gate fprintf(stderr, "%s: Couldn't open %s (%s)\n", prog, PTY_DEV_DIR,
280*0Sstevel@tonic-gate strerror(errno));
281*0Sstevel@tonic-gate return 1;
282*0Sstevel@tonic-gate };
283*0Sstevel@tonic-gate /*
284*0Sstevel@tonic-gate * Look for pseudo-terminal controller device files in the devices
285*0Sstevel@tonic-gate * directory.
286*0Sstevel@tonic-gate */
287*0Sstevel@tonic-gate while(*cntrl < 0 && (file = readdir(dir))) {
288*0Sstevel@tonic-gate if(strncmp(file->d_name, PTY_CNTRL, sizeof(PTY_CNTRL)-1) == 0) {
289*0Sstevel@tonic-gate /*
290*0Sstevel@tonic-gate * Get the common extension of the control and slave filenames.
291*0Sstevel@tonic-gate */
292*0Sstevel@tonic-gate const char *ext = file->d_name + sizeof(PTY_CNTRL)-1;
293*0Sstevel@tonic-gate if(strlen(ext) > PTY_MAX_SUFFIX)
294*0Sstevel@tonic-gate continue;
295*0Sstevel@tonic-gate /*
296*0Sstevel@tonic-gate * Attempt to open the control file.
297*0Sstevel@tonic-gate */
298*0Sstevel@tonic-gate strlcpy(master_name, PTY_DEV_DIR, sizeof(master_name));
299*0Sstevel@tonic-gate strlcat(master_name, PTY_CNTRL, sizeof(master_name));
300*0Sstevel@tonic-gate strlcat(master_name, ext, sizeof(master_name));
301*0Sstevel@tonic-gate *cntrl = open(master_name, O_RDWR);
302*0Sstevel@tonic-gate if(*cntrl < 0)
303*0Sstevel@tonic-gate continue;
304*0Sstevel@tonic-gate /*
305*0Sstevel@tonic-gate * Attempt to open the matching slave file.
306*0Sstevel@tonic-gate */
307*0Sstevel@tonic-gate strlcpy(slave_name, PTY_DEV_DIR, PTY_MAX_NAME);
308*0Sstevel@tonic-gate strlcat(slave_name, PTY_SLAVE, PTY_MAX_NAME);
309*0Sstevel@tonic-gate strlcat(slave_name, ext, PTY_MAX_NAME);
310*0Sstevel@tonic-gate };
311*0Sstevel@tonic-gate };
312*0Sstevel@tonic-gate closedir(dir);
313*0Sstevel@tonic-gate #if HAVE_SYSV_PTY
314*0Sstevel@tonic-gate };
315*0Sstevel@tonic-gate #endif
316*0Sstevel@tonic-gate /*
317*0Sstevel@tonic-gate * Did we fail to find a pseudo-terminal pair that we could open?
318*0Sstevel@tonic-gate */
319*0Sstevel@tonic-gate if(*cntrl < 0) {
320*0Sstevel@tonic-gate fprintf(stderr, "%s: Unable to find a free pseudo-terminal.\n", prog);
321*0Sstevel@tonic-gate return 1;
322*0Sstevel@tonic-gate };
323*0Sstevel@tonic-gate /*
324*0Sstevel@tonic-gate * System V systems require the program that opens the master to
325*0Sstevel@tonic-gate * grant access to the slave side of the pseudo-terminal.
326*0Sstevel@tonic-gate */
327*0Sstevel@tonic-gate #ifdef HAVE_SYSV_PTY
328*0Sstevel@tonic-gate if(grantpt(*cntrl) < 0 ||
329*0Sstevel@tonic-gate unlockpt(*cntrl) < 0) {
330*0Sstevel@tonic-gate fprintf(stderr, "%s: Unable to unlock terminal (%s).\n", prog,
331*0Sstevel@tonic-gate strerror(errno));
332*0Sstevel@tonic-gate return 1;
333*0Sstevel@tonic-gate };
334*0Sstevel@tonic-gate #endif
335*0Sstevel@tonic-gate /*
336*0Sstevel@tonic-gate * Success.
337*0Sstevel@tonic-gate */
338*0Sstevel@tonic-gate return 0;
339*0Sstevel@tonic-gate }
340*0Sstevel@tonic-gate
341*0Sstevel@tonic-gate /*.......................................................................
342*0Sstevel@tonic-gate * Open the slave end of a pseudo-terminal.
343*0Sstevel@tonic-gate *
344*0Sstevel@tonic-gate * Input:
345*0Sstevel@tonic-gate * prog const char * The name of this program.
346*0Sstevel@tonic-gate * slave_name char * The filename of the slave device.
347*0Sstevel@tonic-gate * Output:
348*0Sstevel@tonic-gate * return int The file descriptor of the successfully opened
349*0Sstevel@tonic-gate * slave device, or < 0 on error.
350*0Sstevel@tonic-gate */
pty_open_slave(const char * prog,char * slave_name)351*0Sstevel@tonic-gate static int pty_open_slave(const char *prog, char *slave_name)
352*0Sstevel@tonic-gate {
353*0Sstevel@tonic-gate int fd; /* The file descriptor of the slave device */
354*0Sstevel@tonic-gate /*
355*0Sstevel@tonic-gate * Place the process in its own process group. In system-V based
356*0Sstevel@tonic-gate * OS's, this ensures that when the pseudo-terminal is opened, it
357*0Sstevel@tonic-gate * becomes the controlling terminal of the process.
358*0Sstevel@tonic-gate */
359*0Sstevel@tonic-gate if(setsid() < 0) {
360*0Sstevel@tonic-gate fprintf(stderr, "%s: Unable to form new process group (%s).\n", prog,
361*0Sstevel@tonic-gate strerror(errno));
362*0Sstevel@tonic-gate return -1;
363*0Sstevel@tonic-gate };
364*0Sstevel@tonic-gate /*
365*0Sstevel@tonic-gate * Attempt to open the specified device.
366*0Sstevel@tonic-gate */
367*0Sstevel@tonic-gate fd = open(slave_name, O_RDWR);
368*0Sstevel@tonic-gate if(fd < 0) {
369*0Sstevel@tonic-gate fprintf(stderr, "%s: Unable to open pseudo-terminal slave device (%s).\n",
370*0Sstevel@tonic-gate prog, strerror(errno));
371*0Sstevel@tonic-gate return -1;
372*0Sstevel@tonic-gate };
373*0Sstevel@tonic-gate /*
374*0Sstevel@tonic-gate * On system-V streams based systems, we need to push the stream modules
375*0Sstevel@tonic-gate * that implement pseudo-terminal and termio interfaces. At least on
376*0Sstevel@tonic-gate * Solaris, which pushes these automatically when a slave is opened,
377*0Sstevel@tonic-gate * this is redundant, so ignore errors when pushing the modules.
378*0Sstevel@tonic-gate */
379*0Sstevel@tonic-gate #if HAVE_SYSV_PTY
380*0Sstevel@tonic-gate (void) ioctl(fd, I_PUSH, "ptem");
381*0Sstevel@tonic-gate (void) ioctl(fd, I_PUSH, "ldterm");
382*0Sstevel@tonic-gate /*
383*0Sstevel@tonic-gate * On BSD based systems other than SunOS 4.x, the following makes the
384*0Sstevel@tonic-gate * pseudo-terminal the controlling terminal of the child process.
385*0Sstevel@tonic-gate * According to the pseudo-terminal example code in Steven's
386*0Sstevel@tonic-gate * Advanced programming in the unix environment, the !defined(CIBAUD)
387*0Sstevel@tonic-gate * part of the clause prevents this from being used under SunOS. Since
388*0Sstevel@tonic-gate * I only have his code with me, and won't have access to the book,
389*0Sstevel@tonic-gate * I don't know why this is necessary.
390*0Sstevel@tonic-gate */
391*0Sstevel@tonic-gate #elif defined(TIOCSCTTY) && !defined(CIBAUD)
392*0Sstevel@tonic-gate if(ioctl(fd, TIOCSCTTY, (char *) 0) < 0) {
393*0Sstevel@tonic-gate fprintf(stderr, "%s: Unable to establish controlling terminal (%s).\n",
394*0Sstevel@tonic-gate prog, strerror(errno));
395*0Sstevel@tonic-gate close(fd);
396*0Sstevel@tonic-gate return -1;
397*0Sstevel@tonic-gate };
398*0Sstevel@tonic-gate #endif
399*0Sstevel@tonic-gate return fd;
400*0Sstevel@tonic-gate }
401*0Sstevel@tonic-gate
402*0Sstevel@tonic-gate /*.......................................................................
403*0Sstevel@tonic-gate * Read input from the controlling terminal of the program, using
404*0Sstevel@tonic-gate * gl_get_line(), and feed it to the user's program running in a child
405*0Sstevel@tonic-gate * process, via the controller side of the pseudo-terminal. Also pass
406*0Sstevel@tonic-gate * data received from the user's program via the conroller end of
407*0Sstevel@tonic-gate * the pseudo-terminal, to stdout.
408*0Sstevel@tonic-gate *
409*0Sstevel@tonic-gate * Input:
410*0Sstevel@tonic-gate * prog const char * The name of this program.
411*0Sstevel@tonic-gate * cntrl int The file descriptor of the controller end of the
412*0Sstevel@tonic-gate * pseudo-terminal.
413*0Sstevel@tonic-gate * Output:
414*0Sstevel@tonic-gate * return int 0 - OK.
415*0Sstevel@tonic-gate * 1 - Error.
416*0Sstevel@tonic-gate */
pty_parent(const char * prog,int cntrl)417*0Sstevel@tonic-gate static int pty_parent(const char *prog, int cntrl)
418*0Sstevel@tonic-gate {
419*0Sstevel@tonic-gate GetLine *gl = NULL; /* The gl_get_line() resource object */
420*0Sstevel@tonic-gate char *line; /* An input line read from the user */
421*0Sstevel@tonic-gate char *rbuff=NULL; /* A buffer for reading from the pseudo terminal */
422*0Sstevel@tonic-gate /*
423*0Sstevel@tonic-gate * Allocate the gl_get_line() resource object.
424*0Sstevel@tonic-gate */
425*0Sstevel@tonic-gate gl = new_GetLine(PTY_MAX_LINE, PTY_HIST_SIZE);
426*0Sstevel@tonic-gate if(!gl)
427*0Sstevel@tonic-gate return pty_stop_parent(1, cntrl, gl, rbuff);
428*0Sstevel@tonic-gate /*
429*0Sstevel@tonic-gate * Allocate a buffer to use to accumulate bytes read from the
430*0Sstevel@tonic-gate * pseudo-terminal.
431*0Sstevel@tonic-gate */
432*0Sstevel@tonic-gate rbuff = (char *) malloc(PTY_MAX_READ+1);
433*0Sstevel@tonic-gate if(!rbuff)
434*0Sstevel@tonic-gate return pty_stop_parent(1, cntrl, gl, rbuff);
435*0Sstevel@tonic-gate rbuff[0] = '\0';
436*0Sstevel@tonic-gate /*
437*0Sstevel@tonic-gate * Register an event handler to watch for data appearing from the
438*0Sstevel@tonic-gate * user's program on the controller end of the pseudo terminal.
439*0Sstevel@tonic-gate */
440*0Sstevel@tonic-gate if(gl_watch_fd(gl, cntrl, GLFD_READ, pty_read_from_program, rbuff))
441*0Sstevel@tonic-gate return pty_stop_parent(1, cntrl, gl, rbuff);
442*0Sstevel@tonic-gate /*
443*0Sstevel@tonic-gate * Read input lines from the user and pass them on to the user's program,
444*0Sstevel@tonic-gate * by writing to the controller end of the pseudo-terminal.
445*0Sstevel@tonic-gate */
446*0Sstevel@tonic-gate while((line=gl_get_line(gl, rbuff, NULL, 0))) {
447*0Sstevel@tonic-gate if(pty_write_to_fd(cntrl, line, strlen(line)))
448*0Sstevel@tonic-gate return pty_stop_parent(1, cntrl, gl, rbuff);
449*0Sstevel@tonic-gate rbuff[0] = '\0';
450*0Sstevel@tonic-gate };
451*0Sstevel@tonic-gate return pty_stop_parent(0, cntrl, gl, rbuff);
452*0Sstevel@tonic-gate }
453*0Sstevel@tonic-gate
454*0Sstevel@tonic-gate /*.......................................................................
455*0Sstevel@tonic-gate * This is a private return function of pty_parent(), used to release
456*0Sstevel@tonic-gate * dynamically allocated resources, close the controller end of the
457*0Sstevel@tonic-gate * pseudo-terminal, and wait for the child to exit. It returns the
458*0Sstevel@tonic-gate * exit status of the child process, unless the caller reports an
459*0Sstevel@tonic-gate * error itself, in which case the caller's error status is returned.
460*0Sstevel@tonic-gate *
461*0Sstevel@tonic-gate * Input:
462*0Sstevel@tonic-gate * waserr int True if the caller is calling this function because
463*0Sstevel@tonic-gate * an error occured.
464*0Sstevel@tonic-gate * cntrl int The file descriptor of the controller end of the
465*0Sstevel@tonic-gate * pseudo-terminal.
466*0Sstevel@tonic-gate * gl GetLine * The resource object of gl_get_line().
467*0Sstevel@tonic-gate * rbuff char * The buffer used to accumulate bytes read from
468*0Sstevel@tonic-gate * the pseudo-terminal.
469*0Sstevel@tonic-gate * Output:
470*0Sstevel@tonic-gate * return int The desired exit status of the program.
471*0Sstevel@tonic-gate */
pty_stop_parent(int waserr,int cntrl,GetLine * gl,char * rbuff)472*0Sstevel@tonic-gate static int pty_stop_parent(int waserr, int cntrl, GetLine *gl, char *rbuff)
473*0Sstevel@tonic-gate {
474*0Sstevel@tonic-gate int status; /* The return status of the child process */
475*0Sstevel@tonic-gate /*
476*0Sstevel@tonic-gate * Close the controller end of the terminal.
477*0Sstevel@tonic-gate */
478*0Sstevel@tonic-gate close(cntrl);
479*0Sstevel@tonic-gate /*
480*0Sstevel@tonic-gate * Delete the resource object.
481*0Sstevel@tonic-gate */
482*0Sstevel@tonic-gate gl = del_GetLine(gl);
483*0Sstevel@tonic-gate /*
484*0Sstevel@tonic-gate * Delete the read buffer.
485*0Sstevel@tonic-gate */
486*0Sstevel@tonic-gate if(rbuff)
487*0Sstevel@tonic-gate free(rbuff);
488*0Sstevel@tonic-gate /*
489*0Sstevel@tonic-gate * Wait for the user's program to end.
490*0Sstevel@tonic-gate */
491*0Sstevel@tonic-gate (void) wait(&status);
492*0Sstevel@tonic-gate /*
493*0Sstevel@tonic-gate * Return either our error status, or the return status of the child
494*0Sstevel@tonic-gate * program.
495*0Sstevel@tonic-gate */
496*0Sstevel@tonic-gate return waserr ? 1 : status;
497*0Sstevel@tonic-gate }
498*0Sstevel@tonic-gate
499*0Sstevel@tonic-gate /*.......................................................................
500*0Sstevel@tonic-gate * Run the user's program, with its stdin and stdout connected to the
501*0Sstevel@tonic-gate * slave end of the psuedo-terminal.
502*0Sstevel@tonic-gate *
503*0Sstevel@tonic-gate * Input:
504*0Sstevel@tonic-gate * prog const char * The name of this program.
505*0Sstevel@tonic-gate * slave int The file descriptor of the slave end of the
506*0Sstevel@tonic-gate * pseudo terminal.
507*0Sstevel@tonic-gate * argv char *[] The argument vector to pass to the user's program,
508*0Sstevel@tonic-gate * where argv[0] is the name of the user's program,
509*0Sstevel@tonic-gate * and the last argument is followed by a pointer
510*0Sstevel@tonic-gate * to NULL.
511*0Sstevel@tonic-gate * Output:
512*0Sstevel@tonic-gate * return int If this function returns at all, an error must
513*0Sstevel@tonic-gate * have occured when trying to overlay the process
514*0Sstevel@tonic-gate * with the user's program. In this case 1 is
515*0Sstevel@tonic-gate * returned.
516*0Sstevel@tonic-gate */
pty_child(const char * prog,int slave,char * argv[])517*0Sstevel@tonic-gate static int pty_child(const char *prog, int slave, char *argv[])
518*0Sstevel@tonic-gate {
519*0Sstevel@tonic-gate struct termios attr; /* The terminal attributes */
520*0Sstevel@tonic-gate /*
521*0Sstevel@tonic-gate * We need to stop the pseudo-terminal from echoing everything that we send it.
522*0Sstevel@tonic-gate */
523*0Sstevel@tonic-gate if(tcgetattr(slave, &attr)) {
524*0Sstevel@tonic-gate fprintf(stderr, "%s: Can't get pseudo-terminal attributes (%s).\n", prog,
525*0Sstevel@tonic-gate strerror(errno));
526*0Sstevel@tonic-gate return 1;
527*0Sstevel@tonic-gate };
528*0Sstevel@tonic-gate attr.c_lflag &= ~(ECHO);
529*0Sstevel@tonic-gate while(tcsetattr(slave, TCSADRAIN, &attr)) {
530*0Sstevel@tonic-gate if(errno != EINTR) {
531*0Sstevel@tonic-gate fprintf(stderr, "%s: tcsetattr error: %s\n", prog, strerror(errno));
532*0Sstevel@tonic-gate return 1;
533*0Sstevel@tonic-gate };
534*0Sstevel@tonic-gate };
535*0Sstevel@tonic-gate /*
536*0Sstevel@tonic-gate * Arrange for stdin, stdout and stderr to be connected to the slave device,
537*0Sstevel@tonic-gate * ignoring errors that imply that either stdin or stdout is closed.
538*0Sstevel@tonic-gate */
539*0Sstevel@tonic-gate while(dup2(slave, STDIN_FILENO) < 0 && errno==EINTR)
540*0Sstevel@tonic-gate ;
541*0Sstevel@tonic-gate while(dup2(slave, STDOUT_FILENO) < 0 && errno==EINTR)
542*0Sstevel@tonic-gate ;
543*0Sstevel@tonic-gate while(dup2(slave, STDERR_FILENO) < 0 && errno==EINTR)
544*0Sstevel@tonic-gate ;
545*0Sstevel@tonic-gate /*
546*0Sstevel@tonic-gate * Run the user's program.
547*0Sstevel@tonic-gate */
548*0Sstevel@tonic-gate if(execvp(argv[0], argv) < 0) {
549*0Sstevel@tonic-gate fprintf(stderr, "%s: Unable to execute %s (%s).\n", prog, argv[0],
550*0Sstevel@tonic-gate strerror(errno));
551*0Sstevel@tonic-gate fflush(stderr);
552*0Sstevel@tonic-gate _exit(1);
553*0Sstevel@tonic-gate };
554*0Sstevel@tonic-gate return 0; /* This should never be reached */
555*0Sstevel@tonic-gate }
556*0Sstevel@tonic-gate
557*0Sstevel@tonic-gate /*.......................................................................
558*0Sstevel@tonic-gate * This is the event-handler that is called by gl_get_line() whenever
559*0Sstevel@tonic-gate * there is tet waiting to be read from the user's program, via the
560*0Sstevel@tonic-gate * controller end of the pseudo-terminal. See libtecla.h for details
561*0Sstevel@tonic-gate * about its arguments.
562*0Sstevel@tonic-gate */
GL_FD_EVENT_FN(pty_read_from_program)563*0Sstevel@tonic-gate static GL_FD_EVENT_FN(pty_read_from_program)
564*0Sstevel@tonic-gate {
565*0Sstevel@tonic-gate char *nlptr; /* A pointer to the last newline in the accumulated string */
566*0Sstevel@tonic-gate char *crptr; /* A pointer to the last '\r' in the accumulated string */
567*0Sstevel@tonic-gate char *nextp; /* A pointer to the next unprocessed character */
568*0Sstevel@tonic-gate /*
569*0Sstevel@tonic-gate * Get the read buffer in which we are accumulating a line to be
570*0Sstevel@tonic-gate * forwarded to stdout.
571*0Sstevel@tonic-gate */
572*0Sstevel@tonic-gate char *rbuff = (char *) data;
573*0Sstevel@tonic-gate /*
574*0Sstevel@tonic-gate * New data may arrive while we are processing the current read, and
575*0Sstevel@tonic-gate * it is more efficient to display this here than to keep returning to
576*0Sstevel@tonic-gate * gl_get_line() and have it display the latest prefix as a prompt,
577*0Sstevel@tonic-gate * followed by the current input line, so we loop, delaying a bit at
578*0Sstevel@tonic-gate * the end of each iteration to check for more data arriving from
579*0Sstevel@tonic-gate * the application, before finally returning to gl_get_line() when
580*0Sstevel@tonic-gate * no more input is available.
581*0Sstevel@tonic-gate */
582*0Sstevel@tonic-gate do {
583*0Sstevel@tonic-gate /*
584*0Sstevel@tonic-gate * Get the current length of the output string.
585*0Sstevel@tonic-gate */
586*0Sstevel@tonic-gate int len = strlen(rbuff);
587*0Sstevel@tonic-gate /*
588*0Sstevel@tonic-gate * Read the text from the program.
589*0Sstevel@tonic-gate */
590*0Sstevel@tonic-gate int nnew = read(fd, rbuff + len, PTY_MAX_READ - len);
591*0Sstevel@tonic-gate if(nnew < 0)
592*0Sstevel@tonic-gate return GLFD_ABORT;
593*0Sstevel@tonic-gate len += nnew;
594*0Sstevel@tonic-gate /*
595*0Sstevel@tonic-gate * Nul terminate the accumulated string.
596*0Sstevel@tonic-gate */
597*0Sstevel@tonic-gate rbuff[len] = '\0';
598*0Sstevel@tonic-gate /*
599*0Sstevel@tonic-gate * Find the last newline and last carriage return in the buffer, if any.
600*0Sstevel@tonic-gate */
601*0Sstevel@tonic-gate nlptr = strrchr(rbuff, '\n');
602*0Sstevel@tonic-gate crptr = strrchr(rbuff, '\r');
603*0Sstevel@tonic-gate /*
604*0Sstevel@tonic-gate * We want to output up to just before the last newline or carriage
605*0Sstevel@tonic-gate * return. If there are no newlines of carriage returns in the line,
606*0Sstevel@tonic-gate * and the buffer is full, then we should output the whole line. In
607*0Sstevel@tonic-gate * all cases a new output line will be started after the latest text
608*0Sstevel@tonic-gate * has been output. The intention is to leave any incomplete line
609*0Sstevel@tonic-gate * in the buffer, for (perhaps temporary) use as the current prompt.
610*0Sstevel@tonic-gate */
611*0Sstevel@tonic-gate if(nlptr) {
612*0Sstevel@tonic-gate nextp = crptr && crptr < nlptr ? crptr : nlptr;
613*0Sstevel@tonic-gate } else if(crptr) {
614*0Sstevel@tonic-gate nextp = crptr;
615*0Sstevel@tonic-gate } else if(len >= PTY_MAX_READ) {
616*0Sstevel@tonic-gate nextp = rbuff + len;
617*0Sstevel@tonic-gate } else {
618*0Sstevel@tonic-gate nextp = NULL;
619*0Sstevel@tonic-gate };
620*0Sstevel@tonic-gate /*
621*0Sstevel@tonic-gate * Do we have any text to output yet?
622*0Sstevel@tonic-gate */
623*0Sstevel@tonic-gate if(nextp) {
624*0Sstevel@tonic-gate /*
625*0Sstevel@tonic-gate * If there was already some text in rbuff before this function
626*0Sstevel@tonic-gate * was called, then it will have been used as a prompt. Arrange
627*0Sstevel@tonic-gate * to rewrite this prefix, plus the new suffix, by moving back to
628*0Sstevel@tonic-gate * the start of the line.
629*0Sstevel@tonic-gate */
630*0Sstevel@tonic-gate if(len > 0)
631*0Sstevel@tonic-gate (void) pty_write_to_fd(STDOUT_FILENO, "\r", 1);
632*0Sstevel@tonic-gate /*
633*0Sstevel@tonic-gate * Write everything up to the last newline to stdout.
634*0Sstevel@tonic-gate */
635*0Sstevel@tonic-gate (void) pty_write_to_fd(STDOUT_FILENO, rbuff, nextp - rbuff);
636*0Sstevel@tonic-gate /*
637*0Sstevel@tonic-gate * Start a new line.
638*0Sstevel@tonic-gate */
639*0Sstevel@tonic-gate (void) pty_write_to_fd(STDOUT_FILENO, "\r\n", 2);
640*0Sstevel@tonic-gate /*
641*0Sstevel@tonic-gate * Skip trailing carriage returns and newlines.
642*0Sstevel@tonic-gate */
643*0Sstevel@tonic-gate while(*nextp=='\n' || *nextp=='\r')
644*0Sstevel@tonic-gate nextp++;
645*0Sstevel@tonic-gate /*
646*0Sstevel@tonic-gate * Move any unwritten text following the newline, to the start of the
647*0Sstevel@tonic-gate * buffer.
648*0Sstevel@tonic-gate */
649*0Sstevel@tonic-gate memmove(rbuff, nextp, len - (nextp - rbuff) + 1);
650*0Sstevel@tonic-gate };
651*0Sstevel@tonic-gate } while(pty_master_readable(fd, PTY_READ_TIMEOUT));
652*0Sstevel@tonic-gate /*
653*0Sstevel@tonic-gate * Make the incomplete line in the output buffer the current prompt.
654*0Sstevel@tonic-gate */
655*0Sstevel@tonic-gate gl_replace_prompt(gl, rbuff);
656*0Sstevel@tonic-gate return GLFD_REFRESH;
657*0Sstevel@tonic-gate }
658*0Sstevel@tonic-gate
659*0Sstevel@tonic-gate /*.......................................................................
660*0Sstevel@tonic-gate * Write a given string to a specified file descriptor.
661*0Sstevel@tonic-gate *
662*0Sstevel@tonic-gate * Input:
663*0Sstevel@tonic-gate * fd int The file descriptor to write to.
664*0Sstevel@tonic-gate * string const char * The string to write (of at least 'n' characters).
665*0Sstevel@tonic-gate * n int The number of characters to write.
666*0Sstevel@tonic-gate * Output:
667*0Sstevel@tonic-gate * return int 0 - OK.
668*0Sstevel@tonic-gate * 1 - Error.
669*0Sstevel@tonic-gate */
pty_write_to_fd(int fd,const char * string,int n)670*0Sstevel@tonic-gate static int pty_write_to_fd(int fd, const char *string, int n)
671*0Sstevel@tonic-gate {
672*0Sstevel@tonic-gate int ndone = 0; /* The number of characters written so far */
673*0Sstevel@tonic-gate /*
674*0Sstevel@tonic-gate * Do as many writes as are needed to write the whole string.
675*0Sstevel@tonic-gate */
676*0Sstevel@tonic-gate while(ndone < n) {
677*0Sstevel@tonic-gate int nnew = write(fd, string + ndone, n - ndone);
678*0Sstevel@tonic-gate if(nnew > 0)
679*0Sstevel@tonic-gate ndone += nnew;
680*0Sstevel@tonic-gate else if(errno != EINTR)
681*0Sstevel@tonic-gate return 1;
682*0Sstevel@tonic-gate };
683*0Sstevel@tonic-gate return 0;
684*0Sstevel@tonic-gate }
685*0Sstevel@tonic-gate
686*0Sstevel@tonic-gate /*.......................................................................
687*0Sstevel@tonic-gate * This is the signal handler that is called when the child process
688*0Sstevel@tonic-gate * that is running the user's program exits for any reason. It closes
689*0Sstevel@tonic-gate * the slave end of the terminal, so that gl_get_line() in the parent
690*0Sstevel@tonic-gate * process sees an end of file.
691*0Sstevel@tonic-gate */
pty_child_exited(int sig)692*0Sstevel@tonic-gate static void pty_child_exited(int sig)
693*0Sstevel@tonic-gate {
694*0Sstevel@tonic-gate raise(SIGINT);
695*0Sstevel@tonic-gate }
696*0Sstevel@tonic-gate
697*0Sstevel@tonic-gate /*.......................................................................
698*0Sstevel@tonic-gate * Return non-zero after a given amount of time if there is data waiting
699*0Sstevel@tonic-gate * to be read from a given file descriptor.
700*0Sstevel@tonic-gate *
701*0Sstevel@tonic-gate * Input:
702*0Sstevel@tonic-gate * fd int The descriptor to watch.
703*0Sstevel@tonic-gate * usec long The number of micro-seconds to wait for input to
704*0Sstevel@tonic-gate * arrive before giving up.
705*0Sstevel@tonic-gate * Output:
706*0Sstevel@tonic-gate * return int 0 - No data is waiting to be read (or select isn't
707*0Sstevel@tonic-gate * available).
708*0Sstevel@tonic-gate * 1 - Data is waiting to be read.
709*0Sstevel@tonic-gate */
pty_master_readable(int fd,long usec)710*0Sstevel@tonic-gate static int pty_master_readable(int fd, long usec)
711*0Sstevel@tonic-gate {
712*0Sstevel@tonic-gate #if HAVE_SELECT
713*0Sstevel@tonic-gate fd_set rfds; /* The set of file descriptors to check */
714*0Sstevel@tonic-gate struct timeval timeout; /* The timeout */
715*0Sstevel@tonic-gate FD_ZERO(&rfds);
716*0Sstevel@tonic-gate FD_SET(fd, &rfds);
717*0Sstevel@tonic-gate timeout.tv_sec = 0;
718*0Sstevel@tonic-gate timeout.tv_usec = usec;
719*0Sstevel@tonic-gate return select(fd+1, &rfds, NULL, NULL, &timeout) == 1;
720*0Sstevel@tonic-gate #else
721*0Sstevel@tonic-gate return 0;
722*0Sstevel@tonic-gate #endif
723*0Sstevel@tonic-gate }
724