9902495604
From-SVN: r18731
462 lines
11 KiB
C
462 lines
11 KiB
C
|
|
/*
|
|
* $Id: server.c,v 1.1 1998/03/20 16:19:41 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 ()
|
|
{
|
|
sigsend (P_PID, (id_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 ()
|
|
{
|
|
|
|
currentAction.sa_sigaction =
|
|
currentAction.sa_handler = sigHandler;
|
|
currentAction.sa_flags = SA_SIGINFO;
|
|
sigemptyset (¤tAction.sa_mask);
|
|
|
|
sigaction (SIGPIPE, ¤tAction, &savePipeAction);
|
|
sigaction (SIGALRM, ¤tAction, &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;
|
|
}
|
|
}
|