1 /*
2  * Copyright (C) 2003  Sam Horrocks
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17  *
18  */
19 
20 #include "speedy.h"
21 
speedy_frontend_dispose(slotnum_t gslotnum,slotnum_t fslotnum)22 void speedy_frontend_dispose(slotnum_t gslotnum, slotnum_t fslotnum) {
23     if (fslotnum) {
24 	gr_slot_t *gslot = &FILE_SLOT(gr_slot, gslotnum);
25 
26 	speedy_slot_remove(fslotnum, &(gslot->fe_head), &(gslot->fe_tail));
27 	SLOT_FREE(fslotnum, "frontend (speedy_frontend_dispose)");
28     }
29 }
30 
speedy_frontend_remove_running(const slotnum_t fslotnum)31 void speedy_frontend_remove_running(const slotnum_t fslotnum) {
32     fe_slot_t *fslot = &FILE_SLOT(fe_slot, fslotnum);
33 
34     if (fslot->backend) {
35 	be_slot_t *bslot = &FILE_SLOT(be_slot, fslot->backend);
36 	if (bslot->fe_running == fslotnum)
37 	    bslot->fe_running = fslot->backend;
38     }
39     speedy_slot_remove(fslotnum, &(FILE_HEAD.fe_run_head), &(FILE_HEAD.fe_run_tail));
40     SLOT_FREE(fslotnum, "frontend (remove_running)");
41 }
42 
43 #ifdef SPEEDY_FRONTEND
44 
speedy_frontend_collect_status(const slotnum_t fslotnum,int * exit_on_sig,int * exit_val)45 int speedy_frontend_collect_status
46     (const slotnum_t fslotnum, int *exit_on_sig, int *exit_val)
47 {
48     fe_slot_t *fslot = &FILE_SLOT(fe_slot, fslotnum);
49 
50     if (fslot->backend && speedy_backend_dead(fslot->backend))
51 	speedy_backend_died(fslot->backend);
52 
53     if (fslot->backend == 0) {
54 	*exit_on_sig = fslot->exit_on_sig;
55 	*exit_val = fslot->exit_val;
56 	speedy_frontend_remove_running(fslotnum);
57 	return 1;
58     }
59     return 0;
60 }
61 
speedy_frontend_clean_running(void)62 void speedy_frontend_clean_running(void) {
63     /* See if we can kill some dead frontends in the fe_run list */
64     while (FILE_HEAD.fe_run_tail && speedy_frontend_dead(FILE_HEAD.fe_run_tail))
65 	speedy_frontend_remove_running(FILE_HEAD.fe_run_tail);
66 }
67 
68 
69 /*
70  * Signal handling routines
71  */
72 
73 #define NUMSIGS (sizeof(signum) / sizeof(int))
74 
75 static const int	signum[] = {SIGALRM};
76 static char		sig_setup_done;
77 static time_t		next_alarm;
78 static SigList		sl;
79 
sig_handler_teardown(int put_back_alarm)80 static void sig_handler_teardown(int put_back_alarm) {
81 
82     if (!sig_setup_done)
83 	return;
84 
85     alarm(0);
86 
87     speedy_sig_free(&sl);
88 
89     /* Put back alarm */
90     if (put_back_alarm && next_alarm) {
91 	next_alarm -= speedy_util_time();
92 	alarm(next_alarm > 0 ? next_alarm : 1);
93     }
94 
95     sig_setup_done = 0;
96 }
97 
sig_handler_setup(void)98 static void sig_handler_setup(void) {
99     sig_handler_teardown(1);
100 
101     /* Save alarm for later */
102     if ((next_alarm = alarm(0))) {
103 	next_alarm += speedy_util_time();
104     }
105 
106     speedy_sig_init(&sl, signum, NUMSIGS, SIG_BLOCK);
107 
108     sig_setup_done = 1;
109 }
110 
111 /*
112  * End of Signal handling routines
113  */
114 
115 #define BE_SUFFIX "_backend"
116 
117 /* Spawn the be_parent process */
be_parent_spawn(slotnum_t gslotnum)118 static void be_parent_spawn(slotnum_t gslotnum) {
119     int pid;
120     const char * const *argv;
121 
122     /* Get args for exec'ing backend */
123     argv = speedy_opt_exec_argv();
124 
125     /* Fork */
126     pid = fork();
127 
128     if (pid > 0) {
129 	/* Parent */
130 
131 	int child_status;
132 
133 	if (waitpid(pid, &child_status, 0) == -1)
134 	    speedy_util_die("wait");
135     }
136     else if (pid == 0) {
137 	/* Child */
138 
139 	/* Get rid of alarm handler and any alarms */
140 	sig_handler_teardown(0);
141 
142 	/* Unblock any signals due to file lock */
143 	speedy_file_fork_child();
144 
145 	/* Fork again */
146 	pid = fork();
147 
148 	if (pid == -1) {
149 	    speedy_util_exit(1,1);
150 	}
151 	else if (pid) {
152 	    /* Parent of Grandchild */
153 
154 	    /* We don't hold the lock on the temp file, but our parent does,
155 	     * and it's waiting for us to exit before proceeding, so it's
156 	     * safe to write to the file here
157 	     */
158 	    FILE_SLOT(gr_slot, gslotnum).be_parent = pid;
159 	    FILE_SLOT(gr_slot, gslotnum).be_starting = pid;
160 
161 	    speedy_util_exit(0,1);
162 	}
163 	else {
164 	    /* Grandchild */
165 
166 	    /* We should be in our own session */
167 	    setsid();
168 
169 	    /* Exec the backend */
170 	    speedy_util_execvp(argv[0], argv);
171 
172 	    /* Failed.  Try the original argv[0] + "_backend" */
173 	    {
174 		const char *orig_file = speedy_opt_orig_argv()[0];
175 		if (orig_file && *orig_file) {
176 		    char *fname;
177 
178 		    speedy_new(
179 			fname, strlen(orig_file)+sizeof(BE_SUFFIX)+1, char
180 		    );
181 		    sprintf(fname, "%s%s", orig_file, BE_SUFFIX);
182 		    speedy_util_execvp(fname, argv);
183 		}
184 	    }
185 	    speedy_util_die(argv[0]);
186 	}
187     } else {
188 	speedy_util_die("fork");
189     }
190 }
191 
192 /* Check on / spawn backends.  Should only be done by the fe at the
193  * head of the list (think 100+ fe's in the queue)
194  */
backend_check(slotnum_t gslotnum,int * did_spawn)195 static int backend_check(slotnum_t gslotnum, int *did_spawn) {
196     gr_slot_t *gslot = &FILE_SLOT(gr_slot, gslotnum);
197 
198     /* Don't spawn a backend while a backend is starting */
199     if (speedy_group_be_starting(gslotnum))
200 	return 1;
201 
202     /* If we already did this once, it didn't work */
203     if (*did_spawn)
204 	return 0;
205 
206     /* Start up a be_parent if necessary */
207     if (!gslot->be_parent)
208 	be_parent_spawn(gslotnum);
209 
210     /* Are we below the maxbackends limit? */
211     if (speedy_backend_below_maxbe(gslotnum)) {
212 
213 	/* Signal the be parent to start a new backend */
214 	if (speedy_group_start_be(gslotnum)) {
215 	    /* Let it start one before spawning again */
216 	    gslot->be_starting = gslot->be_parent;
217 	    *did_spawn = 1;
218 	}
219     } else {
220 	/* If we're above the maxbaceknds limit, we still need to ping the
221 	 * be parent to make sure it's alive.
222 	 */
223 	speedy_group_parent_sig(gslotnum, 0);
224     }
225     return 1;
226 }
227 
228 /* Go up the fe list, going to the next group if we're at the
229  * begininng of the list.  Wrap to the first group if we go off the end
230  * of the group list.  Worst case we wrap around and return ourself.
231  */
fe_prev(slotnum_t * gslotnum,slotnum_t * fslotnum)232 static void fe_prev(slotnum_t *gslotnum, slotnum_t *fslotnum) {
233     *fslotnum = speedy_slot_prev(*fslotnum);
234     while (!*fslotnum) {
235 	if (!(*gslotnum = speedy_slot_next(*gslotnum)) &&
236 	    !(*gslotnum = FILE_HEAD.group_head))
237 	{
238 	    DIE_QUIET("Group list or frontend lists are corrupt");
239 	}
240 	*fslotnum = FILE_SLOT(gr_slot, *gslotnum).fe_tail;
241     }
242 }
243 
frontend_check_prev(slotnum_t gslotnum,slotnum_t fslotnum)244 static void frontend_check_prev(slotnum_t gslotnum, slotnum_t fslotnum) {
245     fe_prev(&gslotnum, &fslotnum);
246 
247     while (speedy_frontend_dead(fslotnum)) {
248 	slotnum_t g_prev = gslotnum, f_prev = fslotnum;
249 
250 	/* Must do "prev" function while this slot/group is still valid */
251 	fe_prev(&g_prev, &f_prev);
252 
253 	/* This frontend is not running so dispose of it */
254 	speedy_frontend_dispose(gslotnum, fslotnum);
255 
256 	/* Try to remove this group if possible */
257 	speedy_group_cleanup(gslotnum);
258 
259 	/* If we wrapped around to ourself, then all done */
260 	if (f_prev == fslotnum)
261 	    break;
262 
263 	gslotnum = g_prev;
264 	fslotnum = f_prev;
265     }
266 }
267 
268 /* Check that the frontend in front of is running.  Also run backend check
269  * if we are the head frontend
270  */
frontend_ping(slotnum_t gslotnum,slotnum_t fslotnum,int * did_spawn)271 static int frontend_ping
272     (slotnum_t gslotnum, slotnum_t fslotnum, int *did_spawn)
273 {
274     /* Check the frontend previous to us.  This may remove it */
275     frontend_check_prev(gslotnum, fslotnum);
276 
277     /* If we're not the head of the list, then all done */
278     if (speedy_slot_prev(fslotnum))
279 	return 1;
280 
281     /* Do a check of backends.  Returns false if we cannot start be */
282     return backend_check(gslotnum, did_spawn);
283 }
284 
285 
286 /* Get a backend the hard-way - by queueing up
287 */
get_a_backend_hard(slotnum_t gslotnum,slotnum_t fslotnum,slotnum_t * bslotnum)288 static int get_a_backend_hard
289     (slotnum_t gslotnum, slotnum_t fslotnum, slotnum_t *bslotnum)
290 {
291     int file_changed, did_spawn = 0, spawn_working = 1, sent_sig;
292     *bslotnum = 0;
293 
294     /* Install sig handlers */
295     sig_handler_setup();
296 
297     /* Put ourself at the end of the fe queue */
298     speedy_slot_append(fslotnum,
299 	&(FILE_SLOT(gr_slot, gslotnum).fe_head),
300 	&(FILE_SLOT(gr_slot, gslotnum).fe_tail));
301 
302     while (1) {
303 	/* Send signals to frontends */
304 	speedy_group_sendsigs(gslotnum);
305 
306 	sent_sig = FILE_SLOT(fe_slot, fslotnum).sent_sig;
307 	FILE_SLOT(fe_slot, fslotnum).sent_sig = 0;
308 
309 	/* If our sent_sig flag is set, and there are be's for us to use ,
310 	 * then all done.
311 	*/
312 	if (sent_sig &&
313 	    (*bslotnum = speedy_backend_be_wait_get(gslotnum)))
314 	{
315 	    break;
316 	}
317 
318 	/* Check on frontends/backends running */
319 	spawn_working = frontend_ping(gslotnum, fslotnum, &did_spawn);
320 
321 	/* Frontend ping may have invalidated our group */
322 	if (!spawn_working || !speedy_group_isvalid(gslotnum))
323 	    break;
324 
325 	/* Unlock the file */
326 	speedy_file_set_state(FS_HAVESLOTS);
327 
328 	/* Set an alarm for one-second or so. */
329 	alarm(OPTVAL_BECHECKTIMEOUT);
330 
331 	/* Wait for a timeout or signal from backend */
332 	speedy_sig_wait(&sl);
333 
334 	/* Find out if our file changed.  Do this while unlocked */
335 	file_changed = speedy_script_changed();
336 
337 	/* Acquire lock.  If group bad or file changed, then done */
338 	if (!speedy_group_lock(gslotnum) || file_changed)
339 	    break;
340     }
341 
342     /* Remove our FE slot from the queue.  */
343     speedy_slot_remove(fslotnum,
344 	&(FILE_SLOT(gr_slot, gslotnum).fe_head),
345 	&(FILE_SLOT(gr_slot, gslotnum).fe_tail));
346 
347     /* Put sighandlers back to their original state */
348     sig_handler_teardown(1);
349 
350     return spawn_working;
351 }
352 
get_a_backend(slotnum_t fslotnum,slotnum_t * gslotnum)353 static int get_a_backend(slotnum_t fslotnum, slotnum_t *gslotnum) {
354     slotnum_t bslotnum = 0;
355     int spawn_working = 1;
356 
357     /* Locate the group for our script */
358     *gslotnum = speedy_script_find();
359 
360     /* Try to quickly grab a backend without queueing */
361     if (!FILE_SLOT(gr_slot, *gslotnum).fe_head)
362 	bslotnum = speedy_backend_be_wait_get(*gslotnum);
363 
364     /* If that failed, use the queue */
365     if (!bslotnum)
366 	spawn_working = get_a_backend_hard(*gslotnum, fslotnum, &bslotnum);
367 
368     /* Clean up the group if necessary */
369     speedy_group_cleanup(*gslotnum);
370 
371     FILE_SLOT(fe_slot, fslotnum).backend = bslotnum;
372     return spawn_working;
373 }
374 
375 
speedy_frontend_connect(int socks[NUMFDS],slotnum_t * fslotnum_p)376 int speedy_frontend_connect(int socks[NUMFDS], slotnum_t *fslotnum_p) {
377     static int did_clean;
378     int connected = 0, spawn_working = 1, sockets_open = 0;
379 
380     /* May need options from the #! line in the script.  This also
381      * opens the script file
382      */
383     speedy_opt_read_shbang();
384 
385     while (spawn_working && !connected) {
386 	slotnum_t gslotnum, bslotnum, fslotnum;
387 
388 	/* Create sockets in preparation for connect.  This may take a while,
389 	 * esp on FreeBSD, when it's out of sockets.
390 	 */
391 	if (!sockets_open++)
392 	    speedy_ipc_connect_prepare(socks);
393 
394 	/* Lock temp file */
395 	speedy_file_set_state(FS_CORRUPT);
396 
397 	/* Need to clean out the fe_run list, once per frontend execution */
398 	if (!did_clean++)
399 	    speedy_frontend_clean_running();
400 
401 	/* Allocate a frontend slot */
402 	fslotnum = SLOT_ALLOC("frontend (speedy_frontend_connect)");
403 	FILE_SLOT(fe_slot, fslotnum).pid = speedy_util_getpid();
404 
405 	/* Try to find a backend.  Bad return status if cannot spawn */
406 	spawn_working = get_a_backend(fslotnum, &gslotnum);
407 
408 	/* Did we get a backend slot to connect to? */
409 	if (spawn_working && (bslotnum = FILE_SLOT(fe_slot, fslotnum).backend))
410 	{
411 	    /* Try to connect to this backend. */
412 	    connected = speedy_ipc_connect(bslotnum, socks);
413 
414 	    if (!connected) {
415 		/* Failed to connect */
416 		sockets_open = 0;
417 
418 		/* Make sure to get rid of backend record */
419 		speedy_backend_dispose(gslotnum, bslotnum);
420 	    }
421 	} else {
422 	    connected = 0;
423 	}
424 
425 	if (fslotnum_p)
426 	    *fslotnum_p = 0;
427 
428 	if (connected) {
429 	    be_slot_t *bslot = &FILE_SLOT(be_slot, bslotnum);
430 
431 	    /* See if caller wants to hold onto fslot for exit status */
432 	    if (fslotnum_p) {
433 		*fslotnum_p = fslotnum;
434 
435 		/* Link our frontend to that backend */
436 		bslot->fe_running = fslotnum;
437 
438 		/* Add our frontend to the list of running fe's */
439 		speedy_slot_insert(fslotnum, &(FILE_HEAD.fe_run_head), &(FILE_HEAD.fe_run_tail));
440 	    } else {
441 		/* Fe_running must be non-zero while backend is running */
442 		bslot->fe_running = bslotnum;
443 	    }
444 
445 	    /* Prevent further spawns until this backend starts to run */
446 	    FILE_SLOT(gr_slot, gslotnum).be_starting = bslot->pid;
447 	}
448 
449 	if (fslotnum_p && *fslotnum_p) {
450 	    speedy_file_set_state(FS_HAVESLOTS);
451 	} else {
452 	    /* Jettison this frontend */
453 	    SLOT_FREE(fslotnum, "frontend (speedy_frontend_connect)");
454 	    speedy_file_set_state(FS_OPEN);
455 	}
456     }
457     if (sockets_open && !connected) {
458 	int i;
459 	for (i = 0; i < NUMFDS; ++i)
460 	    close(socks[i]);
461     }
462     speedy_script_close();
463     return spawn_working;
464 }
465 
466 /* Return size of the buffer needed to send a string of the given length */
467 #define STR_BUFSIZE(l) (1 + (l >= MAX_SHORT_STR ? sizeof(int) : 0) + l)
468 
469 /* Add something to the buffer */
470 #define BUF_ENLARGE(b,l) \
471     if ((b)->len + (l) > (b)->alloced) \
472 	enlarge_buf((b),(l))
473 
474 #define ADD2(b,s,l) \
475     speedy_memcpy((b)->buf + (b)->len, (s), (l)); \
476     (b)->len += (l)
477 
478 #define ADD(b,s,l) BUF_ENLARGE(b,l); ADD2(b,s,l)
479 
480 #define ADDCHAR2(b,c) ((b)->buf)[(b)->len++] = (char)c
481 
482 #define ADDCHAR(b,c) BUF_ENLARGE(b,1); ADDCHAR2(b,c)
483 
484 #define ADD_DEVINO(b,stbuf) \
485     do { \
486 	SpeedyDevIno devino = speedy_util_stat_devino(stbuf); \
487 	ADD((b), &devino, sizeof(SpeedyDevIno)); \
488     } while (0)
489 
490 #define ADD_STRING(b, s, l) \
491     do { \
492 	if ((l) >= MAX_SHORT_STR) { \
493 	    BUF_ENLARGE(b, (sizeof(int)+1)); \
494 	    ADDCHAR2(b, MAX_SHORT_STR); \
495 	    ADD2(b, &(l), sizeof(int)); \
496 	} else { \
497 	    ADDCHAR(b, l); \
498 	} \
499 	ADD(b, s, l); \
500     } while (0)
501 
enlarge_buf(SpeedyBuf * b,int min_to_add)502 static void enlarge_buf(SpeedyBuf *b, int min_to_add) {
503     int new_size = b->alloced * SPEEDY_REALLOC_MULT;
504     int min_size = b->len + min_to_add;
505     if (new_size < min_size)
506 	new_size = min_size;
507     b->alloced = new_size;
508     speedy_renew(b->buf, new_size, char);
509 }
510 
alloc_buf(SpeedyBuf * b,int bytes)511 static void alloc_buf(SpeedyBuf *b, int bytes) {
512     b->len = 0;
513     b->alloced = bytes;
514     if (bytes)
515 	speedy_new(b->buf, bytes, char);
516     else
517 	b->buf = NULL;
518 }
519 
520 /* Add a string to the buffer */
add_string(SpeedyBuf * b,const char * s,int l)521 static void add_string(SpeedyBuf *b, const char *s, int l) {
522     ADD_STRING(b, s, l);
523 }
524 
525 /* Copy a block of strings into the buffer,  */
526 /* Profiling shows this is the top function for cpu time */
add_strings(register SpeedyBuf * b,register const char * const * p)527 static void add_strings(register SpeedyBuf *b, register const char * const * p)
528 {
529     int l;
530     register const char *s;
531 
532     /* Add strings in p array */
533     for (; (s = *p); ++p) {
534 	if ((l = strlen(s))) {
535 	    ADD_STRING(b, s, l);
536 	}
537     }
538 
539     /* Terminate with zero-length string */
540     ADDCHAR(b, 0);
541 }
542 
speedy_frontend_mkenv(const char * const * envp,const char * const * scr_argv,int min_alloc,SpeedyBuf * sb,int script_has_cwd)543 void speedy_frontend_mkenv(
544     const char * const * envp, const char * const * scr_argv, int min_alloc,
545     SpeedyBuf *sb, int script_has_cwd
546 )
547 {
548     struct stat dir_stat;
549     const char *script_fname = speedy_opt_script_fname();
550 
551     if (!script_fname)
552 	speedy_script_missing();
553 
554     /* Create buffer */
555 #ifdef SPEEDY_EFENCE
556     alloc_buf(sb, min_alloc);
557 #else
558     alloc_buf(sb, max(512, min_alloc));
559 #endif
560 
561     /* Add env and argv */
562     add_strings(sb, envp);
563     add_strings(sb, scr_argv+1);
564 
565     /* Put script filename into buffer */
566     add_string(sb, script_fname, strlen(script_fname));
567 
568     /* Put script device/inode into buffer */
569     ADD_DEVINO(sb, speedy_script_getstat());
570 
571     /* Handle passing over cwd */
572     if (script_has_cwd) {
573 	ADDCHAR(sb, SPEEDY_CWD_IN_SCRIPT);
574     }
575     else if (stat(".", &dir_stat) != -1) {
576 	ADDCHAR(sb, SPEEDY_CWD_DEVINO);
577 	ADD_DEVINO(sb, &dir_stat);
578     } else {
579 	ADDCHAR(sb, SPEEDY_CWD_UNKNOWN);
580     }
581 }
582 
speedy_frontend_proto2(int err_sock,int first_byte)583 void speedy_frontend_proto2(int err_sock, int first_byte) {
584     int n, cwd_len, buflen;
585     char *bp, *cwd;
586     PollInfo pi;
587     SpeedyBuf b;
588 
589     if (!first_byte)
590 	return;
591 
592     /* Get current directory */
593     cwd = speedy_util_getcwd();
594     cwd_len = cwd ? strlen(cwd) : 0;
595 
596     /* Create buffer for the string */
597     alloc_buf(&b, STR_BUFSIZE(cwd_len));
598 
599     /* Put cwd into the buffer */
600     if (cwd) {
601 	add_string(&b, cwd, cwd_len);
602 	speedy_free(cwd);
603     } else {
604 	add_string(&b, "", 0);
605     }
606 
607     /* Send it over */
608     speedy_poll_init(&pi, err_sock);
609     bp = b.buf;
610     buflen = b.len;
611     while (1) {
612 
613 	/* TEST - send over one byte at a time to test the poll */
614 	/* n = write(err_sock, bp, 1); */
615 
616 	n = write(err_sock, bp, buflen);
617 	if (n == -1 && SP_NOTREADY(errno))
618 	    n = 0;
619 	if (n == -1)
620 	    break;
621 
622 	if (!(buflen -= n))
623 	    break;
624 	bp += n;
625 
626 	/* Do this instead of bothering to change socket to non-blocking */
627 	speedy_poll_quickwait(&pi, err_sock, SPEEDY_POLLOUT, 1000);
628     }
629     speedy_poll_free(&pi);
630     speedy_free(b.buf);
631 
632     shutdown(err_sock, 1);
633 }
634 
635 #endif /* SPEEDY_FRONTEND */
636