gcc/c++tools/server.cc

978 lines
22 KiB
C++
Raw Normal View History

/* 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
#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
#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. */
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);
// 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-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;
}
#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;
}