ksgi

Форк
0
/
kfcgi.c 
1135 строк · 25.0 Кб
1
/*	$Id$ */
2
/*
3
 * Copyright (c) 2015--2016 Kristaps Dzonsons <kristaps@bsd.lv>
4
 *
5
 * Permission to use, copy, modify, and distribute this software for any
6
 * purpose with or without fee is hereby granted, provided that the above
7
 * copyright notice and this permission notice appear in all copies.
8
 *
9
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
 */
17
#include "config.h"
18

19
#include <sys/socket.h>
20
#include <sys/stat.h>
21
#include <sys/un.h>
22
#include <sys/wait.h>
23

24
#include <assert.h>
25
#include <errno.h>
26
#include <fcntl.h>
27
#include <getopt.h>
28
#include <limits.h>
29
#include <pwd.h>
30
#include <poll.h>
31
#include <signal.h>
32
#include <stdarg.h>
33
#include <stdio.h>
34
#include <stdlib.h>
35
#include <stdint.h>
36
#include <string.h>
37
#include <syslog.h>
38
#include <time.h>
39
#include <unistd.h>
40

41
/*
42
 * This is used by the "variable pool" implementation to keep track of
43
 * children, their input descriptor, and their active work.
44
 */
45
struct	worker {
46
	int	 fd; /* active request fd or -1 */
47
	int	 ctrl; /* control socket */
48
	pid_t	 pid; /* process */
49
	time_t	 last; /* last deschedule or 0 if never */
50
	uint64_t cookie;
51
};
52

53
/*
54
 * Whether we're supposed to stop or whether we've had a child exit.
55
 */
56
static	volatile sig_atomic_t stop = 0;
57
static	volatile sig_atomic_t chld = 0;
58
static	volatile sig_atomic_t hup = 0;
59

60
static	int verbose = 0;
61

62
static 	void dbg(const char *fmt, ...) 
63
		__attribute__((format(printf, 1, 2)));
64

65
static void
66
sighandlehup(int sig)
67
{
68

69
	hup = 1;
70
}
71

72
static void
73
sighandlestop(int sig)
74
{
75

76
	stop = 1;
77
}
78

79
static void
80
sighandlechld(int sig)
81
{
82

83
	chld = 1;
84
}
85

86
static void
87
dbg(const char *fmt, ...) 
88
{
89
	va_list	 ap;
90

91
	if (0 == verbose)
92
		return;
93
	va_start(ap, fmt);
94
	vsyslog(LOG_DEBUG, fmt, ap);
95
	va_end(ap);
96
}
97

98
/*
99
 * Fully write a file descriptor and the non-optional buffer.
100
 * Return 0 on failure, 1 on success.
101
 */
102
static int
103
fullwritefd(int fd, int sendfd, void *b, size_t bsz)
104
{
105
	struct msghdr	 msg;
106
	int		 rc;
107
	char		 buf[CMSG_SPACE(sizeof(fd))];
108
	struct iovec 	 io;
109
	struct cmsghdr	*cmsg;
110
	struct pollfd	 pfd;
111

112
	assert(bsz && bsz <= 256);
113

114
	memset(buf, 0, sizeof(buf));
115
	memset(&msg, 0, sizeof(struct msghdr));
116
	memset(&io, 0, sizeof(struct iovec));
117

118
	io.iov_base = b;
119
	io.iov_len = bsz;
120

121
	msg.msg_iov = &io;
122
	msg.msg_iovlen = 1;
123
	msg.msg_control = buf;
124
	msg.msg_controllen = sizeof(buf);
125

126
	cmsg = CMSG_FIRSTHDR(&msg);
127
	cmsg->cmsg_level = SOL_SOCKET;
128
	cmsg->cmsg_type = SCM_RIGHTS;
129
	cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
130

131
	*((int *)CMSG_DATA(cmsg)) = sendfd;
132

133
	msg.msg_controllen = cmsg->cmsg_len;
134

135
	pfd.fd = fd;
136
	pfd.events = POLLOUT;
137
again:
138
	if ((rc = poll(&pfd, 1, -1)) < 0) {
139
		syslog(LOG_ERR, "poll: passing "
140
			"connection to worker: %m");
141
		return(0);
142
	} else if (0 == rc) {
143
		syslog(LOG_WARNING, "poll: passing "
144
			"connection to worker: timeout!?");
145
		goto again;
146
	} else if ( ! (POLLOUT & pfd.revents)) {
147
		syslog(LOG_ERR, "poll: passing "
148
			"connection to worker: disconnect");
149
		return(0);
150
	} else if (sendmsg(fd, &msg, 0) < 0) {
151
		syslog(LOG_ERR, "sendmsg: passing "
152
			"connection to worker: %m");
153
		return(0);
154
	}
155
	return(1);
156
}
157

158
/*
159
 * Fully read a buffer of size "bufsz".
160
 * Returns 0 on failure (soft and hard), 1 on success.
161
 */
