master
c 489 lines 16.1 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "plugin_proc.h"
4
5 // ----------------------------------------------------------------------------
6 // taken from gnulib/mountlist.c
7
8 #ifndef ME_REMOTE
9 /* A file system is "remote" if its Fs_name contains a ':'
10 or if (it is of type (smbfs or cifs) and its Fs_name starts with '//')
11 or Fs_name is equal to "-hosts" (used by autofs to mount remote fs). */
12 # define ME_REMOTE(Fs_name, Fs_type) \
13 (strchr (Fs_name, ':') != NULL \
14 || ((Fs_name)[0] == '/' \
15 && (Fs_name)[1] == '/' \
16 && (strcmp (Fs_type, "smbfs") == 0 \
17 || strcmp (Fs_type, "cifs") == 0)) \
18 || (strcmp("-hosts", Fs_name) == 0))
19 #endif
20
21 #define ME_DUMMY_0(Fs_name, Fs_type) \
22 (strcmp (Fs_type, "autofs") == 0 \
23 || strcmp (Fs_type, "proc") == 0 \
24 || strcmp (Fs_type, "subfs") == 0 \
25 /* for Linux 2.6/3.x */ \
26 || strcmp (Fs_type, "debugfs") == 0 \
27 || strcmp (Fs_type, "devpts") == 0 \
28 || strcmp (Fs_type, "fusectl") == 0 \
29 || strcmp (Fs_type, "mqueue") == 0 \
30 || strcmp (Fs_type, "rpc_pipefs") == 0 \
31 || strcmp (Fs_type, "sysfs") == 0 \
32 /* FreeBSD, Linux 2.4 */ \
33 || strcmp (Fs_type, "devfs") == 0 \
34 /* for NetBSD 3.0 */ \
35 || strcmp (Fs_type, "kernfs") == 0 \
36 /* for Irix 6.5 */ \
37 || strcmp (Fs_type, "ignore") == 0)
38
39 /* Historically, we have marked as "dummy" any file system of type "none",
40 but now that programs like du need to know about bind-mounted directories,
41 we grant an exception to any with "bind" in its list of mount options.
42 I.e., those are *not* dummy entries. */
43 # define ME_DUMMY(Fs_name, Fs_type) \
44 (ME_DUMMY_0 (Fs_name, Fs_type) || strcmp (Fs_type, "none") == 0)
45
46 // ----------------------------------------------------------------------------
47
48 // find the mount info with the given major:minor
49 // in the supplied linked list of mountinfo structures
50 struct mountinfo *mountinfo_find(struct mountinfo *root, unsigned long major, unsigned long minor, char *device) {
51 struct mountinfo *mi;
52
53 uint32_t hash = simple_hash(device);
54
55 for(mi = root; mi ; mi = mi->next)
56 if (unlikely(
57 mi->major == major &&
58 mi->minor == minor &&
59 mi->mount_source_name_hash == hash &&
60 !strcmp(mi->mount_source_name, device)))
61 return mi;
62
63 return NULL;
64 }
65
66 // find the mount info with the given filesystem and mount_source
67 // in the supplied linked list of mountinfo structures
68 struct mountinfo *mountinfo_find_by_filesystem_mount_source(struct mountinfo *root, const char *filesystem, const char *mount_source) {
69 struct mountinfo *mi;
70 uint32_t filesystem_hash = simple_hash(filesystem), mount_source_hash = simple_hash(mount_source);
71
72 for(mi = root; mi ; mi = mi->next)
73 if(unlikely(mi->filesystem
74 && mi->mount_source
75 && mi->filesystem_hash == filesystem_hash
76 && mi->mount_source_hash == mount_source_hash
77 && !strcmp(mi->filesystem, filesystem)
78 && !strcmp(mi->mount_source, mount_source)))
79 return mi;
80
81 return NULL;
82 }
83
84 struct mountinfo *mountinfo_find_by_filesystem_super_option(struct mountinfo *root, const char *filesystem, const char *super_options) {
85 struct mountinfo *mi;
86 uint32_t filesystem_hash = simple_hash(filesystem);
87
88 size_t solen = strlen(super_options);
89
90 for(mi = root; mi ; mi = mi->next)
91 if(unlikely(mi->filesystem
92 && mi->super_options
93 && mi->filesystem_hash == filesystem_hash
94 && !strcmp(mi->filesystem, filesystem))) {
95
96 // super_options is a comma separated list
97 char *s = mi->super_options, *e;
98 while(*s) {
99 e = s + 1;
100 while(*e && *e != ',') e++;
101
102 size_t len = e - s;
103 if(unlikely(len == solen && !strncmp(s, super_options, len)))
104 return mi;
105
106 if(*e == ',') s = ++e;
107 else s = e;
108 }
109 }
110
111 return NULL;
112 }
113
114 static void mountinfo_free(struct mountinfo *mi) {
115 freez(mi->root);
116 freez(mi->mount_point_stat_path);
117 freez(mi->mount_point);
118 freez(mi->mount_options);
119 freez(mi->persistent_id);
120 /*
121 if(mi->optional_fields_count) {
122 int i;
123 for(i = 0; i < mi->optional_fields_count ; i++)
124 free(*mi->optional_fields[i]);
125 }
126 free(mi->optional_fields);
127 */
128 freez(mi->filesystem);
129 freez(mi->mount_source);
130 freez(mi->mount_source_name);
131 freez(mi->super_options);
132 freez(mi);
133 }
134
135 // free a linked list of mountinfo structures
136 void mountinfo_free_all(struct mountinfo *mi) {
137 while(mi) {
138 struct mountinfo *t = mi;
139 mi = mi->next;
140
141 mountinfo_free(t);
142 }
143 }
144
145 static char *strdupz_decoding_octal(const char *string) {
146 char *buffer = strdupz(string);
147
148 char *d = buffer;
149 const char *s = string;
150
151 while(*s) {
152 if(unlikely(*s == '\\')) {
153 s++;
154 if(likely(isdigit(*s) && isdigit(s[1]) && isdigit(s[2]))) {
155 char c = *s++ - '0';
156 c <<= 3;
157 c |= *s++ - '0';
158 c <<= 3;
159 c |= *s++ - '0';
160 *d++ = c;
161 }
162 else *d++ = '_';
163 }
164 else *d++ = *s++;
165 }
166 *d = '\0';
167
168 return buffer;
169 }
170
171 static inline int is_read_only(const char *s) {
172 if(!s) return 0;
173
174 size_t len = strlen(s);
175 if(len < 2) return 0;
176 if(len == 2) {
177 if(!strcmp(s, "ro")) return 1;
178 return 0;
179 }
180 if(!strncmp(s, "ro,", 3)) return 1;
181 if(!strncmp(&s[len - 3], ",ro", 3)) return 1;
182 if(strstr(s, ",ro,")) return 1;
183 return 0;
184 }
185
186 // for the full list of protected mount points look at
187 // https://github.com/systemd/systemd/blob/1eb3ef78b4df28a9e9f464714208f2682f957e36/src/core/namespace.c#L142-L149
188 // https://github.com/systemd/systemd/blob/1eb3ef78b4df28a9e9f464714208f2682f957e36/src/core/namespace.c#L180-L194
189 static const char *systemd_protected_mount_points[] = {
190 "/home",
191 "/root",
192 "/usr",
193 "/boot",
194 "/efi",
195 "/etc",
196 "/run/user",
197 "/lib",
198 "/lib64",
199 "/bin",
200 "/sbin",
201 NULL
202 };
203
204 static inline int mount_point_is_protected(char *mount_point)
205 {
206 for (size_t i = 0; systemd_protected_mount_points[i] != NULL; i++)
207 if (!strcmp(mount_point, systemd_protected_mount_points[i]))
208 return 1;
209
210 return 0;
211 }
212
213 // read the whole mountinfo into a linked list
214 struct mountinfo *mountinfo_read(int do_statvfs) {
215 char filename[FILENAME_MAX + 1];
216
217 snprintfz(filename, FILENAME_MAX, "%s/root/proc/1/mountinfo", netdata_configured_host_prefix);
218 procfile *ff = procfile_open(filename, " \t", PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
219
220 // Inside docker with '-v "/:/host/root:ro'
221 bool host_root_prefix = ff != NULL;
222
223 if (!ff) {
224 snprintfz(filename, FILENAME_MAX, "%s/proc/self/mountinfo", netdata_configured_host_prefix);
225 ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT);
226 }
227 if (!ff) {
228 snprintfz(filename, FILENAME_MAX, "%s/proc/1/mountinfo", netdata_configured_host_prefix);
229 ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT);
230 }
231 if (!ff)
232 return NULL;
233
234 ff = procfile_readall(ff);
235 if(unlikely(!ff))
236 return NULL;
237
238 struct mountinfo *root = NULL, *last = NULL, *mi = NULL;
239
240 // create a dictionary to track uniqueness
241 DICTIONARY *dict = dictionary_create_advanced(
242 DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_NAME_LINK_DONT_CLONE,
243 &dictionary_stats_category_collectors, 0);
244
245 unsigned long l, lines = procfile_lines(ff);
246 for(l = 0; l < lines ;l++) {
247 if(unlikely(procfile_linewords(ff, l) < 5))
248 continue;
249
250 // make sure we don't add the same item twice
251 char *v = (char *)dictionary_set(dict, procfile_lineword(ff, l, 4), "N", 2);
252 if(v) {
253 if(*v == 'O') continue;
254 *v = 'O';
255 }
256
257 mi = mallocz(sizeof(struct mountinfo));
258
259 unsigned long w = 0;
260 mi->id = str2ul(procfile_lineword(ff, l, w)); w++;
261 mi->parentid = str2ul(procfile_lineword(ff, l, w)); w++;
262
263 char *major = procfile_lineword(ff, l, w), *minor; w++;
264 for(minor = major; *minor && *minor != ':' ;minor++) ;
265
266 if(unlikely(!*minor)) {
267 collector_error("Cannot parse major:minor on '%s' at line %lu of '%s'", major, l + 1, filename);
268 freez(mi);
269 continue;
270 }
271
272 *minor = '\0';
273 minor++;
274
275 mi->flags = 0;
276
277 mi->major = str2ul(major);
278 mi->minor = str2ul(minor);
279
280 mi->root = strdupz(procfile_lineword(ff, l, w)); w++;
281 mi->root_hash = simple_hash(mi->root);
282
283 mi->mount_point = strdupz_decoding_octal(procfile_lineword(ff, l, w));w++;
284 mi->mount_point_hash = simple_hash(mi->mount_point);
285
286 if (host_root_prefix) {
287 snprintfz(filename, FILENAME_MAX, "%s/root%s", netdata_configured_host_prefix, mi->mount_point);
288 mi->mount_point_stat_path = strdupz(filename);
289 } else {
290 mi->mount_point_stat_path = strdupz(mi->mount_point);
291 }
292
293 mi->persistent_id = strdupz(mi->mount_point);
294 netdata_fix_chart_id(mi->persistent_id);
295 mi->persistent_id_hash = simple_hash(mi->persistent_id);
296
297 mi->mount_options = strdupz(procfile_lineword(ff, l, w)); w++;
298
299 if(unlikely(is_read_only(mi->mount_options)))
300 mi->flags |= MOUNTINFO_READONLY;
301
302 if(unlikely(mount_point_is_protected(mi->mount_point)))
303 mi->flags |= MOUNTINFO_IS_IN_SYSD_PROTECTED_LIST;
304
305 // count the optional fields
306 /*
307 unsigned long wo = w;
308 */
309 mi->optional_fields_count = 0;
310 char *s = procfile_lineword(ff, l, w);
311 while(*s && *s != '-') {
312 w++;
313 s = procfile_lineword(ff, l, w);
314 mi->optional_fields_count++;
315 }
316
317 /*
318 if(unlikely(mi->optional_fields_count)) {
319 // we have some optional fields
320 // read them into a new array of pointers;
321
322 mi->optional_fields = mallocz(mi->optional_fields_count * sizeof(char *));
323
324 int i;
325 for(i = 0; i < mi->optional_fields_count ; i++) {
326 *mi->optional_fields[wo] = strdupz(procfile_lineword(ff, l, w));
327 wo++;
328 }
329 }
330 else
331 mi->optional_fields = NULL;
332 */
333
334 if(likely(*s == '-')) {
335 w++;
336
337 mi->filesystem = strdupz(procfile_lineword(ff, l, w)); w++;
338 mi->filesystem_hash = simple_hash(mi->filesystem);
339
340 mi->mount_source = strdupz_decoding_octal(procfile_lineword(ff, l, w)); w++;
341 mi->mount_source_hash = simple_hash(mi->mount_source);
342
343 mi->mount_source_name = strdupz(basename(mi->mount_source));
344 mi->mount_source_name_hash = simple_hash(mi->mount_source_name);
345
346 mi->super_options = strdupz(procfile_lineword(ff, l, w)); w++;
347
348 if(unlikely(is_read_only(mi->super_options)))
349 mi->flags |= MOUNTINFO_READONLY;
350
351 if(unlikely(ME_DUMMY(mi->mount_source, mi->filesystem)))
352 mi->flags |= MOUNTINFO_IS_DUMMY;
353
354 if(unlikely(ME_REMOTE(mi->mount_source, mi->filesystem)))
355 mi->flags |= MOUNTINFO_IS_REMOTE;
356
357 // mark as BIND the duplicates (i.e. same filesystem + same source)
358 if(do_statvfs) {
359 struct stat buf;
360 if(unlikely(stat(mi->mount_point_stat_path, &buf) == -1)) {
361 mi->st_dev = 0;
362 mi->flags |= MOUNTINFO_NO_STAT;
363 }
364 else {
365 mi->st_dev = buf.st_dev;
366
367 struct mountinfo *mt;
368 for(mt = root; mt; mt = mt->next) {
369 if(unlikely(mt->st_dev == mi->st_dev && !(mt->flags & MOUNTINFO_IS_SAME_DEV))) {
370 if(strlen(mi->mount_point) < strlen(mt->mount_point))
371 mt->flags |= MOUNTINFO_IS_SAME_DEV;
372 else
373 mi->flags |= MOUNTINFO_IS_SAME_DEV;
374 }
375 }
376 }
377 }
378 else {
379 mi->st_dev = 0;
380 }
381
382 //try to detect devices with same minor and major modes. Within these,
383 //the larger mount point is considered a bind.
384 struct mountinfo *mt;
385 for(mt = root; mt; mt = mt->next) {
386 if(unlikely(mt->major == mi->major && mt->minor == mi->minor && !(mi->flags & MOUNTINFO_IS_BIND))) {
387 if(strlen(mi->root) < strlen(mt->root))
388 mt->flags |= MOUNTINFO_IS_BIND;
389 else
390 mi->flags |= MOUNTINFO_IS_BIND;
391 }
392 }
393 }
394 else {
395 mi->filesystem = NULL;
396 mi->filesystem_hash = 0;
397
398 mi->mount_source = NULL;
399 mi->mount_source_hash = 0;
400
401 mi->mount_source_name = NULL;
402 mi->mount_source_name_hash = 0;
403
404 mi->super_options = NULL;
405
406 mi->st_dev = 0;
407 }
408
409 // check if it has size
410 if(do_statvfs && !(mi->flags & MOUNTINFO_IS_DUMMY)) {
411 struct statvfs buff_statvfs;
412 if(unlikely(statvfs(mi->mount_point_stat_path, &buff_statvfs) < 0)) {
413 mi->flags |= MOUNTINFO_NO_STAT;
414 }
415 else if(unlikely(!buff_statvfs.f_blocks /* || !buff_statvfs.f_files */)) {
416 mi->flags |= MOUNTINFO_NO_SIZE;
417 }
418 }
419
420 // link it
421 if(unlikely(!root))
422 root = mi;
423 else
424 last->next = mi;
425
426 last = mi;
427 mi->next = NULL;
428
429 /*
430 #ifdef NETDATA_INTERNAL_CHECKS
431 fprintf(stderr, "MOUNTINFO: %ld %ld %lu:%lu root '%s', persistent id '%s', mount point '%s', mount options '%s', filesystem '%s', mount source '%s', super options '%s'%s%s%s%s%s%s\n",
432 mi->id,
433 mi->parentid,
434 mi->major,
435 mi->minor,
436 mi->root,
437 mi->persistent_id,
438 (mi->mount_point)?mi->mount_point:"",
439 (mi->mount_options)?mi->mount_options:"",
440 (mi->filesystem)?mi->filesystem:"",
441 (mi->mount_source)?mi->mount_source:"",
442 (mi->super_options)?mi->super_options:"",
443 (mi->flags & MOUNTINFO_IS_DUMMY)?" DUMMY":"",
444 (mi->flags & MOUNTINFO_IS_BIND)?" BIND":"",
445 (mi->flags & MOUNTINFO_IS_REMOTE)?" REMOTE":"",
446 (mi->flags & MOUNTINFO_NO_STAT)?" NOSTAT":"",
447 (mi->flags & MOUNTINFO_NO_SIZE)?" NOSIZE":"",
448 (mi->flags & MOUNTINFO_IS_SAME_DEV)?" SAMEDEV":""
449 );
450 #endif
451 */
452 }
453
454 /* find if the mount options have "bind" in them
455 {
456 FILE *fp = setmntent(MOUNTED, "r");
457 if (fp != NULL) {
458 struct mntent mntbuf;
459 struct mntent *mnt;
460 char buf[4096 + 1];
461
462 while ((mnt = getmntent_r(fp, &mntbuf, buf, 4096))) {
463 char *bind = hasmntopt(mnt, "bind");
464 if(unlikely(bind)) {
465 struct mountinfo *mi;
466 for(mi = root; mi ; mi = mi->next) {
467 if(unlikely(strcmp(mnt->mnt_dir, mi->mount_point) == 0)) {
468 fprintf(stderr, "Mount point '%s' is BIND\n", mi->mount_point);
469 mi->flags |= MOUNTINFO_IS_BIND;
470 break;
471 }
472 }
473
474 #ifdef NETDATA_INTERNAL_CHECKS
475 if(unlikely(!mi)) {
476 collector_error("Mount point '%s' not found in /proc/self/mountinfo", mnt->mnt_dir);
477 }
478 #endif
479 }
480 }
481 endmntent(fp);
482 }
483 }
484 */
485
486 dictionary_destroy(dict);
487 procfile_close(ff);
488 return root;
489 }