1 // Copyright 2018 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "filepath.h"
6 #include <string.h>
7 #include <sys/stat.h>
8 #include <sys/types.h>
9 #include <vector>
10 #include "utils.h"
11
AppendToString(std::string * target,std::string & source)12 inline void AppendToString(std::string* target, std::string& source) {
13 target->append(source);
14 }
15
DirName()16 FilePath FilePath::DirName() {
17 FilePath new_path(path_);
18
19 std::string::size_type last_separator =
20 new_path.path_.find_last_of(kSeparators, std::string::npos);
21
22 unsigned int letter = 0;
23 if (last_separator == std::string::npos) {
24 // path_ is in the current directory.
25 new_path.path_.resize(letter + 1);
26 } else if (last_separator == letter + 1) {
27 // path_ is in the root directory.
28 new_path.path_.resize(letter + 2);
29 } else if (last_separator == letter + 2 &&
30 IsSeparator(new_path.path_[letter + 1])) {
31 // path_ is in "//" (possibly with a drive letter); leave the double
32 // separator intact indicating alternate root.
33 new_path.path_.resize(letter + 3);
34 } else if (last_separator != 0) {
35 // path_ is somewhere else, trim the basename.
36 new_path.path_.resize(last_separator);
37 }
38 new_path.StripTrailingSeparatorsInternal();
39 if (!new_path.path_.length())
40 new_path.path_ = kCurrentDirectory;
41
42 return new_path;
43 }
44
value() const45 const std::string& FilePath::value() const {
46 return this->path_;
47 }
48
IsSeparator(char character)49 bool FilePath::IsSeparator(char character) {
50 for (size_t i = 0; i < strlen(kSeparators) - 1; ++i) {
51 if (character == kSeparators[i]) {
52 return true;
53 }
54 }
55 return false;
56 }
57
Append(const FilePath & path)58 FilePath FilePath::Append(const FilePath& path) {
59 // return FilePath(this->path_ + path.path_);
60 std::string component = path.value();
61 std::string appended = component;
62 std::string without_nuls;
63
64 std::string::size_type nul_pos = component.find(kStringTerminator);
65 if (nul_pos != std::string::npos) {
66 // without_nuls = component.substr(0, nul_pos);
67 appended = std::string(without_nuls);
68 }
69
70 // DCHECK(appended.length() <= 0 || appended[0] !=
71 // this->separator.c_str()[0]);
72 if (path_.compare(kCurrentDirectory) == 0 && !appended.empty()) {
73 // Append normally doesn't do any normalization, but as a special case,
74 // when appending to kCurrentDirectory, just return a new path for the
75 // component argument. Appending component to kCurrentDirectory would
76 // serve no purpose other than needlessly lengthening the path, and
77 // it's likely in practice to wind up with FilePath objects containing
78 // only kCurrentDirectory when calling DirName on a single relative path
79 // component.
80 return FilePath(appended);
81 }
82 FilePath new_path(path_);
83 new_path.StripTrailingSeparatorsInternal();
84
85 // Don't append a separator if the path is empty (indicating the current
86 // directory) or if the path component is empty (indicating nothing to
87 // append).
88 if (!appended.empty() && !new_path.path_.empty()) {
89 // Don't append a separator if the path still ends with a trailing
90 // separator after stripping (indicating the root directory).
91 char tmp = new_path.path_.back();
92 if (!IsSeparator(tmp)) {
93 // Don't append a separator if the path is just a drive letter.
94 if (FindDriveLetter(new_path.path_) + 1 != new_path.path_.length()) {
95 new_path.path_.append(1, kSeparators[0]);
96 }
97 }
98 }
99 AppendToString(&new_path.path_, appended);
100 return new_path;
101 }
102
StripTrailingSeparatorsInternal()103 void FilePath::StripTrailingSeparatorsInternal() {
104 // If there is no drive letter, start will be 1, which will prevent
105 // stripping the leading separator if there is only one separator. If there
106 // is a drive letter, start will be set appropriately to prevent stripping
107 // the first separator following the drive letter, if a separator
108 // immediately follows the drive letter.
109 std::string::size_type start = FindDriveLetter(path_) + 2;
110
111 std::string::size_type last_stripped = std::string::npos;
112 for (std::string::size_type pos = path_.length();
113 pos > start && IsSeparator(path_[pos - 1]); --pos) {
114 // If the string only has two separators and they're at the beginning,
115 // don't strip them, unless the string began with more than two
116 // separators.
117 if (pos != start + 1 || last_stripped == start + 2 ||
118 !IsSeparator(path_[start - 1])) {
119 path_.resize(pos - 1);
120 last_stripped = pos;
121 }
122 }
123 }
124
FindDriveLetter(std::string path)125 std::string::size_type FindDriveLetter(std::string path) {
126 #if defined(FILE_PATH_USES_DRIVE_LETTERS)
127 // This is dependent on an ASCII-based character set, but that's a
128 // reasonable assumption. iswalpha can be too inclusive here.
129 if (path.length() >= 2 && path[1] == L':' &&
130 ((path[0] >= L'A' && path[0] <= L'Z') ||
131 (path[0] >= L'a' && path[0] <= L'z'))) {
132 return 1;
133 }
134 #endif // FILE_PATH_USES_DRIVE_LETTERS
135 return std::string::npos;
136 }
137
CreateDirectory(FilePath & full_path)138 bool CreateDirectory(FilePath& full_path) {
139 std::vector<FilePath> subpaths;
140
141 // Collect a list of all parent directories.
142 FilePath last_path = full_path;
143 subpaths.push_back(full_path);
144 for (FilePath path = full_path.DirName(); path.value() != last_path.value();
145 path = path.DirName()) {
146 subpaths.push_back(path);
147 last_path = path;
148 }
149
150 // Iterate through the parents and create the missing ones.
151 for (auto i = subpaths.rbegin(); i != subpaths.rend(); ++i) {
152 if (check_dir_existence(i->value().c_str()))
153 continue;
154 if (mkdir(i->value().c_str(), 0700) == 0)
155 continue;
156 // Mkdir failed, but it might have failed with EEXIST, or some other error
157 // due to the the directory appearing out of thin air. This can occur if
158 // two processes are trying to create the same file system tree at the same
159 // time. Check to see if it exists and make sure it is a directory.
160 if (!check_dir_existence(i->value().c_str())) {
161 return false;
162 }
163 }
164 return true;
165 }
166