mirror of
https://gcc.gnu.org/git/gcc.git
synced 2024-11-23 10:54:07 +08:00
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:
parent
e831ad4dab
commit
35fc243fca
125
c++tools/Makefile.in
Normal file
125
c++tools/Makefile.in
Normal 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
43
c++tools/config.h.in
Normal 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
4840
c++tools/configure
vendored
Executable file
File diff suppressed because it is too large
Load Diff
210
c++tools/configure.ac
Normal file
210
c++tools/configure.ac
Normal 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
272
c++tools/resolver.cc
Normal 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
105
c++tools/resolver.h
Normal 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
976
c++tools/server.cc
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user