r39378 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r39377‎ | r39378 | r39379 >
Date:04:27, 15 August 2008
Author:river
Status:old
Tags:
Comment:
switchboard 2. faster, more scalable and less crashy.
Modified paths:
  • /trunk/tools/switchboard2/Makefile (added) (history)
  • /trunk/tools/switchboard2/Makefile.config.example (added) (history)
  • /trunk/tools/switchboard2/config.cc (added) (history)
  • /trunk/tools/switchboard2/config.h (added) (history)
  • /trunk/tools/switchboard2/fcgi.cc (added) (history)
  • /trunk/tools/switchboard2/fcgi.h (added) (history)
  • /trunk/tools/switchboard2/main.cc (added) (history)
  • /trunk/tools/switchboard2/process.cc (added) (history)
  • /trunk/tools/switchboard2/process.h (added) (history)
  • /trunk/tools/switchboard2/process_factory.cc (added) (history)
  • /trunk/tools/switchboard2/process_factory.h (added) (history)
  • /trunk/tools/switchboard2/request_thread.cc (added) (history)
  • /trunk/tools/switchboard2/request_thread.h (added) (history)
  • /trunk/tools/switchboard2/swexec.c (added) (history)
  • /trunk/tools/switchboard2/swkill.c (added) (history)
  • /trunk/tools/switchboard2/util.h (added) (history)
  • /trunk/tools/switchboard2/version.h (added) (history)

Diff [purge]

Index: trunk/tools/switchboard2/swexec.c
@@ -0,0 +1,368 @@
 2+/* Copyright 2008 The Apache Software Foundation. */
 3+/* Copyright 2008 River Tarnell <river@wikimedia.org */
 4+/* Licensed to the Apache Software Foundation (ASF) under one or more
 5+ * contributor license agreements. See the NOTICE file distributed with
 6+ * this work for additional information regarding copyright ownership.
 7+ * The ASF licenses this file to You under the Apache License, Version 2.0
 8+ * (the "License"); you may not use this file except in compliance with
 9+ * the License. You may obtain a copy of the License at
 10+ *
 11+ * http://www.apache.org/licenses/LICENSE-2.0
 12+ *
 13+ * Unless required by applicable law or agreed to in writing, software
 14+ * distributed under the License is distributed on an "AS IS" BASIS,
 15+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 16+ * See the License for the specific language governing permissions and
 17+ * limitations under the License.
 18+ */
 19+
 20+/*
 21+ * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache
 22+ */
 23+
 24+#if defined(__sun) && defined(__svr4__)
 25+/* Solaris */
 26+# define USE_PROJECTS
 27+#endif
 28+
 29+#define SB_SUEXEC_UMASK 022
 30+
 31+#include <sys/param.h>
 32+#include <sys/stat.h>
 33+#include <sys/types.h>
 34+#include <string.h>
 35+#include <time.h>
 36+#include <unistd.h>
 37+#include <stdio.h>
 38+#include <stdarg.h>
 39+#include <stdlib.h>
 40+#include <errno.h>
 41+
 42+#ifdef USE_PROJECTS
 43+# include <sys/task.h>
 44+# include <project.h>
 45+#endif
 46+
 47+#include <pwd.h>
 48+#include <grp.h>
 49+
 50+#if defined(PATH_MAX)
 51+#define SB_MAXPATH PATH_MAX
 52+#elif defined(MAXPATHLEN)
 53+#define SB_MAXPATH MAXPATHLEN
 54+#else
 55+#define SB_MAXPATH 8192
 56+#endif
 57+
 58+#define SB_ENVBUF 256
 59+
 60+extern char **environ;
 61+static FILE *log = NULL;
 62+
 63+static const char *const safe_env_lst[] =
 64+{
 65+ "TZ=",
 66+ NULL
 67+};
 68+
 69+
 70+static void
 71+err_output(int is_error, const char *fmt, va_list ap)
 72+{
 73+ time_t timevar;
 74+ struct tm *lt;
 75+
 76+ if (!log) {
 77+ if ((log = fopen(SB_LOG_EXEC, "a")) == NULL) {
 78+ fprintf(stderr, "suexec failure: could not open log file\n");
 79+ perror("fopen");
 80+ exit(1);
 81+ }
 82+ }
 83+
 84+ if (is_error) {
 85+ fprintf(stderr, "suexec policy violation: see suexec log for more "
 86+ "details\n");
 87+ }
 88+
 89+ time(&timevar);
 90+ lt = localtime(&timevar);
 91+
 92+ fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ",
 93+ lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
 94+ lt->tm_hour, lt->tm_min, lt->tm_sec);
 95+
 96+ vfprintf(log, fmt, ap);
 97+
 98+ fflush(log);
 99+}
 100+
 101+static void
 102+log_err(const char *fmt,...)
 103+{
 104+ va_list ap;
 105+
 106+ va_start(ap, fmt);
 107+ err_output(1, fmt, ap); /* 1 == is_error */
 108+ va_end(ap);
 109+}
 110+
 111+static void
 112+log_no_err(const char *fmt,...)
 113+{
 114+ va_list ap;
 115+
 116+ va_start(ap, fmt);
 117+ err_output(0, fmt, ap); /* 0 == !is_error */
 118+ va_end(ap);
 119+}
 120+
 121+static void
 122+clean_env(void)
 123+{
 124+ char envbuf[512];
 125+ char **cleanenv;
 126+ char **ep;
 127+ int cidx = 0;
 128+ int idx;
 129+
 130+ /* While cleaning the environment, the environment should be clean.
 131+ * (e.g. malloc() may get the name of a file for writing debugging info.
 132+ * Bad news if MALLOC_DEBUG_FILE is set to /etc/passwd. Sprintf() may be
 133+ * susceptible to bad locale settings....)
 134+ * (from PR 2790)
 135+ */
 136+ char **envp = environ;
 137+ char *empty_ptr = NULL;
 138+
 139+ environ = &empty_ptr; /* VERY safe environment */
 140+
 141+ if ((cleanenv = (char **) calloc(SB_ENVBUF, sizeof(char *))) == NULL) {
 142+ log_err("failed to malloc memory for environment\n");
 143+ exit(120);
 144+ }
 145+
 146+ sprintf(envbuf, "PATH=%s", SB_SAFE_PATH);
 147+ cleanenv[cidx] = strdup(envbuf);
 148+ cidx++;
 149+ sprintf(envbuf, "PHP_FCGI_MAX_REQUESTS=5000");
 150+ cleanenv[cidx] = strdup(envbuf);
 151+ cidx++;
 152+
 153+ for (ep = envp; *ep && cidx < SB_ENVBUF-1; ep++) {
 154+ for (idx = 0; safe_env_lst[idx]; idx++) {
 155+ if (!strncmp(*ep, safe_env_lst[idx],
 156+ strlen(safe_env_lst[idx]))) {
 157+ cleanenv[cidx] = *ep;
 158+ cidx++;
 159+ break;
 160+ }
 161+ }
 162+ }
 163+
 164+ cleanenv[cidx] = NULL;
 165+
 166+ environ = cleanenv;
 167+}
 168+
 169+int
 170+main(int argc, char *argv[])
 171+{
 172+ int userdir = 0; /* ~userdir flag */
 173+ uid_t uid; /* user information */
 174+ gid_t gid; /* target group placeholder */
 175+ char *target_uname; /* target user name */
 176+ char *target_gname; /* target group name */
 177+ char *target_homedir; /* target home directory */
 178+ char *actual_uname; /* actual user name */
 179+ char *actual_gname; /* actual group name */
 180+ char *prog; /* name of this program */
 181+ struct passwd *pw; /* password entry holder */
 182+ struct group *gr; /* group entry holder */
 183+#ifdef USE_PROJECTS
 184+ struct project proj;
 185+ char nssbuf[PROJECT_BUFSZ];
 186+#endif
 187+
 188+ /*
 189+ * Start with a "clean" environment
 190+ */
 191+ clean_env();
 192+
 193+ prog = argv[0];
 194+ /*
 195+ * Check existence/validity of the UID of the user
 196+ * running this program. Error out if invalid.
 197+ */
 198+ uid = getuid();
 199+ if ((pw = getpwuid(uid)) == NULL) {
 200+ log_err("crit: invalid uid: (%ld)\n", (long) uid);
 201+ exit(102);
 202+ }
 203+
 204+ /*
 205+ * If there are a proper number of arguments, set
 206+ * all of them to variables. Otherwise, error out.
 207+ */
 208+ if (argc < 3) {
 209+ log_err("too few arguments\n");
 210+ exit(101);
 211+ }
 212+ target_uname = argv[1];
 213+ target_gname = argv[2];
 214+
 215+ /*
 216+ * Check to see if the user running this program
 217+ * is the user allowed to do so as defined in
 218+ * suexec.h. If not the allowed user, error out.
 219+ */
 220+ if (strcmp(SB_USER, pw->pw_name)) {
 221+ log_err("user mismatch (%s instead of %s)\n", pw->pw_name, SB_USER);
 222+ exit(103);
 223+ }
 224+
 225+ /*
 226+ * Error out if the target username is invalid.
 227+ */
 228+ if (strspn(target_uname, "1234567890") != strlen(target_uname)) {
 229+ if ((pw = getpwnam(target_uname)) == NULL) {
 230+ log_err("invalid target user name: (%s)\n", target_uname);
 231+ exit(105);
 232+ }
 233+ } else {
 234+ if ((pw = getpwuid(atoi(target_uname))) == NULL) {
 235+ log_err("invalid target user id: (%s)\n", target_uname);
 236+ exit(121);
 237+ }
 238+ }
 239+
 240+ /*
 241+ * Error out if the target group name is invalid.
 242+ */
 243+ if (strspn(target_gname, "1234567890") != strlen(target_gname)) {
 244+ if ((gr = getgrnam(target_gname)) == NULL) {
 245+ log_err("invalid target group name: (%s)\n", target_gname);
 246+ exit(106);
 247+ }
 248+ } else {
 249+ if ((gr = getgrgid(atoi(target_gname))) == NULL) {
 250+ log_err("invalid target group id: (%s)\n", target_gname);
 251+ exit(106);
 252+ }
 253+ }
 254+ gid = gr->gr_gid;
 255+ actual_gname = strdup(gr->gr_name);
 256+
 257+ /*
 258+ * Save these for later since initgroups will hose the struct
 259+ */
 260+ uid = pw->pw_uid;
 261+ actual_uname = strdup(pw->pw_name);
 262+ target_homedir = strdup(pw->pw_dir);
 263+
 264+ /*
 265+ * Log the transaction here to be sure we have an open log
 266+ * before we setuid().
 267+ */
 268+ log_no_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n",
 269+ target_uname, actual_uname,
 270+ target_gname, actual_gname,
 271+ PHP_BIN);
 272+
 273+ /*
 274+ * Error out if attempt is made to execute as root or as
 275+ * a UID less than SB_UID_MIN. Tsk tsk.
 276+ */
 277+ if ((uid == 0) || (uid < SB_UID_MIN)) {
 278+ log_err("cannot run as forbidden uid (%d/%s)\n", uid, PHP_BIN);
 279+ exit(107);
 280+ }
 281+
 282+ /*
 283+ * Error out if attempt is made to execute as root group
 284+ * or as a GID less than SB_GID_MIN. Tsk tsk.
 285+ */
 286+ if ((gid == 0) || (gid < SB_GID_MIN)) {
 287+ log_err("cannot run as forbidden gid (%d/%s)\n", gid, PHP_BIN);
 288+ exit(108);
 289+ }
 290+
 291+#ifdef USE_PROJECTS
 292+ if (getdefaultproj(actual_uname, &proj, nssbuf, sizeof(nssbuf)) == NULL) {
 293+ log_err("failed to get default project (%s: %s)",
 294+ actual_uname, strerror(errno));
 295+ exit(113);
 296+ }
 297+
 298+ if (setproject(proj.pj_name, actual_uname, TASK_FINAL) != 0) {
 299+ log_err("failed to set project (%s: %s)",
 300+ actual_uname, strerror(errno));
 301+ exit(114);
 302+ }
 303+#endif
 304+
 305+ /*
 306+ * Change UID/GID here so that the following tests work over NFS.
 307+ *
 308+ * Initialize the group access list for the target user,
 309+ * and setgid() to the target group. If unsuccessful, error out.
 310+ */
 311+ if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
 312+ log_err("failed to setgid (%ld: %s)\n", (long) gid, PHP_BIN);
 313+ exit(109);
 314+ }
 315+
 316+ /*
 317+ * setuid() to the target user. Error out on fail.
 318+ */
 319+ if ((setuid(uid)) != 0) {
 320+ log_err("failed to setuid (%ld: %s)\n", (long) uid, PHP_BIN);
 321+ exit(110);
 322+ }
 323+
 324+ if (chdir(target_homedir) != 0) {
 325+ log_err("cannot chdir to home directory (%s)\n", target_homedir);
 326+ exit(112);
 327+ }
 328+
 329+#ifdef SB_SUEXEC_UMASK
 330+ /*
 331+ * umask() uses inverse logic; bits are CLEAR for allowed access.
 332+ */
 333+ if ((~SB_SUEXEC_UMASK) & 0022) {
 334+ log_err("notice: SB_SUEXEC_UMASK of %03o allows "
 335+ "write permission to group and/or other\n", SB_SUEXEC_UMASK);
 336+ }
 337+ umask(SB_SUEXEC_UMASK);
 338+#endif /* SB_SUEXEC_UMASK */
 339+
 340+ /*
 341+ * Be sure to close the log file so the CGI can't
 342+ * mess with it. If the exec fails, it will be reopened
 343+ * automatically when log_err is called. Note that the log
 344+ * might not actually be open if SB_LOG_EXEC isn't defined.
 345+ * However, the "log" call isn't ifdef'd so let's be defensive
 346+ * and assume someone might have done something with it
 347+ * outside an ifdef'd SB_LOG_EXEC block.
 348+ */
 349+ if (log != NULL) {
 350+ fclose(log);
 351+ log = NULL;
 352+ }
 353+
 354+ /*
 355+ * Execute the command, replacing our image with its own.
 356+ */
 357+ execl(PHP_BIN, PHP_BIN, NULL);
 358+
 359+ /*
 360+ * (I can't help myself...sorry.)
 361+ *
 362+ * Uh oh. Still here. Where's the kaboom? There was supposed to be an
 363+ * EARTH-shattering kaboom!
 364+ *
 365+ * Oh well, log the failure and error out.
 366+ */
 367+ log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), PHP_BIN);
 368+ exit(255);
 369+}
