| /* C++ modules. Experimental! |
| Copyright (C) 2018-2022 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-2022 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 (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; |
| |
| #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; |
| } |