/* C++ modules. Experimental! Copyright (C) 2018-2022 Free Software Foundation, Inc. Written by Nathan Sidwell 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 . */ #include "config.h" #include "resolver.h" // C++ #include #include #include // C #include #include #include #include // OS #include #include #include #include // 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 # ifdef HAVE_AF_UNIX /* sockaddr_un */ # include # endif # include # ifdef HAVE_AF_INET6 /* sockaddr_in6, getaddrinfo, freeaddrinfo, gai_sterror, ntohs, htons. */ # include # endif #ifdef HAVE_INET_NTOP /* inet_ntop. */ #include #endif #endif #ifndef HAVE_AF_INET6 # define gai_strerror(X) "" #endif #ifndef AI_NUMERICSERV #define AI_NUMERICSERV 0 #endif #include // Select or epoll #if NETWORKING #ifdef HAVE_EPOLL /* epoll_create, epoll_ctl, epoll_pwait */ #include #endif #if defined (HAVE_PSELECT) || defined (HAVE_SELECT) /* pselect or select */ #include #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_set_t; typedef std::vector 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 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; }