Index: trunk/tools/switchboard2/swkill.c
@@ -0,0 +1,247 @@
 2+/* Licensed to the Apache Software Foundation (ASF) under one or more
 3+ * contributor license agreements. See the NOTICE file distributed with
 4+ * this work for additional information regarding copyright ownership.
 5+ * The ASF licenses this file to You under the Apache License, Version 2.0
 6+ * (the "License"); you may not use this file except in compliance with
 7+ * the License. You may obtain a copy of the License at
 8+ *
 9+ * http://www.apache.org/licenses/LICENSE-2.0
 10+ *
 11+ * Unless required by applicable law or agreed to in writing, software
 12+ * distributed under the License is distributed on an "AS IS" BASIS,
 13+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14+ * See the License for the specific language governing permissions and
 15+ * limitations under the License.
 16+ */
 17+
 18+/*
 19+ * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache
 20+ *
 21+ ***********************************************************************
 22+ *
 23+ * NOTE! : DO NOT edit this code!!! Unless you know what you are doing,
 24+ * editing this code might open up your system in unexpected
 25+ * ways to would-be crackers. Every precaution has been taken
 26+ * to make this code as safe as possible; alter it at your own
 27+ * risk.
 28+ *
 29+ ***********************************************************************
 30+ *
 31+ *
 32+ */
 33+
 34+#define SB_SUEXEC_UMASK 022
 35+
 36+#include <sys/param.h>
 37+#include <sys/stat.h>
 38+#include <sys/types.h>
 39+#include <string.h>
 40+#include <time.h>
 41+#include <unistd.h>
 42+#include <stdio.h>
 43+#include <stdarg.h>
 44+#include <stdlib.h>
 45+#include <errno.h>
 46+#include <signal.h>
 47+
 48+#include <pwd.h>
 49+#include <grp.h>
 50+
 51+#if defined(PATH_MAX)
 52+#define SB_MAXPATH PATH_MAX
 53+#elif defined(MAXPATHLEN)
 54+#define SB_MAXPATH MAXPATHLEN
 55+#else
 56+#define SB_MAXPATH 8192
 57+#endif
 58+
 59+#define SB_ENVBUF 256
 60+
 61+static FILE *log = NULL;
 62+
 63+static void err_output(int is_error, const char *fmt, va_list ap)
 64+{
 65+#ifdef SB_LOG_EXEC
 66+ time_t timevar;
 67+ struct tm *lt;
 68+
 69+ if (!log) {
 70+ if ((log = fopen(SB_LOG_EXEC, "a")) == NULL) {
 71+ fprintf(stderr, "suexec failure: could not open log file\n");
 72+ perror("fopen");
 73+ exit(1);
 74+ }
 75+ }
 76+
 77+ if (is_error) {
 78+ fprintf(stderr, "suexec policy violation: see suexec log for more "
 79+ "details\n");
 80+ }
 81+
 82+ time(&timevar);
 83+ lt = localtime(&timevar);
 84+
 85+ fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: swkill: ",
 86+ lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
 87+ lt->tm_hour, lt->tm_min, lt->tm_sec);
 88+
 89+ vfprintf(log, fmt, ap);
 90+
 91+ fflush(log);
 92+#endif /* SB_LOG_EXEC */
 93+ return;
 94+}
 95+
 96+static void log_err(const char *fmt,...)
 97+{
 98+#ifdef SB_LOG_EXEC
 99+ va_list ap;
 100+
 101+ va_start(ap, fmt);
 102+ err_output(1, fmt, ap); /* 1 == is_error */
 103+ va_end(ap);
 104+#endif /* SB_LOG_EXEC */
 105+ return;
 106+}
 107+
 108+static void log_no_err(const char *fmt,...)
 109+{
 110+#ifdef SB_LOG_EXEC
 111+ va_list ap;
 112+
 113+ va_start(ap, fmt);
 114+ err_output(0, fmt, ap); /* 0 == !is_error */
 115+ va_end(ap);
 116+#endif /* SB_LOG_EXEC */
 117+ return;
 118+}
 119+
 120+int main(int argc, char *argv[])
 121+{
 122+ char *prog;
 123+ char *target_uname, *target_gname;
 124+ char *actual_uname, *actual_gname;
 125+ uid_t uid; /* user information */
 126+ gid_t gid; /* target group placeholder */
 127+ pid_t target_pid;
 128+ struct passwd *pw; /* password entry holder */
 129+ struct group *gr; /* group entry holder */
 130+
 131+ prog = argv[0];
 132+ /*
 133+ * Check existence/validity of the UID of the user
 134+ * running this program. Error out if invalid.
 135+ */
 136+ uid = getuid();
 137+ if ((pw = getpwuid(uid)) == NULL) {
 138+ log_err("crit: invalid uid: (%ld)\n", uid);
 139+ exit(102);
 140+ }
 141+
 142+ /*
 143+ * If there are a proper number of arguments, set
 144+ * all of them to variables. Otherwise, error out.
 145+ */
 146+ if (argc < 4) {
 147+ log_err("too few arguments\n");
 148+ exit(101);
 149+ }
 150+ target_uname = argv[1];
 151+ target_gname = argv[2];
 152+ target_pid = atoi(argv[3]);
 153+
 154+ if (target_pid < 1) {
 155+ log_err("invalid target pid (%s)\n", argv[3]);
 156+ exit(102);
 157+ }
 158+
 159+ /*
 160+ * Check to see if the user running this program
 161+ * is the user allowed to do so as defined in
 162+ * suexec.h. If not the allowed user, error out.
 163+ */
 164+ if (strcmp(SB_USER, pw->pw_name)) {
 165+ log_err("user mismatch (%s instead of %s)\n", pw->pw_name, SB_USER);
 166+ exit(103);
 167+ }
 168+
 169+ /*
 170+ * Error out if the target username is invalid.
 171+ */
 172+ if (strspn(target_uname, "1234567890") != strlen(target_uname)) {
 173+ if ((pw = getpwnam(target_uname)) == NULL) {
 174+ log_err("invalid target user name: (%s)\n", target_uname);
 175+ exit(105);
 176+ }
 177+ }
 178+ else {
 179+ if ((pw = getpwuid(atoi(target_uname))) == NULL) {
 180+ log_err("invalid target user id: (%s)\n", target_uname);
 181+ exit(121);
 182+ }
 183+ }
 184+
 185+ /*
 186+ * Error out if the target group name is invalid.
 187+ */
 188+ if (strspn(target_gname, "1234567890") != strlen(target_gname)) {
 189+ if ((gr = getgrnam(target_gname)) == NULL) {
 190+ log_err("invalid target group name: (%s)\n", target_gname);
 191+ exit(106);
 192+ }
 193+ }
 194+ else {
 195+ if ((gr = getgrgid(atoi(target_gname))) == NULL) {
 196+ log_err("invalid target group id: (%s)\n", target_gname);
 197+ exit(106);
 198+ }
 199+ }
 200+ gid = gr->gr_gid;
 201+ actual_gname = strdup(gr->gr_name);
 202+
 203+ /*
 204+ * Save these for later since initgroups will hose the struct
 205+ */
 206+ uid = pw->pw_uid;
 207+ actual_uname = strdup(pw->pw_name);
 208+
 209+ /*
 210+ * Error out if attempt is made to execute as root or as
 211+ * a UID less than SB_UID_MIN. Tsk tsk.
 212+ */
 213+ if ((uid == 0) || (uid < SB_UID_MIN)) {
 214+ log_err("cannot run as forbidden uid (%d)\n", uid);
 215+ exit(107);
 216+ }
 217+
 218+ /*
 219+ * Error out if attempt is made to execute as root group
 220+ * or as a GID less than SB_GID_MIN. Tsk tsk.
 221+ */
 222+ if ((gid == 0) || (gid < SB_GID_MIN)) {
 223+ log_err("cannot run as forbidden gid (%d)\n", gid);
 224+ exit(108);
 225+ }
 226+
 227+ /*
 228+ * Change UID/GID here so that the following tests work over NFS.
 229+ *
 230+ * Initialize the group access list for the target user,
 231+ * and setgid() to the target group. If unsuccessful, error out.
 232+ */
 233+ if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
 234+ log_err("failed to setgid (%ld)\n", gid);
 235+ exit(109);
 236+ }
 237+
 238+ /*
 239+ * setuid() to the target user. Error out on fail.
 240+ */
 241+ if ((setuid(uid)) != 0) {
 242+ log_err("failed to setuid (%ld)\n", uid);
 243+ exit(110);
 244+ }
 245+
 246+ kill(target_pid, 9);
 247+ exit(0);
 248+}
