| /* C++ modules. Experimental! -*- c++ -*- |
| Copyright (C) 2017-2022 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" |
| |
| #include "resolver.h" |
| // C++ |
| #include <algorithm> |
| #include <memory> |
| // C |
| #include <cstring> |
| // OS |
| #include <fcntl.h> |
| #include <unistd.h> |
| #if 0 // 1 for testing no mmap |
| #define MAPPED_READING 0 |
| #else |
| #ifdef IN_GCC |
| #if HAVE_MMAP_FILE && _POSIX_MAPPED_FILES > 0 |
| #define MAPPED_READING 1 |
| #else |
| #define MAPPED_READING 0 |
| #endif |
| #else |
| #ifdef HAVE_SYS_MMAN_H |
| #include <sys/mman.h> |
| #define MAPPED_READING 1 |
| #else |
| #define MAPPED_READING 0 |
| #endif |
| #endif |
| #endif |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| |
| #if !defined (IN_GCC) && !MAPPED_READING |
| #define xmalloc(X) malloc(X) |
| #endif |
| |
| #if !HOST_HAS_O_CLOEXEC |
| #define O_CLOEXEC 0 |
| #endif |
| |
| #ifndef DIR_SEPARATOR |
| #define DIR_SEPARATOR '/' |
| #endif |
| |
| module_resolver::module_resolver (bool map, bool xlate) |
| : default_map (map), default_translate (xlate) |
| { |
| } |
| |
| module_resolver::~module_resolver () |
| { |
| if (fd_repo >= 0) |
| close (fd_repo); |
| } |
| |
| bool |
| module_resolver::set_repo (std::string &&r, bool force) |
| { |
| if (force || repo.empty ()) |
| { |
| repo = std::move (r); |
| force = true; |
| } |
| return force; |
| } |
| |
| bool |
| module_resolver::add_mapping (std::string &&module, std::string &&file, |
| bool force) |
| { |
| auto res = map.emplace (std::move (module), std::move (file)); |
| if (res.second) |
| force = true; |
| else if (force) |
| res.first->second = std::move (file); |
| |
| return force; |
| } |
| |
| int |
| module_resolver::read_tuple_file (int fd, char const *prefix, bool force) |
| { |
| struct stat stat; |
| if (fstat (fd, &stat) < 0) |
| return -errno; |
| |
| if (!stat.st_size) |
| return 0; |
| |
| void *buffer = nullptr; |
| #if MAPPED_READING |
| // Just map the file, we're gonna read all of it, so no need for |
| // line buffering |
| buffer = mmap (nullptr, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); |
| if (buffer == MAP_FAILED) |
| return -errno; |
| struct Deleter { |
| void operator()(void* p) const { munmap(p, size); } |
| size_t size; |
| }; |
| std::unique_ptr<void, Deleter> guard(buffer, Deleter{(size_t)stat.st_size}); |
| #else |
| buffer = xmalloc (stat.st_size); |
| if (!buffer) |
| return -errno; |
| struct Deleter { void operator()(void* p) const { free(p); } }; |
| std::unique_ptr<void, Deleter> guard(buffer); |
| if (read (fd, buffer, stat.st_size) != stat.st_size) |
| return -errno; |
| #endif |
| |
| size_t prefix_len = prefix ? strlen (prefix) : 0; |
| unsigned lineno = 0; |
| |
| for (char const *begin = reinterpret_cast <char const *> (buffer), |
| *end = begin + stat.st_size, *eol; |
| begin != end; begin = eol + 1) |
| { |
| lineno++; |
| eol = std::find (begin, end, '\n'); |
| if (eol == end) |
| // last line has no \n, ignore the line, you lose |
| break; |
| |
| auto *pos = begin; |
| bool pfx_search = prefix_len != 0; |
| |
| pfx_search: |
| while (*pos == ' ' || *pos == '\t') |
| pos++; |
| |
| auto *space = pos; |
| while (*space != '\n' && *space != ' ' && *space != '\t') |
| space++; |
| |
| if (pos == space) |
| // at end of line, nothing here |
| continue; |
| |
| if (pfx_search) |
| { |
| if (size_t (space - pos) == prefix_len |
| && std::equal (pos, space, prefix)) |
| pfx_search = false; |
| pos = space; |
| goto pfx_search; |
| } |
| |
| std::string module (pos, space); |
| while (*space == ' ' || *space == '\t') |
| space++; |
| std::string file (space, eol); |
| |
| if (module[0] == '$') |
| { |
| if (module == "$root") |
| set_repo (std::move (file)); |
| else |
| return lineno; |
| } |
| else |
| { |
| if (file.empty ()) |
| file = GetCMIName (module); |
| add_mapping (std::move (module), std::move (file), force); |
| } |
| } |
| |
| return 0; |
| } |
| |
| char const * |
| module_resolver::GetCMISuffix () |
| { |
| return "gcm"; |
| } |
| |
| module_resolver * |
| module_resolver::ConnectRequest (Cody::Server *s, unsigned version, |
| std::string &a, std::string &i) |
| { |
| if (!version || version > Cody::Version) |
| s->ErrorResponse ("version mismatch"); |
| else if (a != "GCC") |
| // Refuse anything but GCC |
| ErrorResponse (s, std::string ("only GCC supported")); |
| else if (!ident.empty () && ident != i) |
| // Failed ident check |
| ErrorResponse (s, std::string ("bad ident")); |
| else |
| // Success! |
| s->ConnectResponse ("gcc"); |
| |
| return this; |
| } |
| |
| int |
| module_resolver::ModuleRepoRequest (Cody::Server *s) |
| { |
| s->PathnameResponse (repo); |
| return 0; |
| } |
| |
| int |
| module_resolver::cmi_response (Cody::Server *s, std::string &module) |
| { |
| auto iter = map.find (module); |
| if (iter == map.end ()) |
| { |
| std::string file = default_map ? GetCMIName (module) : std::string (); |
| auto res = map.emplace (module, file); |
| iter = res.first; |
| } |
| |
| if (iter->second.empty ()) |
| s->ErrorResponse ("no such module"); |
| else |
| s->PathnameResponse (iter->second); |
| |
| return 0; |
| } |
| |
| int |
| module_resolver::ModuleExportRequest (Cody::Server *s, Cody::Flags, |
| std::string &module) |
| { |
| return cmi_response (s, module); |
| } |
| |
| int |
| module_resolver::ModuleImportRequest (Cody::Server *s, Cody::Flags, |
| std::string &module) |
| { |
| return cmi_response (s, module); |
| } |
| |
| int |
| module_resolver::IncludeTranslateRequest (Cody::Server *s, Cody::Flags, |
| std::string &include) |
| { |
| auto iter = map.find (include); |
| if (iter == map.end () && default_translate) |
| { |
| // Not found, look for it |
| auto file = GetCMIName (include); |
| struct stat statbuf; |
| bool ok = true; |
| |
| #if HAVE_FSTATAT |
| int fd_dir = AT_FDCWD; |
| if (!repo.empty ()) |
| { |
| if (fd_repo == -1) |
| { |
| fd_repo = open (repo.c_str (), |
| O_RDONLY | O_CLOEXEC | O_DIRECTORY); |
| if (fd_repo < 0) |
| fd_repo = -2; |
| } |
| fd_dir = fd_repo; |
| } |
| |
| if (!repo.empty () && fd_repo < 0) |
| ok = false; |
| else if (fstatat (fd_dir, file.c_str (), &statbuf, 0) < 0 |
| || !S_ISREG (statbuf.st_mode)) |
| ok = false; |
| #else |
| auto append = repo; |
| append.push_back (DIR_SEPARATOR); |
| append.append (file); |
| if (stat (append.c_str (), &statbuf) < 0 |
| || !S_ISREG (statbuf.st_mode)) |
| ok = false; |
| #endif |
| if (!ok) |
| // Mark as not present |
| file.clear (); |
| auto res = map.emplace (include, file); |
| iter = res.first; |
| } |
| |
| if (iter == map.end () || iter->second.empty ()) |
| s->BoolResponse (false); |
| else |
| s->PathnameResponse (iter->second); |
| |
| return 0; |
| } |
| |
| /* This handles a client notification to the server that a CMI has been |
| produced for a module. For this simplified server, we just accept |
| the transaction and respond with "OK". */ |
| |
| int |
| module_resolver::ModuleCompiledRequest (Cody::Server *s, Cody::Flags, |
| std::string &) |
| { |
| s->OKResponse(); |
| return 0; |
| } |