| /* |
| * 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.h" |
| #include "filter.h" |
| |
| #ifdef __linux__ |
| |
| #include <dirent.h> |
| |
| 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_info("sysfs device file not correct format"); |
| 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 dev_filter *f, struct device *dev) |
| { |
| 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; |
| } else |
| 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; |
| |
| 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 |