Index: trunk/tools/switchboard2/config.cc
@@ -0,0 +1,296 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#include <string>
 12+#include <fstream>
 13+#include <cerrno>
 14+
 15+#include <boost/format.hpp>
 16+#include <boost/tokenizer.hpp>
 17+#include <boost/assign/list_of.hpp>
 18+#include <boost/bind.hpp>
 19+#include <boost/lexical_cast.hpp>
 20+using boost::format;
 21+
 22+#include <log4cxx/logger.h>
 23+
 24+#include "config.h"
 25+
 26+config mainconf;
 27+
 28+config::config()
 29+ : max_procs(0)
 30+ , max_procs_per_user(0)
 31+{
 32+}
 33+
 34+configuration_loader::configuration_loader()
 35+ : logger(log4cxx::Logger::getLogger("config"))
 36+{
 37+}
 38+
 39+bool
 40+configuration_loader::load(std::string const &filename, config &newconf)
 41+{
 42+ std::string line;
 43+ std::ifstream file(filename.c_str());
 44+
 45+ lineno_ = 0;
 46+ file_ = filename;
 47+
 48+ if (!file) {
 49+ LOG4CXX_ERROR(logger,
 50+ format("cannot open configuration file \"%s\": %s")
 51+ % filename % std::strerror(errno));
 52+ return false;
 53+ }
 54+
 55+ while (std::getline(file, line)) {
 56+ lineno_++;
 57+
 58+ if (line.empty() || line[0] == '#')
 59+ continue;
 60+
 61+ if (!parse_config_line(line, newconf))
 62+ return false;
 63+ }
 64+
 65+ LOG4CXX_INFO(logger,
 66+ format("read configuration from \"%s\"")
 67+ % filename);
 68+
 69+ return true;
 70+}
 71+
 72+std::map<std::string, configuration_loader::confline_t>
 73+ configuration_loader::conflines =
 74+ boost::assign::map_list_of
 75+ ("listen", boost::bind(&configuration_loader::f_listen, _1, _2, _3))
 76+ ("logconf", boost::bind(&configuration_loader::f_logconf, _1, _2, _3))
 77+ ("sockdir", boost::bind(&configuration_loader::f_sockdir, _1, _2, _3))
 78+ ("docroot", boost::bind(&configuration_loader::f_docroot, _1, _2, _3))
 79+ ("userdir", boost::bind(&configuration_loader::f_userdir, _1, _2, _3))
 80+ ("max-procs", boost::bind(&configuration_loader::f_max_procs, _1, _2, _3))
 81+ ("max-procs-per-user", boost::bind(&configuration_loader::f_max_procs_per_user, _1, _2, _3))
 82+ ("max-q-per-user", boost::bind(&configuration_loader::f_max_q_per_user, _1, _2, _3))
 83+ ("server-type", boost::bind(&configuration_loader::f_server_type, _1, _2, _3))
 84+ ;
 85+
 86+bool
 87+configuration_loader::parse_config_line(
 88+ std::string const &line,
 89+ config &newconf)
 90+{
 91+ boost::escaped_list_separator<char> sep('\\', ' ', '"');
 92+ boost::tokenizer<boost::escaped_list_separator<char> > tok(line, sep);
 93+ std::vector<std::string> fields;
 94+
 95+ std::copy(tok.begin(), tok.end(), std::back_inserter(fields));
 96+
 97+ if (fields.empty())
 98+ return false;
 99+
 100+ std::map<std::string, confline_t>::iterator it =
 101+ conflines.find(fields[0]);
 102+
 103+ if ((it = conflines.find(fields[0])) == conflines.end()) {
 104+ LOG4CXX_ERROR(logger,
 105+ format("\"%s\", line %d: unknown directive: \"%s\"")
 106+ % file_ % lineno_ % fields[0]);
 107+ return false;
 108+ }
 109+
 110+ it->second(this, fields, newconf);
 111+
 112+ return true;
 113+}
 114+
 115+bool
 116+configuration_loader::f_listen(
 117+ std::vector<std::string> &fields,
 118+ config &newconf)
 119+{
 120+ if (fields.size() < 2 ||
 121+ (fields[1][0] == '/' && fields.size() != 2) ||
 122+ (fields[1][0] != '/' && fields.size() != 3)) {
 123+ LOG4CXX_ERROR(logger,
 124+ format("\"%s\", line %d: usage: listen <address> <port>\n")
 125+ % file_ % lineno_);
 126+ LOG4CXX_ERROR(logger,
 127+ format("\"%s\", line %d: listen <pathname>\n")
 128+ % file_ % lineno_);
 129+ return false;
 130+ }
 131+
 132+ conf_listener lsnr;
 133+ lsnr.host = fields[1];
 134+ if (fields.size() == 3)
 135+ lsnr.port = fields[2];
 136+ newconf.listeners.push_back(lsnr);
 137+ return true;
 138+}
 139+
 140+bool
 141+configuration_loader::f_logconf(
 142+ std::vector<std::string> &fields,
 143+ config &newconf)
 144+{
 145+ if (fields.size() != 2) {
 146+ LOG4CXX_ERROR(logger,
 147+ format("\"%s\", line %d: usage: logconf <file>\n")
 148+ % file_ % lineno_);
 149+ return false;
 150+ }
 151+
 152+ newconf.logconf = fields[1];
 153+ return true;
 154+}
 155+
 156+bool
 157+configuration_loader::f_sockdir(
 158+ std::vector<std::string> &fields,
 159+ config &newconf)
 160+{
 161+ if (fields.size() != 2) {
 162+ LOG4CXX_ERROR(logger,
 163+ format("\"%s\", line %d: usage: sockdir <directory>\n")
 164+ % file_ % lineno_);
 165+ return false;
 166+ }
 167+
 168+ newconf.sockdir = fields[1];
 169+ return true;
 170+}
 171+
 172+bool
 173+configuration_loader::f_userdir(
 174+ std::vector<std::string> &fields,
 175+ config &newconf)
 176+{
 177+ if (fields.size() != 2) {
 178+ LOG4CXX_ERROR(logger,
 179+ format("\"%s\", line %d: usage: userdir <directory>\n")
 180+ % file_ % lineno_);
 181+ return false;
 182+ }
 183+
 184+ newconf.userdir = fields[1];
 185+ return true;
 186+}
 187+
 188+bool
 189+configuration_loader::f_docroot(
 190+ std::vector<std::string> &fields,
 191+ config &newconf)
 192+{
 193+ if (fields.size() != 2) {
 194+ LOG4CXX_ERROR(logger,
 195+ format("\"%s\", line %d: usage: docroot <directory>\n")
 196+ % file_ % lineno_);
 197+ return false;
 198+ }
 199+
 200+ newconf.docroot = fields[1];
 201+ return true;
 202+}
 203+
 204+bool
 205+configuration_loader::f_max_procs(
 206+ std::vector<std::string> &fields,
 207+ config &newconf)
 208+{
 209+ if (fields.size() != 2) {
 210+ LOG4CXX_ERROR(logger,
 211+ format("\"%s\", line %d: usage: max-procs <number>\n")
 212+ % file_ % lineno_);
 213+ return false;
 214+ }
 215+
 216+ try {
 217+ newconf.max_procs = boost::lexical_cast<int>(fields[1]);
 218+ return true;
 219+ } catch (boost::bad_lexical_cast &e) {
 220+ LOG4CXX_ERROR(logger,
 221+ format("\"%s\", line %d: usage: max-procs <number>\n")
 222+ % file_ % lineno_);
 223+ return false;
 224+ }
 225+}
 226+
 227+bool
 228+configuration_loader::f_max_procs_per_user(
 229+ std::vector<std::string> &fields,
 230+ config &newconf)
 231+{
 232+ if (fields.size() != 2) {
 233+ LOG4CXX_ERROR(logger,
 234+ format("\"%s\", line %d: usage: max-procs-per-user <number>\n")
 235+ % file_ % lineno_);
 236+ return false;
 237+ }
 238+
 239+ try {
 240+ newconf.max_procs_per_user = boost::lexical_cast<int>(fields[1]);
 241+ return true;
 242+ } catch (boost::bad_lexical_cast &e) {
 243+ LOG4CXX_ERROR(logger,
 244+ format("\"%s\", line %d: usage: max-procs-per-user <number>\n")
 245+ % file_ % lineno_);
 246+ return false;
 247+ }
 248+}
 249+
 250+bool
 251+configuration_loader::f_max_q_per_user(
 252+ std::vector<std::string> &fields,
 253+ config &newconf)
 254+{
 255+ if (fields.size() != 2) {
 256+ LOG4CXX_ERROR(logger,
 257+ format("\"%s\", line %d: usage: max-q-per-user <number>\n")
 258+ % file_ % lineno_);
 259+ return false;
 260+ }
 261+
 262+ try {
 263+ newconf.max_q_per_user = boost::lexical_cast<int>(fields[1]);
 264+ return true;
 265+ } catch (boost::bad_lexical_cast &e) {
 266+ LOG4CXX_ERROR(logger,
 267+ format("\"%s\", line %d: usage: max-q-per-user <number>\n")
 268+ % file_ % lineno_);
 269+ return false;
 270+ }
 271+}
 272+
 273+bool
 274+configuration_loader::f_server_type(
 275+ std::vector<std::string> &fields,
 276+ config &newconf)
 277+{
 278+ if (fields.size() != 2) {
 279+ LOG4CXX_ERROR(logger,
 280+ format("\"%s\", line %d: usage: server-type <sjs|apache>\n")
 281+ % file_ % lineno_);
 282+ return false;
 283+ }
 284+
 285+ if (fields[1] == "sjs")
 286+ newconf.servtype = serv_sjs;
 287+ else if (fields[1] == "apache")
 288+ newconf.servtype = serv_apache;
 289+ else {
 290+ LOG4CXX_ERROR(logger,
 291+ format("\"%s\", line %d: usage: server-type <sjs|apache>\n")
 292+ % file_ % lineno_);
 293+ return false;
 294+ }
 295+
 296+ return true;
 297+}
Index: trunk/tools/switchboard2/process_factory.cc
@@ -0,0 +1,184 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#include <sys/stat.h>
 12+#include <pwd.h>
 13+
 14+#include <cerrno>
 15+
 16+#include "process_factory.h"
 17+#include "config.h"
 18+#include "util.h"
 19+
 20+process_factory &
 21+process_factory::instance()
 22+{
 23+ static process_factory inst;
 24+ return inst;
 25+}
 26+
 27+process_factory::process_factory()
 28+ : id_(0)
 29+{
 30+}
 31+
 32+process_factory::~process_factory()
 33+{
 34+}
 35+
 36+processp
 37+process_factory::get_process(
 38+ std::map<std::string, std::string> &params)
 39+{
 40+ std::map<std::string, std::string>::const_iterator it;
 41+
 42+ uid_t uid;
 43+ gid_t gid;
 44+
 45+ std::string script_path;
 46+
 47+ /*
 48+ * Trying to find the script from path the env is a mess. Under SJS
 49+ * web server, we take the value from SCRIPT_NAME, and translate it
 50+ * into a path on disk using the 'docroot' and 'userdir' configuration
 51+ * options. Under Apache, this doesn't work, because SCRIPT_NAME
 52+ * contains garbage. Instead we take PATH_TRANSLATED, which is the
 53+ * on-disk path with the PATH_INFO appended, and remove path components
 54+ * from it until we end up with a path which exists.
 55+ *
 56+ * Other web servers might require different handling; I haven't tested
 57+ * any other than SJS and Apache.
 58+ */
 59+ if (mainconf.servtype == serv_apache) {
 60+ if ((it = params.find("PATH_TRANSLATED")) == params.end())
 61+ throw creation_failure("PATH_TRANSLATED not specified");
 62+
 63+ std::string s = it->second;
 64+ struct stat sb;
 65+ bool err = false;
 66+
 67+ while (stat(s.c_str(), &sb) == -1
 68+ && errno == ENOTDIR)
 69+ {
 70+ std::string::size_type n;
 71+ if ((n = s.rfind('/')) == std::string::npos)
 72+ throw creation_failure("script not found");
 73+
 74+ s.erase(n);
 75+ }
 76+
 77+ script_path = s;
 78+ } else if (mainconf.servtype == serv_sjs) {
 79+ if ((it = params.find("SCRIPT_NAME")) == params.end())
 80+ throw creation_failure("neither SCRIPT_NAME nor PATH_TRANSLATED specified");
 81+
 82+ std::string script_name = it->second;
 83+ if (script_name.empty())
 84+ throw creation_failure("SCRIPT_NAME is empty");
 85+
 86+ if (script_name.size() >= 2 &&
 87+ script_name[0] == '/' && script_name[1] == '~') {
 88+ /*
 89+ * The format is /~user/path/to/script.php
 90+ * We need to change it to
 91+ * /home/user/public_html/path/to/script.php.
 92+ */
 93+ std::string username;
 94+ std::string script;
 95+ script_name.erase(script_name.begin(), script_name.begin() + 2);
 96+ if (script_name.empty())
 97+ throw creation_failure("invalid SCRIPT_NAME");
 98+ std::string::size_type n = script_name.find('/');
 99+ username.assign(script_name.begin(), script_name.begin() + n);
 100+ script.assign(script_name.begin() + n + 1, script_name.end());
 101+
 102+ struct passwd *pwd;
 103+ if ((pwd = getpwnam(username.c_str())) == NULL)
 104+ throw creation_failure("user does not exist");
 105+ script_path = std::string(pwd->pw_dir) + '/' + mainconf.userdir
 106+ + '/' + script;
 107+ } else {
 108+ /*
 109+ * Script is relative to docroot.
 110+ */
 111+ script_path = mainconf.docroot + script_name;
 112+ }
 113+
 114+ params["SCRIPT_FILENAME"] = script_path;
 115+ }
 116+
 117+ struct stat sb;
 118+ if (lstat(script_path.c_str(), &sb) == 0) {
 119+ uid = sb.st_uid;
 120+ gid = sb.st_gid;
 121+
 122+ /*
 123+ * Make sure the path is under the docroot or the
 124+ * user's userdir.
 125+ */
 126+ std::string x = mainconf.docroot + '/';
 127+ if (script_path.substr(0, x.size()) != x) {
 128+ struct passwd *pwd = getpwuid(uid);
 129+ if (pwd == NULL)
 130+ throw creation_failure("script owner doesn't exist");
 131+ x = std::string(pwd->pw_dir) + '/' + mainconf.userdir + '/';
 132+ if (script_path.substr(0, x.size()) != x)
 133+ throw creation_failure("script not under docroot or userdir");
 134+ }
 135+ } else
 136+ throw creation_failure("script doesn't exist");
 137+
 138+ std::ostringstream strm;
 139+ strm << mainconf.sockdir << '/' << "php_" << uid << "_" << id_++;
 140+
 141+ /*
 142+ * Look for a process in the freelist.
 143+ */
 144+ int mppu = mainconf.max_procs_per_user;
 145+ int mp = mainconf.max_procs;
 146+
 147+ pthread_mutex_lock(&lock_);
 148+ int &thisuser = peruser_[uid];
 149+
 150+ while ( (mppu > 0 && thisuser >= mppu) ||
 151+ (mp > 0 && curprocs_ > mp))
 152+ {
 153+ pthread_cond_wait(&curprocs_cond, &lock_);
 154+ }
 155+
 156+ peruser_[uid]++;
 157+ curprocs_++;
 158+
 159+ freelist_t::index<by_uid>::type &idx = freelist_.get<by_uid>();
 160+ freelist_t::index<by_uid>::type::iterator fit;
 161+ if ((fit = idx.find(uid)) != idx.end()) {
 162+ processp p = fit->proc;
 163+ idx.erase(fit);
 164+ pthread_mutex_unlock(&lock_);
 165+ return p;
 166+ }
 167+
 168+ pthread_mutex_unlock(&lock_);
 169+
 170+ return processp(new process(uid, gid, strm.str()));
 171+}
 172+
 173+void
 174+process_factory::release_process(processp proc)
 175+{
 176+ free_process p;
 177+ p.uid = proc->uid();
 178+ p.proc = proc;
 179+
 180+ lock lck(lock_);
 181+ freelist_.push_back(p);
 182+ curprocs_--;
 183+ peruser_[p.uid]--;
 184+ pthread_cond_signal(&curprocs_cond);
 185+}
