1
2/*
3 *  server.c  Set up and handle communications with a server process.
4 *
5 *  Server Handling copyright 1992-1999, 2001 The Free Software Foundation
6 *
7 *  Server Handling is free software.
8 *  You may redistribute it and/or modify it under the terms of the
9 *  GNU General Public License, as published by the Free Software
10 *  Foundation; either version 2, or (at your option) any later version.
11 *
12 *  Server Handling is distributed in the hope that it will be useful,
13 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 *  GNU General Public License for more details.
16 *
17 *  You should have received a copy of the GNU General Public License
18 *  along with Server Handling.  See the file "COPYING".  If not,
19 *  write to:  The Free Software Foundation, Inc.,
20 *             59 Temple Place - Suite 330,
21 *             Boston,  MA  02111-1307, USA.
22 *
23 * As a special exception, The Free Software Foundation gives
24 * permission for additional uses of the text contained in his release
25 * of ServerHandler.
26 *
27 * The exception is that, if you link the ServerHandler library with other
28 * files to produce an executable, this does not by itself cause the
29 * resulting executable to be covered by the GNU General Public License.
30 * Your use of that executable is in no way restricted on account of
31 * linking the ServerHandler library code into it.
32 *
33 * This exception does not however invalidate any other reasons why
34 * the executable file might be covered by the GNU General Public License.
35 *
36 * This exception applies only to the code released by The Free
37 * Software Foundation under the name ServerHandler.  If you copy code
38 * from other sources under the General Public License into a copy of
39 * ServerHandler, as the General Public License permits, the exception
40 * does not apply to the code that you add in this way.  To avoid
41 * misleading anyone as to the status of such modified files, you must
42 * delete this exception notice from them.
43 *
44 * If you write modifications of your own for ServerHandler, it is your
45 * choice whether to permit this exception to apply to your modifications.
46 * If you do not wish that, delete this exception notice.
47 */
48
49#include "fixlib.h"
50#include "server.h"
51
52STATIC volatile enum t_bool read_pipe_timeout;
53STATIC pid_t server_master_pid = NOPROCESS;
54
55tSCC* def_args[] =
56{ (char *) NULL, (char *) NULL };
57STATIC t_pf_pair server_pair =
58{ (FILE *) NULL, (FILE *) NULL };
59STATIC pid_t server_id = NULLPROCESS;
60/*
61 *  Arbitrary text that should not be found in the shell output.
62 *  It must be a single line and appear verbatim at the start of
63 *  the terminating output line.
64 */
65tSCC z_done[] = "ShElL-OuTpUt-HaS-bEeN-cOmPlEtEd";
66tSCC* p_cur_dir = (char *) NULL;
67
68/*
69 *  load_data
70 *
71 *  Read data from a file pointer (a pipe to a process in this context)
72 *  until we either get EOF or we get a marker line back.
73 *  The read data are stored in a malloc-ed string that is truncated
74 *  to size at the end.  Input is assumed to be an ASCII string.
75 */
76static char *
77load_data (FILE* fp)
78{
79  char *pz_text;
80  size_t text_size;
81  char *pz_scan;
82  char z_line[1024];
83  t_bool got_done = BOOL_FALSE;
84
85  text_size = sizeof (z_line) * 2;
86  pz_scan = pz_text = xmalloc (text_size);
87
88  for (;;)
89    {
90      size_t used_ct;
91
92      alarm (10);
93      read_pipe_timeout = BOOL_FALSE;
94      if (fgets (z_line, sizeof (z_line), fp) == (char *) NULL)
95        break;
96
97      if (strncmp (z_line, z_done, sizeof (z_done) - 1) == 0)
98	{
99	  got_done = BOOL_TRUE;
100        break;
101	}
102
103      strcpy (pz_scan, z_line);
104      pz_scan += strlen (z_line);
105      used_ct = (size_t) (pz_scan - pz_text);
106
107      if (text_size - used_ct < sizeof (z_line))
108        {
109          size_t off = (size_t) (pz_scan - pz_text);
110
111          text_size += 4096;
112          pz_text = xrealloc (pz_text, text_size);
113          pz_scan = pz_text + off;
114        }
115    }
116
117  alarm (0);
118  if (read_pipe_timeout || ! got_done)
119    {
120      free ((void *) pz_text);
121      return (char *) NULL;
122    }
123
124  while ((pz_scan > pz_text) && ISSPACE (pz_scan[-1]))
125    pz_scan--;
126  *pz_scan = NUL;
127  return xrealloc (pz_text, strlen (pz_text) + 1);
128}
129
130
131/*
132 *  close_server
133 *
134 *  Make certain the server process is dead, close the
135 *  pipes to it and from it, finally NULL out the file pointers
136 */
137void
138close_server (void)
139{
140  if (  (server_id != NULLPROCESS)
141     && (server_master_pid == getpid ()))
142    {
143      kill ((pid_t) server_id, SIGKILL);
144      server_id = NULLPROCESS;
145      server_master_pid = NOPROCESS;
146      fclose (server_pair.pf_read);
147      fclose (server_pair.pf_write);
148      server_pair.pf_read = server_pair.pf_write = (FILE *) NULL;
149    }
150}
151
152/*
153 *  sig_handler really only handles the timeout and pipe signals.
154 *  This ensures that we do not wait forever on a request
155 *  to our server, and also that if the server dies, we do not
156 *  die from a sigpipe problem.
157 */
158static void
159sig_handler (int signo ATTRIBUTE_UNUSED)
160{
161#ifdef DEBUG
162  /* FIXME: this is illegal to do in a signal handler.  */
163  fprintf (stderr,
164          "fixincl ERROR:  sig_handler: killed pid %ld due to %s\n",
165          (long) server_id, signo == SIGPIPE ? "SIGPIPE" : "SIGALRM");
166#endif
167  close_server ();
168  read_pipe_timeout = BOOL_TRUE;
169}
170
171
172/*
173 *  server_setup  Establish the signal handler for PIPE and ALARM.
174 *  Also establishes the current directory to give to the
175 *  server process at the start of every server command.
176 */
177static void
178server_setup (void)
179{
180  static int atexit_done = 0;
181  char buff [MAXPATHLEN + 1];
182
183  if (atexit_done++ == 0)
184    atexit (close_server);
185  else
186    fputs ("NOTE: server restarted\n", stderr);
187
188  server_master_pid = getpid ();
189
190  signal (SIGPIPE, sig_handler);
191  signal (SIGALRM, sig_handler);
192
193  fputs ("trap : 1\n", server_pair.pf_write);
194  fflush (server_pair.pf_write);
195  getcwd (buff, MAXPATHLEN + 1);
196  p_cur_dir = xstrdup (buff);
197}
198
199/*
200 *  find_shell
201 *
202 *  Locate a shell suitable for use.  For various reasons
203 *  (like the use of "trap" in server_setup(), it must be a
204 *  Bourne-like shell.
205 *
206 *  Most of the time, /bin/sh is preferred, but sometimes
207 *  it's quite broken (like on Ultrix).  autoconf lets you
208 *  override with $CONFIG_SHELL, so we do the same.
209 */
210
211static const char *
212find_shell (void)
213{
214  char * shell = getenv ("CONFIG_SHELL");
215  if (shell)
216    return shell;
217
218  return "/bin/sh";
219}
220
221
222/*
223 *  run_shell
224 *
225 *  Run a shell command on the server.  The command string
226 *  passed in is wrapped inside the sequence:
227 *
228 *     cd <original directory>
229 *     <command string>
230 *     echo
231 *     echo <end-of-command-marker>
232 *
233 *  This ensures that all commands start at a known place in
234 *  the directory structure, that any incomplete output lines
235 *  are completed and that our special marker sequence appears on
236 *  a line by itself.  We have chosen a marker that is
237 *  excessively unlikely to be reproduced in normal output:
238 *
239 *     "ShElL-OuTpUt-HaS-bEeN-cOmPlEtEd"
240 */
241char *
242run_shell (const char* pz_cmd)
243{
244  tSCC zNoServer[] = "Server not running, cannot run:\n%s\n\n";
245  t_bool retry = BOOL_TRUE;
246
247 do_retry:
248  /*  IF the shell server process is not running yet,
249      THEN try to start it.  */
250  if (server_id == NULLPROCESS)
251    {
252      def_args[0] = find_shell ();
253
254      server_id = proc2_fopen (&server_pair, def_args);
255      if (server_id > 0)
256        server_setup ();
257    }
258
259  /*  IF it is still not running, THEN return the nil string.  */
260  if (server_id <= 0)
261    {
262      fprintf (stderr, zNoServer, pz_cmd);
263      return xcalloc (1, 1);
264    }
265
266  /*  Make sure the process will pay attention to us, send the
267     supplied command, and then have it output a special marker that
268     we can find.  */
269  fprintf (server_pair.pf_write, "cd %s\n%s\n\necho\necho %s\n",
270           p_cur_dir, pz_cmd, z_done);
271  fflush (server_pair.pf_write);
272
273  /*  IF the server died and we received a SIGPIPE,
274      THEN return an empty string.  */
275  if (server_id == NULLPROCESS)
276    {
277      fprintf (stderr, zNoServer, pz_cmd);
278      return xcalloc (1, 1);
279    }
280
281  /*  Now try to read back all the data.  If we fail due to either a
282     sigpipe or sigalrm (timeout), we will return the nil string.  */
283  {
284    char *pz = load_data (server_pair.pf_read);
285
286    if (pz == (char *) NULL)
287      {
288	close_server ();
289
290	if (retry)
291	  {
292	    retry = BOOL_FALSE;
293	    goto do_retry;
294	  }
295
296        fprintf (stderr, "CLOSING SHELL SERVER - command failure:\n\t%s\n",
297                 pz_cmd);
298        pz = xcalloc (1, 1);
299      }
300#ifdef DEBUG
301    fprintf( stderr, "run_shell command success:  %s\n", pz );
302#endif
303    return pz;
304  }
305}
306