| /* 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; | |
| } |