# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: rousskov@measurement-factory.com-20100614212201-\
#   o66t5l0kki2ri5ii
# target_branch: http://www.squid-cache.org/bzr/squid3/trunk
# testament_sha1: 2e4d2114dab96b38690dc2a5ad30f653087078fa
# timestamp: 2010-06-30 17:59:56 -0600
# base_revision_id: squid3@treenet.co.nz-20100303093849-\
#   ndhnc129fhg9xuie
# 
# Begin patch
=== modified file 'configure.in'
--- configure.in	2010-02-03 12:36:21 +0000
+++ configure.in	2010-03-30 21:54:40 +0000
@@ -4,7 +4,7 @@
 dnl
 dnl
 dnl
-AC_INIT([Squid Web Proxy],[3.HEAD-BZR],[http://www.squid-cache.org/bugs/],[squid])
+AC_INIT([Squid Web Proxy],[3.HEAD-SMP-BZR],[http://www.squid-cache.org/bugs/],[squid])
 AC_PREREQ(2.61)
 AC_CONFIG_HEADERS([include/autoconf.h])
 AC_CONFIG_AUX_DIR(cfgaux)
@@ -4233,6 +4233,7 @@
 	src/ident/Makefile \
 	src/ip/Makefile \
 	src/log/Makefile \
+	src/ipc/Makefile \
 	contrib/Makefile \
 	snmplib/Makefile \
 	icons/Makefile \

=== modified file 'src/Makefile.am'
--- src/Makefile.am	2010-01-12 21:16:29 +0000
+++ src/Makefile.am	2010-03-30 21:54:40 +0000
@@ -34,7 +34,7 @@
 	LoadableModules.h \
 	LoadableModules.cc
 
-SUBDIRS	= base comm eui acl fs repl auth ip icmp ident log
+SUBDIRS	= base comm eui acl fs repl auth ip icmp ident log ipc
 
 if USE_ADAPTATION
 SUBDIRS += adaptation
@@ -164,7 +164,8 @@
 	auth/libauth.la \
 	acl/libapi.la \
 	ip/libip.la \
-	fs/libfs.la
+	fs/libfs.la \
+	ipc/libipc.la
 
 EXTRA_PROGRAMS = \
 	DiskIO/DiskDaemon/diskd \

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc	2010-02-06 00:13:14 +0000
+++ src/cache_cf.cc	2010-03-30 21:54:40 +0000
@@ -69,6 +69,7 @@
 #include "StoreFileSystem.h"
 #include "SwapDir.h"
 #include "wordlist.h"
+#include "ipc/Kids.h"
 
 #if HAVE_GLOB_H
 #include <glob.h>
@@ -258,6 +259,110 @@
     return error_count;
 }
 
+static void
+ReplaceSubstr(char*& str, int& len, unsigned substrIdx, unsigned substrLen, const char* newSubstr)
+{
+    assert(str != NULL);
+    assert(newSubstr != NULL);
+
+    unsigned newSubstrLen = strlen(newSubstr);
+    if (newSubstrLen > substrLen)
+        str = (char*)realloc(str, len - substrLen + newSubstrLen + 1);
+
+    // move tail part including zero
+    memmove(str + substrIdx + newSubstrLen, str + substrIdx + substrLen, len - substrIdx - substrLen + 1);
+    // copy new substring in place
+    memcpy(str + substrIdx, newSubstr, newSubstrLen);
+
+    len = strlen(str);
+}
+
+static void
+SubstituteMacro(char*& line, int& len, const char* macroName, const char* substStr)
+{
+    assert(line != NULL);
+    assert(macroName != NULL);
+    assert(substStr != NULL);
+    unsigned macroNameLen = strlen(macroName);
+    while (const char* macroPos = strstr(line, macroName)) // we would replace all occurrences
+        ReplaceSubstr(line, len, macroPos - line, macroNameLen, substStr);
+}
+
+static void
+ProcessMacros(char*& line, int& len)
+{
+    SubstituteMacro(line, len, "${process_name}", KidName);
+    SubstituteMacro(line, len, "${process_number}", xitoa(KidIdentifier));
+}
+
+static void
+trim_trailing_ws(char* str)
+{
+    assert(str != NULL);
+    unsigned i = strlen(str);
+    while ((i > 0) && xisspace(str[i - 1]))
+        --i;
+    str[i] = '\0';
+}
+
+static const char*
+FindStatement(const char* line, const char* statement)
+{
+    assert(line != NULL);
+    assert(statement != NULL);
+
+    const char* str = skip_ws(line);
+    unsigned len = strlen(statement);
+    if (strncmp(str, statement, len) == 0) {
+        str += len;
+        if (*str == '\0')
+            return str;
+        else if (xisspace(*str))
+            return skip_ws(str);
+    }
+
+    return NULL;
+}
+
+static bool
+StrToInt(const char* str, long& number)
+{
+    assert(str != NULL);
+
+    char* end;
+    number = strtol(str, &end, 0);
+
+    return (end != str) && (*end == '\0'); // returns true if string contains nothing except number
+}
+
+static bool
+EvalBoolExpr(const char* expr)
+{
+    assert(expr != NULL);
+    if (strcmp(expr, "true") == 0) {
+        return true;
+    } else if (strcmp(expr, "false") == 0) {
+        return false;
+    } else if (const char* equation = strchr(expr, '=')) {
+        const char* rvalue = skip_ws(equation + 1);
+        char* lvalue = (char*)xmalloc(equation - expr + 1);
+        xstrncpy(lvalue, expr, equation - expr + 1);
+        trim_trailing_ws(lvalue);
+
+        long number1;
+        if (!StrToInt(lvalue, number1))
+            fatalf("String is not a integer number: '%s'\n", lvalue);
+        long number2;
+        if (!StrToInt(rvalue, number2))
+            fatalf("String is not a integer number: '%s'\n", rvalue);
+
+        xfree(lvalue);
+        return number1 == number2;
+    }
+    fatalf("Unable to evaluate expression '%s'\n", expr);
+    return false; // this place cannot be reached
+}
+
 static int
 parseOneConfigFile(const char *file_name, unsigned int depth)
 {
@@ -298,6 +403,7 @@
 
     config_lineno = 0;
 
+    Vector<bool> if_states;
     while (fgets(config_input_line, BUFSIZ, fp)) {
         config_lineno++;
 
@@ -357,20 +463,38 @@
             continue;
         }
 
+        trim_trailing_ws(tmp_line);
+        ProcessMacros(tmp_line, tmp_line_len);
         debugs(3, 5, "Processing: '" << tmp_line << "'");
 
-        /* Handle includes here */
-        if (tmp_line_len >= 9 && strncmp(tmp_line, "include", 7) == 0 && xisspace(tmp_line[7])) {
-            err_count += parseManyConfigFiles(tmp_line + 8, depth + 1);
-        } else if (!parse_line(tmp_line)) {
-            debugs(3, 0, HERE << cfg_filename << ":" << config_lineno << " unrecognized: '" << tmp_line << "'");
-            err_count++;
+        if (const char* expr = FindStatement(tmp_line, "if")) {
+            if_states.push_back(EvalBoolExpr(expr)); // store last if-statement meaning
+        } else if (FindStatement(tmp_line, "endif")) {
+            if (!if_states.empty())
+                if_states.pop_back(); // remove last if-statement meaning
+            else
+                fatalf("'endif' without 'if'\n");
+        } else if (FindStatement(tmp_line, "else")) {
+            if (!if_states.empty())
+                if_states.back() = !if_states.back();
+            else
+                fatalf("'else' without 'if'\n");
+        } else if (if_states.empty() || if_states.back()) { // test last if-statement meaning if present
+            /* Handle includes here */
+            if (tmp_line_len >= 9 && strncmp(tmp_line, "include", 7) == 0 && xisspace(tmp_line[7])) {
+                err_count += parseManyConfigFiles(tmp_line + 8, depth + 1);
+            } else if (!parse_line(tmp_line)) {
+                debugs(3, 0, HERE << cfg_filename << ":" << config_lineno << " unrecognized: '" << tmp_line << "'");
+                err_count++;
+            }
         }
 
         safe_free(tmp_line);
         tmp_line_len = 0;
 
     }
+    if (!if_states.empty())
+        fatalf("if-statement without 'endif'\n");
 
     if (is_pipe) {
         int ret = pclose(fp);

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2010-03-03 09:38:49 +0000
+++ src/cf.data.pre	2010-03-26 13:52:36 +0000
@@ -57,6 +57,43 @@
   This arbitrary restriction is to prevent recursive include references
   from causing Squid entering an infinite loop whilst trying to load
   configuration files.
+
+
+  Conditional configuration
+
+	If-statements can be used to make configuration directives
+	depend on conditions:
+
+	    if <CONDITION>
+	        ... regular configuration directives ...
+	    [else
+	        ... regular configuration directives ...]
+	    endif
+
+	The else part is optional. The keywords "if", "else", and "endif"
+	must be typed on their own lines, as if they were regular
+	configuration directives.
+
+	These individual conditions types are supported:
+
+	    true
+		Always evaluates to true.
+	    false
+		Always evaluates to false.
+	    <integer> = <integer>
+	        Equality comparison of two integer numbers.
+
+
+  SMP-Related Macros
+
+	The following SMP-related preprocessor macros can be used.
+
+	${process_name} expands to the current Squid process "name"
+	(e.g., squid1, squid2, or cache1).
+
+	${process_number} expands to the current Squid process
+	identifier, which is an integer number (e.g., 1, 2, 3) unique
+	across all Squid processes.
 COMMENT_END
 
 COMMENT_START
@@ -6807,4 +6844,15 @@
 	Whether to lookup the EUI or MAC address of a connected client.
 DOC_END
 
+NAME: main_processes
+TYPE: int
+LOC: Config.main_processes
+DEFAULT: 1
+DOC_START
+	Number of main Squid processes to fork.
+	0: "no daemon" mode, like running "squid -N ..."
+	1: "no SMP" mode, start one main Squid process daemon (default)
+	N: start N main Squid process daemons
+DOC_END
+
 EOF

=== modified file 'src/client_side.cc'
--- src/client_side.cc	2010-01-29 01:13:09 +0000
+++ src/client_side.cc	2010-05-24 20:09:03 +0000
@@ -102,6 +102,7 @@
 #include "ident/Config.h"
 #include "ident/Ident.h"
 #include "ip/IpIntercept.h"
+#include "ipc/StartListening.h"
 #include "MemBuf.h"
 #include "MemObject.h"
 #include "ProtoPort.h"
@@ -113,6 +114,33 @@
 #define comm_close comm_lingering_close
 #endif
 
+/// dials clientHttpConnectionOpened or clientHttpsConnectionOpened call
+class ListeningStartedDialer: public CallDialer, public Ipc::StartListeningCb
+{
+public:
+    typedef void (*Handler)(int fd, int errNo, http_port_list *portCfg);
+    ListeningStartedDialer(Handler aHandler, http_port_list *aPortCfg):
+        handler(aHandler), portCfg(aPortCfg) {}
+
+    virtual void print(std::ostream &os) const { startPrint(os) <<
+        ", port=" << (void*)portCfg << ')'; }
+
+    virtual bool canDial(AsyncCall &) const { return true; }
+    virtual void dial(AsyncCall &) { (handler)(fd, errNo, portCfg); }
+
+public:
+    Handler handler;
+
+private:
+    http_port_list *portCfg; ///< from Config.Sockaddr.http
+};
+
+
+static void clientHttpConnectionOpened(int fd, int errNo, http_port_list *s);
+#if USE_SSL
+static void clientHttpsConnectionOpened(int fd, int errNo, http_port_list *s);
+#endif
+
 /* our socket-related context */
 
 
@@ -3329,12 +3357,39 @@
 
 #endif /* USE_SSL */
 
+/// check FD after clientHttp[s]ConnectionOpened, adjust HttpSockets as needed
+static bool
+OpenedHttpSocket(int fd, const char *msgIfFail)
+{
+    if (fd < 0) {
+        Must(NHttpSockets > 0); // we tried to open some
+        --NHttpSockets; // there will be fewer sockets than planned
+        Must(HttpSockets[NHttpSockets] < 0); // no extra fds received
+
+        if (!NHttpSockets) // we could not open any listen sockets at all
+            fatal(msgIfFail);
+
+        return false;
+    }
+    return true;
+}
+
+/// find any unused HttpSockets[] slot and store fd there or return false
+static bool
+AddOpenedHttpSocket(int fd)
+{
+    bool found = false;
+    for (int i = 0; i < NHttpSockets && !found; i++) {
+        if ((found = HttpSockets[i] < 0))
+            HttpSockets[i] = fd;
+    }
+    return found;
+}
 
 static void
 clientHttpConnectionsOpen(void)
 {
     http_port_list *s = NULL;
-    int fd = -1;
 #if USE_SSL
     int bumpCount = 0; // counts http_ports with sslBump option
 #endif
@@ -3358,18 +3413,34 @@
 
         /* AYJ: 2009-12-27: bit bumpy. new ListenStateData(...) should be doing all the Comm:: stuff ... */
 
-        enter_suid();
-
-        if (s->spoof_client_ip) {
-            fd = comm_open_listener(SOCK_STREAM, IPPROTO_TCP, s->s, (COMM_NONBLOCKING|COMM_TRANSPARENT), "HTTP Socket");
-        } else {
-            fd = comm_open_listener(SOCK_STREAM, IPPROTO_TCP, s->s, COMM_NONBLOCKING, "HTTP Socket");
-        }
-
-        leave_suid();
-
-        if (fd < 0)
-            continue;
+        const int openFlags = COMM_NONBLOCKING |
+            (s->spoof_client_ip ? COMM_TRANSPARENT : 0);
+
+        AsyncCall::Pointer callback = asyncCall(33,2,
+            "clientHttpConnectionOpened",
+            ListeningStartedDialer(&clientHttpConnectionOpened, s));
+        Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->s, openFlags,
+            Ipc::fdnHttpSocket, callback);
+
+        HttpSockets[NHttpSockets++] = -1; // set in clientHttpConnectionOpened
+    }
+
+#if USE_SSL
+    if (bumpCount && !Config.accessList.ssl_bump)
+        debugs(33, 1, "WARNING: http_port(s) with SslBump found, but no " <<
+               std::endl << "\tssl_bump ACL configured. No requests will be " <<
+               "bumped.");
+#endif
+}
+
+/// process clientHttpConnectionsOpen result
+static void
+clientHttpConnectionOpened(int fd, int, http_port_list *s)
+{
+    if (!OpenedHttpSocket(fd, "Cannot open HTTP Port"))
+        return;
+
+    Must(s);
 
         AsyncCall::Pointer call = commCbCall(5,5, "SomeCommAcceptHandler(httpAccept)",
                                              CommAcceptCbPtrFun(httpAccept, s));
@@ -3384,15 +3455,7 @@
                << " HTTP connections at " << s->s
                << ", FD " << fd << "." );
 
-        HttpSockets[NHttpSockets++] = fd;
-    }
-
-#if USE_SSL
-    if (bumpCount && !Config.accessList.ssl_bump)
-        debugs(33, 1, "WARNING: http_port(s) with SslBump found, but no " <<
-               std::endl << "\tssl_bump ACL configured. No requests will be " <<
-               "bumped.");
-#endif
+    Must(AddOpenedHttpSocket(fd)); // otherwise, we have received a fd we did not ask for
 }
 
 #if USE_SSL
@@ -3400,7 +3463,6 @@
 clientHttpsConnectionsOpen(void)
 {
     https_port_list *s;
-    int fd;
 
     for (s = Config.Sockaddr.https; s; s = (https_port_list *)s->http.next) {
         if (MAXHTTPPORTS == NHttpSockets) {
@@ -3415,25 +3477,33 @@
             continue;
         }
 
-        enter_suid();
-        fd = comm_open_listener(SOCK_STREAM,
-                                IPPROTO_TCP,
-                                s->http.s,
-                                COMM_NONBLOCKING, "HTTPS Socket");
-        leave_suid();
-
-        if (fd < 0)
-            continue;
+        AsyncCall::Pointer call = asyncCall(33, 2, "clientHttpsConnectionOpened",
+            ListeningStartedDialer(&clientHttpsConnectionOpened, &s->http));
+
+        Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->http.s, COMM_NONBLOCKING,
+            Ipc::fdnHttpsSocket, call);
+
+        HttpSockets[NHttpSockets++] = -1;
+    }
+}
+
+/// process clientHttpsConnectionsOpen result
+static void
+clientHttpsConnectionOpened(int fd, int, http_port_list *s)
+{
+    if (!OpenedHttpSocket(fd, "Cannot open HTTPS Port"))
+        return;
+
+    Must(s);
 
         AsyncCall::Pointer call = commCbCall(5,5, "SomeCommAcceptHandler(httpsAccept)",
                                              CommAcceptCbPtrFun(httpsAccept, s));
 
         s->listener = new Comm::ListenStateData(fd, call, true);
 
-        debugs(1, 1, "Accepting HTTPS connections at " << s->http.s << ", FD " << fd << ".");
+        debugs(1, 1, "Accepting HTTPS connections at " << s->s << ", FD " << fd << ".");
 
-        HttpSockets[NHttpSockets++] = fd;
-    }
+    Must(AddOpenedHttpSocket(fd)); // otherwise, we have received a fd we did not ask for
 }
 
 #endif