162
static int
163
fullread(int fd, void *buf, size_t bufsz)
164
{
165
	ssize_t	 	 ssz;
166
	size_t	 	 sz;
167
	struct pollfd	 pfd;
168
	int		 rc;
169

170
	pfd.fd = fd;
171
	pfd.events = POLLIN;
172

173
	for (sz = 0; sz < bufsz; sz += (size_t)ssz) {
174
		if ((rc = poll(&pfd, 1, -1)) < 0) {
175
			syslog(LOG_ERR, "poll: receiving "
176
				"ack from worker: %m");
177
			return(0);
178
		} else if (0 == rc) {
179
			syslog(LOG_WARNING, "poll: receiving "
180
				"ack from worker: timeout");
181
			ssz = 0;
182
			continue;
183
		} else if ( ! (POLLIN & pfd.revents)) {
184
			syslog(LOG_ERR, "poll: receiving "
185
				"ack from worker: disconnect");
186
			return(0);
187
		} else if ((ssz = read(fd, buf + sz, bufsz - sz)) < 0) {
188
			syslog(LOG_ERR, "read: receiving "
189
				"ack from worker: %m");
190
			return(0);
191
		} else if (0 == ssz && sz > 0) {
192
			syslog(LOG_ERR, "read: receiving "
193
				"ack from worker: short read");
194
			return(0);
195
		} else if (0 == ssz && sz == 0) {
196
			syslog(LOG_ERR, "read: receiving "
197
				"ack from worker: EOF");
198
			return(0);
199
		} else if (sz > SIZE_MAX - (size_t)ssz) {
200
			syslog(LOG_ERR, "read: receiving "
201
				"ack from worker: overflow");
202
			return(0);
203
		}
204
	}
205

206
	return(1);
207
}
208

209
/*
210
 * Create a non-blocking socket pair.
211
 * Remove 0 on failure, 1 on success.
212
 */
213
static int
214
xsocketpair(int *sock)
215
{
216
	int	 rc, fl1, fl2;
217

218
	sock[0] = sock[1] = -1;
219
	rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sock);
220
	if (-1 == rc)
221
		syslog(LOG_ERR, "socketpair: "
222
			"connection to worker: %m");
223
	else if (-1 == (fl1 = fcntl(sock[0], F_GETFL, 0)))
224
		syslog(LOG_ERR, "fcntl: F_GETFL: "
225
			"connection to worker: %m");
226
	else if (-1 == (fl2 = fcntl(sock[1], F_GETFL, 0)))
227
		syslog(LOG_ERR, "fcntl: F_GETFL: "
228
			"connection to worker: %m");
229
	else if (-1 == fcntl(sock[0], F_SETFL, fl1 | O_NONBLOCK))
230
		syslog(LOG_ERR, "fcntl: F_SETFL: "
231
			"connection to worker: %m");
232
	else if (-1 == fcntl(sock[1], F_SETFL, fl2 | O_NONBLOCK))
233
		syslog(LOG_ERR, "fcntl: F_SETFL: "
234
			"connection to worker: %m");
235
	else
236
		return(1);
237

238
	if (-1 != sock[0])
239
		close(sock[0]);
240
	if (-1 != sock[1])
241
		close(sock[1]);
242

243
	return(0);
244
}
245

246
/*
247
 * Start a worker for the variable pool.
248
 */
249
static int
250
varpool_start(struct worker *w, const struct worker *ws, 
251
	size_t wsz, int fd, char **nargv)
252
{
253
	int	 pair[2];
254
	char	 buf[64];
255
	size_t	 i;
256

257
	w->ctrl = w->fd = -1;
258
	w->pid = -1;
259

260
	if ( ! xsocketpair(pair))
261
		return(0);
262
	w->ctrl = pair[0];
263

264
	if (-1 == (w->pid = fork())) {
265
		syslog(LOG_ERR, "fork: worker: %m");
266
		close(pair[0]);
267
		close(pair[1]);
268
		w->ctrl = -1;
269
		return(0);
270
	} else if (0 == w->pid) {
271
		/* 
272
		 * Close out all current descriptors.
273
		 * Note that we're a "new-mode" FastCGI manager to the
274
		 * child via our environment variable.
275
		 */
276
		for (i = 0; i < wsz; i++)
277
			if (-1 != ws[i].ctrl && 
278
			    -1 == close(ws[i].ctrl))
279
				syslog(LOG_ERR, "close: "
280
					"worker cleanup: %m");
281

282
		close(fd);
283
		snprintf(buf, sizeof(buf), "%d", pair[1]);
284
		setenv("FCGI_LISTENSOCK_DESCRIPTORS", buf, 1);
285
		execv(nargv[0], nargv);
286
		syslog(LOG_ERR, "execv: %s: %m", nargv[0]);
287
		_exit(EXIT_FAILURE);
288
	}
289

290
	/* Close the child descriptor. */
291
	if (-1 == close(pair[1])) {
292
		syslog(LOG_ERR, "close: worker-%u pipe: %m", w->pid);
293
		return(0);
294
	}
295

296
	dbg("worker-%u: started", w->pid);
297
	return(1);
298
}
299

