2 * "Interpreter" that you can put in #! like this
3 * #!/usr/bin/cgi-fcgi-interp [<options>] <interpreter>
6 * cgi-fcgi-interp [<option> ..] <interpreter> <script> [<ignored> ...]
7 * cgi-fcgi-interp [<option>,..],<interpreter> <script> [<ignored> ...]
8 * cgi-fcgi-interp '[<option> ..] <interpreter>' <script> [<ignored> ...]
11 * cgi-fcgi-interp.[ch] - Convenience wrapper for cgi-fcgi
13 * Copyright 2016 Ian Jackson
14 * Copyright 1982,1986,1993 The Regents of the University of California
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 3 of the License, or
19 * (at your option) any later version.
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
26 * You should have received a copy of the GNU General Public
27 * License along with this file; if not, consult the Free Software
28 * Foundation's website at www.fsf.org, or the GNU Project website at
31 * See below for a BSD 3-clause notice regarding timespeccmp.
34 * The result is a program which looks, when executed via the #!
35 * line, like a CGI program. But the script inside will be executed
36 * via <interpreter> in an fcgi context.
41 * The real interpreter to use. Eg "perl". Need not
42 * be an absolute path; will be fed to execvp.
45 * Add <ident-info> to the unique identifying information for
46 * this fcgi program. May be repeated; order is significant.
48 * -E<ident-info-env-var>
49 * Look <ident-info-env-var> up in the environment and add
50 * <ident-info-env-var>=<value> as if specified with -G. If
51 * the variable is unset in the environment, it is as if
52 * -G<ident-info-env-var> was specified.
55 * Use <ident> rather than hex(sha256(<interp>\0<script>\0))
56 * as the basename of the leafname of the fcgi rendezvous
57 * socket. If <ident> contains only hex digit characters it
58 * ought to be no more than 32 characters. <ident> should
59 * not contain spaces or commas (see below).
62 * Start <numservers> instances of the program. This
63 * determines the maximum concurrency. (Note that unlike
64 * speedy, the specified number of servers is started
65 * right away.) The default is 4.
68 * Stale server check interval, in seconds. The worker
69 * process group will get a SIGTERM when it is no longer
70 * needed to process new requests. Ideally it would continue
71 * to serve any existing requests. The SIGTERM will arrive no
72 * earlier than <interval> after the last request arrived at
73 * the containing webserver. Default is 300.
76 * Debug mode. Do not actually run program. Instead, print
77 * out what we would do.
79 * <options> and <interpreter> can be put into a single argument
80 * to cgi-fcgi-interp, separated by spaces or commas. <interpreter>
83 * cgi-fcgi-interp automatically expires old sockets, including
84 * ones where the named script is out of date.
87 * Uses one of two directories
88 * /var/run/user/<UID>/cgi-fcgi-interp/
89 * ~/.cgi-fcgi-interp/<node>/
90 * and inside there uses these paths
92 * l<ident> used to lock around garbage collection
94 * If -M<ident> is not specified then an initial substring of the
95 * lowercase hex of the sha256 of <interp>\0<script>\0 is
96 * used. The substring is chosen so that the whole path is 10 bytes
97 * shorter than sizeof(sun_path). But always at least 33 characters.
99 * <node> is truncated at the first `.' and after the first 32
103 * - see if /var/run/user exists
104 * if so, lstat /var/run/user/<UID> and check that
105 * we own it and it's X700; if not, fail
106 * if it's ok then <base> is /var/run/user/<UID>
107 * otherwise, look for and maybe create ~/.cgi-fcgi-interp
108 * (where ~ is HOME or from getpwuid)
109 * and then <base> is ~/.cgi-fcgi-interp/<node>
110 * - calculate pathname (checking <ident> length is OK)
111 * - check for and maybe create <base>
112 * - stat and lstat the <script>
113 * - stat the socket and check its timestamp
114 * if it is too old, unlink it
115 * - dup stderr, mark no cloexec
116 * - set CHIARKUTILS_CGIFCGIINTERP_STAGE2=<stderr-copy-fd>
117 * - run cgi-fcgi -connect SOCKET <script>
119 * When CHIARKUTILS_CGIFCGIINTERP_STAGE2 is set, --stage2 does this:
120 * - dup2 <was-stderr> to fd 2
121 * - open /dev/null and expect fd 1 (and if not, close it)
122 * - become a new process group
123 * - lstat <socket> to find its inum, mtime
124 * - fork/exec <interp> <script>
125 * - periodically lstat <interp> and <script> and
126 * if mtime is newer than our start time
127 * kill process group (at second iteration)
131 #include "timespeccmp.h"
133 #define STAGE2_VAR "CHIARKUTILS_CGIFCGIINTERP_STAGE2"
135 static const char *stage2;
137 const char our_name[] = "cgi-fcgi-interp";
139 static int numservers=4, debugmode;
140 static int check_interval=300;
142 const struct cmdinfo cmdinfos[]= {
144 { 0, 'M', 1, .call=of_iassign, .iassignto= &numservers },
145 { 0, 'D', 0, .iassignto= &debugmode, .arg= 1 },
146 { 0, 'c', 1, .call=of_iassign, .iassignto= &check_interval },
150 void fusagemessage(FILE *f) {
151 fprintf(f, "usage: #!/usr/bin/cgi-fcgi-interp [<options>]\n");
154 void ident_addinit(void) {
157 static int stderr_copy;
159 static void make_stderr_copy(void) {
160 stderr_copy = dup(2);
161 if (stderr_copy < 0) diee("dup stderr (for copy for stage2)");
164 static void prep_stage2(void) {
167 const char *stage2_val = m_asprintf("%d", stderr_copy);
168 r = setenv(STAGE2_VAR, stage2_val, 1);
169 if (r) diee("set %s (to announce to stage2)", STAGE2_VAR);
174 static bool stab_isnewer(const struct stat *a, const struct stat *b) {
176 fprintf(stderr,"stab_isnewer mtim %lu.%06lu %lu.06%lu\n",
177 (unsigned long)a->st_mtim.tv_sec,
178 (unsigned long)a->st_mtim.tv_nsec,
179 (unsigned long)b->st_mtim.tv_sec,
180 (unsigned long)b->st_mtim.tv_nsec);
181 return timespeccmp(&a->st_mtim, &b->st_mtim, >);
184 static void stab_mtimenow(struct stat *out) {
185 int r = clock_gettime(CLOCK_REALTIME, &out->st_mtim);
186 if (r) diee("(stage2) clock_gettime");
188 fprintf(stderr,"stab_mtimenow mtim %lu.%06lu\n",
189 (unsigned long)out->st_mtim.tv_sec,
190 (unsigned long)out->st_mtim.tv_nsec);
193 #else /* !defined(st_mtime) */
195 static bool stab_isnewer(const struct stat *a, const struct stat *b) {
197 fprintf(stderr,"stab_isnewer mtime %lu %lu\n",
198 (unsigned long)a->st_mtime,
199 (unsigned long)b->st_mtime);
200 return a->st_mtime > b->st_mtime;
203 static void stab_mtimenow(struct stat *out) {
204 out->st_mtime = time(NULL);
205 if (out->st_mtime == (time_t)-1) diee("(stage2) time()");
207 fprintf(stderr,"stab_mtimenow mtime %lu\n",
208 (unsigned long)out->st_mtime);
211 #endif /* !defined(st_mtime) */
213 static bool check_garbage_vs(const struct stat *started) {
214 struct stat script_stab;
217 r = lstat(script, &script_stab);
218 if (r) diee("lstat script (%s)",script);
220 if (stab_isnewer(&script_stab, started))
223 if (S_ISLNK(script_stab.st_mode)) {
224 r = stat(script, &script_stab);
225 if (r) diee("stat script (%s0",script);
227 if (stab_isnewer(&script_stab, started))
234 static bool check_garbage(void) {
235 struct stat sock_stab;
238 r = lstat(socket_path, &sock_stab);
240 if ((errno == ENOENT))
241 return 0; /* well, no garbage then */
242 diee("stat socket (%s)",socket_path);
245 return check_garbage_vs(&sock_stab);
248 static void tidy_garbage(void) {
249 /* We lock l<ident> and re-check. The effect of this is that each
250 * stale socket is removed only once. So unless multiple updates to
251 * the script happen rapidly, we can't be racing with the cgi-fcgi
252 * (which is recreating the socket */
256 lockfd = acquire_lock();
258 if (check_garbage()) {
259 r = unlink(socket_path);
261 if (!(errno == ENOENT))
262 diee("remove out-of-date socket (%s)", socket_path);
267 if (r) diee("close lock (%s)", lock_path);
270 /* stage2 predeclarations */
271 static void record_baseline_time(void);
272 static void become_pgrp(void);
273 static void setup_handlers(void);
274 static void spawn_script(void);
275 static void queue_alarm(void);
276 static void start_logging(void);
277 static void await_something(void);
279 int main(int unused_argc, const char *const *argv) {
282 stage2 = getenv(STAGE2_VAR);
284 int stderrfd = atoi(stage2);
287 r = dup2(stderrfd, 2);
290 r = open("/dev/null",O_WRONLY);
291 if (r<0) diee("open /dev/null as stdout");
293 else if (r!=1) die("open /dev/null for stdout gave bad fd %d",r);
296 if (r) diee("close saved stderr fd");
300 if (!script) badusage("need script argument");
306 bool isgarbage = check_garbage();
309 printf("socket: %s\n",socket_path);
310 printf("interp: %s\n",interp);
311 printf("script: %s\n",script);
312 printf("garbage: %d\n",isgarbage);
323 "cgi-fcgi", "-connect", socket_path,
325 m_asprintf("%d", numservers),
327 diee("exec cgi-fcgi");
331 record_baseline_time();
345 /* It is most convenient to handle the recheck timeout, as well as
346 * child death, in signal handlers. Our signals all block each other,
347 * and the main program has signals blocked except in sigsuspend, so
348 * we don't need to worry about async-signal-safety, or errno. */
350 static struct stat baseline_time;
351 static pid_t script_child, stage2_pgrp;
352 static bool out_of_date;
355 static void record_baseline_time(void) {
356 stab_mtimenow(&baseline_time);
359 static void become_pgrp(void) {
362 stage2_pgrp = getpid();
365 if (r) diee("(stage2) setpgid");
368 static void atexit_handler(void) {
371 sighandler_t sigr = signal(SIGTERM,SIG_IGN);
372 if (sigr == SIG_ERR) warninge("(stage2) signal(SIGTERM,SIG_IGN)");
374 r = killpg(stage2_pgrp,SIGTERM);
375 if (r) warninge("(stage) killpg failed");
378 static void alarm_handler(int dummy) {
381 exit(0); /* transfers control to atexit_handler */
383 out_of_date = check_garbage_vs(&baseline_time);
387 static void child_handler(int dummy) {
390 pid_t got = waitpid(-1, &status, WNOHANG);
391 if (got == (pid_t)-1) diee("(stage2) waitpid");
392 if (got != script_child) {
393 warning("(stage2) waitpid got status %d for unknown child [%lu]",
394 status, (unsigned long)got);
397 if (WIFEXITED(status)) {
398 int v = WEXITSTATUS(status);
399 if (v) warning("program failed with error exit status %d", v);
401 } else if (WIFSIGNALED(status)) {
402 int s = WTERMSIG(status);
403 warning("program died due to fatal signal %s%s",
404 strsignal(s), WCOREDUMP(status) ? " (core dumped" : "");
405 assert(status & 0xff);
408 die("program failed with crazy wait status %#x", status);
414 static void setup_handlers(void) {
418 r = atexit(atexit_handler);
419 if (r) diee("(stage2) atexit");
421 sigemptyset(&sa.sa_mask);
422 sigaddset(&sa.sa_mask, SIGALRM);
423 sigaddset(&sa.sa_mask, SIGCHLD);
426 r = sigprocmask(SIG_BLOCK, &sa.sa_mask, 0);
427 if (r) diee("(stage2) sigprocmask(SIG_BLOCK,)");
429 sa.sa_handler = alarm_handler;
430 r = sigaction(SIGALRM, &sa, 0);
431 if (r) diee("(stage2) sigaction SIGALRM");
433 sa.sa_flags |= SA_NOCLDSTOP;
434 sa.sa_handler = child_handler;
435 r = sigaction(SIGCHLD, &sa, 0);
436 if (r) diee("(stage2) sigaction SIGCHLD");
439 static void spawn_script(void) {
444 if (r) diee("(stage2) pipe");
446 script_child = fork();
447 if (script_child == (pid_t)-1) diee("(stage2) fork");
449 r = close(errpipes[0]);
450 if (r) diee("(stage2 child) close errpipes[0]");
452 r = dup2(errpipes[1], 2);
453 if (r != 2) diee("(stage2 child) dup2 stderr");
456 interp, script, (char*)0);
457 diee("(stage2) exec interpreter (`%s', for `%s')\n",interp,script);
460 r = close(errpipes[1]);
461 if (r) diee("(stage2) close errpipes[1]");
463 errpipe = errpipes[0];
464 r = fcntl(errpipe, F_SETFL, O_NONBLOCK);
465 if (r) diee("(stage2) set errpipe nonblocking");
468 static void queue_alarm(void) {
469 alarm(check_interval);
472 static void start_logging(void) {
475 openlog(script, LOG_NOWAIT|LOG_PID, LOG_USER);
478 if (r!=2) diee("dup2 stdout to stderr");
481 static void errpipe_readable(void) {
482 static char buf[1024];
485 /* %: does not contain newlines
489 /* %%%%%%%%%%%__________________ */
493 int avail = sizeof(buf) - pending;
494 ssize_t got = read(errpipe, buf+pending, avail);
496 if (errno==EINTR) continue;
497 else if (errno==EWOULDBLOCK || errno==EAGAIN) return;
498 else diee("(stage2) errpipe read");
501 warning("program closed its stderr fd");
505 int scanned = pending;
509 const char *newline = memchr(buf+scanned, '\n', pending-scanned);
512 printupto = newline-buf;
514 } else if (!eaten && pending==sizeof(buf)) { /* overflow */
520 syslog(LOG_ERR,"stderr: %.*s", printupto-eaten, buf+eaten);
525 memmove(buf, buf+eaten, pending);
529 static void await_something(void) {
538 FD_SET(errpipe, &rfds);
539 r = pselect(errpipe+1, &rfds,0,0, 0, &mask);
541 if (errno != EINTR) diee("(stage2) sigsuspend");
545 assert(FD_ISSET(errpipe, &rfds));