/* $Id: unixfork.c,v 1.6 2001/12/26 22:17:05 sybalsky Exp $ (C) Copyright Venue, All Rights Reserved */ /************************************************************************/ /* */ /* Code to fork a subprocess for Unix communication */ /* */ /* */ /* */ /************************************************************************/ /************************************************************************/ /* */ /* (C) Copyright 1989-1998 Venue. All Rights Reserved. */ /* Manufactured in the United States of America. */ /* */ /************************************************************************/ #include "version.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef OS5 #include #include #endif #include "dbprint.h" #include "unixfork.h" #ifdef DEBUG /* required by DBPRINT from dbprint.h */ int flushing = 0; #endif /* Used to communicate between Unix subprocesses and LISP */ static long StartTime; /* Time, for creating pipe filenames */ static char shcom[512]; /* Here because I'm suspicious of */ /* large allocations on the stack */ static inline ssize_t SAFEREAD(int f, char *b, size_t c) { ssize_t res; do { res = read(f, b, c); if (res >= 0) return (res); } while (errno == EINTR || errno == EAGAIN); perror("reading UnixPipeIn"); return (res); } /************************************************************************/ /* */ /* F o r k U n i x S h e l l */ /* */ /* Fork a PTY connection to a C-shell process. */ /* Returns PID of process, or -1 if something failed */ /* */ /* */ /************************************************************************/ /* Creates a PTY connection to a shell */ static int ForkUnixShell(int slot, char *PtySlave, char *termtype, char *shellarg) { int PID, SlaveFD; struct termios tio; char *argvec[4] = {NULL, NULL, NULL, NULL}; char *shell = NULL; char *userShell = NULL; PID = fork(); if (PID != 0) { if (shellarg != shcom) free(shellarg); return (PID); } if (0 > setsid()) /* create us a new session for tty purposes */ perror("setsid"); /* Open the slave side */ SlaveFD = open(PtySlave, O_RDWR); if (SlaveFD == -1) { perror("Slave Open"); perror(PtySlave); exit(0); } #ifdef OS5 ioctl(SlaveFD, I_PUSH, "ptem"); ioctl(SlaveFD, I_PUSH, "ldterm"); #endif /* OS5 */ /* Set up as basic display terminal: canonical erase, kill processing, echo, backspace to erase, echo ctrl chars as ^x, kill line by backspacing */ tcgetattr(SlaveFD, &tio); tio.c_lflag |= ICANON | ECHO | ECHOE | ECHOCTL | ECHOKE; tcsetattr(SlaveFD, TCSANOW, &tio); (void)dup2(SlaveFD, 0); (void)dup2(SlaveFD, 1); (void)dup2(SlaveFD, 2); (void)close(SlaveFD); /* set the LDESHELL variable so the underlying shell initialization can see it and configure the shell appropriately, though this may not be so important any more */ setenv("LDESHELL", "YES", 1); if (termtype[0] != 0) { /* set the TERM environment var */ setenv("TERM", termtype, 1); } /* Start up shell -- use SHELL environment variable as long as it's in /etc/shells */ shell = getenv("SHELL"); if (shell == NULL) /* shell of last resort */ shell = "/bin/sh"; for (userShell = getusershell(); userShell != NULL && strcmp(shell, userShell) != 0; userShell = getusershell()); if (userShell == NULL) { perror("$(SHELL) not found in /etc/shells"); exit(0); } /* argvec entries initialized to NULL */ argvec[0] = strrchr(userShell, '/') + 1; if (shellarg[0] != 0) { /* setup to run command */ argvec[1] = "-c"; /* read commands from next arg */ argvec[2] = shellarg; } execv(userShell, argvec); /* Should never get here */ perror("execv"); exit(0); } /* fork_Unix is the secondary process spawned right after LISP is started, to avoid having TWO 8 mbyte images sitting around. It listens to the pipe LispToUnix waiting for requests, and responds on UnixToLisp. The data passed through this pipe is in 6 byte packets, of the form: Byte 0: Command character, one of: S: Fork PTY (shell) process. This is used for CHAT windows. P: New version of S, takes 2 string args. F: Fork piped shell, takes 1 string arg. K: Kill process E: Exit (kill all subprocesses) C: Close stdin to subprocess Byte 1: Process number (0 to NPROCS - 1) Not used for S, F, and E commands [For S&P, pty letter] [For F, process # for pipe naming] Byte 2: Value, used as follows: Only used for W command, contains byte to write [For S&P, pty number] Byte 3: Slot number. Byte 4: unused. Byte 5: unused. In the case of F & P commands, additional data follows the 6 byte packet. This consists of 2 bytes representing the length of the shell command string, and the string itself. fork_Unix will return another 6 byte packet. The bytes are the same as those of the packet received except: F, S, P: Bytes 1, 2, 4, and 5 are the Unix process id Byte 3 is 1 if successful, 0 if not R: Byte 2 is value of byte read from stdin, if any Byte 3 is 1 if successful, 2 if EOF, 0 if nothing waiting W: Bytes 0, 1, 4, 5 are the Process ID of the terminated process Bytes 2 & 3 are the high & low bytes of the exit status. K: Bytes 1 and 2 are the high and low bytes of the exit status of the process. Byte 3 is 1 if an exit status was available. C: Always the same */ int fork_Unix(void) { int LispToUnix[2], /* Incoming pipe from LISP */ UnixToLisp[2], /* Outgoing pipe to LISP */ UnixPID, LispPipeIn, LispPipeOut, slot; pid_t pid; sigset_t signals; char IOBuf[6]; /* XXX: signed, because slot comes from here */ unsigned short tmp = 0; char *cmdstring; /* Pipes between LISP subr and process */ if (pipe(LispToUnix) == -1) { perror("pipe"); exit(-1); } if (pipe(UnixToLisp) == -1) { perror("pipe"); exit(-1); } StartTime = time(0); /* Save the time, to create filenames with */ StartTime &= 0xFFFFFF; /* as a positive number! */ /* interrupts need to be blocked here so subprocess won't see them */ sigemptyset(&signals); sigaddset(&signals, SIGVTALRM); sigaddset(&signals, SIGIO); sigaddset(&signals, SIGALRM); sigaddset(&signals, SIGXFSZ); sigaddset(&signals, SIGFPE); sigprocmask(SIG_BLOCK, &signals, NULL); if ((UnixPID = fork()) == -1) { /* Fork off small version of the emulator */ perror("fork"); exit(-1); } if (UnixPID != 0) { /* JRB - fork_Unix is now called in ldeboot; leave UnixPipe{In,Out} open and put their numbers in the environment so parent can find them */ /* JDS - NB that sprintf doesn't always return a string! */ char tempstring[30]; snprintf(tempstring, sizeof(tempstring), "%d", UnixToLisp[0]); setenv("LDEPIPEIN", tempstring, 1); snprintf(tempstring, sizeof(tempstring), "%d", LispToUnix[1]); setenv("LDEPIPEOUT", tempstring, 1); snprintf(tempstring, sizeof(tempstring), "%ld", StartTime); setenv("LDESTARTTIME", tempstring, 1); snprintf(tempstring, sizeof(tempstring), "%d", UnixPID); setenv("LDEUNIXPID", tempstring, 1); close(LispToUnix[0]); close(UnixToLisp[1]); return (1); } LispPipeIn = LispToUnix[0]; LispPipeOut = UnixToLisp[1]; close(LispToUnix[1]); close(UnixToLisp[0]); fcntl(LispPipeIn, F_SETFL, fcntl(LispPipeIn, F_GETFL, 0) & ~O_NONBLOCK); while (1) { ssize_t len; len = SAFEREAD(LispPipeIn, IOBuf, 6); if (len < 0) { perror("Error reading packet by slave"); exit(0); } else if (len != 6) { DBPRINT(("Input packet wrong length: %zd", len)); exit(0); } slot = IOBuf[3]; IOBuf[3] = 1; /* Start by signalling success in return-code */ switch (IOBuf[0]) { case 'S': case 'P': /* Fork PTY shell */ if (slot >= 0) { /* Found a free slot */ char termtype[64]; char slavepty[32]; /* For slave pty name */ if (SAFEREAD(LispPipeIn, (char *)&tmp, 2) < 0) perror("Slave reading slave pty len"); if (SAFEREAD(LispPipeIn, slavepty, tmp) < 0) perror("Slave reading slave pty id"); if (IOBuf[0] == 'P') { /* The new style, which takes term type & command to csh */ if (SAFEREAD(LispPipeIn, (char *)&tmp, 2) < 0) perror("Slave reading cmd length"); if (tmp > 63) perror("Slave termtype length too long"); if (SAFEREAD(LispPipeIn, termtype, tmp) < 0) perror("Slave reading termtype"); termtype[tmp] = '\0'; if (SAFEREAD(LispPipeIn, (char *)&tmp, 2) < 0) perror("Slave reading cmd length"); if (tmp > 510) cmdstring = (char *)malloc(tmp + 5); else cmdstring = shcom; if (SAFEREAD(LispPipeIn, cmdstring, tmp) < 0) perror("Slave reading shcom"); } else /* old style, no args */ { termtype[0] = 0; cmdstring = shcom; cmdstring[0] = 0; } /* Alloc a PTY and fork */ pid = ForkUnixShell(slot, slavepty, termtype, cmdstring); if (pid == -1) { printf("Impossible failure from ForkUnixShell??\n"); fflush(stdout); IOBuf[3] = 0; } else { /* ForkUnixShell sets the pid and standard in/out variables */ IOBuf[1] = (pid >> 8) & 0xFF; IOBuf[2] = pid & 0xFF; IOBuf[4] = (pid >> 16) & 0xFF; IOBuf[5] = (pid >> 24) & 0xFF; } } else { printf("Can't get process slot for PTY shell.\n"); fflush(stdout); IOBuf[3] = 0; } break; case 'F': /* Fork pipe command */ if (slot >= 0) { /* Read in the length of the shell command, and then the command */ if (SAFEREAD(LispPipeIn, (char *)&tmp, 2) < 0) perror("Slave reading cmd length"); if (tmp > 510) cmdstring = (char *)malloc(tmp + 5); else cmdstring = shcom; if (SAFEREAD(LispPipeIn, cmdstring, tmp) < 0) perror("Slave reading cmd"); DBPRINT(("Cmd len = %d.\n", tmp)); DBPRINT(("Rev'd cmd string: %s\n", cmdstring)); pid = fork(); /* Fork */ if (pid == 0) { int i; int status, sock; struct sockaddr_un addr; char PipeName[40]; sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { perror("slave socket"); exit(0); } sprintf(PipeName, "/tmp/LPU%ld-%d", StartTime, slot); memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, PipeName); status = connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)); if (status < 0) { perror("slave connect"); printf("Name = %s.\n", PipeName); fflush(stdout); exit(0); } else { DBPRINT(("Slave connected on %s.\n", PipeName)); } /* Copy the pipes onto stdin, stdout, and stderr */ dup2(sock, 0); dup2(sock, 1); dup2(sock, 2); /* Make sure everything else is closed. */ for (i = 3; i < sysconf(_SC_OPEN_MAX); i++) close(i); /* Run the shell command and get the result */ status = system(cmdstring); if (cmdstring != shcom) free(cmdstring); /* Comment out to fix USAR 11302 (FXAR 320) unlink(PipeName); */ _exit((status & ~0xff) ? (status >> 8) : status); } /* Check for error doing the fork */ if (pid == (pid_t)-1) { perror("unixcomm: fork"); IOBuf[3] = 0; } else { IOBuf[1] = (pid >> 8) & 0xFF; IOBuf[2] = pid & 0xFF; IOBuf[4] = (pid >> 16) & 0xFF; IOBuf[5] = (pid >> 24) & 0xFF; } } else { printf("No process slots available.\n"); IOBuf[3] = 0; /* Couldn't get a process slot */ } break; case 'W': /* Wait for a process to die. */ { int status; status = 0; IOBuf[0] = 0; IOBuf[1] = 0; DBPRINT(("About to wait for processes.\n")); retry1: pid = waitpid(-1, &status, WNOHANG); if (pid == -1 && errno == EINTR) goto retry1; if (pid > 0) { /* Ignore processes which are suspended but haven't exited (this shouldn't happen) */ if (WIFSTOPPED(status)) break; IOBuf[5] = (pid >> 24) & 0xFF; IOBuf[4] = (pid >> 16) & 0xFF; IOBuf[3] = status >> 8; IOBuf[2] = status & 0xFF; IOBuf[1] = pid & 0xFF; IOBuf[0] = (pid >> 8) & 0xFF; } DBPRINT(("wait3 returned pid = %ld.\n", (long)pid)); } break; case 'C': /* Close stdin to subprocess */ break; case 'K': /* Kill subprocess */ break; } /* End of switch */ /* Return the status/data packet */ write(LispPipeOut, IOBuf, 6); } }