-/* Bind mount detection. Note: if you change this, change tmpwatch as well.
+/* Bind mount detection.
Copyright (C) 2005, 2007, 2008, 2012 Red Hat, Inc. All rights reserved.
This copyrighted material is made available to anyone wishing to use, modify,
#include "conf.h"
#include "lib.h"
+#include <algorithm>
#include <atomic>
#include <fcntl.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <thread>
+#include <optional>
+#include <unordered_map>
using namespace std;
string mount_point;
string fs_type;
string source;
+
+ // Derived properties.
+ bool pruned_due_to_fs_type;
+ bool pruned_due_to_path;
+ bool to_remove;
};
/* Path to mountinfo */
-static const char *mountinfo_path;
-atomic<bool> mountinfo_updated{ false };
+static atomic<bool> mountinfo_updated{ false };
-multimap<pair<int, int>, mount> mount_entries; // Keyed by device major/minor.
+// Keyed by device major/minor.
+using MountEntries = multimap<pair<int, int>, mount>;
/* Read a line from F.
Return a string, or empty string on error. */
return true;
}
+static bool find_whether_under_pruned(
+ int id,
+ const unordered_map<int, mount *> &id_to_mount,
+ unordered_map<int, bool> *id_to_pruned_cache)
+{
+ auto cache_it = id_to_pruned_cache->find(id);
+ if (cache_it != id_to_pruned_cache->end()) {
+ return cache_it->second;
+ }
+ auto mount_it = id_to_mount.find(id);
+ if (mount_it == id_to_mount.end()) {
+ // Should not happen.
+ return false;
+ }
+
+ bool result =
+ mount_it->second->pruned_due_to_fs_type ||
+ mount_it->second->pruned_due_to_path ||
+ (mount_it->second->parent_id != 0 &&
+ find_whether_under_pruned(mount_it->second->parent_id,
+ id_to_mount, id_to_pruned_cache));
+ id_to_pruned_cache->emplace(id, result);
+ return result;
+}
+
/* Read mount information from mountinfo_path, update mount_entries and
num_mount_entries.
- Return 0 if OK, -1 on error. */
-static int read_mount_entries(void)
+ Return std::nullopt on error. */
+static optional<MountEntries> read_mount_entries(void)
{
- FILE *f = fopen(mountinfo_path, "r");
+ FILE *f = fopen(MOUNTINFO_PATH, "r");
if (f == nullptr) {
- return -1;
+ return {};
+ }
+
+ MountEntries mount_entries;
+
+ {
+ mount me;
+ while (read_mount_entry(f, &me)) {
+ string fs_type_upper = me.fs_type;
+ for (char &c : fs_type_upper) {
+ c = toupper(c);
+ }
+ me.pruned_due_to_fs_type =
+ (find(conf_prunefs.begin(), conf_prunefs.end(), fs_type_upper) != conf_prunefs.end());
+ size_t prunepath_index = 0; // Search the entire list every time.
+ me.pruned_due_to_path =
+ string_list_contains_dir_path(&conf_prunepaths, &prunepath_index, me.mount_point.c_str());
+ mount_entries.emplace(make_pair(me.dev_major, me.dev_minor), me);
+ if (conf_debug_pruning) {
+ fprintf(stderr,
+ " `%s' (%d on %d) is `%s' of `%s' (%u:%u), type `%s' (pruned_fs=%d, pruned_path=%d)\n",
+ me.mount_point.c_str(), me.id, me.parent_id, me.root.c_str(), me.source.c_str(),
+ me.dev_major, me.dev_minor, me.fs_type.c_str(), me.pruned_due_to_fs_type,
+ me.pruned_due_to_path);
+ }
+ }
+ fclose(f);
}
- mount_entries.clear();
+ // Now propagate pruned status recursively through parent links
+ // (e.g. if /run is tmpfs, then /run/foo should also be pruned).
+ unordered_map<int, mount *> id_to_mount;
+ for (auto &[key, me] : mount_entries) {
+ id_to_mount[me.id] = &me;
+ }
+ unordered_map<int, bool> id_to_pruned_cache;
+ for (auto &[key, me] : mount_entries) {
+ me.to_remove =
+ find_whether_under_pruned(me.id, id_to_mount, &id_to_pruned_cache);
+ if (conf_debug_pruning && me.to_remove) {
+ fprintf(stderr, " `%s' is, or is under, a pruned file system; removing\n",
+ me.mount_point.c_str());
+ }
+ }
- mount me;
- while (read_mount_entry(f, &me)) {
- if (conf_debug_pruning) {
- fprintf(stderr,
- " `%s' (%d on %d) is `%s' of `%s' (%u:%u), type `%s'\n",
- me.mount_point.c_str(), me.id, me.parent_id, me.root.c_str(), me.source.c_str(),
- me.dev_major, me.dev_minor, me.fs_type.c_str());
+ // Now take out those that we won't see due to file system type anyway,
+ // so that we don't inadvertently prefer them to others during bind mount
+ // duplicate detection.
+ for (auto it = mount_entries.begin(); it != mount_entries.end(); ) {
+ if (it->second.to_remove) {
+ it = mount_entries.erase(it);
+ } else {
+ ++it;
}
- mount_entries.emplace(make_pair(me.dev_major, me.dev_minor), me);
}
- fclose(f);
- return 0;
+
+ return mount_entries;
}
/* Bind mount path list maintenace and top-level interface. */
if (conf_debug_pruning) {
fprintf(stderr, "Rebuilding bind_mount_paths:\n");
}
- if (read_mount_entries() != 0) {
+ optional<MountEntries> mount_entries = read_mount_entries();
+ if (!mount_entries.has_value()) {
return;
}
if (conf_debug_pruning) {
bind_mount_paths.clear();
- for (const auto &[dev_id, me] : mount_entries) {
- const auto &[first, second] = mount_entries.equal_range(make_pair(me.dev_major, me.dev_minor));
+ for (const auto &[dev_id, me] : *mount_entries) {
+ const auto &[first, second] = mount_entries->equal_range(make_pair(me.dev_major, me.dev_minor));
for (auto it = first; it != second; ++it) {
const mount &other = it->second;
if (other.id == me.id) {
}
/* Initialize state for is_bind_mount(), to read data from MOUNTINFO. */
-void bind_mount_init(const char *mountinfo)
+void bind_mount_init()
{
- mountinfo_path = mountinfo;
- mountinfo_fd = open(mountinfo_path, O_RDONLY);
+ mountinfo_fd = open(MOUNTINFO_PATH, O_RDONLY);
if (mountinfo_fd == -1)
return;
rebuild_bind_mount_paths();