Path: utzoo!mnetor!uunet!steinmetz!mercutio!lanzo From: lanzo@mercutio.steinmetz (Mark Lanzo) Newsgroups: comp.unix.questions Subject: Re: Another attempt at getting help with process communications Message-ID: <8246@steinmetz.steinmetz.UUCP> Date: 17 Dec 87 18:52:44 GMT References: <8214@steinmetz.steinmetz.UUCP> <9801@mimsy.UUCP> Sender: root@steinmetz.steinmetz.UUCP Reply-To: lanzo@mercutio.UUCP (Mark Lanzo) Organization: General Electric CRD, Schenectady, NY Lines: 365 Summary: LONG! 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 #include #include #include #include #include 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<