]> git.sesse.net Git - plocate/blob - bind-mount.cpp
Release plocate 1.1.22.
[plocate] / bind-mount.cpp
1 /* Bind mount detection.  Note: if you change this, change tmpwatch as well.
2
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
6 Public License v.2.
7
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.
11
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.
15
16 Author: Miloslav Trmac <mitr@redhat.com>
17
18 plocate modifications: Copyright (C) 2020 Steinar H. Gunderson.
19 plocate parts and modifications are licensed under the GPLv2 or, at your option,
20 any later version.
21 */
22
23 #include "bind-mount.h"
24
25 #include "conf.h"
26 #include "lib.h"
27
28 #include <atomic>
29 #include <fcntl.h>
30 #include <limits.h>
31 #include <map>
32 #include <poll.h>
33 #include <stddef.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <string>
38 #include <sys/stat.h>
39 #include <sys/time.h>
40 #include <thread>
41
42 using namespace std;
43
44 /* mountinfo handling */
45
46 /* A single mountinfo entry */
47 struct mount {
48         int id, parent_id;
49         unsigned dev_major, dev_minor;
50         string root;
51         string mount_point;
52         string fs_type;
53         string source;
54 };
55
56 /* Path to mountinfo */
57 static const char *mountinfo_path;
58 atomic<bool> mountinfo_updated{ false };
59
60 multimap<pair<int, int>, mount> mount_entries;  // Keyed by device major/minor.
61
62 /* Read a line from F.
63    Return a string, or empty string on error. */
64 static string read_mount_line(FILE *f)
65 {
66         string line;
67
68         for (;;) {
69                 char buf[LINE_MAX];
70
71                 if (fgets(buf, sizeof(buf), f) == nullptr) {
72                         if (feof(f))
73                                 break;
74                         return "";
75                 }
76                 size_t chunk_length = strlen(buf);
77                 if (chunk_length > 0 && buf[chunk_length - 1] == '\n') {
78                         line.append(buf, chunk_length - 1);
79                         break;
80                 }
81                 line.append(buf, chunk_length);
82         }
83         return line;
84 }
85
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)
90 {
91         const char *src = *str;
92         while (*src == ' ' || *src == '\t') {
93                 src++;
94         }
95         if (*src == 0) {
96                 return -1;
97         }
98         string mount_string;
99         for (;;) {
100                 char c = *src;
101
102                 switch (c) {
103                 case 0:
104                 case ' ':
105                 case '\t':
106                         goto done;
107
108                 case '\\':
109                         if (src[1] >= '0' && src[1] <= '7' && src[2] >= '0' && src[2] <= '7' && src[3] >= '0' && src[3] <= '7') {
110                                 unsigned v;
111
112                                 v = ((src[1] - '0') << 6) | ((src[2] - '0') << 3) | (src[3] - '0');
113                                 if (v <= UCHAR_MAX) {
114                                         mount_string.push_back(v);
115                                         src += 4;
116                                         break;
117                                 }
118                         }
119                         /* Else fall through */
120
121                 default:
122                         mount_string.push_back(c);
123                         src++;
124                 }
125         }
126
127 done:
128         *str = src;
129         if (dest != nullptr) {
130                 *dest = move(mount_string);
131         }
132         return 0;
133 }
134
135 /* Read a single entry from F. Return true if succesful. */
136 static bool read_mount_entry(FILE *f, mount *me)
137 {
138         string line = read_mount_line(f);
139         if (line.empty()) {
140                 return false;
141         }
142         size_t offset;
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) {
145                 return false;
146         }
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) {
151                 return false;
152         }
153         bool separator_found;
154         do {
155                 string option;
156                 if (parse_mount_string(&option, &ptr) != 0) {
157                         return false;
158                 }
159                 separator_found = strcmp(option.c_str(), "-") == 0;
160         } while (!separator_found);
161
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) {
165                 return false;
166         }
167         return true;
168 }
169
170 /* Read mount information from mountinfo_path, update mount_entries and
171    num_mount_entries.
172    Return 0 if OK, -1 on error. */
173 static int read_mount_entries(void)
174 {
175         FILE *f = fopen(mountinfo_path, "r");
176         if (f == nullptr) {
177                 return -1;
178         }
179
180         mount_entries.clear();
181
182         mount me;
183         while (read_mount_entry(f, &me)) {
184                 if (conf_debug_pruning) {
185                         /* This is debugging output, don't mark anything for translation */
186                         fprintf(stderr,
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());
190                 }
191                 mount_entries.emplace(make_pair(me.dev_major, me.dev_minor), me);
192         }
193         fclose(f);
194         return 0;
195 }
196
197 /* Bind mount path list maintenace and top-level interface. */
198
199 /* mountinfo_path file descriptor, or -1 */
200 static int mountinfo_fd;
201
202 /* Known bind mount paths */
203 static struct vector<string> bind_mount_paths; /* = { 0, }; */
204
205 /* Next bind_mount_paths entry */
206 static size_t bind_mount_paths_index; /* = 0; */
207
208 /* Rebuild bind_mount_paths */
209 static void rebuild_bind_mount_paths(void)
210 {
211         if (conf_debug_pruning) {
212                 /* This is debugging output, don't mark anything for translation */
213                 fprintf(stderr, "Rebuilding bind_mount_paths:\n");
214         }
215         if (read_mount_entries() != 0) {
216                 return;
217         }
218         if (conf_debug_pruning) {
219                 /* This is debugging output, don't mark anything for translation */
220                 fprintf(stderr, "Matching bind_mount_paths:\n");
221         }
222
223         bind_mount_paths.clear();
224
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.
231                                 continue;
232                         }
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());
240                                 }
241                                 bind_mount_paths.push_back(me.mount_point);
242                                 break;
243                         }
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());
249                                 }
250                                 bind_mount_paths.push_back(me.mount_point);
251                                 break;
252                         }
253                 }
254         }
255         if (conf_debug_pruning) {
256                 /* This is debugging output, don't mark anything for translation */
257                 fprintf(stderr, "...done\n");
258         }
259         string_list_dir_path_sort(&bind_mount_paths);
260 }
261
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)
265 {
266         if (mountinfo_updated.exchange(false)) {  // Atomic test-and-clear.
267                 rebuild_bind_mount_paths();
268                 bind_mount_paths_index = 0;
269         }
270         return string_list_contains_dir_path(&bind_mount_paths,
271                                              &bind_mount_paths_index, path);
272 }
273
274 /* Initialize state for is_bind_mount(), to read data from MOUNTINFO. */
275 void bind_mount_init(const char *mountinfo)
276 {
277         mountinfo_path = mountinfo;
278         mountinfo_fd = open(mountinfo_path, O_RDONLY);
279         if (mountinfo_fd == -1)
280                 return;
281         rebuild_bind_mount_paths();
282
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.
290         //
291         // The thread is forcibly terminated on exit(), so we just let it loop forever.
292         thread poll_thread([&] {
293                 for (;;) {
294                         struct pollfd pfd;
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) {
301                                 perror("poll()");
302                                 exit(1);
303                         }
304                         if ((pfd.revents & POLLPRI) != 0) {
305                                 mountinfo_updated = true;
306                         }
307                 }
308         });
309         poll_thread.detach();
310 }