374 lines
8.3 KiB
C++
374 lines
8.3 KiB
C++
/* C++ modules. Experimental!
|
|
Copyright (C) 2017-2021 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"
|
|
#if defined (__unix__)
|
|
// Solaris11's socket header used bcopy, which we poison. cody.hh
|
|
// will include it later under the above check
|
|
#include <sys/socket.h>
|
|
#endif
|
|
#define INCLUDE_STRING
|
|
#define INCLUDE_VECTOR
|
|
#include "system.h"
|
|
|
|
#include "line-map.h"
|
|
#include "diagnostic-core.h"
|
|
#include "mapper-client.h"
|
|
#include "intl.h"
|
|
|
|
#include "../../c++tools/resolver.h"
|
|
|
|
#if !HOST_HAS_O_CLOEXEC
|
|
#define O_CLOEXEC 0
|
|
#endif
|
|
|
|
module_client::module_client (pex_obj *p, int fd_from, int fd_to)
|
|
: Client (fd_from, fd_to), pex (p)
|
|
{
|
|
}
|
|
|
|
static module_client *
|
|
spawn_mapper_program (char const **errmsg, std::string &name,
|
|
char const *full_program_name)
|
|
{
|
|
/* Split writable at white-space. No space-containing args for
|
|
you! */
|
|
// At most every other char could be an argument
|
|
char **argv = new char *[name.size () / 2 + 2];
|
|
unsigned arg_no = 0;
|
|
char *str = new char[name.size ()];
|
|
memcpy (str, name.c_str () + 1, name.size ());
|
|
|
|
for (auto ptr = str; ; ++ptr)
|
|
{
|
|
while (*ptr == ' ')
|
|
ptr++;
|
|
if (!*ptr)
|
|
break;
|
|
|
|
if (!arg_no)
|
|
{
|
|
/* @name means look in the compiler's install dir. */
|
|
if (ptr[0] == '@')
|
|
ptr++;
|
|
else
|
|
full_program_name = nullptr;
|
|
}
|
|
|
|
argv[arg_no++] = ptr;
|
|
while (*ptr && *ptr != ' ')
|
|
ptr++;
|
|
if (!*ptr)
|
|
break;
|
|
*ptr = 0;
|
|
}
|
|
argv[arg_no] = nullptr;
|
|
|
|
auto *pex = pex_init (PEX_USE_PIPES, progname, NULL);
|
|
FILE *to = pex_input_pipe (pex, false);
|
|
name = argv[0];
|
|
if (!to)
|
|
*errmsg = "connecting input";
|
|
else
|
|
{
|
|
int flags = PEX_SEARCH;
|
|
|
|
if (full_program_name)
|
|
{
|
|
/* Prepend the invoking path, if the mapper is a simple
|
|
file name. */
|
|
size_t dir_len = progname - full_program_name;
|
|
std::string argv0;
|
|
argv0.reserve (dir_len + name.size ());
|
|
argv0.append (full_program_name, dir_len).append (name);
|
|
name = std::move (argv0);
|
|
argv[0] = const_cast <char *> (name.c_str ());
|
|
flags = 0;
|
|
}
|
|
int err;
|
|
*errmsg = pex_run (pex, flags, argv[0], argv, NULL, NULL, &err);
|
|
}
|
|
delete[] str;
|
|
delete[] argv;
|
|
|
|
int fd_from = -1, fd_to = -1;
|
|
if (!*errmsg)
|
|
{
|
|
FILE *from = pex_read_output (pex, false);
|
|
if (from && (fd_to = dup (fileno (to))) >= 0)
|
|
fd_from = fileno (from);
|
|
else
|
|
*errmsg = "connecting output";
|
|
fclose (to);
|
|
}
|
|
|
|
if (*errmsg)
|
|
{
|
|
pex_free (pex);
|
|
return nullptr;
|
|
}
|
|
|
|
return new module_client (pex, fd_from, fd_to);
|
|
}
|
|
|
|
module_client *
|
|
module_client::open_module_client (location_t loc, const char *o,
|
|
void (*set_repo) (const char *),
|
|
char const *full_program_name)
|
|
{
|
|
module_client *c = nullptr;
|
|
std::string ident;
|
|
std::string name;
|
|
char const *errmsg = nullptr;
|
|
unsigned line = 0;
|
|
|
|
if (o && o[0])
|
|
{
|
|
/* Maybe a local or ipv6 address. */
|
|
name = o;
|
|
auto last = name.find_last_of ('?');
|
|
if (last != name.npos)
|
|
{
|
|
ident = name.substr (last + 1);
|
|
name.erase (last);
|
|
}
|
|
|
|
if (name.size ())
|
|
{
|
|
switch (name[0])
|
|
{
|
|
case '<':
|
|
// <from>to or <>fromto, or <>
|
|
{
|
|
size_t pos = name.find ('>', 1);
|
|
if (pos == std::string::npos)
|
|
pos = name.size ();
|
|
std::string from (name, 1, pos - 1);
|
|
std::string to;
|
|
if (pos != name.size ())
|
|
to.append (name, pos + 1, std::string::npos);
|
|
|
|
int fd_from = -1, fd_to = -1;
|
|
if (from.empty () && to.empty ())
|
|
{
|
|
fd_from = fileno (stdin);
|
|
fd_to = fileno (stdout);
|
|
}
|
|
else
|
|
{
|
|
char *ptr;
|
|
if (!from.empty ())
|
|
{
|
|
/* Sadly str::stoul is not portable. */
|
|
const char *cstr = from.c_str ();
|
|
fd_from = strtoul (cstr, &ptr, 10);
|
|
if (*ptr)
|
|
{
|
|
/* Not a number -- a named pipe. */
|
|
int dir = to.empty ()
|
|
? O_RDWR | O_CLOEXEC : O_RDONLY | O_CLOEXEC;
|
|
fd_from = open (cstr, dir);
|
|
}
|
|
if (to.empty ())
|
|
fd_to = fd_from;
|
|
}
|
|
|
|
if (!from.empty () && fd_from < 0)
|
|
;
|
|
else if (to.empty ())
|
|
;
|
|
else
|
|
{
|
|
const char *cstr = to.c_str ();
|
|
fd_to = strtoul (cstr, &ptr, 10);
|
|
if (*ptr)
|
|
{
|
|
/* Not a number, a named pipe. */
|
|
int dir = from.empty ()
|
|
? O_RDWR | O_CLOEXEC : O_WRONLY | O_CLOEXEC;
|
|
fd_to = open (cstr, dir);
|
|
if (fd_to < 0)
|
|
close (fd_from);
|
|
}
|
|
if (from.empty ())
|
|
fd_from = fd_to;
|
|
}
|
|
}
|
|
|
|
if (fd_from < 0 || fd_to < 0)
|
|
errmsg = "opening";
|
|
else
|
|
c = new module_client (fd_from, fd_to);
|
|
}
|
|
break;
|
|
|
|
case '=':
|
|
// =localsocket
|
|
{
|
|
int fd = -1;
|
|
#if CODY_NETWORKING
|
|
fd = Cody::OpenLocal (&errmsg, name.c_str () + 1);
|
|
#endif
|
|
if (fd >= 0)
|
|
c = new module_client (fd, fd);
|
|
}
|
|
break;
|
|
|
|
case '|':
|
|
// |program and args
|
|
c = spawn_mapper_program (&errmsg, name, full_program_name);
|
|
break;
|
|
|
|
default:
|
|
// file or hostname:port
|
|
{
|
|
auto colon = name.find_last_of (':');
|
|
if (colon != name.npos)
|
|
{
|
|
char const *cptr = name.c_str () + colon;
|
|
char *endp;
|
|
unsigned port = strtoul (cptr + 1, &endp, 10);
|
|
|
|
if (port && endp != cptr + 1 && !*endp)
|
|
{
|
|
name[colon] = 0;
|
|
int fd = 01;
|
|
#if CODY_NETWORKING
|
|
fd = Cody::OpenInet6 (&errmsg, name.c_str (), port);
|
|
#endif
|
|
name[colon] = ':';
|
|
|
|
if (fd >= 0)
|
|
c = new module_client (fd, fd);
|
|
}
|
|
}
|
|
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!c)
|
|
{
|
|
// Make a default in-process client
|
|
bool file = !errmsg && !name.empty ();
|
|
auto r = new module_resolver (!file, true);
|
|
|
|
if (file)
|
|
{
|
|
int fd = open (name.c_str (), O_RDONLY | O_CLOEXEC);
|
|
if (fd < 0)
|
|
errmsg = "opening";
|
|
else
|
|
{
|
|
if (int l = r->read_tuple_file (fd, ident, false))
|
|
{
|
|
if (l > 0)
|
|
line = l;
|
|
errmsg = "reading";
|
|
}
|
|
|
|
close (fd);
|
|
}
|
|
}
|
|
else
|
|
r->set_repo ("gcm.cache");
|
|
|
|
auto *s = new Cody::Server (r);
|
|
c = new module_client (s);
|
|
}
|
|
|
|
#ifdef SIGPIPE
|
|
if (!c->IsDirect ())
|
|
/* We need to ignore sig pipe for a while. */
|
|
c->sigpipe = signal (SIGPIPE, SIG_IGN);
|
|
#endif
|
|
|
|
if (errmsg)
|
|
error_at (loc, line ? G_("failed %s mapper %qs line %u")
|
|
: G_("failed %s mapper %qs"), errmsg, name.c_str (), line);
|
|
|
|
// now wave hello!
|
|
c->Cork ();
|
|
c->Connect (std::string ("GCC"), ident);
|
|
c->ModuleRepo ();
|
|
auto packets = c->Uncork ();
|
|
|
|
auto &connect = packets[0];
|
|
if (connect.GetCode () == Cody::Client::PC_CONNECT)
|
|
c->flags = Cody::Flags (connect.GetInteger ());
|
|
else if (connect.GetCode () == Cody::Client::PC_ERROR)
|
|
error_at (loc, "failed mapper handshake %s", connect.GetString ().c_str ());
|
|
|
|
auto &repo = packets[1];
|
|
if (repo.GetCode () == Cody::Client::PC_PATHNAME)
|
|
set_repo (repo.GetString ().c_str ());
|
|
|
|
return c;
|
|
}
|
|
|
|
void
|
|
module_client::close_module_client (location_t loc, module_client *mapper)
|
|
{
|
|
if (mapper->IsDirect ())
|
|
{
|
|
auto *s = mapper->GetServer ();
|
|
auto *r = s->GetResolver ();
|
|
delete s;
|
|
delete r;
|
|
}
|
|
else
|
|
{
|
|
if (mapper->pex)
|
|
{
|
|
int fd_write = mapper->GetFDWrite ();
|
|
if (fd_write >= 0)
|
|
close (fd_write);
|
|
|
|
int status;
|
|
pex_get_status (mapper->pex, 1, &status);
|
|
|
|
pex_free (mapper->pex);
|
|
mapper->pex = NULL;
|
|
|
|
if (WIFSIGNALED (status))
|
|
error_at (loc, "mapper died by signal %s",
|
|
strsignal (WTERMSIG (status)));
|
|
else if (WIFEXITED (status) && WEXITSTATUS (status) != 0)
|
|
error_at (loc, "mapper exit status %d",
|
|
WEXITSTATUS (status));
|
|
}
|
|
else
|
|
{
|
|
int fd_read = mapper->GetFDRead ();
|
|
close (fd_read);
|
|
}
|
|
|
|
#ifdef SIGPIPE
|
|
// Restore sigpipe
|
|
if (mapper->sigpipe != SIG_IGN)
|
|
signal (SIGPIPE, mapper->sigpipe);
|
|
#endif
|
|
}
|
|
|
|
delete mapper;
|
|
}
|