]> git.sesse.net Git - plocate/blob - bind-mount.cpp
Remove obsolete comment about obstacks.
[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 if it is not nullptr.  Return 0 if OK, -1 on error. */
88 static int parse_mount_string(string *dest, const char **str)
89 {
90         const char *src = *str;
91         while (*src == ' ' || *src == '\t') {
92                 src++;
93         }
94         if (*src == 0) {
95                 return -1;
96         }
97         string mount_string;
98         for (;;) {
99                 char c = *src;
100
101                 switch (c) {
102                 case 0:
103                 case ' ':
104                 case '\t':
105                         goto done;
106
107                 case '\\':
108                         if (src[1] >= '0' && src[1] <= '7' && src[2] >= '0' && src[2] <= '7' && src[3] >= '0' && src[3] <= '7') {
109                                 unsigned v;
110
111                                 v = ((src[1] - '0') << 6) | ((src[2] - '0') << 3) | (src[3] - '0');
112                                 if (v <= UCHAR_MAX) {
113                                         mount_string.push_back(v);
114                                         src += 4;
115                                         break;
116                                 }
117                         }
118                         /* Else fall through */
119
120                 default:
121                         mount_string.push_back(c);
122                         src++;
123                 }
124         }
125
126 done:
127         *str = src;
128         if (dest != nullptr) {
129                 *dest = move(mount_string);
130         }
131         return 0;
132 }
133
134 /* Read a single entry from F. Return true if succesful. */
135 static bool read_mount_entry(FILE *f, mount *me)
136 {
137         string line = read_mount_line(f);
138         if (line.empty()) {
139                 return false;
140         }
141         size_t offset;
142         if (sscanf(line.c_str(), "%d %d %u:%u%zn", &me->id, &me->parent_id, &me->dev_major,
143                    &me->dev_minor, &offset) != 4) {
144                 return false;
145         }
146         const char *ptr = line.c_str() + offset;
147         if (parse_mount_string(&me->root, &ptr) != 0 ||
148             parse_mount_string(&me->mount_point, &ptr) != 0 ||
149             parse_mount_string(nullptr, &ptr) != 0) {
150                 return false;
151         }
152         bool separator_found;
153         do {
154                 string option;
155                 if (parse_mount_string(&option, &ptr) != 0) {
156                         return false;
157                 }
158                 separator_found = strcmp(option.c_str(), "-") == 0;
159         } while (!separator_found);
160
161         if (parse_mount_string(&me->fs_type, &ptr) != 0 ||
162             parse_mount_string(&me->source, &ptr) != 0 ||
163             parse_mount_string(nullptr, &ptr) != 0) {
164                 return false;
165         }
166         return true;
167 }
168
169 /* Read mount information from mountinfo_path, update mount_entries and
170    num_mount_entries.
171    Return 0 if OK, -1 on error. */
172 static int read_mount_entries(void)
173 {
174         FILE *f = fopen(mountinfo_path, "r");
175         if (f == nullptr) {
176                 return -1;
177         }
178
179         mount_entries.clear();
180
181         mount me;
182         while (read_mount_entry(f, &me)) {
183                 if (conf_debug_pruning) {
184                         fprintf(stderr,
185                                 " `%s' (%d on %d) is `%s' of `%s' (%u:%u), type `%s'\n",
186                                 me.mount_point.c_str(), me.id, me.parent_id, me.root.c_str(), me.source.c_str(),
187                                 me.dev_major, me.dev_minor, me.fs_type.c_str());
188                 }
189                 mount_entries.emplace(make_pair(me.dev_major, me.dev_minor), me);
190         }
191         fclose(f);
192         return 0;
193 }
194
195 /* Bind mount path list maintenace and top-level interface. */
196
197 /* mountinfo_path file descriptor, or -1 */
198 static int mountinfo_fd;
199
200 /* Known bind mount paths */
201 static struct vector<string> bind_mount_paths; /* = { 0, }; */
202
203 /* Next bind_mount_paths entry */
204 static size_t bind_mount_paths_index; /* = 0; */
205
206 /* Rebuild bind_mount_paths */
207 static void rebuild_bind_mount_paths(void)
208 {
209         if (conf_debug_pruning) {
210                 fprintf(stderr, "Rebuilding bind_mount_paths:\n");
211         }
212         if (read_mount_entries() != 0) {
213                 return;
214         }
215         if (conf_debug_pruning) {
216                 fprintf(stderr, "Matching bind_mount_paths:\n");
217         }
218
219         bind_mount_paths.clear();
220
221         for (const auto &[dev_id, me] : mount_entries) {
222                 const auto &[first, second] = mount_entries.equal_range(make_pair(me.dev_major, me.dev_minor));
223                 for (auto it = first; it != second; ++it) {
224                         const mount &other = it->second;
225                         if (other.id == me.id) {
226                                 // Don't compare an element to itself.
227                                 continue;
228                         }
229                         // We have two mounts from the same device. Is one a prefix of the other?
230                         // If there are two that are equal, prefer the one with lowest ID.
231                         if (me.root.size() > other.root.size() && me.root.find(other.root) == 0) {
232                                 if (conf_debug_pruning) {
233                                         fprintf(stderr, " => adding `%s' (root `%s' is a child of `%s', mounted on `%s')\n",
234                                                 me.mount_point.c_str(), me.root.c_str(), other.root.c_str(), other.mount_point.c_str());
235                                 }
236                                 bind_mount_paths.push_back(me.mount_point);
237                                 break;
238                         }
239                         if (me.root == other.root && me.id > other.id) {
240                                 if (conf_debug_pruning) {
241                                         fprintf(stderr, " => adding `%s' (duplicate of mount point `%s')\n",
242                                                 me.mount_point.c_str(), other.mount_point.c_str());
243                                 }
244                                 bind_mount_paths.push_back(me.mount_point);
245                                 break;
246                         }
247                 }
248         }
249         if (conf_debug_pruning) {
250                 fprintf(stderr, "...done\n");
251         }
252         string_list_dir_path_sort(&bind_mount_paths);
253 }
254
255 /* Return true if PATH is a destination of a bind mount.
256    (Bind mounts "to self" are ignored.) */
257 bool is_bind_mount(const char *path)
258 {
259         if (mountinfo_updated.exchange(false)) {  // Atomic test-and-clear.
260                 rebuild_bind_mount_paths();
261                 bind_mount_paths_index = 0;
262         }
263         return string_list_contains_dir_path(&bind_mount_paths,
264                                              &bind_mount_paths_index, path);
265 }
266
267 /* Initialize state for is_bind_mount(), to read data from MOUNTINFO. */
268 void bind_mount_init(const char *mountinfo)
269 {
270         mountinfo_path = mountinfo;
271         mountinfo_fd = open(mountinfo_path, O_RDONLY);
272         if (mountinfo_fd == -1)
273                 return;
274         rebuild_bind_mount_paths();
275
276         // mlocate re-polls this for each and every directory it wants to check,
277         // for unclear reasons; it's possible that it's worried about a new recursive
278         // bind mount being made while updatedb is running, causing an infinite loop?
279         // Since it's probably for some good reason, we do the same, but we don't
280         // want the barrage of syscalls. It's not synchronous, but the poll signal
281         // isn't either; there's a slight race condition, but one that could only
282         // be exploited by root.
283         //
284         // The thread is forcibly terminated on exit(), so we just let it loop forever.
285         thread poll_thread([&] {
286                 for (;;) {
287                         struct pollfd pfd;
288                         /* Unfortunately (mount --bind $path $path/subdir) would leave st_dev
289                            unchanged between $path and $path/subdir, so we must keep reparsing
290                            mountinfo_path each time it changes. */
291                         pfd.fd = mountinfo_fd;
292                         pfd.events = POLLPRI;
293                         if (poll(&pfd, 1, /*timeout=*/-1) == -1) {
294                                 perror("poll()");
295                                 exit(1);
296                         }
297                         if ((pfd.revents & POLLPRI) != 0) {
298                                 mountinfo_updated = true;
299                         }
300                 }
301         });
302         poll_thread.detach();
303 }