1 /* Bind mount detection. Note: if you change this, change tmpwatch as well.
3 Copyright (C) 2005, 2007, 2008, 2012 Red Hat, Inc. All rights reserved.
4 This copyrighted material is made available to anyone wishing to use, modify,
5 copy, or redistribute it subject to the terms and conditions of the GNU General
8 This program is distributed in the hope that it will be useful, but WITHOUT ANY
9 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
10 PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 You should have received a copy of the GNU General Public License along with
13 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
14 Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 Author: Miloslav Trmac <mitr@redhat.com>
18 plocate modifications: Copyright (C) 2020 Steinar H. Gunderson.
19 plocate parts and modifications are licensed under the GPLv2 or, at your option,
23 #include "bind-mount.h"
44 /* mountinfo handling */
46 /* A single mountinfo entry */
49 unsigned dev_major, dev_minor;
56 /* Path to mountinfo */
57 static const char *mountinfo_path;
58 atomic<bool> mountinfo_updated{ false };
60 multimap<pair<int, int>, mount> mount_entries; // Keyed by device major/minor.
62 /* Read a line from F.
63 Return a string, or empty string on error. */
64 static string read_mount_line(FILE *f)
71 if (fgets(buf, sizeof(buf), f) == nullptr) {
76 size_t chunk_length = strlen(buf);
77 if (chunk_length > 0 && buf[chunk_length - 1] == '\n') {
78 line.append(buf, chunk_length - 1);
81 line.append(buf, chunk_length);
86 /* Parse a space-delimited entry in STR, decode octal escapes, write it to
87 DEST (allocated from mount_string_obstack) if it is not nullptr.
88 Return 0 if OK, -1 on error. */
89 static int parse_mount_string(string *dest, const char **str)
91 const char *src = *str;
92 while (*src == ' ' || *src == '\t') {
109 if (src[1] >= '0' && src[1] <= '7' && src[2] >= '0' && src[2] <= '7' && src[3] >= '0' && src[3] <= '7') {
112 v = ((src[1] - '0') << 6) | ((src[2] - '0') << 3) | (src[3] - '0');
113 if (v <= UCHAR_MAX) {
114 mount_string.push_back(v);
119 /* Else fall through */
122 mount_string.push_back(c);
129 if (dest != nullptr) {
130 *dest = move(mount_string);
135 /* Read a single entry from F. Return true if succesful. */
136 static bool read_mount_entry(FILE *f, mount *me)
138 string line = read_mount_line(f);
143 if (sscanf(line.c_str(), "%d %d %u:%u%zn", &me->id, &me->parent_id, &me->dev_major,
144 &me->dev_minor, &offset) != 4) {
147 const char *ptr = line.c_str() + offset;
148 if (parse_mount_string(&me->root, &ptr) != 0 ||
149 parse_mount_string(&me->mount_point, &ptr) != 0 ||
150 parse_mount_string(nullptr, &ptr) != 0) {
153 bool separator_found;
156 if (parse_mount_string(&option, &ptr) != 0) {
159 separator_found = strcmp(option.c_str(), "-") == 0;
160 } while (!separator_found);
162 if (parse_mount_string(&me->fs_type, &ptr) != 0 ||
163 parse_mount_string(&me->source, &ptr) != 0 ||
164 parse_mount_string(nullptr, &ptr) != 0) {
170 /* Read mount information from mountinfo_path, update mount_entries and
172 Return 0 if OK, -1 on error. */
173 static int read_mount_entries(void)
175 FILE *f = fopen(mountinfo_path, "r");
180 mount_entries.clear();
183 while (read_mount_entry(f, &me)) {
184 if (conf_debug_pruning) {
185 /* This is debugging output, don't mark anything for translation */
187 " `%s' (%d on %d) is `%s' of `%s' (%u:%u), type `%s'\n",
188 me.mount_point.c_str(), me.id, me.parent_id, me.root.c_str(), me.source.c_str(),
189 me.dev_major, me.dev_minor, me.fs_type.c_str());
191 mount_entries.emplace(make_pair(me.dev_major, me.dev_minor), me);
197 /* Bind mount path list maintenace and top-level interface. */
199 /* mountinfo_path file descriptor, or -1 */
200 static int mountinfo_fd;
202 /* Known bind mount paths */
203 static struct vector<string> bind_mount_paths; /* = { 0, }; */
205 /* Next bind_mount_paths entry */
206 static size_t bind_mount_paths_index; /* = 0; */
208 /* Rebuild bind_mount_paths */
209 static void rebuild_bind_mount_paths(void)
211 if (conf_debug_pruning) {
212 /* This is debugging output, don't mark anything for translation */
213 fprintf(stderr, "Rebuilding bind_mount_paths:\n");
215 if (read_mount_entries() != 0) {
218 if (conf_debug_pruning) {
219 /* This is debugging output, don't mark anything for translation */
220 fprintf(stderr, "Matching bind_mount_paths:\n");
223 bind_mount_paths.clear();
225 for (const auto &[dev_id, me] : mount_entries) {
226 const auto &[first, second] = mount_entries.equal_range(make_pair(me.dev_major, me.dev_minor));
227 for (auto it = first; it != second; ++it) {
228 const mount &other = it->second;
229 if (other.id == me.id) {
230 // Don't compare an element to itself.
233 // We have two mounts from the same device. Is one a prefix of the other?
234 // If there are two that are equal, prefer the one with lowest ID.
235 if (me.root.size() > other.root.size() && me.root.find(other.root) == 0) {
236 if (conf_debug_pruning) {
237 /* This is debugging output, don't mark anything for translation */
238 fprintf(stderr, " => adding `%s' (root `%s' is a child of `%s', mounted on `%s')\n",
239 me.mount_point.c_str(), me.root.c_str(), other.root.c_str(), other.mount_point.c_str());
241 bind_mount_paths.push_back(me.mount_point);
244 if (me.root == other.root && me.id > other.id) {
245 if (conf_debug_pruning) {
246 /* This is debugging output, don't mark anything for translation */
247 fprintf(stderr, " => adding `%s' (duplicate of mount point `%s')\n",
248 me.mount_point.c_str(), other.mount_point.c_str());
250 bind_mount_paths.push_back(me.mount_point);
255 if (conf_debug_pruning) {
256 /* This is debugging output, don't mark anything for translation */
257 fprintf(stderr, "...done\n");
259 string_list_dir_path_sort(&bind_mount_paths);
262 /* Return true if PATH is a destination of a bind mount.
263 (Bind mounts "to self" are ignored.) */
264 bool is_bind_mount(const char *path)
266 if (mountinfo_updated.exchange(false)) { // Atomic test-and-clear.
267 rebuild_bind_mount_paths();
268 bind_mount_paths_index = 0;
270 return string_list_contains_dir_path(&bind_mount_paths,
271 &bind_mount_paths_index, path);
274 /* Initialize state for is_bind_mount(), to read data from MOUNTINFO. */
275 void bind_mount_init(const char *mountinfo)
277 mountinfo_path = mountinfo;
278 mountinfo_fd = open(mountinfo_path, O_RDONLY);
279 if (mountinfo_fd == -1)
281 rebuild_bind_mount_paths();
283 // mlocate re-polls this for each and every directory it wants to check,
284 // for unclear reasons; it's possible that it's worried about a new recursive
285 // bind mount being made while updatedb is running, causing an infinite loop?
286 // Since it's probably for some good reason, we do the same, but we don't
287 // want the barrage of syscalls. It's not synchronous, but the poll signal
288 // isn't either; there's a slight race condition, but one that could only
289 // be exploited by root.
291 // The thread is forcibly terminated on exit(), so we just let it loop forever.
292 thread poll_thread([&] {
295 /* Unfortunately (mount --bind $path $path/subdir) would leave st_dev
296 unchanged between $path and $path/subdir, so we must keep reparsing
297 mountinfo_path each time it changes. */
298 pfd.fd = mountinfo_fd;
299 pfd.events = POLLPRI;
300 if (poll(&pfd, 1, /*timeout=*/-1) == -1) {
304 if ((pfd.revents & POLLPRI) != 0) {
305 mountinfo_updated = true;
309 poll_thread.detach();