/* * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. * * This file is part of LVM2. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License v.2.1. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/misc/lib.h" #include "lib/filters/filter.h" #ifdef __linux__ #include #include static int _locate_sysfs_blocks(const char *sysfs_dir, char *path, size_t len, unsigned *sysfs_depth) { struct stat info; unsigned i; static const struct dir_class { const char path[32]; int depth; } classes[] = { /* * unified classification directory for all kernel subsystems * * /sys/subsystem/block/devices * |-- sda -> ../../../devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda * |-- sda1 -> ../../../devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1 * `-- sr0 -> ../../../devices/pci0000:00/0000:00:1f.2/host1/target1:0:0/1:0:0:0/block/sr0 * */ { "subsystem/block/devices", 0 }, /* * block subsystem as a class * * /sys/class/block * |-- sda -> ../../devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda * |-- sda1 -> ../../devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1 * `-- sr0 -> ../../devices/pci0000:00/0000:00:1f.2/host1/target1:0:0/1:0:0:0/block/sr0 * */ { "class/block", 0 }, /* * old block subsystem layout with nested directories * * /sys/block/ * |-- sda * | |-- capability * | |-- dev * ... * | |-- sda1 * | | |-- dev * ... * | * `-- sr0 * |-- capability * |-- dev * ... * */ { "block", 1 } }; for (i = 0; i < DM_ARRAY_SIZE(classes); ++i) if ((dm_snprintf(path, len, "%s%s", sysfs_dir, classes[i].path) >= 0) && (stat(path, &info) == 0)) { *sysfs_depth = classes[i].depth; return 1; } return 0; } /*---------------------------------------------------------------- * We need to store a set of dev_t. *--------------------------------------------------------------*/ struct entry { struct entry *next; dev_t dev; }; #define SET_BUCKETS 64 struct dev_set { struct dm_pool *mem; const char *sys_block; unsigned sysfs_depth; int initialised; struct entry *slots[SET_BUCKETS]; }; static struct dev_set *_dev_set_create(struct dm_pool *mem, const char *sys_block, unsigned sysfs_depth) { struct dev_set *ds; if (!(ds = dm_pool_zalloc(mem, sizeof(*ds)))) return NULL; ds->mem = mem; if (!(ds->sys_block = dm_pool_strdup(mem, sys_block))) return NULL; ds->sysfs_depth = sysfs_depth; ds->initialised = 0; return ds; } static unsigned _hash_dev(dev_t dev) { return (major(dev) ^ minor(dev)) & (SET_BUCKETS - 1); } /* * Doesn't check that the set already contains dev. */ static int _set_insert(struct dev_set *ds, dev_t dev) { struct entry *e; unsigned h = _hash_dev(dev); if (!(e = dm_pool_alloc(ds->mem, sizeof(*e)))) return 0; e->next = ds->slots[h]; e->dev = dev; ds->slots[h] = e; return 1; } static int _set_lookup(struct dev_set *ds, dev_t dev) { unsigned h = _hash_dev(dev); struct entry *e; for (e = ds->slots[h]; e; e = e->next) if (e->dev == dev) return 1; return 0; } /*---------------------------------------------------------------- * filter methods *--------------------------------------------------------------*/ static int _parse_dev(const char *file, FILE *fp, dev_t *result) { unsigned major, minor; char buffer[64]; if (!fgets(buffer, sizeof(buffer), fp)) { log_error("Empty sysfs device file: %s", file); return 0; } if (sscanf(buffer, "%u:%u", &major, &minor) != 2) { log_error("Incorrect format for sysfs device file: %s.", file); return 0; } *result = makedev(major, minor); return 1; } static int _read_dev(const char *file, dev_t *result) { int r; FILE *fp; if (!(fp = fopen(file, "r"))) { log_sys_error("fopen", file); return 0; } r = _parse_dev(file, fp, result); if (fclose(fp)) log_sys_error("fclose", file); return r; } /* * Recurse through sysfs directories, inserting any devs found. */ static int _read_devs(struct dev_set *ds, const char *dir, unsigned sysfs_depth) { struct dirent *d; DIR *dr; struct stat info; char path[PATH_MAX]; char file[PATH_MAX]; dev_t dev = { 0 }; int r = 1; if (!(dr = opendir(dir))) { log_sys_error("opendir", dir); return 0; } while ((d = readdir(dr))) { if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) continue; if (dm_snprintf(path, sizeof(path), "%s/%s", dir, d->d_name) < 0) { log_error("sysfs path name too long: %s in %s", d->d_name, dir); continue; } /* devices have a "dev" file */ if (dm_snprintf(file, sizeof(file), "%s/dev", path) < 0) { log_error("sysfs path name too long: %s in %s", d->d_name, dir); continue; } if (!stat(file, &info)) { /* recurse if we found a device and expect subdirs */ if (sysfs_depth) _read_devs(ds, path, sysfs_depth - 1); /* add the device we have found */ if (_read_dev(file, &dev)) _set_insert(ds, dev); } } if (closedir(dr)) log_sys_error("closedir", dir); return r; } static int _init_devs(struct dev_set *ds) { if (!_read_devs(ds, ds->sys_block, ds->sysfs_depth)) { ds->initialised = -1; return 0; } ds->initialised = 1; return 1; } static int _accept_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name) { struct dev_set *ds = (struct dev_set *) f->private; if (!ds->initialised) _init_devs(ds); /* Pass through if initialisation failed */ if (ds->initialised != 1) return 1; if (!_set_lookup(ds, dev->dev)) { log_debug_devs("%s: Skipping (sysfs)", dev_name(dev)); return 0; } return 1; } static void _destroy(struct dev_filter *f) { struct dev_set *ds = (struct dev_set *) f->private; if (f->use_count) log_error(INTERNAL_ERROR "Destroying sysfs filter while in use %u times.", f->use_count); dm_pool_destroy(ds->mem); } struct dev_filter *sysfs_filter_create(void) { const char *sysfs_dir = dm_sysfs_dir(); char sys_block[PATH_MAX]; unsigned sysfs_depth; struct dm_pool *mem; struct dev_set *ds; struct dev_filter *f; if (!*sysfs_dir) { log_verbose("No proc filesystem found: skipping sysfs filter"); return NULL; } if (!_locate_sysfs_blocks(sysfs_dir, sys_block, sizeof(sys_block), &sysfs_depth)) return NULL; if (!(mem = dm_pool_create("sysfs", 256))) { log_error("sysfs pool creation failed"); return NULL; } if (!(ds = _dev_set_create(mem, sys_block, sysfs_depth))) { log_error("sysfs dev_set creation failed"); goto bad; } if (!(f = dm_pool_zalloc(mem, sizeof(*f)))) goto_bad; f->passes_filter = _accept_p; f->destroy = _destroy; f->use_count = 0; f->private = ds; f->name = "sysfs"; log_debug_devs("Sysfs filter initialised."); return f; bad: dm_pool_destroy(mem); return NULL; } #else struct dev_filter *sysfs_filter_create(const char *sysfs_dir __attribute__((unused))) { return NULL; } #endif