Code

Copied src/utils_mount.[ch] from quota-branch to trunk
[collectd.git] / src / utils_mount.c
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;
138 static void
139 uuidcache_addentry(char *device, char *label, char *uuid)
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));
156 static void
157 uuidcache_init(void)
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);
226 static unsigned char
227 fromhex(char c)
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         }
238 static char *
239 get_spec_by_x(int n, const char *t)
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;
264 static char *
265 get_spec_by_uuid(const char *s)
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;
292 static char *
293 get_spec_by_volume_label(const char *s)
295         return get_spec_by_x(VOL, s);
298 static char *
299 get_device_name(const char *item)
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;
320 #if HAVE_GETVFSENT
321 static void
322 cu_mount_getvfsmnt(FILE *mntf, cu_mount_t **list)
324         DBG("TODO: getvfsmnt");
325         *list = NULL;
327 #endif /* HAVE_GETVFSENT */
331 #if HAVE_LISTMNTENT
332 static cu_mount_t *
333 cu_mount_listmntent(struct tabmntent *mntlist, cu_mount_t **list)
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)
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)
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)
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)
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)
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)
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) */