1011 lines
23 KiB
C++
1011 lines
23 KiB
C++
|
/* C++ modules. Experimental!
|
||
|
Copyright (C) 2018-2022 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>
|
||
|
#include <cstdlib>
|
||
|
// 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
|
||
|
|
||
|
#ifndef AI_NUMERICSERV
|
||
|
#define AI_NUMERICSERV 0
|
||
|
#endif
|
||
|
|
||
|
#include <getopt.h>
|
||
|
|
||
|
// Select or epoll
|
||
|
#if 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
|
||
|
|
||
|
/* Imported from libcpp/system.h
|
||
|
Use gcc_assert(EXPR) to test invariants. */
|
||
|
#if ENABLE_ASSERT_CHECKING
|
||
|
#define gcc_assert(EXPR) \
|
||
|
((void)(!(EXPR) ? fancy_abort (__FILE__, __LINE__, __FUNCTION__), 0 : 0))
|
||
|
#elif (GCC_VERSION >= 4005)
|
||
|
#define gcc_assert(EXPR) \
|
||
|
((void)(__builtin_expect (!(EXPR), 0) ? __builtin_unreachable (), 0 : 0))
|
||
|
#else
|
||
|
/* Include EXPR, so that unused variable warnings do not occur. */
|
||
|
#define gcc_assert(EXPR) ((void)(0 && (EXPR)))
|
||
|
#endif
|
||
|
|
||
|
/* Use gcc_unreachable() to mark unreachable locations (like an
|
||
|
unreachable default case of a switch. Do not use gcc_assert(0). */
|
||
|
#if (GCC_VERSION >= 4005) && !ENABLE_ASSERT_CHECKING
|
||
|
#define gcc_unreachable() __builtin_unreachable ()
|
||
|
#else
|
||
|
#define gcc_unreachable() (fancy_abort (__FILE__, __LINE__, __FUNCTION__))
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#if NETWORKING
|
||
|
struct netmask {
|
||
|
in6_addr addr;
|
||
|
unsigned bits;
|
||
|
|
||
|
netmask (const in6_addr &a, unsigned b)
|
||
|
{
|
||
|
if (b > sizeof (in6_addr) * 8)
|
||
|
b = sizeof (in6_addr) * 8;
|
||
|
bits = b;
|
||
|
unsigned byte = (b + 7) / 8;
|
||
|
unsigned ix = 0;
|
||
|
for (ix = 0; ix < byte; ix++)
|
||
|
addr.s6_addr[ix] = a.s6_addr[ix];
|
||
|
for (; ix != sizeof (in6_addr); ix++)
|
||
|
addr.s6_addr[ix] = 0;
|
||
|
if (b & 3)
|
||
|
addr.s6_addr[b/7] &= (255 << 8) >> (b & 3);
|
||
|
}
|
||
|
|
||
|
bool includes (const in6_addr &a) const
|
||
|
{
|
||
|
unsigned byte = bits / 8;
|
||
|
for (unsigned ix = 0; ix != byte; ix++)
|
||
|
if (addr.s6_addr[ix] != a.s6_addr[ix])
|
||
|
return false;
|
||
|
if (bits & 3)
|
||
|
if ((addr.s6_addr[byte] ^ a.s6_addr[byte]) >> (8 - (bits & 3)))
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/* Netmask comparison. */
|
||
|
struct netmask_cmp {
|
||
|
bool operator() (const netmask &a, const netmask &b) const
|
||
|
{
|
||
|
if (a.bits != b.bits)
|
||
|
return a.bits < b.bits;
|
||
|
for (unsigned ix = 0; ix != sizeof (in6_addr); ix++)
|
||
|
if (a.addr.s6_addr[ix] != b.addr.s6_addr[ix])
|
||
|
return a.addr.s6_addr[ix] < b.addr.s6_addr[ix];
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
typedef std::set<netmask, 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";
|
||
|
|
||
|
#if NETWORKING
|
||
|
static netmask_set_t netmask_set;
|
||
|
|
||
|
static netmask_vec_t accept_addrs;
|
||
|
#endif
|
||
|
|
||
|
/* Strip out the source directory from FILE. */
|
||
|
|
||
|
static const char *
|
||
|
trim_src_file (const char *file)
|
||
|
{
|
||
|
static const char me[] = __FILE__;
|
||
|
unsigned pos = 0;
|
||
|
|
||
|
while (file[pos] == me[pos] && me[pos])
|
||
|
pos++;
|
||
|
while (pos && !IS_DIR_SEPARATOR (me[pos-1]))
|
||
|
pos--;
|
||
|
|
||
|
return file + pos;
|
||
|
}
|
||
|
|
||
|
/* Die screaming. */
|
||
|
|
||
|
void ATTRIBUTE_NORETURN ATTRIBUTE_PRINTF_1 ATTRIBUTE_COLD
|
||
|
internal_error (const char *fmt, ...)
|
||
|
{
|
||
|
fprintf (stderr, "%s:Internal error ", progname);
|
||
|
va_list args;
|
||
|
|
||
|
va_start (args, fmt);
|
||
|
vfprintf (stderr, fmt, args);
|
||
|
va_end (args);
|
||
|
fprintf (stderr, "\n");
|
||
|
|
||
|
exit (2);
|
||
|
}
|
||
|
|
||
|
/* Hooked to from gcc_assert & gcc_unreachable. */
|
||
|
|
||
|
#if ENABLE_ASSERT_CHECKING
|
||
|
void ATTRIBUTE_NORETURN ATTRIBUTE_COLD
|
||
|
fancy_abort (const char *file, int line, const char *func)
|
||
|
{
|
||
|
internal_error ("in %s, at %s:%d", func, trim_src_file (file), line);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Exploded on a signal. */
|
||
|
|
||
|
static void ATTRIBUTE_NORETURN ATTRIBUTE_COLD
|
||
|
crash_signal (int sig)
|
||
|
{
|
||
|
signal (sig, SIG_DFL);
|
||
|
// strsignal is not portable :(
|
||
|
internal_error ("signal %d", sig);
|
||
|
}
|
||
|
|
||
|
/* A fatal error of some kind. */
|
||
|
|
||
|
static void ATTRIBUTE_NORETURN ATTRIBUTE_COLD ATTRIBUTE_PRINTF_1
|
||
|
error (const char *msg, ...)
|
||
|
{
|
||
|
fprintf (stderr, "%s:error: ", progname);
|
||
|
va_list args;
|
||
|
|
||
|
va_start (args, msg);
|
||
|
vfprintf (stderr, msg, args);
|
||
|
va_end (args);
|
||
|
fprintf (stderr, "\n");
|
||
|
|
||
|
exit (1);
|
||
|
}
|
||
|
|
||
|
#if NETWORKING
|
||
|
/* Progress messages to the user. */
|
||
|
static bool ATTRIBUTE_PRINTF_1 ATTRIBUTE_COLD
|
||
|
noisy (const char *fmt, ...)
|
||
|
{
|
||
|
fprintf (stderr, "%s:", progname);
|
||
|
va_list args;
|
||
|
va_start (args, fmt);
|
||
|
vfprintf (stderr, fmt, args);
|
||
|
va_end (args);
|
||
|
fprintf (stderr, "\n");
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* More messages to the user. */
|
||
|
|
||
|
static void ATTRIBUTE_PRINTF_2
|
||
|
fnotice (FILE *file, const char *fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
|
||
|
va_start (args, fmt);
|
||
|
vfprintf (file, fmt, args);
|
||
|
va_end (args);
|
||
|
}
|
||
|
|
||
|
static void ATTRIBUTE_NORETURN
|
||
|
print_usage (int error_p)
|
||
|
{
|
||
|
FILE *file = error_p ? stderr : stdout;
|
||
|
int status = error_p ? 1 : 0;
|
||
|
|
||
|
fnotice (file, "Usage: %s [OPTION...] [CONNECTION] [MAPPINGS...] \n\n",
|
||
|
progname);
|
||
|
fnotice (file, "C++ Module Mapper.\n\n");
|
||
|
fnotice (file, " -a, --accept Netmask to accept from\n");
|
||
|
fnotice (file, " -f, --fallback Use fallback for missing mappings\n");
|
||
|
fnotice (file, " -h, --help Print this help, then exit\n");
|
||
|
fnotice (file, " -n, --noisy Print progress messages\n");
|
||
|
fnotice (file, " -1, --one One connection and then exit\n");
|
||
|
fnotice (file, " -r, --root DIR Root compiled module directory\n");
|
||
|
fnotice (file, " -s, --sequential Process connections sequentially\n");
|
||
|
fnotice (file, " -v, --version Print version number, then exit\n");
|
||
|
fnotice (file, "Send SIGTERM(%d) to terminate\n", SIGTERM);
|
||
|
fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n",
|
||
|
bug_report_url);
|
||
|
exit (status);
|
||
|
}
|
||
|
|
||
|
/* Print version information and exit. */
|
||
|
|
||
|
static void ATTRIBUTE_NORETURN
|
||
|
print_version (void)
|
||
|
{
|
||
|
fnotice (stdout, "%s %s%s\n", progname, pkgversion_string, version_string);
|
||
|
fprintf (stdout, "Copyright %s 2018-2022 Free Software Foundation, Inc.\n",
|
||
|
("(C)"));
|
||
|
fnotice (stdout,
|
||
|
("This is free software; see the source for copying conditions.\n"
|
||
|
"There is NO warranty; not even for MERCHANTABILITY or \n"
|
||
|
"FITNESS FOR A PARTICULAR PURPOSE.\n\n"));
|
||
|
exit (0);
|
||
|
}
|
||
|
|
||
|
/* ARG is a netmask to accept from. Add it to the table. Return
|
||
|
false if we fail to resolve it. */
|
||
|
|
||
|
static bool
|
||
|
accept_from (char *arg ATTRIBUTE_UNUSED)
|
||
|
{
|
||
|
bool ok = true;
|
||
|
#if HAVE_AF_INET6
|
||
|
unsigned bits = sizeof (in6_addr) * 8;
|
||
|
char *slash = strrchr (arg, '/');
|
||
|
if (slash)
|
||
|
{
|
||
|
*slash = 0;
|
||
|
if (slash[1])
|
||
|
{
|
||
|
char *endp;
|
||
|
bits = strtoul (slash + 1, &endp, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
addrinfo hints;
|
||
|
|
||
|
hints.ai_flags = AI_NUMERICSERV;
|
||
|
hints.ai_family = AF_INET6;
|
||
|
hints.ai_socktype = SOCK_STREAM;
|
||
|
hints.ai_protocol = 0;
|
||
|
hints.ai_addrlen = 0;
|
||
|
hints.ai_addr = NULL;
|
||
|
hints.ai_canonname = NULL;
|
||
|
hints.ai_next = NULL;
|
||
|
|
||
|
struct addrinfo *addrs = NULL;
|
||
|
/* getaddrinfo requires either hostname or servname to be non-null, so that we must
|
||
|
set a port number (to cover the case that the string passed contains just /NN).
|
||
|
Use an arbitrary in-range port number, but avoiding "0" which triggers a bug on
|
||
|
some BSD variants. */
|
||
|
if (int e = getaddrinfo (slash == arg ? NULL : arg, "1", &hints, &addrs))
|
||
|
{
|
||
|
noisy ("cannot resolve '%s': %s", arg, gai_strerror (e));
|
||
|
ok = false;
|
||
|
}
|
||
|
else
|
||
|
for (addrinfo *next = addrs; next; next = next->ai_next)
|
||
|
if (next->ai_family == AF_INET6)
|
||
|
{
|
||
|
netmask mask (((const sockaddr_in6 *)next->ai_addr)->sin6_addr, bits);
|
||
|
netmask_set.insert (mask);
|
||
|
}
|
||
|
freeaddrinfo (addrs);
|
||
|
#endif
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
/* Process args, return index to first non-arg. */
|
||
|
|
||
|
static int
|
||
|
process_args (int argc, char **argv)
|
||
|
{
|
||
|
static const struct option options[] =
|
||
|
{
|
||
|
{ "accept", required_argument, NULL, 'a' },
|
||
|
{ "help", no_argument, NULL, 'h' },
|
||
|
{ "map", no_argument, NULL, 'm' },
|
||
|
{ "noisy", no_argument, NULL, 'n' },
|
||
|
{ "one", no_argument, NULL, '1' },
|
||
|
{ "root", required_argument, NULL, 'r' },
|
||
|
{ "sequential", no_argument, NULL, 's' },
|
||
|
{ "translate",no_argument, NULL, 't' },
|
||
|
{ "version", no_argument, NULL, 'v' },
|
||
|
{ 0, 0, 0, 0 }
|
||
|
};
|
||
|
int opt;
|
||
|
bool bad_accept = false;
|
||
|
const char *opts = "a:fhmn1r:stv";
|
||
|
while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
|
||
|
{
|
||
|
switch (opt)
|
||
|
{
|
||
|
case 'a':
|
||
|
if (!accept_from (optarg))
|
||
|
bad_accept = true;
|
||
|
break;
|
||
|
case 'h':
|
||
|
print_usage (false);
|
||
|
/* print_usage will exit. */
|
||
|
case 'f': // deprecated alias
|
||
|
case 'm':
|
||
|
flag_map = true;
|
||
|
break;
|
||
|
case 'n':
|
||
|
flag_noisy = true;
|
||
|
break;
|
||
|
case '1':
|
||
|
flag_one = true;
|
||
|
break;
|
||
|
case 'r':
|
||
|
flag_root = optarg;
|
||
|
break;
|
||
|
case 's':
|
||
|
flag_sequential = true;
|
||
|
break;
|
||
|
case 't':
|
||
|
flag_xlate = true;
|
||
|
break;
|
||
|
case 'v':
|
||
|
print_version ();
|
||
|
/* print_version will exit. */
|
||
|
default:
|
||
|
print_usage (true);
|
||
|
/* print_usage will exit. */
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bad_accept)
|
||
|
error ("failed to resolve all accept addresses");
|
||
|
|
||
|
return optind;
|
||
|
}
|
||
|
|
||
|
#if NETWORKING
|
||
|
|
||
|
/* Manipulate the EPOLL state, or do nothing, if there is epoll. */
|
||
|
|
||
|
#ifdef HAVE_EPOLL
|
||
|
static inline void
|
||
|
do_epoll_ctl (int epoll_fd, int code, int event, int fd, unsigned data)
|
||
|
{
|
||
|
epoll_event ev;
|
||
|
ev.events = event;
|
||
|
ev.data.u32 = data;
|
||
|
if (epoll_ctl (epoll_fd, code, fd, &ev))
|
||
|
{
|
||
|
noisy ("epoll_ctl error:%s", xstrerror (errno));
|
||
|
gcc_unreachable ();
|
||
|
}
|
||
|
}
|
||
|
#define my_epoll_ctl(EFD,C,EV,FD,CL) \
|
||
|
((EFD) >= 0 ? do_epoll_ctl (EFD,C,EV,FD,CL) : (void)0)
|
||
|
#else
|
||
|
#define my_epoll_ctl(EFD,C,EV,FD,CL) ((void)(EFD), (void)(FD), (void)(CL))
|
||
|
#endif
|
||
|
|
||
|
/* We increment this to tell the server to shut down. */
|
||
|
static volatile int term = false;
|
||
|
static volatile int kill_sock_fd = -1;
|
||
|
#if !defined (HAVE_PSELECT) && defined (HAVE_SELECT)
|
||
|
static int term_pipe[2] = {-1, -1};
|
||
|
#else
|
||
|
#define term_pipe ((int *)NULL)
|
||
|
#endif
|
||
|
|
||
|
/* A terminate signal. Shutdown gracefully. */
|
||
|
|
||
|
static void
|
||
|
term_signal (int sig)
|
||
|
{
|
||
|
signal (sig, term_signal);
|
||
|
term = term + 1;
|
||
|
if (term_pipe && term_pipe[1] >= 0)
|
||
|
write (term_pipe[1], &term_pipe[1], 1);
|
||
|
}
|
||
|
|
||
|
/* A kill signal. Shutdown immediately. */
|
||
|
|
||
|
static void
|
||
|
kill_signal (int sig)
|
||
|
{
|
||
|
signal (sig, SIG_DFL);
|
||
|
int sock_fd = kill_sock_fd;
|
||
|
if (sock_fd >= 0)
|
||
|
close (sock_fd);
|
||
|
exit (2);
|
||
|
}
|
||
|
|
||
|
bool process_server (Cody::Server *server, unsigned slot, int epoll_fd)
|
||
|
{
|
||
|
switch (server->GetDirection ())
|
||
|
{
|
||
|
case Cody::Server::READING:
|
||
|
if (int err = server->Read ())
|
||
|
return !(err == EINTR || err == EAGAIN);
|
||
|
server->ProcessRequests ();
|
||
|
server->PrepareToWrite ();
|
||
|
break;
|
||
|
|
||
|
case Cody::Server::WRITING:
|
||
|
if (int err = server->Write ())
|
||
|
return !(err == EINTR || err == EAGAIN);
|
||
|
server->PrepareToRead ();
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// We should never get here
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// We've changed direction, so update epoll
|
||
|
gcc_assert (server->GetFDRead () == server->GetFDWrite ());
|
||
|
my_epoll_ctl (epoll_fd, EPOLL_CTL_MOD,
|
||
|
server->GetDirection () == Cody::Server::READING
|
||
|
? EPOLLIN : EPOLLOUT, server->GetFDRead (), slot + 1);
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void close_server (Cody::Server *server, int epoll_fd)
|
||
|
{
|
||
|
my_epoll_ctl (epoll_fd, EPOLL_CTL_DEL, EPOLLIN, server->GetFDRead (), 0);
|
||
|
|
||
|
close (server->GetFDRead ());
|
||
|
|
||
|
delete server;
|
||
|
}
|
||
|
|
||
|
int open_server (bool ip6, int sock_fd)
|
||
|
{
|
||
|
sockaddr_in6 addr;
|
||
|
socklen_t addr_len = sizeof (addr);
|
||
|
|
||
|
#ifdef HAVE_ACCEPT4
|
||
|
int client_fd = accept4 (sock_fd, ip6 ? (sockaddr *)&addr : nullptr,
|
||
|
&addr_len, SOCK_NONBLOCK);
|
||
|
#else
|
||
|
int client_fd = accept (sock_fd, ip6 ? (sockaddr *)&addr : nullptr, &addr_len);
|
||
|
#endif
|
||
|
if (client_fd < 0)
|
||
|
{
|
||
|
error ("cannot accept: %s", xstrerror (errno));
|
||
|
flag_one = true;
|
||
|
}
|
||
|
else if (ip6)
|
||
|
{
|
||
|
const char *str = NULL;
|
||
|
#if HAVE_INET_NTOP
|
||
|
char name[INET6_ADDRSTRLEN];
|
||
|
str = inet_ntop (addr.sin6_family, &addr.sin6_addr, name, sizeof (name));
|
||
|
#endif
|
||
|
if (!accept_addrs.empty ())
|
||
|
{
|
||
|
netmask_vec_t::iterator e = accept_addrs.end ();
|
||
|
for (netmask_vec_t::iterator i = accept_addrs.begin ();
|
||
|
i != e; ++i)
|
||
|
if (i->includes (addr.sin6_addr))
|
||
|
goto present;
|
||
|
close (client_fd);
|
||
|
client_fd = -1;
|
||
|
noisy ("Rejecting connection from disallowed source '%s'",
|
||
|
str ? str : "");
|
||
|
present:;
|
||
|
}
|
||
|
if (client_fd >= 0)
|
||
|
flag_noisy && noisy ("Accepting connection from '%s'", str ? str : "");
|
||
|
}
|
||
|
|
||
|
return client_fd;
|
||
|
}
|
||
|
|
||
|
/* A server listening on bound socket SOCK_FD. */
|
||
|
|
||
|
static void
|
||
|
server (bool ipv6, int sock_fd, module_resolver *resolver)
|
||
|
{
|
||
|
int epoll_fd = -1;
|
||
|
|
||
|
signal (SIGTERM, term_signal);
|
||
|
#ifdef HAVE_EPOLL
|
||
|
epoll_fd = epoll_create (1);
|
||
|
#endif
|
||
|
if (epoll_fd >= 0)
|
||
|
my_epoll_ctl (epoll_fd, EPOLL_CTL_ADD, EPOLLIN, sock_fd, 0);
|
||
|
|
||
|
#if defined (HAVE_EPOLL) || defined (HAVE_PSELECT) || defined (HAVE_SELECT)
|
||
|
sigset_t mask;
|
||
|
{
|
||
|
sigset_t block;
|
||
|
sigemptyset (&block);
|
||
|
sigaddset (&block, SIGTERM);
|
||
|
sigprocmask (SIG_BLOCK, &block, &mask);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#ifdef HAVE_EPOLL
|
||
|
const unsigned max_events = 20;
|
||
|
epoll_event events[max_events];
|
||
|
#endif
|
||
|
#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
|
||
|
fd_set readers, writers;
|
||
|
#endif
|
||
|
if (term_pipe)
|
||
|
pipe (term_pipe);
|
||
|
|
||
|
// We need stable references to servers, so this array can contain nulls
|
||
|
std::vector<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
|
||
|
#if NETWORKING
|
||
|
#ifdef SIGINT
|
||
|
signal (SIGINT, kill_signal);
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
int argno = process_args (argc, argv);
|
||
|
|
||
|
std::string name;
|
||
|
int sock_fd = -1; /* Socket fd, otherwise stdin/stdout. */
|
||
|
module_resolver r (flag_map, flag_xlate);
|
||
|
|
||
|
if (argno != argc)
|
||
|
{
|
||
|
name = argv[argno];
|
||
|
sock_fd = maybe_parse_socket (name, &r);
|
||
|
if (!name.empty ())
|
||
|
argno++;
|
||
|
}
|
||
|
|
||
|
if (argno != argc)
|
||
|
for (; argno != argc; argno++)
|
||
|
{
|
||
|
std::string option = argv[argno];
|
||
|
char const *prefix = nullptr;
|
||
|
auto ident = option.find_last_of ('?');
|
||
|
if (ident != option.npos)
|
||
|
{
|
||
|
prefix = option.c_str () + ident + 1;
|
||
|
option[ident] = 0;
|
||
|
}
|
||
|
int fd = open (option.c_str (), O_RDONLY | O_CLOEXEC);
|
||
|
int err = 0;
|
||
|
if (fd < 0)
|
||
|
err = errno;
|
||
|
else
|
||
|
{
|
||
|
err = r.read_tuple_file (fd, prefix, false);
|
||
|
close (fd);
|
||
|
}
|
||
|
|
||
|
if (err)
|
||
|
error ("failed reading '%s': %s", option.c_str (), xstrerror (err));
|
||
|
}
|
||
|
else
|
||
|
r.set_default_map (true);
|
||
|
|
||
|
if (flag_root)
|
||
|
r.set_repo (flag_root);
|
||
|
|
||
|
#ifdef HAVE_AF_INET6
|
||
|
netmask_set_t::iterator end = netmask_set.end ();
|
||
|
for (netmask_set_t::iterator iter = netmask_set.begin ();
|
||
|
iter != end; ++iter)
|
||
|
{
|
||
|
netmask_vec_t::iterator e = accept_addrs.end ();
|
||
|
for (netmask_vec_t::iterator i = accept_addrs.begin (); i != e; ++i)
|
||
|
if (i->includes (iter->addr))
|
||
|
goto present;
|
||
|
accept_addrs.push_back (*iter);
|
||
|
present:;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if NETWORKING
|
||
|
if (sock_fd >= 0)
|
||
|
{
|
||
|
server (name[0] != '=', sock_fd, &r);
|
||
|
if (name[0] == '=')
|
||
|
unlink (name.c_str () + 1);
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
{
|
||
|
auto server = Cody::Server (&r, 0, 1);
|
||
|
|
||
|
int err = 0;
|
||
|
for (;;)
|
||
|
{
|
||
|
server.PrepareToRead ();
|
||
|
while ((err = server.Read ()))
|
||
|
{
|
||
|
if (err == EINTR || err == EAGAIN)
|
||
|
continue;
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
server.ProcessRequests ();
|
||
|
|
||
|
server.PrepareToWrite ();
|
||
|
while ((err = server.Write ()))
|
||
|
{
|
||
|
if (err == EINTR || err == EAGAIN)
|
||
|
continue;
|
||
|
goto done;
|
||
|
}
|
||
|
}
|
||
|
done:;
|
||
|
if (err > 0)
|
||
|
error ("communication error:%s", xstrerror (err));
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|