Apache Mesos
path.hpp
Go to the documentation of this file.
1 // Licensed under the Apache License, Version 2.0 (the "License");
2 // you may not use this file except in compliance with the License.
3 // You may obtain a copy of the License at
4 //
5 // http://www.apache.org/licenses/LICENSE-2.0
6 //
7 // Unless required by applicable law or agreed to in writing, software
8 // distributed under the License is distributed on an "AS IS" BASIS,
9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 // See the License for the specific language governing permissions and
11 // limitations under the License.
12 
13 #ifndef __STOUT_PATH_HPP__
14 #define __STOUT_PATH_HPP__
15 
16 #include <string>
17 #include <utility>
18 #include <vector>
19 
20 #include <glog/logging.h>
21 
22 #include <stout/attributes.hpp>
23 #include <stout/stringify.hpp>
24 #include <stout/strings.hpp>
25 
26 #include <stout/os/constants.hpp>
27 
28 
29 namespace path {
30 
31 // Converts a fully formed URI to a filename for the platform.
32 //
33 // On all platforms, the optional "file://" prefix is removed if it
34 // exists.
35 //
36 // On Windows, this also converts "/" characters to "\" characters.
37 // The Windows file system APIs don't work with "/" in the filename
38 // when using long paths (although they do work fine if the file
39 // path happens to be short).
40 //
41 // NOTE: Currently, Mesos uses URIs and files somewhat interchangably.
42 // For compatibility, lack of "file://" prefix is not considered an
43 // error.
44 inline std::string from_uri(const std::string& uri)
45 {
46  // Remove the optional "file://" if it exists.
47  // TODO(coffler): Remove the `hostname` component.
48  const std::string path = strings::remove(uri, "file://", strings::PREFIX);
49 
50 #ifndef __WINDOWS__
51  return path;
52 #else
53  return strings::replace(path, "/", "\\");
54 #endif // __WINDOWS__
55 }
56 
57 
58 // Normalizes a given pathname and removes redundant separators and up-level
59 // references.
60 //
61 // Pathnames like `A/B/`, `A///B`, `A/./B`, 'A/foobar/../B` are all normalized
62 // to `A/B`. An empty pathname is normalized to `.`. Up-level entry also cannot
63 // escape a root path, in which case an error will be returned.
64 //
65 // This function follows the rules described in path_resolution(7) for Linux.
66 // However, it only performs pure lexical processing without touching the
67 // actual filesystem.
69  const std::string& path,
70  const char _separator = os::PATH_SEPARATOR)
71 {
72  if (path.empty()) {
73  return ".";
74  }
75 
76  std::vector<std::string> components;
77  const bool isAbs = (path[0] == _separator);
78  const std::string separator(1, _separator);
79 
80  // TODO(jasonlai): Handle pathnames (including absolute paths) in Windows.
81 
82  foreach (const std::string& component, strings::tokenize(path, separator)) {
83  // Skips empty components and "." (current directory).
84  if (component == "." || component.empty()) {
85  continue;
86  }
87 
88  if (component == "..") {
89  if (components.empty()) {
90  if (isAbs) {
91  return Error("Absolute path '" + path + "' tries to escape root");
92  }
93  components.push_back(component);
94  } else if (components.back() == "..") {
95  components.push_back(component);
96  } else {
97  components.pop_back();
98  }
99  } else {
100  components.push_back(component);
101  }
102  }
103 
104  if (components.empty()) {
105  return isAbs ? separator : ".";
106  } else if (isAbs) {
107  // Make sure that a separator is prepended if it is an absolute path.
108  components.insert(components.begin(), "");
109  }
110 
111  return strings::join(separator, components);
112 }
113 
114 
115 // Base case.
116 inline std::string join(
117  const std::string& path1,
118  const std::string& path2,
119  const char _separator = os::PATH_SEPARATOR)
120 {
121  const std::string separator = stringify(_separator);
122  return strings::remove(path1, separator, strings::SUFFIX) +
123  separator +
124  strings::remove(path2, separator, strings::PREFIX);
125 }
126 
127 
128 template <typename... Paths>
129 inline std::string join(
130  const std::string& path1,
131  const std::string& path2,
132  Paths&&... paths)
133 {
134  return join(path1, join(path2, std::forward<Paths>(paths)...));
135 }
136 
137 
138 inline std::string join(
139  const std::vector<std::string>& paths,
140  const char separator = os::PATH_SEPARATOR)
141 {
142  if (paths.empty()) {
143  return "";
144  }
145 
146  std::string result = paths[0];
147  for (size_t i = 1; i < paths.size(); ++i) {
148  result = join(result, paths[i], separator);
149  }
150  return result;
151 }
152 
153 
158 inline bool is_absolute(const std::string& path)
159 {
160 #ifndef __WINDOWS__
162 #else
163  // NOTE: We do not use `PathIsRelative` Windows utility function
164  // here because it does not support long paths.
165  //
166  // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
167  // for details on paths. In short, an absolute path for files on Windows
168  // looks like one of the following:
169  // * "[A-Za-z]:\"
170  // * "[A-Za-z]:/"
171  // * "\\?\..."
172  // * "\\server\..." where "server" is a network host.
173  //
174  // NOLINT(whitespace/line_length)
175 
176  // A uniform naming convention (UNC) name of any format,
177  // always starts with two backslash characters.
178  if (strings::startsWith(path, "\\\\")) {
179  return true;
180  }
181 
182  // A disk designator with a slash, for example "C:\" or "d:/".
183  if (path.length() < 3) {
184  return false;
185  }
186 
187  const char letter = path[0];
188  if (!((letter >= 'A' && letter <= 'Z') ||
189  (letter >= 'a' && letter <= 'z'))) {
190  return false;
191  }
192 
193  std::string colon = path.substr(1, 2);
194  return colon == ":\\" || colon == ":/";
195 #endif // __WINDOWS__
196 }
197 
198 STOUT_DEPRECATED inline bool absolute(const std::string& path)
199 {
200  return is_absolute(path);
201 }
202 
203 } // namespace path {
204 
205 
212 class Path
213 {
214 public:
215  Path() : value(), separator(os::PATH_SEPARATOR) {}
216 
217  explicit Path(
218  const std::string& path, const char path_separator = os::PATH_SEPARATOR)
219  : value(strings::remove(path, "file://", strings::PREFIX)),
220  separator(path_separator)
221  {}
222 
223  // TODO(cmaloney): Add more useful operations such as 'directoryname()',
224  // 'filename()', etc.
225 
249  inline std::string basename() const
250  {
251  if (value.empty()) {
252  return std::string(".");
253  }
254 
255  size_t end = value.size() - 1;
256 
257  // Remove trailing slashes.
258  if (value[end] == separator) {
259  end = value.find_last_not_of(separator, end);
260 
261  // Paths containing only slashes result into "/".
262  if (end == std::string::npos) {
263  return stringify(separator);
264  }
265  }
266 
267  // 'start' should point towards the character after the last slash
268  // that is non trailing.
269  size_t start = value.find_last_of(separator, end);
270 
271  if (start == std::string::npos) {
272  start = 0;
273  } else {
274  start++;
275  }
276 
277  return value.substr(start, end + 1 - start);
278  }
279 
280  // TODO(hausdorff) Make sure this works on Windows for very short path names,
281  // such as "C:\Temp". There is a distinction between "C:" and "C:\", the
282  // former means "current directory of the C drive", while the latter means
283  // "The root of the C drive". Also make sure that UNC paths are handled.
284  // Will probably need to use the Windows path functions for that.
308  inline std::string dirname() const
309  {
310  if (value.empty()) {
311  return std::string(".");
312  }
313 
314  size_t end = value.size() - 1;
315 
316  // Remove trailing slashes.
317  if (value[end] == separator) {
318  end = value.find_last_not_of(separator, end);
319  }
320 
321  // Remove anything trailing the last slash.
322  end = value.find_last_of(separator, end);
323 
324  // Paths containing no slashes result in ".".
325  if (end == std::string::npos) {
326  return std::string(".");
327  }
328 
329  // Paths containing only slashes result in "/".
330  if (end == 0) {
331  return stringify(separator);
332  }
333 
334  // 'end' should point towards the last non slash character
335  // preceding the last slash.
336  end = value.find_last_not_of(separator, end);
337 
338  // Paths containing no non slash characters result in "/".
339  if (end == std::string::npos) {
340  return stringify(separator);
341  }
342 
343  return value.substr(0, end + 1);
344  }
345 
364  {
365  std::string _basename = basename();
366  size_t index = _basename.rfind('.');
367 
368  if (_basename == "." || _basename == ".." || index == std::string::npos) {
369  return None();
370  }
371 
372  return _basename.substr(index);
373  }
374 
375  // Checks whether the path is absolute.
376  inline bool is_absolute() const
377  {
378  return path::is_absolute(value);
379  }
380 
381  // Implicit conversion from Path to string.
382  operator std::string() const
383  {
384  return value;
385  }
386 
387  const std::string& string() const
388  {
389  return value;
390  }
391 
392  // An iterator over path components. Paths are expected to be normalized.
393  //
394  // The effect of using this iterator is to split the path at its
395  // separator and iterate over the different splits. This means in
396  // particular that this class performs no path normalization.
398  {
399  public:
400  using iterator_category = std::forward_iterator_tag;
401  using value_type = std::string;
402  using difference_type = std::string::const_iterator::difference_type;
403  using pointer = std::string::const_iterator::pointer;
404 
405  // We cannot return a reference type (or `const char*` for that
406  // matter) as we neither own string values nor deal with setting up
407  // proper null-terminated output buffers.
408  //
409  // TODO(bbannier): Consider introducing a `string_view`-like class
410  // to wrap path components and use it as a reference type here and as
411  // `value_type`, and as return value for most `Path` member functions above.
412  using reference = std::string;
413 
414  explicit const_iterator(
415  const Path* path_,
416  std::string::const_iterator offset_)
417  : path(path_), offset(offset_) {}
418 
419  // Disallow construction from temporary as we hold a reference to `path`.
420  explicit const_iterator(Path&& path) = delete;
421 
423  {
424  offset = std::find(offset, path->string().end(), path->separator);
425 
426  // If after incrementing we have reached the end return immediately.
427  if (offset == path->string().end()) {
428  return *this;
429  } else {
430  // If we are not at the end we have a separator to skip.
431  ++offset;
432  }
433 
434  return *this;
435  }
436 
438  {
439  const_iterator it = *this;
440  ++(*this);
441  return it;
442  }
443 
444  bool operator==(const const_iterator& other) const
445  {
446  CHECK_EQ(path, other.path)
447  << "Iterators into different paths cannot be compared";
448 
449  return (!path && !other.path) || offset == other.offset;
450  }
451 
452  bool operator!=(const const_iterator& other) const
453  {
454  return !(*this == other);
455  }
456 
458  {
459  auto end = std::find(offset, path->string().end(), path->separator);
460  return reference(offset, end);
461  }
462 
463  private:
464  const Path* path = nullptr;
465  std::string::const_iterator offset;
466  };
467 
469  {
470  return const_iterator(this, string().begin());
471  }
472 
473  const_iterator end() const { return const_iterator(this, string().end()); }
474 
475 private:
476  std::string value;
477  char separator;
478 };
479 
480 
481 inline bool operator==(const Path& left, const Path& right)
482 {
483  return left.string() == right.string();
484 }
485 
486 
487 inline bool operator!=(const Path& left, const Path& right)
488 {
489  return !(left == right);
490 }
491 
492 
493 inline bool operator<(const Path& left, const Path& right)
494 {
495  return left.string() < right.string();
496 }
497 
498 
499 inline bool operator>(const Path& left, const Path& right)
500 {
501  return right < left;
502 }
503 
504 
505 inline bool operator<=(const Path& left, const Path& right)
506 {
507  return !(left > right);
508 }
509 
510 
511 inline bool operator>=(const Path& left, const Path& right)
512 {
513  return !(left < right);
514 }
515 
516 
517 inline std::ostream& operator<<(
518  std::ostream& stream,
519  const Path& path)
520 {
521  return stream << path.string();
522 }
523 
524 
525 namespace path {
526 
527 // Compute path of `path` relative to `base`.
529  const std::string& path_,
530  const std::string& base_,
531  char path_separator = os::PATH_SEPARATOR)
532 {
533  if (path::is_absolute(path_) != path::is_absolute(base_)) {
534  return Error(
535  "Relative paths can only be computed between paths which are either "
536  "both absolute or both relative");
537  }
538 
539  // Normalize both `path` and `base`.
540  Try<std::string> normalized_path = path::normalize(path_);
541  Try<std::string> normalized_base = path::normalize(base_);
542 
543  if (normalized_path.isError()) {
544  return normalized_path;
545  }
546 
547  if (normalized_base.isError()) {
548  return normalized_base;
549  }
550 
551  // If normalized `path` and `base` are identical return `.`.
552  if (*normalized_path == *normalized_base) {
553  return ".";
554  }
555 
556  const Path path(*normalized_path);
557  const Path base(*normalized_base);
558 
559  auto path_it = path.begin();
560  auto base_it = base.begin();
561 
562  const auto path_end = path.end();
563  const auto base_end = base.end();
564 
565  // Strip common path components.
566  for (; path_it != path_end && base_it != base_end && *path_it == *base_it;
567  ++path_it, ++base_it) {
568  }
569 
570  std::vector<std::string> result;
571  result.reserve(
572  std::distance(base_it, base_end) + std::distance(path_it, path_end));
573 
574  // If we have not fully consumed the range of `base` we need to go
575  // up from `path` to reach `base`. Insert ".." into the result.
576  if (base_it != base_end) {
577  for (; base_it != base_end; ++base_it) {
578  result.emplace_back("..");
579  }
580  }
581 
582  // Add remaining path components to the result.
583  for (; path_it != path_end; ++path_it) {
584  result.emplace_back(*path_it);
585  }
586 
587  return join(result, path_separator);
588 }
589 
590 } // namespace path {
591 
592 #endif // __STOUT_PATH_HPP__
Definition: path.hpp:29
bool is_absolute(const std::string &path)
Returns whether the given path is an absolute path.
Definition: path.hpp:158
Definition: errorbase.hpp:36
std::stringstream & join(std::stringstream &stream, const std::string &separator, T &&...args)
Definition: strings.hpp:307
Definition: check.hpp:33
std::string reference
Definition: path.hpp:412
std::string value_type
Definition: path.hpp:401
Definition: format.hpp:45
const_iterator & operator++()
Definition: path.hpp:422
std::string paths()
Definition: os.hpp:138
Definition: posix_signalhandler.hpp:23
event_base * base
std::string join(const std::string &path1, const std::string &path2, const char _separator=os::PATH_SEPARATOR)
Definition: path.hpp:116
Try< Nothing > start(const std::string &name)
Starts the slice with the given name (via &#39;systemctl start <name>&#39;).
std::string remove(const std::string &from, const std::string &substring, Mode mode=ANY)
Definition: strings.hpp:41
bool operator>=(const Path &left, const Path &right)
Definition: path.hpp:511
const_iterator end() const
Definition: path.hpp:473
std::vector< std::string > tokenize(const std::string &s, const std::string &delims, const Option< size_t > &maxTokens=None())
Definition: strings.hpp:139
bool operator>(const Path &left, const Path &right)
Definition: path.hpp:499
Definition: strings.hpp:36
Represents a POSIX or Windows file system path and offers common path manipulations.
Definition: path.hpp:212
bool operator==(const const_iterator &other) const
Definition: path.hpp:444
reference operator*() const
Definition: path.hpp:457
std::string from_uri(const std::string &uri)
Definition: path.hpp:44
STOUT_DEPRECATED bool absolute(const std::string &path)
Definition: path.hpp:198
std::string::const_iterator::difference_type difference_type
Definition: path.hpp:402
std::string replace(const std::string &s, const std::string &from, const std::string &to)
Definition: strings.hpp:113
Path(const std::string &path, const char path_separator=os::PATH_SEPARATOR)
Definition: path.hpp:217
std::string dirname() const
Extracts the component up to, but not including, the final &#39;/&#39;.
Definition: path.hpp:308
Try< std::string > normalize(const std::string &path, const char _separator=os::PATH_SEPARATOR)
Definition: path.hpp:68
Try< bool > remove(const std::string &link, const Handle &parent, uint16_t protocol)
std::ostream & operator<<(std::ostream &stream, const Path &path)
Definition: path.hpp:517
Definition: none.hpp:27
bool operator!=(const const_iterator &other) const
Definition: path.hpp:452
bool isError() const
Definition: try.hpp:78
Option< std::string > extension() const
Returns the file extension of the path, including the dot.
Definition: path.hpp:363
#define STOUT_DEPRECATED
Definition: attributes.hpp:59
bool operator==(const Path &left, const Path &right)
Definition: path.hpp:481
const_iterator begin() const
Definition: path.hpp:468
bool operator<=(const Path &left, const Path &right)
Definition: path.hpp:505
const_iterator operator++(int)
Definition: path.hpp:437
Try< std::string > relative(const std::string &path_, const std::string &base_, char path_separator=os::PATH_SEPARATOR)
Definition: path.hpp:528
std::string::const_iterator::pointer pointer
Definition: path.hpp:403
bool operator<(const Path &left, const Path &right)
Definition: path.hpp:493
const_iterator(const Path *path_, std::string::const_iterator offset_)
Definition: path.hpp:414
Definition: uri.hpp:21
std::string basename() const
Extracts the component following the final &#39;/&#39;.
Definition: path.hpp:249
std::string stringify(int flags)
bool startsWith(const std::string &s, const std::string &prefix)
Definition: strings.hpp:381
const std::string & string() const
Definition: path.hpp:387
Path()
Definition: path.hpp:215
bool operator!=(const Path &left, const Path &right)
Definition: path.hpp:487
bool is_absolute() const
Definition: path.hpp:376
constexpr char PATH_SEPARATOR
Definition: constants.hpp:24
std::forward_iterator_tag iterator_category
Definition: path.hpp:400
Try< std::list< std::string > > find(const std::string &directory, const std::string &pattern)
Definition: find.hpp:37
Definition: path.hpp:397
Definition: strings.hpp:35