300
/*
301
 * A variable-sized pool of web application clients.
302
 * A minimum of "wsz" applications are always running, and will grow to
303
 * "maxwsz" at which point no new connections are accepted.
304
 * This is an EXPERIMENTAL extension.
305
 */
306
static int
307
varpool(size_t wsz, size_t maxwsz, time_t waittime,
308
	int fd, const char *sockpath, char *argv[])
309
{
310
	struct worker	*ws;
311
	struct worker	*slough;
312
	size_t		 pfdsz, opfdsz, pfdmaxsz, i, j, minwsz,
313
			 sloughsz, sloughmaxsz;
314
	int		 rc, exitcode, afd, accepting;
315
	struct pollfd	*pfd, *opfd;
316
	struct sockaddr_storage ss;
317
	socklen_t	 sslen;
318
	void		*pp;
319
	time_t		 t;
320
	sigset_t	 set;
321
	uint64_t	 cookie;
322

323
	dbg("YOU ARE RUNNING VARIABLE MODE AT YOUR OWN RISK.");
324

325
	signal(SIGCHLD, sighandlechld);
326
	signal(SIGTERM, sighandlestop);
327
	signal(SIGHUP, sighandlehup);
328
	sigemptyset(&set);
329
	sigaddset(&set, SIGCHLD);
330
	sigaddset(&set, SIGTERM);
331
	sigaddset(&set, SIGHUP);
332
	sigprocmask(SIG_BLOCK, &set, NULL);
333

334
again:
335
	stop = chld = hup = 0;
336

337
	/* 
338
	 * Allocate worker array, polling descriptor array, and slough
339
	 * array (exiting workers).
340
	 * Set our initial exit code and our acceptance status.
341
	 */
342
	minwsz = wsz;
343
	ws = calloc(wsz, sizeof(struct worker));
344
	sloughmaxsz = (maxwsz - minwsz) * 2;
345
	slough = calloc(sloughmaxsz, sizeof(struct worker));
346
	pfdmaxsz = wsz + 1;
347
	pfd = calloc(pfdmaxsz, sizeof(struct pollfd));
348
	exitcode = 0;
349

350
	if (NULL == ws || NULL == pfd || NULL == slough) {
351
		/* Serious problems. */
352
		syslog(LOG_ERR, "calloc: initialisation: %m");
353
		close(fd);
354
		unlink(sockpath);
355
		free(ws);
356
		free(slough);
357
		free(pfd);
358
		return(0);
359
	}
360

361
	/* Zeroth pfd is always the domain socket. */
362
	pfd[0].fd = fd;
363
	pfd[0].events = POLLIN;
364

365
	/* Guard against bad de-allocation. */
366
	for (i = 0; i < wsz; i++) {
367
		ws[i].ctrl = ws[i].fd = -1;
368
		ws[i].pid = -1;
369
	}
370

371
	sloughsz = 0;
372
	accepting = 1;
373
	pfdsz = 1;
374

375
	/*
376
	 * Start up the [initial] worker processes.
377
	 * We'll later spin up workers when we need them.
378
	 * If these die immediately, we'll find out below when we
379
	 * unblock our signals.
380
	 */
381
	for (i = 0; i < wsz; i++)
382
		if ( ! varpool_start(&ws[i], ws, wsz, fd, argv))
383
			goto out;
384
pollagain:
385
	/*
386
	 * Main part.
387
	 * Poll on our control socket (pfd[0]) and the children that
388
	 * have active connections.
389
	 * If we're not accepting new connections, avoid polling on the
390
	 * first entry by offsetting.
391
	 * We have a timeout if we get a termination signal whilst
392
	 * waiting.
393
	 */
394
	opfd = accepting ? pfd : pfd + 1;
395
	opfdsz = accepting ? pfdsz : pfdsz - 1;
396

397
	sigprocmask(SIG_UNBLOCK, &set, NULL);
398
	rc = poll(opfd, opfdsz, 1000);
399
	sigprocmask(SIG_BLOCK, &set, NULL);
400

401
	/* Did an error occur? */
402
	if (rc < 0 && EINTR != errno) {
403
		syslog(LOG_ERR, "poll: main event: %m");
404
		goto out;
405
	} 
406

407
	if (stop) {
408
		/* 
409
		 * If we're being requested to stop, go to exit now. 
410
		 * We'll immediately kill off our children and wait.
411
		 */
412
		dbg("servicing exit request");
413
		exitcode = 1;
414
		goto out;
415
	} else if (chld) {
416
		/*
417
		 * A child has exited.
418
		 * This can mean one of two things: either a worker has
419
		 * exited abnormally or one of the "sloughed" workers
420
		 * has finished its exit.
421
		 */
422
		if (0 == sloughsz) {
423
			syslog(LOG_ERR, "worker unexpectedly exited");
424
			goto out;
425
		}
426

427
		/* Look at the running children. */
428
		for (i = 0; i < wsz; i++) {
429
			assert(-1 != ws[i].pid);
430
			rc = waitpid(ws[i].pid, NULL, WNOHANG);
431
			if (rc < 0) {
432
				syslog(LOG_ERR, "wait: worker-%u "
433
					"check: %m", ws[i].pid);
434
				goto out;
435
			} else if (0 == rc)
436
				continue;
437
			/*
438
			 * We found a process that has already exited.
439
			 * Close its fd as well so that we know that the
440
			 * pid of -1 means it's completely finished.
441
			 */
442
			syslog(LOG_ERR, "worker-%u "
443
				"unexpectedly exited", ws[i].pid);
444
			if (-1 == close(ws[i].ctrl))
445
				syslog(LOG_ERR, "close: worker-%u "
446
					"control socket: %m",
447
					ws[wsz - 1].pid);
448
			ws[i].pid = -1;
449
			goto out;
450
		}
451

452
		/* Ok... an exiting child can be reaped. */
453
		chld = 0;
454
		for (i = 0; i < sloughsz; ) {
455
			rc = waitpid(slough[i].pid, NULL, WNOHANG);
456
			if (0 == rc) {
457
				i++;
458
				continue;
459
			} else if (rc < 0) {
460
				syslog(LOG_ERR, "wait: sloughed "
461
					"worker-%u check: %m", 
462
					slough[i].pid);
463
				goto out;
464
			}
465
			dbg("slough: releasing worker-%u\n", 
466
				slough[i].pid);
467
			if (i < sloughsz - 1)
468
				slough[i] = slough[sloughsz - 1];
469
			sloughsz--;
470
		}
471
	} else if (hup) {
472
		dbg("servicing restart request");
473
		goto out;
474
	}
475

476
	/*
477
	 * See if we should reduce our pool size.
478
	 * We only do this if we've grown beyond the minimum.
479
	 */
480
	if (wsz > minwsz) {
481
		t = time(NULL);
482
		/*
483
		 * We only reduce if the last worker is (1) not running
484
		 * and (2) was last descheduled a while ago.
485
		 */
486
		assert(-1 != ws[wsz - 1].fd || ws[wsz - 1].last);
487
		assert(wsz > 1);
488

489
		if (-1 == ws[wsz - 1].fd &&
490
		    t - ws[wsz - 1].last > waittime) {
491
			assert(-1 != ws[wsz - 1].ctrl);
492
			assert(-1 != ws[wsz - 1].pid);
493
			assert(-1 == ws[wsz - 1].fd);
494
			assert(-1 == pfd[pfdmaxsz - 1].fd);
495

496
			if (sloughsz >= sloughmaxsz) {
497
				syslog(LOG_ERR, "slough pool "
498
					"maximum size reached");
499
				goto out;
500
			}
501
			
502
			/* Close down the worker in the usual way. */
503
			if (-1 == close(ws[wsz - 1].ctrl)) {
504
				syslog(LOG_ERR, "close: worker-%u "
505
					"control socket: %m",
506
					ws[wsz - 1].pid);
507
				goto out;
508
			} 
509
			if (-1 == kill(ws[wsz - 1].pid, SIGTERM)) {
510
				syslog(LOG_ERR, "kill: worker-%u: %m",
511
					ws[wsz - 1].pid);
512
				goto out;
513
			}
514

515
			/* 
516
			 * Append the dying client to the slough array,
517
			 * since workers may take time to die.
518
			 */
519
			dbg("slough: acquiring worker-%u\n", 
520
				ws[wsz - 1].pid);
521
			slough[sloughsz++] = ws[wsz - 1];
522

523
			/* Reallocations. */
524
			pp = reallocarray(ws, wsz - 1, 
525
				sizeof(struct worker));
526
			if (NULL == pp) {
527
				syslog(LOG_ERR, "reallocarray: "
528
					"worker array: %m");
529
				goto out;
530
			}
531
			ws = pp;
532
			wsz--;
533
			pp = reallocarray(pfd, pfdmaxsz - 1,
534
				sizeof(struct pollfd));
535
			if (NULL == pp) {
536
				syslog(LOG_ERR, "reallocarray: "
537
					"descriptor array: %m");
538
				goto out;
539
			}
540
			pfd = pp;
541
			pfdmaxsz--;
542
		}
543
	}
544
	
545
	if (0 == rc)
546
		goto pollagain;
547

548
	/*
549
	 * Now we see which of the workers has exited.
550
	 * We do this until we've processed all of them.
551
	 */
552
	assert(rc > 0);
553
	for (i = 1; i < pfdsz && rc > 0; ) {
554
		if (POLLHUP & pfd[i].revents ||
555
		    POLLERR & pfd[i].revents) {
556
			syslog(LOG_ERR, "poll: worker disconnect");
557
			goto out;
558
		} else if ( ! (POLLIN & pfd[i].revents)) {
559
			i++;
560
			continue;
561
		} 
562

563
		/* 
564
		 * Read the "identifier" that the child process gives
565
		 * to us.
566
		 */
567
		if ( ! fullread(pfd[i].fd, &cookie, sizeof(uint64_t)))
568
			goto out;
569

570
		/*
571
		 * First, look up the worker that's now free.
572
		 * Mark its active descriptor as free.
573
		 * TODO: use a queue of "working" processes to prevent
574
		 * this from being so expensive.
575
		 */
576
		for (j = 0; j < wsz; j++)
577
			if (ws[j].cookie == cookie)
578
				break;
579

580
		if (j == wsz) {
581
			syslog(LOG_ERR, "poll: bad worker response");
582
			goto out;
583
		}
584

585
		dbg("worker-%u: release %d", ws[j].pid, ws[j].fd);
586

587
		/*
588
		 * Close the descriptor (that we still hold) and mark
589
		 * this worker as no longer working.
590
		 */
591
		rc--;
592
		close(ws[j].fd);
593
		if (0 == accepting) {
594
			accepting = 1;
595
			dbg("rate-limiting: disabled");
596
		}
597
		ws[j].fd = -1;
598
		ws[j].last = time(NULL);
599

600
		/*
601
		 * Now, clear the active descriptor from the file
602
		 * descriptor array.
603
		 * Do so by flipping it into the last slot then
604
		 * truncating the array size.
605
		 * Obviously, we only do this if we're not the current
606
		 * end of array...
607
		 */
608
		if (pfdsz - 1 != i)
609
			pfd[i] = pfd[pfdsz - 1];
610
		pfd[pfdsz - 1].fd = -1;
611
		pfdsz--;
612
	}
613

614
	if (0 == accepting)
615
		goto pollagain;
616

617
	if (POLLHUP & pfd[0].revents) {
618
		syslog(LOG_ERR, "poll: control hangup");
619
		goto out;
620
	} else if (POLLERR & pfd[0].revents) {
621
		syslog(LOG_ERR, "poll: control error?");
622
		goto out;
623
	} else if ( ! (POLLIN & pfd[0].revents))
624
		goto pollagain;
625

626
	/* 
627
	 * We have a new request.
628
	 * First, see if we need to allocate more workers.
629
	 */
630
	if (pfdsz == pfdmaxsz) {
631
		if (wsz + 1 > maxwsz) {
632
			accepting = 0;
633
			dbg("rate-limiting: enabled");
634
			goto pollagain;
635
		}
636
		pp = reallocarray(pfd, pfdmaxsz + 1,
637
			sizeof(struct pollfd));
638
		if (NULL == pp) {
639
			syslog(LOG_ERR, "reallocarray: workers: %m");
640
			goto out;
641
		}
642
		pfd = pp;
643
		memset(&pfd[pfdmaxsz], 0, sizeof(struct pollfd));
644
		pfd[pfdmaxsz].fd = -1;
645

646
		pp = reallocarray(ws, wsz + 1,
647
			sizeof(struct worker));
648
		if (NULL == pp) {
649
			syslog(LOG_ERR, "reallocarray: descriptors: %m");
650
			goto out;
651
		}
652
		ws = pp;
653
		memset(&ws[wsz], 0, sizeof(struct worker));
654

655
		if ( ! varpool_start(&ws[wsz], ws, wsz + 1, fd, argv))
656
			goto out;
657
		pfdmaxsz++;
658
		wsz++;
659
	} 
660

661
	/*
662
	 * Actually accept the socket.
663
	 * Don't do anything with it, however.
664
	 */
665
	sslen = sizeof(ss);
666
	afd = accept(fd, (struct sockaddr *)&ss, &sslen);
667
	if (afd < 0) {
668
		if (EAGAIN == errno || 
669
		    EWOULDBLOCK == errno)
670
			goto pollagain;
671
		syslog(LOG_ERR, "accept: new connection: %m");
672
		goto out;
673
	} 
674

675
	/*
676
	 * Look up the next unavailable worker.
677
	 */
678
	for (i = 0; i < wsz; i++)
679
		if (-1 == ws[i].fd) 
680
			break;
681

682
	assert(i < wsz);
683
	ws[i].fd = afd;
684
#if HAVE_ARC4RANDOM
685
	ws[i].cookie = arc4random();
686
#else
687
	ws[i].cookie = random();
688
#endif
689
	dbg("worker-%u: acquire %d "
690
		"(pollers %zu/%zu: workers %zu/%zu)", 
691
		ws[i].pid, afd, pfdsz, pfdmaxsz, wsz, maxwsz);
692
	pfd[pfdsz].events = POLLIN;
693
	pfd[pfdsz].fd = ws[i].ctrl;
694
	pfdsz++;
695

696
	if (fullwritefd(ws[i].ctrl, ws[i].fd, &ws[i].cookie, sizeof(uint64_t)))
697
		goto pollagain;
698

699
out:
700
	if ( ! hup) {
701
		/*
702
		 * If we're not going to restart, then close the FastCGI
703
		 * file descriptor as soon as possible.
704
		 */
705
		dbg("closing control socket");
706
		if (-1 == close(fd))
707
			syslog(LOG_ERR, "close: control: %m");
708
		fd = -1;
709
	}
710

711
	/*
712
	 * Close the application's control socket; then, if that doesn't
713
	 * make it exit (kcgi(3) will, but other applications may not),
714
	 * we also deliver a SIGTERM.
715
	 */
716
	for (i = 0; i < wsz; i++) {
717
		if (-1 == ws[i].pid)
718
			continue;
719
		dbg("worker-%u: terminating", ws[i].pid);
720
		if (-1 == close(ws[i].ctrl))
721
			syslog(LOG_ERR, "close: "
722
				"worker-%u control: %m", ws[i].pid);
723
		if (-1 == kill(ws[i].pid, SIGTERM))
724
			syslog(LOG_ERR, "kill: "
725
				"worker-%u: %m", ws[i].pid);
726
	}
727

728
	/*
729
	 * Now wait for the children and pending children.
730
	 */
731
	for (i = 0; i < wsz; i++) {
732
		if (-1 == ws[i].pid)
733
			continue;
734
		dbg("worker-%u: reaping", ws[i].pid);
735
		if (-1 == waitpid(ws[i].pid, NULL, 0))
736
			syslog(LOG_ERR, "wait: "
737
				"worker-%u: %m", ws[i].pid);
738
	}
739

740
	for (i = 0; i < sloughsz; i++) {
741
		dbg("sloughed worker-%u: reaping", slough[i].pid);
742
		if (-1 == waitpid(slough[i].pid, NULL, 0))
743
			syslog(LOG_ERR, "wait: sloughed "
744
				"worker-%u: %m", slough[i].pid);
745
	}
746

747
	free(ws);
748
	free(slough);
749
	free(pfd);
750

751
	if (hup)
752
		goto again;
753

754
	/* 
755
	 * Now we're really exiting.
756
	 * Do our final cleanup if we didn't already...
757
	 */
758
	if (-1 != fd) {
759
		dbg("closing control socket");
760
		if (-1 == close(fd))
761
			syslog(LOG_ERR, "close: control: %m");
762
	}
763
	return(exitcode);
764
}
765

