/** * collectd - src/utils_mount.c * Copyright (C) 2005,2006 Niki W. Waibel * * This program is free software; you can redistribute it and/ * or modify it under the terms of the GNU General Public Li- * cence as published by the Free Software Foundation; either * version 2 of the Licence, or any later version. * * This program is distributed in the hope that it will be use- * ful, but WITHOUT ANY WARRANTY; without even the implied war- * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public Licence for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Author: * Niki W. Waibel **/ #if HAVE_CONFIG_H #include "config.h" #endif #define _GNU_SOURCE #include "collectd.h" #include "utils/mount/mount.h" #if HAVE_XFS_XQM_H #include #define XFS_SUPER_MAGIC_STR "XFSB" #define XFS_SUPER_MAGIC2_STR "BSFX" #endif #include "plugin.h" /* ERROR() macro */ #include "utils/common/common.h" /* sstrncpy() et alii */ #if HAVE_GETVFSSTAT #if HAVE_SYS_TYPES_H #include #endif #if HAVE_SYS_STATVFS_H #include #endif /* #endif HAVE_GETVFSSTAT */ #elif HAVE_GETFSSTAT #if HAVE_SYS_PARAM_H #include #endif #if HAVE_SYS_UCRED_H #include #endif #if HAVE_SYS_MOUNT_H #include #endif #endif /* HAVE_GETFSSTAT */ #if HAVE_MNTENT_H #include #endif #if HAVE_SYS_MNTTAB_H #include #endif #if HAVE_PATHS_H #include #endif #ifdef COLLECTD_MNTTAB #undef COLLECTD_MNTTAB #endif #if defined(_PATH_MOUNTED) /* glibc */ #define COLLECTD_MNTTAB _PATH_MOUNTED #elif defined(MNTTAB) /* Solaris */ #define COLLECTD_MNTTAB MNTTAB #elif defined(MNT_MNTTAB) #define COLLECTD_MNTTAB MNT_MNTTAB #elif defined(MNTTABNAME) #define COLLECTD_MNTTAB MNTTABNAME #elif defined(KMTAB) #define COLLECTD_MNTTAB KMTAB #else #define COLLECTD_MNTTAB "/etc/mnttab" #endif /* *** *** *** ********************************************* *** *** *** */ /* *** *** *** *** *** *** private functions *** *** *** *** *** *** */ /* *** *** *** ********************************************* *** *** *** */ /* stolen from quota-3.13 (quota-tools) */ #define PROC_PARTITIONS "/proc/partitions" #define DEVLABELDIR "/dev" #define UUID 1 #define VOL 2 static struct uuidCache_s { struct uuidCache_s *next; char uuid[16]; char *label; char *device; } *uuidCache = NULL; #define EXT2_SUPER_MAGIC 0xEF53 struct ext2_super_block { unsigned char s_dummy1[56]; unsigned char s_magic[2]; unsigned char s_dummy2[46]; unsigned char s_uuid[16]; char s_volume_name[16]; }; #define ext2magic(s) \ ((unsigned int)s.s_magic[0] + (((unsigned int)s.s_magic[1]) << 8)) #if HAVE_XFS_XQM_H struct xfs_super_block { unsigned char s_magic[4]; unsigned char s_dummy[28]; unsigned char s_uuid[16]; unsigned char s_dummy2[60]; char s_fsname[12]; }; #endif /* HAVE_XFS_XQM_H */ #define REISER_SUPER_MAGIC "ReIsEr2Fs" struct reiserfs_super_block { unsigned char s_dummy1[52]; unsigned char s_magic[10]; unsigned char s_dummy2[22]; unsigned char s_uuid[16]; char s_volume_name[16]; }; /* for now, only ext2 and xfs are supported */ static int get_label_uuid(const char *device, char **label, char *uuid) { /* start with ext2 and xfs tests, taken from mount_guess_fstype */ /* should merge these later */ int fd, rv = 1; size_t namesize; struct ext2_super_block e2sb; #if HAVE_XFS_XQM_H struct xfs_super_block xfsb; #endif struct reiserfs_super_block reisersb; fd = open(device, O_RDONLY); if (fd == -1) { return rv; } if (lseek(fd, 1024, SEEK_SET) == 1024 && read(fd, (char *)&e2sb, sizeof(e2sb)) == sizeof(e2sb) && ext2magic(e2sb) == EXT2_SUPER_MAGIC) { memcpy(uuid, e2sb.s_uuid, sizeof(e2sb.s_uuid)); namesize = sizeof(e2sb.s_volume_name); *label = smalloc(namesize + 1); sstrncpy(*label, e2sb.s_volume_name, namesize); rv = 0; #if HAVE_XFS_XQM_H } else if (lseek(fd, 0, SEEK_SET) == 0 && read(fd, (char *)&xfsb, sizeof(xfsb)) == sizeof(xfsb) && (strncmp((char *)&xfsb.s_magic, XFS_SUPER_MAGIC_STR, 4) == 0 || strncmp((char *)&xfsb.s_magic, XFS_SUPER_MAGIC2_STR, 4) == 0)) { memcpy(uuid, xfsb.s_uuid, sizeof(xfsb.s_uuid)); namesize = sizeof(xfsb.s_fsname); *label = smalloc(namesize + 1); sstrncpy(*label, xfsb.s_fsname, namesize); rv = 0; #endif /* HAVE_XFS_XQM_H */ } else if (lseek(fd, 65536, SEEK_SET) == 65536 && read(fd, (char *)&reisersb, sizeof(reisersb)) == sizeof(reisersb) && !strncmp((char *)&reisersb.s_magic, REISER_SUPER_MAGIC, 9)) { memcpy(uuid, reisersb.s_uuid, sizeof(reisersb.s_uuid)); namesize = sizeof(reisersb.s_volume_name); *label = smalloc(namesize + 1); sstrncpy(*label, reisersb.s_volume_name, namesize); rv = 0; } close(fd); return rv; } static void uuidcache_addentry(char *device, char *label, char *uuid) { struct uuidCache_s *last; if (!uuidCache) { last = uuidCache = smalloc(sizeof(*uuidCache)); } else { for (last = uuidCache; last->next; last = last->next) ; last->next = smalloc(sizeof(*uuidCache)); last = last->next; } last->next = NULL; last->device = device; last->label = label; memcpy(last->uuid, uuid, sizeof(last->uuid)); } static void uuidcache_init(void) { char line[100]; char *s; int ma, mi, sz; static char ptname[100]; FILE *procpt; char uuid[16], *label = NULL; char device[110]; int handleOnFirst; if (uuidCache) { return; } procpt = fopen(PROC_PARTITIONS, "r"); if (procpt == NULL) { return; } for (int firstPass = 1; firstPass >= 0; firstPass--) { fseek(procpt, 0, SEEK_SET); while (fgets(line, sizeof(line), procpt)) { if (sscanf(line, " %d %d %d %[^\n ]", &ma, &mi, &sz, ptname) != 4) { continue; } /* skip extended partitions (heuristic: size 1) */ if (sz == 1) { continue; } /* look only at md devices on first pass */ handleOnFirst = !strncmp(ptname, "md", 2); if (firstPass != handleOnFirst) { continue; } /* skip entire disk (minor 0, 64, ... on ide; 0, 16, ... on sd) */ /* heuristic: partition name ends in a digit */ for (s = ptname; *s; s++) ; if (isdigit((int)s[-1])) { /* * Note: this is a heuristic only - there is no reason * why these devices should live in /dev. * Perhaps this directory should be specifiable by option. * One might for example have /devlabel with links to /dev * for the devices that may be accessed in this way. * (This is useful, if the cdrom on /dev/hdc must not * be accessed.) */ snprintf(device, sizeof(device), "%s/%s", DEVLABELDIR, ptname); if (!get_label_uuid(device, &label, uuid)) { uuidcache_addentry(sstrdup(device), label, uuid); } } } } fclose(procpt); } static unsigned char fromhex(char c) { if (isdigit((int)c)) { return c - '0'; } else if (islower((int)c)) { return c - 'a' + 10; } else { return c - 'A' + 10; } } static char *get_spec_by_x(int n, const char *t) { struct uuidCache_s *uc; uuidcache_init(); uc = uuidCache; while (uc) { switch (n) { case UUID: if (!memcmp(t, uc->uuid, sizeof(uc->uuid))) { return sstrdup(uc->device); } break; case VOL: if (!strcmp(t, uc->label)) { return sstrdup(uc->device); } break; } uc = uc->next; } return NULL; } static char *get_spec_by_uuid(const char *s) { char uuid[16]; if (strlen(s) != 36 || s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-') { goto bad_uuid; } for (int i = 0; i < 16; i++) { if (*s == '-') { s++; } if (!isxdigit((int)s[0]) || !isxdigit((int)s[1])) { goto bad_uuid; } uuid[i] = ((fromhex(s[0]) << 4) | fromhex(s[1])); s += 2; } return get_spec_by_x(UUID, uuid); bad_uuid: DEBUG("utils_mount: Found an invalid UUID: %s", s); return NULL; } static char *get_spec_by_volume_label(const char *s) { return get_spec_by_x(VOL, s); } static char *get_device_name(const char *optstr) { char *rc; if (optstr == NULL) { return NULL; } else if (strncmp(optstr, "UUID=", 5) == 0) { DEBUG("utils_mount: TODO: check UUID= code!"); rc = get_spec_by_uuid(optstr + 5); } else if (strncmp(optstr, "LABEL=", 6) == 0) { DEBUG("utils_mount: TODO: check LABEL= code!"); rc = get_spec_by_volume_label(optstr + 6); } else { rc = sstrdup(optstr); } if (!rc) { DEBUG("utils_mount: Error checking device name: optstr = %s", optstr); } return rc; } /* What weird OS is this..? I can't find any info with google :/ -octo */ #if HAVE_LISTMNTENT && 0 static cu_mount_t *cu_mount_listmntent(void) { cu_mount_t *last = *list; struct mntent *mnt; struct tabmntent *mntlist; if (listmntent(&mntlist, COLLECTD_MNTTAB, NULL, NULL) < 0) { #if COLLECT_DEBUG DEBUG("utils_mount: calling listmntent() failed: %s", STRERRNO); #endif /* COLLECT_DEBUG */ } for (struct tabmntent *p = mntlist; p; p = p->next) { char *loop = NULL, *device = NULL; mnt = p->ment; loop = cu_mount_getoptionvalue(mnt->mnt_opts, "loop="); if (loop == NULL) { /* no loop= mount */ device = get_device_name(mnt->mnt_fsname); if (device == NULL) { DEBUG("utils_mount: can't get devicename for fs (%s) %s (%s)" ": ignored", mnt->mnt_type, mnt->mnt_dir, mnt->mnt_fsname); continue; } } else { device = loop; } if (*list == NULL) { *list = (cu_mount_t *)smalloc(sizeof(cu_mount_t)); last = *list; } else { while (last->next != NULL) { /* is last really last? */ last = last->next; } last->next = (cu_mount_t *)smalloc(sizeof(cu_mount_t)); last = last->next; } last->dir = sstrdup(mnt->mnt_dir); last->spec_device = sstrdup(mnt->mnt_fsname); last->device = device; last->type = sstrdup(mnt->mnt_type); last->options = sstrdup(mnt->mnt_opts); last->next = NULL; } /* for(p = mntlist; p; p = p->next) */ return last; } /* cu_mount_t *cu_mount_listmntent(void) */ /* #endif HAVE_LISTMNTENT */ /* 4.4BSD and Mac OS X (getfsstat) or NetBSD (getvfsstat) */ #elif HAVE_GETVFSSTAT || HAVE_GETFSSTAT static cu_mount_t *cu_mount_getfsstat(void) { #if HAVE_GETFSSTAT #define STRUCT_STATFS struct statfs #define CMD_STATFS getfsstat #define FLAGS_STATFS MNT_NOWAIT /* #endif HAVE_GETFSSTAT */ #elif HAVE_GETVFSSTAT #define STRUCT_STATFS struct statvfs #define CMD_STATFS getvfsstat #define FLAGS_STATFS ST_NOWAIT #endif /* HAVE_GETVFSSTAT */ int bufsize; STRUCT_STATFS *buf; int num; cu_mount_t *first = NULL; cu_mount_t *last = NULL; cu_mount_t *new = NULL; /* Get the number of mounted file systems */ if ((bufsize = CMD_STATFS(NULL, 0, FLAGS_STATFS)) < 1) { #if COLLECT_DEBUG DEBUG("utils_mount: getv?fsstat failed: %s", STRERRNO); #endif /* COLLECT_DEBUG */ return NULL; } if ((buf = calloc(bufsize, sizeof(*buf))) == NULL) return NULL; /* The bufsize needs to be passed in bytes. Really. This is not in the * manpage.. -octo */ if ((num = CMD_STATFS(buf, bufsize * sizeof(STRUCT_STATFS), FLAGS_STATFS)) < 1) { #if COLLECT_DEBUG DEBUG("utils_mount: getv?fsstat failed: %s", STRERRNO); #endif /* COLLECT_DEBUG */ free(buf); return NULL; } for (int i = 0; i < num; i++) { if ((new = calloc(1, sizeof(*new))) == NULL) break; /* Copy values from `struct mnttab' */ new->dir = sstrdup(buf[i].f_mntonname); new->spec_device = sstrdup(buf[i].f_mntfromname); new->type = sstrdup(buf[i].f_fstypename); new->options = NULL; new->device = get_device_name(new->options); new->next = NULL; /* Append to list */ if (first == NULL) { first = new; last = new; } else { last->next = new; last = new; } } free(buf); return first; } /* #endif HAVE_GETVFSSTAT || HAVE_GETFSSTAT */ /* Solaris (SunOS 10): int getmntent(FILE *fp, struct mnttab *mp); */ #elif HAVE_TWO_GETMNTENT || HAVE_GEN_GETMNTENT || HAVE_SUN_GETMNTENT static cu_mount_t *cu_mount_gen_getmntent(void) { struct mnttab mt; FILE *fp; cu_mount_t *first = NULL; cu_mount_t *last = NULL; cu_mount_t *new = NULL; DEBUG("utils_mount: (void); COLLECTD_MNTTAB = %s", COLLECTD_MNTTAB); if ((fp = fopen(COLLECTD_MNTTAB, "r")) == NULL) { ERROR("fopen (%s): %s", COLLECTD_MNTTAB, STRERRNO); return NULL; } while (getmntent(fp, &mt) == 0) { if ((new = calloc(1, sizeof(*new))) == NULL) break; /* Copy values from `struct mnttab' */ new->dir = sstrdup(mt.mnt_mountp); new->spec_device = sstrdup(mt.mnt_special); new->type = sstrdup(mt.mnt_fstype); new->options = sstrdup(mt.mnt_mntopts); new->device = get_device_name(new->options); new->next = NULL; /* Append to list */ if (first == NULL) { first = new; last = new; } else { last->next = new; last = new; } } fclose(fp); return first; } /* static cu_mount_t *cu_mount_gen_getmntent (void) */ #elif HAVE_SEQ_GETMNTENT #warn "This version of `getmntent' hat not yet been implemented!" /* #endif HAVE_SEQ_GETMNTENT */ #elif HAVE_GETMNTENT_R static cu_mount_t *cu_mount_getmntent(void) { FILE *fp; struct mntent me; char mntbuf[1024]; cu_mount_t *first = NULL; cu_mount_t *last = NULL; cu_mount_t *new = NULL; DEBUG("utils_mount: (void); COLLECTD_MNTTAB = %s", COLLECTD_MNTTAB); if ((fp = setmntent(COLLECTD_MNTTAB, "r")) == NULL) { ERROR("setmntent (%s): %s", COLLECTD_MNTTAB, STRERRNO); return NULL; } while (getmntent_r(fp, &me, mntbuf, sizeof(mntbuf))) { if ((new = calloc(1, sizeof(*new))) == NULL) break; /* Copy values from `struct mntent *' */ new->dir = sstrdup(me.mnt_dir); new->spec_device = sstrdup(me.mnt_fsname); new->type = sstrdup(me.mnt_type); new->options = sstrdup(me.mnt_opts); new->device = get_device_name(new->options); new->next = NULL; DEBUG("utils_mount: new = {dir = %s, spec_device = %s, type = %s, options " "= %s, device = %s}", new->dir, new->spec_device, new->type, new->options, new->device); /* Append to list */ if (first == NULL) { first = new; last = new; } else { last->next = new; last = new; } } endmntent(fp); DEBUG("utils_mount: return 0x%p", (void *)first); return first; } /* HAVE_GETMNTENT_R */ #elif HAVE_ONE_GETMNTENT static cu_mount_t *cu_mount_getmntent(void) { FILE *fp; struct mntent *me; cu_mount_t *first = NULL; cu_mount_t *last = NULL; cu_mount_t *new = NULL; DEBUG("utils_mount: (void); COLLECTD_MNTTAB = %s", COLLECTD_MNTTAB); if ((fp = setmntent(COLLECTD_MNTTAB, "r")) == NULL) { ERROR("setmntent (%s): %s", COLLECTD_MNTTAB, STRERRNO); return NULL; } while ((me = getmntent(fp)) != NULL) { if ((new = calloc(1, sizeof(*new))) == NULL) break; /* Copy values from `struct mntent *' */ new->dir = sstrdup(me->mnt_dir); new->spec_device = sstrdup(me->mnt_fsname); new->type = sstrdup(me->mnt_type); new->options = sstrdup(me->mnt_opts); new->device = get_device_name(new->options); new->next = NULL; DEBUG("utils_mount: new = {dir = %s, spec_device = %s, type = %s, options " "= %s, device = %s}", new->dir, new->spec_device, new->type, new->options, new->device); /* Append to list */ if (first == NULL) { first = new; last = new; } else { last->next = new; last = new; } } endmntent(fp); DEBUG("utils_mount: return 0x%p", (void *)first); return first; } #endif /* HAVE_ONE_GETMNTENT */ /* *** *** *** ******************************************** *** *** *** */ /* *** *** *** *** *** *** public functions *** *** *** *** *** *** */ /* *** *** *** ******************************************** *** *** *** */ cu_mount_t *cu_mount_getlist(cu_mount_t **list) { cu_mount_t *new; cu_mount_t *first = NULL; cu_mount_t *last = NULL; if (list == NULL) return NULL; if (*list != NULL) { first = *list; last = first; while (last->next != NULL) last = last->next; } #if HAVE_LISTMNTENT && 0 new = cu_mount_listmntent(); #elif HAVE_GETVFSSTAT || HAVE_GETFSSTAT new = cu_mount_getfsstat(); #elif HAVE_TWO_GETMNTENT || HAVE_GEN_GETMNTENT || HAVE_SUN_GETMNTENT new = cu_mount_gen_getmntent(); #elif HAVE_SEQ_GETMNTENT #error "This version of `getmntent' hat not yet been implemented!" #elif HAVE_GETMNTENT_R new = cu_mount_getmntent(); #elif HAVE_ONE_GETMNTENT new = cu_mount_getmntent(); #else #error "Could not determine how to find mountpoints." #endif if (first != NULL) { last->next = new; } else { first = new; last = new; *list = first; } while ((last != NULL) && (last->next != NULL)) last = last->next; return last; } /* cu_mount_t *cu_mount_getlist(cu_mount_t **list) */ void cu_mount_freelist(cu_mount_t *list) { cu_mount_t *next; for (cu_mount_t *this = list; this != NULL; this = next) { next = this->next; sfree(this->dir); sfree(this->spec_device); sfree(this->device); sfree(this->type); sfree(this->options); sfree(this); } } /* void cu_mount_freelist(cu_mount_t *list) */ char *cu_mount_checkoption(char *line, const char *keyword, int full) { char *line2, *l2, *p1, *p2; size_t l; if (line == NULL || keyword == NULL) { return NULL; } if (full != 0) { full = 1; } line2 = sstrdup(line); l2 = line2; while (*l2 != '\0') { if (*l2 == ',') { *l2 = '\0'; } l2++; } l = strlen(keyword); p1 = line - 1; p2 = strchr(line, ','); do { if (strncmp(line2 + (p1 - line) + 1, keyword, l + full) == 0) { free(line2); return p1 + 1; } p1 = p2; if (p1 != NULL) { p2 = strchr(p1 + 1, ','); } } while (p1 != NULL); free(line2); return NULL; } /* char *cu_mount_checkoption(char *line, char *keyword, int full) */ char *cu_mount_getoptionvalue(char *line, const char *keyword) { char *r; r = cu_mount_checkoption(line, keyword, 0); if (r != NULL) { char *p; r += strlen(keyword); p = strchr(r, ','); if (p == NULL) { return sstrdup(r); } else { char *m; if ((p - r) == 1) { return NULL; } m = smalloc(p - r + 1); sstrncpy(m, r, p - r + 1); return m; } } return r; } /* char *cu_mount_getoptionvalue(char *line, const char *keyword) */ int cu_mount_type(const char *type) { if (strcmp(type, "ext3") == 0) return CUMT_EXT3; if (strcmp(type, "ext2") == 0) return CUMT_EXT2; if (strcmp(type, "ufs") == 0) return CUMT_UFS; if (strcmp(type, "vxfs") == 0) return CUMT_VXFS; if (strcmp(type, "zfs") == 0) return CUMT_ZFS; return CUMT_UNKNOWN; } /* int cu_mount_type(const char *type) */