mirror of
git://sourceware.org/git/lvm2.git
synced 2025-01-21 22:04:19 +03:00
ef96186add
The devices file /etc/lvm/devices/system.devices is a list of devices that lvm can use. This is the default system devices file, which is specified in lvm.conf devices/devicesfile. The command option --devicesfile <filename> allows lvm to be used with a different set of devices. This allows different applications to use lvm on different sets of devices, e.g. system devices do not need to be exposed to an application using lvm on its own devices, and application devices do not need to be exposed to the system. In most cases (with limited exceptions), lvm will not read or use a device not listed in the devices file. When the devices file is used, the regex filter is not used, and the filter settings in lvm.conf are ignored. filter-deviceid is used when the devices file is enabled, and rejects any device that does not match an entry in the devices file. Set use_devicesfile=0 in lvm.conf or set --devicesfile "" on the command line to disable the use of a devices file. When disabled, lvm will see and use any device on the system that passes the regex filter (and other standard filters.) A device ID, e.g. wwid or serial number from sysfs, is a unique ID that identifies a device. The device ID is generally independent of the device content, and lvm can get the device ID without reading the device. The device ID is used in the devices file as the primary method of identifying device entries, and is also included in VG metadata for PVs. Each device_id has a device_id_type which indicates where the device_id comes from, e.g. "sys_wwid" means the device_id comes from the sysfs wwid file. Others are sys_serial, mpath_uuid, loop_file, md_uuid, devname. (devname is the device path, which is a fallback when no other proper device_id_type is available.) filter-deviceid permits lvm to use only devices on the system that have a device_id matching a devices file entry. Using the device_id, lvm can determine the set of devices to use without reading any devices, so the devices file will constrain lvm in two ways: 1. it limits the devices that lvm will read. 2. it limits the devices that lvm will use. In some uncommon cases, e.g. when devices have no unique ID and device_id has to fall back to using the devname, lvm may need to read all devices on the system to determine which ones correspond to the devices file entries. In this case, the devices file does not limit the devices that lvm reads, but it does limit the devices that lvm uses. pvcreate/vgcreate/vgextend are not constrained by the devices file, and will look outside it to find the new PV. They assign the new PV a device_id and add it to the devices file. It is also possible to explicitly add new PVs to the devices file before using them in pvcreate/etc, in which case these commands would not need to look outside the devices file for the new device. vgimportdevices VG looks at all devices on the system to find an existing VG and add its devices to the devices file. The command is not limited by an existing devices file. The command will also add device_ids to the VG metadata if the VG does not yet include device_ids. vgimportdevices -a imports devices for all accessible VGs. Since vgimportdevices does not limit itself to devices in an existing devices file, the lvm.conf regex filter applies. Adding --foreign will import devices for foreign VGs, but device_ids are not added to foreign VGs. Incomplete VGs are not imported. The lvmdevices command manages the devices file. The primary purpose is to edit the devices file, but it will read PV headers to find/check PVIDs. (It does not read, process or modify VG metadata.) lvmdevices . Displays devices file entries. lvmdevices --check . Checks devices file entries. lvmdevices --update . Updates devices file entries. lvmdevices --adddev <devname> . Adds devices_file entry (reads pv header). lvmdevices --deldev <devname> . Removes devices file entry. lvmdevices --addpvid <pvid> . Reads pv header of all devices to find <pvid>, and if found adds devices file entry. lvmdevices --delpvid <pvid> . Removes devices file entry. The vgimportclone command has a new option --importdevices that does the equivalent of vgimportdevices with the cloned devices that are being imported. The devices are "uncloned" (new vgname and pvids) while at the same time adding the devices to the devices file. This allows cloned PVs to be imported without duplicate PVs ever appearing on the system. The command option --devices <devnames> allows a specific list of devices to be exposed to the lvm command, overriding the devices file.
1317 lines
33 KiB
C
1317 lines
33 KiB
C
/*
|
|
* Copyright (C) 2013 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 "base/memory/zalloc.h"
|
|
#include "lib/misc/lib.h"
|
|
#include "lib/device/dev-type.h"
|
|
#include "lib/device/device-types.h"
|
|
#include "lib/mm/xlate.h"
|
|
#include "lib/config/config.h"
|
|
#include "lib/metadata/metadata.h"
|
|
#include "lib/device/bcache.h"
|
|
#include "lib/label/label.h"
|
|
#include "lib/commands/toolcontext.h"
|
|
|
|
#ifdef BLKID_WIPING_SUPPORT
|
|
#include <blkid.h>
|
|
#endif
|
|
|
|
#ifdef UDEV_SYNC_SUPPORT
|
|
#include <libudev.h>
|
|
#include "lib/device/dev-ext-udev-constants.h"
|
|
#endif
|
|
|
|
#include <libgen.h>
|
|
#include <ctype.h>
|
|
|
|
/*
|
|
* dev is pmem if /sys/dev/block/<major>:<minor>/queue/dax is 1
|
|
*/
|
|
|
|
int dev_is_pmem(struct device *dev)
|
|
{
|
|
FILE *fp;
|
|
char path[PATH_MAX];
|
|
int is_pmem = 0;
|
|
|
|
if (dm_snprintf(path, sizeof(path), "%sdev/block/%d:%d/queue/dax",
|
|
dm_sysfs_dir(),
|
|
(int) MAJOR(dev->dev),
|
|
(int) MINOR(dev->dev)) < 0) {
|
|
log_warn("Sysfs path for %s dax is too long.", dev_name(dev));
|
|
return 0;
|
|
}
|
|
|
|
if (!(fp = fopen(path, "r")))
|
|
return 0;
|
|
|
|
if (fscanf(fp, "%d", &is_pmem) != 1)
|
|
log_warn("Failed to parse DAX %s.", path);
|
|
|
|
if (is_pmem)
|
|
log_debug("%s is pmem", dev_name(dev));
|
|
|
|
if (fclose(fp))
|
|
log_sys_debug("fclose", path);
|
|
|
|
return is_pmem ? 1 : 0;
|
|
}
|
|
|
|
/*
|
|
* An nvme device has major number 259 (BLKEXT), minor number <minor>,
|
|
* and reading /sys/dev/block/259:<minor>/device/dev shows a character
|
|
* device cmajor:cminor where cmajor matches the major number of the
|
|
* nvme character device entry in /proc/devices. Checking all of that
|
|
* is excessive and unnecessary compared to just comparing /dev/name*.
|
|
*/
|
|
|
|
int dev_is_nvme(struct dev_types *dt, struct device *dev)
|
|
{
|
|
struct dm_str_list *strl;
|
|
|
|
if (dev->flags & DEV_IS_NVME)
|
|
return 1;
|
|
|
|
dm_list_iterate_items(strl, &dev->aliases) {
|
|
if (!strncmp(strl->str, "/dev/nvme", 9)) {
|
|
log_debug("Found nvme device %s", dev_name(dev));
|
|
dev->flags |= DEV_IS_NVME;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int dev_is_lv(struct device *dev)
|
|
{
|
|
FILE *fp;
|
|
char path[PATH_MAX];
|
|
char buffer[64];
|
|
int ret = 0;
|
|
|
|
if (dm_snprintf(path, sizeof(path), "%sdev/block/%d:%d/dm/uuid",
|
|
dm_sysfs_dir(),
|
|
(int) MAJOR(dev->dev),
|
|
(int) MINOR(dev->dev)) < 0) {
|
|
log_warn("Sysfs dm uuid path for %s is too long.", dev_name(dev));
|
|
return 0;
|
|
}
|
|
|
|
if (!(fp = fopen(path, "r")))
|
|
return 0;
|
|
|
|
if (!fgets(buffer, sizeof(buffer), fp))
|
|
log_debug("Failed to read %s.", path);
|
|
else if (!strncmp(buffer, "LVM-", 4))
|
|
ret = 1;
|
|
|
|
if (fclose(fp))
|
|
log_sys_debug("fclose", path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct dev_types *create_dev_types(const char *proc_dir,
|
|
const struct dm_config_node *cn)
|
|
{
|
|
struct dev_types *dt;
|
|
char line[80];
|
|
char proc_devices[PATH_MAX];
|
|
FILE *pd = NULL;
|
|
int i, j = 0;
|
|
int line_maj = 0;
|
|
int blocksection = 0;
|
|
size_t dev_len = 0;
|
|
const struct dm_config_value *cv;
|
|
const char *name;
|
|
char *nl;
|
|
|
|
if (!(dt = zalloc(sizeof(struct dev_types)))) {
|
|
log_error("Failed to allocate device type register.");
|
|
return NULL;
|
|
}
|
|
|
|
if (!*proc_dir) {
|
|
log_verbose("No proc filesystem found: using all block device types");
|
|
for (i = 0; i < NUMBER_OF_MAJORS; i++)
|
|
dt->dev_type_array[i].max_partitions = 1;
|
|
return dt;
|
|
}
|
|
|
|
if (dm_snprintf(proc_devices, sizeof(proc_devices),
|
|
"%s/devices", proc_dir) < 0) {
|
|
log_error("Failed to create /proc/devices string");
|
|
goto bad;
|
|
}
|
|
|
|
if (!(pd = fopen(proc_devices, "r"))) {
|
|
log_sys_error("fopen", proc_devices);
|
|
goto bad;
|
|
}
|
|
|
|
while (fgets(line, sizeof(line), pd) != NULL) {
|
|
i = 0;
|
|
while (line[i] == ' ')
|
|
i++;
|
|
|
|
/* If it's not a number it may be name of section */
|
|
line_maj = atoi(line + i);
|
|
|
|
if (line_maj < 0 || line_maj >= NUMBER_OF_MAJORS) {
|
|
/*
|
|
* Device numbers shown in /proc/devices are actually direct
|
|
* numbers passed to registering function, however the kernel
|
|
* uses only 12 bits, so use just 12 bits for major.
|
|
*/
|
|
if ((nl = strchr(line, '\n'))) *nl = '\0';
|
|
log_warn("WARNING: /proc/devices line: %s, replacing major with %d.",
|
|
line, line_maj & (NUMBER_OF_MAJORS - 1));
|
|
line_maj &= (NUMBER_OF_MAJORS - 1);
|
|
}
|
|
|
|
if (!line_maj) {
|
|
blocksection = (line[i] == 'B') ? 1 : 0;
|
|
continue;
|
|
}
|
|
|
|
/* We only want block devices ... */
|
|
if (!blocksection)
|
|
continue;
|
|
|
|
/* Find the start of the device major name */
|
|
while (line[i] != ' ' && line[i] != '\0')
|
|
i++;
|
|
while (line[i] == ' ')
|
|
i++;
|
|
|
|
/* Look for md device */
|
|
if (!strncmp("md", line + i, 2) && isspace(*(line + i + 2)))
|
|
dt->md_major = line_maj;
|
|
|
|
/* Look for blkext device */
|
|
if (!strncmp("blkext", line + i, 6) && isspace(*(line + i + 6)))
|
|
dt->blkext_major = line_maj;
|
|
|
|
/* Look for drbd device */
|
|
if (!strncmp("drbd", line + i, 4) && isspace(*(line + i + 4)))
|
|
dt->drbd_major = line_maj;
|
|
|
|
/* Look for DASD */
|
|
if (!strncmp("dasd", line + i, 4) && isspace(*(line + i + 4)))
|
|
dt->dasd_major = line_maj;
|
|
|
|
/* Look for EMC powerpath */
|
|
if (!strncmp("emcpower", line + i, 8) && isspace(*(line + i + 8)))
|
|
dt->emcpower_major = line_maj;
|
|
|
|
/* Look for Veritas Dynamic Multipathing */
|
|
if (!strncmp("VxDMP", line + i, 5) && isspace(*(line + i + 5)))
|
|
dt->vxdmp_major = line_maj;
|
|
|
|
if (!strncmp("loop", line + i, 4) && isspace(*(line + i + 4)))
|
|
dt->loop_major = line_maj;
|
|
|
|
if (!strncmp("power2", line + i, 6) && isspace(*(line + i + 6)))
|
|
dt->power2_major = line_maj;
|
|
|
|
/* Look for device-mapper device */
|
|
/* FIXME Cope with multiple majors */
|
|
if (!strncmp("device-mapper", line + i, 13) && isspace(*(line + i + 13)))
|
|
dt->device_mapper_major = line_maj;
|
|
|
|
/* Major is SCSI device */
|
|
if (!strncmp("sd", line + i, 2) && isspace(*(line + i + 2)))
|
|
dt->dev_type_array[line_maj].flags |= PARTITION_SCSI_DEVICE;
|
|
|
|
/* Go through the valid device names and if there is a
|
|
match store max number of partitions */
|
|
for (j = 0; _dev_known_types[j].name[0]; j++) {
|
|
dev_len = strlen(_dev_known_types[j].name);
|
|
if (dev_len <= strlen(line + i) &&
|
|
!strncmp(_dev_known_types[j].name, line + i, dev_len) &&
|
|
(line_maj < NUMBER_OF_MAJORS)) {
|
|
dt->dev_type_array[line_maj].max_partitions =
|
|
_dev_known_types[j].max_partitions;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!cn)
|
|
continue;
|
|
|
|
/* Check devices/types for local variations */
|
|
for (cv = cn->v; cv; cv = cv->next) {
|
|
if (cv->type != DM_CFG_STRING) {
|
|
log_error("Expecting string in devices/types "
|
|
"in config file");
|
|
if (fclose(pd))
|
|
log_sys_error("fclose", proc_devices);
|
|
goto bad;
|
|
}
|
|
dev_len = strlen(cv->v.str);
|
|
name = cv->v.str;
|
|
cv = cv->next;
|
|
if (!cv || cv->type != DM_CFG_INT) {
|
|
log_error("Max partition count missing for %s "
|
|
"in devices/types in config file",
|
|
name);
|
|
if (fclose(pd))
|
|
log_sys_error("fclose", proc_devices);
|
|
goto bad;
|
|
}
|
|
if (!cv->v.i) {
|
|
log_error("Zero partition count invalid for "
|
|
"%s in devices/types in config file",
|
|
name);
|
|
if (fclose(pd))
|
|
log_sys_error("fclose", proc_devices);
|
|
goto bad;
|
|
}
|
|
if (dev_len <= strlen(line + i) &&
|
|
!strncmp(name, line + i, dev_len) &&
|
|
(line_maj < NUMBER_OF_MAJORS)) {
|
|
dt->dev_type_array[line_maj].max_partitions = cv->v.i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fclose(pd))
|
|
log_sys_error("fclose", proc_devices);
|
|
|
|
return dt;
|
|
bad:
|
|
free(dt);
|
|
return NULL;
|
|
}
|
|
|
|
int dev_subsystem_part_major(struct dev_types *dt, struct device *dev)
|
|
{
|
|
dev_t primary_dev;
|
|
|
|
if (MAJOR(dev->dev) == dt->device_mapper_major)
|
|
return 1;
|
|
|
|
if (MAJOR(dev->dev) == dt->md_major)
|
|
return 1;
|
|
|
|
if (MAJOR(dev->dev) == dt->drbd_major)
|
|
return 1;
|
|
|
|
if (MAJOR(dev->dev) == dt->emcpower_major)
|
|
return 1;
|
|
|
|
if (MAJOR(dev->dev) == dt->power2_major)
|
|
return 1;
|
|
|
|
if (MAJOR(dev->dev) == dt->vxdmp_major)
|
|
return 1;
|
|
|
|
if ((MAJOR(dev->dev) == dt->blkext_major) &&
|
|
dev_get_primary_dev(dt, dev, &primary_dev) &&
|
|
(MAJOR(primary_dev) == dt->md_major))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char *dev_subsystem_name(struct dev_types *dt, struct device *dev)
|
|
{
|
|
if (dev->flags & DEV_IS_NVME)
|
|
return "NVME";
|
|
|
|
if (MAJOR(dev->dev) == dt->device_mapper_major)
|
|
return "DM";
|
|
|
|
if (MAJOR(dev->dev) == dt->md_major)
|
|
return "MD";
|
|
|
|
if (MAJOR(dev->dev) == dt->drbd_major)
|
|
return "DRBD";
|
|
|
|
if (MAJOR(dev->dev) == dt->dasd_major)
|
|
return "DASD";
|
|
|
|
if (MAJOR(dev->dev) == dt->emcpower_major)
|
|
return "EMCPOWER";
|
|
|
|
if (MAJOR(dev->dev) == dt->power2_major)
|
|
return "POWER2";
|
|
|
|
if (MAJOR(dev->dev) == dt->vxdmp_major)
|
|
return "VXDMP";
|
|
|
|
if (MAJOR(dev->dev) == dt->blkext_major)
|
|
return "BLKEXT";
|
|
|
|
if (MAJOR(dev->dev) == dt->loop_major)
|
|
return "LOOP";
|
|
|
|
return "";
|
|
}
|
|
|
|
int major_max_partitions(struct dev_types *dt, int major)
|
|
{
|
|
if (major >= NUMBER_OF_MAJORS)
|
|
return 0;
|
|
|
|
return dt->dev_type_array[major].max_partitions;
|
|
}
|
|
|
|
int major_is_scsi_device(struct dev_types *dt, int major)
|
|
{
|
|
if (major >= NUMBER_OF_MAJORS)
|
|
return 0;
|
|
|
|
return (dt->dev_type_array[major].flags & PARTITION_SCSI_DEVICE) ? 1 : 0;
|
|
}
|
|
|
|
static int _loop_is_with_partscan(struct device *dev)
|
|
{
|
|
FILE *fp;
|
|
int partscan = 0;
|
|
char path[PATH_MAX];
|
|
char buffer[64];
|
|
|
|
if (dm_snprintf(path, sizeof(path), "%sdev/block/%d:%d/loop/partscan",
|
|
dm_sysfs_dir(),
|
|
(int) MAJOR(dev->dev),
|
|
(int) MINOR(dev->dev)) < 0) {
|
|
log_warn("Sysfs path for partscan is too long.");
|
|
return 0;
|
|
}
|
|
|
|
if (!(fp = fopen(path, "r")))
|
|
return 0; /* not there -> no partscan */
|
|
|
|
if (!fgets(buffer, sizeof(buffer), fp)) {
|
|
log_warn("Failed to read %s.", path);
|
|
} else if (sscanf(buffer, "%d", &partscan) != 1) {
|
|
log_warn("Failed to parse %s '%s'.", path, buffer);
|
|
partscan = 0;
|
|
}
|
|
|
|
if (fclose(fp))
|
|
log_sys_debug("fclose", path);
|
|
|
|
return partscan;
|
|
}
|
|
|
|
int dev_get_partition_number(struct device *dev, int *num)
|
|
{
|
|
char path[PATH_MAX];
|
|
char buf[8] = { 0 };
|
|
dev_t devt = dev->dev;
|
|
struct stat sb;
|
|
|
|
if (dev->part != -1) {
|
|
*num = dev->part;
|
|
return 1;
|
|
}
|
|
|
|
if (dm_snprintf(path, sizeof(path), "%sdev/block/%d:%d/partition",
|
|
dm_sysfs_dir(), (int)MAJOR(devt), (int)MINOR(devt)) < 0) {
|
|
log_error("Failed to create sysfs path for %s", dev_name(dev));
|
|
return 0;
|
|
}
|
|
|
|
if (stat(path, &sb)) {
|
|
dev->part = 0;
|
|
*num = 0;
|
|
return 1;
|
|
}
|
|
|
|
if (!get_sysfs_value(path, buf, sizeof(buf), 0)) {
|
|
log_error("Failed to read sysfs path for %s", dev_name(dev));
|
|
return 0;
|
|
}
|
|
|
|
if (!buf[0]) {
|
|
log_error("Failed to read sysfs partition value for %s", dev_name(dev));
|
|
return 0;
|
|
}
|
|
|
|
dev->part = atoi(buf);
|
|
*num = dev->part;
|
|
return 1;
|
|
}
|
|
|
|
/* See linux/genhd.h and fs/partitions/msdos */
|
|
#define PART_MAGIC 0xAA55
|
|
#define PART_MAGIC_OFFSET UINT64_C(0x1FE)
|
|
#define PART_OFFSET UINT64_C(0x1BE)
|
|
|
|
struct partition {
|
|
uint8_t boot_ind;
|
|
uint8_t head;
|
|
uint8_t sector;
|
|
uint8_t cyl;
|
|
uint8_t sys_ind; /* partition type */
|
|
uint8_t end_head;
|
|
uint8_t end_sector;
|
|
uint8_t end_cyl;
|
|
uint32_t start_sect;
|
|
uint32_t nr_sects;
|
|
} __attribute__((packed));
|
|
|
|
static int _has_sys_partition(struct device *dev)
|
|
{
|
|
char path[PATH_MAX];
|
|
struct stat info;
|
|
int major = (int) MAJOR(dev->dev);
|
|
int minor = (int) MINOR(dev->dev);
|
|
|
|
/* check if dev is a partition */
|
|
if (dm_snprintf(path, sizeof(path), "%s/dev/block/%d:%d/partition",
|
|
dm_sysfs_dir(), major, minor) < 0) {
|
|
log_error("dm_snprintf partition failed");
|
|
return 0;
|
|
}
|
|
|
|
if (stat(path, &info) == -1) {
|
|
if (errno != ENOENT)
|
|
log_sys_error("stat", path);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int _is_partitionable(struct dev_types *dt, struct device *dev)
|
|
{
|
|
int parts = major_max_partitions(dt, MAJOR(dev->dev));
|
|
|
|
if (MAJOR(dev->dev) == dt->device_mapper_major)
|
|
return 1;
|
|
|
|
/* All MD devices are partitionable via blkext (as of 2.6.28) */
|
|
if (MAJOR(dev->dev) == dt->md_major)
|
|
return 1;
|
|
|
|
/* All loop devices are partitionable via blkext (as of 3.2) */
|
|
if ((MAJOR(dev->dev) == dt->loop_major) &&
|
|
_loop_is_with_partscan(dev))
|
|
return 1;
|
|
|
|
if (dev_is_nvme(dt, dev)) {
|
|
/* If this dev is already a partition then it's not partitionable. */
|
|
if (_has_sys_partition(dev))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
if ((parts <= 1) || (MINOR(dev->dev) % parts))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _has_partition_table(struct device *dev)
|
|
{
|
|
int ret = 0;
|
|
unsigned p;
|
|
struct {
|
|
uint8_t skip[PART_OFFSET];
|
|
struct partition part[4];
|
|
uint16_t magic;
|
|
} __attribute__((packed)) buf; /* sizeof() == SECTOR_SIZE */
|
|
|
|
if (!dev_read_bytes(dev, UINT64_C(0), sizeof(buf), &buf))
|
|
return_0;
|
|
|
|
/* FIXME Check for other types of partition table too */
|
|
|
|
/* Check for msdos partition table */
|
|
if (buf.magic == xlate16(PART_MAGIC)) {
|
|
for (p = 0; p < 4; ++p) {
|
|
/* Table is invalid if boot indicator not 0 or 0x80 */
|
|
if (buf.part[p].boot_ind & 0x7f) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
/* Must have at least one non-empty partition */
|
|
if (buf.part[p].nr_sects)
|
|
ret = 1;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef UDEV_SYNC_SUPPORT
|
|
static int _udev_dev_is_partitioned(struct dev_types *dt, struct device *dev)
|
|
{
|
|
struct dev_ext *ext;
|
|
struct udev_device *device;
|
|
const char *value;
|
|
|
|
if (!(ext = dev_ext_get(dev)))
|
|
return_0;
|
|
|
|
device = (struct udev_device *) ext->handle;
|
|
if (!(value = udev_device_get_property_value(device, DEV_EXT_UDEV_BLKID_PART_TABLE_TYPE)))
|
|
return 0;
|
|
|
|
/*
|
|
* Device-mapper devices have DEV_EXT_UDEV_BLKID_PART_TABLE_TYPE
|
|
* variable set if there's partition table found on whole device.
|
|
* Partitions do not have this variable set - it's enough to use
|
|
* only this variable to decide whether this device has partition
|
|
* table on it.
|
|
*/
|
|
if (MAJOR(dev->dev) == dt->device_mapper_major)
|
|
return 1;
|
|
|
|
/*
|
|
* Other devices have DEV_EXT_UDEV_BLKID_PART_TABLE_TYPE set for
|
|
* *both* whole device and partitions. We need to look at the
|
|
* DEV_EXT_UDEV_DEVTYPE in addition to decide - whole device
|
|
* with partition table on it has this variable set to
|
|
* DEV_EXT_UDEV_DEVTYPE_DISK.
|
|
*/
|
|
if (!(value = udev_device_get_property_value(device, DEV_EXT_UDEV_DEVTYPE)))
|
|
return_0;
|
|
|
|
return !strcmp(value, DEV_EXT_UDEV_DEVTYPE_DISK);
|
|
}
|
|
#else
|
|
static int _udev_dev_is_partitioned(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int _native_dev_is_partitioned(struct dev_types *dt, struct device *dev)
|
|
{
|
|
int r;
|
|
|
|
if (!scan_bcache)
|
|
return -EAGAIN;
|
|
|
|
if (!_is_partitionable(dt, dev))
|
|
return 0;
|
|
|
|
/* Unpartitioned DASD devices are not supported. */
|
|
if ((MAJOR(dev->dev) == dt->dasd_major) && dasd_is_cdl_formatted(dev))
|
|
return 1;
|
|
|
|
r = _has_partition_table(dev);
|
|
|
|
return r;
|
|
}
|
|
|
|
int dev_is_partitioned(struct dev_types *dt, struct device *dev)
|
|
{
|
|
if (dev->ext.src == DEV_EXT_NONE)
|
|
return _native_dev_is_partitioned(dt, dev);
|
|
|
|
if (dev->ext.src == DEV_EXT_UDEV)
|
|
return _udev_dev_is_partitioned(dt, dev);
|
|
|
|
log_error(INTERNAL_ERROR "Missing hook for partition table recognition "
|
|
"using external device info source %s", dev_ext_name(dev));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Get primary dev for the dev supplied.
|
|
*
|
|
* We can get a primary device for a partition either by:
|
|
* A: knowing the number of partitions allowed for the dev and also
|
|
* which major:minor number represents the primary and partition device
|
|
* (by using the dev_types->dev_type_array)
|
|
* B: by the existence of the 'partition' sysfs attribute
|
|
* (/dev/block/<major>:<minor>/partition)
|
|
*
|
|
* Method A is tried first, then method B as a fallback if A fails.
|
|
*
|
|
* N.B. Method B can only do the decision based on the pure existence of
|
|
* the 'partition' sysfs item. There's no direct scan for partition
|
|
* tables whatsoever!
|
|
*
|
|
* Returns:
|
|
* 0 on error
|
|
* 1 if the dev is already a primary dev, primary dev in 'result'
|
|
* 2 if the dev is a partition, primary dev in 'result'
|
|
*/
|
|
int dev_get_primary_dev(struct dev_types *dt, struct device *dev, dev_t *result)
|
|
{
|
|
const char *sysfs_dir = dm_sysfs_dir();
|
|
int major = (int) MAJOR(dev->dev);
|
|
int minor = (int) MINOR(dev->dev);
|
|
char path[PATH_MAX];
|
|
char temp_path[PATH_MAX];
|
|
char buffer[64];
|
|
FILE *fp = NULL;
|
|
int parts, residue, size, ret = 0;
|
|
|
|
/*
|
|
* /dev/nvme devs don't use the major:minor numbering like
|
|
* block dev types that have their own major number, so
|
|
* the calculation based on minor number doesn't work.
|
|
*/
|
|
if (dev_is_nvme(dt, dev))
|
|
goto sys_partition;
|
|
|
|
/*
|
|
* Try to get the primary dev out of the
|
|
* list of known device types first.
|
|
*/
|
|
if ((parts = dt->dev_type_array[major].max_partitions) > 1) {
|
|
if ((residue = minor % parts)) {
|
|
*result = MKDEV(major, (minor - residue));
|
|
ret = 2;
|
|
} else {
|
|
*result = dev->dev;
|
|
ret = 1; /* dev is not a partition! */
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
sys_partition:
|
|
/*
|
|
* If we can't get the primary dev out of the list of known device
|
|
* types, try to look at sysfs directly then. This is more complex
|
|
* way and it also requires certain sysfs layout to be present
|
|
* which might not be there in old kernels!
|
|
*/
|
|
if (!_has_sys_partition(dev)) {
|
|
*result = dev->dev;
|
|
ret = 1;
|
|
goto out; /* dev is not a partition! */
|
|
}
|
|
|
|
/*
|
|
* extract parent's path from the partition's symlink, e.g.:
|
|
* - readlink /sys/dev/block/259:0 = ../../block/md0/md0p1
|
|
* - dirname ../../block/md0/md0p1 = ../../block/md0
|
|
* - basename ../../block/md0/md0 = md0
|
|
* Parent's 'dev' sysfs attribute = /sys/block/md0/dev
|
|
*/
|
|
if ((size = readlink(dirname(path), temp_path, sizeof(temp_path) - 1)) < 0) {
|
|
log_sys_error("readlink", path);
|
|
goto out;
|
|
}
|
|
|
|
temp_path[size] = '\0';
|
|
|
|
if (dm_snprintf(path, sizeof(path), "%s/block/%s/dev",
|
|
sysfs_dir, basename(dirname(temp_path))) < 0) {
|
|
log_error("dm_snprintf dev failed");
|
|
goto out;
|
|
}
|
|
|
|
/* finally, parse 'dev' attribute and create corresponding dev_t */
|
|
if (!(fp = fopen(path, "r"))) {
|
|
if (errno == ENOENT)
|
|
log_error("sysfs file %s does not exist.", path);
|
|
else
|
|
log_sys_error("fopen", path);
|
|
goto out;
|
|
}
|
|
|
|
if (!fgets(buffer, sizeof(buffer), fp)) {
|
|
log_sys_error("fgets", path);
|
|
goto out;
|
|
}
|
|
|
|
if (sscanf(buffer, "%d:%d", &major, &minor) != 2) {
|
|
log_error("sysfs file %s not in expected MAJ:MIN format: %s",
|
|
path, buffer);
|
|
goto out;
|
|
}
|
|
*result = MKDEV(major, minor);
|
|
ret = 2;
|
|
out:
|
|
if (fp && fclose(fp))
|
|
log_sys_error("fclose", path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef BLKID_WIPING_SUPPORT
|
|
int get_fs_block_size(struct device *dev, uint32_t *fs_block_size)
|
|
{
|
|
char *block_size_str = NULL;
|
|
|
|
if ((block_size_str = blkid_get_tag_value(NULL, "BLOCK_SIZE", dev_name(dev)))) {
|
|
*fs_block_size = (uint32_t)atoi(block_size_str);
|
|
free(block_size_str);
|
|
log_debug("Found blkid BLOCK_SIZE %u for fs on %s", *fs_block_size, dev_name(dev));
|
|
return 1;
|
|
} else {
|
|
log_debug("No blkid BLOCK_SIZE for fs on %s", dev_name(dev));
|
|
*fs_block_size = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
#else
|
|
int get_fs_block_size(struct device *dev, uint32_t *fs_block_size)
|
|
{
|
|
log_debug("Disabled blkid BLOCK_SIZE for fs.");
|
|
*fs_block_size = 0;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef BLKID_WIPING_SUPPORT
|
|
|
|
static inline int _type_in_flag_list(const char *type, uint32_t flag_list)
|
|
{
|
|
return (((flag_list & TYPE_LVM2_MEMBER) && !strcmp(type, "LVM2_member")) ||
|
|
((flag_list & TYPE_LVM1_MEMBER) && !strcmp(type, "LVM1_member")) ||
|
|
((flag_list & TYPE_DM_SNAPSHOT_COW) && !strcmp(type, "DM_snapshot_cow")));
|
|
}
|
|
|
|
#define MSG_FAILED_SIG_OFFSET "Failed to get offset of the %s signature on %s."
|
|
#define MSG_FAILED_SIG_LENGTH "Failed to get length of the %s signature on %s."
|
|
#define MSG_WIPING_SKIPPED " Wiping skipped."
|
|
|
|
static int _blkid_wipe(blkid_probe probe, struct device *dev, const char *name,
|
|
uint32_t types_to_exclude, uint32_t types_no_prompt,
|
|
int yes, force_t force)
|
|
{
|
|
static const char _msg_wiping[] = "Wiping %s signature on %s.";
|
|
const char *offset = NULL, *type = NULL, *magic = NULL,
|
|
*usage = NULL, *label = NULL, *uuid = NULL;
|
|
loff_t offset_value;
|
|
size_t len;
|
|
|
|
if (!blkid_probe_lookup_value(probe, "TYPE", &type, NULL)) {
|
|
if (_type_in_flag_list(type, types_to_exclude))
|
|
return 2;
|
|
if (blkid_probe_lookup_value(probe, "SBMAGIC_OFFSET", &offset, NULL)) {
|
|
if (force < DONT_PROMPT) {
|
|
log_error(MSG_FAILED_SIG_OFFSET, type, name);
|
|
return 0;
|
|
}
|
|
|
|
log_error("WARNING: " MSG_FAILED_SIG_OFFSET MSG_WIPING_SKIPPED, type, name);
|
|
return 2;
|
|
}
|
|
if (blkid_probe_lookup_value(probe, "SBMAGIC", &magic, &len)) {
|
|
if (force < DONT_PROMPT) {
|
|
log_error(MSG_FAILED_SIG_LENGTH, type, name);
|
|
return 0;
|
|
}
|
|
|
|
log_warn("WARNING: " MSG_FAILED_SIG_LENGTH MSG_WIPING_SKIPPED, type, name);
|
|
return 2;
|
|
}
|
|
} else if (!blkid_probe_lookup_value(probe, "PTTYPE", &type, NULL)) {
|
|
if (blkid_probe_lookup_value(probe, "PTMAGIC_OFFSET", &offset, NULL)) {
|
|
if (force < DONT_PROMPT) {
|
|
log_error(MSG_FAILED_SIG_OFFSET, type, name);
|
|
return 0;
|
|
}
|
|
|
|
log_warn("WARNING: " MSG_FAILED_SIG_OFFSET MSG_WIPING_SKIPPED, type, name);
|
|
return 2;
|
|
}
|
|
if (blkid_probe_lookup_value(probe, "PTMAGIC", &magic, &len)) {
|
|
if (force < DONT_PROMPT) {
|
|
log_error(MSG_FAILED_SIG_LENGTH, type, name);
|
|
return 0;
|
|
}
|
|
|
|
log_warn("WARNING: " MSG_FAILED_SIG_LENGTH MSG_WIPING_SKIPPED, type, name);
|
|
return 2;
|
|
}
|
|
usage = "partition table";
|
|
} else
|
|
return_0;
|
|
|
|
offset_value = strtoll(offset, NULL, 10);
|
|
|
|
if (!usage)
|
|
(void) blkid_probe_lookup_value(probe, "USAGE", &usage, NULL);
|
|
(void) blkid_probe_lookup_value(probe, "LABEL", &label, NULL);
|
|
(void) blkid_probe_lookup_value(probe, "UUID", &uuid, NULL);
|
|
/* Return values ignored here, in the worst case we print NULL */
|
|
|
|
log_verbose("Found existing signature on %s at offset %s: LABEL=\"%s\" "
|
|
"UUID=\"%s\" TYPE=\"%s\" USAGE=\"%s\"",
|
|
name, offset, label, uuid, type, usage);
|
|
|
|
if (!_type_in_flag_list(type, types_no_prompt)) {
|
|
if (!yes && (force == PROMPT) &&
|
|
yes_no_prompt("WARNING: %s signature detected on %s at offset %s. "
|
|
"Wipe it? [y/n]: ", type, name, offset) == 'n') {
|
|
log_error("Aborted wiping of %s.", type);
|
|
return 0;
|
|
}
|
|
log_print_unless_silent(_msg_wiping, type, name);
|
|
} else
|
|
log_verbose(_msg_wiping, type, name);
|
|
|
|
if (!dev_write_zeros(dev, offset_value, len)) {
|
|
log_error("Failed to wipe %s signature on %s.", type, name);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _wipe_known_signatures_with_blkid(struct device *dev, const char *name,
|
|
uint32_t types_to_exclude,
|
|
uint32_t types_no_prompt,
|
|
int yes, force_t force, int *wiped)
|
|
{
|
|
blkid_probe probe = NULL;
|
|
int found = 0, left = 0, wiped_tmp;
|
|
int r_wipe;
|
|
int r = 0;
|
|
|
|
if (!wiped)
|
|
wiped = &wiped_tmp;
|
|
*wiped = 0;
|
|
|
|
/* TODO: Should we check for valid dev - _dev_is_valid(dev)? */
|
|
|
|
if (!(probe = blkid_new_probe_from_filename(dev_name(dev)))) {
|
|
log_error("Failed to create a new blkid probe for device %s.", dev_name(dev));
|
|
goto out;
|
|
}
|
|
|
|
blkid_probe_enable_partitions(probe, 1);
|
|
blkid_probe_set_partitions_flags(probe, BLKID_PARTS_MAGIC);
|
|
|
|
blkid_probe_enable_superblocks(probe, 1);
|
|
blkid_probe_set_superblocks_flags(probe, BLKID_SUBLKS_LABEL |
|
|
BLKID_SUBLKS_UUID |
|
|
BLKID_SUBLKS_TYPE |
|
|
BLKID_SUBLKS_USAGE |
|
|
BLKID_SUBLKS_VERSION |
|
|
BLKID_SUBLKS_MAGIC |
|
|
BLKID_SUBLKS_BADCSUM);
|
|
|
|
while (!blkid_do_probe(probe)) {
|
|
if ((r_wipe = _blkid_wipe(probe, dev, name, types_to_exclude, types_no_prompt, yes, force)) == 1) {
|
|
(*wiped)++;
|
|
if (blkid_probe_step_back(probe)) {
|
|
log_error("Failed to step back blkid probe to check just wiped signature.");
|
|
goto out;
|
|
}
|
|
}
|
|
/* do not count excluded types */
|
|
if (r_wipe != 2)
|
|
found++;
|
|
}
|
|
|
|
if (!found)
|
|
r = 1;
|
|
|
|
left = found - *wiped;
|
|
if (!left)
|
|
r = 1;
|
|
else
|
|
log_warn("%d existing signature%s left on the device.",
|
|
left, left > 1 ? "s" : "");
|
|
out:
|
|
if (probe)
|
|
blkid_free_probe(probe);
|
|
return r;
|
|
}
|
|
|
|
#endif /* BLKID_WIPING_SUPPORT */
|
|
|
|
static int _wipe_signature(struct device *dev, const char *type, const char *name,
|
|
int wipe_len, int yes, force_t force, int *wiped,
|
|
int (*signature_detection_fn)(struct device *dev, uint64_t *offset_found, int full))
|
|
{
|
|
int wipe;
|
|
uint64_t offset_found;
|
|
|
|
wipe = signature_detection_fn(dev, &offset_found, 1);
|
|
if (wipe == -1) {
|
|
log_error("Fatal error while trying to detect %s on %s.",
|
|
type, name);
|
|
return 0;
|
|
}
|
|
|
|
if (wipe == 0)
|
|
return 1;
|
|
|
|
/* Specifying --yes => do not ask. */
|
|
if (!yes && (force == PROMPT) &&
|
|
yes_no_prompt("WARNING: %s detected on %s. Wipe it? [y/n]: ",
|
|
type, name) == 'n') {
|
|
log_error("Aborted wiping of %s.", type);
|
|
return 0;
|
|
}
|
|
|
|
log_print_unless_silent("Wiping %s on %s.", type, name);
|
|
if (!dev_write_zeros(dev, offset_found, wipe_len)) {
|
|
log_error("Failed to wipe %s on %s.", type, name);
|
|
return 0;
|
|
}
|
|
|
|
(*wiped)++;
|
|
return 1;
|
|
}
|
|
|
|
static int _wipe_known_signatures_with_lvm(struct device *dev, const char *name,
|
|
uint32_t types_to_exclude __attribute__((unused)),
|
|
uint32_t types_no_prompt __attribute__((unused)),
|
|
int yes, force_t force, int *wiped)
|
|
{
|
|
int wiped_tmp;
|
|
|
|
if (!wiped)
|
|
wiped = &wiped_tmp;
|
|
*wiped = 0;
|
|
|
|
if (!_wipe_signature(dev, "software RAID md superblock", name, 4, yes, force, wiped, dev_is_md_component) ||
|
|
!_wipe_signature(dev, "swap signature", name, 10, yes, force, wiped, dev_is_swap) ||
|
|
!_wipe_signature(dev, "LUKS signature", name, 8, yes, force, wiped, dev_is_luks))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int wipe_known_signatures(struct cmd_context *cmd, struct device *dev,
|
|
const char *name, uint32_t types_to_exclude,
|
|
uint32_t types_no_prompt, int yes, force_t force,
|
|
int *wiped)
|
|
{
|
|
int blkid_wiping_enabled = find_config_tree_bool(cmd, allocation_use_blkid_wiping_CFG, NULL);
|
|
|
|
#ifdef BLKID_WIPING_SUPPORT
|
|
if (blkid_wiping_enabled)
|
|
return _wipe_known_signatures_with_blkid(dev, name,
|
|
types_to_exclude,
|
|
types_no_prompt,
|
|
yes, force, wiped);
|
|
#endif
|
|
if (blkid_wiping_enabled) {
|
|
log_warn("allocation/use_blkid_wiping=1 configuration setting is set "
|
|
"while LVM is not compiled with blkid wiping support.");
|
|
log_warn("Falling back to native LVM signature detection.");
|
|
}
|
|
return _wipe_known_signatures_with_lvm(dev, name,
|
|
types_to_exclude,
|
|
types_no_prompt,
|
|
yes, force, wiped);
|
|
}
|
|
|
|
#ifdef __linux__
|
|
|
|
static int _snprintf_attr(char *buf, size_t buf_size, const char *sysfs_dir,
|
|
const char *attribute, dev_t dev)
|
|
{
|
|
if (dm_snprintf(buf, buf_size, "%s/dev/block/%d:%d/%s", sysfs_dir,
|
|
(int)MAJOR(dev), (int)MINOR(dev),
|
|
attribute) < 0) {
|
|
log_warn("dm_snprintf %s failed.", attribute);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static unsigned long _dev_topology_attribute(struct dev_types *dt,
|
|
const char *attribute,
|
|
struct device *dev,
|
|
unsigned long default_value)
|
|
{
|
|
const char *sysfs_dir = dm_sysfs_dir();
|
|
char path[PATH_MAX], buffer[64];
|
|
FILE *fp;
|
|
struct stat info;
|
|
dev_t uninitialized_var(primary);
|
|
unsigned long result = default_value;
|
|
unsigned long value = 0UL;
|
|
|
|
if (!attribute || !*attribute)
|
|
goto_out;
|
|
|
|
if (!sysfs_dir || !*sysfs_dir)
|
|
goto_out;
|
|
|
|
if (!_snprintf_attr(path, sizeof(path), sysfs_dir, attribute, dev->dev))
|
|
goto_out;
|
|
|
|
/*
|
|
* check if the desired sysfs attribute exists
|
|
* - if not: either the kernel doesn't have topology support
|
|
* or the device could be a partition
|
|
*/
|
|
if (stat(path, &info) == -1) {
|
|
if (errno != ENOENT) {
|
|
log_sys_debug("stat", path);
|
|
goto out;
|
|
}
|
|
if (!dev_get_primary_dev(dt, dev, &primary))
|
|
goto out;
|
|
|
|
/* get attribute from partition's primary device */
|
|
if (!_snprintf_attr(path, sizeof(path), sysfs_dir, attribute, primary))
|
|
goto_out;
|
|
|
|
if (stat(path, &info) == -1) {
|
|
if (errno != ENOENT)
|
|
log_sys_debug("stat", path);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!(fp = fopen(path, "r"))) {
|
|
log_sys_debug("fopen", path);
|
|
goto out;
|
|
}
|
|
|
|
if (!fgets(buffer, sizeof(buffer), fp)) {
|
|
log_sys_debug("fgets", path);
|
|
goto out_close;
|
|
}
|
|
|
|
if (sscanf(buffer, "%lu", &value) != 1) {
|
|
log_warn("sysfs file %s not in expected format: %s", path, buffer);
|
|
goto out_close;
|
|
}
|
|
|
|
log_very_verbose("Device %s: %s is %lu%s.",
|
|
dev_name(dev), attribute, value, default_value ? "" : " bytes");
|
|
|
|
result = value >> SECTOR_SHIFT;
|
|
|
|
if (!result && value) {
|
|
log_warn("WARNING: Device %s: %s is %lu and is unexpectedly less than sector.",
|
|
dev_name(dev), attribute, value);
|
|
result = 1;
|
|
}
|
|
|
|
out_close:
|
|
if (fclose(fp))
|
|
log_sys_debug("fclose", path);
|
|
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
unsigned long dev_alignment_offset(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return _dev_topology_attribute(dt, "alignment_offset", dev, 0UL);
|
|
}
|
|
|
|
unsigned long dev_minimum_io_size(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return _dev_topology_attribute(dt, "queue/minimum_io_size", dev, 0UL);
|
|
}
|
|
|
|
unsigned long dev_optimal_io_size(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return _dev_topology_attribute(dt, "queue/optimal_io_size", dev, 0UL);
|
|
}
|
|
|
|
unsigned long dev_discard_max_bytes(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return _dev_topology_attribute(dt, "queue/discard_max_bytes", dev, 0UL);
|
|
}
|
|
|
|
unsigned long dev_discard_granularity(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return _dev_topology_attribute(dt, "queue/discard_granularity", dev, 0UL);
|
|
}
|
|
|
|
int dev_is_rotational(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return (int) _dev_topology_attribute(dt, "queue/rotational", dev, 1UL);
|
|
}
|
|
#else
|
|
|
|
int dev_get_primary_dev(struct dev_types *dt, struct device *dev, dev_t *result)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
unsigned long dev_alignment_offset(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return 0UL;
|
|
}
|
|
|
|
unsigned long dev_minimum_io_size(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return 0UL;
|
|
}
|
|
|
|
unsigned long dev_optimal_io_size(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return 0UL;
|
|
}
|
|
|
|
unsigned long dev_discard_max_bytes(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return 0UL;
|
|
}
|
|
|
|
unsigned long dev_discard_granularity(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return 0UL;
|
|
}
|
|
|
|
int dev_is_rotational(struct dev_types *dt, struct device *dev)
|
|
{
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
#ifdef UDEV_SYNC_SUPPORT
|
|
|
|
/*
|
|
* Udev daemon usually has 30s timeout to process each event by default.
|
|
* But still, that value can be changed in udev configuration and we
|
|
* don't have libudev API to read the actual timeout value used.
|
|
*/
|
|
|
|
/* FIXME: Is this long enough to wait for udev db to get initialized?
|
|
*
|
|
* Take also into consideration that this check is done for each
|
|
* device that is scanned so we don't want to wait for a long time
|
|
* if there's something wrong with udev, e.g. timeouts! With current
|
|
* libudev API, we can't recognize whether the event processing has
|
|
* not finished yet and it's still being processed or whether it has
|
|
* failed already due to timeout in udev - in both cases the
|
|
* udev_device_get_is_initialized returns 0.
|
|
*/
|
|
#define UDEV_DEV_IS_COMPONENT_ITERATION_COUNT 100
|
|
#define UDEV_DEV_IS_COMPONENT_USLEEP 100000
|
|
|
|
static struct udev_device *_udev_get_dev(struct device *dev)
|
|
{
|
|
struct udev *udev_context = udev_get_library_context();
|
|
struct udev_device *udev_device = NULL;
|
|
int initialized = 0;
|
|
unsigned i = 0;
|
|
|
|
if (!udev_context) {
|
|
log_warn("WARNING: No udev context available to check if device %s is multipath component.", dev_name(dev));
|
|
return NULL;
|
|
}
|
|
|
|
while (1) {
|
|
if (i >= UDEV_DEV_IS_COMPONENT_ITERATION_COUNT)
|
|
break;
|
|
|
|
if (udev_device)
|
|
udev_device_unref(udev_device);
|
|
|
|
if (!(udev_device = udev_device_new_from_devnum(udev_context, 'b', dev->dev))) {
|
|
log_warn("WARNING: Failed to get udev device handler for device %s.", dev_name(dev));
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef HAVE_LIBUDEV_UDEV_DEVICE_GET_IS_INITIALIZED
|
|
if ((initialized = udev_device_get_is_initialized(udev_device)))
|
|
break;
|
|
#else
|
|
if ((initialized = (udev_device_get_property_value(udev_device, DEV_EXT_UDEV_DEVLINKS) != NULL)))
|
|
break;
|
|
#endif
|
|
|
|
log_debug("Device %s not initialized in udev database (%u/%u, %u microseconds).", dev_name(dev),
|
|
i + 1, UDEV_DEV_IS_COMPONENT_ITERATION_COUNT,
|
|
i * UDEV_DEV_IS_COMPONENT_USLEEP);
|
|
|
|
if (!udev_sleeping())
|
|
break;
|
|
|
|
usleep(UDEV_DEV_IS_COMPONENT_USLEEP);
|
|
i++;
|
|
}
|
|
|
|
if (!initialized) {
|
|
log_warn("WARNING: Device %s not initialized in udev database even after waiting %u microseconds.",
|
|
dev_name(dev), i * UDEV_DEV_IS_COMPONENT_USLEEP);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
return udev_device;
|
|
}
|
|
|
|
int udev_dev_is_mpath_component(struct device *dev)
|
|
{
|
|
struct udev_device *udev_device;
|
|
const char *value;
|
|
int ret = 0;
|
|
|
|
if (!obtain_device_list_from_udev())
|
|
return 0;
|
|
|
|
if (!(udev_device = _udev_get_dev(dev)))
|
|
return 0;
|
|
|
|
value = udev_device_get_property_value(udev_device, DEV_EXT_UDEV_BLKID_TYPE);
|
|
if (value && !strcmp(value, DEV_EXT_UDEV_BLKID_TYPE_MPATH)) {
|
|
log_debug("Device %s is multipath component based on blkid variable in udev db (%s=\"%s\").",
|
|
dev_name(dev), DEV_EXT_UDEV_BLKID_TYPE, value);
|
|
ret = 1;
|
|
goto out;
|
|
}
|
|
|
|
value = udev_device_get_property_value(udev_device, DEV_EXT_UDEV_MPATH_DEVICE_PATH);
|
|
if (value && !strcmp(value, "1")) {
|
|
log_debug("Device %s is multipath component based on multipath variable in udev db (%s=\"%s\").",
|
|
dev_name(dev), DEV_EXT_UDEV_MPATH_DEVICE_PATH, value);
|
|
ret = 1;
|
|
goto out;
|
|
}
|
|
out:
|
|
udev_device_unref(udev_device);
|
|
return ret;
|
|
}
|
|
|
|
int udev_dev_is_md_component(struct device *dev)
|
|
{
|
|
struct udev_device *udev_device;
|
|
const char *value;
|
|
int ret = 0;
|
|
|
|
if (!obtain_device_list_from_udev()) {
|
|
dev->flags |= DEV_UDEV_INFO_MISSING;
|
|
return 0;
|
|
}
|
|
|
|
if (!(udev_device = _udev_get_dev(dev)))
|
|
return 0;
|
|
|
|
value = udev_device_get_property_value(udev_device, DEV_EXT_UDEV_BLKID_TYPE);
|
|
if (value && !strcmp(value, DEV_EXT_UDEV_BLKID_TYPE_SW_RAID)) {
|
|
log_debug("Device %s is md raid component based on blkid variable in udev db (%s=\"%s\").",
|
|
dev_name(dev), DEV_EXT_UDEV_BLKID_TYPE, value);
|
|
dev->flags |= DEV_IS_MD_COMPONENT;
|
|
ret = 1;
|
|
goto out;
|
|
}
|
|
out:
|
|
udev_device_unref(udev_device);
|
|
return ret;
|
|
}
|
|
|
|
#else
|
|
|
|
int udev_dev_is_mpath_component(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int udev_dev_is_md_component(struct device *dev)
|
|
{
|
|
dev->flags |= DEV_UDEV_INFO_MISSING;
|
|
return 0;
|
|
}
|
|
|
|
#endif
|