vul_expand_path.cxx
Go to the documentation of this file.
1 // This is core/vul/vul_expand_path.cxx
2 //:
3 // \file
4 // \author fsm
5 
6 #include "vul_expand_path.h"
7 #include <cstdlib>
8 #include <functional>
9 #include <map>
10 #include <utility>
11 #include <vector>
12 
13 #if defined(_WIN32)
14 
15 //:
16 // \note This Windows version only performs some of the operations done by the Unix version.
17 std::string vul_expand_path_internal(std::string path)
18 {
19  if (path == "/")
20  return path; // FIXME: without this something breaks; not sure why.
21 
22  { // main processing and reduction goes here.
23  std::vector<std::string> bits;
24 
25  // split the path into bits. a "bit" is either a single slash or a
26  // sequence of non-slash characters.
27  for (unsigned int i=0; i<path.size(); ) {
28  if (path[i] == '/') {
29  bits.push_back("/");
30  ++i;
31  }
32  else {
33  unsigned int j=i;
34  while (j<path.size() && path[j]!='/')
35  ++j;
36  bits.push_back(std::string(path.c_str()+i, path.c_str()+j));
37  i = j;
38  }
39  }
40 
41  // process the bits
42  while (true)
43  {
44  bool again = false;
45  for (unsigned int i=0; i<bits.size(); ++i)
46  {
47  // remove repeated / unless it is initial '//' as used in windows UNC names
48  if (i>0 && i+1<bits.size() && bits[i] == "/" && bits[i+1] == "/") {
49  bits.erase(bits.begin() + i);
50  again = true;
51  }
52 
53  // remove trailing /
54  if (i+1 == bits.size() && bits[i] == "/") {
55  bits.pop_back();
56  again = true;
57  }
58 
59  // collapse foo/.. into /
60  if (i+2<bits.size() && !(bits[i]=="/") && bits[i+1]=="/" && bits[i+2]=="..") {
61  bits.erase(bits.begin() + i+2); // ..
62  bits.erase(bits.begin() + i); // foo
63  again = true;
64  }
65 
66  // remove /. altogether
67  if (i+1<bits.size() && bits[i]=="/" && bits[i+1]==".") {
68  bits.erase(bits.begin() + i+1); // /
69  bits.erase(bits.begin() + i); // .
70  again = true;
71  }
72  }
73  if (!again)
74  break;
75  }
76 
77  // recompose the path from its bits
78  path = "";
79  for (unsigned int i=0; i<bits.size(); ++i)
80  path += bits[i];
81 #ifdef DEBUG
82  std::cerr << "recomposed : " << path << '\n';
83 #endif
84  }
85 
86  // no more ideas
87  return path;
88 }
89 
90 //:
91 // Note: this Windows version in similar to the uncached Unix version
92 std::string vul_expand_path(std::string path)
93 {
94  return vul_expand_path_internal(path);
95 }
96 
97 
98 #if VXL_USE_WIN_WCHAR_T
99 //:
100 // \note This Windows version only performs some of the operations done by the Unix version.
101 std::wstring vul_expand_path_internal(std::wstring path)
102 {
103  if (path == L"/")
104  return path; // FIXME: without this something breaks; not sure why.
105 
106  { // main processing and reduction goes here.
107  std::vector<std::wstring> bits;
108 
109  // split the path into bits. a "bit" is either a single slash or a
110  // sequence of non-slash characters.
111  for (unsigned int i=0; i<path.size(); ) {
112  if (path[i] == L'/') {
113  bits.push_back(L"/");
114  ++i;
115  }
116  else {
117  unsigned int j=i;
118  while (j<path.size() && path[j]!=L'/')
119  ++j;
120  bits.push_back(std::wstring(path.c_str()+i, path.c_str()+j));
121  i = j;
122  }
123  }
124 
125  // process the bits
126  while (true)
127  {
128  bool again = false;
129  for (unsigned int i=0; i<bits.size(); ++i)
130  {
131  // remove repeated / unless it is initial '//' as used in windows UNC names
132  if (i>0 && i+1<bits.size() && bits[i] == L"/" && bits[i+1] == L"/") {
133  bits.erase(bits.begin() + i);
134  again = true;
135  }
136 
137  // remove trailing /
138  if (i+1 == bits.size() && bits[i] == L"/") {
139  bits.pop_back();
140  again = true;
141  }
142 
143  // collapse foo/.. into /
144  if (i+2<bits.size() && !(bits[i]==L"/") && bits[i+1]==L"/" && bits[i+2]==L"..") {
145  bits.erase(bits.begin() + i+2); // ..
146  bits.erase(bits.begin() + i); // foo
147  again = true;
148  }
149 
150  // remove /. altogether
151  if (i+1<bits.size() && bits[i]==L"/" && bits[i+1]==L".") {
152  bits.erase(bits.begin() + i+1); // /
153  bits.erase(bits.begin() + i); // .
154  again = true;
155  }
156  }
157  if (!again)
158  break;
159  }
160 
161  // recompose the path from its bits
162  path = L"";
163  for (unsigned int i=0; i<bits.size(); ++i)
164  path += bits[i];
165 #ifdef DEBUG
166  std::cerr << "recomposed : " << path << '\n';
167 #endif
168  }
169 
170  // no more ideas
171  return path;
172 }
173 
174 //:
175 // Note: this Windows version in similar to the uncached Unix version
176 std::wstring vul_expand_path(std::wstring path)
177 {
178  return vul_expand_path_internal(path);
179 }
180 
181 #endif //VXL_USE_WIN_WCHAR_T
182 
183 #else // #if defined(_WIN32)
184 
185 #ifdef _MSC_VER
186 # include <vcl_msvc_warnings.h>
187 #endif
188 #include <sys/types.h>
189 #include <sys/stat.h>
190 #include <dirent.h>
191 #include <unistd.h>
192 
193 static
194 std::string vul_expand_path_internal(std::string path)
195 {
196  if (path == "/")
197  return path; // FIXME: without this something breaks; not sure why.
198 
199  // expand ~/ or just ~
200  if ((path.size()>=2 && path[0] == '~' && path[1] == '/') || path == "~") {
201  char const *HOME = std::getenv("HOME");
202  if (! HOME) {
203  // urgh!
204  HOME = "/HOME";
205  }
206  path = std::string(HOME) + std::string(path.c_str() + 1);
207  }
208 
209  // if the path doesn't begin with a / then it must be relative to the
210  // current directory.
211  if (path.size()>=1 && path[0] != '/')
212  path = std::string("./") + path;
213 
214  // expand ./ or just .
215  if ((path.size()>=2 && path[0] == '.' && path[1] == '/') || path == ".") {
216  char cwd[4096];
217  if( getcwd(cwd, sizeof cwd) == nullptr ) {
218  path = "<error: current working directory path > 4096 characters>";
219  } else {
220  path = std::string(cwd) + path.substr(1);
221  }
222  }
223 
224  { // main processing and reduction goes here.
225  std::vector<std::string> bits;
226 
227  // split the path into bits. a "bit" is either a single slash or a
228  // sequence of non-slash characters.
229  for (unsigned int i=0; i<path.size(); ) {
230  if (path[i] == '/') {
231  bits.emplace_back("/");
232  ++i;
233  }
234  else {
235  unsigned int j=i;
236  while (j<path.size() && path[j]!='/')
237  ++j;
238  bits.emplace_back(path.c_str()+i, path.c_str()+j);
239  i = j;
240  }
241  }
242 
243  // process the bits
244  while (true)
245  {
246  bool again = false;
247  for (unsigned int i=0; i<bits.size(); ++i)
248  {
249  // remove repeated /
250  if (i+1<bits.size() && bits[i] == "/" && bits[i+1] == "/") {
251  bits.erase(bits.begin() + i);
252  again = true;
253  }
254 
255  // remove trailing /
256  if (i+1 == bits.size() && bits[i] == "/") {
257  bits.pop_back();
258  again = true;
259  }
260 
261  // collapse foo/.. into /
262  if (i+2<bits.size() && !(bits[i]=="/") && bits[i+1]=="/" && bits[i+2]=="..") {
263  bits.erase(bits.begin() + i+2); // ..
264  bits.erase(bits.begin() + i); // foo
265  again = true;
266  }
267 
268  // remove /. altogether
269  if (i+1<bits.size() && bits[i]=="/" && bits[i+1]==".") {
270  bits.erase(bits.begin() + i+1); // /
271  bits.erase(bits.begin() + i); // .
272  again = true;
273  }
274  }
275  if (!again)
276  break;
277  }
278 
279  // recompose the path from its bits
280  path = "";
281  for (const auto & bit : bits)
282  path += bit;
283 #ifdef DEBUG
284  std::cerr << "recomposed : " << path << '\n';
285 #endif
286  }
287 
288  // look for symbolic links to expand
289  for (unsigned int i=1; i<=path.size(); ++i)
290  {
291  if (i==path.size() || path[i] == '/')
292  {
293  std::string sub(path.c_str(), path.c_str() + i);
294  char buf[4096];
295  int len = readlink(sub.c_str(), buf, sizeof buf);
296  if (len != -1)
297  {
298  // it's a symlink. we should expand it and recurse.
299 #ifdef DEBUG
300  std::cerr << "before expansion : " << path << '\n';
301 #endif
302  if (buf[0] == '/') {
303  // the target of the link starts with '/' so must be an
304  // absolute path : ...foo/bar/etc... => buf/etc...
305  path = std::string(buf, buf+len) + std::string(path.c_str() + i);
306  }
307  else
308  {
309  // the target is relative to the symlink's directory.
310  int j=i-1;
311  while (j>=0 && path[j] != '/')
312  --j;
313  if (j>=0) {
314  // found another slash : ...foo/bar/etc... where bar is the symlink.
315  std::string a = std::string(path.c_str(), path.c_str()+j+1);
316  std::string b = std::string(buf, buf+len);
317  std::string c = std::string(path.c_str() + i, path.c_str() + path.size());
318 #ifdef DEBUG
319  std::cerr << "a = " << a << "\nb = " << b << "\nc = " << c << '\n';
320 #endif
321  path = a + b + c;
322  }
323  else {
324  // gurgle. only one slash. must be : /bar/etc where bar is the symlink.
325  path = std::string(buf, buf+len) + std::string(path.c_str() + i);
326  }
327  }
328 
329 #ifdef DEBUG
330  std::cerr << "after expansion : " << path << '\n';
331 #endif
332  return vul_expand_path_internal(path);
333  }
334  }
335  }
336 
337  // no more ideas
338  return path;
339 }
340 
341 typedef std::map<std::string, std::string, std::less<std::string> > map_t;
342 
343 std::string vul_expand_path(std::string path)
344 {
345  // create the cache.
346  static map_t the_map;
347 
348  // look for the given path in the map.
349  auto i = the_map.find(path);
350 
351  if (i == the_map.end()) {
352  // not in the map, so compute it :
353  std::string mapped = vul_expand_path_internal(path);
354  // cache it :
355  i = the_map.insert(map_t::value_type(path, mapped)).first;
356  }
357 
358  //
359  return (*i).second;
360 }
361 
362 std::string vul_expand_path_uncached(std::string path)
363 {
364  return vul_expand_path_internal(std::move(path));
365 }
366 
367 #endif // _WIN32
Contains two functions to compute expanded form of a given path.
std::string vul_expand_path_uncached(std::string path)
Expand given path.
std::map< std::string, std::string, std::less< std::string > > map_t
std::string vul_expand_path(std::string path)
Expand given path.