blob: 381897181d03bdf36037fffdd209eb1621708acc [file] [log] [blame]
/*
* $Id: server.c,v 1.2 1998/05/28 09:21:29 korbb Exp $
*
* Server Handling copyright 1992-1998 Bruce Korb
*
* Server Handling is free software.
* You may redistribute it and/or modify it under the terms of the
* GNU General Public License, as published by the Free Software
* Foundation; either version 2, or (at your option) any later version.
*
* Server Handling is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Server Handling. See the file "COPYING". If not,
* write to: The Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* As a special exception, Bruce Korb gives permission for additional
* uses of the text contained in his release of ServerHandler.
*
* The exception is that, if you link the ServerHandler library with other
* files to produce an executable, this does not by itself cause the
* resulting executable to be covered by the GNU General Public License.
* Your use of that executable is in no way restricted on account of
* linking the ServerHandler library code into it.
*
* This exception does not however invalidate any other reasons why
* the executable file might be covered by the GNU General Public License.
*
* This exception applies only to the code released by Bruce Korb under
* the name ServerHandler. If you copy code from other sources under the
* General Public License into a copy of ServerHandler, as the General Public
* License permits, the exception does not apply to the code that you add
* in this way. To avoid misleading anyone as to the status of such
* modified files, you must delete this exception notice from them.
*
* If you write modifications of your own for ServerHandler, it is your
* choice whether to permit this exception to apply to your modifications.
* If you do not wish that, delete this exception notice.
*/
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/param.h>
#include "server.h"
#ifdef DEBUG
#define STATIC
#else
#define STATIC static
#endif
#ifndef tSCC
#define tSCC static const char
#endif
#ifndef NUL
#define NUL '\0'
#endif
STATIC bool readPipeTimeout;
STATIC tpChar defArgs[] =
{(char *) NULL, "-p", (char *) NULL};
STATIC tpfPair serverPair =
{(FILE *) NULL, (FILE *) NULL};
STATIC pid_t serverId = NULLPROCESS;
/*
* Arbitrary text that should not be found in the shell output.
* It must be a single line and appear verbatim at the start of
* the terminating output line.
*/
tSCC zDone[] = "ShElL-OuTpUt-HaS-bEeN-cOmPlEtEd";
STATIC tpChar pCurDir = (char *) NULL;
/*
* chainOpen
*
* Given an FD for an inferior process to use as stdin,
* start that process and return a NEW FD that that process
* will use for its stdout. Requires the argument vector
* for the new process and, optionally, a pointer to a place
* to store the child's process id.
*/
int
chainOpen (stdinFd, ppArgs, pChild)
int stdinFd;
tpChar *ppArgs;
pid_t *pChild;
{
tFdPair stdoutPair =
{-1, -1};
pid_t chId;
char *pzCmd;
/*
* Create a pipe it will be the child process' stdout,
* and the parent will read from it.
*/
if ((pipe ((int *) &stdoutPair) < 0))
{
if (pChild != (pid_t *) NULL)
*pChild = NOPROCESS;
return -1;
}
/*
* If we did not get an arg list, use the default
*/
if (ppArgs == (tpChar *) NULL)
ppArgs = defArgs;
/*
* If the arg list does not have a program,
* assume the "SHELL" from the environment, or, failing
* that, then sh. Set argv[0] to whatever we decided on.
*/
if (pzCmd = *ppArgs,
(pzCmd == (char *) NULL) || (*pzCmd == '\0'))
{
pzCmd = getenv ("SHELL");
if (pzCmd == (char *) NULL)
pzCmd = "sh";
}
#ifdef DEBUG_PRINT
printf ("START: %s\n", pzCmd);
{
int idx = 0;
while (ppArgs[++idx] != (char *) NULL)
printf (" ARG %2d: %s\n", idx, ppArgs[idx]);
}
#endif
/*
* Call fork() and see which process we become
*/
chId = fork ();
switch (chId)
{
case NOPROCESS: /* parent - error in call */
close (stdoutPair.readFd);
close (stdoutPair.writeFd);
if (pChild != (pid_t *) NULL)
*pChild = NOPROCESS;
return -1;
default: /* parent - return opposite FD's */
if (pChild != (pid_t *) NULL)
*pChild = chId;
#ifdef DEBUG_PRINT
printf ("for pid %d: stdin from %d, stdout to %d\n"
"for parent: read from %d\n",
chId, stdinFd, stdoutPair.writeFd, stdoutPair.readFd);
#endif
close (stdinFd);
close (stdoutPair.writeFd);
return stdoutPair.readFd;
case NULLPROCESS: /* child - continue processing */
break;
}
/*
* Close the pipe end handed back to the parent process
*/
close (stdoutPair.readFd);
/*
* Close our current stdin and stdout
*/
close (STDIN_FILENO);
close (STDOUT_FILENO);
/*
* Make the fd passed in the stdin, and the write end of
* the new pipe become the stdout.
*/
fcntl (stdoutPair.writeFd, F_DUPFD, STDOUT_FILENO);
fcntl (stdinFd, F_DUPFD, STDIN_FILENO);
if (*ppArgs == (char *) NULL)
*ppArgs = pzCmd;
execvp (pzCmd, ppArgs);
fprintf (stderr, "Error %d: Could not execvp( '%s', ... ): %s\n",
errno, pzCmd, strerror (errno));
exit (EXIT_PANIC);
}
/*
* p2open
*
* Given a pointer to an argument vector, start a process and
* place its stdin and stdout file descriptors into an fd pair
* structure. The "writeFd" connects to the inferior process
* stdin, and the "readFd" connects to its stdout. The calling
* process should write to "writeFd" and read from "readFd".
* The return value is the process id of the created process.
*/
pid_t
p2open (pPair, ppArgs)
tFdPair *pPair;
tpChar *ppArgs;
{
pid_t chId;
/*
* Create a bi-directional pipe. Writes on 0 arrive on 1
* and vice versa, so the parent and child processes will
* read and write to opposite FD's.
*/
if (pipe ((int *) pPair) < 0)
return NOPROCESS;
pPair->readFd = chainOpen (pPair->readFd, ppArgs, &chId);
if (chId == NOPROCESS)
close (pPair->writeFd);
return chId;
}
/*
* p2fopen
*
* Identical to "p2open()", except that the "fd"'s are "fdopen(3)"-ed
* into file pointers instead.
*/
pid_t
p2fopen (pfPair, ppArgs)
tpfPair *pfPair;
tpChar *ppArgs;
{
tFdPair fdPair;
pid_t chId = p2open (&fdPair, ppArgs);
if (chId == NOPROCESS)
return chId;
pfPair->pfRead = fdopen (fdPair.readFd, "r");
pfPair->pfWrite = fdopen (fdPair.writeFd, "w");
return chId;
}
/*
* loadData
*
* Read data from a file pointer (a pipe to a process in this context)
* until we either get EOF or we get a marker line back.
* The read data are stored in a malloc-ed string that is truncated
* to size at the end. Input is assumed to be an ASCII string.
*/
STATIC char *
loadData (fp)
FILE *fp;
{
char *pzText;
size_t textSize;
char *pzScan;
char zLine[1024];
textSize = sizeof (zLine) * 2;
pzScan = \
pzText = malloc (textSize);
if (pzText == (char *) NULL)
return pzText;
for (;;)
{
size_t usedCt;
alarm (10);
readPipeTimeout = BOOL_FALSE;
if (fgets (zLine, sizeof (zLine), fp) == (char *) NULL)
break;
if (strncmp (zLine, zDone, sizeof (zDone) - 1) == 0)
break;
strcpy (pzScan, zLine);
pzScan += strlen (zLine);
usedCt = (size_t) (pzScan - pzText);
if (textSize - usedCt < sizeof (zLine))
{
size_t off = (size_t) (pzScan - pzText);
void *p;
textSize += 4096;
p = realloc ((void *) pzText, textSize);
if (p == (void *) NULL)
{
fprintf (stderr, "Failed to get 0x%08X bytes\n", textSize);
free ((void *) pzText);
return (char *) NULL;
}
pzText = (char *) p;
pzScan = pzText + off;
}
}
alarm (0);
if (readPipeTimeout)
{
free ((void *) pzText);
return (char *) NULL;
}
while ((pzScan > pzText) && isspace (pzScan[-1]))
pzScan--;
*pzScan = NUL;
return realloc ((void *) pzText, strlen (pzText) + 1);
}
/*
* SHELL SERVER PROCESS CODE
*/
#ifdef DONT_HAVE_SIGSEND
typedef enum
{
P_ALL, P_PID, P_GID, P_UID, P_PGID, P_SID, P_CID
}
idtype_t;
typedef long id_t;
STATIC int
sigsend (idtype, id, sig)
idtype_t idtype;
id_t id;
int sig;
{
switch (idtype)
{
case P_PID:
kill ((pid_t) id, sig);
break;
case P_ALL:
case P_GID:
case P_UID:
case P_PGID:
case P_SID:
case P_CID:
errno = EINVAL;
return -1;
/*NOTREACHED */
}
return 0;
}
#endif /* HAVE_SIGSEND */
STATIC void
closeServer ()
{
kill( (pid_t) serverId, SIGKILL);
serverId = NULLPROCESS;
fclose (serverPair.pfRead);
fclose (serverPair.pfWrite);
serverPair.pfRead = serverPair.pfWrite = (FILE *) NULL;
}
struct sigaction savePipeAction;
struct sigaction saveAlrmAction;
struct sigaction currentAction;
STATIC void
sigHandler (signo)
int signo;
{
closeServer ();
readPipeTimeout = BOOL_TRUE;
}
STATIC void
serverSetup ()
{
#ifndef SA_SIGINFO
# define SA_SIGINFO 0
#else
currentAction.sa_sigaction =
#endif
currentAction.sa_handler = sigHandler;
currentAction.sa_flags = SA_SIGINFO;
sigemptyset( &currentAction.sa_mask );
sigaction( SIGPIPE, &currentAction, &savePipeAction );
sigaction( SIGALRM, &currentAction, &saveAlrmAction );
atexit( &closeServer );
fputs ("trap : INT\n", serverPair.pfWrite);
fflush (serverPair.pfWrite);
pCurDir = getcwd ((char *) NULL, MAXPATHLEN + 1);
}
char *
runShell (pzCmd)
const char *pzCmd;
{
tSCC zNil[] = "";
/*
* IF the shell server process is not running yet,
* THEN try to start it.
*/
if (serverId == NULLPROCESS)
{
serverId = p2fopen (&serverPair, defArgs);
if (serverId > 0)
serverSetup ();
}
/*
* IF it is still not running,
* THEN return the nil string.
*/
if (serverId <= 0)
return (char *) zNil;
/*
* Make sure the process will pay attention to us,
* send the supplied command, and then
* have it output a special marker that we can find.
*/
fprintf (serverPair.pfWrite, "\\cd %s\n%s\n\necho\necho %s\n",
pCurDir, pzCmd, zDone);
fflush (serverPair.pfWrite);
if (serverId == NULLPROCESS)
return (char *) NULL;
/*
* Now try to read back all the data. If we fail due to either
* a sigpipe or sigalrm (timeout), we will return the nil string.
*/
{
char *pz = loadData (serverPair.pfRead);
if (pz == (char *) NULL)
{
fprintf (stderr, "CLOSING SHELL SERVER - command failure:\n\t%s\n",
pzCmd);
closeServer ();
pz = (char *) zNil;
}
return pz;
}
}