Index: trunk/tools/switchboard2/config.h
@@ -0,0 +1,84 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#ifndef CONFIG_H
 12+#define CONFIG_H
 13+
 14+#include <string>
 15+#include <vector>
 16+#include <stdexcept>
 17+#include <map>
 18+
 19+#include <boost/function.hpp>
 20+
 21+#include <log4cxx/logger.h>
 22+
 23+struct config_error : std::runtime_error {
 24+ config_error(char const *what) : std::runtime_error(what) {}
 25+};
 26+
 27+struct conf_listener {
 28+ std::string host;
 29+ std::string port;
 30+};
 31+
 32+enum server_type {
 33+ serv_unknown = 0,
 34+ serv_sjs,
 35+ serv_apache
 36+};
 37+
 38+struct config {
 39+ config();
 40+
 41+ std::vector<conf_listener>
 42+ listeners;
 43+ std::string logconf;
 44+ std::string sockdir;
 45+ std::string docroot;
 46+ std::string userdir;
 47+ int max_procs;
 48+ int max_procs_per_user;
 49+ int max_q_per_user;
 50+ server_type servtype;
 51+};
 52+
 53+struct configuration_loader {
 54+ configuration_loader();
 55+
 56+ bool load(std::string const &filename, config &newconf);
 57+
 58+private:
 59+ bool parse_config_line(std::string const &line, config &newconf);
 60+
 61+ int lineno_;
 62+ std::string file_;
 63+
 64+ typedef boost::function<bool (configuration_loader *,
 65+ std::vector<std::string> &fields,
 66+ config &newconf)> confline_t;
 67+
 68+ static std::map<std::string, confline_t> conflines;
 69+
 70+ bool f_listen(std::vector<std::string> &fields, config &newconf);
 71+ bool f_logconf(std::vector<std::string> &fields, config &newconf);
 72+ bool f_sockdir(std::vector<std::string> &fields, config &newconf);
 73+ bool f_docroot(std::vector<std::string> &fields, config &newconf);
 74+ bool f_userdir(std::vector<std::string> &fields, config &newconf);
 75+ bool f_max_procs(std::vector<std::string> &fields, config &newconf);
 76+ bool f_max_procs_per_user(std::vector<std::string> &fields, config &newconf);
 77+ bool f_max_q_per_user(std::vector<std::string> &fields, config &newconf);
 78+ bool f_server_type(std::vector<std::string> &fields, config &newconf);
 79+
 80+ log4cxx::LoggerPtr logger;
 81+};
 82+
 83+extern config mainconf;
 84+
 85+#endif
Index: trunk/tools/switchboard2/process_factory.h
@@ -0,0 +1,66 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#ifndef PROCESS_FACTORY_H
 12+#define PROCESS_FACTORY_H
 13+
 14+#include <map>
 15+#include <string>
 16+
 17+#include <pthread.h>
 18+
 19+#include <boost/noncopyable.hpp>
 20+#include <boost/multi_index_container.hpp>
 21+#include <boost/multi_index/indexed_by.hpp>
 22+#include <boost/multi_index/member.hpp>
 23+#include <boost/multi_index/ordered_index.hpp>
 24+#include <boost/multi_index/tag.hpp>
 25+#include <boost/multi_index/sequenced_index.hpp>
 26+
 27+#include "process.h"
 28+
 29+namespace mi = boost::multi_index;
 30+
 31+struct free_process {
 32+ uid_t uid;
 33+ processp proc;
 34+};
 35+
 36+struct by_uid {};
 37+typedef boost::multi_index_container<
 38+ free_process,
 39+ mi::indexed_by<
 40+ mi::sequenced<>,
 41+ mi::ordered_non_unique<mi::tag<by_uid>,
 42+ mi::member<free_process, uid_t, &free_process::uid> >
 43+ >
 44+> freelist_t;
 45+
 46+struct process_factory : boost::noncopyable {
 47+ static process_factory &instance();
 48+
 49+ process_factory();
 50+ ~process_factory();
 51+
 52+ processp get_process(std::map<std::string, std::string> &params);
 53+ void release_process(processp proc);
 54+
 55+private:
 56+ int id_;
 57+ freelist_t freelist_;
 58+ pthread_mutex_t lock_;
 59+
 60+ std::map<uid_t, int> peruser_;
 61+ int curprocs_;
 62+
 63+ //pthread_mutex_t curprocs_mtx;
 64+ pthread_cond_t curprocs_cond;
 65+};
 66+
 67+#endif /* !PROCESS_FACTORY_H */
Index: trunk/tools/switchboard2/fcgi.cc
@@ -0,0 +1,145 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#include <iostream>
 12+#include <cerrno>
 13+#include <cstring>
 14+#include <unistd.h>
 15+
 16+#include "fcgi.h"
 17+
 18+namespace fcgi {
 19+
 20+void
 21+pretty_print_string(std::ostream &strm, std::string const &str)
 22+{
 23+ strm << '"';
 24+ for (int i = 0; i < str.size(); ++i) {
 25+ if (str[i] > 0x1F && str[i] < 0x7F)
 26+ strm << str[i];
 27+ else
 28+ strm << "\\0" << std::oct << (int)str[i];
 29+ }
 30+ strm << '"';
 31+}
 32+
 33+void
 34+pretty_print_record(std::ostream &strm, fcgi::record const &rec)
 35+{
 36+ switch (rec.type_) {
 37+ case rectype::begin_request:
 38+ strm << "{FCGI_BEGIN_REQUEST, ";
 39+ strm << rec.request_id() << ", ";
 40+ break;
 41+
 42+ case rectype::end_request:
 43+ strm << "{FCGI_END_REQUEST, ";
 44+ strm << rec.request_id() << ", ";
 45+ break;
 46+
 47+ case rectype::params:
 48+ strm << "{FCGI_PARAMS, ";
 49+ strm << rec.request_id() << ", ";
 50+ pretty_print_string(strm, std::string(rec.contentData.begin(), rec.contentData.end()));
 51+ break;
 52+
 53+ case rectype::stdin_:
 54+ strm << "{FCGI_STDIN, ";
 55+ strm << rec.request_id() << ", ";
 56+ pretty_print_string(strm, std::string(rec.contentData.begin(), rec.contentData.end()));
 57+ break;
 58+
 59+ case rectype::stdout_:
 60+ strm << "{FCGI_STDOUT, ";
 61+ strm << rec.request_id() << ", ";
 62+ pretty_print_string(strm, std::string(rec.contentData.begin(), rec.contentData.end()));
 63+ break;
 64+
 65+ default:
 66+ strm << "{unknown type, ";
 67+ strm << rec.request_id() << ", ";
 68+ break;
 69+ }
 70+
 71+ strm << "}\n";
 72+}
 73+
 74+bool
 75+read_fcgi_record(int fd, fcgi::record *rec)
 76+{
 77+ /*
 78+ * Format:
 79+ * version 1 byte
 80+ * type 1 byte
 81+ * request id 2 bytes
 82+ * content length 2 bytes
 83+ * padding length 1 byte
 84+ * reserved 1 byte
 85+ * content data variable
 86+ * padding data variable
 87+ */
 88+
 89+ ssize_t i;
 90+
 91+ if ((i = read(fd, static_cast<void *>(rec), 8)) < 8) {
 92+ std::fprintf(stderr, "couldn't read entire record header\n");
 93+ if (i == -1)
 94+ std::fprintf(stderr, " error: %s\n", std::strerror(errno));
 95+ else
 96+ std::fprintf(stderr, " bytes read = %d\n", i);
 97+ return false;
 98+ }
 99+
 100+ rec->contentData.resize(rec->content_length());
 101+ rec->paddingData.resize(rec->padding_length());
 102+
 103+ if (rec->content_length() > 0) {
 104+ if (read(fd, &rec->contentData[0], rec->content_length()) <
 105+ rec->content_length()) {
 106+ std::fprintf(stderr, "couldn't read entire content\n");
 107+ return false;
 108+ }
 109+ }
 110+
 111+ if (rec->padding_length() > 0) {
 112+ if (read(fd, &rec->paddingData[0], rec->padding_length()) <
 113+ rec->padding_length()) {
 114+ std::fprintf(stderr, "couldn't read entire padding\n");
 115+ return false;
 116+ }
 117+ }
 118+
 119+#if 0
 120+ std::cerr << "read: ";
 121+ pretty_print_record(std::cerr, *rec);
 122+#endif
 123+
 124+ return true;
 125+}
 126+
 127+bool
 128+write_fcgi_record(int fd, fcgi::record const &rec)
 129+{
 130+#if 0
 131+ std::cerr << "write: ";
 132+ pretty_print_record(std::cerr, rec);
 133+#endif
 134+
 135+ write(fd, static_cast<void const *>(&rec), 8);
 136+ if (rec.content_length() > 0)
 137+ write(fd, static_cast<void const *>(&rec.contentData[0]),
 138+ rec.content_length());
 139+ if (rec.padding_length() > 0)
 140+ write(fd, static_cast<void const *>(&rec.paddingData[0]),
 141+ rec.padding_length());
 142+
 143+ return true;
 144+}
 145+
 146+} // namespace fcgi