@@ -3447,7 +3517,7 @@
 #endif
 
     if (NHttpSockets < 1)
-        fatal("Cannot open HTTP Port");
+        fatal("No HTTP or HTTPS ports configured"); // defaults prohibit this?
 }
 
 void

=== modified file 'src/comm.cc'
--- src/comm.cc	2010-01-02 10:17:00 +0000
+++ src/comm.cc	2010-05-04 15:34:46 +0000
@@ -71,6 +71,8 @@
 
 static void commStopHalfClosedMonitor(int fd);
 static IOCB commHalfClosedReader;
+static void comm_init_opened(int new_socket, IpAddress &addr, unsigned char TOS, const char *note, struct addrinfo *AI);
+static int comm_apply_flags(int new_socket, IpAddress &addr, int flags, struct addrinfo *AI);
 
 
 struct comm_io_callback_t {
@@ -687,7 +689,6 @@
             const char *note)
 {
     int new_socket;
-    fde *F = NULL;
     int tos = 0;
     struct addrinfo *AI = NULL;
 
@@ -743,6 +744,29 @@
 
 #endif
 
+    comm_init_opened(new_socket, addr, TOS, note, AI);
+    new_socket = comm_apply_flags(new_socket, addr, flags, AI);
+
+    addr.FreeAddrInfo(AI);
+
+    PROF_stop(comm_open);
+
+    return new_socket;
+}
+
+/// update FD tables after a local or remote (IPC) comm_openex();
+void
+comm_init_opened(int new_socket,
+            IpAddress &addr,
+            unsigned char TOS,
+            const char *note,
+            struct addrinfo *AI)
+{
+    assert(new_socket >= 0);
+    assert(AI);
+
+    fde *F = NULL;
+
     /* update fdstat */
     debugs(5, 5, "comm_open: FD " << new_socket << " is a new socket");
 
@@ -760,6 +784,19 @@
     F->tos = TOS;
 
     F->sock_family = AI->ai_family;
+}
+
+/// apply flags after a local comm_open*() call;
+/// returns new_socket or -1 on error
+static int
+comm_apply_flags(int new_socket,
+            IpAddress &addr,
+            int flags,
+            struct addrinfo *AI)
+{
+    assert(new_socket >= 0);
+    assert(AI);
+    const int sock_type = AI->ai_socktype;
 
     if (!(flags & COMM_NOCLOEXEC))
         commSetCloseOnExec(new_socket);
@@ -790,18 +827,14 @@
 
         if (commBind(new_socket, *AI) != COMM_OK) {
             comm_close(new_socket);
-            addr.FreeAddrInfo(AI);
             return -1;
-            PROF_stop(comm_open);
         }
     }
 
-    addr.FreeAddrInfo(AI);
-
     if (flags & COMM_NONBLOCKING)
         if (commSetNonBlocking(new_socket) == COMM_ERROR) {
+            comm_close(new_socket);
             return -1;
-            PROF_stop(comm_open);
         }
 
 #ifdef TCP_NODELAY
@@ -813,11 +846,50 @@
     if (Config.tcpRcvBufsz > 0 && sock_type == SOCK_STREAM)
         commSetTcpRcvbuf(new_socket, Config.tcpRcvBufsz);
 
-    PROF_stop(comm_open);
-
     return new_socket;
 }
 
+void
+comm_import_opened(int fd,
+            IpAddress &addr,
+            int flags,
+            const char *note,
+            struct addrinfo *AI)
+{
+    debugs(5, 2, HERE << " FD " << fd << " at " << addr);
+    assert(fd >= 0);
+    assert(AI);
+
+    comm_init_opened(fd, addr, 0, note, AI);
+
+    if (!(flags & COMM_NOCLOEXEC))
+        fd_table[fd].flags.close_on_exec = 1;
+
+    if (addr.GetPort() > (u_short) 0) {
+#ifdef _SQUID_MSWIN_
+        if (sock_type != SOCK_DGRAM)
+#endif
+            fd_table[fd].flags.nolinger = 1;
+    }
+
+    if ((flags & COMM_TRANSPARENT))
+        fd_table[fd].flags.transparent = 1;
+
+    if (flags & COMM_NONBLOCKING)
+        fd_table[fd].flags.nonblocking = 1;
+
+#ifdef TCP_NODELAY
+    if (AI->ai_socktype == SOCK_STREAM)
+        fd_table[fd].flags.nodelay = 1;
+#endif
+
+    /* no fd_table[fd].flags. updates needed for these conditions:
+     * if ((flags & COMM_REUSEADDR)) ...
+     * if ((flags & COMM_DOBIND) ...) ...
+     */
+}
+
+
 CBDATA_CLASS_INIT(ConnectStateData);
 
 void *
@@ -2409,3 +2481,97 @@
         return EVENT_ERROR;
     };
 }
+
+/// Create a unix-domain socket (UDS) that only supports FD_MSGHDR I/O.
+int
+comm_open_uds(int sock_type,
+              int proto,
+              struct sockaddr_un* addr,
+              int flags)
+{
+    // TODO: merge with comm_openex() when IpAddress becomes NetAddress
+
+    int new_socket;
+
+    PROF_start(comm_open);
+    /* Create socket for accepting new connections. */
+    statCounter.syscalls.sock.sockets++;
+
+    /* Setup the socket addrinfo details for use */
+    struct addrinfo AI;
+    AI.ai_flags = 0;
+    AI.ai_family = PF_UNIX;
+    AI.ai_socktype = sock_type;
+    AI.ai_protocol = proto;
+    AI.ai_addrlen = SUN_LEN(addr);
+    AI.ai_addr = (sockaddr*)addr;
+    AI.ai_canonname = NULL;
+    AI.ai_next = NULL;
+
+    debugs(50, 3, HERE << "Attempt open socket for: " << addr->sun_path);
+
+    if ((new_socket = socket(AI.ai_family, AI.ai_socktype, AI.ai_protocol)) < 0) {
+        /* Increase the number of reserved fd's if calls to socket()
+         * are failing because the open file table is full.  This
+         * limits the number of simultaneous clients */
+
+        if (limitError(errno)) {
+            debugs(50, DBG_IMPORTANT, HERE << "socket failure: " << xstrerror());
+            fdAdjustReserved();
+        } else {
+            debugs(50, DBG_CRITICAL, HERE << "socket failure: " << xstrerror());
+        }
+
+        PROF_stop(comm_open);
+        return -1;
+    }
+
+    debugs(50, 3, HERE "Opened UDS FD " << new_socket << " : family=" << AI.ai_family << ", type=" << AI.ai_socktype << ", protocol=" << AI.ai_protocol);
+
+    /* update fdstat */
+    debugs(50, 5, HERE << "FD " << new_socket << " is a new socket");
+
+    assert(!isOpen(new_socket));
+    fd_open(new_socket, FD_MSGHDR, NULL);
+
+    fdd_table[new_socket].close_file = NULL;
+
+    fdd_table[new_socket].close_line = 0;
+
+    fd_table[new_socket].sock_family = AI.ai_family;
+
+    if (!(flags & COMM_NOCLOEXEC))
+        commSetCloseOnExec(new_socket);
+
+    if (flags & COMM_REUSEADDR)
+        commSetReuseAddr(new_socket);
+
+    if (flags & COMM_NONBLOCKING) {
+        if (commSetNonBlocking(new_socket) != COMM_OK) {
+            comm_close(new_socket);
+            PROF_stop(comm_open);
+            return -1;
+        }
+    }
+
+    if (flags & COMM_DOBIND) {
+        if (commBind(new_socket, AI) != COMM_OK) {
+            comm_close(new_socket);
+            PROF_stop(comm_open);
+            return -1;
+        }
+    }
+
+#ifdef TCP_NODELAY
+    if (sock_type == SOCK_STREAM)
+        commSetTcpNoDelay(new_socket);
+
+#endif
+
+    if (Config.tcpRcvBufsz > 0 && sock_type == SOCK_STREAM)
+        commSetTcpRcvbuf(new_socket, Config.tcpRcvBufsz);
+
+    PROF_stop(comm_open);
+
+    return new_socket;
+}

=== modified file 'src/comm.h'
--- src/comm.h	2009-12-31 02:35:01 +0000
+++ src/comm.h	2010-05-02 01:42:47 +0000
@@ -55,6 +55,9 @@
 SQUIDCEXTERN void comm_exit(void);
 
 SQUIDCEXTERN int comm_open(int, int, IpAddress &, int, const char *note);
+SQUIDCEXTERN int comm_open_uds(int sock_type, int proto, struct sockaddr_un* addr, int flags);
+/// update Comm state after getting a comm_open() FD from another process
+SQUIDCEXTERN void comm_import_opened(int fd, IpAddress &addr, int flags, const char *note, struct addrinfo *AI);
 
 /**
  * Open a port specially bound for listening or sending through a specific port.

=== modified file 'src/debug.cc'
--- src/debug.cc	2009-11-22 20:37:27 +0000
+++ src/debug.cc	2010-04-26 07:09:03 +0000
@@ -36,6 +36,7 @@
 #include "Debug.h"
 #include "SquidTime.h"
 #include "util.h"
+#include "ipc/Kids.h"
 
 /* for shutting_down flag in xassert() */
 #include "globals.h"
@@ -52,6 +53,7 @@
 static char *debug_log_file = NULL;
 static int Ctx_Lock = 0;
 static const char *debugLogTime(void);
+static const char *debugLogKid(void);
 static void ctx_print(void);
 #if HAVE_SYSLOG
 #ifdef LOG_LOCAL4
@@ -117,8 +119,9 @@
     va_start(args2, format);
     va_start(args3, format);
 
-    snprintf(f, BUFSIZ, "%s| %s",
+    snprintf(f, BUFSIZ, "%s%s| %s",
              debugLogTime(),
+             debugLogKid(),
              format);
 
     _db_print_file(f, args1);
@@ -543,6 +546,18 @@
     return buf;
 }
 
+static const char *
+debugLogKid(void)
+{
+    if (KidIdentifier != 0) {
+		static char buf[16];
+        snprintf(buf, sizeof(buf), " kid%d", KidIdentifier);
+		return buf;
+    }
+
+    return "";
+}
+
 void
 xassert(const char *msg, const char *file, int line)
 {

=== modified file 'src/enums.h'
--- src/enums.h	2010-01-01 21:16:57 +0000
+++ src/enums.h	2010-04-29 20:12:03 +0000
@@ -69,6 +69,7 @@
     FD_FILE,
     FD_SOCKET,
     FD_PIPE,
+    FD_MSGHDR,
     FD_UNKNOWN
 };
 

=== modified file 'src/fd.cc'
--- src/fd.cc	2009-03-06 13:26:57 +0000
+++ src/fd.cc	2010-04-29 20:12:03 +0000
@@ -45,6 +45,9 @@
 int socket_write_method(int, const char *, int);
 int file_read_method(int, char *, int);
 int file_write_method(int, const char *, int);
+#else
+int msghdr_read_method(int, char *, int);
+int msghdr_write_method(int, const char *, int);
 #endif
 
 const char *fdTypeStr[] = {
@@ -53,6 +56,7 @@
     "File",
     "Socket",
     "Pipe",
+    "MsgHdr",
     "Unknown"
 };
 
@@ -169,6 +173,24 @@
     return i;
 }
 
+int
+msghdr_read_method(int fd, char *buf, int len)
+{
+    PROF_start(read);
+    const int i = recvmsg(fd, reinterpret_cast<msghdr*>(buf), MSG_DONTWAIT);
+    PROF_stop(read);
+    return i;
+}
+
+int
+msghdr_write_method(int fd, const char *buf, int len)
+{
+    PROF_start(write);
+    const int i = sendmsg(fd, reinterpret_cast<const msghdr*>(buf), MSG_NOSIGNAL);
+    PROF_stop(write);
+    return i > 0 ? len : i; // len is imprecise but the caller expects a match
+}
+
 #endif
 
 void
@@ -213,9 +235,18 @@
     }
 
 #else
-    F->read_method = &default_read_method;
-
-    F->write_method = &default_write_method;
+    switch (type) {
+
+    case FD_MSGHDR:
+        F->read_method = &msghdr_read_method;
+        F->write_method = &msghdr_write_method;
+        break;
+
+    default:
+        F->read_method = &default_read_method;
+        F->write_method = &default_write_method;
+        break;
+    }
 
 #endif
 

=== modified file 'src/htcp.cc'
--- src/htcp.cc	2010-02-11 23:48:51 +0000
+++ src/htcp.cc	2010-06-14 21:22:01 +0000
@@ -46,6 +46,24 @@
 #include "http.h"
 #include "icmp/net_db.h"
 #include "AccessLogEntry.h"
+#include "ipc/StartListening.h"
+
+/// dials htcpIncomingConnectionOpened call
+class HtcpListeningStartedDialer: public CallDialer,
+    public Ipc::StartListeningCb
+{
+public:
+    typedef void (*Handler)(int fd, int errNo);
+    HtcpListeningStartedDialer(Handler aHandler): handler(aHandler) {}
+
+    virtual void print(std::ostream &os) const { startPrint(os) << ')'; }
+
+    virtual bool canDial(AsyncCall &) const { return true; }
+    virtual void dial(AsyncCall &) { (handler)(fd, errNo); }
+
+public:
+    Handler handler;
+};
 
 typedef struct _Countstr Countstr;
 
