1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "path.h"
18 
19 #include <android-base/strings.h>
20 #include <android-base/logging.h>
21 
22 #include <algorithm>
23 #include <iterator>
24 #include <limits>
25 #include <memory>
26 
27 #include <dirent.h>
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <unistd.h>
31 
32 using namespace std::literals;
33 
34 namespace android::incremental::path {
35 
operator ()(char l,char r) const36 bool PathCharsLess::operator()(char l, char r) const {
37     int ll = l == '/' ? std::numeric_limits<char>::min() - 1 : l;
38     int rr = r == '/' ? std::numeric_limits<char>::min() - 1 : r;
39     return ll < rr;
40 }
41 
operator ()(std::string_view l,std::string_view r) const42 bool PathLess::operator()(std::string_view l, std::string_view r) const {
43     return std::lexicographical_compare(std::begin(l), std::end(l), std::begin(r), std::end(r),
44                                         PathCharsLess());
45 }
46 
preparePathComponent(std::string_view & path,bool trimAll)47 static void preparePathComponent(std::string_view& path, bool trimAll) {
48     // need to check for double front slash as a single one has a separate meaning in front
49     while (!path.empty() && path.front() == '/' &&
50            (trimAll || (path.size() > 1 && path[1] == '/'))) {
51         path.remove_prefix(1);
52     }
53     // for the back we don't care about double-vs-single slash difference
54     while (path.size() > !trimAll && path.back() == '/') {
55         path.remove_suffix(1);
56     }
57 }
58 
append_next_path(std::string & target,std::string_view path)59 void details::append_next_path(std::string& target, std::string_view path) {
60     preparePathComponent(path, !target.empty());
61     if (path.empty()) {
62         return;
63     }
64     if (!target.empty() && !target.ends_with('/')) {
65         target.push_back('/');
66     }
67     target += path;
68 }
69 
relativize(std::string_view parent,std::string_view nested)70 std::string_view relativize(std::string_view parent, std::string_view nested) {
71     if (!nested.starts_with(parent)) {
72         return nested;
73     }
74     if (nested.size() == parent.size()) {
75         return {};
76     }
77     if (nested[parent.size()] != '/') {
78         return nested;
79     }
80     auto relative = nested.substr(parent.size());
81     while (relative.front() == '/') {
82         relative.remove_prefix(1);
83     }
84     return relative;
85 }
86 
isAbsolute(std::string_view path)87 bool isAbsolute(std::string_view path) {
88     return !path.empty() && path[0] == '/';
89 }
90 
normalize(std::string_view path)91 std::string normalize(std::string_view path) {
92     if (path.empty()) {
93         return {};
94     }
95     if (path.starts_with("../"sv)) {
96         return {};
97     }
98 
99     std::string result;
100     if (isAbsolute(path)) {
101         path.remove_prefix(1);
102     } else {
103         char buffer[PATH_MAX];
104         if (!::getcwd(buffer, sizeof(buffer))) {
105             return {};
106         }
107         result += buffer;
108     }
109 
110     size_t start = 0;
111     size_t end = 0;
112     for (; end != path.npos; start = end + 1) {
113         end = path.find('/', start);
114         // Next component, excluding the separator
115         auto part = path.substr(start, end - start);
116         if (part.empty() || part == "."sv) {
117             continue;
118         }
119         if (part == ".."sv) {
120             if (result.empty()) {
121                 return {};
122             }
123             auto lastPos = result.rfind('/');
124             if (lastPos == result.npos) {
125                 result.clear();
126             } else {
127                 result.resize(lastPos);
128             }
129             continue;
130         }
131         result += '/';
132         result += part;
133     }
134 
135     return result;
136 }
137 
basename(std::string_view path)138 std::string_view basename(std::string_view path) {
139     if (path.empty()) {
140         return {};
141     }
142     if (path == "/"sv) {
143         return "/"sv;
144     }
145     auto pos = path.rfind('/');
146     while (!path.empty() && pos == path.size() - 1) {
147         path.remove_suffix(1);
148         pos = path.rfind('/');
149     }
150     if (pos == path.npos) {
151         return path.empty() ? "/"sv : path;
152     }
153     return path.substr(pos + 1);
154 }
155 
dirname(std::string_view path)156 std::string_view dirname(std::string_view path) {
157     if (path.empty()) {
158         return {};
159     }
160     if (path == "/"sv) {
161         return "/"sv;
162     }
163     const auto pos = path.rfind('/');
164     if (pos == 0) {
165         return "/"sv;
166     }
167     if (pos == path.npos) {
168         return "."sv;
169     }
170     return path.substr(0, pos);
171 }
172 
CStrWrapper(std::string_view sv)173 details::CStrWrapper::CStrWrapper(std::string_view sv) {
174     if (!sv.data()) {
175         mCstr = "";
176     } else if (sv[sv.size()] == '\0') {
177         mCstr = sv.data();
178     } else {
179         mCopy.emplace(sv);
180         mCstr = mCopy->c_str();
181     }
182 }
183 
isEmptyDir(std::string_view dir)184 std::optional<bool> isEmptyDir(std::string_view dir) {
185     const auto d = std::unique_ptr<DIR, decltype(&::closedir)>{::opendir(c_str(dir)), ::closedir};
186     if (!d) {
187         if (errno == EPERM || errno == EACCES) {
188             return std::nullopt;
189         }
190         return false;
191     }
192     while (auto entry = ::readdir(d.get())) {
193         if (entry->d_type != DT_DIR) {
194             return false;
195         }
196         if (entry->d_name != "."sv && entry->d_name != ".."sv) {
197             return false;
198         }
199     }
200     return true;
201 }
202 
startsWith(std::string_view path,std::string_view prefix)203 bool startsWith(std::string_view path, std::string_view prefix) {
204     if (!base::StartsWith(path, prefix)) {
205         return false;
206     }
207     return path.size() == prefix.size() || path[prefix.size()] == '/';
208 }
209 
210 } // namespace android::incremental::path
211