Add c++tools

Part of our module implementation adds a sample mapper server, the
guts of which are used by the default in-process mapping of cc1plus.
Rather than add another executable to gcc/cp/, this creates a new
c++tools directory where this and any other c++ tools might live.
The toplevel changes are a subsequent commit, because ... git.

c++tools/ChangeLog:

	* Makefile.in: New.
	* config.h.in: New.
	* configure: New.
	* configure.ac: New.
	* resolver.cc: New.
	* resolver.h: New.
	* server.cc: New.
This commit is contained in:
Nathan Sidwell 2020-12-14 09:30:00 -08:00
parent e831ad4dab
commit 35fc243fca
7 changed files with 6571 additions and 0 deletions

125
c++tools/Makefile.in Normal file
View File

@ -0,0 +1,125 @@
# Makefile for c++tools
# Copyright 2020 Free Software Foundation, Inc.
#
# 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/>.
srcdir := @srcdir@
prefix := @prefix@
bindir := @bindir@
libexecdir := @libexecdir@
target_noncanonical := @target_noncanonical@
version := $(shell cat $(srcdir)/../gcc/BASE-VER)
libexecsubdir := $(libexecdir)/gcc/$(target_noncanonical)/$(version)
INSTALL := @INSTALL@
AUTOCONF := @AUTOCONF@
AUTOHEADER := @AUTOHEADER@
CXX := @CXX@
CXXFLAGS := @CXXFLAGS@
CXXOPTS := $(CXXFLAGS) -fno-exceptions -fno-rtti
EXEEXT := @EXEEXT@
LIBIBERTY := ../libiberty/libiberty.a
VERSION.O := ../gcc/version.o
all::
mostlyclean::
rm -f $(MAPPER.O)
clean::
rm -f c++-mapper-server$(exeext)
distclean::
rm -f config.log config.status config.h
maintainer-clean::
install::
check:
installcheck:
dvi:
pdf:
html:
info:
install-info:
install-pdf:
install-man:
install-html:
vpath %.cc $(srcdir)
vpath %.in $(srcdir)
.SUFFIXES:
.SUFFIXES: .cc .o
# Per-source & per-directory compile flags (warning: recursive)
SRC_CXXFLAGS = $(CXXFLAGS$(patsubst $(srcdir)%,%,$1)) \
$(if $(filter-out $(srcdir)/,$1),\
$(call $0,$(dir $(patsubst %/,%,$1))))
%.o: %.cc
$(CXX) $(strip $(CXXOPTS) $(call SRC_CXXFLAGS,$<) $(CXXINC)) \
-MMD -MP -MF ${@:.o=.d} -c -o $@ $<
ifeq (@CXX_AUX_TOOLS@,yes)
all::g++-mapper-server$(exeext)
MAPPER.O := server.o resolver.o
CODYLIB = ../libcody/libcody.a
CXXINC += -I$(srcdir)/../libcody -I$(srcdir)/../include -I$(srcdir)/../gcc -I.
g++-mapper-server$(exeext): $(MAPPER.O) $(CODYLIB)
+$(CXX) $(LDFLAGS) -o $@ $^ $(VERSION.O) $(LIBIBERTY)
# copy to gcc dir so tests there can run
all::../gcc/g++-mapper-server$(exeext)
../gcc/g++-mapper-server$(exeext): g++-mapper-server$(exeext)
$(INSTALL) -p $< $@
install::
$(INSTALL) -p g++-mapper-server$(exeext) $(DESTDIR)$(libexecsubdir)
endif
ifneq ($(MAINTAINER),)
override MAINTAINER += $1
endif
ifeq (@MAINTAINER@,yes)
MAINTAINER = $2
else
MAINTAINER = \# --enable-maintainer-mode to rebuild $1, or make MAINTAINER=touch
endif
all:: Makefile
Makefile: $(srcdir)/Makefile.in config.status
$(SHELL) ./config.status Makefile
$(srcdir)/configure: $(srcdir)/configure.ac
$(call MAINTAINER,$@,cd $(@D) && $(AUTOCONF) -W all,error)
$(srcdir)/config.h.in: $(srcdir)/configure.ac
$(call MAINTAINER,$@,cd $(@D) && $(AUTOHEADER) -f -W all,error)
config.h: config.status config.h.in
./$< --header=$@
touch $@
config.status: $(srcdir)/configure $(srcdir)/config.h.in
if test -x $@; then ./$@ -recheck; else $< @configure_args@; fi
.PHONY: all check clean distclean maintainer-clean
-include $(MAPPER.O:.o=.d)

43
c++tools/config.h.in Normal file
View File

@ -0,0 +1,43 @@
/* config.h.in. Generated from configure.ac by autoheader. */
/* Define if accept4 provided. */
#undef HAVE_ACCEPT4
/* Define if AF_INET6 supported. */
#undef HAVE_AF_INET6
/* Define if AF_UNIX supported. */
#undef HAVE_AF_UNIX
/* Define if epoll_create, epoll_ctl, epoll_pwait provided. */
#undef HAVE_EPOLL
/* Define if inet_ntop provided. */
#undef HAVE_INET_NTOP
/* Define if pselect provided. */
#undef HAVE_PSELECT
/* Define if select provided. */
#undef HAVE_SELECT
/* Define if O_CLOEXEC supported by fcntl. */
#undef HOST_HAS_O_CLOEXEC
/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT
/* Define to the full name of this package. */
#undef PACKAGE_NAME
/* Define to the full name and version of this package. */
#undef PACKAGE_STRING
/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME
/* Define to the home page for this package. */
#undef PACKAGE_URL
/* Define to the version of this package. */
#undef PACKAGE_VERSION

4840
c++tools/configure vendored Executable file

File diff suppressed because it is too large Load Diff

210
c++tools/configure.ac Normal file
View File

@ -0,0 +1,210 @@
# Configure script for c++tools
# Copyright (C) 2020 Free Software Foundation, Inc.
# Written by Nathan Sidwell <nathan@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_TARGET
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

272
c++tools/resolver.cc Normal file
View File

@ -0,0 +1,272 @@
/* C++ modules. Experimental! -*- c++ -*-
Copyright (C) 2017-2020 Free Software Foundation, Inc.
Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include "config.h"
#include "resolver.h"
// C++
#include <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;
}

105
c++tools/resolver.h Normal file
View File

@ -0,0 +1,105 @@
/* C++ modules. Experimental! -*- c++ -*-
Copyright (C) 2017-2020 Free Software Foundation, Inc.
Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#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

976
c++tools/server.cc Normal file
View File

@ -0,0 +1,976 @@
/* C++ modules. Experimental!
Copyright (C) 2018-2020 Free Software Foundation, Inc.
Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include "config.h"
#include "resolver.h"
// C++
#include <set>
#include <vector>
#include <map>
// C
#include <csignal>
#include <cstring>
#include <cstdarg>
// 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;
}