@@ -225,6 +243,8 @@
     RR_RESPONSE
 };
 
+static void htcpIncomingConnectionOpened(int fd, int errNo);
+
 static u_int32_t msg_id_counter = 0;
 static int htcpInSocket = -1;
 static int htcpOutSocket = -1;
@@ -1483,20 +1503,15 @@
     IpAddress incomingAddr = Config.Addrs.udp_incoming;
     incomingAddr.SetPort(Config.Port.htcp);
 
-    enter_suid();
-    htcpInSocket = comm_open_listener(SOCK_DGRAM,
+    AsyncCall::Pointer call = asyncCall(31, 2,
+        "htcpIncomingConnectionOpened",
+        HtcpListeningStartedDialer(&htcpIncomingConnectionOpened));
+
+    Ipc::StartListening(SOCK_DGRAM,
                                       IPPROTO_UDP,
                                       incomingAddr,
                                       COMM_NONBLOCKING,
-                                      "HTCP Socket");
-    leave_suid();
-
-    if (htcpInSocket < 0)
-        fatal("Cannot open HTCP Socket");
-
-    commSetSelect(htcpInSocket, COMM_SELECT_READ, htcpRecv, NULL, 0);
-
-    debugs(31, 1, "Accepting HTCP messages on port " << Config.Port.htcp << ", FD " << htcpInSocket << ".");
+                                      Ipc::fdnInHtcpSocket, call);
 
     if (!Config.Addrs.udp_outgoing.IsNoAddr()) {
         IpAddress outgoingAddr = Config.Addrs.udp_outgoing;
@@ -1518,8 +1533,6 @@
         debugs(31, 1, "Outgoing HTCP messages on port " << Config.Port.htcp << ", FD " << htcpOutSocket << ".");
 
         fd_note(htcpInSocket, "Incoming HTCP socket");
-    } else {
-        htcpOutSocket = htcpInSocket;
     }
 
     if (!htcpDetailPool) {
@@ -1527,6 +1540,22 @@
     }
 }
 
+static void
+htcpIncomingConnectionOpened(int fd, int errNo)
+{
+    htcpInSocket = fd;
+
+    if (htcpInSocket < 0)
+        fatal("Cannot open HTCP Socket");
+
+    commSetSelect(htcpInSocket, COMM_SELECT_READ, htcpRecv, NULL, 0);
+
+    debugs(31, 1, "Accepting HTCP messages on port " << Config.Port.htcp << ", FD " << htcpInSocket << ".");
+
+    if (Config.Addrs.udp_outgoing.IsNoAddr())
+        htcpOutSocket = htcpInSocket;
+}
+
 int
 htcpQuery(StoreEntry * e, HttpRequest * req, peer * p)
 {

=== modified file 'src/icp_v2.cc'
--- src/icp_v2.cc	2009-11-09 11:25:11 +0000
+++ src/icp_v2.cc	2010-06-14 21:22:01 +0000
@@ -48,8 +48,31 @@
 #include "SwapDir.h"
 #include "icmp/net_db.h"
 #include "ip/IpAddress.h"
+#include "ipc/StartListening.h"
 #include "rfc1738.h"
 
+/// dials icpIncomingConnectionOpened call
+class IcpListeningStartedDialer: public CallDialer,
+    public Ipc::StartListeningCb
+{
+public:
+    typedef void (*Handler)(int fd, int errNo, IpAddress& addr);
+    IcpListeningStartedDialer(Handler aHandler, IpAddress& anAddr):
+        handler(aHandler), addr(anAddr) {}
+
+    virtual void print(std::ostream &os) const { startPrint(os) <<
+        ", address=" << addr << ')'; }
+
+    virtual bool canDial(AsyncCall &) const { return true; }
+    virtual void dial(AsyncCall &) { (handler)(fd, errNo, addr); }
+
+public:
+    Handler handler;
+    IpAddress addr;
+};
+
+static void icpIncomingConnectionOpened(int fd, int errNo, IpAddress& addr);
+
 /// \ingroup ServerProtocolICPInternal2
 static void icpLogIcp(const IpAddress &, log_type, int, const char *, int);
 
@@ -656,35 +679,22 @@
 
     struct addrinfo *xai = NULL;
     int x;
-    wordlist *s;
 
     if ((port = Config.Port.icp) <= 0)
         return;
 
-    enter_suid();
-
     addr = Config.Addrs.udp_incoming;
     addr.SetPort(port);
-    theInIcpConnection = comm_open_listener(SOCK_DGRAM,
+
+    AsyncCall::Pointer call = asyncCall(12, 2,
+        "icpIncomingConnectionOpened",
+        IcpListeningStartedDialer(&icpIncomingConnectionOpened, addr));
+
+    Ipc::StartListening(SOCK_DGRAM,
                                             IPPROTO_UDP,
                                             addr,
                                             COMM_NONBLOCKING,
-                                            "ICP Socket");
-    leave_suid();
-
-    if (theInIcpConnection < 0)
-        fatal("Cannot open ICP Port");
-
-    commSetSelect(theInIcpConnection,
-                  COMM_SELECT_READ,
-                  icpHandleUdp,
-                  NULL,
-                  0);
-
-    for (s = Config.mcast_group_list; s; s = s->next)
-        ipcache_nbgethostbyname(s->key, mcastJoinGroups, NULL);
-
-    debugs(12, 1, "Accepting ICP messages at " << addr << ", FD " << theInIcpConnection << ".");
+                                            Ipc::fdnInIcpSocket, call);
 
     addr.SetEmpty(); // clear for next use.
     addr = Config.Addrs.udp_outgoing;
@@ -710,10 +720,6 @@
         debugs(12, 1, "Outgoing ICP messages on port " << addr.GetPort() << ", FD " << theOutIcpConnection << ".");
 
         fd_note(theOutIcpConnection, "Outgoing ICP socket");
-
-        fd_note(theInIcpConnection, "Incoming ICP socket");
-    } else {
-        theOutIcpConnection = theInIcpConnection;
     }
 
     theOutICPAddr.SetEmpty();
@@ -730,6 +736,31 @@
     theOutICPAddr.FreeAddrInfo(xai);
 }
 
+static void
+icpIncomingConnectionOpened(int fd, int errNo, IpAddress& addr)
+{
+    theInIcpConnection = fd;
+
+    if (theInIcpConnection < 0)
+        fatal("Cannot open ICP Port");
+
+    commSetSelect(theInIcpConnection,
+                  COMM_SELECT_READ,
+                  icpHandleUdp,
+                  NULL,
+                  0);
+
+    for (const wordlist *s = Config.mcast_group_list; s; s = s->next)
+        ipcache_nbgethostbyname(s->key, mcastJoinGroups, NULL);
+
+    debugs(12, 1, "Accepting ICP messages at " << addr << ", FD " << theInIcpConnection << ".");
+
+        fd_note(theInIcpConnection, "Incoming ICP socket");
+
+    if (Config.Addrs.udp_outgoing.IsNoAddr())
+        theOutIcpConnection = theInIcpConnection;
+}
+
 /**
  * icpConnectionShutdown only closes the 'in' socket if it is
  * different than the 'out' socket.

=== modified file 'src/ip/IpAddress.cc'
--- src/ip/IpAddress.cc	2010-03-01 01:13:17 +0000
+++ src/ip/IpAddress.cc	2010-05-02 01:20:05 +0000
@@ -879,6 +879,11 @@
     return 0;
 }
 
+int IpAddress::compareWhole(const IpAddress &rhs) const
+{
+    return memcmp(this, &rhs, sizeof(*this));
+}
+
 bool IpAddress::operator ==(const IpAddress &s) const
 {
     return (0 == matchIPAddr(s));
@@ -1041,9 +1046,9 @@
 
     p += ToHostname(p, blen);
 
-    if (m_SocketAddr.sin6_port > 0 && p < (buf+blen-6) ) {
-        /* 6 is max length of expected ':port' (short int) */
-        snprintf(p, 6,":%d", GetPort() );
+    if (m_SocketAddr.sin6_port > 0 && p <= (buf+blen-7) ) {
+        // ':port' (short int) needs at most 6 bytes plus 1 for 0-terminator
+        snprintf(p, 7, ":%d", GetPort() );
     }
 
     // force a null-terminated string

=== modified file 'src/ip/IpAddress.h'
--- src/ip/IpAddress.h	2010-02-28 22:04:23 +0000
+++ src/ip/IpAddress.h	2010-05-02 01:20:05 +0000
@@ -326,6 +326,13 @@
      */
     int matchIPAddr(const IpAddress &rhs) const;
 
+    /** Compare taking IP, port, protocol, etc. into account. Returns an
+        integer  less  than,  equal  to,  or greater than zero if the object
+        is found, respectively, to be less than, to match, or to be greater
+        than rhs. The exact ordering algorithm is not specified and may change.
+    */
+    int compareWhole(const IpAddress &rhs) const;
+
     /**
      *  Get RFC 3493 addrinfo structure from the IpAddress data
      *  for protocol-neutral socket operations.

=== added directory 'src/ipc'
=== added file 'src/ipc/Coordinator.cc'
--- src/ipc/Coordinator.cc	1970-01-01 00:00:00 +0000
+++ src/ipc/Coordinator.cc	2010-05-02 18:49:25 +0000
@@ -0,0 +1,172 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+
+#include "config.h"
+#include "comm.h"
+#include "ipc/Coordinator.h"
+#include "ipc/FdNotes.h"
+#include "ipc/SharedListen.h"
+
+
+CBDATA_NAMESPACED_CLASS_INIT(Ipc, Coordinator);
+Ipc::Coordinator* Ipc::Coordinator::TheInstance = NULL;
+
+
+Ipc::Coordinator::Coordinator():
+    Port(coordinatorAddr)
+{
+}
+
+void Ipc::Coordinator::start()
+{
+    Port::start();
+}
+
+Ipc::StrandCoord* Ipc::Coordinator::findStrand(int kidId)
+{
+    typedef Strands::iterator SI;
+    for (SI iter = strands.begin(); iter != strands.end(); ++iter) {
+        if (iter->kidId == kidId)
+            return &(*iter);
+    }
+    return NULL;
+}
+
+void Ipc::Coordinator::registerStrand(const StrandCoord& strand)
+{
+    if (StrandCoord* found = findStrand(strand.kidId))
+        *found = strand;
+    else
+        strands.push_back(strand);
+}
+
+void Ipc::Coordinator::receive(const TypedMsgHdr& message)
+{
+    switch (message.type()) {
+    case mtRegistration:
+        debugs(54, 6, HERE << "Registration request");
+        handleRegistrationRequest(StrandCoord(message));
+        break;
+
+    case mtSharedListenRequest:
+        debugs(54, 6, HERE << "Shared listen request");
+        handleSharedListenRequest(SharedListenRequest(message));
+        break;
+
+    case mtDescriptorGet:
+        debugs(54, 6, HERE << "Descriptor get request");
+        handleDescriptorGet(Descriptor(message));
+        break;
+
+    default:
+        debugs(54, 1, HERE << "Unhandled message type: " << message.type());
+        break;
+    }
+}
+
+void Ipc::Coordinator::handleRegistrationRequest(const StrandCoord& strand)
+{
+    registerStrand(strand);
+
+    // send back an acknowledgement; TODO: remove as not needed?
+    TypedMsgHdr message;
+    strand.pack(message);
+    SendMessage(MakeAddr(strandAddrPfx, strand.kidId), message);
+}
+
+void
+Ipc::Coordinator::handleSharedListenRequest(const SharedListenRequest& request)
+{
+    debugs(54, 4, HERE << "kid" << request.requestorId <<
+        " needs shared listen FD for " << request.params.addr);
+    Listeners::const_iterator i = listeners.find(request.params);
+    int errNo = 0;
+    const int sock = (i != listeners.end()) ?
+        i->second : openListenSocket(request, errNo);
+
+    debugs(54, 3, HERE << "sending shared listen FD " << sock << " for " <<
+        request.params.addr << " to kid" << request.requestorId <<
+        " mapId=" << request.mapId);
+
+    SharedListenResponse response(sock, errNo, request.mapId);
+    TypedMsgHdr message;
+    response.pack(message);
+    SendMessage(MakeAddr(strandAddrPfx, request.requestorId), message);
+}
+
+int
+Ipc::Coordinator::openListenSocket(const SharedListenRequest& request,
+        int &errNo)
+{
+    const OpenListenerParams &p = request.params;
+
+    debugs(54, 6, HERE << "opening listen FD at " << p.addr << " for kid" <<
+        request.requestorId);
+
+    IpAddress addr = p.addr; // comm_open_listener may modify it
+
+    enter_suid();
+    const int sock = comm_open_listener(p.sock_type, p.proto, addr, p.flags,
+        FdNote(p.fdNote));
+    errNo = (sock >= 0) ? 0 : errno;
+    leave_suid();
+
+    // cache positive results
+    if (sock >= 0)
+        listeners[request.params] = sock;
+
+    return sock;
+}
+
+void Ipc::Coordinator::handleDescriptorGet(const Descriptor& request)
+{
+    // XXX: hack: create descriptor here
+    char buffer[64];
+    snprintf(buffer, sizeof(buffer), "/tmp/squid_shared_file.txt");
+    static int fd = -1;
+    if (fd < 0) {
+        fd = open(buffer, O_CREAT | O_RDWR | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+        int n = snprintf(buffer, sizeof(buffer), "coord: created %d\n", fd);
+        ssize_t bytes = write(fd, buffer, n);
+        Must(bytes == n);
+        debugs(54, 6, "Created FD " << fd << " for kid" << request.fromKid);
+    } else {
+        int n = snprintf(buffer, sizeof(buffer), "coord: updated %d\n", fd);
+        ssize_t bytes = write(fd, buffer, n);
+        Must(bytes == n);
+    }
+
+    debugs(54, 6, "Sending FD " << fd << " to kid" << request.fromKid);
+
+    Descriptor response(-1, fd);
+    TypedMsgHdr message;
+    response.pack(message);
+    SendMessage(MakeAddr(strandAddrPfx, request.fromKid), message);
+
+    // XXX: close(fd); fd should be opened until the message has not reached rec iver 
+}
+
+void Ipc::Coordinator::broadcastSignal(int sig) const
+{
+    typedef Strands::const_iterator SCI;
+    for (SCI iter = strands.begin(); iter != strands.end(); ++iter) {
+        debugs(54, 5, HERE << "signal " << sig << " to kid" << iter->kidId <<
+            ", PID=" << iter->pid);
+        kill(iter->pid, sig);
+    }
+}
+
+Ipc::Coordinator* Ipc::Coordinator::Instance()
+{
+    if (!TheInstance)
+        TheInstance = new Coordinator;
+    // XXX: if the Coordinator job quits, this pointer will become invalid
+    // we could make Coordinator death fatal, except during exit, but since
+    // Strands do not re-register, even process death would be pointless.
+    return TheInstance;
+}

=== added file 'src/ipc/Coordinator.h'
--- src/ipc/Coordinator.h	1970-01-01 00:00:00 +0000
+++ src/ipc/Coordinator.h	2010-05-02 18:49:25 +0000
@@ -0,0 +1,66 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#ifndef SQUID_IPC_COORDINATOR_H
+#define SQUID_IPC_COORDINATOR_H
+
+
+#include "Array.h"
+#include <map>
+#include "ipc/Port.h"
+#include "ipc/Messages.h"
+#include "ipc/SharedListen.h"
+
+namespace Ipc
+{
+
+///  Coordinates shared activities of Strands (Squid processes or threads)
+class Coordinator: public Port
+{
+public:
+    static Coordinator* Instance();
+
+public:
+    Coordinator();
+
+    void broadcastSignal(int sig) const; ///< send sig to registered strands
+
+protected:
+    virtual void start(); // Port (AsyncJob) API
+    virtual void receive(const TypedMsgHdr& message); // Port API
+
+    StrandCoord* findStrand(int kidId); ///< registered strand or NULL
+    void registerStrand(const StrandCoord &); ///< adds or updates existing
+    void handleRegistrationRequest(const StrandCoord &); ///< register,ACK
+
+    /// returns cached socket or calls openListenSocket()
+    void handleSharedListenRequest(const SharedListenRequest& request);
+    void handleDescriptorGet(const Descriptor& request);
+
+    /// calls comm_open_listener()
+    int openListenSocket(const SharedListenRequest& request, int &errNo);
+
+private:
+    typedef Vector<StrandCoord> Strands; ///< unsorted strands
+    Strands strands; ///< registered processes and threads
+
+    typedef std::map<OpenListenerParams, int> Listeners; ///< params:fd map
+    Listeners listeners; ///< cached comm_open_listener() results
+
+    static Coordinator* TheInstance; ///< the only class instance in existence
+
+    CBDATA_CLASS2(Coordinator);
+
+private:
+    Coordinator(const Coordinator&); // not implemented
+    Coordinator& operator =(const Coordinator&); // not implemented
+};
+
+
+} // namespace Ipc
+
+#endif /* SQUID_IPC_COORDINATOR_H */