Index: trunk/tools/switchboard2/fcgi.h
@@ -0,0 +1,199 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id: fcgi.h 38741 2008-08-06 21:00:45Z river $ */
 10+
 11+#ifndef FCGI_H
 12+#define FCGI_H
 13+
 14+#include <vector>
 15+#include <stdexcept>
 16+#include <map>
 17+
 18+#include <boost/shared_ptr.hpp>
 19+
 20+namespace fcgi {
 21+
 22+ namespace rectype {
 23+ enum rectype_t {
 24+ begin_request = 1,
 25+ abort_request = 2,
 26+ end_request = 3,
 27+ params = 4,
 28+ stdin_ = 5,
 29+ stdout_ = 6,
 30+ stderr_ = 7,
 31+ data = 8,
 32+ get_values = 9,
 33+ get_values_result = 10,
 34+ unknown = 11
 35+ };
 36+ }
 37+
 38+ namespace role {
 39+ enum role_t {
 40+ responder = 1,
 41+ authorizer = 2,
 42+ filter = 3
 43+ };
 44+ }
 45+
 46+ struct record {
 47+ unsigned char version_;
 48+ unsigned char type_;
 49+ unsigned char requestId1_;
 50+ unsigned char requestId0_;
 51+ unsigned char contentLength1_;
 52+ unsigned char contentLength0_;
 53+ unsigned char paddingLength_;
 54+ unsigned char reserved_;
 55+ std::vector<unsigned char> contentData;
 56+ std::vector<unsigned char> paddingData;
 57+
 58+ int version() const {
 59+ return version_;
 60+ }
 61+
 62+ rectype::rectype_t type() const {
 63+ return rectype::rectype_t(type_);
 64+ }
 65+
 66+ std::size_t content_length() const {
 67+ return (contentLength1_ << 8) | contentLength0_;
 68+ }
 69+
 70+ void content_length(std::size_t n) {
 71+ contentLength1_ = n >> 8;
 72+ contentLength0_ = n & 0xFF;
 73+ }
 74+
 75+ std::size_t padding_length() const {
 76+ return paddingLength_;
 77+ }
 78+
 79+ int request_id() const {
 80+ return (requestId1_ << 8) | requestId0_;
 81+ }
 82+
 83+ void request_id(int n) {
 84+ requestId1_ = n >> 8;
 85+ requestId0_ = n & 0xFF;
 86+ }
 87+ };
 88+
 89+ struct begin_request_payload {
 90+ unsigned char role1_;
 91+ unsigned char role0_;
 92+ unsigned char flags_;
 93+ unsigned char reserved[5];
 94+
 95+ role::role_t role() const {
 96+ return role::role_t((role1_ << 8) | role0_);
 97+ }
 98+
 99+ int flags() const {
 100+ return flags_;
 101+ }
 102+ };
 103+
 104+ typedef boost::shared_ptr<record> recordp;
 105+
 106+ typedef std::map<std::string, std::string> params;
 107+
 108+ struct short_data : std::runtime_error {
 109+ short_data() : std::runtime_error("FastCGI parameter data is truncated") {}
 110+ };
 111+
 112+ template<typename InputIterator>
 113+ int decode_length(InputIterator &begin, InputIterator end)
 114+ {
 115+ if (std::distance(begin, end) < 1)
 116+ throw short_data();
 117+
 118+ int len;
 119+ if (*begin <= 0x7F) {
 120+ len = *begin;
 121+ begin++;
 122+ return len;
 123+ }
 124+
 125+ if (std::distance(begin, end) < 4)
 126+ throw short_data();
 127+
 128+ len = (int)((*begin & 0x7F) << 24)
 129+ | (*(begin + 1) << 16)
 130+ | (*(begin + 2) << 8)
 131+ | *(begin + 3);
 132+ begin += 4;
 133+ return len;
 134+ }
 135+
 136+ template<typename InputIterator, typename OutputIterator>
 137+ void decode_params(
 138+ InputIterator begin,
 139+ InputIterator end,
 140+ OutputIterator output)
 141+ {
 142+ /*
 143+ * Params are encoded as <name len><value len><name><value>.
 144+ * Lengths are either 1 byte < 0x7f, or 4 bytes.
 145+ */
 146+ while (std::distance(begin, end) > 0) {
 147+ int namelen, valuelen;
 148+
 149+ namelen = decode_length(begin, end);
 150+ valuelen = decode_length(begin, end);
 151+
 152+ if (std::distance(begin, end) < (namelen + valuelen))
 153+ throw short_data();
 154+
 155+ std::string name(begin, begin + namelen);
 156+ std::string value(begin + namelen, begin + namelen + valuelen);
 157+ *output++ = std::make_pair(name, value);
 158+ begin += (namelen + valuelen);
 159+ }
 160+ }
 161+
 162+ template<typename OutputIterator>
 163+ void encode_length(std::size_t s, OutputIterator &output) {
 164+ if (s <= 127) {
 165+ *output++ = (unsigned char) s;
 166+ } else {
 167+ *output++ = (unsigned char) (((s & 0xFF000000) >> 24) | 0x80);
 168+ *output++ = (unsigned char) ((s & 0x00FF0000) >> 16);
 169+ *output++ = (unsigned char) ((s & 0x0000FF00) >> 8);
 170+ *output++ = (unsigned char) (s & 0x000000FF);
 171+ }
 172+ }
 173+
 174+ template<typename InputIterator, typename OutputIterator>
 175+ void encode_params(
 176+ InputIterator begin,
 177+ InputIterator end,
 178+ OutputIterator output)
 179+ {
 180+ while (begin != end) {
 181+ std::pair<std::string, std::string> const &p(*begin);
 182+
 183+ encode_length(p.first.size(), output);
 184+ encode_length(p.second.size(), output);
 185+
 186+ std::copy(p.first.begin(), p.first.end(), output);
 187+ std::copy(p.second.begin(), p.second.end(), output);
 188+
 189+ begin++;
 190+ }
 191+ }
 192+
 193+ bool read_fcgi_record(int fd, record *rec);
 194+ bool write_fcgi_record(int fd, record const &rec);
 195+
 196+ void pretty_print_record(std::ostream &strm, record const &rec);
 197+
 198+} // namespace fcgi
 199+
 200+#endif
