]> git.sesse.net Git - plocate/blob - bind-mount.cpp
Remove some comments left over from mlocate.
[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                         fprintf(stderr,
186                                 " `%s' (%d on %d) is `%s' of `%s' (%u:%u), type `%s'\n",
187                                 me.mount_point.c_str(), me.id, me.parent_id, me.root.c_str(), me.source.c_str(),
188                                 me.dev_major, me.dev_minor, me.fs_type.c_str());
189                 }
190                 mount_entries.emplace(make_pair(me.dev_major, me.dev_minor), me);
191         }
192         fclose(f);
193         return 0;
194 }
195
196 /* Bind mount path list maintenace and top-level interface. */
197
198 /* mountinfo_path file descriptor, or -1 */
199 static int mountinfo_fd;
200
201 /* Known bind mount paths */
202 static struct vector<string> bind_mount_paths; /* = { 0, }; */
203
204 /* Next bind_mount_paths entry */
205 static size_t bind_mount_paths_index; /* = 0; */
206
207 /* Rebuild bind_mount_paths */
208 static void rebuild_bind_mount_paths(void)
209 {
210         if (conf_debug_pruning) {
211                 fprintf(stderr, "Rebuilding bind_mount_paths:\n");
212         }
213         if (read_mount_entries() != 0) {
214                 return;
215         }
216         if (conf_debug_pruning) {
217                 fprintf(stderr, "Matching bind_mount_paths:\n");
218         }
219
220         bind_mount_paths.clear();
221
222         for (const auto &[dev_id, me] : mount_entries) {
223                 const auto &[first, second] = mount_entries.equal_range(make_pair(me.dev_major, me.dev_minor));
224                 for (auto it = first; it != second; ++it) {
225                         const mount &other = it->second;
226                         if (other.id == me.id) {
227                                 // Don't compare an element to itself.
228                                 continue;
229                         }
230                         // We have two mounts from the same device. Is one a prefix of the other?
231                         // If there are two that are equal, prefer the one with lowest ID.
232                         if (me.root.size() > other.root.size() && me.root.find(other.root) == 0) {
233                                 if (conf_debug_pruning) {
234                                         fprintf(stderr, " => adding `%s' (root `%s' is a child of `%s', mounted on `%s')\n",
235                                                 me.mount_point.c_str(), me.root.c_str(), other.root.c_str(), other.mount_point.c_str());
236                                 }
237                                 bind_mount_paths.push_back(me.mount_point);
238                                 break;
239                         }
240                         if (me.root == other.root && me.id > other.id) {
241                                 if (conf_debug_pruning) {
242                                         fprintf(stderr, " => adding `%s' (duplicate of mount point `%s')\n",
243                                                 me.mount_point.c_str(), other.mount_point.c_str());
244                                 }
245                                 bind_mount_paths.push_back(me.mount_point);
246                                 break;
247                         }
248                 }
249         }
250         if (conf_debug_pruning) {
251                 fprintf(stderr, "...done\n");
252         }
253         string_list_dir_path_sort(&bind_mount_paths);
254 }
255
256 /* Return true if PATH is a destination of a bind mount.
257    (Bind mounts "to self" are ignored.) */
258 bool is_bind_mount(const char *path)
259 {
260         if (mountinfo_updated.exchange(false)) {  // Atomic test-and-clear.
261                 rebuild_bind_mount_paths();
262                 bind_mount_paths_index = 0;
263         }
264         return string_list_contains_dir_path(&bind_mount_paths,
265                                              &bind_mount_paths_index, path);
266 }
267
268 /* Initialize state for is_bind_mount(), to read data from MOUNTINFO. */
269 void bind_mount_init(const char *mountinfo)
270 {
271         mountinfo_path = mountinfo;
272         mountinfo_fd = open(mountinfo_path, O_RDONLY);
273         if (mountinfo_fd == -1)
274                 return;
275         rebuild_bind_mount_paths();
276
277         // mlocate re-polls this for each and every directory it wants to check,
278         // for unclear reasons; it's possible that it's worried about a new recursive
279         // bind mount being made while updatedb is running, causing an infinite loop?
280         // Since it's probably for some good reason, we do the same, but we don't
281         // want the barrage of syscalls. It's not synchronous, but the poll signal
282         // isn't either; there's a slight race condition, but one that could only
283         // be exploited by root.
284         //
285         // The thread is forcibly terminated on exit(), so we just let it loop forever.
286         thread poll_thread([&] {
287                 for (;;) {
288                         struct pollfd pfd;
289                         /* Unfortunately (mount --bind $path $path/subdir) would leave st_dev
290                            unchanged between $path and $path/subdir, so we must keep reparsing
291                            mountinfo_path each time it changes. */
292                         pfd.fd = mountinfo_fd;
293                         pfd.events = POLLPRI;
294                         if (poll(&pfd, 1, /*timeout=*/-1) == -1) {
295                                 perror("poll()");
296                                 exit(1);
297                         }
298                         if ((pfd.revents & POLLPRI) != 0) {
299                                 mountinfo_updated = true;
300                         }
301                 }
302         });
303         poll_thread.detach();
304 }