=== added file 'src/ipc/FdNotes.cc'
--- src/ipc/FdNotes.cc	1970-01-01 00:00:00 +0000
+++ src/ipc/FdNotes.cc	2010-06-14 21:22:01 +0000
@@ -0,0 +1,31 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#include "config.h"
+#include "Debug.h"
+#include "ipc/FdNotes.h"
+
+
+const char *
+Ipc::FdNote(int fdNoteId)
+{
+    static const char *FdNotes[Ipc::fdnEnd] = {
+        "None", // fdnNone
+        "HTTP Socket", // fdnHttpSocket
+        "HTTPS Socket", // fdnHttpsSocket
+        "Incoming SNMP Socket", // fdnInSnmpSocket
+        "Outgoing SNMP Socket", // fdnOutSnmpSocket
+        "Incoming ICP Socket", // fdnInIcpSocket
+        "Incoming HTCP Socket" // fdnInHtcpSocket
+    };
+
+    if (fdnNone < fdNoteId && fdNoteId < fdnEnd)
+        return FdNotes[fdNoteId];
+
+    debugs(54, 1, HERE << "salvaged bug: wrong fd_note ID: " << fdNoteId);
+    return FdNotes[fdnNone];
+}

=== added file 'src/ipc/FdNotes.h'
--- src/ipc/FdNotes.h	1970-01-01 00:00:00 +0000
+++ src/ipc/FdNotes.h	2010-06-14 21:22:01 +0000
@@ -0,0 +1,26 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#ifndef SQUID_IPC_FD_NOTES_H
+#define SQUID_IPC_FD_NOTES_H
+
+namespace Ipc
+{
+
+/// We cannot send char* FD notes to other processes. Pass int IDs and convert.
+
+/// fd_note() label ID
+typedef enum { fdnNone, fdnHttpSocket, fdnHttpsSocket,
+               fdnInSnmpSocket, fdnOutSnmpSocket, 
+               fdnInIcpSocket, fdnInHtcpSocket, fdnEnd } FdNoteId;
+
+extern const char *FdNote(int fdNodeId); ///< converts FdNoteId into a string
+
+} // namespace Ipc;
+
+
+#endif /* SQUID_IPC_FD_NOTES_H */

=== added file 'src/ipc/Kid.cc'
--- src/ipc/Kid.cc	1970-01-01 00:00:00 +0000
+++ src/ipc/Kid.cc	2010-03-30 21:54:40 +0000
@@ -0,0 +1,122 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#include "config.h"
+#include "ipc/Kid.h"
+
+Kid::Kid():
+    badFailures(0),
+    pid(-1),
+    startTime(0),
+    isRunning(false)
+{
+}
+
+Kid::Kid(const String& kid_name):
+    theName(kid_name),
+    badFailures(0),
+    pid(-1),
+    startTime(0),
+    isRunning(false)
+{
+}
+
+/// called when this kid got started, records PID
+void Kid::start(pid_t cpid)
+{
+    assert(!running());
+    assert(cpid > 0);
+
+    isRunning = true;
+    pid = cpid;
+    time(&startTime);
+}
+
+/// called when kid terminates, sets exiting status
+void Kid::stop(status_type exitStatus)
+{
+    assert(running());
+    assert(startTime != 0);
+
+    isRunning = false;
+
+    time_t stop_time;
+    time(&stop_time);
+    if ((stop_time - startTime) < fastFailureTimeLimit)
+        badFailures++;
+    else
+        badFailures = 0; // the failures are not "frequent" [any more]
+
+    status = exitStatus;
+}
+
+/// returns true if tracking of kid is stopped
+bool Kid::running() const
+{
+    return isRunning;
+}
+
+/// returns current pid for a running kid and last pid for a stopped kid
+pid_t Kid::getPid() const
+{
+    assert(pid > 0);
+    return pid;
+}
+
+/// whether the failures are "repeated and frequent"
+bool Kid::hopeless() const
+{
+    return badFailures > badFailureLimit;
+}
+
+/// returns true if the process terminated normally
+bool Kid::calledExit() const
+{
+    return (pid > 0) && !running() && WIFEXITED(status);
+}
+
+/// returns the exit status of the process
+int Kid::exitStatus() const
+{
+    return WEXITSTATUS(status);
+}
+
+/// whether the process exited with a given exit status code
+bool Kid::calledExit(int code) const
+{
+    return calledExit() && (exitStatus() == code);
+}
+
+/// whether the process exited with code 0
+bool Kid::exitedHappy() const
+{
+    return calledExit(0);
+}
+
+/// returns true if the kid was terminated by a signal
+bool Kid::signaled() const
+{
+    return (pid > 0) && !running() && WIFSIGNALED(status);
+}
+
+/// returns the number of the signal that caused the kid to terminate
+int Kid::termSignal() const
+{
+    return WTERMSIG(status);
+}
+
+/// whether the process was terminated by a given signal
+bool Kid::signaled(int sgnl) const
+{
+    return signaled() && (termSignal() == sgnl);
+}
+
+/// returns kid name
+const String& Kid::name() const
+{
+    return theName;
+}

=== added file 'src/ipc/Kid.h'
--- src/ipc/Kid.h	1970-01-01 00:00:00 +0000
+++ src/ipc/Kid.h	2010-03-30 21:54:40 +0000
@@ -0,0 +1,85 @@
+/*
+ * $Id$
+ *
+ */
+
+#ifndef SQUID_IPC_KID_H
+#define SQUID_IPC_KID_H
+
+#include "SquidString.h"
+
+
+/// Squid child, including current forked process info and
+/// info persistent across restarts
+class Kid
+{
+public:
+#ifdef _SQUID_NEXT_
+    typedef union wait status_type;
+#else
+    typedef int status_type;
+#endif
+
+    /// keep restarting until the number of bad failures exceed this limit
+    enum { badFailureLimit = 4 };
+
+    /// slower start failures are not "frequent enough" to be counted as "bad"
+    enum { fastFailureTimeLimit = 10 }; // seconds
+
+public:
+    Kid();
+
+    Kid(const String& kid_name);
+
+    /// called when this kid got started, records PID
+    void start(pid_t cpid);
+
+    /// called when kid terminates, sets exiting status
+    void stop(status_type exitStatus);
+
+    /// returns true if tracking of kid is stopped
+    bool running() const;
+
+    /// returns current pid for a running kid and last pid for a stopped kid
+    pid_t getPid() const;
+
+    /// whether the failures are "repeated and frequent"
+    bool hopeless() const;
+
+    /// returns true if the process terminated normally
+    bool calledExit() const;
+
+    /// returns the exit status of the process
+    int exitStatus() const;
+
+    /// whether the process exited with a given exit status code
+    bool calledExit(int code) const;
+
+    /// whether the process exited with code 0
+    bool exitedHappy() const;
+
+    /// returns true if the kid was terminated by a signal
+    bool signaled() const;
+
+    /// returns the number of the signal that caused the kid to terminate
+    int termSignal() const;
+
+    /// whether the process was terminated by a given signal
+    bool signaled(int sgnl) const;
+
+    /// returns kid name
+    const String& name() const;
+
+private:
+    // Information preserved across restarts
+    String theName; ///< process name
+    int badFailures; ///< number of "repeated frequent" failures
+
+    // Information specific to a running or stopped kid
+    pid_t  pid; ///< current (for a running kid) or last (for stopped kid) PID
+    time_t startTime; ///< last start time
+    bool   isRunning; ///< whether the kid is assumed to be alive
+    status_type status; ///< exit status of a stopped kid
+};
+
+#endif /* SQUID_IPC_KID_H */

=== added file 'src/ipc/Kids.cc'
--- src/ipc/Kids.cc	1970-01-01 00:00:00 +0000
+++ src/ipc/Kids.cc	2010-04-26 07:09:03 +0000
@@ -0,0 +1,98 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#include "config.h"
+#include "ipc/Kids.h"
+
+Kids TheKids;
+char KidName[NAME_MAX];
+int  KidIdentifier;
+
+Kids::Kids()
+{
+}
+
+/// maintain n kids
+void Kids::init(size_t n)
+{
+    assert(n > 0);
+
+    if (storage.size() > 0)
+        storage.clean();
+
+    storage.reserve(n);
+
+    char kid_name[32];
+
+    // add Kid records for all n main strands
+    for (size_t i = 1; i <= n; ++i) {
+        snprintf(kid_name, sizeof(kid_name), "(squid-%d)", (int)i);
+        storage.push_back(Kid(kid_name));
+    }
+
+    // if coordination is needed, add a Kid record for Coordinator
+    if (n > 1) {
+        snprintf(kid_name, sizeof(kid_name), "(squid-coord-%d)", (int)(n + 1));
+        storage.push_back(Kid(kid_name));
+    }
+}
+
+/// returns kid by pid
+Kid* Kids::find(pid_t pid)
+{
+    assert(pid > 0);
+    assert(count() > 0);
+
+    for (size_t i = 0; i < storage.size(); ++i) {
+        if (storage[i].getPid() == pid)
+            return &storage[i];
+    }
+    return NULL;
+}
+
+/// returns the kid by index, useful for kids iteration
+Kid& Kids::get(size_t i)
+{
+    assert(i >= 0 && i < count());
+    return storage[i];
+}
+
+/// whether all kids are hopeless
+bool Kids::allHopeless() const
+{
+    for (size_t i = 0; i < storage.size(); ++i) {
+        if (!storage[i].hopeless())
+            return false;
+    }
+    return true;
+}
+
+/// whether all kids called exited happy
+bool Kids::allExitedHappy() const
+{
+    for (size_t i = 0; i < storage.size(); ++i) {
+        if (!storage[i].exitedHappy())
+            return false;
+    }
+    return true;
+}
+
+/// whether all kids died from a given signal
+bool Kids::allSignaled(int sgnl) const
+{
+    for (size_t i = 0; i < storage.size(); ++i) {
+        if (!storage[i].signaled(sgnl))
+            return false;
+    }
+    return true;
+}
+
+/// returns the number of kids
+size_t Kids::count() const
+{
+    return storage.size();
+}

=== added file 'src/ipc/Kids.h'
--- src/ipc/Kids.h	1970-01-01 00:00:00 +0000
+++ src/ipc/Kids.h	2010-03-30 21:54:40 +0000
@@ -0,0 +1,55 @@
+/*
+ * $Id$
+ *
+ */
+
+#ifndef SQUID_IPC_KIDS_H
+#define SQUID_IPC_KIDS_H
+
+#include "Array.h"
+#include "ipc/Kid.h"
+
+
+/// a collection of kids
+class Kids
+{
+public:
+    Kids ();
+
+private:
+    Kids (const Kids&); ///< not implemented
+    Kids& operator= (const Kids&); ///< not implemented
+
+public:
+    /// maintain n kids
+    void init(size_t n);
+
+    /// returns kid by pid
+    Kid* find(pid_t pid);
+
+    /// returns the kid by index, useful for kids iteration
+    Kid& get(size_t i);
+
+    /// whether all kids are hopeless
+    bool allHopeless() const;
+
+    /// whether all kids called exited happy
+    bool allExitedHappy() const;
+
+    /// whether all kids died from a given signal
+    bool allSignaled(int sgnl) const;
+
+    /// returns the number of kids
+    size_t count() const;
+
+private:
+    Vector<Kid> storage;
+};
+
+extern Kids TheKids; ///< All kids being maintained
+
+extern char KidName[NAME_MAX]; ///< current Squid process name (e.g., squid2)
+extern int KidIdentifier; ///< current Squid process number (e.g., 4)
+
+
+#endif /* SQUID_IPC_KIDS_H */

=== added file 'src/ipc/Makefile.am'
--- src/ipc/Makefile.am	1970-01-01 00:00:00 +0000
+++ src/ipc/Makefile.am	2010-05-02 18:49:25 +0000
@@ -0,0 +1,30 @@
+include $(top_srcdir)/src/Common.am
+include $(top_srcdir)/src/TestHeaders.am
+
+noinst_LTLIBRARIES = libipc.la
+
+libipc_la_SOURCES = \
+	FdNotes.cc \
+	FdNotes.h \
+	Kid.cc \
+	Kid.h \
+	Kids.cc \
+	Kids.h \
+	Messages.cc \
+	Messages.h \
+	StartListening.cc \
+	StartListening.h \
+	SharedListen.cc \
+	SharedListen.h \
+	TypedMsgHdr.cc \
+	TypedMsgHdr.h \
+	Coordinator.cc \
+	Coordinator.h \
+	UdsOp.cc \
+	UdsOp.h \
+	Port.cc \
+	Port.h \
+	Strand.cc \
+	Strand.h
+
+DEFS += -DDEFAULT_PREFIX=\"$(prefix)\"

