| /* C++ modules. Experimental! -*- c++ -*- | |
| Copyright (C) 2017-2025 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; | |
| } |