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