=== added file 'src/ipc/Messages.cc'
--- src/ipc/Messages.cc	1970-01-01 00:00:00 +0000
+++ src/ipc/Messages.cc	2010-04-30 18:01:29 +0000
@@ -0,0 +1,61 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+
+#include "config.h"
+#include "comm.h"
+#include "ipc/Messages.h"
+#include "ipc/TypedMsgHdr.h"
+
+
+Ipc::StrandCoord::StrandCoord(): kidId(-1), pid(0)
+{
+}
+
+Ipc::StrandCoord::StrandCoord(int aKidId, pid_t aPid): kidId(aKidId), pid(aPid)
+{
+}
+
+Ipc::StrandCoord::StrandCoord(const TypedMsgHdr &hdrMsg): kidId(-1), pid(0)
+{
+    hdrMsg.getData(mtRegistration, this, sizeof(*this));
+}
+
+void Ipc::StrandCoord::pack(TypedMsgHdr &hdrMsg) const
+{
+    hdrMsg.putData(mtRegistration, this, sizeof(*this));
+}
+
+
+Ipc::Descriptor::Descriptor(): fromKid(-1), fd(-1)
+{
+}
+
+Ipc::Descriptor::Descriptor(int aFromKid, int aFd): fromKid(aFromKid), fd(aFd)
+{
+}
+
+Ipc::Descriptor::Descriptor(const TypedMsgHdr &hdrMsg): fromKid(-1), fd(-1)
+{
+    if (hdrMsg.type() == mtDescriptorGet) {
+        hdrMsg.getData(mtDescriptorGet, this, sizeof(*this));
+        fd = -1;
+    } else {
+        hdrMsg.getData(mtDescriptorPut, this, sizeof(*this));
+        fd = hdrMsg.getFd();
+    }
+}
+
+void Ipc::Descriptor::pack(TypedMsgHdr &hdrMsg) const
+{
+    if (fd >= 0) {
+        hdrMsg.putData(mtDescriptorPut, this, sizeof(*this));
+        hdrMsg.putFd(fd);
+    } else {
+        hdrMsg.putData(mtDescriptorGet, this, sizeof(*this));
+    }
+}

=== added file 'src/ipc/Messages.h'
--- src/ipc/Messages.h	1970-01-01 00:00:00 +0000
+++ src/ipc/Messages.h	2010-05-02 18:49:25 +0000
@@ -0,0 +1,57 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#ifndef SQUID_IPC_MESSAGES_H
+#define SQUID_IPC_MESSAGES_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+/// Declare IPC messages. These classes translate between high-level
+/// information and low-level TypedMsgHdr (i.e., struct msghdr) buffers.
+
+namespace Ipc
+{
+
+class TypedMsgHdr;
+
+typedef enum { mtNone = 0, mtRegistration,
+    mtSharedListenRequest, mtSharedListenResponse,
+    mtDescriptorGet, mtDescriptorPut } MessageType;
+
+/// Strand location details
+class StrandCoord {
+public:
+    StrandCoord(); ///< unknown location
+    StrandCoord(int akidId, pid_t aPid); ///< from registrant
+    explicit StrandCoord(const TypedMsgHdr &hdrMsg); ///< from recvmsg()
+    void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg()
+
+public:
+    int kidId; ///< internal Squid process number
+    pid_t pid; ///< OS process or thread identifier
+};
+
+/// a [socket] descriptor information
+class Descriptor
+{
+public:
+    Descriptor(); ///< unknown descriptor
+    Descriptor(int fromKid, int fd); ///< from descriptor sender or requestor
+    explicit Descriptor(const TypedMsgHdr &hdrMsg); ///< from recvmsg()
+    void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg()
+
+public:
+    int fromKid; /// the source of this message
+    int fd; ///< raw descriptor value
+};
+
+
+} // namespace Ipc;
+
+
+#endif /* SQUID_IPC_MESSAGES_H */

=== added file 'src/ipc/Port.cc'
--- src/ipc/Port.cc	1970-01-01 00:00:00 +0000
+++ src/ipc/Port.cc	2010-04-29 22:35:11 +0000
@@ -0,0 +1,65 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+
+#include "config.h"
+#include "CommCalls.h"
+#include "ipc/Port.h"
+
+const char Ipc::coordinatorAddr[] = DEFAULT_PREFIX "/var/run/coordinator.ipc";
+const char Ipc::strandAddrPfx[] = DEFAULT_PREFIX "/var/run/squid";
+
+
+Ipc::Port::Port(const String& aListenAddr):
+    UdsOp(aListenAddr)
+{
+    setOptions(COMM_NONBLOCKING | COMM_DOBIND);
+}
+
+void Ipc::Port::start()
+{
+    UdsOp::start();
+    listen();
+}
+
+void Ipc::Port::listen()
+{
+    debugs(54, 6, HERE);
+    buf.prepForReading();
+    AsyncCall::Pointer readHandler = asyncCall(54, 6, "Ipc::Port::noteRead",
+        CommCbMemFunT<Port, CommIoCbParams>(this, &Port::noteRead));
+    comm_read(fd(), buf.raw(), buf.size(), readHandler);
+}
+
+bool Ipc::Port::doneAll() const
+{
+    return false; // listen forever
+}
+
+String Ipc::Port::MakeAddr(const char* pathAddr, int id)
+{
+    assert(id >= 0);
+    String addr = pathAddr;
+    addr.append('-');
+    addr.append(xitoa(id));
+    addr.append(".ipc");
+    return addr;
+}
+
+void Ipc::Port::noteRead(const CommIoCbParams& params)
+{
+    debugs(54, 6, HERE << "FD " << params.fd << " flag " << params.flag <<
+        " [" << this << ']');
+    if (params.flag == COMM_OK) {
+        assert(params.buf == buf.raw());
+        receive(buf);
+    }
+    // TODO: if there was a fatal error on our socket, close the socket before
+    // trying to listen again and print a level-1 error message.
+
+    listen();
+}

=== added file 'src/ipc/Port.h'
--- src/ipc/Port.h	1970-01-01 00:00:00 +0000
+++ src/ipc/Port.h	2010-04-29 20:12:03 +0000
@@ -0,0 +1,53 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#ifndef SQUID_IPC_PORT_H
+#define SQUID_IPC_PORT_H
+
+
+#include "SquidString.h"
+#include "ipc/UdsOp.h"
+
+
+namespace Ipc
+{
+
+
+/// Waits for and receives incoming IPC messages; kids handle the messages
+class Port: public UdsOp
+{
+public:
+    Port(const String &aListenAddr);
+
+protected:
+    /// calculates IPC message address for strand #id at path
+    static String MakeAddr(const char *path, int id);
+
+    virtual void start() = 0; // UdsOp (AsyncJob) API; has body
+    virtual bool doneAll() const; // UdsOp (AsyncJob) API
+
+    /// read the next incoming message
+    void listen();
+
+    /// handle IPC message just read
+    virtual void receive(const TypedMsgHdr& message) = 0;
+
+private:
+    void noteRead(const CommIoCbParams &params); // Comm callback API
+
+private:
+    TypedMsgHdr buf; ///< msghdr struct filled by Comm
+};
+
+
+extern const char coordinatorAddr[]; ///< where coordinator listens
+extern const char strandAddrPfx[]; ///< strand's listening address prefix
+
+} // namespace Ipc
+
+
+#endif /* SQUID_IPC_PORT_H */

=== added file 'src/ipc/SharedListen.cc'
--- src/ipc/SharedListen.cc	1970-01-01 00:00:00 +0000
+++ src/ipc/SharedListen.cc	2010-05-02 18:49:25 +0000
@@ -0,0 +1,149 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#include "config.h"
+#include <map>
+#include "comm.h"
+#include "ipc/Port.h"
+#include "ipc/Messages.h"
+#include "ipc/Kids.h"
+#include "ipc/TypedMsgHdr.h"
+#include "ipc/StartListening.h"
+#include "ipc/SharedListen.h"
+
+
+/// holds information necessary to handle JoinListen response
+class PendingOpenRequest
+{
+public:
+    Ipc::OpenListenerParams params; ///< actual comm_open_sharedListen() parameters
+    AsyncCall::Pointer callback; // who to notify
+};
+
+/// maps ID assigned at request time to the response callback
+typedef std::map<int, PendingOpenRequest> SharedListenRequestMap;
+static SharedListenRequestMap TheSharedListenRequestMap;
+
+static int
+AddToMap(const PendingOpenRequest &por)
+{
+    // find unused ID using linear seach; there should not be many entries
+    for (int id = 0; true; ++id) {
+        if (TheSharedListenRequestMap.find(id) == TheSharedListenRequestMap.end()) {
+            TheSharedListenRequestMap[id] = por;
+            return id;
+        }
+    }
+    assert(false); // not reached
+    return -1;
+}
+
+Ipc::OpenListenerParams::OpenListenerParams()
+{
+    xmemset(this, 0, sizeof(*this));
+}
+
+bool
+Ipc::OpenListenerParams::operator <(const OpenListenerParams &p) const
+{
+    if (sock_type != p.sock_type)
+        return sock_type < p.sock_type;
+
+    if (proto != p.proto)
+        return proto < p.proto;
+
+    // ignore flags and fdNote differences because they do not affect binding
+
+    return addr.compareWhole(p.addr) < 0;
+}
+
+
+
+Ipc::SharedListenRequest::SharedListenRequest(): requestorId(-1), mapId(-1)
+{
+    // caller will then set public data members
+}
+
+Ipc::SharedListenRequest::SharedListenRequest(const TypedMsgHdr &hdrMsg)
+{
+    hdrMsg.getData(mtSharedListenRequest, this, sizeof(*this));
+}
+
+void Ipc::SharedListenRequest::pack(TypedMsgHdr &hdrMsg) const
+{
+    hdrMsg.putData(mtSharedListenRequest, this, sizeof(*this));
+}
+
+
+Ipc::SharedListenResponse::SharedListenResponse(int aFd, int anErrNo, int aMapId):
+    fd(aFd), errNo(anErrNo), mapId(aMapId)
+{
+}
+
+Ipc::SharedListenResponse::SharedListenResponse(const TypedMsgHdr &hdrMsg):
+    fd(-1), errNo(0), mapId(-1)
+{
+    hdrMsg.getData(mtSharedListenResponse, this, sizeof(*this));
+    fd = hdrMsg.getFd();
+}
+
+void Ipc::SharedListenResponse::pack(TypedMsgHdr &hdrMsg) const
+{
+    hdrMsg.putData(mtSharedListenResponse, this, sizeof(*this));
+    hdrMsg.putFd(fd);
+}
+
+
+void Ipc::JoinSharedListen(const OpenListenerParams &params,
+    AsyncCall::Pointer &callback)
+{
+    PendingOpenRequest por;
+    por.params = params;
+    por.callback = callback;
+
+    SharedListenRequest request;
+    request.requestorId = KidIdentifier;
+    request.params = por.params;
+    request.mapId = AddToMap(por);
+
+    debugs(54, 3, HERE << "getting listening FD for " << request.params.addr <<
+        " mapId=" << request.mapId);
+
+    TypedMsgHdr message;
+    request.pack(message);
+    SendMessage(coordinatorAddr, message);
+}
+
+void Ipc::SharedListenJoined(const SharedListenResponse &response)
+{
+    const int fd = response.fd;
+
+    debugs(54, 3, HERE << "got listening FD " << fd << " errNo=" <<
+        response.errNo << " mapId=" << response.mapId);
+
+    Must(TheSharedListenRequestMap.find(response.mapId) != TheSharedListenRequestMap.end());
+    PendingOpenRequest por = TheSharedListenRequestMap[response.mapId];
+    Must(por.callback != NULL);
+    TheSharedListenRequestMap.erase(response.mapId);
+
+    if (fd >= 0) {
+        OpenListenerParams &p = por.params;
+        struct addrinfo *AI = NULL;
+        p.addr.GetAddrInfo(AI);
+        AI->ai_socktype = p.sock_type;
+        AI->ai_protocol = p.proto;
+        comm_import_opened(fd, p.addr, p.flags, FdNote(p.fdNote), AI);
+        p.addr.FreeAddrInfo(AI);
+    }
+
+    StartListeningCb *cbd =
+        dynamic_cast<StartListeningCb*>(por.callback->getDialer());
+    Must(cbd);
+    cbd->fd = fd;
+    cbd->errNo = response.errNo;
+    ScheduleCallHere(por.callback);
+}

=== added file 'src/ipc/SharedListen.h'
--- src/ipc/SharedListen.h	1970-01-01 00:00:00 +0000
+++ src/ipc/SharedListen.h	2010-05-02 18:49:25 +0000
@@ -0,0 +1,74 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#ifndef SQUID_IPC_SHARED_LISTEN_H
+#define SQUID_IPC_SHARED_LISTEN_H
+
+#include "base/AsyncCall.h"
+
+namespace Ipc
+{
+
+/// "shared listen" is when concurrent processes are listening on the same fd
+
+/// comm_open_listener() parameters holder
+class OpenListenerParams
+{
+public:
+    OpenListenerParams();
+
+    bool operator <(const OpenListenerParams &p) const; ///< useful for map<>
+
+    int sock_type;
+    int proto;
+    IpAddress addr; ///< will be memset and memcopied
+    int flags;
+    int fdNote; ///< index into fd_note() comment strings
+};
+
+class TypedMsgHdr;
+
+/// a request for a listen socket with given parameters
+class SharedListenRequest
+{
+public:
+    SharedListenRequest(); ///< from OpenSharedListen() which then sets public data
+    explicit SharedListenRequest(const TypedMsgHdr &hdrMsg); ///< from recvmsg()
+    void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg()
+
+public:
+    int requestorId; ///< kidId of the requestor
+
+    OpenListenerParams params; ///< actual comm_open_sharedListen() parameters
+
+    int mapId; ///< to map future response to the requestor's callback
+};
+
+/// a response to SharedListenRequest
+class SharedListenResponse
+{
+public:
+    SharedListenResponse(int fd, int errNo, int mapId);
+    explicit SharedListenResponse(const TypedMsgHdr &hdrMsg); ///< from recvmsg()
+    void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg()
+
+public:
+    int fd; ///< opened listening socket or -1
+    int errNo; ///< errno value from comm_open_sharedListen() call
+    int mapId; ///< to map future response to the requestor's callback
+};
+
+/// prepare and send SharedListenRequest to Coordinator
+extern void JoinSharedListen(const OpenListenerParams &, AsyncCall::Pointer &);
+
+/// process Coordinator response to SharedListenRequest
+extern void SharedListenJoined(const SharedListenResponse &response);
+
+} // namespace Ipc;
+
+
+#endif /* SQUID_IPC_SHARED_LISTEN_H */

=== added file 'src/ipc/StartListening.cc'
--- src/ipc/StartListening.cc	1970-01-01 00:00:00 +0000
+++ src/ipc/StartListening.cc	2010-06-14 20:45:53 +0000
@@ -0,0 +1,58 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#include "config.h"
+#include "comm.h"
+#include "TextException.h"
+#include "ipc/SharedListen.h"
+#include "ipc/StartListening.h"
+
+
+Ipc::StartListeningCb::StartListeningCb(): fd(-1), errNo(0)
+{
+}
+
+Ipc::StartListeningCb::~StartListeningCb()
+{
+}
+
+std::ostream &Ipc::StartListeningCb::startPrint(std::ostream &os) const
+{
+    return os << "(FD " << fd << ", err=" << errNo;
+}
+
+
+void Ipc::StartListening(int sock_type, int proto, IpAddress &addr,
+    int flags, FdNoteId fdNote, AsyncCall::Pointer &callback)
+{
+    OpenListenerParams p;
+    p.sock_type = sock_type;
+    p.proto = proto;
+    p.addr = addr;
+    p.flags = flags;
+    p.fdNote = fdNote;
+
+    if (UsingSmp()) { // if SMP is on, share
+        Ipc::JoinSharedListen(p, callback);
+        return; // wait for the call back
+    }
+
+    enter_suid();
+    const int sock = comm_open_listener(p.sock_type, p.proto, p.addr, p.flags,
+        FdNote(p.fdNote));
+    const int errNo = (sock >= 0) ? 0 : errno;
+    leave_suid();
+
+    debugs(54, 3, HERE << "opened listen FD " << sock << " for " << p.addr);
+
+    StartListeningCb *cbd =
+        dynamic_cast<StartListeningCb*>(callback->getDialer());
+    Must(cbd);
+    cbd->fd = sock;
+    cbd->errNo = errNo;
+    ScheduleCallHere(callback);
+}

