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