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 #define LOG_TAG "incfs-mounts"
18
19 #include "MountRegistry.h"
20
21 #include "incfs.h"
22 #include "path.h"
23 #include "split.h"
24
25 #include <android-base/logging.h>
26
27 #include <charconv>
28 #include <unordered_map>
29
30 #include <poll.h>
31 #include <stdlib.h>
32
33 using namespace std::literals;
34
35 namespace android::incfs {
36
37 // /proc/self/mountinfo may have some special characters in paths replaced with their
38 // octal codes in the following pattern: '\xxx', e.g. \040 for space character.
39 // This function translates those patterns back into corresponding characters.
fixProcPath(std::string & path)40 static void fixProcPath(std::string& path) {
41 static const auto kPrefix = "\\"sv;
42 static const auto kPatternLength = 4;
43 auto pos = std::search(path.begin(), path.end(), kPrefix.begin(), kPrefix.end());
44 if (pos == path.end()) {
45 return;
46 }
47 auto dest = pos;
48 do {
49 if (path.end() - pos < kPatternLength || !std::equal(kPrefix.begin(), kPrefix.end(), pos)) {
50 *dest++ = *pos++;
51 } else {
52 int charCode;
53 auto res = std::from_chars(&*(pos + kPrefix.size()), &*(pos + kPatternLength), charCode,
54 8);
55 if (res.ec == std::errc{}) {
56 *dest++ = char(charCode);
57 } else {
58 // Didn't convert, let's keep it as is.
59 dest = std::copy(pos, pos + kPatternLength, dest);
60 pos += kPatternLength;
61 }
62 }
63 } while (pos != path.end());
64 path.erase(dest, path.end());
65 }
66
binds() const67 std::vector<std::pair<std::string_view, std::string_view>> MountRegistry::Mounts::Mount::binds()
68 const {
69 std::vector<std::pair<std::string_view, std::string_view>> result;
70 result.reserve(mBase->binds.size());
71 for (auto it : mBase->binds) {
72 result.emplace_back(it->second.first, it->first);
73 }
74 return result;
75 }
76
swap(MountRegistry::Mounts & other)77 void MountRegistry::Mounts::swap(MountRegistry::Mounts& other) {
78 roots.swap(other.roots);
79 rootByBindPoint.swap(other.rootByBindPoint);
80 }
81
clear()82 void MountRegistry::Mounts::clear() {
83 roots.clear();
84 rootByBindPoint.clear();
85 }
86
rootIndex(std::string_view path) const87 std::pair<int, MountRegistry::BindMap::const_iterator> MountRegistry::Mounts::rootIndex(
88 std::string_view path) const {
89 auto it = rootByBindPoint.lower_bound(path);
90 if (it != rootByBindPoint.end() && it->first == path) {
91 return {it->second.second, it};
92 }
93 if (it != rootByBindPoint.begin()) {
94 --it;
95 if (path::startsWith(path, it->first) && path.size() > it->first.size()) {
96 const auto index = it->second.second;
97 if (index >= int(roots.size()) || roots[index].empty()) {
98 LOG(ERROR) << "[incfs] Root for path '" << path << "' #" << index
99 << " is not valid";
100 return {-1, {}};
101 }
102 return {index, it};
103 }
104 }
105 return {-1, {}};
106 }
107
rootFor(std::string_view path) const108 std::string_view MountRegistry::Mounts::rootFor(std::string_view path) const {
109 auto [index, _] = rootIndex(path::normalize(path));
110 if (index < 0) {
111 return {};
112 }
113 return roots[index].path;
114 }
115
rootAndSubpathFor(std::string_view path) const116 std::pair<std::string_view, std::string> MountRegistry::Mounts::rootAndSubpathFor(
117 std::string_view path) const {
118 auto normalPath = path::normalize(path);
119 auto [index, bindIt] = rootIndex(normalPath);
120 if (index < 0) {
121 return {};
122 }
123
124 const auto& bindSubdir = bindIt->second.first;
125 const auto pastBindSubdir = path::relativize(bindIt->first, normalPath);
126 const auto& root = roots[index];
127 return {root.path, path::join(bindSubdir, pastBindSubdir)};
128 }
129
addRoot(std::string_view root,std::string_view backingDir)130 void MountRegistry::Mounts::addRoot(std::string_view root, std::string_view backingDir) {
131 const auto index = roots.size();
132 auto absolute = path::normalize(root);
133 auto it = rootByBindPoint.insert_or_assign(absolute, std::pair{std::string(), index}).first;
134 roots.push_back({std::move(absolute), path::normalize(backingDir), {it}});
135 }
136
removeRoot(std::string_view root)137 void MountRegistry::Mounts::removeRoot(std::string_view root) {
138 auto absolute = path::normalize(root);
139 auto it = rootByBindPoint.find(absolute);
140 if (it == rootByBindPoint.end()) {
141 LOG(WARNING) << "[incfs] Trying to remove non-existent root '" << root << '\'';
142 return;
143 }
144 const auto index = it->second.second;
145 if (index >= int(roots.size())) {
146 LOG(ERROR) << "[incfs] Root '" << root << "' has index " << index
147 << " out of bounds (total roots count is " << roots.size();
148 return;
149 }
150
151 if (index + 1 == int(roots.size())) {
152 roots.pop_back();
153 // Run a small GC job here as we may be able to remove some obsolete
154 // entries.
155 while (roots.back().empty()) {
156 roots.pop_back();
157 }
158 } else {
159 roots[index].clear();
160 }
161 rootByBindPoint.erase(it);
162 }
163
moveBind(std::string_view src,std::string_view dest)164 void MountRegistry::Mounts::moveBind(std::string_view src, std::string_view dest) {
165 auto srcAbsolute = path::normalize(src);
166 auto destAbsolute = path::normalize(dest);
167 if (srcAbsolute == destAbsolute) {
168 return;
169 }
170
171 auto [root, rootIt] = rootIndex(srcAbsolute);
172 if (root < 0) {
173 LOG(ERROR) << "[incfs] No root found for bind move from " << src << " to " << dest;
174 return;
175 }
176
177 if (roots[root].path == srcAbsolute) {
178 // moving the whole root
179 roots[root].path = destAbsolute;
180 }
181
182 // const_cast<> here is safe as we're erasing that element on the next line.
183 const auto newRootIt = rootByBindPoint
184 .insert_or_assign(std::move(destAbsolute),
185 std::pair{std::move(const_cast<std::string&>(
186 rootIt->second.first)),
187 root})
188 .first;
189 rootByBindPoint.erase(rootIt);
190 const auto bindIt = std::find(roots[root].binds.begin(), roots[root].binds.end(), rootIt);
191 *bindIt = newRootIt;
192 }
193
addBind(std::string_view what,std::string_view where)194 void MountRegistry::Mounts::addBind(std::string_view what, std::string_view where) {
195 auto whatAbsolute = path::normalize(what);
196 auto [root, rootIt] = rootIndex(whatAbsolute);
197 if (root < 0) {
198 LOG(ERROR) << "[incfs] No root found for bind from " << what << " to " << where;
199 return;
200 }
201
202 const auto& currentBind = rootIt->first;
203 auto whatSubpath = path::relativize(currentBind, whatAbsolute);
204 const auto& subdir = rootIt->second.first;
205 auto realSubdir = path::join(subdir, whatSubpath);
206 auto it = rootByBindPoint
207 .insert_or_assign(path::normalize(where),
208 std::pair{std::move(realSubdir), root})
209 .first;
210 roots[root].binds.push_back(it);
211 }
212
removeBind(std::string_view what)213 void MountRegistry::Mounts::removeBind(std::string_view what) {
214 auto absolute = path::normalize(what);
215 auto [root, rootIt] = rootIndex(absolute);
216 if (root < 0) {
217 LOG(WARNING) << "[incfs] Trying to remove non-existent bind point '" << what << '\'';
218 return;
219 }
220 if (roots[root].path == absolute) {
221 removeRoot(absolute);
222 return;
223 }
224
225 rootByBindPoint.erase(rootIt);
226 auto& binds = roots[root].binds;
227 auto itBind = std::find(binds.begin(), binds.end(), rootIt);
228 std::swap(binds.back(), *itBind);
229 binds.pop_back();
230 }
231
MountRegistry(std::string_view filesystem)232 MountRegistry::MountRegistry(std::string_view filesystem)
233 : mFilesystem(filesystem.empty() ? INCFS_NAME : filesystem),
234 mMountInfo(::open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC)) {
235 if (!mMountInfo.ok()) {
236 PLOG(FATAL) << "Failed to open the /proc/mounts file";
237 }
238 mMounts.loadFrom(mMountInfo, mFilesystem);
239 }
240
241 MountRegistry::~MountRegistry() = default;
242
rootFor(std::string_view path)243 std::string MountRegistry::rootFor(std::string_view path) {
244 auto lock = ensureUpToDate();
245 return std::string(mMounts.rootFor(path));
246 }
rootAndSubpathFor(std::string_view path)247 std::pair<std::string, std::string> MountRegistry::rootAndSubpathFor(std::string_view path) {
248 auto lock = ensureUpToDate();
249 auto [root, subpath] = mMounts.rootAndSubpathFor(path);
250 return {std::string(root), std::move(subpath)};
251 }
252
copyMounts()253 MountRegistry::Mounts MountRegistry::copyMounts() {
254 auto lock = ensureUpToDate();
255 return mMounts;
256 }
257
reload()258 void MountRegistry::reload() {
259 (void)ensureUpToDate();
260 }
261
ensureUpToDate()262 std::unique_lock<std::mutex> MountRegistry::ensureUpToDate() {
263 pollfd pfd = {.fd = mMountInfo.get(), .events = POLLERR | POLLPRI};
264 const auto res = TEMP_FAILURE_RETRY(poll(&pfd, 1, 0));
265 if (res == 0) {
266 // timeout - nothing to do, up to date
267 return std::unique_lock{mDataMutex};
268 }
269
270 // reload even if poll() fails: (1) it usually doesn't and (2) it's better to be safe.
271 std::unique_lock lock(mDataMutex);
272 mMounts.loadFrom(mMountInfo, mFilesystem);
273 return lock;
274 }
275
276 template <class Callback>
forEachLine(base::borrowed_fd fd,Callback && cb)277 static bool forEachLine(base::borrowed_fd fd, Callback&& cb) {
278 static constexpr auto kBufSize = 128 * 1024;
279 char buffer[kBufSize];
280 const char* nextLine = buffer;
281 char* nextRead = buffer;
282 int64_t pos = 0;
283 for (;;) {
284 const auto read = pread(fd.get(), nextRead, std::end(buffer) - nextRead, pos);
285 if (read == 0) {
286 break;
287 }
288 if (read < 0) {
289 if (errno == EINTR) {
290 continue;
291 }
292 return false;
293 }
294
295 pos += read;
296 const auto readEnd = nextRead + read;
297 auto chunk = std::string_view{nextLine, size_t(readEnd - nextLine)};
298 do {
299 auto lineEnd = chunk.find('\n');
300 if (lineEnd == chunk.npos) {
301 break;
302 }
303 cb(chunk.substr(0, lineEnd));
304 chunk.remove_prefix(lineEnd + 1);
305 } while (!chunk.empty());
306
307 const auto remainingSize = readEnd - chunk.end();
308 memmove(buffer, chunk.end(), remainingSize);
309 nextLine = buffer;
310 nextRead = buffer + remainingSize;
311 }
312
313 if (nextLine < nextRead) {
314 cb({nextLine, size_t(nextRead - nextLine)});
315 }
316
317 return true;
318 }
319
loadFrom(base::borrowed_fd fd,std::string_view filesystem)320 bool MountRegistry::Mounts::loadFrom(base::borrowed_fd fd, std::string_view filesystem) {
321 struct MountInfo {
322 std::string root;
323 std::string backing;
324 std::vector<std::pair<std::string, std::string>> bindPoints;
325 };
326 std::unordered_map<std::string, MountInfo> mountsByGroup(16);
327 std::vector<std::string_view> items(12);
328 const auto parsed = forEachLine(fd, [&](std::string_view line) {
329 if (line.empty()) {
330 return;
331 }
332 Split(line, ' ', &items);
333 if (items.size() < 10) {
334 LOG(WARNING) << "[incfs] bad line in mountinfo: '" << line << '\'';
335 return;
336 }
337 // Note: there are optional fields in the line, starting at [6]. Anything after that should
338 // be indexed from the end.
339 const auto name = items.rbegin()[2];
340 if (!name.starts_with(filesystem)) {
341 return;
342 }
343 const auto groupId = items[2];
344 auto subdir = items[3];
345 auto mountPoint = std::string(items[4]);
346 fixProcPath(mountPoint);
347 mountPoint = path::normalize(mountPoint);
348 auto& mount = mountsByGroup[std::string(groupId)];
349 if (subdir == "/"sv) {
350 if (mount.root.empty()) {
351 mount.root.assign(mountPoint);
352 mount.backing.assign(items.rbegin()[1]);
353 fixProcPath(mount.backing);
354 } else {
355 LOG(WARNING) << "[incfs] incfs root '" << mount.root
356 << "' mounted in multiple places, ignoring later mount '" << mountPoint
357 << '\'';
358 }
359 subdir = ""sv;
360 }
361 mount.bindPoints.emplace_back(std::string(subdir), std::move(mountPoint));
362 });
363
364 if (!parsed) {
365 return false;
366 }
367
368 rootByBindPoint.clear();
369 // preserve the allocated capacity, but clean existing data
370 roots.resize(mountsByGroup.size());
371 for (auto& root : roots) {
372 root.binds.clear();
373 }
374
375 int index = 0;
376 for (auto& [_, mount] : mountsByGroup) {
377 Root& root = roots[index];
378 auto& binds = root.binds;
379 binds.reserve(mount.bindPoints.size());
380 for (auto& [subdir, bind] : mount.bindPoints) {
381 auto it =
382 rootByBindPoint
383 .insert_or_assign(std::move(bind), std::pair(std::move(subdir), index))
384 .first;
385 binds.push_back(it);
386 }
387 root.path = std::move(mount.root);
388 root.backing = std::move(mount.backing);
389 ++index;
390 }
391
392 LOG(INFO) << "[incfs] Loaded " << filesystem << " mount info: " << roots.size()
393 << " instances, " << rootByBindPoint.size() << " mount points";
394 if (base::VERBOSE >= base::GetMinimumLogSeverity()) {
395 for (auto&& [root, backing, binds] : roots) {
396 LOG(INFO) << "[incfs] '" << root << '\'';
397 LOG(INFO) << "[incfs] backing: '" << backing << '\'';
398 for (auto&& bind : binds) {
399 LOG(INFO) << "[incfs] bind : '" << bind->second.first << "'->'" << bind->first
400 << '\'';
401 }
402 }
403 }
404 return true;
405 }
406
load(base::borrowed_fd mountInfo,std::string_view filesystem)407 auto MountRegistry::Mounts::load(base::borrowed_fd mountInfo, std::string_view filesystem)
408 -> Mounts {
409 Mounts res;
410 res.loadFrom(mountInfo, filesystem);
411 return res;
412 }
413
414 } // namespace android::incfs
415