=== added file 'src/ipc/StartListening.h'
--- src/ipc/StartListening.h	1970-01-01 00:00:00 +0000
+++ src/ipc/StartListening.h	2010-05-02 18:49:25 +0000
@@ -0,0 +1,43 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#ifndef SQUID_IPC_START_LISTENING_H
+#define SQUID_IPC_START_LISTENING_H
+
+#include <iosfwd>
+#include "ipc/FdNotes.h"
+#include "base/AsyncCall.h"
+
+class IpAddress;
+
+namespace Ipc
+{
+
+/// common API for all StartListening() callbacks
+class StartListeningCb
+{
+public:
+    StartListeningCb();
+    virtual ~StartListeningCb();
+
+    /// starts printing arguments, return os
+    std::ostream &startPrint(std::ostream &os) const;
+
+public:
+    int fd; ///< opened listening socket or -1
+    int errNo; ///< errno value from the comm_open_listener() call
+};
+
+/// Depending on whether SMP is on, either ask Coordinator to send us
+/// the listening FD or call comm_open_listener() directly.
+extern void StartListening(int sock_type, int proto, IpAddress &addr,
+    int flags, FdNoteId fdNote, AsyncCall::Pointer &callback);
+
+} // namespace Ipc;
+
+
+#endif /* SQUID_IPC_START_LISTENING_H */

=== added file 'src/ipc/Strand.cc'
--- src/ipc/Strand.cc	1970-01-01 00:00:00 +0000
+++ src/ipc/Strand.cc	2010-05-02 18:49:25 +0000
@@ -0,0 +1,97 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#include "config.h"
+#include "ipc/Strand.h"
+#include "ipc/Messages.h"
+#include "ipc/SharedListen.h"
+#include "ipc/Kids.h"
+
+
+CBDATA_NAMESPACED_CLASS_INIT(Ipc, Strand);
+
+
+Ipc::Strand::Strand():
+    Port(MakeAddr(strandAddrPfx, KidIdentifier)),
+    isRegistered(false)
+{
+}
+
+void Ipc::Strand::start()
+{
+    Port::start();
+    registerSelf();
+}
+
+void Ipc::Strand::registerSelf()
+{
+    debugs(54, 6, HERE);
+    Must(!isRegistered);
+    TypedMsgHdr message;
+    StrandCoord(KidIdentifier, getpid()).pack(message);
+    SendMessage(coordinatorAddr, message);
+    setTimeout(6, "Ipc::Strand::timeoutHandler"); // TODO: make 6 configurable?
+}
+
+void Ipc::Strand::receive(const TypedMsgHdr &message)
+{
+    debugs(54, 6, HERE << message.type());
+    switch (message.type()) {
+
+    case mtRegistration:
+        handleRegistrationResponse(StrandCoord(message));
+        break;
+
+    case mtSharedListenResponse:
+        SharedListenJoined(SharedListenResponse(message));
+        break;
+
+    case mtDescriptorPut:
+        putDescriptor(Descriptor(message));
+        break;
+
+    default:
+        debugs(54, 1, HERE << "Unhandled message type: " << message.type());
+        break;
+    }
+}
+
+void Ipc::Strand::handleRegistrationResponse(const StrandCoord &strand)
+{
+    // handle registration response from the coordinator; it could be stale
+    if (strand.kidId == KidIdentifier && strand.pid == getpid()) {
+        debugs(54, 6, "kid" << KidIdentifier << " registered");
+        clearTimeout(); // we are done
+
+        debugs(54, 6, HERE << "requesting FD");
+        Descriptor request(KidIdentifier, -1);
+        TypedMsgHdr message;
+        request.pack(message);
+        SendMessage(coordinatorAddr, message);
+    } else {
+        // could be an ACK to the registration message of our dead predecessor
+        debugs(54, 6, "kid" << KidIdentifier << " is not yet registered");
+        // keep listening, with a timeout
+    }
+}
+
+/// receive descriptor we asked for
+void Ipc::Strand::putDescriptor(const Descriptor &message)
+{
+    debugs(54, 6, HERE << "got FD " << message.fd);
+    char buffer[64];
+    const int n = snprintf(buffer, sizeof(buffer), "strand: kid%d wrote using FD %d\n", KidIdentifier, message.fd);
+    ssize_t bytes = write(message.fd, buffer, n);
+    Must(bytes == n);
+}
+
+void Ipc::Strand::timedout()
+{
+    debugs(54, 6, HERE << isRegistered);
+    if (!isRegistered)
+        fatalf("kid%d registration timed out", KidIdentifier);
+}

=== added file 'src/ipc/Strand.h'
--- src/ipc/Strand.h	1970-01-01 00:00:00 +0000
+++ src/ipc/Strand.h	2010-04-29 22:35:11 +0000
@@ -0,0 +1,51 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#ifndef SQUID_IPC_STRAND_H
+#define SQUID_IPC_STRAND_H
+
+#include "ipc/Port.h"
+
+
+namespace Ipc
+{
+
+class StrandCoord;
+class Descriptor;
+
+/// Receives coordination messages on behalf of its process or thread
+class Strand: public Port
+{
+public:
+    Strand();
+
+    virtual void start(); // Port (AsyncJob) API
+
+protected:
+    virtual void timedout(); // Port (UsdOp) API
+    virtual void receive(const TypedMsgHdr &message); // Port API
+
+private:
+    void registerSelf(); /// let Coordinator know this strand exists
+    void handleRegistrationResponse(const StrandCoord &strand);
+    void putDescriptor(const Descriptor &message);
+
+private:
+    bool isRegistered; ///< whether Coordinator ACKed registration (unused)
+
+    CBDATA_CLASS2(Strand);
+
+private:
+    Strand(const Strand&); // not implemented
+    Strand& operator =(const Strand&); // not implemented
+};
+
+
+}
+
+
+#endif /* SQUID_IPC_STRAND_H */

=== added file 'src/ipc/TypedMsgHdr.cc'
--- src/ipc/TypedMsgHdr.cc	1970-01-01 00:00:00 +0000
+++ src/ipc/TypedMsgHdr.cc	2010-05-01 23:47:46 +0000
@@ -0,0 +1,167 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+
+#include "config.h"
+#include <string.h>
+#include "TextException.h"
+#include "ipc/TypedMsgHdr.h"
+
+Ipc::TypedMsgHdr::TypedMsgHdr()
+{
+	xmemset(this, 0, sizeof(*this));
+	sync();
+}
+
+Ipc::TypedMsgHdr::TypedMsgHdr(const TypedMsgHdr &tmh)
+{
+	xmemcpy(this, &tmh, sizeof(*this));
+	sync();
+}
+
+Ipc::TypedMsgHdr &Ipc::TypedMsgHdr::operator =(const TypedMsgHdr &tmh)
+{
+	if (this != &tmh) { // skip assignment to self
+		xmemcpy(this, &tmh, sizeof(*this));
+		sync();
+	}
+	return *this;
+}
+
+// update msghdr and ios pointers based on msghdr counters
+void Ipc::TypedMsgHdr::sync()
+{
+	if (msg_name) { // we have a name
+		msg_name = &name;
+	} else {
+		Must(!msg_namelen && !msg_name);
+	}
+
+	if (msg_iov) { // we have a data component
+		Must(msg_iovlen == 1);
+		msg_iov = ios;
+		ios[0].iov_base = &data;
+		Must(ios[0].iov_len == sizeof(data));
+	} else {
+		Must(!msg_iovlen && !msg_iov);
+	}
+
+	if (msg_control) { // we have a control component
+		Must(msg_controllen > 0);
+		msg_control = &ctrl;
+	} else {
+		Must(!msg_controllen && !msg_control);
+	}
+}
+
+
+
+int
+Ipc::TypedMsgHdr::type() const
+{
+	Must(msg_iovlen == 1);
+	return data.type_;
+}
+
+void
+Ipc::TypedMsgHdr::address(const struct sockaddr_un& addr)
+{
+	allocName();
+	name = addr;
+    msg_name = &name;
+    msg_namelen = SUN_LEN(&name);
+}
+
+void
+Ipc::TypedMsgHdr::getData(int destType, void *raw, size_t size) const
+{
+	Must(type() == destType);
+	Must(size == data.size);
+	xmemcpy(raw, data.raw, size);
+}
+
+void
+Ipc::TypedMsgHdr::putData(int aType, const void *raw, size_t size)
+{
+	Must(size <= sizeof(data.raw));
+	allocData();
+	data.type_ = aType;
+	data.size = size;
+	xmemcpy(data.raw, raw, size);
+}
+
+void
+Ipc::TypedMsgHdr::putFd(int fd)
+{
+	Must(fd >= 0);
+	allocControl();
+
+	const int fdCount = 1;
+
+	struct cmsghdr *cmsg = CMSG_FIRSTHDR(this);
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fdCount);
+
+    int *fdStore = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+	xmemcpy(fdStore, &fd, fdCount * sizeof(int));
+    msg_controllen = cmsg->cmsg_len;
+}
+
+int
+Ipc::TypedMsgHdr::getFd() const
+{
+	Must(msg_control && msg_controllen);
+
+	struct cmsghdr *cmsg = CMSG_FIRSTHDR(this);
+	Must(cmsg->cmsg_level == SOL_SOCKET);
+	Must(cmsg->cmsg_type == SCM_RIGHTS);
+
+	const int fdCount = 1;
+    const int *fdStore = reinterpret_cast<const int*>(CMSG_DATA(cmsg));
+	int fd = -1;
+	xmemcpy(&fd, fdStore, fdCount * sizeof(int));
+	return fd;
+}
+
+void
+Ipc::TypedMsgHdr::prepForReading()
+{
+	xmemset(this, 0, sizeof(*this));
+	allocName();
+	allocData();
+	allocControl();
+}
+
+/// initialize io vector with one io record
+void
+Ipc::TypedMsgHdr::allocData()
+{
+	Must(!msg_iovlen && !msg_iov);
+	msg_iovlen = 1;
+	msg_iov = ios;
+	ios[0].iov_base = &data;
+	ios[0].iov_len = sizeof(data);
+	data.type_ = 0;
+	data.size = 0;
+}
+
+void
+Ipc::TypedMsgHdr::allocName()
+{
+	Must(!msg_name && !msg_namelen);
+    msg_name = &name;
+    msg_namelen = sizeof(name); // is that the right size?
+}
+
+void
+Ipc::TypedMsgHdr::allocControl()
+{
+	Must(!msg_control && !msg_controllen);
+	msg_control = &ctrl;
+	msg_controllen = sizeof(ctrl);
+}

=== added file 'src/ipc/TypedMsgHdr.h'
--- src/ipc/TypedMsgHdr.h	1970-01-01 00:00:00 +0000
+++ src/ipc/TypedMsgHdr.h	2010-05-02 17:58:46 +0000
@@ -0,0 +1,67 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#ifndef SQUID_IPC_TYPED_MSG_HDR_H
+#define SQUID_IPC_TYPED_MSG_HDR_H
+
+#include "config.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+#if HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+
+namespace Ipc
+{
+
+/// struct msghdr with a known type, fixed-size I/O and control buffers
+class TypedMsgHdr: public msghdr
+{
+public:
+    TypedMsgHdr();
+    TypedMsgHdr(const TypedMsgHdr &tmh);
+    TypedMsgHdr &operator =(const TypedMsgHdr &tmh);
+
+    // type-safe access to message details
+    int type() const; ///< returns stored type or zero if none
+    void address(const struct sockaddr_un& addr); ///< sets [dest.] address
+    void getData(int ofType, void *raw, size_t size) const; ///< checks type
+    void putData(int aType, const void *raw, size_t size); ///< stores type
+	void putFd(int aFd); ///< stores descriptor
+	int getFd() const; ///< returns descriptor
+
+    /// raw, type-independent access for I/O
+	void prepForReading(); ///< reset and provide all buffers
+	char *raw() { return reinterpret_cast<char*>(this); }
+	const char *raw() const { return reinterpret_cast<const char*>(this); }
+    size_t size() const { return sizeof(*this); } ///< not true message size
+
+private:
+	void sync();
+	void allocData();
+	void allocName();
+	void allocControl();
+
+private:
+	struct sockaddr_un name; ///< same as .msg_name
+
+	struct iovec ios[1]; ///< same as .msg_iov[]
+
+	struct DataBuffer {
+		int type_; ///< Message kind, uses MessageType values
+		size_t size; ///< actual raw data size (for sanity checks)
+		char raw[250]; ///< buffer with type-specific data
+	} data; ///< same as .msg_iov[0].iov_base
+
+	struct CtrlBuffer {
+		char raw[CMSG_SPACE(sizeof(int))]; ///< control buffer space for one fd
+	} ctrl; ///< same as .msg_control
+};
+
+} // namespace Ipc
+
+#endif /* SQUID_IPC_TYPED_MSG_HDR_H */

