1// CODYlib		-*- mode:c++ -*-
2// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
3// License: Apache v2.0
4
5// Cody
6#include "internal.hh"
7// OS
8#include <fcntl.h>
9#include <unistd.h>
10#include <sys/stat.h>
11#include <sys/types.h>
12
13#if ((defined (__unix__)						\
14      && defined _POSIX_C_SOURCE					\
15      && (_POSIX_C_SOURCE - 0) >= 200809L)				\
16     || (defined (__Apple__)						\
17	 && defined (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) 	\
18	 && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101000))
19// Autoconf test?
20#define HAVE_FSTATAT 1
21#else
22#define HAVE_FSTATAT 0
23#endif
24
25// Resolver code
26
27#if __windows__
28inline bool IsDirSep (char c)
29{
30  return c == '/' || c == '\\';
31}
32inline bool IsAbsPath (char const *str)
33{
34  // IIRC windows has the concept of per-drive current directories,
35  // which make drive-using paths confusing.  Let's not get into that.
36  return IsDirSep (str)
37    || (((str[0] >= 'A' && str[0] <= 'Z')
38	 || (str[0] >= 'a' && str[0] <= 'z'))&& str[1] == ':');
39}
40#else
41inline bool IsDirSep (char c)
42{
43  return c == '/';
44}
45inline bool IsAbsPath (char const *str)
46{
47  return IsDirSep (str[0]);
48}
49#endif
50
51constexpr char DIR_SEPARATOR = '/';
52
53constexpr char DOT_REPLACE = ','; // Replace . directories
54constexpr char COLON_REPLACE = '-'; // Replace : (partition char)
55constexpr char const REPO_DIR[] = "cmi.cache";
56
57namespace Cody {
58
59Resolver::~Resolver ()
60{
61}
62
63char const *Resolver::GetCMISuffix ()
64{
65  return "cmi";
66}
67
68std::string Resolver::GetCMIName (std::string const &module)
69{
70  std::string result;
71
72  result.reserve (module.size () + 8);
73  bool is_header = false;
74  bool is_abs = false;
75
76  if (IsAbsPath (module.c_str ()))
77    is_header = is_abs = true;
78  else if (module.front () == '.' && IsDirSep (module.c_str ()[1]))
79    is_header = true;
80
81  if (is_abs)
82    {
83      result.push_back ('.');
84      result.append (module);
85    }
86  else
87    result = std::move (module);
88
89  if (is_header)
90    {
91      if (!is_abs)
92	result[0] = DOT_REPLACE;
93
94      /* Map .. to DOT_REPLACE, DOT_REPLACE.  */
95      for (size_t ix = 1; ; ix++)
96	{
97	  ix = result.find ('.', ix);
98	  if (ix == result.npos)
99	    break;
100	  if (ix + 2 > result.size ())
101	    break;
102	  if (result[ix + 1] != '.')
103	    continue;
104	  if (!IsDirSep (result[ix - 1]))
105	    continue;
106	  if (!IsDirSep (result[ix + 2]))
107	    continue;
108	  result[ix] = DOT_REPLACE;
109	  result[ix + 1] = DOT_REPLACE;
110	}
111    }
112  else if (COLON_REPLACE != ':')
113    {
114      // There can only be one colon in a module name
115      auto colon = result.find (':');
116      if (colon != result.npos)
117	result[colon] = COLON_REPLACE;
118    }
119
120  if (char const *suffix = GetCMISuffix ())
121    {
122      result.push_back ('.');
123      result.append (suffix);
124    }
125
126  return result;
127}
128
129void Resolver::WaitUntilReady (Server *)
130{
131}
132
133Resolver *Resolver::ConnectRequest (Server *s, unsigned version,
134			       std::string &, std::string &)
135{
136  if (version > Version)
137    s->ErrorResponse ("version mismatch");
138  else
139    s->ConnectResponse ("default");
140
141  return this;
142}
143
144int Resolver::ModuleRepoRequest (Server *s)
145{
146  s->PathnameResponse (REPO_DIR);
147  return 0;
148}
149
150// Deprecated resolver functions
151int Resolver::ModuleExportRequest (Server *s, Flags, std::string &module)
152{
153  auto cmi = GetCMIName (module);
154  s->PathnameResponse (cmi);
155  return 0;
156}
157
158int Resolver::ModuleImportRequest (Server *s, Flags, std::string &module)
159{
160  auto cmi = GetCMIName (module);
161  s->PathnameResponse (cmi);
162  return 0;
163}
164
165int Resolver::ModuleCompiledRequest (Server *s, Flags, std::string &)
166{
167  s->OKResponse ();
168  return 0;
169}
170
171int Resolver::IncludeTranslateRequest (Server *s, Flags, std::string &include)
172{
173  bool xlate = false;
174
175  // This is not the most efficient
176  auto cmi = GetCMIName (include);
177  struct stat statbuf;
178
179#if HAVE_FSTATAT
180  int fd_dir = open (REPO_DIR, O_RDONLY | O_CLOEXEC | O_DIRECTORY);
181  if (fd_dir >= 0
182      && fstatat (fd_dir, cmi.c_str (), &statbuf, 0) == 0
183      && S_ISREG (statbuf.st_mode))
184    // Sadly can't easily check if this process has read access,
185    // except by trying to open it.
186    xlate = true;
187  if (fd_dir >= 0)
188    close (fd_dir);
189#else
190  std::string append = REPO_DIR;
191  append.push_back (DIR_SEPARATOR);
192  append.append (cmi);
193  if (stat (append.c_str (), &statbuf) == 0
194      || S_ISREG (statbuf.st_mode))
195    xlate = true;
196#endif
197
198  if (xlate)
199    s->PathnameResponse (cmi);
200  else
201    s->BoolResponse (false);
202
203  return 0;
204}
205
206void Resolver::ErrorResponse (Server *server, std::string &&msg)
207{
208  server->ErrorResponse (msg);
209}
210
211}
212