mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-22 17:35:59 +03:00
fcbef05aae
Hmm rpmlint suggest fsf is using a different address these days, so lets keep it up-to-date
304 lines
6.7 KiB
C
304 lines
6.7 KiB
C
/*
|
|
* Copyright (C) 2011 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"
|
|
#include "activate.h"
|
|
#ifdef UDEV_SYNC_SUPPORT
|
|
#include <libudev.h>
|
|
#include "dev-ext-udev-constants.h"
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
|
|
#include <dirent.h>
|
|
|
|
#define MPATH_PREFIX "mpath-"
|
|
|
|
static const char *_get_sysfs_name(struct device *dev)
|
|
{
|
|
const char *name;
|
|
|
|
if (!(name = strrchr(dev_name(dev), '/'))) {
|
|
log_error("Cannot find '/' in device name.");
|
|
return NULL;
|
|
}
|
|
name++;
|
|
|
|
if (!*name) {
|
|
log_error("Device name is not valid.");
|
|
return NULL;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
static const char *_get_sysfs_name_by_devt(const char *sysfs_dir, dev_t devno,
|
|
char *buf, size_t buf_size)
|
|
{
|
|
const char *name;
|
|
char path[PATH_MAX];
|
|
int size;
|
|
|
|
if (dm_snprintf(path, sizeof(path), "%s/dev/block/%d:%d", sysfs_dir,
|
|
(int) MAJOR(devno), (int) MINOR(devno)) < 0) {
|
|
log_error("Sysfs path string is too long.");
|
|
return NULL;
|
|
}
|
|
|
|
if ((size = readlink(path, buf, buf_size - 1)) < 0) {
|
|
log_sys_error("readlink", path);
|
|
return NULL;
|
|
}
|
|
buf[size] = '\0';
|
|
|
|
if (!(name = strrchr(buf, '/'))) {
|
|
log_error("Cannot find device name in sysfs path.");
|
|
return NULL;
|
|
}
|
|
name++;
|
|
|
|
return name;
|
|
}
|
|
|
|
static int _get_sysfs_string(const char *path, char *buffer, int max_size)
|
|
{
|
|
FILE *fp;
|
|
int r = 0;
|
|
|
|
if (!(fp = fopen(path, "r"))) {
|
|
log_sys_error("fopen", path);
|
|
return 0;
|
|
}
|
|
|
|
if (!fgets(buffer, max_size, fp))
|
|
log_sys_error("fgets", path);
|
|
else
|
|
r = 1;
|
|
|
|
if (fclose(fp))
|
|
log_sys_error("fclose", path);
|
|
|
|
return r;
|
|
}
|
|
|
|
static int _get_sysfs_get_major_minor(const char *sysfs_dir, const char *kname, int *major, int *minor)
|
|
{
|
|
char path[PATH_MAX], buffer[64];
|
|
|
|
if (dm_snprintf(path, sizeof(path), "%s/block/%s/dev", sysfs_dir, kname) < 0) {
|
|
log_error("Sysfs path string is too long.");
|
|
return 0;
|
|
}
|
|
|
|
if (!_get_sysfs_string(path, buffer, sizeof(buffer)))
|
|
return_0;
|
|
|
|
if (sscanf(buffer, "%d:%d", major, minor) != 2) {
|
|
log_error("Failed to parse major minor from %s", buffer);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _get_parent_mpath(const char *dir, char *name, int max_size)
|
|
{
|
|
struct dirent *d;
|
|
DIR *dr;
|
|
int r = 0;
|
|
|
|
if (!(dr = opendir(dir))) {
|
|
log_sys_error("opendir", dir);
|
|
return 0;
|
|
}
|
|
|
|
*name = '\0';
|
|
while ((d = readdir(dr))) {
|
|
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
|
continue;
|
|
|
|
/* There should be only one holder if it is multipath */
|
|
if (*name) {
|
|
r = 0;
|
|
break;
|
|
}
|
|
|
|
strncpy(name, d->d_name, max_size);
|
|
r = 1;
|
|
}
|
|
|
|
if (closedir(dr))
|
|
log_sys_error("closedir", dir);
|
|
|
|
return r;
|
|
}
|
|
|
|
#ifdef UDEV_SYNC_SUPPORT
|
|
static int _udev_dev_is_mpath(struct device *dev)
|
|
{
|
|
const char *value;
|
|
struct dev_ext *ext;
|
|
|
|
if (!(ext = dev_ext_get(dev)))
|
|
return_0;
|
|
|
|
value = udev_device_get_property_value((struct udev_device *)ext->handle, DEV_EXT_UDEV_BLKID_TYPE);
|
|
if (value && !strcmp(value, DEV_EXT_UDEV_BLKID_TYPE_MPATH))
|
|
return 1;
|
|
|
|
value = udev_device_get_property_value((struct udev_device *)ext->handle, DEV_EXT_UDEV_MPATH_DEVICE_PATH);
|
|
if (value && !strcmp(value, "1"))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int _udev_dev_is_mpath(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int _native_dev_is_mpath(struct dev_filter *f, struct device *dev)
|
|
{
|
|
struct dev_types *dt = (struct dev_types *) f->private;
|
|
const char *part_name, *name;
|
|
struct stat info;
|
|
char path[PATH_MAX], parent_name[PATH_MAX];
|
|
const char *sysfs_dir = dm_sysfs_dir();
|
|
int major = MAJOR(dev->dev);
|
|
int minor = MINOR(dev->dev);
|
|
dev_t primary_dev;
|
|
|
|
/* Limit this filter only to SCSI devices */
|
|
if (!major_is_scsi_device(dt, MAJOR(dev->dev)))
|
|
return 0;
|
|
|
|
switch (dev_get_primary_dev(dt, dev, &primary_dev)) {
|
|
case 2: /* The dev is partition. */
|
|
part_name = dev_name(dev); /* name of original dev for log_debug msg */
|
|
if (!(name = _get_sysfs_name_by_devt(sysfs_dir, primary_dev, parent_name, sizeof(parent_name))))
|
|
return_0;
|
|
log_debug_devs("%s: Device is a partition, using primary "
|
|
"device %s for mpath component detection",
|
|
part_name, name);
|
|
break;
|
|
case 1: /* The dev is already a primary dev. Just continue with the dev. */
|
|
if (!(name = _get_sysfs_name(dev)))
|
|
return_0;
|
|
break;
|
|
default: /* 0, error. */
|
|
log_error("Failed to get primary device for %d:%d.", major, minor);
|
|
return 0;
|
|
}
|
|
|
|
if (dm_snprintf(path, sizeof(path), "%s/block/%s/holders", sysfs_dir, name) < 0) {
|
|
log_error("Sysfs path to check mpath is too long.");
|
|
return 0;
|
|
}
|
|
|
|
/* also will filter out partitions */
|
|
if (stat(path, &info))
|
|
return 0;
|
|
|
|
if (!S_ISDIR(info.st_mode)) {
|
|
log_error("Path %s is not a directory.", path);
|
|
return 0;
|
|
}
|
|
|
|
if (!_get_parent_mpath(path, parent_name, sizeof(parent_name)))
|
|
return 0;
|
|
|
|
if (!_get_sysfs_get_major_minor(sysfs_dir, parent_name, &major, &minor))
|
|
return_0;
|
|
|
|
if (major != dt->device_mapper_major)
|
|
return 0;
|
|
|
|
return lvm_dm_prefix_check(major, minor, MPATH_PREFIX);
|
|
}
|
|
|
|
static int _dev_is_mpath(struct dev_filter *f, struct device *dev)
|
|
{
|
|
if (dev->ext.src == DEV_EXT_NONE)
|
|
return _native_dev_is_mpath(f, dev);
|
|
|
|
if (dev->ext.src == DEV_EXT_UDEV)
|
|
return _udev_dev_is_mpath(dev);
|
|
|
|
log_error(INTERNAL_ERROR "Missing hook for mpath recognition "
|
|
"using external device info source %s", dev_ext_name(dev));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define MSG_SKIPPING "%s: Skipping mpath component device"
|
|
|
|
static int _ignore_mpath(struct dev_filter *f, struct device *dev)
|
|
{
|
|
if (_dev_is_mpath(f, dev) == 1) {
|
|
if (dev->ext.src == DEV_EXT_NONE)
|
|
log_debug_devs(MSG_SKIPPING, dev_name(dev));
|
|
else
|
|
log_debug_devs(MSG_SKIPPING " [%s:%p]", dev_name(dev),
|
|
dev_ext_name(dev), dev->ext.handle);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void _destroy(struct dev_filter *f)
|
|
{
|
|
if (f->use_count)
|
|
log_error(INTERNAL_ERROR "Destroying mpath filter while in use %u times.", f->use_count);
|
|
|
|
dm_free(f);
|
|
}
|
|
|
|
struct dev_filter *mpath_filter_create(struct dev_types *dt)
|
|
{
|
|
const char *sysfs_dir = dm_sysfs_dir();
|
|
struct dev_filter *f;
|
|
|
|
if (!*sysfs_dir) {
|
|
log_verbose("No proc filesystem found: skipping multipath filter");
|
|
return NULL;
|
|
}
|
|
|
|
if (!(f = dm_zalloc(sizeof(*f)))) {
|
|
log_error("mpath filter allocation failed");
|
|
return NULL;
|
|
}
|
|
|
|
f->passes_filter = _ignore_mpath;
|
|
f->destroy = _destroy;
|
|
f->use_count = 0;
|
|
f->private = dt;
|
|
|
|
log_debug_devs("mpath filter initialised.");
|
|
|
|
return f;
|
|
}
|
|
|
|
#else
|
|
|
|
struct dev_filter *mpath_filter_create(struct dev_types *dt)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|