1 /**
2 * collectd - src/utils_mount.c
3 * Copyright (C) 2005 Niki W. Waibel
4 *
5 * This program is free software; you can redistribute it and/
6 * or modify it under the terms of the GNU General Public Li-
7 * cence as published by the Free Software Foundation; either
8 * version 2 of the Licence, or any later version.
9 *
10 * This program is distributed in the hope that it will be use-
11 * ful, but WITHOUT ANY WARRANTY; without even the implied war-
12 * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public Licence for more details.
14 *
15 * You should have received a copy of the GNU General Public
16 * Licence along with this program; if not, write to the Free
17 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
18 * USA.
19 *
20 * Author:
21 * Niki W. Waibel <niki.waibel@gmx.net>
22 **/
26 #include "common.h"
27 #if HAVE_XFS_XQM_H
28 # include <xfs/xqm.h>
29 #define XFS_SUPER_MAGIC_STR "XFSB"
30 #define XFS_SUPER_MAGIC2_STR "BSFX"
31 #endif
32 #include "utils_debug.h"
33 #include "utils_mount.h"
37 /* *** *** *** ********************************************* *** *** *** */
38 /* *** *** *** *** *** *** private functions *** *** *** *** *** *** */
39 /* *** *** *** ********************************************* *** *** *** */
43 /* stolen from quota-3.13 (quota-tools) */
45 #define PROC_PARTITIONS "/proc/partitions"
46 #define DEVLABELDIR "/dev"
47 #define UUID 1
48 #define VOL 2
50 static struct uuidCache_s {
51 struct uuidCache_s *next;
52 char uuid[16];
53 char *label;
54 char *device;
55 } *uuidCache = NULL;
57 #define EXT2_SUPER_MAGIC 0xEF53
58 struct ext2_super_block {
59 unsigned char s_dummy1[56];
60 unsigned char s_magic[2];
61 unsigned char s_dummy2[46];
62 unsigned char s_uuid[16];
63 char s_volume_name[16];
64 };
65 #define ext2magic(s) ((unsigned int)s.s_magic[0] \
66 + (((unsigned int)s.s_magic[1]) << 8))
68 #if HAVE_XFS_XQM_H
69 struct xfs_super_block {
70 unsigned char s_magic[4];
71 unsigned char s_dummy[28];
72 unsigned char s_uuid[16];
73 unsigned char s_dummy2[60];
74 char s_fsname[12];
75 };
76 #endif /* HAVE_XFS_XQM_H */
78 #define REISER_SUPER_MAGIC "ReIsEr2Fs"
79 struct reiserfs_super_block {
80 unsigned char s_dummy1[52];
81 unsigned char s_magic[10];
82 unsigned char s_dummy2[22];
83 unsigned char s_uuid[16];
84 char s_volume_name[16];
85 };
87 /* for now, only ext2 and xfs are supported */
88 static int
89 get_label_uuid(const char *device, char **label, char *uuid)
90 {
91 /* start with ext2 and xfs tests, taken from mount_guess_fstype */
92 /* should merge these later */
93 int fd, rv = 1;
94 size_t namesize;
95 struct ext2_super_block e2sb;
96 #if HAVE_XFS_XQM_H
97 struct xfs_super_block xfsb;
98 #endif
99 struct reiserfs_super_block reisersb;
101 fd = open(device, O_RDONLY);
102 if(fd == -1) {
103 return rv;
104 }
106 if(lseek(fd, 1024, SEEK_SET) == 1024
107 && read(fd, (char *)&e2sb, sizeof(e2sb)) == sizeof(e2sb)
108 && ext2magic(e2sb) == EXT2_SUPER_MAGIC) {
109 memcpy(uuid, e2sb.s_uuid, sizeof(e2sb.s_uuid));
110 namesize = sizeof(e2sb.s_volume_name);
111 *label = smalloc(namesize + 1);
112 sstrncpy(*label, e2sb.s_volume_name, namesize);
113 rv = 0;
114 #if HAVE_XFS_XQM_H
115 } else if(lseek(fd, 0, SEEK_SET) == 0
116 && read(fd, (char *)&xfsb, sizeof(xfsb)) == sizeof(xfsb)
117 && (strncmp((char *)&xfsb.s_magic, XFS_SUPER_MAGIC_STR, 4) == 0 ||
118 strncmp((char *)&xfsb.s_magic, XFS_SUPER_MAGIC2_STR, 4) == 0)) {
119 memcpy(uuid, xfsb.s_uuid, sizeof(xfsb.s_uuid));
120 namesize = sizeof(xfsb.s_fsname);
121 *label = smalloc(namesize + 1);
122 sstrncpy(*label, xfsb.s_fsname, namesize);
123 rv = 0;
124 #endif /* HAVE_XFS_XQM_H */
125 } else if(lseek(fd, 65536, SEEK_SET) == 65536
126 && read(fd, (char *)&reisersb, sizeof(reisersb)) == sizeof(reisersb)
127 && !strncmp((char *)&reisersb.s_magic, REISER_SUPER_MAGIC, 9)) {
128 memcpy(uuid, reisersb.s_uuid, sizeof(reisersb.s_uuid));
129 namesize = sizeof(reisersb.s_volume_name);
130 *label = smalloc(namesize + 1);
131 sstrncpy(*label, reisersb.s_volume_name, namesize);
132 rv = 0;
133 }
134 close(fd);
135 return rv;
136 }
138 static void
139 uuidcache_addentry(char *device, char *label, char *uuid)
140 {
141 struct uuidCache_s *last;
143 if(!uuidCache) {
144 last = uuidCache = smalloc(sizeof(*uuidCache));
145 } else {
146 for(last = uuidCache; last->next; last = last->next);
147 last->next = smalloc(sizeof(*uuidCache));
148 last = last->next;
149 }
150 last->next = NULL;
151 last->device = device;
152 last->label = label;
153 memcpy(last->uuid, uuid, sizeof(last->uuid));
154 }
156 static void
157 uuidcache_init(void)
158 {
159 char line[100];
160 char *s;
161 int ma, mi, sz;
162 static char ptname[100];
163 FILE *procpt;
164 char uuid[16], *label = NULL;
165 char device[110];
166 int firstPass;
167 int handleOnFirst;
169 if(uuidCache) {
170 return;
171 }
173 procpt = fopen(PROC_PARTITIONS, "r");
174 if(procpt == NULL) {
175 return;
176 }
178 for(firstPass = 1; firstPass >= 0; firstPass--) {
179 fseek(procpt, 0, SEEK_SET);
180 while(fgets(line, sizeof(line), procpt)) {
181 if(sscanf(line, " %d %d %d %[^\n ]",
182 &ma, &mi, &sz, ptname) != 4)
183 {
184 continue;
185 }
187 /* skip extended partitions (heuristic: size 1) */
188 if(sz == 1) {
189 continue;
190 }
192 /* look only at md devices on first pass */
193 handleOnFirst = !strncmp(ptname, "md", 2);
194 if(firstPass != handleOnFirst) {
195 continue;
196 }
198 /* skip entire disk (minor 0, 64, ... on ide;
199 0, 16, ... on sd) */
200 /* heuristic: partition name ends in a digit */
202 for(s = ptname; *s; s++);
204 if(isdigit((int)s[-1])) {
205 /*
206 * Note: this is a heuristic only - there is no reason
207 * why these devices should live in /dev.
208 * Perhaps this directory should be specifiable by option.
209 * One might for example have /devlabel with links to /dev
210 * for the devices that may be accessed in this way.
211 * (This is useful, if the cdrom on /dev/hdc must not
212 * be accessed.)
213 */
214 snprintf(device, sizeof(device), "%s/%s",
215 DEVLABELDIR, ptname);
216 if(!get_label_uuid(device, &label, uuid)) {
217 uuidcache_addentry(sstrdup(device),
218 label, uuid);
219 }
220 }
221 }
222 }
223 fclose(procpt);
224 }
226 static unsigned char
227 fromhex(char c)
228 {
229 if(isdigit((int)c)) {
230 return (c - '0');
231 } else if(islower((int)c)) {
232 return (c - 'a' + 10);
233 } else {
234 return (c - 'A' + 10);
235 }
236 }
238 static char *
239 get_spec_by_x(int n, const char *t)
240 {
241 struct uuidCache_s *uc;
243 uuidcache_init();
244 uc = uuidCache;
246 while(uc) {
247 switch(n) {
248 case UUID:
249 if(!memcmp(t, uc->uuid, sizeof(uc->uuid))) {
250 return sstrdup(uc->device);
251 }
252 break;
253 case VOL:
254 if(!strcmp(t, uc->label)) {
255 return sstrdup(uc->device);
256 }
257 break;
258 }
259 uc = uc->next;
260 }
261 return NULL;
262 }
264 static char *
265 get_spec_by_uuid(const char *s)
266 {
267 char uuid[16];
268 int i;
270 if(strlen(s) != 36
271 || s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-') {
272 goto bad_uuid;
273 }
275 for(i=0; i<16; i++) {
276 if(*s == '-') {
277 s++;
278 }
279 if(!isxdigit((int)s[0]) || !isxdigit((int)s[1])) {
280 goto bad_uuid;
281 }
282 uuid[i] = ((fromhex(s[0]) << 4) | fromhex(s[1]));
283 s += 2;
284 }
285 return get_spec_by_x(UUID, uuid);
287 bad_uuid:
288 DBG("Found an invalid UUID: %s", s);
289 return NULL;
290 }
292 static char *
293 get_spec_by_volume_label(const char *s)
294 {
295 return get_spec_by_x(VOL, s);
296 }
298 static char *
299 get_device_name(const char *item)
300 {
301 char *rc;
303 if(!strncmp(item, "UUID=", 5)) {
304 DBG("TODO: check UUID= code!");
305 rc = get_spec_by_uuid(item + 5);
306 } else if(!strncmp(item, "LABEL=", 6)) {
307 DBG("TODO: check LABEL= code!");
308 rc = get_spec_by_volume_label(item + 6);
309 } else {
310 rc = sstrdup(item);
311 }
312 if(!rc) {
313 DBG("Error checking device name: %s", item);
314 }
315 return rc;
316 }
320 #if HAVE_GETVFSENT
321 static void
322 cu_mount_getvfsmnt(FILE *mntf, cu_mount_t **list)
323 {
324 DBG("TODO: getvfsmnt");
325 *list = NULL;
326 }
327 #endif /* HAVE_GETVFSENT */
331 #if HAVE_LISTMNTENT
332 static cu_mount_t *
333 cu_mount_listmntent(struct tabmntent *mntlist, cu_mount_t **list)
334 {
335 cu_mount_t *last = *list;
336 struct tabmntent *p;
337 struct mntent *mnt;
339 for(p = mntlist; p; p = p->next) {
340 char *loop = NULL, *device = NULL;
342 mnt = p->ment;
343 loop = cu_mount_getoptionvalue(mnt->mnt_opts, "loop=");
344 if(loop == NULL) { /* no loop= mount */
345 device = get_device_name(mnt->mnt_fsname);
346 if(device == NULL) {
347 DBG("can't get devicename for fs (%s) %s (%s)"
348 ": ignored", mnt->mnt_type,
349 mnt->mnt_dir, mnt->mnt_fsname);
350 continue;
351 }
352 } else {
353 device = loop;
354 }
355 if(*list == NULL) {
356 *list = (cu_mount_t *)smalloc(sizeof(cu_mount_t));
357 last = *list;
358 } else {
359 while(last->next != NULL) { /* is last really last? */
360 last = last->next;
361 }
362 last->next = (cu_mount_t *)smalloc(sizeof(cu_mount_t));
363 last = last->next;
364 }
365 last->dir = sstrdup(mnt->mnt_dir);
366 last->spec_device = sstrdup(mnt->mnt_fsname);
367 last->device = device;
368 last->type = sstrdup(mnt->mnt_type);
369 last->options = sstrdup(mnt->mnt_opts);
370 last->next = NULL;
371 } /* for(p = mntlist; p; p = p->next) */
373 return(last);
374 } /* static cu_mount_t *cu_mount_listmntent(struct tabmntent *mntlist,
375 cu_mount_t **list) */
376 #endif /* HAVE_LISTMNTENT */
380 #if HAVE_GETMNTENT
381 static cu_mount_t *
382 cu_mount_getmntent(FILE *mntf, cu_mount_t **list)
383 {
384 cu_mount_t *last = *list;
385 #if HAVE_GETMNTENT1
386 struct mntent *mnt = NULL;
387 #endif
388 #if HAVE_GETMNTENT2
389 struct mntent real_mnt;
390 struct mntent *mnt = &real_mnt;
391 #endif
393 #if HAVE_GETMNTENT1
394 while((mnt = getmntent(mntf)) != NULL) {
395 #endif
396 #if HAVE_GETMNTENT2
397 while(getmntent(mntf, &real_mnt) == 0) {
398 #endif
399 char *loop = NULL, *device = NULL;
401 #if 0
402 DBG("------------------ BEGIN");
403 DBG("mnt->mnt_fsname %s", mnt->mnt_fsname);
404 DBG("mnt->mnt_dir %s", mnt->mnt_dir);
405 DBG("mnt->mnt_type %s", mnt->mnt_type);
406 DBG("mnt->mnt_opts %s", mnt->mnt_opts);
407 DBG("mnt->mnt_freq %d", mnt->mnt_freq);
408 DBG("mnt->mnt_passno %d", mnt->mnt_passno);
409 #endif
411 loop = cu_mount_getoptionvalue(mnt->mnt_opts, "loop=");
412 if(loop == NULL) { /* no loop= mount */
413 device = get_device_name(mnt->mnt_fsname);
414 if(device == NULL) {
415 DBG("can't get devicename for fs (%s) %s (%s)"
416 ": ignored", mnt->mnt_type,
417 mnt->mnt_dir, mnt->mnt_fsname);
418 continue;
419 }
420 } else {
421 device = loop;
422 }
424 #if 0
425 DBG("device: %s", device);
426 DBG("------------------ END");
427 #endif
428 if(*list == NULL) {
429 *list = (cu_mount_t *)smalloc(sizeof(cu_mount_t));
430 last = *list;
431 } else {
432 while(last->next != NULL) { /* is last really last? */
433 last = last->next;
434 }
435 last->next = (cu_mount_t *)smalloc(sizeof(cu_mount_t));
436 last = last->next;
437 }
438 last->dir = sstrdup(mnt->mnt_dir);
439 last->spec_device = sstrdup(mnt->mnt_fsname);
440 last->device = device;
441 last->type = sstrdup(mnt->mnt_type);
442 last->options = sstrdup(mnt->mnt_opts);
443 last->next = NULL;
444 #if HAVE_GETMNTENT2
445 } /* while(getmntent(mntf, &real_mnt) == 0) */
446 #endif
447 #if HAVE_GETMNTENT1
448 } /* while((mnt = getmntent(mntf)) != NULL) */
449 #endif
451 return last;
452 } /* static cu_mount_t *cu_mount_getmntent(FILE *mntf, cu_mount_t **list) */
453 #endif /* HAVE_GETMNTENT */
457 /* *** *** *** ******************************************** *** *** *** */
458 /* *** *** *** *** *** *** public functions *** *** *** *** *** *** */
459 /* *** *** *** ******************************************** *** *** *** */
463 cu_mount_t *
464 cu_mount_getlist(cu_mount_t **list)
465 {
466 cu_mount_t *last = NULL;
468 /* see lib/mountlist.c of coreutils for all (ugly) details! */
470 /*
471 there are two implementations of getmntent():
472 * one argument getmntent:
473 FILE *setmntent(const char *filename, const char *type);
474 struct mntent *getmntent(FILE *fp);
475 int endmntent(FILE *fp);
476 * two argument getmntent:
477 FILE *fopen(const char *path, const char *mode);
478 int getmntent(FILE *fp, struct mnttab *mnt);
479 int fclose(FILE *fp);
480 and a third (linux/gnu style) version called getmntent_r, which is not used
481 here (enough trouble with the two versions above).
482 */
483 #if HAVE_GETMNTENT
484 # if HAVE_GETMNTENT1
485 # define setmntent setmntent
486 # define endmntent endmntent
487 # else
488 # if HAVE_GETMNTENT2
489 # define setmntent fopen
490 # define endmntent fclose
491 # else
492 # error HAVE_GETMNTENT defined, but neither HAVE_GETMNTENT1 nor HAVE_GETMNTENT2
493 # endif /* HAVE_GETMNTENT2 */
494 # endif /* HAVE_GETMNTENT1 */
495 #endif /* HAVE_GETMNTENT */
497 /* the indentation is wrong. is there a better way to do this? */
499 #if HAVE_GETMNTENT && defined(_PATH_MOUNTED)
500 {
501 FILE *mntf = NULL;
502 if((mntf = setmntent(_PATH_MOUNTED, "r")) == NULL) {
503 DBG("opening %s failed: %s", _PATH_MOUNTED, strerror(errno));
504 #endif
505 #if HAVE_GETMNTENT && defined(MNT_MNTTAB)
506 {
507 FILE *mntf = NULL;
508 if((mntf = setmntent(MNT_MNTTAB, "r")) == NULL) {
509 DBG("opening %s failed: %s", MNT_MNTTAB, strerror(errno));
510 #endif
511 #if HAVE_GETMNTENT && defined(MNTTABNAME)
512 {
513 FILE *mntf = NULL;
514 if((mntf = setmntent(MNTTABNAME, "r")) == NULL) {
515 DBG("opening %s failed: %s", MNTTABNAME, strerror(errno));
516 #endif
517 #if HAVE_LISTMNTENT
518 {
519 struct tabmntent *mntlist;
520 if(listmntent(&mntlist, KMTAB, NULL, NULL) < 0) {
521 DBG("calling listmntent() failed: %s", strerror(errno));
522 #endif
523 #if HAVE_GETVFSENT && defined(VFSTAB)
524 /* this is as bad as the next one, read next comment */
525 {
526 FILE *mntf = NULL;
527 if((mntf = fopen(VFSTAB, "r")) == NULL) {
528 DBG("opening %s failed: %s", VFSTAB, strerror(errno));
529 #endif
530 #if HAVE_GETMNTENT && defined(_PATH_MNTTAB)
531 /* _PATH_MNTTAB is usually /etc/fstab and so this should be really
532 the very last thing to try, because it does not provide a list
533 of currently mounted filesystems... */
534 {
535 FILE *mntf = NULL;
536 if((mntf = setmntent(_PATH_MNTTAB, "r")) == NULL) {
537 DBG("opening %s failed: %s", _PATH_MNTTAB, strerror(errno));
538 #endif
540 /* give up */
541 DBG("failed get local mountpoints");
542 return(NULL);
544 #if HAVE_GETMNTENT && defined(_PATH_MNTTAB)
545 } else { last = cu_mount_getmntent(mntf, list); }
546 (void)endmntent(mntf);
547 }
548 #endif
549 #if HAVE_GETVFSENT && defined(VFSTAB)
550 } else { last = cu_mount_getvfsmnt(mntf, list); }
551 (void)fclose(mntf);
552 }
553 #endif
554 #if HAVE_LISTMNTENT
555 } else { last = cu_mount_listmntent(mntlist, list); }
556 freemntlist(mntlist);
557 }
558 #endif
559 #if HAVE_GETMNTENT && defined(MNTTABNAME)
560 } else { last = cu_mount_getmntent(mntf, list); }
561 (void)endmntent(mntf);
562 }
563 #endif
564 #if HAVE_GETMNTENT && defined(MNT_MNTTAB)
565 } else { last = cu_mount_getmntent(mntf, list); }
566 (void)endmntent(mntf);
567 }
568 #endif
569 #if HAVE_GETMNTENT && defined(_PATH_MOUNTED)
570 } else { last = cu_mount_getmntent(mntf, list); }
571 (void)endmntent(mntf);
572 }
573 #endif
574 return(last);
575 } /* cu_mount_t *cu_mount_getlist(cu_mount_t **list) */
579 void
580 cu_mount_freelist(cu_mount_t *list)
581 {
582 cu_mount_t *l = list, *p = NULL;
584 while(l != NULL) {
585 while(l->next != NULL) {
586 p = l;
587 l = l->next;
588 }
589 if(p != NULL) {
590 p->next = NULL;
591 }
592 sfree(l->dir);
593 sfree(l->spec_device);
594 sfree(l->device);
595 sfree(l->type);
596 sfree(l->options);
597 p = NULL;
598 if(l != list) {
599 sfree(l);
600 l = list;
601 } else {
602 sfree(l);
603 l = NULL; /* done by sfree already */
604 }
605 } /* while(l != NULL) */
606 } /* void cu_mount_freelist(cu_mount_t *list) */
610 char *
611 cu_mount_checkoption(char *line, char *keyword, int full)
612 {
613 char *line2, *l2;
614 int l = strlen(keyword);
615 char *p1, *p2;
617 if(line == NULL || keyword == NULL) {
618 return NULL;
619 }
620 if(full != 0) {
621 full = 1;
622 }
624 line2 = sstrdup(line);
625 l2 = line2;
626 while(*l2 != '\0') {
627 if(*l2 == ',') {
628 *l2 = '\0';
629 }
630 l2++;
631 }
633 p1 = line - 1;
634 p2 = strchr(line, ',');
635 do {
636 if(strncmp(line2+(p1-line)+1, keyword, l+full) == 0) {
637 free(line2);
638 return p1+1;
639 }
640 p1 = p2;
641 if(p1 != NULL) {
642 p2 = strchr(p1+1, ',');
643 }
644 } while(p1 != NULL);
646 free(line2);
647 return NULL;
648 } /* char *cu_mount_checkoption(char *line, char *keyword, int full) */
652 char *
653 cu_mount_getoptionvalue(char *line, char *keyword)
654 {
655 char *r;
657 r = cu_mount_checkoption(line, keyword, 0);
658 if(r != NULL) {
659 char *p;
660 r += strlen(keyword);
661 p = strchr(r, ',');
662 if(p == NULL) {
663 if(strlen(r) == 0) {
664 return NULL;
665 }
666 return sstrdup(r);
667 } else {
668 char *m;
669 if((p-r) == 1) {
670 return NULL;
671 }
672 m = (char *)smalloc(p-r+1);
673 sstrncpy(m, r, p-r+1);
674 return m;
675 }
676 }
677 return r;
678 } /* char *cu_mount_getoptionvalue(char *line, char *keyword) */
682 int
683 cu_mount_type(const char *type)
684 {
685 if(strcmp(type, "ext3") == 0) return CUMT_EXT3;
686 if(strcmp(type, "ext2") == 0) return CUMT_EXT2;
687 if(strcmp(type, "ufs") == 0) return CUMT_UFS;
688 if(strcmp(type, "vxfs") == 0) return CUMT_VXFS;
689 if(strcmp(type, "zfs") == 0) return CUMT_ZFS;
690 return CUMT_UNKNOWN;
691 } /* int cu_mount_type(const char *type) */