766
static int
767
fixedpool(size_t wsz, int fd, const char *sockpath, char *argv[])
768
{
769
	pid_t		 *ws;
770
	size_t		  i;
771
	sigset_t	  set, oset;
772
	void 		(*sigfp)(int);
773

774
	/*
775
	 * Dying children should notify us that something is horribly
776
	 * wrong and we should exit.
777
	 * Also handle SIGTERM in the same way.
778
	 */
779
	signal(SIGTERM, sighandlestop);
780
	signal(SIGCHLD, sighandlechld);
781
	signal(SIGHUP, sighandlehup);
782
	sigemptyset(&set);
783
	sigaddset(&set, SIGCHLD);
784
	sigaddset(&set, SIGTERM);
785
	sigaddset(&set, SIGHUP);
786
	sigprocmask(SIG_BLOCK, &set, &oset);
787

788
again:
789
	hup = stop = chld = 0;
790

791
	/* Allocate worker array. */
792
	if (NULL == (ws = calloc(wsz, sizeof(pid_t)))) {
793
		syslog(LOG_ERR, "calloc: initialisation: %m");
794
		close(fd);
795
		unlink(sockpath);
796
		return(0);
797
	}
798

799
	/* 
800
	 * "Zero" the workers.
801
	 * This is in case the initialisation fails.
802
	 */
803
	for (i = 0; i < wsz; i++)
804
		ws[i] = -1;
805

806
	for (i = 0; i < wsz; i++) {
807
		if (-1 == (ws[i] = fork())) {
808
			syslog(LOG_ERR, "fork: worker: %m");
809
			goto out;
810
		} else if (0 == ws[i]) {
811
			/*
812
			 * Assign stdin to be the socket over which
813
			 * we're going to transfer request descriptors
814
			 * when we get them.
815
			 */
816
			if (-1 == dup2(fd, STDIN_FILENO)) {
817
				syslog(LOG_ERR, "dup2: worker: %m");
818
				_exit(EXIT_FAILURE);
819
			}
820
			close(fd);
821
			execv(argv[0], argv);
822
			syslog(LOG_ERR, "execve: %s: %m", argv[0]);
823
			_exit(EXIT_FAILURE);
824
		}
825
	}
826

827
	sigsuspend(&oset);
828

829
	if (stop)
830
		dbg("servicing exit request");
831
	else if (chld)
832
		syslog(LOG_ERR, "worker unexpectedly exited");
833
	else 
834
		dbg("servicing restart request");
835
out:
836
	if ( ! hup) {
837
		/*
838
		 * If we're not going to restart, then close the FastCGI
839
		 * file descriptor as soon as possible.
840
		 */
841
		if (-1 == close(fd))
842
			syslog(LOG_ERR, "close: control: %m");
843
		fd = -1;
844
	}
845

846
	/* Suppress child exit signals whilst we kill them. */
847
	sigfp = signal(SIGCHLD, SIG_DFL);
848

849
	/*
850
	 * Now wait on the children.
851
	 * This can take forever, but properly-written children will
852
	 * exit when receiving SIGTERM.
853
	 */
854
	for (i = 0; i < wsz; i++)
855
		if (-1 != ws[i] && -1 == kill(ws[i], SIGTERM))
856
			syslog(LOG_ERR, "kill: worker-%u: %m", ws[i]);
857

858
	for (i = 0; i < wsz; i++)
859
		if (-1 != ws[i] && -1 == waitpid(ws[i], NULL, 0))
860
			syslog(LOG_ERR, "wait: worker-%u: %m", ws[i]);
861

862
	signal(SIGCHLD, sigfp);
863

864
	free(ws);
865

866
	if (hup)
867
		goto again;
868

869
	/* 
870
	 * Now we're really exiting.
871
	 * Do our final cleanup if we didn't already...
872
	 */
873
	if (-1 != fd && -1 == close(fd))
874
		syslog(LOG_ERR, "close: control: %m");
875
	return(1);
876
}
877

