|  | /* C++ modules.  Experimental! | 
|  | Copyright (C) 2018-2025 Free Software Foundation, Inc. | 
|  | Written by Nathan Sidwell <nathan@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 "resolver.h" | 
|  |  | 
|  | // C++ | 
|  | #include <set> | 
|  | #include <vector> | 
|  | #include <map> | 
|  | // C | 
|  | #include <csignal> | 
|  | #include <cstring> | 
|  | #include <cstdarg> | 
|  | #include <cstdlib> | 
|  | // OS | 
|  | #include <unistd.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/stat.h> | 
|  | #include <fcntl.h> | 
|  |  | 
|  | // 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 | 
|  |  | 
|  | #ifndef AI_NUMERICSERV | 
|  | #define AI_NUMERICSERV 0 | 
|  | #endif | 
|  |  | 
|  | #include <getopt.h> | 
|  |  | 
|  | // Select or epoll | 
|  | #if 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 | 
|  |  | 
|  | // GCC | 
|  | #include "version.h" | 
|  | #include "ansidecl.h" | 
|  | #define HAVE_DECL_BASENAME 1 /* See comment in gcc/configure.ac.  */ | 
|  | #include "libiberty.h" | 
|  |  | 
|  | #if !HOST_HAS_O_CLOEXEC | 
|  | #define O_CLOEXEC 0 | 
|  | #endif | 
|  |  | 
|  | #ifndef IS_DIR_SEPARATOR | 
|  | #define IS_DIR_SEPARATOR(C) ((C) == '/') | 
|  | #endif | 
|  | #ifndef DIR_SEPARATOR | 
|  | #define DIR_SEPARATOR '/' | 
|  | #endif | 
|  |  | 
|  | /* Imported from libcpp/system.h | 
|  | Use gcc_assert(EXPR) to test invariants.  */ | 
|  | #if ENABLE_ASSERT_CHECKING | 
|  | #define gcc_assert(EXPR)                                                \ | 
|  | ((void)(!(EXPR) ? fancy_abort (__FILE__, __LINE__, __FUNCTION__), 0 : 0)) | 
|  | #elif (GCC_VERSION >= 4005) | 
|  | #define gcc_assert(EXPR)                                                \ | 
|  | ((void)(__builtin_expect (!(EXPR), 0) ? __builtin_unreachable (), 0 : 0)) | 
|  | #else | 
|  | /* Include EXPR, so that unused variable warnings do not occur.  */ | 
|  | #define gcc_assert(EXPR) ((void)(0 && (EXPR))) | 
|  | #endif | 
|  |  | 
|  | /* Use gcc_unreachable() to mark unreachable locations (like an | 
|  | unreachable default case of a switch.  Do not use gcc_assert(0).  */ | 
|  | #if (GCC_VERSION >= 4005) && !ENABLE_ASSERT_CHECKING | 
|  | #define gcc_unreachable() __builtin_unreachable () | 
|  | #else | 
|  | #define gcc_unreachable() (fancy_abort (__FILE__, __LINE__, __FUNCTION__)) | 
|  | #endif | 
|  |  | 
|  |  | 
|  | #if 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"; | 
|  |  | 
|  | #if 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 (2); | 
|  | } | 
|  |  | 
|  | /* Hooked to from gcc_assert & gcc_unreachable.  */ | 
|  |  | 
|  | #if ENABLE_ASSERT_CHECKING | 
|  | 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); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* Exploded on a signal.  */ | 
|  |  | 
|  | static void ATTRIBUTE_NORETURN ATTRIBUTE_COLD | 
|  | crash_signal (int sig) | 
|  | { | 
|  | signal (sig, SIG_DFL); | 
|  | // strsignal is not portable :( | 
|  | internal_error ("signal %d", 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); | 
|  | } | 
|  |  | 
|  | #if 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 ? 1 : 0; | 
|  |  | 
|  | fnotice (file, "Usage: %s [OPTION...] [CONNECTION] [MAPPINGS...] \n\n", | 
|  | progname); | 
|  | 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, "%s %s%s\n", progname, pkgversion_string, version_string); | 
|  | fprintf (stdout, "Copyright %s 2018-2025 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 (0); | 
|  | } | 
|  |  | 
|  | /* 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; | 
|  | /* getaddrinfo requires either hostname or servname to be non-null, so that we must | 
|  | set a port number (to cover the case that the string passed contains just /NN). | 
|  | Use an arbitrary in-range port number, but avoiding "0" which triggers a bug on | 
|  | some BSD variants.  */ | 
|  | if (int e = getaddrinfo (slash == arg ? NULL : arg, "1", &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; | 
|  | } | 
|  |  | 
|  | #if 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 defined (HAVE_PSELECT) || defined (HAVE_SELECT) | 
|  | if (active < 0 && sock_fd >= 0 && FD_ISSET (sock_fd, &readers)) | 
|  | active = -1; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | 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; | 
|  |  | 
|  | #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 | 
|  | #if 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 | 
|  |  | 
|  | #if NETWORKING | 
|  | if (sock_fd >= 0) | 
|  | { | 
|  | server (name[0] != '=', sock_fd, &r); | 
|  | if (name[0] == '=') | 
|  | unlink (name.c_str () + 1); | 
|  | } | 
|  | else | 
|  | #endif | 
|  | { | 
|  | 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; | 
|  | } |