]>
Commit | Line | Data |
---|---|---|
0083c904 BK |
1 | |
2 | /* | |
3 | * $Id: server.c,v 1.2 1998/12/16 21:19:16 law Exp $ | |
4 | * | |
5 | * Server Handling copyright 1992-1998 Bruce Korb | |
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, Bruce Korb gives permission for additional | |
24 | * uses of the text contained in his release of ServerHandler. | |
25 | * | |
26 | * The exception is that, if you link the ServerHandler library with other | |
27 | * files to produce an executable, this does not by itself cause the | |
28 | * resulting executable to be covered by the GNU General Public License. | |
29 | * Your use of that executable is in no way restricted on account of | |
30 | * linking the ServerHandler library code into it. | |
31 | * | |
32 | * This exception does not however invalidate any other reasons why | |
33 | * the executable file might be covered by the GNU General Public License. | |
34 | * | |
35 | * This exception applies only to the code released by Bruce Korb under | |
36 | * the name ServerHandler. If you copy code from other sources under the | |
37 | * General Public License into a copy of ServerHandler, as the General Public | |
38 | * License permits, the exception does not apply to the code that you add | |
39 | * in this way. To avoid misleading anyone as to the status of such | |
40 | * modified files, you must delete this exception notice from them. | |
41 | * | |
42 | * If you write modifications of your own for ServerHandler, it is your | |
43 | * choice whether to permit this exception to apply to your modifications. | |
44 | * If you do not wish that, delete this exception notice. | |
45 | */ | |
46 | ||
47 | #include <fcntl.h> | |
48 | #include <errno.h> | |
49 | #include <signal.h> | |
50 | #include <stdlib.h> | |
51 | #include <string.h> | |
52 | #include <ctype.h> | |
53 | #include <sys/param.h> | |
54 | ||
55 | #include "server.h" | |
56 | ||
57 | #ifdef DEBUG | |
58 | #define STATIC | |
59 | #else | |
60 | #define STATIC static | |
61 | #endif | |
62 | #ifndef tSCC | |
63 | #define tSCC static const char | |
64 | #endif | |
65 | #ifndef NUL | |
66 | #define NUL '\0' | |
67 | #endif | |
68 | ||
69 | STATIC bool readPipeTimeout; | |
70 | ||
71 | STATIC tpChar defArgs[] = | |
72 | {(char *) NULL, "-p", (char *) NULL}; | |
73 | STATIC tpfPair serverPair = | |
74 | {(FILE *) NULL, (FILE *) NULL}; | |
75 | STATIC pid_t serverId = NULLPROCESS; | |
76 | /* | |
77 | * Arbitrary text that should not be found in the shell output. | |
78 | * It must be a single line and appear verbatim at the start of | |
79 | * the terminating output line. | |
80 | */ | |
81 | tSCC zDone[] = "ShElL-OuTpUt-HaS-bEeN-cOmPlEtEd"; | |
82 | STATIC tpChar pCurDir = (char *) NULL; | |
83 | ||
84 | /* | |
85 | * chainOpen | |
86 | * | |
87 | * Given an FD for an inferior process to use as stdin, | |
88 | * start that process and return a NEW FD that that process | |
89 | * will use for its stdout. Requires the argument vector | |
90 | * for the new process and, optionally, a pointer to a place | |
91 | * to store the child's process id. | |
92 | */ | |
93 | int | |
94 | chainOpen (stdinFd, ppArgs, pChild) | |
95 | int stdinFd; | |
96 | tpChar *ppArgs; | |
97 | pid_t *pChild; | |
98 | { | |
99 | tFdPair stdoutPair = | |
100 | {-1, -1}; | |
101 | pid_t chId; | |
102 | char *pzCmd; | |
103 | ||
104 | /* | |
105 | * Create a pipe it will be the child process' stdout, | |
106 | * and the parent will read from it. | |
107 | */ | |
108 | if ((pipe ((int *) &stdoutPair) < 0)) | |
109 | { | |
110 | if (pChild != (pid_t *) NULL) | |
111 | *pChild = NOPROCESS; | |
112 | return -1; | |
113 | } | |
114 | ||
115 | /* | |
116 | * If we did not get an arg list, use the default | |
117 | */ | |
118 | if (ppArgs == (tpChar *) NULL) | |
119 | ppArgs = defArgs; | |
120 | ||
121 | /* | |
122 | * If the arg list does not have a program, | |
123 | * assume the "SHELL" from the environment, or, failing | |
124 | * that, then sh. Set argv[0] to whatever we decided on. | |
125 | */ | |
126 | if (pzCmd = *ppArgs, | |
127 | (pzCmd == (char *) NULL) || (*pzCmd == '\0')) | |
128 | { | |
129 | ||
130 | pzCmd = getenv ("SHELL"); | |
131 | if (pzCmd == (char *) NULL) | |
132 | pzCmd = "sh"; | |
133 | } | |
134 | #ifdef DEBUG_PRINT | |
135 | printf ("START: %s\n", pzCmd); | |
136 | { | |
137 | int idx = 0; | |
138 | while (ppArgs[++idx] != (char *) NULL) | |
139 | printf (" ARG %2d: %s\n", idx, ppArgs[idx]); | |
140 | } | |
141 | #endif | |
142 | /* | |
143 | * Call fork() and see which process we become | |
144 | */ | |
145 | chId = fork (); | |
146 | switch (chId) | |
147 | { | |
148 | case NOPROCESS: /* parent - error in call */ | |
149 | close (stdoutPair.readFd); | |
150 | close (stdoutPair.writeFd); | |
151 | if (pChild != (pid_t *) NULL) | |
152 | *pChild = NOPROCESS; | |
153 | return -1; | |
154 | ||
155 | default: /* parent - return opposite FD's */ | |
156 | if (pChild != (pid_t *) NULL) | |
157 | *pChild = chId; | |
158 | #ifdef DEBUG_PRINT | |
159 | printf ("for pid %d: stdin from %d, stdout to %d\n" | |
160 | "for parent: read from %d\n", | |
161 | chId, stdinFd, stdoutPair.writeFd, stdoutPair.readFd); | |
162 | #endif | |
163 | close (stdinFd); | |
164 | close (stdoutPair.writeFd); | |
165 | return stdoutPair.readFd; | |
166 | ||
167 | case NULLPROCESS: /* child - continue processing */ | |
168 | break; | |
169 | } | |
170 | ||
171 | /* | |
172 | * Close the pipe end handed back to the parent process | |
173 | */ | |
174 | close (stdoutPair.readFd); | |
175 | ||
176 | /* | |
177 | * Close our current stdin and stdout | |
178 | */ | |
179 | close (STDIN_FILENO); | |
180 | close (STDOUT_FILENO); | |
181 | ||
182 | /* | |
183 | * Make the fd passed in the stdin, and the write end of | |
184 | * the new pipe become the stdout. | |
185 | */ | |
186 | fcntl (stdoutPair.writeFd, F_DUPFD, STDOUT_FILENO); | |
187 | fcntl (stdinFd, F_DUPFD, STDIN_FILENO); | |
188 | ||
189 | if (*ppArgs == (char *) NULL) | |
190 | *ppArgs = pzCmd; | |
191 | ||
192 | execvp (pzCmd, ppArgs); | |
193 | fprintf (stderr, "Error %d: Could not execvp( '%s', ... ): %s\n", | |
194 | errno, pzCmd, strerror (errno)); | |
195 | exit (EXIT_PANIC); | |
196 | } | |
197 | ||
198 | ||
199 | /* | |
200 | * p2open | |
201 | * | |
202 | * Given a pointer to an argument vector, start a process and | |
203 | * place its stdin and stdout file descriptors into an fd pair | |
204 | * structure. The "writeFd" connects to the inferior process | |
205 | * stdin, and the "readFd" connects to its stdout. The calling | |
206 | * process should write to "writeFd" and read from "readFd". | |
207 | * The return value is the process id of the created process. | |
208 | */ | |
209 | pid_t | |
210 | p2open (pPair, ppArgs) | |
211 | tFdPair *pPair; | |
212 | tpChar *ppArgs; | |
213 | { | |
214 | pid_t chId; | |
215 | ||
216 | /* | |
217 | * Create a bi-directional pipe. Writes on 0 arrive on 1 | |
218 | * and vice versa, so the parent and child processes will | |
219 | * read and write to opposite FD's. | |
220 | */ | |
221 | if (pipe ((int *) pPair) < 0) | |
222 | return NOPROCESS; | |
223 | ||
224 | pPair->readFd = chainOpen (pPair->readFd, ppArgs, &chId); | |
225 | if (chId == NOPROCESS) | |
226 | close (pPair->writeFd); | |
227 | ||
228 | return chId; | |
229 | } | |
230 | ||
231 | ||
232 | /* | |
233 | * p2fopen | |
234 | * | |
235 | * Identical to "p2open()", except that the "fd"'s are "fdopen(3)"-ed | |
236 | * into file pointers instead. | |
237 | */ | |
238 | pid_t | |
239 | p2fopen (pfPair, ppArgs) | |
240 | tpfPair *pfPair; | |
241 | tpChar *ppArgs; | |
242 | { | |
243 | tFdPair fdPair; | |
244 | pid_t chId = p2open (&fdPair, ppArgs); | |
245 | ||
246 | if (chId == NOPROCESS) | |
247 | return chId; | |
248 | ||
249 | pfPair->pfRead = fdopen (fdPair.readFd, "r"); | |
250 | pfPair->pfWrite = fdopen (fdPair.writeFd, "w"); | |
251 | return chId; | |
252 | } | |
253 | ||
254 | ||
255 | /* | |
256 | * loadData | |
257 | * | |
258 | * Read data from a file pointer (a pipe to a process in this context) | |
259 | * until we either get EOF or we get a marker line back. | |
260 | * The read data are stored in a malloc-ed string that is truncated | |
261 | * to size at the end. Input is assumed to be an ASCII string. | |
262 | */ | |
263 | STATIC char * | |
264 | loadData (fp) | |
265 | FILE *fp; | |
266 | { | |
267 | char *pzText; | |
268 | size_t textSize; | |
269 | char *pzScan; | |
270 | char zLine[1024]; | |
271 | ||
272 | textSize = sizeof (zLine) * 2; | |
273 | pzScan = \ | |
274 | pzText = malloc (textSize); | |
275 | ||
276 | if (pzText == (char *) NULL) | |
277 | return pzText; | |
278 | ||
279 | for (;;) | |
280 | { | |
281 | size_t usedCt; | |
282 | ||
283 | alarm (10); | |
284 | readPipeTimeout = BOOL_FALSE; | |
285 | if (fgets (zLine, sizeof (zLine), fp) == (char *) NULL) | |
286 | break; | |
287 | ||
288 | if (strncmp (zLine, zDone, sizeof (zDone) - 1) == 0) | |
289 | break; | |
290 | ||
291 | strcpy (pzScan, zLine); | |
292 | pzScan += strlen (zLine); | |
293 | usedCt = (size_t) (pzScan - pzText); | |
294 | ||
295 | if (textSize - usedCt < sizeof (zLine)) | |
296 | { | |
297 | ||
298 | size_t off = (size_t) (pzScan - pzText); | |
299 | void *p; | |
300 | textSize += 4096; | |
301 | p = realloc ((void *) pzText, textSize); | |
302 | if (p == (void *) NULL) | |
303 | { | |
304 | fprintf (stderr, "Failed to get 0x%08X bytes\n", textSize); | |
305 | free ((void *) pzText); | |
306 | return (char *) NULL; | |
307 | } | |
308 | ||
309 | pzText = (char *) p; | |
310 | pzScan = pzText + off; | |
311 | } | |
312 | } | |
313 | ||
314 | alarm (0); | |
315 | if (readPipeTimeout) | |
316 | { | |
317 | free ((void *) pzText); | |
318 | return (char *) NULL; | |
319 | } | |
320 | ||
321 | while ((pzScan > pzText) && isspace (pzScan[-1])) | |
322 | pzScan--; | |
323 | *pzScan = NUL; | |
324 | return realloc ((void *) pzText, strlen (pzText) + 1); | |
325 | } | |
326 | ||
327 | ||
328 | /* | |
329 | * SHELL SERVER PROCESS CODE | |
330 | */ | |
331 | ||
332 | #ifdef DONT_HAVE_SIGSEND | |
333 | typedef enum | |
334 | { | |
335 | P_ALL, P_PID, P_GID, P_UID, P_PGID, P_SID, P_CID | |
336 | } | |
337 | idtype_t; | |
338 | typedef long id_t; | |
339 | ||
340 | STATIC int | |
341 | sigsend (idtype, id, sig) | |
342 | idtype_t idtype; | |
343 | id_t id; | |
344 | int sig; | |
345 | { | |
346 | switch (idtype) | |
347 | { | |
348 | case P_PID: | |
349 | kill ((pid_t) id, sig); | |
350 | break; | |
351 | ||
352 | case P_ALL: | |
353 | case P_GID: | |
354 | case P_UID: | |
355 | case P_PGID: | |
356 | case P_SID: | |
357 | case P_CID: | |
358 | errno = EINVAL; | |
359 | return -1; | |
360 | /*NOTREACHED */ | |
361 | } | |
362 | ||
363 | return 0; | |
364 | } | |
365 | #endif /* HAVE_SIGSEND */ | |
366 | ||
367 | ||
368 | STATIC void | |
369 | closeServer () | |
370 | { | |
371 | kill( (pid_t) serverId, SIGKILL); | |
372 | serverId = NULLPROCESS; | |
373 | fclose (serverPair.pfRead); | |
374 | fclose (serverPair.pfWrite); | |
375 | serverPair.pfRead = serverPair.pfWrite = (FILE *) NULL; | |
376 | } | |
377 | ||
378 | ||
379 | struct sigaction savePipeAction; | |
380 | struct sigaction saveAlrmAction; | |
381 | struct sigaction currentAction; | |
382 | ||
383 | STATIC void | |
384 | sigHandler (signo) | |
385 | int signo; | |
386 | { | |
387 | closeServer (); | |
388 | readPipeTimeout = BOOL_TRUE; | |
389 | } | |
390 | ||
391 | ||
392 | STATIC void | |
393 | serverSetup () | |
394 | { | |
395 | #ifndef SA_SIGINFO | |
396 | # define SA_SIGINFO 0 | |
397 | #else | |
398 | currentAction.sa_sigaction = | |
399 | #endif | |
400 | currentAction.sa_handler = sigHandler; | |
401 | currentAction.sa_flags = SA_SIGINFO; | |
402 | sigemptyset( ¤tAction.sa_mask ); | |
403 | ||
404 | sigaction( SIGPIPE, ¤tAction, &savePipeAction ); | |
405 | sigaction( SIGALRM, ¤tAction, &saveAlrmAction ); | |
406 | atexit( &closeServer ); | |
407 | ||
408 | fputs ("trap : INT\n", serverPair.pfWrite); | |
409 | fflush (serverPair.pfWrite); | |
410 | pCurDir = getcwd ((char *) NULL, MAXPATHLEN + 1); | |
411 | } | |
412 | ||
413 | ||
414 | char * | |
415 | runShell (pzCmd) | |
416 | const char *pzCmd; | |
417 | { | |
418 | tSCC zNil[] = ""; | |
419 | ||
420 | /* | |
421 | * IF the shell server process is not running yet, | |
422 | * THEN try to start it. | |
423 | */ | |
424 | if (serverId == NULLPROCESS) | |
425 | { | |
426 | serverId = p2fopen (&serverPair, defArgs); | |
427 | if (serverId > 0) | |
428 | serverSetup (); | |
429 | } | |
430 | ||
431 | /* | |
432 | * IF it is still not running, | |
433 | * THEN return the nil string. | |
434 | */ | |
435 | if (serverId <= 0) | |
436 | return (char *) zNil; | |
437 | ||
438 | /* | |
439 | * Make sure the process will pay attention to us, | |
440 | * send the supplied command, and then | |
441 | * have it output a special marker that we can find. | |
442 | */ | |
443 | fprintf (serverPair.pfWrite, "\\cd %s\n%s\n\necho\necho %s\n", | |
444 | pCurDir, pzCmd, zDone); | |
445 | fflush (serverPair.pfWrite); | |
446 | if (serverId == NULLPROCESS) | |
447 | return (char *) NULL; | |
448 | ||
449 | /* | |
450 | * Now try to read back all the data. If we fail due to either | |
451 | * a sigpipe or sigalrm (timeout), we will return the nil string. | |
452 | */ | |
453 | { | |
454 | char *pz = loadData (serverPair.pfRead); | |
455 | if (pz == (char *) NULL) | |
456 | { | |
457 | fprintf (stderr, "CLOSING SHELL SERVER - command failure:\n\t%s\n", | |
458 | pzCmd); | |
459 | closeServer (); | |
460 | pz = (char *) zNil; | |
461 | } | |
462 | return pz; | |
463 | } | |
464 | } |