]> git.sesse.net Git - plocate/blobdiff - bind-mount.cpp
Release plocate 1.1.22.
[plocate] / bind-mount.cpp
index 1390a59fe5eeca7f4005177a891bef1fade9f23a..5b4bb865711402b619769d7ac40c49e30fc11caf 100644 (file)
@@ -1,4 +1,4 @@
-/* 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,
@@ -25,6 +25,7 @@ any later version.
 #include "conf.h"
 #include "lib.h"
 
+#include <algorithm>
 #include <atomic>
 #include <fcntl.h>
 #include <limits.h>
@@ -38,6 +39,8 @@ any later version.
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <thread>
+#include <optional>
+#include <unordered_map>
 
 using namespace std;
 
@@ -51,13 +54,18 @@ struct mount {
        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. */
@@ -166,30 +174,95 @@ static bool read_mount_entry(FILE *f, mount *me)
        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. */
@@ -209,7 +282,8 @@ static void rebuild_bind_mount_paths(void)
        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) {
@@ -218,8 +292,8 @@ static void rebuild_bind_mount_paths(void)
 
        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) {
@@ -265,10 +339,9 @@ bool is_bind_mount(const char *path)
 }
 
 /* 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();