878
int
879
main(int argc, char *argv[])
880
{
881
	int			  c, fd, varp, usemax, useq, nod, logop;
882
	struct passwd		 *pw;
883
	size_t			  i, wsz, sz, lsz, maxwsz;
884
	time_t			  waittime;
885
	const char		 *pname, *sockpath, *chpath,
886
	      			 *sockuser, *procuser, *errstr;
887
	struct sockaddr_un	  un;
888
	mode_t			  old_umask;
889
	uid_t		 	  sockuid, procuid;
890
	gid_t			  sockgid, procgid;
891
	char			**nargv;
892

893
	if ((pname = strrchr(argv[0], '/')) == NULL)
894
		pname = argv[0];
895
	else
896
		++pname;
897

898
	if (0 != geteuid()) {
899
		fprintf(stderr, "%s: need root privileges\n", pname);
900
		return(EXIT_FAILURE);
901
	}
902

903
	sockuid = procuid = sockgid = procgid = -1;
904
	wsz = 5;
905
	usemax = useq = 0;
906
	sockpath = "/var/www/run/httpd.sock";
907
	chpath = "/var/www";
908
	sockuser = procuser = NULL;
909
	varp = 0;
910
	nod = 0;
911
	maxwsz = lsz = 0;
912
	waittime = 60 * 5;
913

914
	while (-1 != (c = getopt(argc, argv, "l:p:n:N:s:u:U:rvdw:")))
915
		switch (c) {
916
		case ('l'):
917
			useq = 1;
918
			lsz = strtonum(optarg, 1, INT_MAX, &errstr);
919
			if (NULL == errstr)
920
				break;
921
			fprintf(stderr, "-l must be "
922
				"between 1 and %d\n", INT_MAX);
923
			return(EXIT_FAILURE);	
924
		case ('n'):
925
			wsz = strtonum(optarg, 0, INT_MAX, &errstr);
926
			if (NULL == errstr)
927
				break;
928
			fprintf(stderr, "-n must be "
929
				"between 0 and %d\n", INT_MAX);
930
			return(EXIT_FAILURE);	
931
		case ('N'):
932
			usemax = 1;
933
			maxwsz = strtonum(optarg, 0, INT_MAX, &errstr);
934
			if (NULL == errstr)
935
				break;
936
			fprintf(stderr, "-N must be "
937
				"between 0 and %d\n", INT_MAX);
938
			return(EXIT_FAILURE);	
939
		case ('p'):
940
			chpath = optarg;
941
			break;	
942
		case ('s'):
943
			sockpath = optarg;
944
			break;	
945
		case ('u'):
946
			sockuser = optarg;
947
			break;	
948
		case ('U'):
949
			procuser = optarg;
950
			break;	
951
		case ('r'):
952
			varp = 1;
953
			break;	
954
		case ('v'):
955
			verbose = 1;
956
			break;	
957
		case ('d'):
958
			nod = 1;
959
			break;	
960
		case ('w'):
961
			waittime = strtonum(optarg, 0, INT_MAX, &errstr);
962
			if (NULL == errstr)
963
				break;
964
			fprintf(stderr, "-w must be "
965
				"between 0 and %d\n", INT_MAX);
966
			return(EXIT_FAILURE);	
967
		default:
968
			goto usage;
969
		}
970

971
	argc -= optind;
972
	argv += optind;
973

974
	if (0 == argc) 
975
		goto usage;
976

977
	if (usemax && varp) {
978
		if (maxwsz == wsz)
979
			varp = 0;
980
		else if (maxwsz < wsz)
981
			goto usage;
982
	} else
983
		maxwsz = wsz * 2;
984

985
	if (0 == useq)
986
		lsz = (varp ? maxwsz : wsz) * 2;
987

988
	assert(lsz);
989

990
	pw = NULL;
991
	if (NULL != procuser && NULL == (pw = getpwnam(procuser))) { 
992
		fprintf(stderr, "%s: no such user\n", procuser);
993
		return(EXIT_FAILURE);
994
	} else if (NULL != pw) {
995
		procuid = pw->pw_uid;
996
		procgid = pw->pw_gid;
997
	}
998

999
	pw = NULL;
1000
	if (NULL != sockuser && NULL == (pw = getpwnam(sockuser))) { 
1001
		fprintf(stderr, "%s: no such user\n", sockuser);
1002
		return(EXIT_FAILURE);
1003
	} else if (NULL != pw) {
1004
		sockuid = pw->pw_uid;
1005
		sockgid = pw->pw_gid;
1006
	}
1007

1008
	/* Do the usual dance to set up UNIX sockets. */
1009
	memset(&un, 0, sizeof(un));
1010
	un.sun_family = AF_UNIX;
1011
	sz = strlcpy(un.sun_path, sockpath, sizeof(un.sun_path));
1012
	if (sz >= sizeof(un.sun_path)) {
1013
		fprintf(stderr, "socket path to long\n");
1014
		return(EXIT_FAILURE);
1015
	}
1016
#if !defined(__linux__) && !defined(__sun)
1017
	un.sun_len = sz;
1018
#endif
1019

1020
	/*
1021
	 * Prepare the socket then unlink any dead existing ones.
1022
	 * This is because we want to control the socket.
1023
	 */
1024
	if (-1 == (fd = socket(AF_UNIX, SOCK_STREAM, 0))) {
1025
		perror("socket");
1026
		return(EXIT_FAILURE);
1027
	} else if (-1 == unlink(sockpath)) {
1028
		if (ENOENT != errno) {
1029
			perror(sockpath);
1030
			close(fd);
1031
			return(EXIT_FAILURE);
1032
		}
1033
	}
1034

1035
	old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
1036

1037
	/* 
1038
	 * Now actually bind to the FastCGI socket set up our
1039
	 * listeners, and make sure that we're not blocking.
1040
	 * If necessary, change the file's ownership.
1041
	 * We buffer up to the number of available workers.
1042
	 */
1043
	if (-1 == bind(fd, (struct sockaddr *)&un, sizeof(un))) {
1044
		perror("bind");
1045
		close(fd);
1046
		return(EXIT_FAILURE);
1047
	}
1048
	umask(old_umask);
1049

1050
	if (NULL != sockuser) 
1051
		if (chown(sockpath, sockuid, sockgid) == -1) {
1052
			perror(sockpath);
1053
			close(fd);
1054
			return(EXIT_FAILURE);
1055
		}
1056

1057
	if (-1 == listen(fd, lsz)) {
1058
		perror(sockpath);
1059
		close(fd);
1060
		return(EXIT_FAILURE);
1061
	}
1062

1063
	/* 
1064
	 * Jail our file-system.
1065
	 */
1066
	if (-1 == chroot(chpath)) {
1067
		perror("chroot");
1068
		close(fd);
1069
		return(EXIT_FAILURE);
1070
	} else if (-1 == chdir("/")) {
1071
		perror("chdir");
1072
		close(fd);
1073
		unlink(sockpath);
1074
		return(EXIT_FAILURE);
1075
	}
1076

1077
	if (NULL != procuser)  {
1078
		if (0 != setgid(procgid)) {
1079
			perror(procuser);
1080
			close(fd);
1081
			return(EXIT_FAILURE);
1082
		} else if (0 != setuid(procuid)) {
1083
			perror(procuser);
1084
			close(fd);
1085
			return(EXIT_FAILURE);
1086
		} else if (-1 != setuid(0)) {
1087
			fprintf(stderr, "%s: managed to regain "
1088
				"root privileges: aborting\n", pname);
1089
			close(fd);
1090
			return(EXIT_FAILURE);
1091
		}
1092
	}
1093

1094
	nargv = calloc(argc + 1, sizeof(char *));
1095
	if (NULL == nargv) {
1096
		perror(NULL);
1097
		close(fd);
1098
		return(EXIT_FAILURE);
1099
	}
1100

1101
	for (i = 0; i < (size_t)argc; i++)
1102
		nargv[i] = argv[i];
1103

1104
	if ( ! nod && -1 == daemon(1, 0)) {
1105
		perror("daemon");
1106
		close(fd);
1107
		unlink(sockpath);
1108
		free(nargv);
1109
		return(EXIT_FAILURE);
1110
	} 
1111

1112
	logop = LOG_PID;
1113
#ifdef LOG_PERROR
1114
	if (nod)
1115
		logop |= LOG_PERROR;
1116
#endif
1117
	openlog(pname, logop, LOG_DAEMON);
1118

1119
	c = varp ?
1120
		varpool(wsz, maxwsz, waittime, fd, sockpath, nargv) :
1121
		fixedpool(wsz, fd, sockpath, nargv);
1122

1123
	free(nargv);
1124
	return(c ? EXIT_SUCCESS : EXIT_FAILURE);
1125
usage:
1126
	fprintf(stderr, "usage: %s "
1127
		"[-l backlog] "
1128
		"[-n workers] "
1129
		"[-p chroot] "
1130
		"[-s sockpath] "
1131
		"[-u sockuser] "
1132
		"[-U procuser] "
1133
		"-- prog [arg1...]\n", pname);
1134
	return(EXIT_FAILURE);
1135
}
1136

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.