=== added file 'src/ipc/UdsOp.cc'
--- src/ipc/UdsOp.cc	1970-01-01 00:00:00 +0000
+++ src/ipc/UdsOp.cc	2010-04-29 20:12:03 +0000
@@ -0,0 +1,132 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+
+#include "config.h"
+#include "comm.h"
+#include "CommCalls.h"
+#include "ipc/UdsOp.h"
+
+
+Ipc::UdsOp::UdsOp(const String& pathAddr):
+    AsyncJob("Ipc::UdsOp"),
+    address(PathToAddress(pathAddr)),
+    options(COMM_NONBLOCKING),
+    fd_(-1)
+{
+    debugs(54, 5, HERE << '[' << this << "] pathAddr=" << pathAddr);
+}
+
+Ipc::UdsOp::~UdsOp()
+{
+    debugs(54, 5, HERE << '[' << this << ']');
+    if (fd_ >= 0)
+        comm_close(fd_);
+}
+
+void Ipc::UdsOp::setOptions(int newOptions)
+{
+    options = newOptions;
+}
+
+int Ipc::UdsOp::fd()
+{
+    if (fd_ < 0) {
+        if (options & COMM_DOBIND)
+            unlink(address.sun_path);
+        fd_ = comm_open_uds(SOCK_DGRAM, 0, &address, options);
+        Must(fd_ >= 0);
+    }
+    return fd_;
+}
+
+void Ipc::UdsOp::setTimeout(int seconds, const char *handlerName)
+{
+    AsyncCall::Pointer handler = asyncCall(54,5, handlerName,
+        CommCbMemFunT<UdsOp, CommTimeoutCbParams>(this,
+            &UdsOp::noteTimeout));
+    commSetTimeout(fd(), seconds, handler);
+}
+
+void Ipc::UdsOp::clearTimeout()
+{
+    commSetTimeout(fd(), -1, NULL, NULL); // TODO: add Comm::ClearTimeout(fd)
+}
+
+void Ipc::UdsOp::noteTimeout(const CommTimeoutCbParams &)
+{
+    timedout(); // our kid handles communication timeout
+}
+
+
+struct sockaddr_un
+Ipc::PathToAddress(const String& pathAddr)
+{
+    assert(pathAddr.size() != 0);
+    struct sockaddr_un unixAddr;
+    memset(&unixAddr, 0, sizeof(unixAddr));
+    unixAddr.sun_family = AF_LOCAL;
+    xstrncpy(unixAddr.sun_path, pathAddr.termedBuf(), sizeof(unixAddr.sun_path));
+    return unixAddr;
+}
+
+
+CBDATA_NAMESPACED_CLASS_INIT(Ipc, UdsSender);
+
+Ipc::UdsSender::UdsSender(const String& pathAddr, const TypedMsgHdr& aMessage):
+    UdsOp(pathAddr),
+    message(aMessage),
+    retries(10), // TODO: make configurable?
+    timeout(10), // TODO: make configurable?
+    writing(false)
+{
+    message.address(address);
+}
+
+void Ipc::UdsSender::start()
+{
+    UdsOp::start();
+    write();
+    if (timeout > 0)
+        setTimeout(timeout, "Ipc::UdsSender::noteTimeout");
+}
+
+bool Ipc::UdsSender::doneAll() const
+{
+    return !writing && UdsOp::doneAll();
+}
+
+void Ipc::UdsSender::write()
+{
+    debugs(54, 5, HERE);
+    AsyncCall::Pointer writeHandler = asyncCall(54, 5, "Ipc::UdsSender::wrote",
+        CommCbMemFunT<UdsSender, CommIoCbParams>(this, &UdsSender::wrote));
+    comm_write(fd(), message.raw(), message.size(), writeHandler);
+    writing = true;
+}
+
+void Ipc::UdsSender::wrote(const CommIoCbParams& params)
+{
+    debugs(54, 5, HERE << "FD " << params.fd << " flag " << params.flag << " [" << this << ']');
+    writing = false;
+    if (params.flag != COMM_OK && retries-- > 0) {
+        sleep(1); // do not spend all tries at once; XXX: use an async timed event instead of blocking here; store the time when we started writing so that we do not sleep if not needed?
+        write(); // XXX: should we close on error so that fd() reopens?
+    }
+}
+
+void Ipc::UdsSender::timedout()
+{
+    debugs(54, 5, HERE);
+    mustStop("timedout");
+}
+
+
+void Ipc::SendMessage(const String& toAddress, const TypedMsgHdr &message)
+{
+    AsyncJob::AsyncStart(new UdsSender(toAddress, message));
+}

=== added file 'src/ipc/UdsOp.h'
--- src/ipc/UdsOp.h	1970-01-01 00:00:00 +0000
+++ src/ipc/UdsOp.h	2010-04-29 20:12:03 +0000
@@ -0,0 +1,97 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 54    Interprocess Communication
+ *
+ */
+
+#ifndef SQUID_IPC_ASYNCUDSOP_H
+#define SQUID_IPC_ASYNCUDSOP_H
+
+
+#include "SquidString.h"
+#include "base/AsyncJob.h"
+#include "ipc/TypedMsgHdr.h"
+
+class CommTimeoutCbParams;
+class CommIoCbParams;
+
+namespace Ipc
+{
+
+/// code shared by unix-domain socket senders (e.g., UdsSender or Coordinator)
+/// and receivers (e.g. Port or Coordinator)
+class UdsOp: public AsyncJob
+{
+public:
+    UdsOp(const String &pathAddr);
+    virtual ~UdsOp();
+
+public:
+    struct sockaddr_un address; ///< UDS address from path; treat as read-only
+
+protected:
+    virtual void timedout() {} ///< called after setTimeout() if timed out
+
+    int fd(); ///< creates if needed and returns raw UDS socket descriptor
+
+    /// call timedout() if no UDS messages in a given number of seconds
+    void setTimeout(int seconds, const char *handlerName);
+    void clearTimeout(); ///< remove previously set timeout, if any
+
+	void setOptions(int newOptions); ///< changes socket options
+
+private:
+    /// Comm timeout callback; calls timedout()
+    void noteTimeout(const CommTimeoutCbParams &p);
+
+private:
+    int options; ///< UDS options
+    int fd_; ///< UDS descriptor
+
+private:
+    UdsOp(const UdsOp &); // not implemented
+    UdsOp &operator= (const UdsOp &); // not implemented
+};
+
+/// converts human-readable filename path into UDS address
+extern struct sockaddr_un PathToAddress(const String &pathAddr);
+
+
+
+// XXX: move UdsSender code to UdsSender.{cc,h}
+/// attempts to send an IPC message a few times, with a timeout
+class UdsSender: public UdsOp
+{
+public:
+    UdsSender(const String& pathAddr, const TypedMsgHdr& aMessage);
+
+protected:
+    virtual void start(); // UdsOp (AsyncJob) API
+    virtual bool doneAll() const; // UdsOp (AsyncJob) API
+    virtual void timedout(); // UdsOp API
+
+private:
+    void write(); ///< schedule writing
+    void wrote(const CommIoCbParams& params); ///< done writing or error
+
+private:
+    TypedMsgHdr message; ///< what to send
+    int retries; ///< how many times to try after a write error
+    int timeout; ///< total time to send the message
+    bool writing; ///< whether Comm started and did not finish writing
+
+    CBDATA_CLASS2(UdsSender);
+
+private:
+    UdsSender(const UdsSender&); // not implemented
+    UdsSender& operator= (const UdsSender&); // not implemented
+};
+
+
+void SendMessage(const String& toAddress, const TypedMsgHdr& message);
+
+
+}
+
+#endif /* SQUID_IPC_ASYNCUDSOP_H */

=== modified file 'src/main.cc'
--- src/main.cc	2009-12-21 12:00:15 +0000
+++ src/main.cc	2010-06-05 19:08:44 +0000
@@ -55,6 +55,9 @@
 #include "StoreFileSystem.h"
 #include "DiskIO/DiskIOModule.h"
 #include "comm.h"
+#include "ipc/Kids.h"
+#include "ipc/Coordinator.h"
+#include "ipc/Strand.h"
 #if USE_EPOLL
 #include "comm_epoll.h"
 #endif
@@ -124,6 +127,10 @@
 static volatile int do_shutdown = 0;
 static volatile int shutdown_status = 0;
 
+static int RotateSignal = -1;
+static int ReconfigureSignal = -1;
+static int ShutdownSignal = -1;
+
 static void mainRotate(void);
 static void mainReconfigureStart(void);
 static void mainReconfigureFinish(void*);
@@ -195,6 +202,10 @@
         doShutdown(do_shutdown > 0 ? (int) Config.shutdownLifetime : 0);
         do_shutdown = 0;
     }
+    BroadcastSignalIfAny(DebugSignal);
+    BroadcastSignalIfAny(RotateSignal);
+    BroadcastSignalIfAny(ReconfigureSignal);
+    BroadcastSignalIfAny(ShutdownSignal);
 
     PROF_stop(SignalEngine_checkEvents);
     return EVENT_IDLE;
@@ -553,6 +564,7 @@
 rotate_logs(int sig)
 {
     do_rotate = 1;
+    RotateSignal = sig;
 #ifndef _SQUID_MSWIN_
 #if !HAVE_SIGACTION
 
@@ -566,6 +578,7 @@
 reconfigure(int sig)
 {
     do_reconfigure = 1;
+    ReconfigureSignal = sig;
 #ifndef _SQUID_MSWIN_
 #if !HAVE_SIGACTION
 
@@ -578,6 +591,7 @@
 shut_down(int sig)
 {
     do_shutdown = sig == SIGINT ? -1 : 1;
+    ShutdownSignal = sig;
 #ifdef SIGTTIN
 
     if (SIGTTIN == sig)
@@ -607,6 +621,19 @@
 static void
 serverConnectionsOpen(void)
 {
+    if (IamPrimaryProcess()) {
+#if USE_WCCP
+
+    wccpConnectionOpen();
+#endif
+
+#if USE_WCCPv2
+
+    wccp2ConnectionOpen();
+#endif
+    }
+    // Coordinator does not start proxying services
+    if (!IamCoordinatorProcess()) {
     clientOpenListenSockets();
     icpConnectionsOpen();
 #if USE_HTCP
@@ -617,15 +644,6 @@
 
     snmpConnectionOpen();
 #endif
-#if USE_WCCP
-
-    wccpConnectionOpen();
-#endif
-
-#if USE_WCCPv2
-
-    wccp2ConnectionOpen();
-#endif
 
     clientdbInit();
     icmpEngine.Open();
@@ -637,12 +655,25 @@
     carpInit();
     peerUserHashInit();
     peerSourceHashInit();
+    }
 }
 
 static void
 serverConnectionsClose(void)
 {
     assert(shutting_down || reconfiguring);
+
+    if (IamPrimaryProcess()) {
+#if USE_WCCP
+
+    wccpConnectionClose();
+#endif
+#if USE_WCCPv2
+
+    wccp2ConnectionClose();
+#endif
+    }
+    if (!IamCoordinatorProcess()) {
     clientHttpConnectionsClose();
     icpConnectionShutdown();
 #if USE_HTCP
@@ -655,16 +686,9 @@
 
     snmpConnectionShutdown();
 #endif
-#if USE_WCCP
-
-    wccpConnectionClose();
-#endif
-#if USE_WCCPv2
-
-    wccp2ConnectionClose();
-#endif
 
     asnFreeMemory();
+    }
 }
 
 static void
@@ -721,10 +745,17 @@
         Config2.onoff.enable_purge = 2;
 
     // parse the config returns a count of errors encountered.
+    const int oldMainProcesses = Config.main_processes;
     if ( parseConfigFile(ConfigFile) != 0) {
         // for now any errors are a fatal condition...
         self_destruct();
     }
+    if (oldMainProcesses != Config.main_processes) {
+        debugs(1, DBG_CRITICAL, "WARNING: Changing 'main_processes' (from " <<
+            oldMainProcesses << " to " << Config.main_processes <<
+            ") is not supported and ignored");
+        Config.main_processes = oldMainProcesses;
+    }
 
     setUmask(Config.umask);
     Mem::Report();
@@ -753,6 +784,8 @@
     redirectInit();
     authenticateInit(&Config.authConfiguration);
     externalAclInit();
+
+    if (IamPrimaryProcess()) {
 #if USE_WCCP
 
     wccpInit();
@@ -761,6 +794,7 @@
 
     wccp2Init();
 #endif
+    }
 
     serverConnectionsOpen();
 
@@ -1015,6 +1049,7 @@
         //   moved to PconnModule::PconnModule()
     }
 
+    if (IamPrimaryProcess()) {
 #if USE_WCCP
     wccpInit();
 
@@ -1024,6 +1059,7 @@
     wccp2Init();
 
 #endif
+    }
 
     serverConnectionsOpen();
 
@@ -1149,9 +1185,28 @@
     return -1; // not reached
 }
 
+/// computes name and ID for the current kid process
+static void
+ConfigureCurrentKid(const char *processName)
+{
+	// kids are marked with parenthesis around their process names
+    if (processName && processName[0] == '(') {
+        if (const char *idStart = strrchr(processName, '-')) {
+            KidIdentifier = atoi(idStart + 1);
+            const int nameLen = idStart - (processName + 1);
+            xstrncpy(KidName, processName + 1, nameLen + 1);
+        }
+    } else {
+        xstrncpy(KidName, APP_SHORTNAME, sizeof(KidName));
+        KidIdentifier = 0;
+    }
+}
+
 int
 SquidMain(int argc, char **argv)
 {
+    ConfigureCurrentKid(argv[0]);
+
 #ifdef _SQUID_WIN32_
 
     int WIN32_init_err;
@@ -1320,7 +1375,7 @@
         return 0;
     }
 
-    if (!opt_no_daemon)
+    if (!opt_no_daemon && Config.main_processes > 0)
         watch_child(argv);
 
     setMaxFD();
@@ -1376,6 +1431,11 @@
 
     mainLoop.setTimeService(&time_engine);
 
+    if (IamCoordinatorProcess())
+        AsyncJob::AsyncStart(Ipc::Coordinator::Instance());
+    else if (UsingSmp() && IamWorkerProcess())
+        AsyncJob::AsyncStart(new Ipc::Strand);
+
     /* at this point we are finished the synchronous startup. */
     starting_up = 0;
 
@@ -1477,11 +1537,11 @@
         do {
 #ifdef _SQUID_NEXT_
             union wait status;
-            rpid = wait3(&status, 0, NULL);
+            rpid = wait4(cpid, &status, 0, NULL);
 #else
 
             int status;
-            rpid = waitpid(-1, &status, 0);
+            rpid = waitpid(cpid, &status, 0);
 #endif
 
         } while (rpid != cpid);
@@ -1493,6 +1553,10 @@
 static int
 checkRunningPid(void)
 {
+    // master process must start alone, but its kids processes may co-exist
+    if (!IamMasterProcess())
+        return 0;
+
     pid_t pid;
 
     if (!debug_log)
@@ -1516,9 +1580,6 @@
 {
 #ifndef _SQUID_MSWIN_
     char *prog;
-    int failcount = 0;
-    time_t start;
-    time_t stop;
 #ifdef _SQUID_NEXT_
 
     union wait status;
@@ -1535,7 +1596,7 @@
 
     int nullfd;
 
-    if (*(argv[0]) == '(')
+    if (!IamMasterProcess())
         return;
 
     openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4);
@@ -1577,25 +1638,39 @@
         dup2(nullfd, 2);
     }
 
+    if (Config.main_processes > 128) {
+        syslog(LOG_ALERT, "Suspiciously high main_processes value: %d",
+            Config.main_processes);
+        // but we keep going in hope that user knows best
+	}
+    TheKids.init(Config.main_processes);
+
+    // keep [re]starting kids until it is time to quit
     for (;;) {
         mainStartScript(argv[0]);
 
-        if ((pid = fork()) == 0) {
-            /* child */
-            openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4);
-            prog = xstrdup(argv[0]);
-            argv[0] = xstrdup("(squid)");
-            execvp(prog, argv);
-            syslog(LOG_ALERT, "execvp failed: %s", xstrerror());
+        // start each kid that needs to be [re]started; once
+        for (int i = TheKids.count() - 1; i >= 0; --i) {
+            Kid& kid = TheKids.get(i);
+            if (kid.hopeless() || kid.exitedHappy() || kid.running())
+                continue;
+
+            if ((pid = fork()) == 0) {
+                /* child */
+                openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4);
+                prog = xstrdup(argv[0]);    /* XXX: leak */
+                argv[0] = const_cast<char*>(kid.name().termedBuf());
+                execvp(prog, argv);
+                syslog(LOG_ALERT, "execvp failed: %s", xstrerror());
+            }
+
+            kid.start(pid);
+            syslog(LOG_NOTICE, "Squid Parent: child process %d started", pid);
         }
 
         /* parent */
         openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4);
 
