| // CODYlib -*- mode:c++ -*- |
| // Copyright (C) 2020 Nathan Sidwell, nathan@acm.org |
| // License: Apache v2.0 |
| |
| // Cody |
| #include "internal.hh" |
| // OS |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #if ((defined (__unix__) \ |
| && defined _POSIX_C_SOURCE \ |
| && (_POSIX_C_SOURCE - 0) >= 200809L) \ |
| || (defined (__Apple__) \ |
| && defined (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) \ |
| && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101000)) |
| // Autoconf test? |
| #define HAVE_FSTATAT 1 |
| #else |
| #define HAVE_FSTATAT 0 |
| #endif |
| |
| // Resolver code |
| |
| #if __windows__ |
| inline bool IsDirSep (char c) |
| { |
| return c == '/' || c == '\\'; |
| } |
| inline bool IsAbsPath (char const *str) |
| { |
| // IIRC windows has the concept of per-drive current directories, |
| // which make drive-using paths confusing. Let's not get into that. |
| return IsDirSep (str) |
| || (((str[0] >= 'A' && str[0] <= 'Z') |
| || (str[0] >= 'a' && str[0] <= 'z'))&& str[1] == ':'); |
| } |
| #else |
| inline bool IsDirSep (char c) |
| { |
| return c == '/'; |
| } |
| inline bool IsAbsPath (char const *str) |
| { |
| return IsDirSep (str[0]); |
| } |
| #endif |
| |
| constexpr char DIR_SEPARATOR = '/'; |
| |
| constexpr char DOT_REPLACE = ','; // Replace . directories |
| constexpr char COLON_REPLACE = '-'; // Replace : (partition char) |
| constexpr char const REPO_DIR[] = "cmi.cache"; |
| |
| namespace Cody { |
| |
| Resolver::~Resolver () |
| { |
| } |
| |
| char const *Resolver::GetCMISuffix () |
| { |
| return "cmi"; |
| } |
| |
| std::string Resolver::GetCMIName (std::string const &module) |
| { |
| std::string result; |
| |
| result.reserve (module.size () + 8); |
| bool is_header = false; |
| bool is_abs = false; |
| |
| if (IsAbsPath (module.c_str ())) |
| is_header = is_abs = true; |
| else if (module.front () == '.' && IsDirSep (module.c_str ()[1])) |
| is_header = true; |
| |
| if (is_abs) |
| { |
| result.push_back ('.'); |
| result.append (module); |
| } |
| else |
| result = std::move (module); |
| |
| if (is_header) |
| { |
| if (!is_abs) |
| result[0] = DOT_REPLACE; |
| |
| /* Map .. to DOT_REPLACE, DOT_REPLACE. */ |
| for (size_t ix = 1; ; ix++) |
| { |
| ix = result.find ('.', ix); |
| if (ix == result.npos) |
| break; |
| if (ix + 2 > result.size ()) |
| break; |
| if (result[ix + 1] != '.') |
| continue; |
| if (!IsDirSep (result[ix - 1])) |
| continue; |
| if (!IsDirSep (result[ix + 2])) |
| continue; |
| result[ix] = DOT_REPLACE; |
| result[ix + 1] = DOT_REPLACE; |
| } |
| } |
| else if (COLON_REPLACE != ':') |
| { |
| // There can only be one colon in a module name |
| auto colon = result.find (':'); |
| if (colon != result.npos) |
| result[colon] = COLON_REPLACE; |
| } |
| |
| if (char const *suffix = GetCMISuffix ()) |
| { |
| result.push_back ('.'); |
| result.append (suffix); |
| } |
| |
| return result; |
| } |
| |
| void Resolver::WaitUntilReady (Server *) |
| { |
| } |
| |
| Resolver *Resolver::ConnectRequest (Server *s, unsigned version, |
| std::string &, std::string &) |
| { |
| if (version > Version) |
| s->ErrorResponse ("version mismatch"); |
| else |
| s->ConnectResponse ("default"); |
| |
| return this; |
| } |
| |
| int Resolver::ModuleRepoRequest (Server *s) |
| { |
| s->PathnameResponse (REPO_DIR); |
| return 0; |
| } |
| |
| // Deprecated resolver functions |
| int Resolver::ModuleExportRequest (Server *s, Flags, std::string &module) |
| { |
| auto cmi = GetCMIName (module); |
| s->PathnameResponse (cmi); |
| return 0; |
| } |
| |
| int Resolver::ModuleImportRequest (Server *s, Flags, std::string &module) |
| { |
| auto cmi = GetCMIName (module); |
| s->PathnameResponse (cmi); |
| return 0; |
| } |
| |
| int Resolver::ModuleCompiledRequest (Server *s, Flags, std::string &) |
| { |
| s->OKResponse (); |
| return 0; |
| } |
| |
| int Resolver::IncludeTranslateRequest (Server *s, Flags, std::string &include) |
| { |
| bool xlate = false; |
| |
| // This is not the most efficient |
| auto cmi = GetCMIName (include); |
| struct stat statbuf; |
| |
| #if HAVE_FSTATAT |
| int fd_dir = open (REPO_DIR, O_RDONLY | O_CLOEXEC | O_DIRECTORY); |
| if (fd_dir >= 0 |
| && fstatat (fd_dir, cmi.c_str (), &statbuf, 0) == 0 |
| && S_ISREG (statbuf.st_mode)) |
| // Sadly can't easily check if this process has read access, |
| // except by trying to open it. |
| xlate = true; |
| if (fd_dir >= 0) |
| close (fd_dir); |
| #else |
| std::string append = REPO_DIR; |
| append.push_back (DIR_SEPARATOR); |
| append.append (cmi); |
| if (stat (append.c_str (), &statbuf) == 0 |
| || S_ISREG (statbuf.st_mode)) |
| xlate = true; |
| #endif |
| |
| if (xlate) |
| s->PathnameResponse (cmi); |
| else |
| s->BoolResponse (false); |
| |
| return 0; |
| } |
| |
| void Resolver::ErrorResponse (Server *server, std::string &&msg) |
| { |
| server->ErrorResponse (msg); |
| } |
| |
| } |