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 <android-base/logging.h>
22 #include <poll.h>
23 #include <stdlib.h>
24 
25 #include <charconv>
26 #include <set>
27 #include <unordered_map>
28 
29 #include "incfs.h"
30 #include "path.h"
31 #include "split.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.subdir, 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.rootIndex, 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.rootIndex;
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 auto MountRegistry::Mounts::rootAndSubpathFor(std::string_view path) const
117         -> std::pair<const Root*, std::string> {
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.subdir;
125     const auto pastBindSubdir = path::relativize(bindIt->first, normalPath);
126     const auto& root = roots[index];
127     return {&root, 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, Bind{std::string(), int(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.rootIndex;
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     for (auto bindIt : roots[index].binds) {
152         rootByBindPoint.erase(bindIt);
153     }
154 
155     if (index + 1 == int(roots.size())) {
156         roots.pop_back();
157         // Run a small GC job here as we may be able to remove some obsolete
158         // entries.
159         while (roots.back().empty()) {
160             roots.pop_back();
161         }
162     } else {
163         roots[index].clear();
164     }
165 }
166 
addBind(std::string_view what,std::string_view where)167 void MountRegistry::Mounts::addBind(std::string_view what, std::string_view where) {
168     auto whatAbsolute = path::normalize(what);
169     auto [root, rootIt] = rootIndex(whatAbsolute);
170     if (root < 0) {
171         LOG(ERROR) << "[incfs] No root found for bind from " << what << " to " << where;
172         return;
173     }
174 
175     const auto& currentBind = rootIt->first;
176     auto whatSubpath = path::relativize(currentBind, whatAbsolute);
177     const auto& subdir = rootIt->second.subdir;
178     auto realSubdir = path::join(subdir, whatSubpath);
179     auto it = rootByBindPoint
180                       .insert_or_assign(path::normalize(where), Bind{std::move(realSubdir), root})
181                       .first;
182     roots[root].binds.push_back(it);
183 }
184 
removeBind(std::string_view what)185 void MountRegistry::Mounts::removeBind(std::string_view what) {
186     auto absolute = path::normalize(what);
187     auto [root, rootIt] = rootIndex(absolute);
188     if (root < 0) {
189         LOG(WARNING) << "[incfs] Trying to remove non-existent bind point '" << what << '\'';
190         return;
191     }
192     if (roots[root].path == absolute) {
193         removeRoot(absolute);
194         return;
195     }
196 
197     rootByBindPoint.erase(rootIt);
198     auto& binds = roots[root].binds;
199     auto itBind = std::find(binds.begin(), binds.end(), rootIt);
200     std::swap(binds.back(), *itBind);
201     binds.pop_back();
202 }
203 
MountRegistry(std::string_view filesystem)204 MountRegistry::MountRegistry(std::string_view filesystem)
205       : mFilesystem(filesystem.empty() ? INCFS_NAME : filesystem),
206         mMountInfo(::open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC)) {
207     if (!mMountInfo.ok()) {
208         PLOG(FATAL) << "Failed to open the /proc/mounts file";
209     }
210     mMounts.loadFrom(mMountInfo, mFilesystem);
211 }
212 
213 MountRegistry::~MountRegistry() = default;
214 
rootFor(std::string_view path)215 std::string MountRegistry::rootFor(std::string_view path) {
216     auto lock = ensureUpToDate();
217     return std::string(mMounts.rootFor(path));
218 }
219 
detailsFor(std::string_view path)220 auto MountRegistry::detailsFor(std::string_view path) -> Details {
221     auto lock = ensureUpToDate();
222     auto [root, subpath] = mMounts.rootAndSubpathFor(path);
223     if (!root) {
224         return {};
225     }
226     return {root->path, root->backing, subpath};
227 }
228 
rootAndSubpathFor(std::string_view path)229 std::pair<std::string, std::string> MountRegistry::rootAndSubpathFor(std::string_view path) {
230     auto lock = ensureUpToDate();
231     auto [root, subpath] = mMounts.rootAndSubpathFor(path);
232     if (!root) {
233         return {};
234     }
235     return {std::string(root->path), std::move(subpath)};
236 }
237 
copyMounts()238 MountRegistry::Mounts MountRegistry::copyMounts() {
239     auto lock = ensureUpToDate();
240     return mMounts;
241 }
242 
reload()243 void MountRegistry::reload() {
244     (void)ensureUpToDate();
245 }
246 
ensureUpToDate()247 std::unique_lock<std::mutex> MountRegistry::ensureUpToDate() {
248     pollfd pfd = {.fd = mMountInfo.get(), .events = POLLERR | POLLPRI};
249     const auto res = TEMP_FAILURE_RETRY(poll(&pfd, 1, 0));
250     if (res == 0) {
251         // timeout - nothing to do, up to date
252         return std::unique_lock{mDataMutex};
253     }
254 
255     // reload even if poll() fails: (1) it usually doesn't and (2) it's better to be safe.
256     std::unique_lock lock(mDataMutex);
257     mMounts.loadFrom(mMountInfo, mFilesystem);
258     return lock;
259 }
260 
261 template <class Callback>
forEachLine(base::borrowed_fd fd,Callback && cb)262 static bool forEachLine(base::borrowed_fd fd, Callback&& cb) {
263     static constexpr auto kBufSize = 128 * 1024;
264     char buffer[kBufSize];
265     const char* nextLine = buffer;
266     char* nextRead = buffer;
267     int64_t pos = 0;
268     for (;;) {
269         const auto read = pread(fd.get(), nextRead, std::end(buffer) - nextRead, pos);
270         if (read == 0) {
271             break;
272         }
273         if (read < 0) {
274             if (errno == EINTR) {
275                 continue;
276             }
277             return false;
278         }
279 
280         pos += read;
281         const auto readEnd = nextRead + read;
282         auto chunk = std::string_view{nextLine, size_t(readEnd - nextLine)};
283         do {
284             auto lineEnd = chunk.find('\n');
285             if (lineEnd == chunk.npos) {
286                 break;
287             }
288             cb(chunk.substr(0, lineEnd));
289             chunk.remove_prefix(lineEnd + 1);
290         } while (!chunk.empty());
291 
292         const auto remainingSize = readEnd - chunk.end();
293         memmove(buffer, chunk.end(), remainingSize);
294         nextLine = buffer;
295         nextRead = buffer + remainingSize;
296     }
297 
298     if (nextLine < nextRead) {
299         cb({nextLine, size_t(nextRead - nextLine)});
300     }
301 
302     return true;
303 }
304 
fixBackingDir(std::string_view dir,std::string_view mountDir)305 static std::string fixBackingDir(std::string_view dir, std::string_view mountDir) {
306     if (!dir.starts_with("/proc/"sv)) {
307         return std::string(dir);
308     }
309 
310     // HACK:
311     // Vold uses a secure way of mounting incremental storages, where it passes in
312     // a virtual symlink in /proc/self/fd/... instead of the original path. Unfortunately,
313     // this symlink string gets preserved by the system and we can't resolve it later.
314     // But it's the only place that uses this symlink, and we know exactly how the
315     // mount and backing directory paths look in that case - so can recover one from
316     // another.
317     if (path::endsWith(mountDir, "mount"sv)) {
318         return path::join(path::dirName(mountDir), "backing_store"sv);
319     }
320     // Well, hack didn't work. Still may not return a /proc/ path
321     return {};
322 }
323 
loadFrom(base::borrowed_fd fd,std::string_view filesystem)324 bool MountRegistry::Mounts::loadFrom(base::borrowed_fd fd, std::string_view filesystem) {
325     struct MountInfo {
326         std::string backing;
327         std::set<std::string, std::less<>> roots;
328         std::vector<std::pair<std::string, std::string>> bindPoints;
329     };
330     std::unordered_map<std::string, MountInfo> mountsByGroup(16);
331     std::vector<std::string_view> items(12);
332     const auto parsed = forEachLine(fd, [&](std::string_view line) {
333         if (line.empty()) {
334             return;
335         }
336         Split(line, ' ', &items);
337         if (items.size() < 10) {
338             LOG(WARNING) << "[incfs] bad line in mountinfo: '" << line << '\'';
339             return;
340         }
341         // Note: there are optional fields in the line, starting at [6]. Anything after that should
342         // be indexed from the end.
343         const auto name = items.rbegin()[2];
344         if (!name.starts_with(filesystem)) {
345             return;
346         }
347         const auto groupId = items[2];
348         auto subdir = items[3];
349         auto mountPoint = std::string(items[4]);
350         fixProcPath(mountPoint);
351         mountPoint = path::normalize(mountPoint);
352         auto& mount = mountsByGroup[std::string(groupId)];
353         auto backingDir = fixBackingDir(items.rbegin()[1], mountPoint);
354         if (!backingDir.empty()) {
355             if (mount.backing.empty()) {
356                 mount.backing = std::move(backingDir);
357             } else if (mount.backing != backingDir) {
358                 LOG(WARNING) << "[incfs] root '"
359                              << (!mount.roots.empty() ? *mount.roots.begin() : "<unknown>")
360                              << "' mounted in multiple places with different backing dirs, '"
361                              << mount.backing << "' vs new '" << backingDir
362                              << "'; updating to the new one";
363                 mount.backing = std::move(backingDir);
364             }
365         }
366         if (subdir == "/"sv) {
367             mount.roots.emplace(mountPoint);
368             subdir = ""sv;
369         }
370         mount.bindPoints.emplace_back(std::string(subdir), std::move(mountPoint));
371     });
372 
373     if (!parsed) {
374         return false;
375     }
376 
377     rootByBindPoint.clear();
378     // preserve the allocated capacity, but clear existing data
379     roots.resize(mountsByGroup.size());
380     for (auto& root : roots) {
381         root.binds.clear();
382     }
383 
384     int index = 0;
385     for (auto& [_, mount] : mountsByGroup) {
386         if (mount.roots.empty()) {
387             // the mount has no root, and without root we have no good way of accessing the
388             // control files - so the only valid reaction here is to ignore it
389             LOG(WARNING) << "[incfs] mount '" << mount.backing << "' has no root, but "
390                          << mount.bindPoints.size() << " bind(s), ignoring";
391             continue;
392         }
393         if (mount.backing.empty()) {
394             LOG(WARNING) << "[incfs] mount '" << *mount.roots.begin()
395                          << "' has no backing dir, but " << mount.bindPoints.size()
396                          << " bind(s), ignoring";
397             continue;
398         }
399 
400         Root& root = roots[index];
401         auto& binds = root.binds;
402         binds.reserve(mount.bindPoints.size());
403         for (auto& [subdir, bind] : mount.bindPoints) {
404             auto it = rootByBindPoint
405                               .insert_or_assign(std::move(bind), Bind{std::move(subdir), index})
406                               .first;
407             binds.push_back(it);
408         }
409         root.backing = std::move(mount.backing);
410         fixProcPath(root.backing);
411 
412         // a trick here: given that as of now we either have exactly one root, or the preferred one
413         // is always at the front, let's pick that one here.
414         root.path = std::move(mount.roots.extract(mount.roots.begin()).value());
415         ++index;
416     }
417     roots.resize(index);
418 
419     LOG(INFO) << "[incfs] Loaded " << filesystem << " mount info: " << roots.size()
420               << " instances, " << rootByBindPoint.size() << " mount points";
421     if (base::VERBOSE >= base::GetMinimumLogSeverity()) {
422         for (auto&& [root, backing, binds] : roots) {
423             LOG(INFO) << "[incfs]  '" << root << '\'';
424             LOG(INFO) << "[incfs]    backing: '" << backing << '\'';
425             for (auto&& bind : binds) {
426                 LOG(INFO) << "[incfs]      bind : '" << bind->second.subdir << "'->'" << bind->first
427                           << '\'';
428             }
429         }
430     }
431     return true;
432 }
433 
load(base::borrowed_fd mountInfo,std::string_view filesystem)434 auto MountRegistry::Mounts::load(base::borrowed_fd mountInfo, std::string_view filesystem)
435         -> Mounts {
436     Mounts res;
437     res.loadFrom(mountInfo, filesystem);
438     return res;
439 }
440 
441 } // namespace android::incfs
442