On 11/3/20 4:17 PM, Nathan Sidwell wrote:
this is the module mapper client and server pieces. It features a default resolver that can read a text file, or generate default mappings from module name to cmi name.
Richard rightly suggested on IRC that the sample server for the module mapper shouldn't be in the gcc/cp dir. It happened to be that way because it started out much more closely coupled, but then it grew legs.
So this patch creates a new c++tools toplevel directory and places the mapper-server and its default resolver there. That means more changes to the toplevel Makefile.def and Makefile.tpl (I've not included the regenerated Makefile.in, nor other generated files in gcc/ and c++tools in this diff.)
We still need to build the default resolver when building cc1plus, and I've placed mapper-resolver.cc there, as a simple #include forwarder to the source in c++tools. I also replace 'gcc/cp/mapper.h' with a client-specific 'gcc/cp/mapper-client.h'. (mapper-client is only linked into cc1plus, so gcc/cp seems the right place for it.)
The sample server relies on gcc/version.o to pick up its version number, and I place it in the libexecsubdir that we place cc1plus. I wasn't comfortable placing it in the install location of g++ itself. I call it a sample server for a reason :)
I will of course provide changelog when committing. nathan -- Nathan Sidwell
diff --git c/Makefile.def w/Makefile.def index 36fd26b0367..6e98d2d3340 100644 --- c/Makefile.def +++ w/Makefile.def @@ -125,12 +134,13 @@ host_modules= { module= libtermcap; no_check=true; missing=distclean; missing=maintainer-clean; }; host_modules= { module= utils; no_check=true; }; +host_modules= { module= c++tools; }; host_modules= { module= gnattools; }; +host_modules= { module= gotools; }; host_modules= { module= lto-plugin; bootstrap=true; extra_configure_flags='--enable-shared @extra_linker_plugin_flags@ @extra_linker_plugin_configure_flags@'; extra_make_flags='@extra_linker_plugin_flags@'; }; host_modules= { module= libcc1; extra_configure_flags=--enable-shared; }; -host_modules= { module= gotools; }; host_modules= { module= libctf; no_install=true; no_check=true; bootstrap=true; }; @@ -381,6 +392,8 @@ dependencies = { module=all-lto-plugin; on=all-libiberty-linker-plugin; }; dependencies = { module=configure-libcc1; on=configure-gcc; }; dependencies = { module=all-libcc1; on=all-gcc; }; +// we want version.o from gcc, and implicitly depend on libcody +dependencies = { module=all-c++tools; on=all-gcc; }; dependencies = { module=all-gotools; on=all-target-libgo; }; dependencies = { module=all-utils; on=all-libiberty; }; diff --git c/Makefile.tpl w/Makefile.tpl index efed1511750..3b88f351d5b 100644 --- c/Makefile.tpl +++ w/Makefile.tpl @@ -864,8 +864,8 @@ local-distclean: -rm -f texinfo/doc/Makefile texinfo/po/POTFILES -rmdir texinfo/doc texinfo/info texinfo/intl texinfo/lib 2>/dev/null -rmdir texinfo/makeinfo texinfo/po texinfo/util 2>/dev/null - -rmdir fastjar gcc gnattools gotools libcc1 libiberty 2>/dev/null - -rmdir texinfo zlib 2>/dev/null + -rmdir c++tools fastjar gcc gnattools gotools 2>/dev/null + -rmdir libcc1 libiberty texinfo zlib 2>/dev/null -find . -name config.cache -exec rm -f {} \; \; 2>/dev/null local-maintainer-clean: diff --git c/c++tools/configure.ac w/c++tools/configure.ac new file mode 100644 index 00000000000..8d882e541df --- /dev/null +++ w/c++tools/configure.ac @@ -0,0 +1,210 @@ +# Configure script for c++tools +# Copyright (C) 2020 Free Software Foundation, Inc. +# Written by Nathan Sidwell <nat...@acm.org> while at FaceBook +# +# This file 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 of the License, or +# (at your option) any later version. +# +# This program 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 this program; see the file COPYING3. If not see +# <http://www.gnu.org/licenses/>. + +# C++ has grown a C++20 mapper server. This may be used to provide +# and/or learn and/or build required modules. This sample server +# shows how the protocol introduced by wg21.link/p1184 may be used. +# By default g++ uses an in-process mapper. + +sinclude(../config/acx.m4) + +AC_INIT(c++tools) + +AC_CONFIG_SRCDIR([server.cc]) + +# Determine the noncanonical names used for directories. +ACX_NONCANONICAL_HOST + +AC_CANONICAL_SYSTEM +AC_PROG_INSTALL + +AC_PROG_CXX +MISSING=`cd $ac_aux_dir && ${PWDCMD-pwd}`/missing +AC_CHECK_PROGS([AUTOCONF], [autoconf], [$MISSING autoconf]) +AC_CHECK_PROGS([AUTOHEADER], [autoheader], [$MISSING autoheader]) + +dnl Enabled by default +AC_MSG_CHECKING([whether to build C++ tools]) + AC_ARG_ENABLE(c++-tools, + [AS_HELP_STRING([--enable-c++-tools], + [build auxiliary c++ tools])], + cxx_aux_tools=$enableval, + cxx_aux_tools=yes) + +AC_MSG_RESULT($cxx_aux_tools) +CXX_AUX_TOOLS="$cxx_aux_tools" +AC_SUBST(CXX_AUX_TOOLS) + +AC_ARG_ENABLE([maintainer-mode], +AS_HELP_STRING([--enable-maintainer-mode], +[enable maintainer mode. Add rules to rebuild configurey bits]),, +[enable_maintainer_mode=no]) +case "$enable_maintainer_mode" in + ("yes") maintainer_mode=yes ;; + ("no") maintainer=no ;; + (*) AC_MSG_ERROR([unknown maintainer mode $enable_maintainer_mode]) ;; +esac +AC_MSG_CHECKING([maintainer-mode]) +AC_MSG_RESULT([$maintainer_mode]) +test "$maintainer_mode" = yes && MAINTAINER=yes +AC_SUBST(MAINTAINER) + +# Check if O_CLOEXEC is defined by fcntl +AC_CACHE_CHECK(for O_CLOEXEC, ac_cv_o_cloexec, [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <fcntl.h>]], [[ +return open ("/dev/null", O_RDONLY | O_CLOEXEC);]])], +[ac_cv_o_cloexec=yes],[ac_cv_o_cloexec=no])]) +if test $ac_cv_o_cloexec = yes; then + AC_DEFINE(HOST_HAS_O_CLOEXEC, 1, + [Define if O_CLOEXEC supported by fcntl.]) +fi + +# C++ Modules would like some networking features to provide the mapping +# server. You can still use modules without them though. +# The following network-related checks could probably do with some +# Windows and other non-linux defenses and checking. + +# Local socket connectivity wants AF_UNIX networking +# Check for AF_UNIX networking +AC_CACHE_CHECK(for AF_UNIX, ac_cv_af_unix, [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h>]],[[ +sockaddr_un un; +un.sun_family = AF_UNSPEC; +int fd = socket (AF_UNIX, SOCK_STREAM, 0); +connect (fd, (sockaddr *)&un, sizeof (un));]])], +[ac_cv_af_unix=yes], +[ac_cv_af_unix=no])]) +if test $ac_cv_af_unix = yes; then + AC_DEFINE(HAVE_AF_UNIX, 1, + [Define if AF_UNIX supported.]) +fi + +# Remote socket connectivity wants AF_INET6 networking +# Check for AF_INET6 networking +AC_CACHE_CHECK(for AF_INET6, ac_cv_af_inet6, [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h>]],[[ +sockaddr_in6 in6; +in6.sin6_family = AF_UNSPEC; +struct addrinfo *addrs = 0; +struct addrinfo hints; +hints.ai_flags = 0; +hints.ai_family = AF_INET6; +hints.ai_socktype = SOCK_STREAM; +hints.ai_protocol = 0; +hints.ai_canonname = 0; +hints.ai_addr = 0; +hints.ai_next = 0; +int e = getaddrinfo ("localhost", 0, &hints, &addrs); +const char *str = gai_strerror (e); +freeaddrinfo (addrs); +int fd = socket (AF_INET6, SOCK_STREAM, 0); +connect (fd, (sockaddr *)&in6, sizeof (in6));]])], +[ac_cv_af_inet6=yes], +[ac_cv_af_inet6=no])]) +if test $ac_cv_af_inet6 = yes; then + AC_DEFINE(HAVE_AF_INET6, 1, + [Define if AF_INET6 supported.]) +fi + +# Efficient server response wants epoll +# Check for epoll_create, epoll_ctl, epoll_pwait +AC_CACHE_CHECK(for epoll, ac_cv_epoll, [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/epoll.h>]],[[ +int fd = epoll_create (1); +epoll_event ev; +ev.events = EPOLLIN; +ev.data.fd = 0; +epoll_ctl (fd, EPOLL_CTL_ADD, 0, &ev); +epoll_pwait (fd, 0, 0, -1, 0);]])], +[ac_cv_epoll=yes], +[ac_cv_epoll=no])]) +if test $ac_cv_epoll = yes; then + AC_DEFINE(HAVE_EPOLL, 1, + [Define if epoll_create, epoll_ctl, epoll_pwait provided.]) +fi + +# If we can't use epoll, try pselect. +# Check for pselect +AC_CACHE_CHECK(for pselect, ac_cv_pselect, [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/select.h>]],[[ +pselect (0, 0, 0, 0, 0, 0);]])], +[ac_cv_pselect=yes], +[ac_cv_pselect=no])]) +if test $ac_cv_pselect = yes; then + AC_DEFINE(HAVE_PSELECT, 1, + [Define if pselect provided.]) +fi + +# And failing that, use good old select. +# If we can't even use this, the server is serialized. +# Check for select +AC_CACHE_CHECK(for select, ac_cv_select, [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/select.h>]],[[ +select (0, 0, 0, 0, 0);]])], +[ac_cv_select=yes], +[ac_cv_select=no])]) +if test $ac_cv_select = yes; then + AC_DEFINE(HAVE_SELECT, 1, + [Define if select provided.]) +fi + +# Avoid some fnctl calls by using accept4, when available. +# Check for accept4 +AC_CACHE_CHECK(for accept4, ac_cv_accept4, [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/socket.h>]],[[ +int err = accept4 (1, 0, 0, SOCK_NONBLOCK);]])], +[ac_cv_accept4=yes], +[ac_cv_accept4=no])]) +if test $ac_cv_accept4 = yes; then + AC_DEFINE(HAVE_ACCEPT4, 1, + [Define if accept4 provided.]) +fi + +# For better server messages, look for a way to stringize network addresses +# Check for inet_ntop +AC_CACHE_CHECK(for inet_ntop, ac_cv_inet_ntop, [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <arpa/inet.h> +#include <netinet/in.h>]],[[ +sockaddr_in6 in6; +char buf[INET6_ADDRSTRLEN]; +const char *str = inet_ntop (AF_INET6, &in6, buf, sizeof (buf));]])], +[ac_cv_inet_ntop=yes], +[ac_cv_inet_ntop=no])]) +if test $ac_cv_inet_ntop = yes; then + AC_DEFINE(HAVE_INET_NTOP, 1, + [Define if inet_ntop provided.]) +fi + +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_FILES([Makefile]) + +AC_OUTPUT diff --git c/c++tools/resolver.cc w/c++tools/resolver.cc new file mode 100644 index 00000000000..5028d2a4a37 --- /dev/null +++ w/c++tools/resolver.cc @@ -0,0 +1,272 @@ +/* C++ modules. Experimental! -*- c++ -*- + Copyright (C) 2017-2020 Free Software Foundation, Inc. + Written by Nathan Sidwell <nat...@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 <algorithm> +// C +#include <cstring> +// OS +#include <fcntl.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> + +#ifndef DIR_SEPARATOR +#define DIR_SEPARATOR '/' +#endif + +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 <char const *> (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, Cody::Flags, + std::string &module) +{ + return cmi_response (s, module); +} + +int +module_resolver::ModuleImportRequest (Cody::Server *s, Cody::Flags, + std::string &module) +{ + return cmi_response (s, module); +} + +int +module_resolver::IncludeTranslateRequest (Cody::Server *s, Cody::Flags, + 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/c++tools/resolver.h w/c++tools/resolver.h new file mode 100644 index 00000000000..19339125b26 --- /dev/null +++ w/c++tools/resolver.h @@ -0,0 +1,105 @@ +/* C++ modules. Experimental! -*- c++ -*- + Copyright (C) 2017-2020 Free Software Foundation, Inc. + Written by Nathan Sidwell <nat...@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/>. */ + +#ifndef GXX_RESOLVER_H +#define GXX_RESOLVER_H 1 + +// Mapper interface for client and server bits +#include "cody.hh" +// C++ +#include <string> +#include <map> + +// 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<std::string, std::string>; + +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 controlled by Cody::Resolver + using parent::ConnectRequest; + virtual module_resolver *ConnectRequest (Cody::Server *, unsigned version, + std::string &agent, + std::string &ident) + override; + using parent::ModuleRepoRequest; + virtual int ModuleRepoRequest (Cody::Server *) override; + using parent::ModuleExportRequest; + virtual int ModuleExportRequest (Cody::Server *s, Cody::Flags, + std::string &module) + override; + using parent::ModuleImportRequest; + virtual int ModuleImportRequest (Cody::Server *s, Cody::Flags, + std::string &module) + override; + using parent::IncludeTranslateRequest; + virtual int IncludeTranslateRequest (Cody::Server *s, Cody::Flags, + std::string &include) + override; + +private: + using parent::GetCMISuffix; + virtual char const *GetCMISuffix () override; + +private: + int cmi_response (Cody::Server *s, std::string &module); +}; + +#endif diff --git c/c++tools/server.cc w/c++tools/server.cc new file mode 100644 index 00000000000..6457dc5b878 --- /dev/null +++ w/c++tools/server.cc @@ -0,0 +1,976 @@ +/* C++ modules. Experimental! + Copyright (C) 2018-2020 Free Software Foundation, Inc. + Written by Nathan Sidwell <nat...@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> +// 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 + +#include <getopt.h> + +// Select or epoll +#ifdef 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 + +#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, 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"; + +#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 (2); +} + +/* 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 ? 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-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 (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; + 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<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 +#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 + { + 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/configure.ac w/gcc/configure.ac index 73034bb902b..d1c980dd64a 100644 --- c/gcc/configure.ac +++ w/gcc/configure.ac @@ -1440,6 +1442,10 @@ fi AC_CHECK_TYPE(ssize_t, int) AC_CHECK_TYPE(caddr_t, char *) +AC_CHECK_TYPE(sighander_t, + AC_DEFINE(HAVE_SIGHANDLER_T, 1, + [Define if <sys/signal.h> defines sighandler_t]), + ,signal.h) GCC_AC_FUNC_MMAP_BLACKLIST @@ -1585,6 +1591,72 @@ if test $ac_cv_f_setlkw = yes; then [Define if F_SETLKW supported by fcntl.]) fi +# Check if O_CLOEXEC is defined by fcntl +AC_CACHE_CHECK(for O_CLOEXEC, ac_cv_o_cloexec, [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <fcntl.h>]], [[ +return open ("/dev/null", O_RDONLY | O_CLOEXEC);]])], +[ac_cv_o_cloexec=yes],[ac_cv_o_cloexec=no])]) +if test $ac_cv_o_cloexec = yes; then + AC_DEFINE(HOST_HAS_O_CLOEXEC, 1, + [Define if O_CLOEXEC supported by fcntl.]) +fi + +# C++ Modules would like some networking features to provide the mapping +# server. You can still use modules without them though. +# The following network-related checks could probably do with some +# Windows and other non-linux defenses and checking. + +# Local socket connectivity wants AF_UNIX networking +# Check for AF_UNIX networking +AC_CACHE_CHECK(for AF_UNIX, ac_cv_af_unix, [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h>]],[[ +sockaddr_un un; +un.sun_family = AF_UNSPEC; +int fd = socket (AF_UNIX, SOCK_STREAM, 0); +connect (fd, (sockaddr *)&un, sizeof (un));]])], +[ac_cv_af_unix=yes], +[ac_cv_af_unix=no])]) +if test $ac_cv_af_unix = yes; then + AC_DEFINE(HAVE_AF_UNIX, 1, + [Define if AF_UNIX supported.]) +fi + +# Remote socket connectivity wants AF_INET6 networking +# Check for AF_INET6 networking +AC_CACHE_CHECK(for AF_INET6, ac_cv_af_inet6, [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h>]],[[ +sockaddr_in6 in6; +in6.sin6_family = AF_UNSPEC; +struct addrinfo *addrs = 0; +struct addrinfo hints; +hints.ai_flags = 0; +hints.ai_family = AF_INET6; +hints.ai_socktype = SOCK_STREAM; +hints.ai_protocol = 0; +hints.ai_canonname = 0; +hints.ai_addr = 0; +hints.ai_next = 0; +int e = getaddrinfo ("localhost", 0, &hints, &addrs); +const char *str = gai_strerror (e); +freeaddrinfo (addrs); +int fd = socket (AF_INET6, SOCK_STREAM, 0); +connect (fd, (sockaddr *)&in6, sizeof (in6));]])], +[ac_cv_af_inet6=yes], +[ac_cv_af_inet6=no])]) +if test $ac_cv_af_inet6 = yes; then + AC_DEFINE(HAVE_AF_INET6, 1, + [Define if AF_INET6 supported.]) +fi + # Restore CFLAGS, CXXFLAGS from before the gcc_AC_NEED_DECLARATIONS tests. CFLAGS="$saved_CFLAGS" CXXFLAGS="$saved_CXXFLAGS" diff --git c/gcc/cp/mapper-client.cc w/gcc/cp/mapper-client.cc new file mode 100644 index 00000000000..22605ca8cf9 --- /dev/null +++ w/gcc/cp/mapper-client.cc @@ -0,0 +1,327 @@ +/* C++ modules. Experimental! + Copyright (C) 2017-2020 Free Software Foundation, Inc. + Written by Nathan Sidwell <nat...@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 "system.h" + +#include "line-map.h" +#include "diagnostic-core.h" +#include "mapper-client.h" +#include "intl.h" + +#include "../../c++tools/resolver.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; + + if (!arg_no) + { + /* @name means look in the compiler's install dir. */ + if (ptr[0] == '@') + ptr++; + else + full_program_name = nullptr; + } + + 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, if the mapper is a simple + file name. */ + 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 <char *> (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 '<': + // <from>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) + c->flags = Cody::Flags (connect.GetInteger ()); + 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-client.h w/gcc/cp/mapper-client.h new file mode 100644 index 00000000000..ca1a0aa5509 --- /dev/null +++ w/gcc/cp/mapper-client.h @@ -0,0 +1,63 @@ +/* C++ modules. Experimental! -*- c++ -*- + Copyright (C) 2020 Free Software Foundation, Inc. + Written by Nathan Sidwell <nat...@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/>. */ + +/* Forward to the header in c++tools. */ + +#ifndef MAPPER_CLIENT_H +#define MAPPER_CLIENT_H 1 + +#include "cody.hh" + +#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; + Cody::Flags flags = Cody::Flags::None; + +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: + Cody::Flags get_flags () const + { + return flags; + } + +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 diff --git c/gcc/cp/mapper-resolver.cc w/gcc/cp/mapper-resolver.cc new file mode 100644 index 00000000000..02ec48c61ea --- /dev/null +++ w/gcc/cp/mapper-resolver.cc @@ -0,0 +1,27 @@ +/* C++ modules. Experimental! -*- c++ -*- + Copyright (C) 2020 Free Software Foundation, Inc. + Written by Nathan Sidwell <nat...@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/>. */ + +/* Forward to the resolver in c++tools. */ + +#include "config.h" +#define INCLUDE_ALGORITHM +#include "system.h" + +#include "../../c++tools/resolver.cc"