[comp.unix.questions] Another attempt at getting help with process communications

lanzo@mercutio.steinmetz (Mark Lanzo) (12/15/87)

Once, again, I appeal to the net for help with process communications.
In a recent article, I described a program whereby I opened up a pair
of pipes for communications between two processes, then used fork/exec
to spawn a subprocess, with communications being maintained between the
two processes via the pipes.  Well, I've given up on that idea since 
there seems to be absolutely no way to defeat the buffering mechanism
for pipes.  So, now I'm trying similar stunts with pseudoterminals.

I realize that I probably can't totally defeat the buffering mechanisms
with pseudoterminals, but at least then I might get line buffering, which
in most circumstances is all I'm really after anyways.
What I'd like to be able to do is open up a pseudoterminal slave end
as "stdin" and "stdout" (with "stderr" connected to "stdout" usually) 
for a subprocess, and have the pseudoterminal master end opened up 
in the master process to maintain communications.  My previous posting
included a algorithm description which went something like:

     call "pipe" to open a pipe which is for writing from parent-to-child
     call "pipe" to open a pipe for writing from child-to-parent
     fork process:

	child				 
        -------                          
	disconnect unused ends of pipes:
	    close parent-to-child write end
	    close child-to-parent read	end
	use close/dup etc to make pipe ends be stdin & stdout.
	set unbuffered I/O mode on stdin, stdout, stderr (I know, this 
	    doesn't do anything useful, I realize now that only the file
	    descriptor table is inherited and that the level 2 I/O 
	    buffers are just part of the overlayed (-ing) program.)
	exec subprocess...

    
        parent                            
	----------			   
	disconnect unused ends of pipes:
	    close child-to-parent write end 
	    close parent-to-child read end
	sit in loop waiting for I/O activity (using "select(2)")

This works, except for the aforementioned buffering problems.
Is there some way I can use a similar process with pseudoterminals?

I tried replacing the "pipe" calls with "open(pty_name,mode,prot)" type
calls, but this didn't seem to do the trick, even when I successfully
opened the pty.

