this is the module mapper client and server pieces. It features a
default resolver that can read a text file, or generate default mappings
from module name to cmi name.
By default the compiler will use an in-process resolved, but with
suitable options can be instructed to communicate with a server over
a) a pipe to a spawned process's stdin/stdout
b) a pair of specified filenos
c) a unix-domain socket (if supported)
d) an ipv6 host/port (if supported)
the server can operat in modes a, c or d.
--
Nathan Sidwell
diff --git c/gcc/cp/mapper-client.cc w/gcc/cp/mapper-client.cc
new file mode 100644
index 00000000000..de259b0564c
--- /dev/null
+++ w/gcc/cp/mapper-client.cc
@@ -0,0 +1,315 @@
+/* C++ modules. Experimental!
+ Copyright (C) 2017-2020 Free Software Foundation, Inc.
+ Written by Nathan Sidwell <nat...@acm.org> while at FaceBook
+
+ This file is part of GCC.
+
+ GCC is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ GCC is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+
+#include "line-map.h"
+#include "diagnostic-core.h"
+#define MAPPER_FOR_GCC 1
+#include "mapper.h"
+#include "intl.h"
+
+module_client::module_client (pex_obj *p, int fd_from, int fd_to)
+ : Client (fd_from, fd_to), pex (p)
+{
+}
+
+static module_client *
+spawn_mapper_program (char const **errmsg, std::string &name,
+ char const *full_program_name)
+{
+ /* Split writable at white-space. No space-containing args for
+ you! */
+ // At most every other char could be an argument
+ char **argv = new char *[name.size () / 2 + 2];
+ unsigned arg_no = 0;
+ char *str = new char[name.size ()];
+ memcpy (str, name.c_str () + 1, name.size ());
+
+ for (auto ptr = str; ; ++ptr)
+ {
+ while (*ptr == ' ')
+ ptr++;
+ if (!*ptr)
+ break;
+ argv[arg_no++] = ptr;
+ while (*ptr && *ptr != ' ')
+ ptr++;
+ if (!*ptr)
+ break;
+ *ptr = 0;
+ }
+ argv[arg_no] = nullptr;
+
+ auto *pex = pex_init (PEX_USE_PIPES, progname, NULL);
+ FILE *to = pex_input_pipe (pex, false);
+ name = argv[0];
+ if (!to)
+ *errmsg = "connecting input";
+ else
+ {
+ int flags = PEX_SEARCH;
+
+ if (full_program_name)
+ {
+ /* Prepend the invoking path. */
+ size_t dir_len = progname - full_program_name;
+ std::string argv0;
+ argv0.reserve (dir_len + name.size ());
+ argv0.append (full_program_name, dir_len).append (name);
+ name = std::move (argv0);
+ argv[0] = const_cast <char *> (name.c_str ());
+ flags = 0;
+ }
+ int err;
+ *errmsg = pex_run (pex, flags, argv[0], argv, NULL, NULL, &err);
+ }
+ delete[] str;
+ delete[] argv;
+
+ int fd_from = -1, fd_to = -1;
+ if (!*errmsg)
+ {
+ FILE *from = pex_read_output (pex, false);
+ if (from && (fd_to = dup (fileno (to))) >= 0)
+ fd_from = fileno (from);
+ else
+ *errmsg = "connecting output";
+ fclose (to);
+ }
+
+ if (*errmsg)
+ {
+ pex_free (pex);
+ return nullptr;
+ }
+
+ return new module_client (pex, fd_from, fd_to);
+}
+
+module_client *
+module_client::open_module_client (location_t loc, const char *o,
+ void (*set_repo) (const char *),
+ char const *full_program_name)
+{
+ module_client *c = nullptr;
+ std::string ident;
+ std::string name;
+ char const *errmsg = nullptr;
+ unsigned line = 0;
+
+ if (o && o[0])
+ {
+ /* Maybe a local or ipv6 address. */
+ name = o;
+ auto last = name.find_last_of ('?');
+ if (last != name.npos)
+ {
+ ident = name.substr (last + 1);
+ name.erase (last);
+ }
+
+ if (name.size ())
+ {
+ switch (name[0])
+ {
+ case '<':
+ // <from>to or <>fromto, or <>
+ {
+ int fd_from = -1, fd_to = -1;
+ char const *ptr = name.c_str ();
+ char *eptr;
+
+ fd_from = strtoul (++ptr, &eptr, 0);
+ if (*eptr == '>')
+ {
+ ptr = eptr;
+ fd_to = strtoul (++ptr, &eptr, 0);
+ if (eptr != ptr && ptr == name.c_str () + 1)
+ fd_from = fd_to;
+ }
+
+ if (*eptr)
+ errmsg = "parsing";
+ else
+ {
+ if (name.size () == 2)
+ {
+ fd_from = fileno (stdin);
+ fd_to = fileno (stdout);
+ }
+ c = new module_client (fd_from, fd_to);
+ }
+ }
+ break;
+
+ case '=':
+ // =localsocket
+ {
+ int fd = -1;
+#if CODY_NETWORKING
+ fd = Cody::OpenLocal (&errmsg, name.c_str () + 1);
+#endif
+ if (fd >= 0)
+ c = new module_client (fd, fd);
+ }
+ break;
+
+ case '|':
+ // |program and args
+ c = spawn_mapper_program (&errmsg, name, full_program_name);
+ break;
+
+ default:
+ // file or hostname:port
+ {
+ auto colon = name.find_last_of (':');
+ if (colon != name.npos)
+ {
+ char const *cptr = name.c_str () + colon;
+ char *endp;
+ unsigned port = strtoul (cptr + 1, &endp, 10);
+
+ if (port && endp != cptr + 1 && !*endp)
+ {
+ name[colon] = 0;
+ int fd = 01;
+#if CODY_NETWORKING
+ fd = Cody::OpenInet6 (&errmsg, name.c_str (), port);
+#endif
+ name[colon] = ':';
+
+ if (fd >= 0)
+ c = new module_client (fd, fd);
+ }
+ }
+
+ }
+ break;
+ }
+ }
+ }
+
+ if (!c)
+ {
+ // Make a default in-process client
+ bool file = !errmsg && !name.empty ();
+ auto r = new module_resolver (!file, true);
+
+ if (file)
+ {
+ int fd = open (name.c_str (), O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ errmsg = "opening";
+ else
+ {
+ if (int l = r->read_tuple_file (fd, ident, false))
+ {
+ if (l > 0)
+ line = l;
+ errmsg = "reading";
+ }
+
+ close (fd);
+ }
+ }
+ else
+ r->set_repo ("gcm.cache");
+
+ auto *s = new Cody::Server (r);
+ c = new module_client (s);
+ }
+
+#ifdef SIGPIPE
+ if (!c->IsDirect ())
+ /* We need to ignore sig pipe for a while. */
+ c->sigpipe = signal (SIGPIPE, SIG_IGN);
+#endif
+
+ if (errmsg)
+ error_at (loc, line ? G_("failed %s mapper %qs line %u")
+ : G_("failed %s mapper %qs"), errmsg, name.c_str (), line);
+
+ // now wave hello!
+ c->Cork ();
+ c->Connect (std::string ("GCC"), ident);
+ c->ModuleRepo ();
+ auto packets = c->Uncork ();
+
+ auto &connect = packets[0];
+ if (connect.GetCode () == Cody::Client::PC_CONNECT)
+ ;
+ else if (connect.GetCode () == Cody::Client::PC_ERROR)
+ error_at (loc, "failed mapper handshake %s", connect.GetString ().c_str ());
+
+ auto &repo = packets[1];
+ if (repo.GetCode () == Cody::Client::PC_PATHNAME)
+ set_repo (repo.GetString ().c_str ());
+
+ return c;
+}
+
+void
+module_client::close_module_client (location_t loc, module_client *mapper)
+{
+ if (mapper->IsDirect ())
+ {
+ auto *s = mapper->GetServer ();
+ auto *r = s->GetResolver ();
+ delete s;
+ delete r;
+ }
+ else
+ {
+ if (mapper->pex)
+ {
+ int fd_write = mapper->GetFDWrite ();
+ if (fd_write >= 0)
+ close (fd_write);
+
+ int status;
+ pex_get_status (mapper->pex, 1, &status);
+
+ pex_free (mapper->pex);
+ mapper->pex = NULL;
+
+ if (WIFSIGNALED (status))
+ error_at (loc, "mapper died by signal %s",
+ strsignal (WTERMSIG (status)));
+ else if (WIFEXITED (status) && WEXITSTATUS (status) != 0)
+ error_at (loc, "mapper exit status %d",
+ WEXITSTATUS (status));
+ }
+ else
+ {
+ int fd_read = mapper->GetFDRead ();
+ close (fd_read);
+ }
+
+#ifdef SIGPIPE
+ // Restore sigpipe
+ if (mapper->sigpipe != SIG_IGN)
+ signal (SIGPIPE, mapper->sigpipe);
+#endif
+ }
+
+ delete mapper;
+}
diff --git c/gcc/cp/mapper-resolver.cc w/gcc/cp/mapper-resolver.cc
new file mode 100644
index 00000000000..7692380812a
--- /dev/null
+++ w/gcc/cp/mapper-resolver.cc
@@ -0,0 +1,266 @@
+/* C++ modules. Experimental! -*- c++ -*-
+ Copyright (C) 2017-2020 Free Software Foundation, Inc.
+ Written by Nathan Sidwell <nat...@acm.org> while at FaceBook
+
+ This file is part of GCC.
+
+ GCC is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ GCC is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+
+#include "mapper.h"
+// C++
+#include <algorithm>
+// C
+#include <cstring>
+// OS
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+module_resolver::module_resolver (bool map, bool xlate)
+ : default_map (map), default_translate (xlate)
+{
+}
+
+module_resolver::~module_resolver ()
+{
+ if (fd_repo >= 0)
+ close (fd_repo);
+}
+
+bool
+module_resolver::set_repo (std::string &&r, bool force)
+{
+ if (force || repo.empty ())
+ {
+ repo = std::move (r);
+ force = true;
+ }
+ return force;
+}
+
+bool
+module_resolver::add_mapping (std::string &&module, std::string &&file,
+ bool force)
+{
+ auto res = map.emplace (std::move (module), std::move (file));
+ if (res.second)
+ force = true;
+ else if (force)
+ res.first->second = std::move (file);
+
+ return force;
+}
+
+int
+module_resolver::read_tuple_file (int fd, char const *prefix, bool force)
+{
+ struct stat stat;
+ if (fstat (fd, &stat) < 0)
+ return -errno;
+
+ if (!stat.st_size)
+ return 0;
+
+ // Just map the file, we're gonna read all of it, so no need for
+ // line buffering
+ void *buffer = mmap (nullptr, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (buffer == MAP_FAILED)
+ return -errno;
+
+ size_t prefix_len = prefix ? strlen (prefix) : 0;
+ unsigned lineno = 0;
+
+ for (char const *begin = reinterpret_cast <char const *> (buffer),
+ *end = begin + stat.st_size, *eol;
+ begin != end; begin = eol + 1)
+ {
+ lineno++;
+ eol = std::find (begin, end, '\n');
+ if (eol == end)
+ // last line has no \n, ignore the line, you lose
+ break;
+
+ auto *pos = begin;
+ bool pfx_search = prefix_len != 0;
+
+ pfx_search:
+ while (*pos == ' ' || *pos == '\t')
+ pos++;
+
+ auto *space = pos;
+ while (*space != '\n' && *space != ' ' && *space != '\t')
+ space++;
+
+ if (pos == space)
+ // at end of line, nothing here
+ continue;
+
+ if (pfx_search)
+ {
+ if (size_t (space - pos) == prefix_len
+ && std::equal (pos, space, prefix))
+ pfx_search = false;
+ pos = space;
+ goto pfx_search;
+ }
+
+ std::string module (pos, space);
+ while (*space == ' ' || *space == '\t')
+ space++;
+ std::string file (space, eol);
+
+ if (module[0] == '$')
+ {
+ if (module == "$root")
+ set_repo (std::move (file));
+ else
+ return lineno;
+ }
+ else
+ {
+ if (file.empty ())
+ file = GetCMIName (module);
+ add_mapping (std::move (module), std::move (file), force);
+ }
+ }
+
+ munmap (buffer, stat.st_size);
+
+ return 0;
+}
+
+char const *
+module_resolver::GetCMISuffix ()
+{
+ return "gcm";
+}
+
+module_resolver *
+module_resolver::ConnectRequest (Cody::Server *s, unsigned version,
+ std::string &a, std::string &i)
+{
+ if (!version || version > Cody::Version)
+ s->ErrorResponse ("version mismatch");
+ else if (a != "GCC")
+ // Refuse anything but GCC
+ ErrorResponse (s, std::string ("only GCC supported"));
+ else if (!ident.empty () && ident != i)
+ // Failed ident check
+ ErrorResponse (s, std::string ("bad ident"));
+ else
+ // Success!
+ s->ConnectResponse ("gcc");
+
+ return this;
+}
+
+int
+module_resolver::ModuleRepoRequest (Cody::Server *s)
+{
+ s->PathnameResponse (repo);
+ return 0;
+}
+
+int
+module_resolver::cmi_response (Cody::Server *s, std::string &module)
+{
+ auto iter = map.find (module);
+ if (iter == map.end ())
+ {
+ std::string file;
+ if (default_map)
+ file = std::move (GetCMIName (module));
+ auto res = map.emplace (module, file);
+ iter = res.first;
+ }
+
+ if (iter->second.empty ())
+ s->ErrorResponse ("no such module");
+ else
+ s->PathnameResponse (iter->second);
+
+ return 0;
+}
+
+int
+module_resolver::ModuleExportRequest (Cody::Server *s, std::string &module)
+{
+ return cmi_response (s, module);
+}
+
+int
+module_resolver::ModuleImportRequest (Cody::Server *s, std::string &module)
+{
+ return cmi_response (s, module);
+}
+
+int
+module_resolver::IncludeTranslateRequest (Cody::Server *s, std::string &include)
+{
+ auto iter = map.find (include);
+ if (iter == map.end () && default_translate)
+ {
+ // Not found, look for it
+ auto file = GetCMIName (include);
+ struct stat statbuf;
+ bool ok = true;
+
+#if HAVE_FSTATAT
+ int fd_dir = AT_FDCWD;
+ if (!repo.empty ())
+ {
+ if (fd_repo == -1)
+ {
+ fd_repo = open (repo.c_str (),
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (fd_repo < 0)
+ fd_repo = -2;
+ }
+ fd_dir = fd_repo;
+ }
+
+ if (!repo.empty () && fd_repo < 0)
+ ok = false;
+ else if (fstatat (fd_dir, file.c_str (), &statbuf, 0) < 0
+ || !S_ISREG (statbuf.st_mode))
+ ok = false;
+#else
+ auto append = repo;
+ append.push_back (DIR_SEPARATOR);
+ append.append (file);
+ if (stat (append.c_str (), &statbuf) < 0
+ || !S_ISREG (statbuf.st_mode))
+ ok = false;
+#endif
+ if (!ok)
+ // Mark as not present
+ file.clear ();
+ auto res = map.emplace (include, file);
+ iter = res.first;
+ }
+
+ if (iter == map.end () || iter->second.empty ())
+ s->BoolResponse (false);
+ else
+ s->PathnameResponse (iter->second);
+
+ return 0;
+}
+
diff --git c/gcc/cp/mapper-server.cc w/gcc/cp/mapper-server.cc
new file mode 100644
index 00000000000..f0d26810501
--- /dev/null
+++ w/gcc/cp/mapper-server.cc
@@ -0,0 +1,968 @@
+/* C++ modules. Experimental!
+ Copyright (C) 2018-2020 Free Software Foundation, Inc.
+ Written by Nathan Sidwell <nat...@acm.org> while at FaceBook
+
+ This file is part of GCC.
+
+ GCC is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ GCC is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "mapper.h"
+
+// C++
+#include <set>
+#include <vector>
+// GCC
+#define INCLUDE_VECTOR
+#define INCLUDE_MAP
+#define INCLUDE_SET
+
+// Network
+/* Include network stuff first. Excitingly OSX10.14 uses bcmp here, which
+ we poison later! */
+#if defined (HAVE_AF_UNIX) || defined (HAVE_AF_INET6)
+/* socket, bind, listen, accept{4} */
+# define NETWORKING 1
+# include <sys/socket.h>
+# ifdef HAVE_AF_UNIX
+/* sockaddr_un */
+# include <sys/un.h>
+# endif
+# include <netinet/in.h>
+# ifdef HAVE_AF_INET6
+/* sockaddr_in6, getaddrinfo, freeaddrinfo, gai_sterror, ntohs, htons. */
+# include <netdb.h>
+# endif
+#ifdef HAVE_INET_NTOP
+/* inet_ntop. */
+#include <arpa/inet.h>
+#endif
+#endif
+#ifndef HAVE_AF_INET6
+# define gai_strerror(X) ""
+#endif
+
+// Excitingly Darwin uses bcmp in its network headers, and we poison
+// that in our setup.
+#include "system.h"
+#include "version.h"
+#include "intl.h"
+#include <getopt.h>
+
+// Select or epoll
+#ifdef NETWORKING
+#ifdef HAVE_EPOLL
+/* epoll_create, epoll_ctl, epoll_pwait */
+#include <sys/epoll.h>
+#endif
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+/* pselect or select */
+#include <sys/select.h>
+#endif
+#endif
+
+#if !HOST_HAS_O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+#ifndef HAVE_SIGHANDLER_T
+typedef void (*sighandler_t) (int);
+#endif
+
+#ifdef NETWORKING
+struct netmask {
+ in6_addr addr;
+ unsigned bits;
+
+ netmask (const in6_addr &a, unsigned b)
+ {
+ if (b > sizeof (in6_addr) * 8)
+ b = sizeof (in6_addr) * 8;
+ bits = b;
+ unsigned byte = (b + 7) / 8;
+ unsigned ix = 0;
+ for (ix = 0; ix < byte; ix++)
+ addr.s6_addr[ix] = a.s6_addr[ix];
+ for (; ix != sizeof (in6_addr); ix++)
+ addr.s6_addr[ix] = 0;
+ if (b & 3)
+ addr.s6_addr[b/7] &= (255 << 8) >> (b & 3);
+ }
+
+ bool includes (const in6_addr &a) const
+ {
+ unsigned byte = bits / 8;
+ for (unsigned ix = 0; ix != byte; ix++)
+ if (addr.s6_addr[ix] != a.s6_addr[ix])
+ return false;
+ if (bits & 3)
+ if ((addr.s6_addr[byte] ^ a.s6_addr[byte]) >> (8 - (bits & 3)))
+ return false;
+ return true;
+ }
+};
+
+/* Netmask comparison. */
+struct netmask_cmp {
+ bool operator() (const netmask &a, const netmask &b) const
+ {
+ if (a.bits != b.bits)
+ return a.bits < b.bits;
+ for (unsigned ix = 0; ix != sizeof (in6_addr); ix++)
+ if (a.addr.s6_addr[ix] != b.addr.s6_addr[ix])
+ return a.addr.s6_addr[ix] < b.addr.s6_addr[ix];
+ return false;
+ }
+};
+
+typedef std::set<netmask, netmask_cmp> netmask_set_t;
+typedef std::vector<netmask> netmask_vec_t;
+#endif
+
+const char *progname;
+
+/* Speak thoughts out loud. */
+static bool flag_noisy = false;
+
+/* One and done. */
+static bool flag_one = false;
+
+/* Serialize connections. */
+static bool flag_sequential = false;
+
+/* Fallback to default if map file is unrewarding. */
+static bool flag_map = false;
+
+/* Fallback to xlate if map file is unrewarding. */
+static bool flag_xlate = false;
+
+/* Root binary directory. */
+static const char *flag_root = "gcm.cache";
+
+#ifdef NETWORKING
+static netmask_set_t netmask_set;
+
+static netmask_vec_t accept_addrs;
+#endif
+
+/* Strip out the source directory from FILE. */
+
+static const char *
+trim_src_file (const char *file)
+{
+ static const char me[] = __FILE__;
+ unsigned pos = 0;
+
+ while (file[pos] == me[pos] && me[pos])
+ pos++;
+ while (pos && !IS_DIR_SEPARATOR (me[pos-1]))
+ pos--;
+
+ return file + pos;
+}
+
+/* Die screaming. */
+
+void ATTRIBUTE_NORETURN ATTRIBUTE_PRINTF_1 ATTRIBUTE_COLD
+internal_error (const char *fmt, ...)
+{
+ fprintf (stderr, "%s:Internal error ", progname);
+ va_list args;
+
+ va_start (args, fmt);
+ vfprintf (stderr, fmt, args);
+ va_end (args);
+ fprintf (stderr, "\n");
+
+ exit (FATAL_EXIT_CODE);
+}
+
+/* Hooked to from gcc_assert & gcc_unreachable. */
+
+void ATTRIBUTE_NORETURN ATTRIBUTE_COLD
+fancy_abort (const char *file, int line, const char *func)
+{
+ internal_error ("in %s, at %s:%d", func, trim_src_file (file), line);
+}
+
+/* Exploded on a signal. */
+
+static void ATTRIBUTE_NORETURN ATTRIBUTE_COLD
+crash_signal (int sig)
+{
+ signal (sig, SIG_DFL);
+ internal_error ("signal %s", strsignal (sig));
+}
+
+/* A fatal error of some kind. */
+
+static void ATTRIBUTE_NORETURN ATTRIBUTE_COLD ATTRIBUTE_PRINTF_1
+error (const char *msg, ...)
+{
+ fprintf (stderr, "%s:error: ", progname);
+ va_list args;
+
+ va_start (args, msg);
+ vfprintf (stderr, msg, args);
+ va_end (args);
+ fprintf (stderr, "\n");
+
+ exit (1);
+}
+
+#ifdef NETWORKING
+/* Progress messages to the user. */
+static bool ATTRIBUTE_PRINTF_1 ATTRIBUTE_COLD
+noisy (const char *fmt, ...)
+{
+ fprintf (stderr, "%s:", progname);
+ va_list args;
+ va_start (args, fmt);
+ vfprintf (stderr, fmt, args);
+ va_end (args);
+ fprintf (stderr, "\n");
+
+ return false;
+}
+#endif
+
+/* More messages to the user. */
+
+static void ATTRIBUTE_PRINTF_2
+fnotice (FILE *file, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start (args, fmt);
+ vfprintf (file, _(fmt), args);
+ va_end (args);
+}
+
+static void ATTRIBUTE_NORETURN
+print_usage (int error_p)
+{
+ FILE *file = error_p ? stderr : stdout;
+ int status = error_p ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE;
+
+ fnotice (file, "Usage: cxx-mapper [OPTION...] [CONNECTION] [MAPPINGS...] \n\n");
+ fnotice (file, "C++ Module Mapper.\n\n");
+ fnotice (file, " -a, --accept Netmask to accept from\n");
+ fnotice (file, " -f, --fallback Use fallback for missing mappings\n");
+ fnotice (file, " -h, --help Print this help, then exit\n");
+ fnotice (file, " -n, --noisy Print progress messages\n");
+ fnotice (file, " -1, --one One connection and then exit\n");
+ fnotice (file, " -r, --root DIR Root compiled module directory\n");
+ fnotice (file, " -s, --sequential Process connections sequentially\n");
+ fnotice (file, " -v, --version Print version number, then exit\n");
+ fnotice (file, "Send SIGTERM(%d) to terminate\n", SIGTERM);
+ fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n",
+ bug_report_url);
+ exit (status);
+}
+
+/* Print version information and exit. */
+
+static void ATTRIBUTE_NORETURN
+print_version (void)
+{
+ fnotice (stdout, "cxx-mapper %s%s\n", pkgversion_string, version_string);
+ fprintf (stdout, "Copyright %s 2018-2020 Free Software Foundation, Inc.\n",
+ _("(C)"));
+ fnotice (stdout,
+ _("This is free software; see the source for copying conditions.\n"
+ "There is NO warranty; not even for MERCHANTABILITY or \n"
+ "FITNESS FOR A PARTICULAR PURPOSE.\n\n"));
+ exit (SUCCESS_EXIT_CODE);
+}
+
+/* ARG is a netmask to accept from. Add it to the table. Return
+ false if we fail to resolve it. */
+
+static bool
+accept_from (char *arg ATTRIBUTE_UNUSED)
+{
+ bool ok = true;
+#if HAVE_AF_INET6
+ unsigned bits = sizeof (in6_addr) * 8;
+ char *slash = strrchr (arg, '/');
+ if (slash)
+ {
+ *slash = 0;
+ if (slash[1])
+ {
+ char *endp;
+ bits = strtoul (slash + 1, &endp, 0);
+ }
+ }
+
+ addrinfo hints;
+
+ hints.ai_flags = AI_NUMERICSERV;
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ hints.ai_addrlen = 0;
+ hints.ai_addr = NULL;
+ hints.ai_canonname = NULL;
+ hints.ai_next = NULL;
+
+ struct addrinfo *addrs = NULL;
+ if (int e = getaddrinfo (slash == arg ? NULL : arg, "0", &hints, &addrs))
+ {
+ noisy ("cannot resolve '%s': %s", arg, gai_strerror (e));
+ ok = false;
+ }
+ else
+ for (addrinfo *next = addrs; next; next = next->ai_next)
+ if (next->ai_family == AF_INET6)
+ {
+ netmask mask (((const sockaddr_in6 *)next->ai_addr)->sin6_addr, bits);
+ netmask_set.insert (mask);
+ }
+ freeaddrinfo (addrs);
+#endif
+ return ok;
+}
+
+/* Process args, return index to first non-arg. */
+
+static int
+process_args (int argc, char **argv)
+{
+ static const struct option options[] =
+ {
+ { "accept", required_argument, NULL, 'a' },
+ { "help", no_argument, NULL, 'h' },
+ { "map", no_argument, NULL, 'm' },
+ { "noisy", no_argument, NULL, 'n' },
+ { "one", no_argument, NULL, '1' },
+ { "root", required_argument, NULL, 'r' },
+ { "sequential", no_argument, NULL, 's' },
+ { "translate",no_argument, NULL, 't' },
+ { "version", no_argument, NULL, 'v' },
+ { 0, 0, 0, 0 }
+ };
+ int opt;
+ bool bad_accept = false;
+ const char *opts = "a:fhmn1r:stv";
+ while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
+ {
+ switch (opt)
+ {
+ case 'a':
+ if (!accept_from (optarg))
+ bad_accept = true;
+ break;
+ case 'h':
+ print_usage (false);
+ /* print_usage will exit. */
+ case 'f': // deprecated alias
+ case 'm':
+ flag_map = true;
+ break;
+ case 'n':
+ flag_noisy = true;
+ break;
+ case '1':
+ flag_one = true;
+ break;
+ case 'r':
+ flag_root = optarg;
+ break;
+ case 's':
+ flag_sequential = true;
+ break;
+ case 't':
+ flag_xlate = true;
+ break;
+ case 'v':
+ print_version ();
+ /* print_version will exit. */
+ default:
+ print_usage (true);
+ /* print_usage will exit. */
+ }
+ }
+
+ if (bad_accept)
+ error ("failed to resolve all accept addresses");
+
+ return optind;
+}
+
+#ifdef NETWORKING
+
+/* Manipulate the EPOLL state, or do nothing, if there is epoll. */
+
+#ifdef HAVE_EPOLL
+static inline void
+do_epoll_ctl (int epoll_fd, int code, int event, int fd, unsigned data)
+{
+ epoll_event ev;
+ ev.events = event;
+ ev.data.u32 = data;
+ if (epoll_ctl (epoll_fd, code, fd, &ev))
+ {
+ noisy ("epoll_ctl error:%s", xstrerror (errno));
+ gcc_unreachable ();
+ }
+}
+#define my_epoll_ctl(EFD,C,EV,FD,CL) \
+ ((EFD) >= 0 ? do_epoll_ctl (EFD,C,EV,FD,CL) : (void)0)
+#else
+#define my_epoll_ctl(EFD,C,EV,FD,CL) ((void)(EFD), (void)(FD), (void)(CL))
+#endif
+
+/* We increment this to tell the server to shut down. */
+static volatile int term = false;
+static volatile int kill_sock_fd = -1;
+#if !defined (HAVE_PSELECT) && defined (HAVE_SELECT)
+static int term_pipe[2] = {-1, -1};
+#else
+#define term_pipe ((int *)NULL)
+#endif
+
+/* A terminate signal. Shutdown gracefully. */
+
+static void
+term_signal (int sig)
+{
+ signal (sig, term_signal);
+ term = term + 1;
+ if (term_pipe && term_pipe[1] >= 0)
+ write (term_pipe[1], &term_pipe[1], 1);
+}
+
+/* A kill signal. Shutdown immediately. */
+
+static void
+kill_signal (int sig)
+{
+ signal (sig, SIG_DFL);
+ int sock_fd = kill_sock_fd;
+ if (sock_fd >= 0)
+ close (sock_fd);
+ exit (2);
+}
+
+bool process_server (Cody::Server *server, unsigned slot, int epoll_fd)
+{
+ switch (server->GetDirection ())
+ {
+ case Cody::Server::READING:
+ if (int err = server->Read ())
+ return !(err == EINTR || err == EAGAIN);
+ server->ProcessRequests ();
+ server->PrepareToWrite ();
+ break;
+
+ case Cody::Server::WRITING:
+ if (int err = server->Write ())
+ return !(err == EINTR || err == EAGAIN);
+ server->PrepareToRead ();
+ break;
+
+ default:
+ // We should never get here
+ return true;
+ }
+
+ // We've changed direction, so update epoll
+ gcc_assert (server->GetFDRead () == server->GetFDWrite ());
+ my_epoll_ctl (epoll_fd, EPOLL_CTL_MOD,
+ server->GetDirection () == Cody::Server::READING
+ ? EPOLLIN : EPOLLOUT, server->GetFDRead (), slot + 1);
+
+ return false;
+}
+
+void close_server (Cody::Server *server, int epoll_fd)
+{
+ my_epoll_ctl (epoll_fd, EPOLL_CTL_DEL, EPOLLIN, server->GetFDRead (), 0);
+
+ close (server->GetFDRead ());
+
+ delete server;
+}
+
+int open_server (bool ip6, int sock_fd)
+{
+ sockaddr_in6 addr;
+ socklen_t addr_len = sizeof (addr);
+
+#ifdef HAVE_ACCEPT4
+ int client_fd = accept4 (sock_fd, ip6 ? (sockaddr *)&addr : nullptr,
+ &addr_len, SOCK_NONBLOCK);
+#else
+ int client_fd = accept (sock_fd, ip6 ? (sockaddr *)&addr : nullptr, &addr_len);
+#endif
+ if (client_fd < 0)
+ {
+ error ("cannot accept: %s", xstrerror (errno));
+ flag_one = true;
+ }
+ else if (ip6)
+ {
+ const char *str = NULL;
+#if HAVE_INET_NTOP
+ char name[INET6_ADDRSTRLEN];
+ str = inet_ntop (addr.sin6_family, &addr.sin6_addr, name, sizeof (name));
+#endif
+ if (!accept_addrs.empty ())
+ {
+ netmask_vec_t::iterator e = accept_addrs.end ();
+ for (netmask_vec_t::iterator i = accept_addrs.begin ();
+ i != e; ++i)
+ if (i->includes (addr.sin6_addr))
+ goto present;
+ close (client_fd);
+ client_fd = -1;
+ noisy ("Rejecting connection from disallowed source '%s'",
+ str ? str : "");
+ present:;
+ }
+ if (client_fd >= 0)
+ flag_noisy && noisy ("Accepting connection from '%s'", str ? str : "");
+ }
+
+ return client_fd;
+}
+
+/* A server listening on bound socket SOCK_FD. */
+
+static void
+server (bool ipv6, int sock_fd, module_resolver *resolver)
+{
+ int epoll_fd = -1;
+
+ signal (SIGTERM, term_signal);
+#ifdef HAVE_EPOLL
+ epoll_fd = epoll_create (1);
+#endif
+ if (epoll_fd >= 0)
+ my_epoll_ctl (epoll_fd, EPOLL_CTL_ADD, EPOLLIN, sock_fd, 0);
+
+#if defined (HAVE_EPOLL) || defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+ sigset_t mask;
+ {
+ sigset_t block;
+ sigemptyset (&block);
+ sigaddset (&block, SIGTERM);
+ sigprocmask (SIG_BLOCK, &block, &mask);
+ }
+#endif
+
+#ifdef HAVE_EPOLL
+ const unsigned max_events = 20;
+ epoll_event events[max_events];
+#endif
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+ fd_set readers, writers;
+#endif
+ if (term_pipe)
+ pipe (term_pipe);
+
+ // We need stable references to servers, so this array can contain nulls
+ std::vector<Cody::Server *> connections;
+ unsigned live = 0;
+ while (sock_fd >= 0 || live)
+ {
+ /* Wait for one or more events. */
+ bool eintr = false;
+ int event_count;
+
+ if (epoll_fd >= 0)
+ {
+#ifdef HAVE_EPOLL
+ event_count = epoll_pwait (epoll_fd, events, max_events, -1, &mask);
+#endif
+ }
+ else
+ {
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+ FD_ZERO (&readers);
+ FD_ZERO (&writers);
+
+ unsigned limit = 0;
+ if (sock_fd >= 0
+ && !(term || (live && (flag_one || flag_sequential))))
+ {
+ FD_SET (sock_fd, &readers);
+ limit = sock_fd + 1;
+ }
+
+ if (term_pipe && term_pipe[0] >= 0)
+ {
+ FD_SET (term_pipe[0], &readers);
+ if (unsigned (term_pipe[0]) >= limit)
+ limit = term_pipe[0] + 1;
+ }
+
+ for (auto iter = connections.begin ();
+ iter != connections.end (); ++iter)
+ if (auto *server = *iter)
+ {
+ int fd = -1;
+ switch (server->GetDirection ())
+ {
+ case Cody::Server::READING:
+ fd = server->GetFDRead ();
+ FD_SET (fd, &readers);
+ break;
+ case Cody::Server::WRITING:
+ fd = server->GetFDWrite ();
+ FD_SET (fd, &writers);
+ break;
+ default:
+ break;
+ }
+
+ if (fd >= 0 && limit <= unsigned (fd))
+ limit = fd + 1;
+ }
+
+#ifdef HAVE_PSELECT
+ event_count = pselect (limit, &readers, &writers, NULL, NULL, &mask);
+#else
+ event_count = select (limit, &readers, &writers, NULL, NULL);
+#endif
+ if (term_pipe && FD_ISSET (term_pipe[0], &readers))
+ {
+ /* Fake up an interrupted system call. */
+ event_count = -1;
+ errno = EINTR;
+ }
+#endif
+ }
+
+ if (event_count < 0)
+ {
+ // Error in waiting
+ if (errno == EINTR)
+ {
+ flag_noisy && noisy ("Interrupted wait");
+ eintr = true;
+ }
+ else
+ error ("cannot %s: %s", epoll_fd >= 0 ? "epoll_wait"
+#ifdef HAVE_PSELECT
+ : "pselect",
+#else
+ : "select",
+#endif
+ xstrerror (errno));
+ event_count = 0;
+ }
+
+ auto iter = connections.begin ();
+ while (event_count--)
+ {
+ // Process an event
+ int active = -2;
+
+ if (epoll_fd >= 0)
+ {
+#ifdef HAVE_EPOLL
+ /* See PR c++/88664 for why a temporary is used. */
+ unsigned data = events[event_count].data.u32;
+ active = int (data) - 1;
+#endif
+ }
+ else
+ {
+ for (; iter != connections.end (); ++iter)
+ if (auto *server = *iter)
+ {
+ bool found = false;
+ switch (server->GetDirection ())
+ {
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+ case Cody::Server::READING:
+ found = FD_ISSET (server->GetFDRead (), &readers);
+ break;
+ case Cody::Server::WRITING:
+ found = FD_ISSET (server->GetFDWrite (), &writers);
+ break;
+#endif
+ default:
+ break;
+ }
+
+ if (found)
+ {
+ active = iter - connections.begin ();
+ ++iter;
+ break;
+ }
+ }
+
+ if (active < 0 && sock_fd >= 0 && FD_ISSET (sock_fd, &readers))
+ active = -1;
+ }
+
+ if (active >= 0)
+ {
+ // Do the action
+ auto *server = connections[active];
+ if (process_server (server, active, epoll_fd))
+ {
+ connections[active] = nullptr;
+ close_server (server, epoll_fd);
+ live--;
+ if (flag_sequential)
+ my_epoll_ctl (epoll_fd, EPOLL_CTL_ADD, EPOLLIN, sock_fd, 0);
+ }
+ }
+ else if (active == -1 && !eintr)
+ {
+ // New connection
+ int fd = open_server (ipv6, sock_fd);
+ if (fd >= 0)
+ {
+#if !defined (HAVE_ACCEPT4) \
+ && (defined (HAVE_EPOLL) || defined (HAVE_PSELECT) || defined (HAVE_SELECT))
+ int flags = fcntl (fd, F_GETFL, 0);
+ fcntl (fd, F_SETFL, flags | O_NONBLOCK);
+#endif
+ auto *server = new Cody::Server (resolver, fd);
+
+ unsigned slot = connections.size ();
+ if (live == slot)
+ connections.push_back (server);
+ else
+ for (auto iter = connections.begin (); ; ++iter)
+ if (!*iter)
+ {
+ *iter = server;
+ slot = iter - connections.begin ();
+ break;
+ }
+ live++;
+ my_epoll_ctl (epoll_fd, EPOLL_CTL_ADD, EPOLLIN, fd, slot + 1);
+ }
+ }
+
+ if (sock_fd >= 0
+ && (term || (live && (flag_one || flag_sequential))))
+ {
+ /* Stop paying attention to sock_fd. */
+ my_epoll_ctl (epoll_fd, EPOLL_CTL_DEL, EPOLLIN, sock_fd, 0);
+ if (flag_one || term)
+ {
+ close (sock_fd);
+ sock_fd = -1;
+ }
+ }
+ }
+ }
+#if defined (HAVE_EPOLL) || defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+ /* Restore the signal mask. */
+ sigprocmask (SIG_SETMASK, &mask, NULL);
+#endif
+
+ gcc_assert (sock_fd < 0);
+ if (epoll_fd >= 0)
+ close (epoll_fd);
+
+ if (term_pipe && term_pipe[0] >= 0)
+ {
+ close (term_pipe[0]);
+ close (term_pipe[1]);
+ }
+}
+
+#endif
+
+static int maybe_parse_socket (std::string &option, module_resolver *r)
+{
+ /* Local or ipv6 address. */
+ auto last = option.find_last_of ('?');
+ if (last != option.npos)
+ {
+ r->set_ident (option.c_str () + last + 1);
+ option.erase (last);
+ }
+ int fd = -2;
+ char const *errmsg = nullptr;
+
+ /* Does it look like a socket? */
+ if (option[0] == '=')
+ {
+ /* A local socket. */
+#if CODY_NETWORKING
+ fd = Cody::ListenLocal (&errmsg, option.c_str () + 1);
+#endif
+ }
+ else
+ {
+ auto colon = option.find_last_of (':');
+ if (colon != option.npos)
+ {
+ /* Try a hostname:port address. */
+ char const *cptr = option.c_str () + colon;
+ char *endp;
+ unsigned port = strtoul (cptr + 1, &endp, 10);
+
+ if (port && endp != cptr + 1 && !*endp)
+ {
+ /* Ends in ':number', treat as ipv6 domain socket. */
+ option.erase (colon);
+#if CODY_NETWORKING
+ fd = Cody::ListenInet6 (&errmsg, option.c_str (), port);
+#endif
+ }
+ }
+ }
+
+ if (errmsg)
+ error ("failed to open socket: %s", errmsg);
+
+ return fd;
+}
+
+int
+main (int argc, char *argv[])
+{
+ const char *p = argv[0] + strlen (argv[0]);
+ while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1]))
+ --p;
+ progname = p;
+
+ xmalloc_set_program_name (progname);
+
+#ifdef SIGSEGV
+ signal (SIGSEGV, crash_signal);
+#endif
+#ifdef SIGILL
+ signal (SIGILL, crash_signal);
+#endif
+#ifdef SIGBUS
+ signal (SIGBUS, crash_signal);
+#endif
+#ifdef SIGABRT
+ signal (SIGABRT, crash_signal);
+#endif
+#ifdef SIGFPE
+ signal (SIGFPE, crash_signal);
+#endif
+#ifdef SIGPIPE
+ /* Ignore sigpipe, so read/write get an error. */
+ signal (SIGPIPE, SIG_IGN);
+#endif
+#ifdef NETWORKING
+#ifdef SIGINT
+ signal (SIGINT, kill_signal);
+#endif
+#endif
+
+ int argno = process_args (argc, argv);
+
+ std::string name;
+ int sock_fd = -1; /* Socket fd, otherwise stdin/stdout. */
+ module_resolver r (flag_map, flag_xlate);
+
+ if (argno != argc)
+ {
+ name = argv[argno];
+ sock_fd = maybe_parse_socket (name, &r);
+ if (!name.empty ())
+ argno++;
+ }
+
+ if (argno != argc)
+ for (; argno != argc; argno++)
+ {
+ std::string option = argv[argno];
+ char const *prefix = nullptr;
+ auto ident = option.find_last_of ('?');
+ if (ident != option.npos)
+ {
+ prefix = option.c_str () + ident + 1;
+ option[ident] = 0;
+ }
+ int fd = open (option.c_str (), O_RDONLY | O_CLOEXEC);
+ int err = 0;
+ if (fd < 0)
+ err = errno;
+ else
+ {
+ err = r.read_tuple_file (fd, prefix, false);
+ close (fd);
+ }
+
+ if (err)
+ error ("failed reading '%s': %s", option.c_str (), xstrerror (err));
+ }
+ else
+ r.set_default_map (true);
+
+ if (flag_root)
+ r.set_repo (flag_root);
+
+#ifdef HAVE_AF_INET6
+ netmask_set_t::iterator end = netmask_set.end ();
+ for (netmask_set_t::iterator iter = netmask_set.begin ();
+ iter != end; ++iter)
+ {
+ netmask_vec_t::iterator e = accept_addrs.end ();
+ for (netmask_vec_t::iterator i = accept_addrs.begin (); i != e; ++i)
+ if (i->includes (iter->addr))
+ goto present;
+ accept_addrs.push_back (*iter);
+ present:;
+ }
+#endif
+
+#ifdef NETWORKING
+ if (sock_fd >= 0)
+ {
+ server (name[0] != '=', sock_fd, &r);
+ if (name[0] == '=')
+ unlink (name.c_str () + 1);
+ }
+ else
+#endif
+ {
+ gcc_assert (sock_fd < 0);
+ auto server = Cody::Server (&r, 0, 1);
+
+ int err = 0;
+ for (;;)
+ {
+ server.PrepareToRead ();
+ while ((err = server.Read ()))
+ {
+ if (err == EINTR || err == EAGAIN)
+ continue;
+ goto done;
+ }
+
+ server.ProcessRequests ();
+
+ server.PrepareToWrite ();
+ while ((err = server.Write ()))
+ {
+ if (err == EINTR || err == EAGAIN)
+ continue;
+ goto done;
+ }
+ }
+ done:;
+ if (err > 0)
+ error ("communication error:%s", xstrerror (err));
+ }
+
+ return 0;
+}
diff --git c/gcc/cp/mapper.h w/gcc/cp/mapper.h
new file mode 100644
index 00000000000..86562252b53
--- /dev/null
+++ w/gcc/cp/mapper.h
@@ -0,0 +1,122 @@
+/* C++ modules. Experimental! -*- c++ -*-
+ Copyright (C) 2017-2020 Free Software Foundation, Inc.
+ Written by Nathan Sidwell <nat...@acm.org> while at FaceBook
+
+ This file is part of GCC.
+
+ GCC is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ GCC is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+// Mapper interface for client and server bits
+#include "cody.hh"
+// C++
+#include <string>
+#include <map>
+
+// This is a GCC class, so GCC coding conventions on new bits.
+class module_resolver : public Cody::Resolver
+{
+public:
+ using parent = Cody::Resolver;
+ using module_map = std::map<std::string, std::string>;
+
+private:
+ std::string repo;
+ std::string ident;
+ module_map map;
+ int fd_repo = -1;
+ bool default_map = true;
+ bool default_translate = true;
+
+public:
+ module_resolver (bool map = true, bool xlate = false);
+ virtual ~module_resolver () override;
+
+public:
+ void set_default_map (bool d)
+ {
+ default_map = d;
+ }
+ void set_default_translate (bool d)
+ {
+ default_translate = d;
+ }
+ void set_ident (char const *i)
+ {
+ ident = i;
+ }
+ bool set_repo (std::string &&repo, bool force = false);
+ bool add_mapping (std::string &&module, std::string &&file,
+ bool force = false);
+
+ // Return +ve line number of error, or -ve errno
+ int read_tuple_file (int fd, char const *prefix, bool force = false);
+ int read_tuple_file (int fd, std::string const &prefix,
+ bool force = false)
+ {
+ return read_tuple_file (fd, prefix.empty () ? nullptr : prefix.c_str (),
+ force);
+ }
+
+public:
+ // Virtual overriders, names are controlle by Cody::Resolver
+ virtual module_resolver *ConnectRequest (Cody::Server *, unsigned version,
+ std::string &agent,
+ std::string &ident)
+ override;
+ virtual int ModuleRepoRequest (Cody::Server *) override;
+ virtual int ModuleExportRequest (Cody::Server *s, std::string &module)
+ override;
+ virtual int ModuleImportRequest (Cody::Server *s, std::string &module)
+ override;
+ virtual int IncludeTranslateRequest (Cody::Server *s, std::string &include)
+ override;
+
+private:
+ virtual char const *GetCMISuffix () override;
+
+private:
+ int cmi_response (Cody::Server *s, std::string &module);
+};
+
+#ifdef MAPPER_FOR_GCC
+#ifndef HAVE_SIGHANDLER_T
+typedef void (*sighandler_t) (int);
+#endif
+
+class module_client : public Cody::Client
+{
+ pex_obj *pex = nullptr;
+ sighandler_t sigpipe = SIG_IGN;
+
+public:
+ module_client (Cody::Server *s)
+ : Client (s)
+ {
+ }
+ module_client (pex_obj *pex, int fd_from, int fd_to);
+
+ module_client (int fd_from, int fd_to)
+ : Client (fd_from, fd_to)
+ {
+ }
+
+public:
+ static module_client *open_module_client (location_t loc, const char *option,
+ void (*set_repo) (const char *),
+ char const *);
+ static void close_module_client (location_t loc, module_client *);
+};
+
+#endif