blob: b9e02168d550970054fbc54a94fc970196f6a78e [file] [log] [blame]
/* 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
#define INCLUDE_MAP
#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 = -1;
#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;
}