X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=chiark-utils.git;a=blobdiff_plain;f=cprogs%2Fcgi-fcgi-interp.c;h=63db94b35fed7a1d3afb3f11c96858a31d3c70d7;hp=6b0585b54f4a9b46e0464d80458c28f4f88b28c7;hb=d8d60f17dad9d2e9a01729d9f21b0a99f1aeb4ce;hpb=5bf620d34ef0036ab8f20f3fa4704889c366fcad diff --git a/cprogs/cgi-fcgi-interp.c b/cprogs/cgi-fcgi-interp.c index 6b0585b..63db94b 100644 --- a/cprogs/cgi-fcgi-interp.c +++ b/cprogs/cgi-fcgi-interp.c @@ -4,7 +4,7 @@ * #!/usr/bin/cgi-fcgi-interp [], */ /* - * cgi-fcgi-interp.[ch] - C helpers common to the whole of chiark-utils + * cgi-fcgi-interp.[ch] - Convenience wrapper for cgi-fcgi * * Copyright 2016 Ian Jackson * Copyright 1982,1986,1993 The Regents of the University of California @@ -142,18 +142,15 @@ #include #include #include -#include #include #include #include +#include #include #include "myopt.h" -#define die common_die -#define diee common_diee - #define MINHEXHASH 33 #define STAGE2_VAR "CHIARKUTILS_CGIFCGIINTERP_STAGE2" @@ -163,12 +160,56 @@ static int check_interval=300; static struct sha256_ctx identsc; -const char *stage2; +static bool logging; +static const char *stage2; + +static void vmsgcore(int estatus, int errnoval, const char *fmt, va_list al) { + int r; -void diee(const char *m) { - err(127, "error: %s failed", m); + if (logging) { + const char *fmt_use = fmt; + char *fmt_free = 0; + if (errnoval) { + r = asprintf(&fmt_free, "%s: %%m", fmt); + if (r) { + fmt_free = 0; + } else { + fmt_use = fmt_free; + } + } + vsyslog(LOG_ERR, fmt_use, al); + free(fmt_free); + } else { + fputs("cgi-fcgi-interp: ",stderr); + vfprintf(stderr,fmt,al); + if (errnoval!=-1) fprintf(stderr,": %s",strerror(errnoval)); + fputc('\n',stderr); + } + if (estatus) exit(estatus); } +#define DEF_MSG(func, attrs, estatus, errnoval, after) \ + static void func(const char *fmt, ...) \ + __attribute__((format(printf,1,2))) attrs; \ + static void func(const char *fmt, ...) { \ + va_list al; \ + va_start(al,fmt); \ + vmsgcore(estatus,errnoval,fmt,al); \ + after \ + } + +DEF_MSG(warninge, /*empty*/, 0, errno, { }); +DEF_MSG(warning , /*empty*/, 0, 0, { }); + +#define DEF_DIE(func, errnoval) \ + DEF_MSG(func, __attribute__((noreturn)), 127, errnoval, { abort(); }) + +DEF_DIE(diee, errno) +DEF_DIE(die, 0) + +void common_diee(const char *m) { diee("%s", m); } +void common_die (const char *m) { die ("%s", m); } + static void fusagemessage(FILE *f) { fprintf(f, "usage: #!/usr/bin/cgi-fcgi-interp []\n"); } @@ -240,16 +281,16 @@ static bool find_run_base_var_run(void) { diee("stat /var/run/user/UID"); } if (!S_ISDIR(stab.st_mode)) { - warnx("%s not a directory, falling back to ~\n", try); + warning("%s not a directory, falling back to ~\n", try); return 0; } if (stab.st_uid != us) { - warnx("%s not owned by uid %lu, falling back to ~\n", try, - (unsigned long)us); + warning("%s not owned by uid %lu, falling back to ~\n", try, + (unsigned long)us); return 0; } if (stab.st_mode & 0077) { - warnx("%s writeable by group or other, falling back to ~\n", try); + warning("%s writeable by group or other, falling back to ~\n", try); return 0; } run_base = m_asprintf("%s/%s", try, "cgi-fcgi-interp"); @@ -271,7 +312,7 @@ static bool find_run_base_home(void) { ut.nodename[32] = 0; run_base_mkdir_p = m_asprintf("%s/%s", pw->pw_dir, ".cgi-fcgi-interp"); - try = m_asprintf("%/%s", run_base_mkdir_p, ut.nodename); + try = m_asprintf("%s/%s", run_base_mkdir_p, ut.nodename); run_base = try; return 1; } @@ -290,10 +331,10 @@ static void find_socket_path(void) { if (!ident) { if (maxidentlen < MINHEXHASH) - errx(127,"base directory `%s'" - " leaves only %d characters for id hash" - " which is too little (<%d)", - run_base, maxidentlen, MINHEXHASH); + die("base directory `%s'" + " leaves only %d characters for id hash" + " which is too little (<%d)", + run_base, maxidentlen, MINHEXHASH); int identlen = maxidentlen > 64 ? 64 : maxidentlen; char *hexident = xmalloc(identlen + 2); @@ -312,19 +353,19 @@ static void find_socket_path(void) { } if (strlen(ident) > maxidentlen) - errx(127, "base directory `%s' plus ident `%s' too long" - " (with spare) for socket (max ident %d)\n", - run_base, ident, maxidentlen); + die("base directory `%s' plus ident `%s' too long" + " (with spare) for socket (max ident %d)\n", + run_base, ident, maxidentlen); r = mkdir(run_base, 0700); if (r && errno==ENOENT && run_base_mkdir_p) { r = mkdir(run_base_mkdir_p, 0700); - if (r) err(127,"mkdir %s (since %s was ENOENT)",run_base_mkdir_p,run_base); + if (r) diee("mkdir %s (since %s was ENOENT)",run_base_mkdir_p,run_base); r = mkdir(run_base, 0700); } if (r) { if (!(errno == EEXIST)) - err(127,"mkdir %s",run_base); + diee("mkdir %s",run_base); } socket_path = m_asprintf("%s/s%s",run_base,ident); @@ -386,7 +427,7 @@ static bool stab_isnewer(const struct stat *a, const struct stat *b) { static void stab_mtimenow(struct stat *out) { int r = clock_gettime(CLOCK_REALTIME, &out->st_mtim); - if (r) err(127,"(stage2) clock_gettime"); + if (r) diee("(stage2) clock_gettime"); if (debugmode) fprintf(stderr,"stab_mtimenow mtim %lu.%06lu\n", (unsigned long)out->st_mtim.tv_sec, @@ -400,12 +441,12 @@ static bool stab_isnewer(const struct stat *a, const struct stat *b) { fprintf(stderr,"stab_isnewer mtime %lu %lu\n", (unsigned long)a->st_mtime, (unsigned long)b->st_mtime); - return a->st_mtime > &b->st_mtime; + return a->st_mtime > b->st_mtime; } static void stab_mtimenow(struct stat *out) { out->st_mtime = time(NULL); - if (baseline_time.st_mtime == (time_t)-1) err(127,"(stage2) time()"); + if (out->st_mtime == (time_t)-1) diee("(stage2) time()"); if (debugmode) fprintf(stderr,"stab_mtimenow mtime %lu\n", (unsigned long)out->st_mtime); @@ -418,14 +459,14 @@ static bool check_garbage_vs(const struct stat *started) { int r; r = lstat(script, &script_stab); - if (r) err(127,"lstat script (%s)",script); + if (r) diee("lstat script (%s)",script); if (stab_isnewer(&script_stab, started)) return 1; if (S_ISLNK(script_stab.st_mode)) { r = stat(script, &script_stab); - if (r) err(127,"stat script (%s0",script); + if (r) diee("stat script (%s0",script); if (stab_isnewer(&script_stab, started)) return 1; @@ -442,7 +483,7 @@ static bool check_garbage(void) { if (r) { if ((errno == ENOENT)) return 0; /* well, no garbage then */ - err(127,"stat socket (%s)",socket_path); + diee("stat socket (%s)",socket_path); } return check_garbage_vs(&sock_stab); @@ -459,26 +500,26 @@ static void tidy_garbage(void) { const char *lock_path = m_asprintf("%s/l%s",run_base,ident); lockfd = open(lock_path, O_CREAT|O_RDWR, 0600); - if (lockfd<0) err(127,"create lock (%s)", lock_path); + if (lockfd<0) diee("create lock (%s)", lock_path); r = flock(lockfd, LOCK_EX); - if (r) err(127,"lock lock (%s)", lock_path); + if (r) diee("lock lock (%s)", lock_path); if (check_garbage()) { r = unlink(socket_path); if (r) { if (!(errno == ENOENT)) - err(127,"remove out-of-date socket (%s)", socket_path); + diee("remove out-of-date socket (%s)", socket_path); } } r = close(lockfd); - if (r) errx(127,"close lock (%s)", lock_path); + if (r) diee("close lock (%s)", lock_path); } static void make_stderr_copy(void) { stderr_copy = dup(2); - if (stderr_copy < 0) err(127,"dup stderr (for copy for stage2)"); + if (stderr_copy < 0) diee("dup stderr (for copy for stage2)"); } static void prep_stage2(void) { @@ -486,7 +527,7 @@ static void prep_stage2(void) { const char *stage2_val = m_asprintf("%d", stderr_copy); r = setenv(STAGE2_VAR, stage2_val, 1); - if (r) err(127,"set %s (to announce to stage2)", STAGE2_VAR); + if (r) diee("set %s (to announce to stage2)", STAGE2_VAR); } static void shbang_opts(const char *const **argv_io, @@ -494,7 +535,7 @@ static void shbang_opts(const char *const **argv_io, myopt(argv_io, cmdinfos); interp = *(*argv_io)++; - if (!interp) errx(127,"need interpreter argument"); + if (!interp) badusage("need interpreter argument"); } /* stage2 predeclarations */ @@ -503,6 +544,7 @@ static void become_pgrp(void); static void setup_handlers(void); static void spawn_script(void); static void queue_alarm(void); +static void start_logging(void); static void await_something(void); int main(int argc, const char *const *argv) { @@ -518,9 +560,12 @@ int main(int argc, const char *const *argv) { assert(r==2); r = open("/dev/null",O_WRONLY); - if (r<0) err(127,"open /dev/null as stdout"); + if (r<0) diee("open /dev/null as stdout"); if (r>=3) close(r); - else if (r!=1) errx(127,"open /dev/null for stdout gave bad fd %d",r); + else if (r!=1) die("open /dev/null for stdout gave bad fd %d",r); + + r = close(stderrfd); + if (r) diee("close saved stderr fd"); } sha256_init(&identsc); @@ -535,14 +580,13 @@ int main(int argc, const char *const *argv) { int split_argc = 0; split_args[split_argc++] = argv[0]; for (;;) { - if (split_argc >= MAX_OPTS) errx(127,"too many options in combined arg"); + if (split_argc >= MAX_OPTS) die("too many options in combined arg"); split_args[split_argc++] = smashedopt; if (smashedopt[0] != '-') /* never true on first iteration */ break; char *delim = strchr(smashedopt,' '); if (!delim) delim = strchr(smashedopt,','); - if (!delim) - errx(127,"combined arg lacks "); + if (!delim) badusage("combined arg lacks "); *delim = 0; smashedopt = delim+1; } @@ -553,14 +597,13 @@ int main(int argc, const char *const *argv) { shbang_opts(&split_argv, cmdinfos); /* sets interp */ - if (!split_argv) errx(127,"combined arg too many non-option arguments"); + if (!split_argv) badusage("combined arg too many non-option arguments"); } else { shbang_opts(&argv, cmdinfos); } script = *argv++; - if (!script) errx(127,"need script argument"); - if (*argv) errx(127,"too many arguments"); + if (!script) badusage("need script argument"); if (!stage2) { @@ -587,7 +630,7 @@ int main(int argc, const char *const *argv) { script, m_asprintf("%d", numservers), (char*)0); - err(127,"exec cgi-fcgi"); + diee("exec cgi-fcgi"); } else { /*stage2*/ @@ -596,6 +639,7 @@ int main(int argc, const char *const *argv) { setup_handlers(); spawn_script(); queue_alarm(); + start_logging(); await_something(); abort(); @@ -612,6 +656,7 @@ int main(int argc, const char *const *argv) { static struct stat baseline_time; static pid_t script_child, stage2_pgrp; static bool out_of_date; +static int errpipe; static void record_baseline_time(void) { stab_mtimenow(&baseline_time); @@ -623,17 +668,17 @@ static void become_pgrp(void) { stage2_pgrp = getpid(); r = setpgid(0,0); - if (r) err(127,"(stage2) setpgid"); + if (r) diee("(stage2) setpgid"); } static void atexit_handler(void) { int r; sighandler_t sigr = signal(SIGTERM,SIG_IGN); - if (sigr == SIG_ERR) warn("(stage2) signal(SIGTERM,SIG_IGN)"); + if (sigr == SIG_ERR) warninge("(stage2) signal(SIGTERM,SIG_IGN)"); r = killpg(stage2_pgrp,SIGTERM); - if (r) warn("(stage) killpg failed"); + if (r) warninge("(stage) killpg failed"); } static void alarm_handler(int dummy) { @@ -649,22 +694,24 @@ static void child_handler(int dummy) { for (;;) { int status; pid_t got = waitpid(-1, &status, WNOHANG); - if (got == (pid_t)-1) err(127,"(stage2) waitpid"); + if (got == (pid_t)-1) diee("(stage2) waitpid"); if (got != script_child) { - warn("(stage2) waitpid got status %d for unknown child [%lu]", - status, (unsigned long)got); + warning("(stage2) waitpid got status %d for unknown child [%lu]", + status, (unsigned long)got); continue; } if (WIFEXITED(status)) { int v = WEXITSTATUS(status); - if (v) warn("program failed with error exit status %d", v); + if (v) warning("program failed with error exit status %d", v); exit(status); } else if (WIFSIGNALED(status)) { int s = WTERMSIG(status); - err(status & 0xff, "program died due to fatal signal %s%s", - strsignal(s), WCOREDUMP(status) ? " (core dumped" : ""); + warning("program died due to fatal signal %s%s", + strsignal(s), WCOREDUMP(status) ? " (core dumped" : ""); + assert(status & 0xff); + exit(status & 0xff); } else { - err(127, "program failed with crazy wait status %#x", status); + die("program failed with crazy wait status %#x", status); } } exit(127); @@ -675,7 +722,7 @@ static void setup_handlers(void) { int r; r = atexit(atexit_handler); - if (r) err(127,"(stage2) atexit"); + if (r) diee("(stage2) atexit"); sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGALRM); @@ -683,40 +730,125 @@ static void setup_handlers(void) { sa.sa_flags = 0; r = sigprocmask(SIG_BLOCK, &sa.sa_mask, 0); - if (r) err(127,"(stage2) sigprocmask(SIG_BLOCK,)"); + if (r) diee("(stage2) sigprocmask(SIG_BLOCK,)"); sa.sa_handler = alarm_handler; r = sigaction(SIGALRM, &sa, 0); - if (r) err(127,"(stage2) sigaction SIGALRM"); + if (r) diee("(stage2) sigaction SIGALRM"); sa.sa_flags |= SA_NOCLDSTOP; sa.sa_handler = child_handler; r = sigaction(SIGCHLD, &sa, 0); - if (r) err(127,"(stage2) sigaction SIGCHLD"); + if (r) diee("(stage2) sigaction SIGCHLD"); } static void spawn_script(void) { + int r; + int errpipes[2]; + + r = pipe(errpipes); + if (r) diee("(stage2) pipe"); + script_child = fork(); - if (script_child == (pid_t)-1) err(127,"(stage2) fork"); + if (script_child == (pid_t)-1) diee("(stage2) fork"); if (!script_child) { + r = close(errpipes[0]); + if (r) diee("(stage2 child) close errpipes[0]"); + + r = dup2(errpipes[1], 2); + if (r != 2) diee("(stage2 child) dup2 stderr"); + execlp(interp, interp, script, (char*)0); - err(127,"(stage2) exec interpreter (`%s', for `%s')\n",interp,script); + diee("(stage2) exec interpreter (`%s', for `%s')\n",interp,script); } + + r = close(errpipes[1]); + if (r) diee("(stage2) close errpipes[1]"); + + errpipe = errpipes[0]; + r = fcntl(errpipe, F_SETFL, O_NONBLOCK); + if (r) diee("(stage2) set errpipe nonblocking"); } static void queue_alarm(void) { alarm(check_interval); } +static void start_logging(void) { + int r; + + openlog(script, LOG_NOWAIT|LOG_PID, LOG_USER); + logging = 1; + r = dup2(1,2); + if (r!=2) diee("dup2 stdout to stderr"); +} + +static void errpipe_readable(void) { + static char buf[1024]; + static int pending; + + /* %: does not contain newlines + * _: empty (garbage) + */ + + /* %%%%%%%%%%%__________________ */ + /* ^ pending */ + + for (;;) { + int avail = sizeof(buf) - pending; + ssize_t got = read(errpipe, buf+pending, avail); + if (got==-1) { + if (errno==EINTR) continue; + else if (errno==EWOULDBLOCK || errno==EAGAIN) return; + else diee("(stage2) errpipe read"); + got = 0; + } else if (got==0) { + warning("program closed its stderr fd"); + errpipe = -1; + return; + } + int scanned = pending; + pending += got; + int eaten = 0; + for (;;) { + const char *newline = memchr(buf+scanned, '\n', pending-scanned); + int printupto, eat; + if (newline) { + printupto = newline-buf; + eat = printupto + 1; + } else if (!eaten && pending==sizeof(buf)) { /* overflow */ + printupto = pending; + eat = printupto; + } else { + break; + } + syslog(LOG_ERR,"stderr: %.*s", printupto-eaten, buf+eaten); + eaten += eat; + scanned = eaten; + } + pending -= eaten; + memmove(buf, buf+eaten, pending); + } +} + static void await_something(void) { int r; sigset_t mask; sigemptyset(&mask); for (;;) { - r = sigsuspend(&mask); - assert(r==-1); - if (errno != EINTR) err(127,"(stage2) sigsuspend"); + fd_set rfds; + FD_ZERO(&rfds); + if (errpipe >= 0) + FD_SET(errpipe, &rfds); + r = pselect(errpipe+1, &rfds,0,0, 0, &mask); + if (r==-1) { + if (errno != EINTR) diee("(stage2) sigsuspend"); + continue; + } + assert(r>0); + assert(FD_ISSET(errpipe, &rfds)); + errpipe_readable(); } }