41f01c9715
Some versions of the BSD getaddrinfo() call do not work with the specific input of "0" for the servname entry (a segv results). Since we are making the call with a dummy port number, the value is actually no important, other than it should be in range. Work around the BSD bug by using "1" instead. Signed-off-by: Iain Sandoe <iain@sandoe.co.uk> c++tools/ChangeLog: * server.cc (accept_from): Use "1" as the dummy port number.
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;
|
|
}
|