1/* C++ modules.  Experimental!	-*- c++ -*-
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
23#include "resolver.h"
24// C++
25#include <algorithm>
26#include <memory>
27// C
28#include <cstring>
29// OS
30#include <fcntl.h>
31#include <unistd.h>
32#if 0 // 1 for testing no mmap
33#define MAPPED_READING 0
34#else
35#ifdef IN_GCC
36#if HAVE_MMAP_FILE && _POSIX_MAPPED_FILES > 0
37#define MAPPED_READING 1
38#else
39#define MAPPED_READING 0
40#endif
41#else
42#ifdef HAVE_SYS_MMAN_H
43#include <sys/mman.h>
44#define MAPPED_READING 1
45#else
46#define MAPPED_READING 0
47#endif
48#endif
49#endif
50
51#include <sys/types.h>
52#include <sys/stat.h>
53
54#if !defined (IN_GCC) && !MAPPED_READING
55#define xmalloc(X) malloc(X)
56#endif
57
58#if !HOST_HAS_O_CLOEXEC
59#define O_CLOEXEC 0
60#endif
61
62#ifndef DIR_SEPARATOR
63#define DIR_SEPARATOR '/'
64#endif
65
66module_resolver::module_resolver (bool map, bool xlate)
67  : default_map (map), default_translate (xlate)
68{
69}
70
71module_resolver::~module_resolver ()
72{
73  if (fd_repo >= 0)
74    close (fd_repo);
75}
76
77bool
78module_resolver::set_repo (std::string &&r, bool force)
79{
80  if (force || repo.empty ())
81    {
82      repo = std::move (r);
83      force = true;
84    }
85  return force;
86}
87
88bool
89module_resolver::add_mapping (std::string &&module, std::string &&file,
90			      bool force)
91{
92  auto res = map.emplace (std::move (module), std::move (file));
93  if (res.second)
94    force = true;
95  else if (force)
96    res.first->second = std::move (file);
97
98  return force;
99}
100
101int
102module_resolver::read_tuple_file (int fd, char const *prefix, bool force)
103{
104  struct stat stat;
105  if (fstat (fd, &stat) < 0)
106    return -errno;
107
108  if (!stat.st_size)
109    return 0;
110
111  void *buffer = nullptr;
112#if MAPPED_READING
113  // Just map the file, we're gonna read all of it, so no need for
114  // line buffering
115  buffer = mmap (nullptr, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
116  if (buffer == MAP_FAILED)
117    return -errno;
118  struct Deleter {
119    void operator()(void* p) const { munmap(p, size); }
120    size_t size;
121  };
122  std::unique_ptr<void, Deleter> guard(buffer, Deleter{(size_t)stat.st_size});
123#else
124  buffer = xmalloc (stat.st_size);
125  if (!buffer)
126    return -errno;
127  struct Deleter { void operator()(void* p) const { free(p); } };
128  std::unique_ptr<void, Deleter> guard(buffer);
129  if (read (fd, buffer, stat.st_size) != stat.st_size)
130    return -errno;
131#endif
132
133  size_t prefix_len = prefix ? strlen (prefix) : 0;
134  unsigned lineno = 0;
135
136  for (char const *begin = reinterpret_cast <char const *> (buffer),
137	 *end = begin + stat.st_size, *eol;
138       begin != end; begin = eol + 1)
139    {
140      lineno++;
141      eol = std::find (begin, end, '\n');
142      if (eol == end)
143	// last line has no \n, ignore the line, you lose
144	break;
145
146      auto *pos = begin;
147      bool pfx_search = prefix_len != 0;
148
149    pfx_search:
150      while (*pos == ' ' || *pos == '\t')
151	pos++;
152
153      auto *space = pos;
154      while (*space != '\n' && *space != ' ' && *space != '\t')
155	space++;
156
157      if (pos == space)
158	// at end of line, nothing here
159	continue;
160
161      if (pfx_search)
162	{
163	  if (size_t (space - pos) == prefix_len
164	      && std::equal (pos, space, prefix))
165	    pfx_search = false;
166	  pos = space;
167	  goto pfx_search;
168	}
169
170      std::string module (pos, space);
171      while (*space == ' ' || *space == '\t')
172	space++;
173      std::string file (space, eol);
174
175      if (module[0] == '$')
176	{
177	  if (module == "$root")
178	    set_repo (std::move (file));
179	  else
180	    return lineno;
181	}
182      else
183	{
184	  if (file.empty ())
185	    file = GetCMIName (module);
186	  add_mapping (std::move (module), std::move (file), force);
187	}
188    }
189
190  return 0;
191}
192
193char const *
194module_resolver::GetCMISuffix ()
195{
196  return "gcm";
197}
198
199module_resolver *
200module_resolver::ConnectRequest (Cody::Server *s, unsigned version,
201				 std::string &a, std::string &i)
202{
203  if (!version || version > Cody::Version)
204    s->ErrorResponse ("version mismatch");
205  else if (a != "GCC")
206    // Refuse anything but GCC
207    ErrorResponse (s, std::string ("only GCC supported"));
208  else if (!ident.empty () && ident != i)
209    // Failed ident check
210    ErrorResponse (s, std::string ("bad ident"));
211  else
212    // Success!
213    s->ConnectResponse ("gcc");
214
215  return this;
216}
217
218int
219module_resolver::ModuleRepoRequest (Cody::Server *s)
220{
221  s->PathnameResponse (repo);
222  return 0;
223}
224
225int
226module_resolver::cmi_response (Cody::Server *s, std::string &module)
227{
228  auto iter = map.find (module);
229  if (iter == map.end ())
230    {
231      std::string file = default_map ? GetCMIName (module) : std::string ();
232      auto res = map.emplace (module, file);
233      iter = res.first;
234    }
235
236  if (iter->second.empty ())
237    s->ErrorResponse ("no such module");
238  else
239    s->PathnameResponse (iter->second);
240
241  return 0;
242}
243
244int
245module_resolver::ModuleExportRequest (Cody::Server *s, Cody::Flags,
246				      std::string &module)
247{
248  return cmi_response (s, module);
249}
250
251int
252module_resolver::ModuleImportRequest (Cody::Server *s, Cody::Flags,
253				      std::string &module)
254{
255  return cmi_response (s, module);
256}
257
258int
259module_resolver::IncludeTranslateRequest (Cody::Server *s, Cody::Flags,
260					  std::string &include)
261{
262  auto iter = map.find (include);
263  if (iter == map.end () && default_translate)
264    {
265      // Not found, look for it
266      auto file = GetCMIName (include);
267      struct stat statbuf;
268      bool ok = true;
269
270#if HAVE_FSTATAT
271      int fd_dir = AT_FDCWD;
272      if (!repo.empty ())
273	{
274	  if (fd_repo == -1)
275	    {
276	      fd_repo = open (repo.c_str (),
277			      O_RDONLY | O_CLOEXEC | O_DIRECTORY);
278	      if (fd_repo < 0)
279		fd_repo = -2;
280	    }
281	  fd_dir = fd_repo;
282	}
283
284      if (!repo.empty () && fd_repo < 0)
285	ok = false;
286      else if (fstatat (fd_dir, file.c_str (), &statbuf, 0) < 0
287	       || !S_ISREG (statbuf.st_mode))
288	ok = false;
289#else
290      auto append = repo;
291      append.push_back (DIR_SEPARATOR);
292      append.append (file);
293      if (stat (append.c_str (), &statbuf) < 0
294	  || !S_ISREG (statbuf.st_mode))
295	ok = false;
296#endif
297      if (!ok)
298	// Mark as not present
299	file.clear ();
300      auto res = map.emplace (include, file);
301      iter = res.first;
302    }
303
304  if (iter == map.end () || iter->second.empty ())
305    s->BoolResponse (false);
306  else
307    s->PathnameResponse (iter->second);
308
309  return 0;
310}
311
312/* This handles a client notification to the server that a CMI has been
313   produced for a module.  For this simplified server, we just accept
314   the transaction and respond with "OK".  */
315
316int
317module_resolver::ModuleCompiledRequest (Cody::Server *s, Cody::Flags,
318				      std::string &)
319{
320  s->OKResponse();
321  return 0;
322}
323