1/* C++ modules. Experimental!
2 Copyright (C) 2017-2024 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 "rich-location.h"
35#include "diagnostic-core.h"
36#include "mapper-client.h"
37#include "intl.h"
38#include "mkdeps.h"
39
40#include "../../c++tools/resolver.h"
41
42#if !HOST_HAS_O_CLOEXEC
43#define O_CLOEXEC 0
44#endif
45
46module_client::module_client (pex_obj *p, int fd_from, int fd_to)
47 : Client (fd_from, fd_to), pex (p)
48{
49}
50
51static module_client *
52spawn_mapper_program (char const **errmsg, std::string &name,
53 char const *full_program_name)
54{
55 /* Split writable at white-space. No space-containing args for
56 you! */
57 // At most every other char could be an argument
58 char **argv = new char *[name.size () / 2 + 2];
59 unsigned arg_no = 0;
60 char *str = new char[name.size ()];
61 memcpy (dest: str, src: name.c_str () + 1, n: name.size ());
62
63 for (auto ptr = str; ; ++ptr)
64 {
65 while (*ptr == ' ')
66 ptr++;
67 if (!*ptr)
68 break;
69
70 if (!arg_no)
71 {
72 /* @name means look in the compiler's install dir. */
73 if (ptr[0] == '@')
74 ptr++;
75 else
76 full_program_name = nullptr;
77 }
78
79 argv[arg_no++] = ptr;
80 while (*ptr && *ptr != ' ')
81 ptr++;
82 if (!*ptr)
83 break;
84 *ptr = 0;
85 }
86 argv[arg_no] = nullptr;
87
88 auto *pex = pex_init (PEX_USE_PIPES, pname: progname, NULL);
89 FILE *to = pex_input_pipe (obj: pex, binary: false);
90 name = argv[0];
91 if (!to)
92 *errmsg = "connecting input";
93 else
94 {
95 int flags = PEX_SEARCH;
96
97 if (full_program_name)
98 {
99 /* Prepend the invoking path, if the mapper is a simple
100 file name. */
101 size_t dir_len = progname - full_program_name;
102 std::string argv0;
103 argv0.reserve (res_arg: dir_len + name.size ());
104 argv0.append (s: full_program_name, n: dir_len).append (str: name);
105 name = std::move (argv0);
106 argv[0] = const_cast <char *> (name.c_str ());
107 flags = 0;
108 }
109 int err;
110 *errmsg = pex_run (obj: pex, flags, executable: argv[0], argv, NULL, NULL, err: &err);
111 }
112 delete[] str;
113 delete[] argv;
114
115 int fd_from = -1, fd_to = -1;
116 if (!*errmsg)
117 {
118 FILE *from = pex_read_output (pex, binary: false);
119 if (from && (fd_to = dup (fileno (to))) >= 0)
120 fd_from = fileno (from);
121 else
122 *errmsg = "connecting output";
123 fclose (stream: to);
124 }
125
126 if (*errmsg)
127 {
128 pex_free (pex);
129 return nullptr;
130 }
131
132 return new module_client (pex, fd_from, fd_to);
133}
134
135module_client *
136module_client::open_module_client (location_t loc, const char *o,
137 class mkdeps *deps,
138 void (*set_repo) (const char *),
139 char const *full_program_name)
140{
141 module_client *c = nullptr;
142 std::string ident;
143 std::string name;
144 char const *errmsg = nullptr;
145 unsigned line = 0;
146
147 if (o && o[0])
148 {
149 /* Maybe a local or ipv6 address. */
150 name = o;
151 auto last = name.find_last_of (c: '?');
152 if (last != name.npos)
153 {
154 ident = name.substr (pos: last + 1);
155 name.erase (pos: last);
156 }
157
158 if (name.size ())
159 {
160 switch (name[0])
161 {
162 case '<':
163 // <from>to or <>fromto, or <>
164 {
165 size_t pos = name.find (c: '>', pos: 1);
166 if (pos == std::string::npos)
167 pos = name.size ();
168 std::string from (name, 1, pos - 1);
169 std::string to;
170 if (pos != name.size ())
171 to.append (str: name, pos: pos + 1, n: std::string::npos);
172
173 int fd_from = -1, fd_to = -1;
174 if (from.empty () && to.empty ())
175 {
176 fd_from = fileno (stdin);
177 fd_to = fileno (stdout);
178 }
179 else
180 {
181 char *ptr;
182 if (!from.empty ())
183 {
184 /* Sadly str::stoul is not portable. */
185 const char *cstr = from.c_str ();
186 fd_from = strtoul (nptr: cstr, endptr: &ptr, base: 10);
187 if (*ptr)
188 {
189 /* Not a number -- a named pipe. */
190 int dir = to.empty ()
191 ? O_RDWR | O_CLOEXEC : O_RDONLY | O_CLOEXEC;
192 fd_from = open (file: cstr, oflag: dir);
193 }
194 if (to.empty ())
195 fd_to = fd_from;
196 }
197
198 if (!from.empty () && fd_from < 0)
199 ;
200 else if (to.empty ())
201 ;
202 else
203 {
204 const char *cstr = to.c_str ();
205 fd_to = strtoul (nptr: cstr, endptr: &ptr, base: 10);
206 if (*ptr)
207 {
208 /* Not a number, a named pipe. */
209 int dir = from.empty ()
210 ? O_RDWR | O_CLOEXEC : O_WRONLY | O_CLOEXEC;
211 fd_to = open (file: cstr, oflag: dir);
212 if (fd_to < 0)
213 close (fd: fd_from);
214 }
215 if (from.empty ())
216 fd_from = fd_to;
217 }
218 }
219
220 if (fd_from < 0 || fd_to < 0)
221 errmsg = "opening";
222 else
223 c = new module_client (fd_from, fd_to);
224 }
225 break;
226
227 case '=':
228 // =localsocket
229 {
230 int fd = -1;
231#if CODY_NETWORKING
232 fd = Cody::OpenLocal (&errmsg, name: name.c_str () + 1);
233#else
234 errmsg = "disabled";
235#endif
236 if (fd >= 0)
237 c = new module_client (fd, fd);
238 }
239 break;
240
241 case '|':
242 // |program and args
243 c = spawn_mapper_program (errmsg: &errmsg, name, full_program_name);
244 break;
245
246 default:
247 // file or hostname:port
248 {
249 auto colon = name.find_last_of (c: ':');
250 if (colon != name.npos)
251 {
252 char const *cptr = name.c_str () + colon;
253 char *endp;
254 unsigned port = strtoul (nptr: cptr + 1, endptr: &endp, base: 10);
255
256 if (port && endp != cptr + 1 && !*endp)
257 {
258 name[colon] = 0;
259 int fd = -1;
260#if CODY_NETWORKING
261 fd = Cody::OpenInet6 (e: &errmsg, name: name.c_str (), port);
262#else
263 errmsg = "disabled";
264#endif
265 name[colon] = ':';
266
267 if (fd >= 0)
268 c = new module_client (fd, fd);
269 }
270 }
271
272 }
273 break;
274 }
275 }
276 }
277
278 if (!c)
279 {
280 // Make a default in-process client
281 bool file = !errmsg && !name.empty ();
282 auto r = new module_resolver (!file, true);
283
284 if (file)
285 {
286 int fd = open (file: name.c_str (), O_RDONLY | O_CLOEXEC);
287 if (fd < 0)
288 errmsg = "opening";
289 else
290 {
291 /* Add the mapper file to the dependency tracking. */
292 if (deps)
293 deps_add_dep (deps, name.c_str ());
294 if (int l = r->read_tuple_file (fd, prefix: ident, force: false))
295 {
296 if (l > 0)
297 line = l;
298 errmsg = "reading";
299 }
300
301 close (fd: fd);
302 }
303 }
304 else
305 r->set_repo (repo: "gcm.cache");
306
307 auto *s = new Cody::Server (r);
308 c = new module_client (s);
309 }
310
311#ifdef SIGPIPE
312 if (!c->IsDirect ())
313 /* We need to ignore sig pipe for a while. */
314 c->sigpipe = signal (SIGPIPE, SIG_IGN);
315#endif
316
317 if (errmsg)
318 error_at (loc, line ? G_("failed %s mapper %qs line %u")
319 : G_("failed %s mapper %qs"), errmsg, name.c_str (), line);
320
321 // now wave hello!
322 c->Cork ();
323 c->Connect (agent: std::string ("GCC"), ident);
324 c->ModuleRepo ();
325 auto packets = c->Uncork ();
326
327 auto &connect = packets[0];
328 if (connect.GetCode () == Cody::Client::PC_CONNECT)
329 c->flags = Cody::Flags (connect.GetInteger ());
330 else if (connect.GetCode () == Cody::Client::PC_ERROR)
331 error_at (loc, "failed mapper handshake %s", connect.GetString ().c_str ());
332
333 auto &repo = packets[1];
334 if (repo.GetCode () == Cody::Client::PC_PATHNAME)
335 set_repo (repo.GetString ().c_str ());
336
337 return c;
338}
339
340void
341module_client::close_module_client (location_t loc, module_client *mapper)
342{
343 if (mapper->IsDirect ())
344 {
345 auto *s = mapper->GetServer ();
346 auto *r = s->GetResolver ();
347 delete s;
348 delete r;
349 }
350 else
351 {
352 if (mapper->pex)
353 {
354 int fd_write = mapper->GetFDWrite ();
355 if (fd_write >= 0)
356 close (fd: fd_write);
357
358 int status;
359 pex_get_status (mapper->pex, count: 1, vector: &status);
360
361 pex_free (mapper->pex);
362 mapper->pex = NULL;
363
364 if (WIFSIGNALED (status))
365 error_at (loc, "mapper died by signal %s",
366 strsignal (WTERMSIG (status)));
367 else if (WIFEXITED (status) && WEXITSTATUS (status) != 0)
368 error_at (loc, "mapper exit status %d",
369 WEXITSTATUS (status));
370 }
371 else
372 {
373 int fd_read = mapper->GetFDRead ();
374 close (fd: fd_read);
375 }
376
377#ifdef SIGPIPE
378 // Restore sigpipe
379 if (mapper->sigpipe != SIG_IGN)
380 signal (SIGPIPE, handler: mapper->sigpipe);
381#endif
382 }
383
384 delete mapper;
385}
386

source code of gcc/cp/mapper-client.cc