-        syslog(LOG_NOTICE, "Squid Parent: child process %d started", pid);
-
-        time(&start);
-
         squid_signal(SIGINT, SIG_IGN, SA_RESTART);
 
 #ifdef _SQUID_NEXT_
@@ -1607,51 +1682,48 @@
         pid = waitpid(-1, &status, 0);
 
 #endif
-
-        time(&stop);
-
-        if (WIFEXITED(status)) {
-            syslog(LOG_NOTICE,
-                   "Squid Parent: child process %d exited with status %d",
-                   pid, WEXITSTATUS(status));
-        } else if (WIFSIGNALED(status)) {
-            syslog(LOG_NOTICE,
-                   "Squid Parent: child process %d exited due to signal %d with status %d",
-                   pid, WTERMSIG(status), WEXITSTATUS(status));
-        } else {
-            syslog(LOG_NOTICE, "Squid Parent: child process %d exited", pid);
+        // Loop to collect all stopped kids before we go to sleep below.
+        do
+        {
+            Kid* kid = TheKids.find(pid);
+            if (kid) {
+                kid->stop(status);
+                if (kid->calledExit()) {
+                    syslog(LOG_NOTICE,
+                       "Squid Parent: child process %d exited with status %d",
+                        kid->getPid(), kid->exitStatus());
+                } else if (kid->signaled()) {
+                    syslog(LOG_NOTICE,
+                       "Squid Parent: child process %d exited due to signal %d with status %d",
+                        kid->getPid(), kid->termSignal(), kid->exitStatus());
+                } else {
+                    syslog(LOG_NOTICE, "Squid Parent: child process %d exited", kid->getPid());
+                }
+            } else {
+                syslog(LOG_NOTICE, "Squid Parent: unknown child process %d exited", pid);
+            }
+#ifdef _SQUID_NEXT_
+        } while ((pid = wait3(&status, WNOHANG, NULL)) > 0);
+#else
+        } while ((pid = waitpid(-1, &status, WNOHANG)) > 0);
+#endif
+
+        if (TheKids.allExitedHappy()) {
+            exit(0);
         }
 
-        if (stop - start < 10)
-            failcount++;
-        else
-            failcount = 0;
-
-        if (failcount == 5) {
+        if (TheKids.allHopeless()) {
             syslog(LOG_ALERT, "Exiting due to repeated, frequent failures");
             exit(1);
         }
 
-        if (WIFEXITED(status))
-            if (WEXITSTATUS(status) == 0)
-                exit(0);
-
-        if (WIFSIGNALED(status)) {
-            switch (WTERMSIG(status)) {
-
-            case SIGKILL:
-                exit(0);
-                break;
-
-            case SIGINT:
-            case SIGTERM:
-                syslog(LOG_ALERT, "Exiting due to unexpected forced shutdown");
-                exit(1);
-                break;
-
-            default:
-                break;
-            }
+        if (TheKids.allSignaled(SIGKILL)) {
+            exit(0);
+        }
+
+        if (TheKids.allSignaled(SIGINT) || TheKids.allSignaled(SIGTERM)) {
+            syslog(LOG_ALERT, "Exiting due to unexpected forced shutdown");
+            exit(1);
         }
 
         squid_signal(SIGINT, SIG_DFL, SA_RESTART);
@@ -1789,10 +1861,12 @@
 
 #endif
 
-    if (Config.pidFilename && strcmp(Config.pidFilename, "none") != 0) {
-        enter_suid();
-        safeunlink(Config.pidFilename, 0);
-        leave_suid();
+    if (IamPrimaryProcess()) {
+        if (Config.pidFilename && strcmp(Config.pidFilename, "none") != 0) {
+            enter_suid();
+            safeunlink(Config.pidFilename, 0);
+            leave_suid();
+        }
     }
 
     debugs(1, 1, "Squid Cache (Version " << version_string << "): Exiting normally.");

=== modified file 'src/protos.h'
--- src/protos.h	2010-01-14 09:18:00 +0000
+++ src/protos.h	2010-06-05 19:08:44 +0000
@@ -575,6 +575,22 @@
 SQUIDCEXTERN pid_t readPidFile(void);
 SQUIDCEXTERN void keepCapabilities(void);
 
+SQUIDCEXTERN void BroadcastSignalIfAny(int& sig);
+/// whether the current process is the parent of all other Squid processes
+SQUIDCEXTERN bool IamMasterProcess();
+/** 
+    whether the current process is dedicated to doing things that only
+    a single process should do, such as PID file maintenance and WCCP
+*/
+SQUIDCEXTERN bool IamPrimaryProcess();
+/// whether the current process coordinates worker processes
+SQUIDCEXTERN bool IamCoordinatorProcess();
+/// whether the current process handles HTTP transactions and such
+SQUIDCEXTERN bool IamWorkerProcess();
+/// Whether there should be more than one worker process running
+SQUIDCEXTERN bool UsingSmp(); // try using specific Iam*() checks above first
+SQUIDCEXTERN int DebugSignal;
+
 /* AYJ debugs function to show locations being reset with memset() */
 SQUIDCEXTERN void *xmemset(void *dst, int, size_t);
 

=== modified file 'src/snmp_core.cc'
--- src/snmp_core.cc	2010-01-14 09:18:00 +0000
+++ src/snmp_core.cc	2010-06-05 18:32:22 +0000
@@ -34,10 +34,30 @@
 #include "cache_snmp.h"
 #include "acl/FilledChecklist.h"
 #include "ip/IpAddress.h"
+#include "ipc/StartListening.h"
 
 #define SNMP_REQUEST_SIZE 4096
 #define MAX_PROTOSTAT 5
 
+
+/// dials snmpConnectionOpened call
+class SnmpListeningStartedDialer: public CallDialer,
+    public Ipc::StartListeningCb
+{
+public:
+    typedef void (*Handler)(int fd, int errNo);
+    SnmpListeningStartedDialer(Handler aHandler): handler(aHandler) {}
+
+    virtual void print(std::ostream &os) const { startPrint(os) << ')'; }
+
+    virtual bool canDial(AsyncCall &) const { return true; }
+    virtual void dial(AsyncCall &) { (handler)(fd, errNo); }
+
+public:
+    Handler handler;
+};
+
+
 IpAddress theOutSNMPAddr;
 
 typedef struct _mib_tree_entry mib_tree_entry;
@@ -58,6 +78,9 @@
 mib_tree_entry *mib_tree_head;
 mib_tree_entry *mib_tree_last;
 
+static void snmpIncomingConnectionOpened(int fd, int errNo);
+static void snmpOutgoingConnectionOpened(int fd, int errNo);
+
 static mib_tree_entry * snmpAddNodeStr(const char *base_str, int o, oid_ParseFn * parsefunction, instance_Fn * instancefunction);
 static mib_tree_entry *snmpAddNode(oid * name, int len, oid_ParseFn * parsefunction, instance_Fn * instancefunction, int children,...);
 static oid *snmpCreateOid(int length,...);
@@ -278,54 +301,71 @@
 void
 snmpConnectionOpen(void)
 {
-    struct addrinfo *xaddr = NULL;
-    int x;
-
     debugs(49, 5, "snmpConnectionOpen: Called");
 
     if (Config.Port.snmp > 0) {
         Config.Addrs.snmp_incoming.SetPort(Config.Port.snmp);
-        enter_suid();
-        theInSnmpConnection = comm_open_listener(SOCK_DGRAM,
+
+        AsyncCall::Pointer call = asyncCall(49, 2,
+            "snmpIncomingConnectionOpened",
+            SnmpListeningStartedDialer(&snmpIncomingConnectionOpened));
+
+        Ipc::StartListening(SOCK_DGRAM,
                               IPPROTO_UDP,
                               Config.Addrs.snmp_incoming,
                               COMM_NONBLOCKING,
-                              "SNMP Port");
-        leave_suid();
-
-        if (theInSnmpConnection < 0)
-            fatal("Cannot open SNMP Port");
-
-        commSetSelect(theInSnmpConnection, COMM_SELECT_READ, snmpHandleUdp, NULL, 0);
-
-        debugs(1, 1, "Accepting SNMP messages on " << Config.Addrs.snmp_incoming << ", FD " << theInSnmpConnection << ".");
+                              Ipc::fdnInSnmpSocket, call);
 
         if (!Config.Addrs.snmp_outgoing.IsNoAddr()) {
             Config.Addrs.snmp_outgoing.SetPort(Config.Port.snmp);
-            enter_suid();
-            theOutSnmpConnection = comm_open_listener(SOCK_DGRAM,
+
+            AsyncCall::Pointer call = asyncCall(49, 2,
+                "snmpOutgoingConnectionOpened",
+                SnmpListeningStartedDialer(&snmpOutgoingConnectionOpened));
+
+             Ipc::StartListening(SOCK_DGRAM,
                                    IPPROTO_UDP,
                                    Config.Addrs.snmp_outgoing,
                                    COMM_NONBLOCKING,
-                                   "SNMP Port");
-            leave_suid();
-
-            if (theOutSnmpConnection < 0)
-                fatal("Cannot open Outgoing SNMP Port");
-
-            commSetSelect(theOutSnmpConnection,
-                          COMM_SELECT_READ,
-                          snmpHandleUdp,
-                          NULL, 0);
-
-            debugs(1, 1, "Outgoing SNMP messages on " << Config.Addrs.snmp_outgoing << ", FD " << theOutSnmpConnection << ".");
-
-            fd_note(theOutSnmpConnection, "Outgoing SNMP socket");
-
-            fd_note(theInSnmpConnection, "Incoming SNMP socket");
-        } else {
-            theOutSnmpConnection = theInSnmpConnection;
+                                   Ipc::fdnOutSnmpSocket, call);
         }
+    }
+}
+
+static void
+snmpIncomingConnectionOpened(int fd, int errNo)
+{
+    theInSnmpConnection = fd;
+    if (theInSnmpConnection < 0)
+        fatal("Cannot open Incoming SNMP Port");
+
+    commSetSelect(theInSnmpConnection, COMM_SELECT_READ, snmpHandleUdp, NULL,
+        0);
+
+    debugs(1, 1, "Accepting SNMP messages on " << Config.Addrs.snmp_incoming <<
+        ", FD " << theInSnmpConnection << ".");
+
+    if (Config.Addrs.snmp_outgoing.IsNoAddr())
+        theOutSnmpConnection = theInSnmpConnection;
+}
+
+static void
+snmpOutgoingConnectionOpened(int fd, int errNo)
+{
+    theOutSnmpConnection = fd;
+    if (theOutSnmpConnection < 0)
+        fatal("Cannot open Outgoing SNMP Port");
+
+    commSetSelect(theOutSnmpConnection, COMM_SELECT_READ, snmpHandleUdp, NULL,
+        0);
+
+    debugs(1, 1, "Outgoing SNMP messages on " << Config.Addrs.snmp_outgoing <<
+        ", FD " << theOutSnmpConnection << ".");
+
+    {
+        struct addrinfo *xaddr = NULL;
+        int x;
+
 
         theOutSNMPAddr.SetEmpty();
 

=== modified file 'src/structs.h'
--- src/structs.h	2010-03-03 09:38:49 +0000
+++ src/structs.h	2010-03-04 06:25:18 +0000
@@ -617,6 +617,7 @@
 
     char *accept_filter;
     int umask;
+    int main_processes;
 
 #if USE_LOADABLE_MODULES
     wordlist *loadable_module_names;

=== modified file 'src/tools.cc'
--- src/tools.cc	2009-12-02 22:39:11 +0000
+++ src/tools.cc	2010-06-05 19:08:44 +0000
@@ -41,6 +41,8 @@
 #include "SquidMath.h"
 #include "SquidTime.h"
 #include "ip/IpIntercept.h"
+#include "ipc/Kids.h"
+#include "ipc/Coordinator.h"
 
 #if HAVE_SYS_PRCTL_H
 #include <sys/prctl.h>
@@ -64,6 +66,7 @@
 extern void log_trace_init(char *);
 #endif
 static void restoreCapabilities(int keep);
+int DebugSignal = -1;
 
 #ifdef _SQUID_LINUX_
 /* Workaround for crappy glic header files */
@@ -397,6 +400,15 @@
     abort();
 }
 
+void
+BroadcastSignalIfAny(int& sig)
+{
+    if (sig > 0) {
+        if (IamCoordinatorProcess())
+            Ipc::Coordinator::Instance()->broadcastSignal(sig);
+        sig = -1;
+    }
+}
 
 void
 sigusr2_handle(int sig)
@@ -404,6 +416,8 @@
     static int state = 0;
     /* no debugs() here; bad things happen if the signal is delivered during _db_print() */
 
+    DebugSignal = sig;
+
     if (state == 0) {
 #ifndef MEM_GEN_TRACE
         Debug::parseOptions("ALL,7");
@@ -790,6 +804,50 @@
 #endif
 }
 
+bool
+IamMasterProcess()
+{
+    return KidIdentifier == 0;
+}
+
+bool
+IamWorkerProcess()
+{
+    // when there is only one process, it has to be the worker
+    if (opt_no_daemon || Config.main_processes == 0)
+        return true;
+
+    return 0 < KidIdentifier && KidIdentifier <= Config.main_processes;
+}
+
+bool
+UsingSmp()
+{
+    return !opt_no_daemon && Config.main_processes > 1;
+}
+
+bool
+IamCoordinatorProcess()
+{
+    return UsingSmp() && KidIdentifier == Config.main_processes + 1;
+}
+
+bool
+IamPrimaryProcess()
+{
+    // when there is only one process, it has to be primary
+    if (opt_no_daemon || Config.main_processes == 0)
+        return true;
+
+    // when there is a master and worker process, the master delegates
+    // primary functions to its only kid
+    if (Config.main_processes == 1)
+        return IamWorkerProcess();
+
+    // in SMP mode, multiple kids delegate primary functions to the coordinator
+    return IamCoordinatorProcess();
+}
+
 void
 writePidFile(void)
 {
@@ -798,6 +856,9 @@
     mode_t old_umask;
     char buf[32];
 
+    if (!IamPrimaryProcess())
+        return;
+
     if ((f = Config.pidFilename) == NULL)
         return;
 

=== modified file 'test-suite/testheaders.sh'
--- test-suite/testheaders.sh	2009-11-19 23:32:21 +0000
+++ test-suite/testheaders.sh	2010-05-02 18:09:21 +0000
@@ -16,6 +16,8 @@
 	dir="${2}"
 fi
 
+exitCode=0
+
 for f in `cd ${dir} && ls -1 *.h 2>/dev/null`; do
 	echo -n "Testing ${dir}/${f} ..."
 	hdr=`echo "${f}" | sed s/.h//`
@@ -32,11 +34,14 @@
 	fi
 	if [ ! -f testHeaderDeps_${hdr}.o ]; then
 		rm testHeaders
-		exit 1
-	fi
+		exitCode=1
+	else
 	echo "OK."
 	# unit-tests require an app to run.
 	# our most-recent object suits this purpose.
 	# let's link or some tests will fail
 	${cc} ./testHeaderDeps_${hdr}.o -o ./testHeaders
+	fi
 done
+
+exit $exitCode

# Begin bundle
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
Mcq6FAwd5lwQuESxSgCn7Vf/8XckU4UJBR9SApA=

