1/* C++ modules.  Experimental!
2   Copyright (C) 2017-2022 Free Software Foundation, Inc.
3   Written by Nathan Sidwell <nathan@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
17You should have received a copy of the GNU General Public License
18along 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
44module_client::module_client (pex_obj *p, int fd_from, int fd_to)
45  : Client (fd_from, fd_to), pex (p)
46{
47}
48
49static module_client *
50spawn_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
133module_client *
134module_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
330void
331module_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