The thing which confuses me most is that I don't know how to handle the
bidirectional communications with a pseudoterminal, especially with the
master end of the pseudoterminal, which can only be opened once (ie., I
can't do an "open("/dev/ptyp0",O_RDONLY,0)" followed by 
"open("/dev/ptyp0",O_WRONLY,0)" to open up two file descriptors in the master
process, one for input and one for output to the subprocess.
If I do "fd = open("/dev/ptyp0",O_RDWR,0)" and then do a "write(fd...)"
followed by a "read(fd...)" will I read what I just wrote?  I would assume
not, but it looked like that was what was happening to me; especially if
the child process exited so that the slave end of the pty wasn't really
connected to anything.

From the master process, I really don't care too much whether the 
channel to the subprocess is via 1 or 2 descriptors; but I definitely need
2 (or 3) descriptors in the subprocess, since these are supposed to be
stdin, stdout, and stderr.

A second question:
    How do I locate an unused pty/tty pair; and how do I ensure that nothing
else tries to access the pty while my job is using it?  Do I just loop
trying to open /dev/ptyp0 then /dev/ttyp0; on failure try /dev/ptyp1 and
/dev/ttyp1, then ptyp2, p3 ... etc, or is there a more clever mechanism
here?  What if there is NO currently unused pty? (as when a bunch of 
people have rlogin'ed (?)).


Miscellaneous:
    I'm using Ultrix 1.2 (going to 2.0 soon), but I'm interesting in solutions
which are as portable as possible between various flavors of Unix.
[Same disclaimer as last time!]

	    Once again, thanks in advance,

		    Mark Lanzo
                    mercutio!lanzo



P.S.
Thanks to those people who did reply to my last article.  I'd reply
directly, but that never seems to work around here.  I definitely 
appreciate the person who pointed out the "select" call to me.

A miscellaneous question regarding "select":

    I did a "select" call something like:

    rd_fds = (1<<fileno(stdin)) | (1<<process[READ]);
    (where process[READ] was the file descriptor no. for the read-end
    of a pipe, the write-end of which was my subprocess' stdout)
    nready = select(32,2,&rd_fds,...)
    
    would return nready = 1, with bit "process[READ]" set in rd_fds
    if my subprocess had died. It seems like I should have gotten
    nready = -1 with errno set to something like EPIPE, or EBADF;
    or just no return at all (unless of course something happened on stdin).

    Is this a bug?  It seems strange that "select" would claim that data
    was available to be read on a pipe which was not even connected to
    anything anymore (and there was not any unread data left in the pipe
    either, at least, ioctl(FIONREAD) returned 0 chars available).

    As long as the subprocess hadn't died, this worked fine, other than
    only telling me about data after a couple of Kb had been buffered.

ron@topaz.rutgers.edu (Ron Natalie) (12/16/87)

Nope, trying to open the control side of the pty's is THE only way
to alloc them now.  I have a small setuid program, however that once
you have opened a pty (it is passed as fd 0) it verifies your claim
and then chowns the pty to you and optionally creates a utmp entry
for you (and likewise for close).

-Ron

chris@mimsy.UUCP (Chris Torek) (12/16/87)

In article <8214@steinmetz.steinmetz.UUCP> lanzo@mercutio.steinmetz
(Mark Lanzo) writes:
[using ptys,]
>The thing which confuses me most is that I don't know how to handle the
>bidirectional communications with a pseudoterminal, especially with the
>master end of the pseudoterminal, which can only be opened once ....

Things written on the master end of a pty pair appear on the slave
end and can be read there.  Things written on the slave end of a
pair appear on the master end, and can be read there.  Things written
on the master end cannot be read back from the master end (unless,
of course, the process on the slave side echoes them).

>... especially if the child process exited so that the slave end of
>the pty wasn't really connected to anything.

When this happens, read(masterfd) should return EOF.

>...I definitely need 2 (or 3) descriptors in the subprocess, since
>these are supposed to be stdin, stdout, and stderr.

	/* assume masterfd is set */
	slavefd = open(slave, 2);
	if (slavefd < 0)
		...	/* something went wrong */
	pid = fork();
	if (pid < 0)
		...	/* something went wrong */
	if (pid == 0) {
		/* child */
		if (dup2(slavefd, 0) < 0 || dup2(0, 1) < 0 || dup2(0, 2) < 0)
			...
		/* discard irrelevent descriptors */
		for (i = getdtablesize(); --i > 2;)
			(void) close(i);
		exec...
		/* get here iff exec fails, so send message to master */
		perror("exec(...)");
		_exit(1);
	}
	(void) close(slavefd);	/* do not want this anymore */
	/* go on with master stuff */

>    How do I locate an unused pty/tty pair;

There should be a nice way to do this, but all existing programs loop
trying to open ptyp0, then ptyp1, then ptyp2, ... ptypf, ptyq0, ....

>and how do I ensure that nothing else tries to access the pty while
>my job is using it?

As long as you have the master end open, no one else can touch it.
Of course, anyone with the proper permissions can open the slave end.

>What if there is NO currently unused pty?

Then you are out of luck.  `Good' programs currently use stat() to
tell whether there is a block of ptys:

	(void) strcpy(name, "/dev/ptyXX");
	for (c = 'p'; c <= 'z'; c++) {
		name[sizeof("/dev/pty") - 1] = c;
		if (stat(name, &st))
			break;	/* no ptys */
		for (i = 0; i < 16; i++) {
			name[sizeof("/dev/ptyp") - 1] = "0123456789abcdef"[i];
			if ((masterfd = open(name, 2)) >= 0)
				goto havemaster;
		}
	}
	/* no ptys */
	...
havemaster:
	/* have the master end */

Note that some wayward programs sometimes leave slave pty protections
such that even after someone opens the master, that user cannot open
the slave.  (Root need not worry about this, of course.)  Hence it
is a safer to do something like this:

			if ((masterfd = open(name, 2)) < 0)
				continue;
			name[sizeof("/dev/p") - 1] = 't';
			if ((slavefd = open(name, 2)) >= 0)
				goto havepty;
			/* someone left a pty unusable; just move on */
			(void) close(masterfd);

-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

lanzo@mercutio.steinmetz (Mark Lanzo) (12/18/87)

In article <9801@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:

   [Lots of stuff about handling file descriptors & ptys,
    for which I am ever grateful...]

Well, as described in a previous posting (or two) I've tried using pty's
for interprocess communications, but without much luck.  I used the info
Chris provided in his reply to help me clean up some of my code; but still
no luck.  Now it appears that my process has been spawned off successfully,
and that I even have opened up a pseudoterminal for communications without
the system making any obvious complaints; but things still don't work.
Specifically, I use a "select" call to wait for some sort of I/O activity;
but "select" always returns instantly telling me I have data available to
be read from the file descriptor attached to the master pty; but when I
go to read the data, THERE IS NONE.  Infuriating.  Well, rather than trying
to describe all the steps I'm doing, I have just included here the source
for a stripped version of the program I'm working on, which I'll just call
"xpty".  I should be able to use a command like "xpty cat foobar" to run
the command "cat foobar" as a subprocess, with its output going to the slave
end of a pty, to be read by the master end.  Can anyone out there spot
any flaws in what I'm doing?

Please excuse if some of the documentation in the program looks like it was
written for the complete idiot --- it was, 'cause the people who are going
to use it are a bunch of VMS hacks who are going to have a rough time
just figuring out how to log in on an Ultrix system; much less port some
software over!  And very shortly I won't be around to help them anymore...


	    Mark Lanzo



======= xpty.c ============= cut here =======================================
#include <stdio.h>
#include <signal.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/wait.h>

extern int errno;
int process[4];
static char buffer[256];
int interesting_rfd = 0, interesting_wfd = 0, interesting_efd = 0;

/* Temporary; until I add a loop to find an open pty */
static char * master_pty = "/dev/ptypf";
static char * slave_pty  = "/dev/ttypf";

#define READ  0
#define WRITE 1


/* Manual claims there's a sys_siglist[] available (psignal(3)) but linker */
/* claims otherwise.  Probably cause I'm using Ultrix 1.2 rather than 2.0  */

char * signame(signal)
    int signal;
{
static char unknown[32];
static char * signames[] = 
    {
    /*	0 */	"SIGNONE",
    /*	1 */	"SIGHUP",
    /*	2 */	"SIGINT",
    /*	3 */	"SIGQUIT",
    /*	4 */	"SIGILL",
    /*	5 */	"SIGTRAP",
    /*	6 */	"SIGIOT",
    /*	7 */	"SIGEMT",
    /*	8 */	"SIGFPE",
    /*	9 */	"SIGKILL",
    /* 10 */	"SIGBUS",
    /* 11 */	"SIGSEGV",
    /* 12 */	"SIGSYS",
    /* 13 */	"SIGPIPE",
    /* 14 */	"SIGALRM",
    /* 15 */	"SIGTERM",
    /* 16 */	"SIGURG",
    /* 17 */	"SIGSTOP",
    /* 18 */	"SIGTSTP",
    /* 19 */	"SIGCONT",
    /* 20 */	"SIGCHLD",
    /* 21 */	"SIGTTIN",
    /* 22 */	"SIGTTOU",
    /* 23 */	"SIGIO",
    /* 24 */	"SIGXCPU",
    /* 25 */	"SIGXFSZ",
    /* 26 */	"SIGVTALRM",
    /* 27 */	"SIGPROF",
    /* 28 */	"SIGWINCH",
    /* 29 */	"SIG29",	    /* Unused? */
    /* 30 */	"SIGUSR1",
    /* 31 */	"SIGUSR2"
    };
if (signal >= 0 && signal < 32) return(signames[signal]);
sprintf(unknown,"SIGNAL(%d)",signal);
return(unknown);
}

/* This routine forks off a subprocess, with communications maintained      */
/* through a pseudoterminal pair.                                           */
/* Arguments are:							    */
/*	channel[]   -- an integer array through which the I/O descriptors   */
/*		       for communication with the child are returned;	    */
/*		       the parent process reads from channel[0] and writes  */
/*		       to channel[1] to talk to the subprocess.		    */
/*                     (note: with this version of things, which uses pty's,*/
/*                     channel[0] and channel[1] are the same value.        */
/*	cmd	    -- the filename of the command to execute; the user's   */
/*		       path will be searched for the command.		    */
/*	args	    -- an array of string pointers which are the arguments  */
/*		       to be passed to the forked process, these are the    */
/*		       "argv" values seen by the subprocess.  By convention,*/
/*		       args[0] is the same as "cmd", but this is not a	    */
/*		       necessity.  This list should be terminated with a    */
/*		       null pointer.					    */

int _spawn(channel,cmd,args)
    int    channel[];
    char * cmd;
    char * args[];
{
int status, i;
long pid;
int master_fd, slave_fd;


if ((master_fd = open(master_pty,O_RDWR,0)) < 0)
    {
    fprintf(stderr,"_spawn: couldn't open master pty\n");
    return(-1);
    }

if ((slave_fd = open(slave_pty,O_RDWR,0)) < 0)
    {
    fprintf(stderr,"_spawn: couldn't open slave pty read end\n");
    close(master_fd);
    return(-1);
    }

fprintf(stderr,"_spawn: master_fd = %d, slave_fd = %d\n",master_fd, slave_fd);

/* Now we come to the code which actually detaches the subprocess.  First   */
/* step is the standard "fork" call which gives us two identical copies	    */
/* of the parent process; the only difference is that one copy (the child)  */
/* gets back a value for "pid" which is zero, the other copy (the parent)   */
/* gets back the pid of the child process.				    */

pid = fork();
if (pid == -1)
    {
    /* If the fork call failed, close all the channels and exit */
    close(master_fd);
    close(slave_fd);
    return(-1);
    }

if (pid)	/* If I'm the parent process */
    {
    /* The use of two descriptors here is more oriented to pipes than */
    /* pty's; this is a leftover from the version of _spawn I put     */
    /* together which used pipes.  I may fix this later...            */
    channel[READ]  = master_fd;
    channel[WRITE] = master_fd;

    /* Disconnect our access to the slave pty.  The only reason we    */
    /* connected to it at all in the master process was to make sure  */
    /* we could get both ends of the pseudoterminal.		      */
    close(slave_fd);

    /* All done; return child's process id back to main program */
    return(pid);
    }


/* If I make it to here, then i'm the child process.  At this stage,	*/
/* this is an exact clone of the parent process, except for the fact	*/
/* that we got back a value of zero for "pid".	What we do now is call	*/
/* one of the "exec" functions to overlay this program with the program */
/* we really want as the child process.	 But first must take care of	*/
/* some basic housekeeping...						*/

/* Rearrange file descriptors to suit us in the child process.  Make    */
/* the slave pty be our stdin, stdout, and stderr; and close all other  */
/* open descriptors.							*/
/* With thanks to Chris Torek of the Univ of MD for the following code  */
/* fragments...                                                         */

if (dup2(slave_fd,0) < 0 || dup2(slave_fd,1) < 0 || dup2(slave_fd,2) < 0)
    {
    char * scream = "\nchild process couldn't connect stdio channels!\n\n";
    /* collapse in hysterics -- couldn't attach stdin/stdout/stderr */
    slave_fd = open("/dev/tty",O_WRONLY,0);
    write(slave_fd, scream, strlen(scream));
    exit(-1);
    }

/* Now close any irrelevant file descriptors inherited from the parent  */
/* process.								*/

for(i = getdtablesize(); --i>2;)
    close(i);

/* Now invoke the subprocess. No return to this program unless an error */
/* occurs (new program overlays this one).  Since the new program	*/
/* inherits our open file descriptors it ends up with its default I/O	*/
/* channels connected to the parent process.				*/
/* Note that since all flavors of "exec" completely demolish the	*/
/* current program by overlaying a new one, that there is no return	*/
/* from the "exec" call.  If we do return, it means that the overlay	*/
/* operation failed.							*/
execvp(cmd,args);

/* Ack!	 Blew up.  Terminate with extreme prejudice.	*/
/* Note that exit() will close all descriptors for us.	*/
exit(-1);
}


/* Temporary routine for debugging purposes: */
int read_data(dev,pre,post)
    int	   dev;		/* File descriptor to read from */
    char * pre;		/* Stupid debug message */
    char * post;	/* Another debug message*/
{
int    nchars;
int    rchars;
int    nread;
int    status;
int    i;
static char * ioerr = "bad ioctl %s, errno = %d\n";

write(fileno(stderr),pre,strlen(pre));
rchars = 0;

/* Following lines are just for debug tracing, delete when things work */
status = ioctl(dev,FIONREAD,&nchars);
fprintf(stderr,"read_data(fd=%d), status=%d, nchars=%d\n",dev,status,nchars);

for(i=0;;i++)
    {
    status = ioctl(dev,FIONREAD,&nchars);
    if (status == -1) 
	{
	fprintf(stderr,ioerr,"-FIONREAD",errno);
	fflush(stderr);
	nchars=0;
	break;
	}
    fprintf(stderr,"\nread_data: pass %d, %d chars available\n",i,nchars);
    fflush(stderr);
    if (nchars <= 0) break;
    if (nchars > 256) nchars = 256;
    nread = read(dev,buffer,nchars);
    if (nread<=0) 
	{
	fprintf(stderr,"read_data: read failed (nread = %d)\n",nread);
	fflush(stderr);
	break;
	}
    rchars += nread;
    write(fileno(stderr),buffer,nread);
    }

write(fileno(stderr),post,strlen(post));
return(rchars);
}


/* This is an interrupt routine which is invoked when the status of a	*/
/* child process changes						*/

int child_event(sig, code, context)
    int sig, code;
    struct sigcontext * context;
{
int pid;
union wait wstatus;
int status;

fprintf(stderr,"\n<%s>, ",signame(sig));
pid = wait3(&wstatus, WNOHANG, 0);
if (pid <= 0) 
    {
    fprintf(stderr,"pid %d\n",pid);
    fflush(stderr);
    return 0;
    }

if (WIFEXITED(wstatus))
    {
    fprintf(stderr,"pid %d, termsig %s, retcode %d\n", pid,
	signame(wstatus.w_stopsig), wstatus.w_retcode);
    /* This following bit only works because we only spawn one child	*/
    /* process off and can assume that process[READ] and process[WRITE] */
    /* are the descriptors associated with the child process.  In truth,*/
    /* to do this properly we should use the pid of the exiting child   */
    /* to set which open file descriptors (in the parent) are no longer */
    /* of interest to us.  All of this is done because "select" seems to*/
    /* crap out if you specify a descriptor which is no longer open (it */
    /* claims you can read from it even though no characters are	*/
    /* available).							*/
    interesting_rfd &= ~(1<<process[READ]);	/* clear bit */
    interesting_wfd &= ~(1<<process[WRITE]);	/* not used at present */
    }
else if (WIFSTOPPED(wstatus))
    {
    fprintf(stderr,"pid %d, stopped by signal %s\n",pid,
	signame(wstatus.w_stopsig));
    }
else if (WIFSIGNALED(wstatus))
    {
    fprintf(stderr,"pid %d, signalled %s\n",pid,
	signame(wstatus.w_termsig));
    }
fflush(stderr);
}
 

main(argc, argv)
    int argc;
    char * argv[];
{
int  stdin_flags;
int  i,j;
int  rfd, wfd, efd, nfd, nready, fd;

fprintf(stderr,"Master process ready:\n");

i = _spawn(process,argv[1],argv+1,1);
if (i<0)
    {
    fprintf(stderr,"can't spawn subprocess!\n");
    exit(1);
    }

fprintf(stderr,"Subprocess spawned, pid = %d:\n",i);
fprintf(stderr,"    Read  via descriptor %d\n",process[READ]);
fprintf(stderr,"    Write via descriptor %d\n",process[WRITE]);
fflush(stderr);

signal(SIGCHLD, child_event);

interesting_rfd = (1<<fileno(stdin)) | (1 << process[READ]);
nfd = 32;

/* main loop. */
for(;;)
    {
    rfd    = interesting_rfd;
    nready = select(nfd, &rfd, NULL, NULL, NULL);
    fprintf(stderr,"main: select returns nready = %d\n",nready);
    fprintf(stderr,"                     rfd    = $%08x\n",rfd);
    fflush(stderr);
    for(i=0;i<32;i++)
	if (rfd & (1<<i))
	    {
	    fprintf(stderr,"Descriptor %d has data available.\n",i);
	    read_data(i,"***START OF DATA***\n","\n***END OF DATA***\n");
	    }
    }

}