|  | 
 | /* | 
 |  *  server.c  Set up and handle communications with a server process. | 
 |  * | 
 |  *  Server Handling copyright 1992-1999, 2001 The Free Software Foundation | 
 |  * | 
 |  *  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., | 
 |  *             51 Franklin Street, Fifth Floor, | 
 |  *             Boston,  MA  02110-1301, USA. | 
 |  * | 
 |  * As a special exception, The Free Software Foundation 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 The Free | 
 |  * Software Foundation 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 "fixlib.h" | 
 | #include "server.h" | 
 |  | 
 | STATIC volatile enum t_bool read_pipe_timeout; | 
 | STATIC pid_t server_master_pid = NOPROCESS; | 
 |  | 
 | tSCC* def_args[] = | 
 | { (char *) NULL, (char *) NULL }; | 
 | STATIC t_pf_pair server_pair = | 
 | { (FILE *) NULL, (FILE *) NULL }; | 
 | STATIC pid_t server_id = 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 z_done[] = "ShElL-OuTpUt-HaS-bEeN-cOmPlEtEd"; | 
 | tSCC* p_cur_dir = (char *) NULL; | 
 |  | 
 | /* | 
 |  *  load_data | 
 |  * | 
 |  *  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 * | 
 | load_data (FILE* fp) | 
 | { | 
 |   char *pz_text; | 
 |   size_t text_size; | 
 |   char *pz_scan; | 
 |   char z_line[1024]; | 
 |   t_bool got_done = BOOL_FALSE; | 
 |  | 
 |   text_size = sizeof (z_line) * 2; | 
 |   pz_scan = pz_text = XNEWVEC (char, text_size); | 
 |  | 
 |   for (;;) | 
 |     { | 
 |       size_t used_ct; | 
 |  | 
 |       alarm (10); | 
 |       read_pipe_timeout = BOOL_FALSE; | 
 |       if (fgets (z_line, sizeof (z_line), fp) == (char *) NULL) | 
 |         break; | 
 |  | 
 |       if (strncmp (z_line, z_done, sizeof (z_done) - 1) == 0) | 
 | 	{ | 
 | 	  got_done = BOOL_TRUE; | 
 | 	  break; | 
 | 	} | 
 |  | 
 |       strcpy (pz_scan, z_line); | 
 |       pz_scan += strlen (z_line); | 
 |       used_ct = (size_t) (pz_scan - pz_text); | 
 |  | 
 |       if (text_size - used_ct < sizeof (z_line)) | 
 |         { | 
 |           size_t off = (size_t) (pz_scan - pz_text); | 
 | 	   | 
 |           text_size += 4096; | 
 |           pz_text = XRESIZEVEC (char, pz_text, text_size); | 
 |           pz_scan = pz_text + off; | 
 |         } | 
 |     } | 
 |  | 
 |   alarm (0); | 
 |   if (read_pipe_timeout || ! got_done) | 
 |     { | 
 |       free ((void *) pz_text); | 
 |       return (char *) NULL; | 
 |     } | 
 |  | 
 |   while ((pz_scan > pz_text) && ISSPACE (pz_scan[-1])) | 
 |     pz_scan--; | 
 |   *pz_scan = NUL; | 
 |   return XRESIZEVEC (char, pz_text, strlen (pz_text) + 1); | 
 | } | 
 |  | 
 |  | 
 | /* | 
 |  *  close_server | 
 |  * | 
 |  *  Make certain the server process is dead, close the  | 
 |  *  pipes to it and from it, finally NULL out the file pointers | 
 |  */ | 
 | void | 
 | close_server (void) | 
 | { | 
 |   if (  (server_id != NULLPROCESS) | 
 |      && (server_master_pid == getpid ())) | 
 |     { | 
 |       kill ((pid_t) server_id, SIGKILL); | 
 |       server_id = NULLPROCESS; | 
 |       server_master_pid = NOPROCESS; | 
 |       fclose (server_pair.pf_read); | 
 |       fclose (server_pair.pf_write); | 
 |       server_pair.pf_read = server_pair.pf_write = (FILE *) NULL; | 
 |     } | 
 | } | 
 |  | 
 | /* | 
 |  *  sig_handler really only handles the timeout and pipe signals. | 
 |  *  This ensures that we do not wait forever on a request | 
 |  *  to our server, and also that if the server dies, we do not | 
 |  *  die from a sigpipe problem. | 
 |  */ | 
 | static void | 
 | sig_handler (int signo ATTRIBUTE_UNUSED) | 
 | { | 
 | #ifdef DEBUG | 
 |   /* FIXME: this is illegal to do in a signal handler.  */ | 
 |   fprintf (stderr, | 
 |           "fixincl ERROR:  sig_handler: killed pid %ld due to %s\n", | 
 |           (long) server_id, signo == SIGPIPE ? "SIGPIPE" : "SIGALRM"); | 
 | #endif | 
 |   close_server (); | 
 |   read_pipe_timeout = BOOL_TRUE; | 
 | } | 
 |  | 
 |  | 
 | /* | 
 |  *  server_setup  Establish the signal handler for PIPE and ALARM. | 
 |  *  Also establishes the current directory to give to the | 
 |  *  server process at the start of every server command. | 
 |  */ | 
 | static void | 
 | server_setup (void) | 
 | { | 
 |   static int atexit_done = 0; | 
 |   char buff [MAXPATHLEN + 1]; | 
 |    | 
 |   if (atexit_done++ == 0) | 
 |     atexit (close_server); | 
 |   else | 
 |     fputs ("NOTE: server restarted\n", stderr); | 
 |  | 
 |   server_master_pid = getpid (); | 
 |  | 
 |   signal (SIGPIPE, sig_handler); | 
 |   signal (SIGALRM, sig_handler); | 
 |  | 
 |   fputs ("trap : 1\n", server_pair.pf_write); | 
 |   fflush (server_pair.pf_write); | 
 |   if (getcwd (buff, MAXPATHLEN + 1) == NULL) | 
 |     buff[0] = 0; | 
 |   p_cur_dir = xstrdup (buff); | 
 | } | 
 |  | 
 | /* | 
 |  *  find_shell | 
 |  * | 
 |  *  Locate a shell suitable for use.  For various reasons | 
 |  *  (like the use of "trap" in server_setup(), it must be a | 
 |  *  Bourne-like shell. | 
 |  * | 
 |  *  Most of the time, /bin/sh is preferred, but sometimes | 
 |  *  it's quite broken (like on Ultrix).  autoconf lets you | 
 |  *  override with $CONFIG_SHELL, so we do the same. | 
 |  */ | 
 |  | 
 | static const char * | 
 | find_shell (void) | 
 | { | 
 |   char * shell = getenv ("CONFIG_SHELL"); | 
 |   if (shell) | 
 |     return shell; | 
 |  | 
 |   return "/bin/sh"; | 
 | } | 
 |  | 
 |  | 
 | /* | 
 |  *  run_shell | 
 |  * | 
 |  *  Run a shell command on the server.  The command string | 
 |  *  passed in is wrapped inside the sequence: | 
 |  * | 
 |  *     cd <original directory> | 
 |  *     <command string> | 
 |  *     echo | 
 |  *     echo <end-of-command-marker> | 
 |  * | 
 |  *  This ensures that all commands start at a known place in | 
 |  *  the directory structure, that any incomplete output lines | 
 |  *  are completed and that our special marker sequence appears on | 
 |  *  a line by itself.  We have chosen a marker that is | 
 |  *  excessively unlikely to be reproduced in normal output: | 
 |  * | 
 |  *     "ShElL-OuTpUt-HaS-bEeN-cOmPlEtEd" | 
 |  */ | 
 | char * | 
 | run_shell (const char* pz_cmd) | 
 | { | 
 |   tSCC zNoServer[] = "Server not running, cannot run:\n%s\n\n"; | 
 |   t_bool retry = BOOL_TRUE; | 
 |  | 
 |  do_retry: | 
 |   /*  IF the shell server process is not running yet, | 
 |       THEN try to start it.  */ | 
 |   if (server_id == NULLPROCESS) | 
 |     { | 
 |       def_args[0] = find_shell (); | 
 |  | 
 |       server_id = proc2_fopen (&server_pair, def_args); | 
 |       if (server_id > 0) | 
 |         server_setup (); | 
 |     } | 
 |  | 
 |   /*  IF it is still not running, THEN return the nil string.  */ | 
 |   if (server_id <= 0) | 
 |     { | 
 |       fprintf (stderr, zNoServer, pz_cmd); | 
 |       return XCNEW (char); | 
 |     } | 
 |  | 
 |   /*  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 (server_pair.pf_write, "cd \"%s\"\n%s\n\necho\necho %s\n", | 
 |            p_cur_dir, pz_cmd, z_done); | 
 |   fflush (server_pair.pf_write); | 
 |  | 
 |   /*  IF the server died and we received a SIGPIPE, | 
 |       THEN return an empty string.  */ | 
 |   if (server_id == NULLPROCESS) | 
 |     { | 
 |       fprintf (stderr, zNoServer, pz_cmd); | 
 |       return XCNEW (char); | 
 |     } | 
 |  | 
 |   /*  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 = load_data (server_pair.pf_read); | 
 |      | 
 |     if (pz == (char *) NULL) | 
 |       { | 
 | 	close_server (); | 
 |  | 
 | 	if (retry) | 
 | 	  { | 
 | 	    retry = BOOL_FALSE; | 
 | 	    goto do_retry; | 
 | 	  } | 
 |  | 
 |         fprintf (stderr, "CLOSING SHELL SERVER - command failure:\n\t%s\n", | 
 |                  pz_cmd); | 
 |         pz = XCNEW (char); | 
 |       } | 
 | #ifdef DEBUG | 
 |     fprintf( stderr, "run_shell command success:  %s\n", pz ); | 
 | #endif | 
 |     return pz; | 
 |   } | 
 | } |