Home | History | Annotate | Line # | Download | only in cp
mapper-client.cc revision 1.1
      1 /* C++ modules.  Experimental!
      2    Copyright (C) 2017-2022 Free Software Foundation, Inc.
      3    Written by Nathan Sidwell <nathan (at) acm.org> while at FaceBook
      4 
      5    This file is part of GCC.
      6 
      7    GCC is free software; you can redistribute it and/or modify it
      8    under the terms of the GNU General Public License as published by
      9    the Free Software Foundation; either version 3, or (at your option)
     10    any later version.
     11 
     12    GCC is distributed in the hope that it will be useful, but
     13    WITHOUT ANY WARRANTY; without even the implied warranty of
     14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     15    General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with GCC; see the file COPYING3.  If not see
     19 <http://www.gnu.org/licenses/>.  */
     20 
     21 #include "config.h"
     22 #if defined (__unix__)
     23 // Solaris11's socket header used bcopy, which we poison.  cody.hh
     24 // will include it later under the above check
     25 #include <sys/socket.h>
     26 #endif
     27 #define INCLUDE_STRING
     28 #define INCLUDE_VECTOR
     29 #define INCLUDE_MAP
     30 #define INCLUDE_MEMORY
     31 #include "system.h"
     32 
     33 #include "line-map.h"
     34 #include "diagnostic-core.h"
     35 #include "mapper-client.h"
     36 #include "intl.h"
     37 
     38 #include "../../c++tools/resolver.h"
     39 
     40 #if !HOST_HAS_O_CLOEXEC
     41 #define O_CLOEXEC 0
     42 #endif
     43 
     44 module_client::module_client (pex_obj *p, int fd_from, int fd_to)
     45   : Client (fd_from, fd_to), pex (p)
     46 {
     47 }
     48 
     49 static module_client *
     50 spawn_mapper_program (char const **errmsg, std::string &name,
     51 		      char const *full_program_name)
     52 {
     53   /* Split writable at white-space.  No space-containing args for
     54      you!  */
     55   // At most every other char could be an argument
     56   char **argv = new char *[name.size () / 2 + 2];
     57   unsigned arg_no = 0;
     58   char *str = new char[name.size ()];
     59   memcpy (str, name.c_str () + 1, name.size ());
     60 
     61   for (auto ptr = str; ; ++ptr)
     62     {
     63       while (*ptr == ' ')
     64 	ptr++;
     65       if (!*ptr)
     66 	break;
     67 
     68       if (!arg_no)
     69 	{
     70 	  /* @name means look in the compiler's install dir.  */
     71 	  if (ptr[0] == '@')
     72 	    ptr++;
     73 	  else
     74 	    full_program_name = nullptr;
     75 	}
     76 
     77       argv[arg_no++] = ptr;
     78       while (*ptr && *ptr != ' ')
     79 	ptr++;
     80       if (!*ptr)
     81 	break;
     82       *ptr = 0;
     83     }
     84   argv[arg_no] = nullptr;
     85 
     86   auto *pex = pex_init (PEX_USE_PIPES, progname, NULL);
     87   FILE *to = pex_input_pipe (pex, false);
     88   name = argv[0];
     89   if (!to)
     90     *errmsg = "connecting input";
     91   else
     92     {
     93       int flags = PEX_SEARCH;
     94 
     95       if (full_program_name)
     96 	{
     97 	  /* Prepend the invoking path, if the mapper is a simple
     98 	     file name.  */
     99 	  size_t dir_len = progname - full_program_name;
    100 	  std::string argv0;
    101 	  argv0.reserve (dir_len + name.size ());
    102 	  argv0.append (full_program_name, dir_len).append (name);
    103 	  name = std::move (argv0);
    104 	  argv[0] = const_cast <char *> (name.c_str ());
    105 	  flags = 0;
    106 	}
    107       int err;
    108       *errmsg = pex_run (pex, flags, argv[0], argv, NULL, NULL, &err);
    109     }
    110   delete[] str;
    111   delete[] argv;
    112 
    113   int fd_from = -1, fd_to = -1;
    114   if (!*errmsg)
    115     {
    116       FILE *from = pex_read_output (pex, false);
    117       if (from && (fd_to = dup (fileno (to))) >= 0)
    118 	fd_from = fileno (from);
    119       else
    120 	*errmsg = "connecting output";
    121       fclose (to);
    122     }
    123 
    124   if (*errmsg)
    125     {
    126       pex_free (pex);
    127       return nullptr;
    128     }
    129 
    130   return new module_client (pex, fd_from, fd_to);
    131 }
    132 
    133 module_client *
    134 module_client::open_module_client (location_t loc, const char *o,
    135 				   void (*set_repo) (const char *),
    136 				   char const *full_program_name)
    137 {
    138   module_client *c = nullptr;
    139   std::string ident;
    140   std::string name;
    141   char const *errmsg = nullptr;
    142   unsigned line = 0;
    143 
    144   if (o && o[0])
    145     {
    146       /* Maybe a local or ipv6 address.  */
    147       name = o;
    148       auto last = name.find_last_of ('?');
    149       if (last != name.npos)
    150 	{
    151 	  ident = name.substr (last + 1);
    152 	  name.erase (last);
    153 	}
    154 
    155       if (name.size ())
    156 	{
    157 	  switch (name[0])
    158 	    {
    159 	    case '<':
    160 	      // <from>to or <>fromto, or <>
    161 	      {
    162 		size_t pos = name.find ('>', 1);
    163 		if (pos == std::string::npos)
    164 		  pos = name.size ();
    165 		std::string from (name, 1, pos - 1);
    166 		std::string to;
    167 		if (pos != name.size ())
    168 		  to.append (name, pos + 1, std::string::npos);
    169 
    170 		int fd_from = -1, fd_to = -1;
    171 		if (from.empty () && to.empty ())
    172 		  {
    173 		    fd_from = fileno (stdin);
    174 		    fd_to = fileno (stdout);
    175 		  }
    176 		else
    177 		  {
    178 		    char *ptr;
    179 		    if (!from.empty ())
    180 		      {
    181 			/* Sadly str::stoul is not portable.  */
    182 			const char *cstr = from.c_str ();
    183 			fd_from = strtoul (cstr, &ptr, 10);
    184 			if (*ptr)
    185 			  {
    186 			    /* Not a number -- a named pipe.  */
    187 			    int dir = to.empty ()
    188 			      ? O_RDWR | O_CLOEXEC : O_RDONLY | O_CLOEXEC;
    189 			    fd_from = open (cstr, dir);
    190 			  }
    191 			if (to.empty ())
    192 			  fd_to = fd_from;
    193 		      }
    194 
    195 		    if (!from.empty () && fd_from < 0)
    196 		      ;
    197 		    else if (to.empty ())
    198 		      ;
    199 		    else
    200 		      {
    201 			const char *cstr = to.c_str ();
    202 			fd_to = strtoul (cstr, &ptr, 10);
    203 			if (*ptr)
    204 			  {
    205 			    /* Not a number, a named pipe.  */
    206 			    int dir = from.empty ()
    207 			      ? O_RDWR | O_CLOEXEC : O_WRONLY | O_CLOEXEC;
    208 			    fd_to = open (cstr, dir);
    209 			    if (fd_to < 0)
    210 			      close (fd_from);
    211 			  }
    212 			if (from.empty ())
    213 			  fd_from = fd_to;
    214 		      }
    215 		  }
    216 
    217 		if (fd_from < 0 || fd_to < 0)
    218 		  errmsg = "opening";
    219 		else
    220 		  c = new module_client (fd_from, fd_to);
    221 	      }
    222 	      break;
    223 
    224 	    case '=':
    225 	      // =localsocket
    226 	      {
    227 		int fd = -1;
    228 #if CODY_NETWORKING
    229 		fd = Cody::OpenLocal (&errmsg, name.c_str () + 1);
    230 #endif
    231 		if (fd >= 0)
    232 		  c = new module_client (fd, fd);
    233 	      }
    234 	      break;
    235 
    236 	    case '|':
    237 	      // |program and args
    238 	      c = spawn_mapper_program (&errmsg, name, full_program_name);
    239 	      break;
    240 
    241 	    default:
    242 	      // file or hostname:port
    243 	      {
    244 		auto colon = name.find_last_of (':');
    245 		if (colon != name.npos)
    246 		  {
    247 		    char const *cptr = name.c_str () + colon;
    248 		    char *endp;
    249 		    unsigned port = strtoul (cptr + 1, &endp, 10);
    250 
    251 		    if (port && endp != cptr + 1 && !*endp)
    252 		      {
    253 			name[colon] = 0;
    254 			int fd = -1;
    255 #if CODY_NETWORKING
    256 			fd = Cody::OpenInet6 (&errmsg, name.c_str (), port);
    257 #endif
    258 			name[colon] = ':';
    259 
    260 			if (fd >= 0)
    261 			  c = new module_client (fd, fd);
    262 		      }
    263 		  }
    264 
    265 	      }
    266 	      break;
    267 	    }
    268 	}
    269     }
    270 
    271   if (!c)
    272     {
    273       // Make a default in-process client
    274       bool file = !errmsg && !name.empty ();
    275       auto r = new module_resolver (!file, true);
    276 
    277       if (file)
    278 	{
    279 	int fd = open (name.c_str (), O_RDONLY | O_CLOEXEC);
    280 	if (fd < 0)
    281 	  errmsg = "opening";
    282 	else
    283 	  {
    284 	    if (int l = r->read_tuple_file (fd, ident, false))
    285 	      {
    286 		if (l > 0)
    287 		  line = l;
    288 		errmsg = "reading";
    289 	      }
    290 
    291 	    close (fd);
    292 	  }
    293 	}
    294       else
    295 	r->set_repo ("gcm.cache");
    296 
    297       auto *s = new Cody::Server (r);
    298       c = new module_client (s);
    299     }
    300 
    301 #ifdef SIGPIPE
    302   if (!c->IsDirect ())
    303     /* We need to ignore sig pipe for a while.  */
    304     c->sigpipe = signal (SIGPIPE, SIG_IGN);
    305 #endif
    306 
    307   if (errmsg)
    308     error_at (loc, line ? G_("failed %s mapper %qs line %u")
    309 	      : G_("failed %s mapper %qs"), errmsg, name.c_str (), line);
    310 
    311   // now wave hello!
    312   c->Cork ();
    313   c->Connect (std::string ("GCC"), ident);
    314   c->ModuleRepo ();
    315   auto packets = c->Uncork ();
    316 
    317   auto &connect = packets[0];
    318   if (connect.GetCode () == Cody::Client::PC_CONNECT)
    319     c->flags = Cody::Flags (connect.GetInteger ());
    320   else if (connect.GetCode () == Cody::Client::PC_ERROR)
    321     error_at (loc, "failed mapper handshake %s", connect.GetString ().c_str ());
    322 
    323   auto &repo = packets[1];
    324   if (repo.GetCode () == Cody::Client::PC_PATHNAME)
    325     set_repo (repo.GetString ().c_str ());
    326 
    327   return c;
    328 }
    329 
    330 void
    331 module_client::close_module_client (location_t loc, module_client *mapper)
    332 {
    333   if (mapper->IsDirect ())
    334     {
    335       auto *s = mapper->GetServer ();
    336       auto *r = s->GetResolver ();
    337       delete s;
    338       delete r;
    339     }
    340   else
    341     {
    342       if (mapper->pex)
    343 	{
    344 	  int fd_write = mapper->GetFDWrite ();
    345 	  if (fd_write >= 0)
    346 	    close (fd_write);
    347 
    348 	  int status;
    349 	  pex_get_status (mapper->pex, 1, &status);
    350 
    351 	  pex_free (mapper->pex);
    352 	  mapper->pex = NULL;
    353 
    354 	  if (WIFSIGNALED (status))
    355 	    error_at (loc, "mapper died by signal %s",
    356 		      strsignal (WTERMSIG (status)));
    357 	  else if (WIFEXITED (status) && WEXITSTATUS (status) != 0)
    358 	    error_at (loc, "mapper exit status %d",
    359 		      WEXITSTATUS (status));
    360 	}
    361       else
    362 	{
    363 	  int fd_read = mapper->GetFDRead ();
    364 	  close (fd_read);
    365 	}
    366 
    367 #ifdef SIGPIPE
    368       // Restore sigpipe
    369       if (mapper->sigpipe != SIG_IGN)
    370 	signal (SIGPIPE, mapper->sigpipe);
    371 #endif
    372     }
    373 
    374   delete mapper;
    375 }
    376