Index: trunk/tools/switchboard2/process.cc
@@ -0,0 +1,121 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#include <sys/types.h>
 12+#include <sys/socket.h>
 13+#include <sys/un.h>
 14+#include <sys/stat.h>
 15+#include <unistd.h>
 16+
 17+#include <cstring>
 18+
 19+#include <boost/format.hpp>
 20+#include <log4cxx/logger.h>
 21+
 22+#include "process.h"
 23+
 24+process::process(
 25+ uid_t uid, gid_t gid,
 26+ std::string const &bindpath)
 27+ : bindpath_(bindpath)
 28+ , uid_(uid)
 29+ , gid_(gid)
 30+ , logger(log4cxx::Logger::getLogger("process"))
 31+{
 32+ LOG4CXX_DEBUG(logger, boost::format("creating new process; uid=%d bindpath=%s")
 33+ % uid % bindpath);
 34+ int sock;
 35+ char uids[64], gids[64];
 36+
 37+ /*
 38+ * Create a listening socket that we'll use to connect to the child.
 39+ */
 40+ if ((sock = ::socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
 41+ throw creation_failure("socket failed");
 42+
 43+ unlink(bindpath.c_str());
 44+ struct sockaddr_un addr;
 45+ std::memset(&addr, 0, sizeof(addr));
 46+ addr.sun_family = AF_UNIX;
 47+ std::strcpy(addr.sun_path, bindpath.c_str());
 48+
 49+ LOG4CXX_DEBUG(logger, "binding...");
 50+ if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1)
 51+ throw creation_failure("startup failed");
 52+
 53+ chmod(bindpath.c_str(), 0777);
 54+
 55+ LOG4CXX_DEBUG(logger, "listening...");
 56+ if (listen(sock, 1) == -1)
 57+ throw creation_failure("listen failed");
 58+
 59+ /*
 60+ * Spawn a new php-cgi process.
 61+ */
 62+ switch(pid_ = fork()) {
 63+ case -1:
 64+ throw creation_failure("can't fork");
 65+
 66+ case 0:
 67+ snprintf(uids, sizeof(uids), "%lu", (unsigned long) uid);
 68+ snprintf(gids, sizeof(gids), "%lu", (unsigned long) gid);
 69+
 70+ close(0);
 71+ close(1);
 72+ close(2);
 73+ dup2(sock, 0);
 74+ close(sock);
 75+ execl(PREFIX "/lib/switchboard/swexec", "swexec", uids, gids, NULL);
 76+ _exit(1);
 77+
 78+ default:
 79+ close(sock);
 80+ break;
 81+ }
 82+ LOG4CXX_DEBUG(logger, boost::format("process created; pid=%d") % pid_);
 83+}
 84+
 85+process::~process()
 86+{
 87+ /*
 88+ * Because the process is setuid, trying to kill it normally won't work.
 89+ * We use the swkill wrapper, which is similar to swexec, except it kills
 90+ * a process.
 91+ */
 92+ char uids[64];
 93+ char gids[64];
 94+ char pids[64];
 95+
 96+ LOG4CXX_DEBUG(logger, boost::format("process@%p destroyed, killing pid %d") % this % pid_);
 97+
 98+ switch(fork()) {
 99+ case -1:
 100+ return;
 101+
 102+ case 0:
 103+ snprintf(uids, sizeof(uids), "%lu", (unsigned long) uid_);
 104+ snprintf(gids, sizeof(gids), "%lu", (unsigned long) gid_);
 105+ snprintf(pids, sizeof(pids), "%lu", (unsigned long) pid_);
 106+
 107+ execl(PREFIX "/lib/switchboard/swkill", "swkill", uids, gids, pids, NULL);
 108+ _exit(1);
 109+ }
 110+}
 111+
 112+int
 113+process::connect(int socket)
 114+{
 115+ LOG4CXX_DEBUG(logger, boost::format("connecting to process socket... %s") % bindpath_);
 116+ struct sockaddr_un addr;
 117+ memset(&addr, 0, sizeof(addr));
 118+ addr.sun_family = AF_UNIX;
 119+ strcpy(addr.sun_path, bindpath_.c_str());
 120+
 121+ return ::connect(socket, (sockaddr *) &addr, sizeof(addr));
 122+}
Index: trunk/tools/switchboard2/request_thread.cc
@@ -0,0 +1,325 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#include <iostream>
 12+#include <cerrno>
 13+
 14+#include <sys/types.h>
 15+#include <sys/socket.h>
 16+#include <sys/un.h>
 17+
 18+#include <fcntl.h>
 19+#include <pthread.h>
 20+
 21+#include "request_thread.h"
 22+#include "fcgi.h"
 23+#include "process_factory.h"
 24+#include "util.h"
 25+
 26+namespace {
 27+
 28+/*
 29+ * This is the number of idle threads, not total.
 30+ */
 31+#if 0
 32+int nthreads;
 33+pthread_mutex_t nthreads_lock = PTHREAD_MUTEX_INITIALIZER;
 34+
 35+work_queue<request_thread *> work;
 36+#endif
 37+
 38+extern "C" void*
 39+do_start_thread(void *arg) {
 40+#if 0
 41+ for (;;) {
 42+ request_thread *req = work.wait();
 43+
 44+ try {
 45+ req->start_request();
 46+ } catch (std::exception &e) {
 47+ std::fprintf(stderr, "exception handling request: %s\n", e.what());
 48+ }
 49+
 50+ delete req;
 51+
 52+#if 0
 53+ pthread_mutex_lock(&nthreads_lock);
 54+ nthreads++;
 55+ pthread_mutex_unlock(&nthreads_lock);
 56+#endif
 57+ __sync_fetch_and_add(&nthreads, 1);
 58+ }
 59+#endif
 60+ request_thread *req = static_cast<request_thread *>(arg);
 61+ try {
 62+ req->start_request();
 63+ } catch (std::exception &e) {
 64+ std::fprintf(stderr, "exception handling request: %s\n", e.what());
 65+ }
 66+
 67+ delete req;
 68+
 69+ return NULL;
 70+}
 71+
 72+} // anonymous namespace
 73+
 74+request_thread::request_thread(int fd)
 75+ : fd_(fd)
 76+ , rid_(-1)
 77+ , cfd_(-1)
 78+{
 79+}
 80+
 81+request_thread::~request_thread()
 82+{
 83+ if (cfd_ != -1)
 84+ close(cfd_);
 85+
 86+ if (fd_ != -1)
 87+ close(fd_);
 88+}
 89+
 90+void
 91+request_thread::start()
 92+{
 93+#if 0
 94+ pthread_mutex_lock(&nthreads_lock);
 95+ if (nthreads == 0) {
 96+#endif
 97+ pthread_attr_t attr;
 98+ pthread_attr_init(&attr);
 99+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
 100+ pthread_create(&tid_, &attr, &do_start_thread, static_cast<void *>(this));
 101+#if 0
 102+ } else
 103+ nthreads--;
 104+ pthread_mutex_unlock(&nthreads_lock);
 105+
 106+ work.add_work(this);
 107+#endif
 108+}
 109+
 110+void
 111+request_thread::start_request()
 112+{
 113+ fcgi::record rec;
 114+
 115+ /*
 116+ * Start by parsing the request header.
 117+ */
 118+ fcgi::read_fcgi_record(fd_, &rec);
 119+
 120+ if (rec.version() != 1)
 121+ throw request_exception("unknown FCGI version");
 122+
 123+ rid_ = rec.request_id();
 124+
 125+ switch (rec.type()) {
 126+ case fcgi::rectype::begin_request:
 127+ handle_normal_request(rec);
 128+ return;
 129+
 130+
 131+ case fcgi::rectype::get_values:
 132+ handle_get_values(rec);
 133+ return;
 134+
 135+ default:
 136+ throw request_exception("unexpected request type at this point");
 137+ }
 138+}
 139+
 140+void
 141+request_thread::handle_normal_request(fcgi::record &initial)
 142+{
 143+ fcgi::record rec;
 144+ fcgi::begin_request_payload *pl = reinterpret_cast<fcgi::begin_request_payload *>(&initial.contentData[0]);
 145+
 146+ if (initial.content_length() != 8)
 147+ throw request_exception("begin request had unexpected content length");
 148+
 149+ switch (pl->role()) {
 150+ case fcgi::role::responder:
 151+ break;
 152+
 153+ default:
 154+ throw request_exception("begin_request had unexpected role");
 155+ }
 156+
 157+ /*
 158+ * Now we should receive at least one params.
 159+ */
 160+ std::vector<unsigned char> paramdata;
 161+
 162+ for (;;) {
 163+ fcgi::read_fcgi_record(fd_, &rec);
 164+
 165+ if (rec.type() != fcgi::rectype::params)
 166+ throw request_exception("unexpected request type at this point");
 167+
 168+ if (rec.content_length() == 0)
 169+ break;
 170+
 171+ paramdata.insert(paramdata.end(), rec.contentData.begin(), rec.contentData.end());
 172+ }
 173+
 174+ /*
 175+ * And at least one stdin.
 176+ */
 177+ std::vector<unsigned char> stdin_;
 178+
 179+ for (;;) {
 180+ fcgi::read_fcgi_record(fd_, &rec);
 181+
 182+ if (rec.type() != fcgi::rectype::stdin_)
 183+ throw request_exception("unexpected request type at this point");
 184+
 185+ if (rec.content_length() == 0)
 186+ break;
 187+
 188+ stdin_.insert(stdin_.end(), rec.contentData.begin(), rec.contentData.end());
 189+ }
 190+
 191+ /*
 192+ * Handle the actual request.
 193+ */
 194+ std::map<std::string, std::string> params;
 195+ fcgi::decode_params(paramdata.begin(), paramdata.end(), std::inserter(params, params.begin()));
 196+
 197+#if 0
 198+ for (std::map<std::string, std::string>::iterator
 199+ it = params.begin(), end = params.end();
 200+ it != end; ++it)
 201+ {
 202+ std::cout << "[" << it->first << "] = [" << it->second << "]\n";
 203+ }
 204+#endif
 205+
 206+ if ((cfd_ = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
 207+ throw request_exception("unix socket creation failed");
 208+
 209+ fcntl(cfd_, F_SETFD, FD_CLOEXEC);
 210+ int r = 0, tries = 0;
 211+ static int const max_tries = 10;
 212+
 213+ do {
 214+ process_ = process_factory::instance().get_process(params);
 215+ if ((r = process_->connect(cfd_)) == -1) {
 216+ if (errno != ECONNREFUSED)
 217+ throw request_exception("can't create PHP");
 218+ } else
 219+ break;
 220+ } while (++tries < max_tries);
 221+
 222+ if (r == -1)
 223+ throw request_exception("can't create PHP (too many tries)");
 224+
 225+ /*
 226+ * Write the request to the child.
 227+ */
 228+ fcgi::record r_begin;
 229+ fcgi::record r_params;
 230+ fcgi::record r_stdin;
 231+
 232+ r_begin.version_ = r_params.version_ = r_stdin.version_ = 1;
 233+ r_begin.paddingLength_ = r_params.paddingLength_ = r_stdin.paddingLength_ = 0;
 234+ r_begin.reserved_ = r_params.reserved_ = r_stdin.reserved_ = 0;
 235+
 236+ r_begin.request_id(rid_);
 237+ r_begin.type_ = fcgi::rectype::begin_request;
 238+ r_begin.content_length(8);
 239+ r_begin.contentData.resize(8);
 240+ r_begin.contentData[0] = 0;
 241+ r_begin.contentData[1] = 1;
 242+ r_begin.contentData[2] = 0;
 243+ fcgi::write_fcgi_record(cfd_, r_begin);
 244+
 245+ std::vector<unsigned char> newparams;
 246+ fcgi::encode_params(params.begin(), params.end(), std::back_inserter(newparams));
 247+ r_params.request_id(rid_);
 248+ r_params.type_ = fcgi::rectype::params;
 249+ r_params.contentData.swap(newparams);
 250+ //r_params.contentData.swap(paramdata);
 251+ r_params.content_length(r_params.contentData.size());
 252+ fcgi::write_fcgi_record(cfd_, r_params);
 253+
 254+ if (!r_params.contentData.empty()) {
 255+ r_params.contentData.clear();
 256+ r_params.content_length(0);
 257+ fcgi::write_fcgi_record(cfd_, r_params);
 258+ }
 259+
 260+ r_stdin.request_id(rid_);
 261+ r_stdin.type_ = fcgi::rectype::stdin_;
 262+ r_stdin.contentData.swap(stdin_);
 263+ r_stdin.content_length(r_stdin.contentData.size());
 264+ fcgi::write_fcgi_record(cfd_, r_stdin);
 265+
 266+ if (!r_stdin.contentData.empty()) {
 267+ r_stdin.contentData.clear();
 268+ r_stdin.content_length(0);
 269+ fcgi::write_fcgi_record(cfd_, r_stdin);
 270+ }
 271+
 272+ /*
 273+ * Now read response requests and forward them to the server.
 274+ */
 275+ fcgi::record resp;
 276+ for (;;) {
 277+ if (!fcgi::read_fcgi_record(cfd_, &resp))
 278+ throw request_exception("couldn't read record from child");
 279+
 280+ if (resp.request_id() != rid_)
 281+ throw request_exception("child sent incorrect request id");
 282+
 283+ fcgi::write_fcgi_record(fd_, resp);
 284+ if (resp.type() == fcgi::rectype::end_request)
 285+ break;
 286+ }
 287+
 288+ close(cfd_);
 289+ cfd_ = -1;
 290+ process_factory::instance().release_process(process_);
 291+ process_.reset();
 292+}
 293+
 294+void
 295+request_thread::handle_get_values(fcgi::record &initial)
 296+{
 297+ std::map<std::string, std::string> request;
 298+ std::map<std::string, std::string> response;
 299+
 300+ fcgi::decode_params(initial.contentData.begin(), initial.contentData.end(),
 301+ std::inserter(request, request.begin()));
 302+
 303+ for (std::map<std::string, std::string>::iterator
 304+ it = request.begin(), end = request.end();
 305+ it != end; ++it)
 306+ {
 307+ if (it->first == "FCGI_MAX_CONNS")
 308+ response["FCGI_MAX_CONNS"] = "100";
 309+ else if (it->first == "FCGI_MAX_REQS")
 310+ response["FCGI_MAX_REQS"] = "100";
 311+ else if (it->first == "FCGI_MPXS_CONNS")
 312+ response["FCGI_MPXS_CONNS"] = "0";
 313+ }
 314+
 315+ fcgi::record rec;
 316+ std::vector<unsigned char> responsedata;
 317+ fcgi::encode_params(response.begin(), response.end(),
 318+ std::back_inserter(rec.contentData));
 319+ rec.version_ = 1;
 320+ rec.type_ = fcgi::rectype::get_values_result;
 321+ rec.requestId0_ = initial.requestId0_;
 322+ rec.requestId1_ = initial.requestId1_;
 323+ rec.reserved_ = 0;
 324+ rec.content_length(rec.contentData.size());
 325+ fcgi::write_fcgi_record(fd_, rec);
 326+}
Index: trunk/tools/switchboard2/util.h
@@ -0,0 +1,65 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#ifndef UTIL_H
 12+#define UTIL_H
 13+
 14+#include <deque>
 15+
 16+#include <pthread.h>
 17+
 18+struct lock {
 19+ lock(pthread_mutex_t &mtx)
 20+ : mtx_(mtx)
 21+ {
 22+ pthread_mutex_lock(&mtx_);
 23+ }
 24+
 25+ ~lock() {
 26+ pthread_mutex_unlock(&mtx_);
 27+ }
 28+
 29+private:
 30+ pthread_mutex_t &mtx_;
 31+};
 32+
 33+template<typename T>
 34+struct work_queue {
 35+ void add_work(T const &work) {
 36+ pthread_mutex_lock(&wq_mutex);
 37+ wq_work.push_back(work);
 38+ pthread_cond_signal(&wq_cond);
 39+ pthread_mutex_unlock(&wq_mutex);
 40+ }
 41+
 42+ T wait() {
 43+ T work;
 44+
 45+ pthread_mutex_lock(&wq_mutex);
 46+ while (wq_work.empty())
 47+ pthread_cond_wait(&wq_cond, &wq_mutex);
 48+
 49+ work = wq_work.front();
 50+ wq_work.pop_front();
 51+ pthread_mutex_unlock(&wq_mutex);
 52+ return work;
 53+ }
 54+
 55+ work_queue() {
 56+ pthread_mutex_init(&wq_mutex, NULL);
 57+ pthread_cond_init(&wq_cond, NULL);
 58+ }
 59+
 60+private:
 61+ std::deque<T> wq_work;
 62+ pthread_cond_t wq_cond;
 63+ pthread_mutex_t wq_mutex;
 64+};
 65+
 66+#endif /* !UTIL_H */
Index: trunk/tools/switchboard2/process.h
@@ -0,0 +1,54 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#ifndef PROCESS_H
 12+#define PROCESS_H
 13+
 14+#include <sys/types.h>
 15+
 16+#include <stdexcept>
 17+
 18+#include <boost/shared_ptr.hpp>
 19+#include <boost/noncopyable.hpp>
 20+#include <log4cxx/logger.h>
 21+
 22+struct connect_failure : std::runtime_error {
 23+ connect_failure(char const *error) : std::runtime_error(error) {}
 24+};
 25+
 26+struct creation_failure : std::runtime_error {
 27+ creation_failure(char const *error) : std::runtime_error(error) {}
 28+};
 29+
 30+struct security_violation : creation_failure {
 31+ security_violation(char const *error) : creation_failure(error) {}
 32+};
 33+
 34+struct process : boost::noncopyable {
 35+ process(uid_t, gid_t, std::string const &bindpath);
 36+ ~process();
 37+
 38+ int connect(int);
 39+
 40+ uid_t uid() const { return uid_; }
 41+ gid_t gid() const { return gid_; }
 42+ pid_t pid() const { return pid_; }
 43+
 44+private:
 45+ std::string bindpath_;
 46+ pid_t pid_;
 47+ uid_t uid_;
 48+ gid_t gid_;
 49+
 50+ log4cxx::LoggerPtr logger;
 51+};
 52+
 53+typedef boost::shared_ptr<process> processp;
 54+
 55+#endif
Index: trunk/tools/switchboard2/request_thread.h
@@ -0,0 +1,44 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#ifndef REQUEST_THREAD_H
 12+#define REQUEST_THREAD_H
 13+
 14+#include <stdexcept>
 15+
 16+#include <pthread.h>
 17+
 18+#include "fcgi.h"
 19+#include "process.h"
 20+
 21+struct request_exception : std::runtime_error {
 22+ request_exception(char const *what)
 23+ : std::runtime_error(what) {}
 24+};
 25+
 26+struct request_thread {
 27+ request_thread(int fd);
 28+ ~request_thread();
 29+
 30+ void start();
 31+ void start_request();
 32+
 33+private:
 34+ void handle_normal_request(fcgi::record &);
 35+ void handle_get_values(fcgi::record &);
 36+
 37+ int fd_;
 38+ int cfd_;
 39+ int rid_;
 40+ pthread_t tid_;
 41+
 42+ processp process_;
 43+};
 44+
 45+#endif /* !REQUEST_THREAD_H */
Index: trunk/tools/switchboard2/main.cc
@@ -0,0 +1,365 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#include <iostream>
 12+#include <cstdio>
 13+#include <cerrno>
 14+#include <cstring>
 15+#include <map>
 16+#include <string>
 17+#include <functional>
 18+#include <iterator>
 19+#include <csignal>
 20+
 21+#include <sys/types.h>
 22+#include <sys/socket.h>
 23+
 24+#include <netinet/in.h>
 25+#include <arpa/inet.h>
 26+
 27+#include <pthread.h>
 28+#include <pwd.h>
 29+#include <grp.h>
 30+#include <fcntl.h>
 31+#include <netdb.h>
 32+#include <cstdio>
 33+
 34+#include <boost/format.hpp>
 35+#include <log4cxx/logger.h>
 36+#include <log4cxx/propertyconfigurator.h>
 37+
 38+#include "fcgi.h"
 39+#include "request_thread.h"
 40+#include "config.h"
 41+#include "version.h"
 42+
 43+ void * acceptor_thread(void *);
 44+
 45+extern "C" void
 46+sigexit(int)
 47+{
 48+ std::exit(0);
 49+}
 50+
 51+int
 52+main(int argc, char **argv)
 53+{
 54+ std::string initlog(CONFDIR "/initlog.conf");
 55+ std::string conf(CONFDIR "/main.conf");
 56+ int c, dflag = 0;
 57+
 58+ while ((c = getopt(argc, argv, "df:i:")) != -1) {
 59+ switch(c) {
 60+ case 'd':
 61+ dflag = 1;
 62+ break;
 63+ case 'f':
 64+ conf = optarg;
 65+ break;
 66+ case 'i':
 67+ initlog = optarg;
 68+ break;
 69+ default:
 70+ return 1;
 71+ }
 72+ }
 73+
 74+ if (access(initlog.c_str(), R_OK) == -1) {
 75+ std::cerr << boost::format("switchboard: initial log configuration file \"%s\" "
 76+ "cannot be read: %s\n")
 77+ % initlog % std::strerror(errno);
 78+ return 1;
 79+ }
 80+
 81+ log4cxx::LoggerPtr mainlogger(log4cxx::Logger::getLogger("main"));
 82+ log4cxx::PropertyConfigurator::configure(initlog);
 83+
 84+ LOG4CXX_INFO(mainlogger, "switchboard " SB_VERSION " starting up...");
 85+
 86+ configuration_loader loader;
 87+ if (!loader.load(conf, mainconf)) {
 88+ LOG4CXX_ERROR(mainlogger, "cannot load configuration");
 89+ return 1;
 90+ }
 91+
 92+ if (mainconf.sockdir.empty()) {
 93+ LOG4CXX_ERROR(mainlogger, "sockdir must be specified");
 94+ return 1;
 95+ }
 96+
 97+ if (mainconf.userdir.empty()) {
 98+ LOG4CXX_ERROR(mainlogger, "userdir must be specified");
 99+ return 1;
 100+ }
 101+
 102+ if (mainconf.docroot.empty()) {
 103+ LOG4CXX_ERROR(mainlogger, "docroot must be specified");
 104+ return 1;
 105+ }
 106+
 107+ if (mainconf.servtype == serv_unknown) {
 108+ LOG4CXX_ERROR(mainlogger, "server type not set");
 109+ return 1;
 110+ }
 111+
 112+ LOG4CXX_INFO(mainlogger, boost::format("loaded configuration from \"%s\"") % conf);
 113+
 114+ for (std::size_t i = 0; i < mainconf.listeners.size(); ++i) {
 115+ if (mainconf.listeners[i].host[0] == '/') {
 116+ struct sockaddr_un addr;
 117+ int lsnsock;
 118+ std::memset(&addr, 0, sizeof(addr));
 119+ strncpy(addr.sun_path, mainconf.listeners[i].host.c_str(), sizeof(addr.sun_path) - 1);
 120+ addr.sun_family = AF_UNIX;
 121+
 122+ unlink(addr.sun_path);
 123+
 124+ if ((lsnsock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
 125+ LOG4CXX_ERROR(mainlogger,
 126+ boost::format("%s: cannot create socket: %s\n")
 127+ % mainconf.listeners[i].host % std::strerror(errno));
 128+ return 1;
 129+ }
 130+
 131+ int one = 1;
 132+ setsockopt(lsnsock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
 133+ fcntl(lsnsock, F_SETFD, FD_CLOEXEC);
 134+
 135+ if (bind(lsnsock, (sockaddr *) &addr, sizeof(addr)) == -1) {
 136+ LOG4CXX_ERROR(mainlogger,
 137+ boost::format("%s: cannot bind: %s\n")
 138+ % mainconf.listeners[i].host % std::strerror(errno));
 139+ return 1;
 140+ }
 141+
 142+ if (listen(lsnsock, 20) == -1) {
 143+ LOG4CXX_ERROR(mainlogger,
 144+ boost::format("%s cannot listen: %s\n")
 145+ % mainconf.listeners[i].host % std::strerror(errno));
 146+ return 1;
 147+ }
 148+ } else {
 149+ int r;
 150+ struct addrinfo hints, *res, *iter;
 151+ std::memset(&hints, 0, sizeof(hints));
 152+ hints.ai_flags = AI_PASSIVE;
 153+ hints.ai_socktype = SOCK_STREAM;
 154+
 155+ if ((r = getaddrinfo(mainconf.listeners[i].host.c_str(),
 156+ mainconf.listeners[i].port.c_str(),
 157+ &hints, &res)) != 0) {
 158+ LOG4CXX_ERROR(mainlogger,
 159+ boost::format("cannot resolve [%s]:%s: %s\n")
 160+ % mainconf.listeners[i].host
 161+ % mainconf.listeners[i].port
 162+ % gai_strerror(r));
 163+ return 1;
 164+ }
 165+
 166+ for (iter = res; iter; iter = iter->ai_next) {
 167+ char ahost[NI_MAXHOST], aserv[NI_MAXSERV];
 168+ int lsnsock;
 169+
 170+ getnameinfo(iter->ai_addr, iter->ai_addrlen,
 171+ ahost, sizeof(ahost), aserv, sizeof(aserv),
 172+ AI_NUMERICHOST | AI_ADDRCONFIG);
 173+
 174+ if ((lsnsock = socket(iter->ai_family, iter->ai_socktype, iter->ai_protocol)) == -1) {
 175+ LOG4CXX_ERROR(mainlogger,
 176+ boost::format("[%s]:%s: cannot create socket: %s\n")
 177+ % ahost % aserv % std::strerror(errno));
 178+ return 1;
 179+ }
 180+
 181+ int one = 1;
 182+ setsockopt(lsnsock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
 183+ fcntl(lsnsock, F_SETFD, FD_CLOEXEC);
 184+
 185+ if (bind(lsnsock, iter->ai_addr, iter->ai_addrlen) == -1) {
 186+ LOG4CXX_ERROR(mainlogger,
 187+ boost::format("[%s]:%s: cannot bind: %s\n")
 188+ % ahost % aserv % std::strerror(errno));
 189+ return 1;
 190+ }
 191+
 192+ if (listen(lsnsock, 20) == -1) {
 193+ LOG4CXX_ERROR(mainlogger,
 194+ boost::format("[%s]:%s: cannot listen: %s\n")
 195+ % ahost % aserv % std::strerror(errno));
 196+ return 1;
 197+ }
 198+
 199+ pthread_t tid;
 200+ pthread_create(&tid, NULL, acceptor_thread, reinterpret_cast<void *>(lsnsock));
 201+ }
 202+
 203+ freeaddrinfo(res);
 204+ }
 205+ }
 206+
 207+ std::string logconf = CONFDIR "/log.conf";
 208+ if (!mainconf.logconf.empty())
 209+ logconf = mainconf.logconf;
 210+
 211+ if (access(logconf.c_str(), R_OK) == -1) {
 212+ LOG4CXX_WARN(mainlogger,
 213+ boost::format("main log configuration \"%s\" cannot be read: %s")
 214+ % logconf % std::strerror(errno));
 215+ return 1;
 216+ } else {
 217+ LOG4CXX_INFO(mainlogger, boost::format("re-initialising logging from \"%s\"")
 218+ % logconf);
 219+ log4cxx::PropertyConfigurator::configure(logconf);
 220+ }
 221+
 222+ if (!getuid()) {
 223+ struct passwd *pw;
 224+ struct group *gr;
 225+
 226+ if ((pw = getpwnam(SB_USER)) == NULL) {
 227+ LOG4CXX_ERROR(mainlogger,
 228+ boost::format("cannot setuid to \"%s\": user lookup failed: %s")
 229+ % SB_USER % std::strerror(errno));
 230+ return 1;
 231+ }
 232+
 233+ if ((gr = getgrnam(SB_GROUP)) == NULL) {
 234+ LOG4CXX_ERROR(mainlogger,
 235+ boost::format("cannot setgid to \"%s\": group lookup failed: %s")
 236+ % SB_USER % std::strerror(errno));
 237+ return 1;
 238+ }
 239+
 240+ if (!getgid()) {
 241+ if (setgid(gr->gr_gid) == -1) {
 242+ LOG4CXX_ERROR(mainlogger,
 243+ boost::format("cannot setgid to \"%s\": %s")
 244+ % SB_GROUP % std::strerror(errno));
 245+ return 1;
 246+ }
 247+ }
 248+
 249+ if (initgroups(SB_USER, gr->gr_gid) == -1) {
 250+ LOG4CXX_ERROR(mainlogger,
 251+ boost::format("cannot initialise supplemental group list: %s")
 252+ % std::strerror(errno));
 253+ return 1;
 254+ }
 255+
 256+ if (!getuid()) {
 257+ if (setuid(pw->pw_uid) == -1) {
 258+ LOG4CXX_ERROR(mainlogger,
 259+ boost::format("cannot setuid to \"%s\": %s")
 260+ % SB_USER % std::strerror(errno));
 261+ return 1;
 262+ }
 263+ }
 264+
 265+ LOG4CXX_INFO(mainlogger,
 266+ boost::format("now running as %s:%s")
 267+ % SB_USER % SB_GROUP);
 268+ }
 269+
 270+ /*
 271+ * Make sure our sockdir is valid, and has the right permissions.
 272+ */
 273+dostat:
 274+ struct stat sb;
 275+ if (stat(mainconf.sockdir.c_str(), &sb) == -1) {
 276+ if (errno == ENOENT) {
 277+ if (mkdir(mainconf.sockdir.c_str(), 0750) == -1) {
 278+ LOG4CXX_ERROR(mainlogger,
 279+ boost::format("cannot create sockdir \"%s\": %s")
 280+ % mainconf.sockdir % std::strerror(errno));
 281+ return 1;
 282+ }
 283+
 284+ LOG4CXX_INFO(mainlogger,
 285+ boost::format("created sockdir \"%s\"")
 286+ % mainconf.sockdir);
 287+ goto dostat;
 288+ } else {
 289+ LOG4CXX_ERROR(mainlogger,
 290+ boost::format("cannot stat sockdir \"%s"": %s")
 291+ % mainconf.sockdir % std::strerror(errno));
 292+ return 1;
 293+ }
 294+ }
 295+
 296+ if (sb.st_uid != getuid()) {
 297+ LOG4CXX_ERROR(mainlogger,
 298+ boost::format("sockdir \"%s\" is not owned by my uid %d")
 299+ % mainconf.sockdir % getuid());
 300+ return 1;
 301+ }
 302+
 303+ if (sb.st_gid != getgid()) {
 304+ LOG4CXX_ERROR(mainlogger,
 305+ boost::format("sockdir \"%s\" is not owned by my gid %d")
 306+ % mainconf.sockdir % getgid());
 307+ return 1;
 308+ }
 309+
 310+ if ((sb.st_mode & S_IRWXO) != 0) {
 311+ LOG4CXX_ERROR(mainlogger,
 312+ boost::format("sockdir \"%s\" has wrong mode (access for others)")
 313+ % mainconf.sockdir);
 314+ return 1;
 315+ }
 316+
 317+
 318+ if (!dflag) {
 319+ LOG4CXX_INFO(mainlogger, "detaching to background...");
 320+ switch (fork()) {
 321+ case -1:
 322+ LOG4CXX_INFO(mainlogger, boost::format("cannot fork: %s")
 323+ % std::strerror(errno));
 324+ return 1;
 325+ case 0:
 326+ break;
 327+ default:
 328+ return 0;
 329+ }
 330+
 331+ setsid();
 332+ chdir("/");
 333+ int fd;
 334+ if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
 335+ dup2(fd, STDIN_FILENO);
 336+ dup2(fd, STDOUT_FILENO);
 337+ dup2(fd, STDERR_FILENO);
 338+ if (fd > STDERR_FILENO)
 339+ close(fd);
 340+ }
 341+ }
 342+
 343+ std::signal(SIGTERM, sigexit);
 344+ std::signal(SIGINT, sigexit);
 345+
 346+ int stat;
 347+ for (;;)
 348+ wait(&stat);
 349+}
 350+
 351+void *
 352+acceptor_thread(void *arg)
 353+{
 354+ int fd = reinterpret_cast<int>(arg);
 355+ int newfd;
 356+ struct sockaddr addr;
 357+ socklen_t addrlen = sizeof(addr);
 358+
 359+ while ((newfd = accept(fd, &addr, &addrlen)) != -1) {
 360+ request_thread *req = new request_thread(newfd);
 361+ req->start();
 362+ }
 363+
 364+ std::printf("accept failed: %s\n", std::strerror(errno));
 365+ return NULL;
 366+}
Index: trunk/tools/switchboard2/version.h
@@ -0,0 +1,15 @@
 2+/* Copyright (c) 2008 River Tarnell <river@wikimedia.org>. */
 3+/*
 4+ * Permission is granted to anyone to use this software for any purpose,
 5+ * including commercial applications, and to alter it and redistribute it
 6+ * freely. This software is provided 'as-is', without any express or implied
 7+ * warranty.
 8+ */
 9+/* $Id$ */
 10+
 11+#ifndef VERSION_H
 12+#define VERSION_H
 13+
 14+#define SB_VERSION "V-2.0.0"
 15+
 16+#endif /* !VERSION_H */
Index: trunk/tools/switchboard2/Makefile.config.example
@@ -0,0 +1,71 @@
 2+### Compiler
 3+# switchboard can be compiled with gcc (tested on 3.4.3) or
 4+# Sun Studio. When using Studio, -library=stlport4 is required.
 5+# Make sure log4cxx is compiled with the same flags.
 6+CC = cc
 7+CXX = CC
 8+
 9+### Suffix for Boost library names
 10+
 11+# Sun:
 12+BOOSTTAG = -sw-mt
 13+
 14+# gcc:
 15+#BOOSTTAG = -gcc43-mt
 16+
 17+### Compiler flags
 18+# See the note about -library=stlport8 above.
 19+EXTRA_CFLAGS = -xO3 -g
 20+EXTRA_CXXFLAGS = $(EXTRA_CFLAGS) -library=stlport4
 21+
 22+# Extra preprocessor flags, e.g. -I to find include files
 23+EXTRA_CPPFLAGS = -I/opt/boost/include/boost-1_35
 24+
 25+# Solaris needs this.
 26+EXTRA_CPPFLAGS += -D_XOPEN_SOURCE=500 -D__EXTENSIONS__
 27+
 28+# Extra linker flags, e.g. -R or -L to find libraries
 29+EXTRA_LDFLAGS = -L/opt/boost/lib -R /opt/boost/lib
 30+
 31+# Extra libs.
 32+EXTRA_LIBS=
 33+
 34+# For Solaris:
 35+EXTRA_LIBS+=-lsocket -lnsl -lproject
 36+
 37+# swexec needs this for project support on solaris
 38+SWEXEC_EXTRA_LIBS = -lproject
 39+
 40+# install program, /usr/ucb/install for Solaris, otherwise /usr/bin/install
 41+INSTALL=/usr/ucb/install
 42+
 43+# The prefix we install into
 44+PREFIX=/opt/switchboard
 45+
 46+# Directory where configuration files are stored
 47+CONFDIR=/etc/switchboard
 48+
 49+# The name of the PHP executable on this system.
 50+# For security reasons, this is compiled into swexec and therefore must
 51+# be set at compile time. It cannot be changed in the configuration
 52+# file.
 53+PHP_BIN=/opt/php/bin/php-cgi
 54+
 55+# The lowest UID and GID that PHP scripts will be executed for. root
 56+# is always excluded, no matter what this is set to.
 57+SB_UID_MIN=100
 58+SB_GID_MIN=100
 59+
 60+# The user and group that switchboard (NOT httpd) is running as.
 61+SB_USER=swtchbd
 62+
 63+# The group that switchboard runs as. For security reasons, this should
 64+# be a dedicated group that is not shared by anything else (particularly
 65+# normal users).
 66+SB_GROUP=swtchbd
 67+
 68+# The $PATH that PHP will run with.
 69+SB_SAFE_PATH=/bin:/usr/bin:/usr/local/bin
 70+
 71+# Logfile
 72+SB_LOG_EXEC=/var/log/swexec
Index: trunk/tools/switchboard2/Makefile
@@ -0,0 +1,106 @@
 2+include Makefile.config
 3+
 4+CPPFLAGS =
 5+INCLUDES = $(EXTRA_CPPFLAGS)
 6+CFLAGS = $(EXTRA_CFLAGS)
 7+CXXFLAGS = $(EXTRA_CXXFLAGS)
 8+LDFLAGS = $(EXTRA_LDFLAGS)
 9+LIBS = -llog4cxx -lboost_system$(BOOSTTAG) -lboost_signals$(BOOSTTAG) $(EXTRA_LIBS)
 10+
 11+CONFIG_FLAGS = \
 12+ -DPREFIX=\"$(PREFIX)\" \
 13+ -DCONFDIR=\"$(CONFDIR)\" \
 14+ -DPHP_BIN=\"$(PHP_BIN)\" \
 15+ -DSB_USER=\"$(SB_USER)\" \
 16+ -DSB_GROUP=\"$(SB_GROUP)\" \
 17+
 18+SWEXEC_FLAGS = \
 19+ -DSB_UID_MIN=$(SB_UID_MIN) \
 20+ -DSB_GID_MIN=$(SB_GID_MIN) \
 21+ -DSB_USER=\"$(SB_USER)\" \
 22+ -DSB_GROUP=\"$(SB_GROUP)\" \
 23+ -DSB_SAFE_PATH=\"$(SB_SAFE_PATH)\" \
 24+ -DPHP_BIN=\"$(PHP_BIN)\" \
 25+ -DSB_LOG_EXEC=\"$(SB_LOG_EXEC)\" \
 26+
 27+SWKILL_FLAGS = \
 28+ -DSB_UID_MIN=$(SB_UID_MIN) \
 29+ -DSB_GID_MIN=$(SB_GID_MIN) \
 30+ -DSB_USER=\"$(SB_USER)\" \
 31+ -DSB_LOG_EXEC=\"$(SB_LOG_EXEC)\" \
 32+
 33+OBJS= \
 34+ main.o \
 35+ request_thread.o \
 36+ fcgi.o \
 37+ process.o \
 38+ config.o \
 39+ process_factory.o \
 40+
 41+PROG= switchboard
 42+SWEXEC= swexec
 43+SWKILL= swkill
 44+
 45+all: $(PROG) $(SWEXEC) $(SWKILL)
 46+
 47+$(PROG): $(OBJS)
 48+ $(CXX) $(CFLAGS) $(CXXFLAGS) $(LDFLAGS) $(OBJS) -o $(PROG) $(LIBS)
 49+
 50+$(SWEXEC): swexec.o
 51+ $(CC) $(CFLAGS) $(LDFLAGS) swexec.o -o $(SWEXEC) $(SWEXEC_EXTRA_LIBS)
 52+
 53+$(SWKILL): swkill.o
 54+ $(CC) $(CFLAGS) $(LDFLAGS) swkill.o -o $(SWKILL)
 55+
 56+.cc.o:
 57+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(CONFIG_FLAGS) $(INCLUDES) $(CFLAGS) -c $<
 58+
 59+swexec.o: swexec.c
 60+ $(CC) $(CPPFLAGS) $(SWEXEC_FLAGS) $(INCLUDES) $(CFLAGS) -c $<
 61+
 62+swkill.o: swkill.c
 63+ $(CC) $(CPPFLAGS) $(SWKILL_FLAGS) $(INCLUDES) $(CFLAGS) -c $<
 64+
 65+install:
 66+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)
 67+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/lib
 68+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/lib/switchboard
 69+ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
 70+ $(INSTALL) -d -m 0755 $(DESTDIR)$(CONFDIR)
 71+ $(INSTALL) -o root -g $(SB_GROUP) -m 0755 switchboard $(DESTDIR)$(PREFIX)/bin
 72+ $(INSTALL) -o root -g $(SB_GROUP) -m 04710 $(SWEXEC) $(DESTDIR)$(PREFIX)/lib/switchboard
 73+ $(INSTALL) -o root -g $(SB_GROUP) -m 04710 $(SWKILL) $(DESTDIR)$(PREFIX)/lib/switchboard
 74+ $(INSTALL) -o root -g bin -m 0644 log.conf.example $(DESTDIR)$(CONFDIR)
 75+ $(INSTALL) -o root -g bin -m 0644 main.conf.example $(DESTDIR)$(CONFDIR)
 76+ $(INSTALL) -o root -g bin -m 0644 initlog.conf $(DESTDIR)$(CONFDIR)
 77+
 78+clean:
 79+ rm -f $(OBJS) $(PROG) $(SWEXEC) $(SWKILL) swexec.o swkill.o
 80+
 81+Makefile.config:
 82+ @echo You must create Makefile.config before you can build switchboard
 83+ @exit 1
 84+
 85+.SUFFIXES: .c .cc .o
 86+.KEEP_STATE:
 87+.PHONY: clean
 88+
 89+gccdepend:
 90+ sed '/^# Do not delete this line -- make depend requires it/,$$d' < Makefile > Makefile.new
 91+ echo '# Do not delete this line -- make depend requires it' >> Makefile.new
 92+ $(CXX) $(CPPFLAGS) $(CONFIG_FLAGS) $(INCLUDES) -MM $(OBJS:.o=.cc) >>Makefile.new
 93+ $(CXX) $(CPPFLAGS) $(SWEXEC_FLAGS) $(INCLUDES) -MM swexec.c >>Makefile.new
 94+ $(CXX) $(CPPFLAGS) $(SWKILL_FLAGS) $(INCLUDES) -MM swkill.c >>Makefile.new
 95+ mv -f Makefile.new Makefile
 96+
 97+# Do not delete this line -- make depend requires it
 98+main.o: main.cc fcgi.h request_thread.h process.h config.h version.h
 99+request_thread.o: request_thread.cc request_thread.h fcgi.h process.h \
 100+ process_factory.h util.h
 101+fcgi.o: fcgi.cc fcgi.h
 102+process.o: process.cc process.h
 103+config.o: config.cc config.h
 104+process_factory.o: process_factory.cc process_factory.h process.h \
 105+ config.h util.h
 106+swexec.o: swexec.c
 107+swkill.o: swkill.c

Status & tagging log