1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-12-21 13:34:40 +03:00
lvm2/tools/lvmdevices.c
David Teigland 761b922178 device_id: handle duplicate serial numbers
Handle multiple devices using the same serial number as
their device id.  After matching devices to devices file
entries, if there is a discrepency between the ondisk PVID
and the devices file PVID, then rematch devices to
devices file entries using PVID, looking at all disks
on the system with the same serial number.
2022-11-07 08:56:02 -06:00

572 lines
16 KiB
C

/*
* Copyright (C) 2020 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 "tools.h"
#include "lib/cache/lvmcache.h"
#include "lib/device/device_id.h"
#include "lib/device/dev-type.h"
#include "lib/filters/filter.h"
/* coverity[unnecessary_header] needed for MuslC */
#include <sys/file.h>
static void _search_devs_for_pvids(struct cmd_context *cmd, struct dm_list *search_pvids, struct dm_list *found_devs)
{
struct dev_iter *iter;
struct device *dev;
struct device_list *devl, *devl2;
struct device_id_list *dil, *dil2;
struct dm_list devs;
int found;
dm_list_init(&devs);
/*
* Create a list of all devices on the system, without applying
* any filters, since we do not want filters to read any of the
* devices yet.
*/
if (!(iter = dev_iter_create(NULL, 0)))
return;
while ((dev = dev_iter_get(cmd, iter))) {
/* Skip devs with a valid match to a du. */
if (get_du_for_dev(cmd, dev))
continue;
if (!(devl = dm_pool_zalloc(cmd->mem, sizeof(*devl))))
continue;
devl->dev = dev;
dm_list_add(&devs, &devl->list);
}
dev_iter_destroy(iter);
/*
* Apply the filters that do not require reading the devices
*/
log_debug("Filtering devices (no data) for pvid search");
cmd->filter_nodata_only = 1;
cmd->filter_deviceid_skip = 1;
dm_list_iterate_items_safe(devl, devl2, &devs) {
if (!cmd->filter->passes_filter(cmd, cmd->filter, devl->dev, NULL))
dm_list_del(&devl->list);
}
/*
* Read header from each dev to see if it has one of the pvids we're
* searching for.
*/
dm_list_iterate_items_safe(devl, devl2, &devs) {
int has_pvid;
/* sets dev->pvid if an lvm label with pvid is found */
if (!label_read_pvid(devl->dev, &has_pvid))
continue;
if (!has_pvid)
continue;
found = 0;
dm_list_iterate_items_safe(dil, dil2, search_pvids) {
if (!strcmp(devl->dev->pvid, dil->pvid)) {
dm_list_del(&devl->list);
dm_list_del(&dil->list);
dm_list_add(found_devs, &devl->list);
log_print("Found PVID %s on %s.", dil->pvid, dev_name(devl->dev));
found = 1;
break;
}
}
if (!found)
label_scan_invalidate(devl->dev);
/*
* FIXME: search all devs in case pvid is duplicated on multiple devs.
*/
if (dm_list_empty(search_pvids))
break;
}
dm_list_iterate_items(dil, search_pvids)
log_error("PVID %s not found on any devices.", dil->pvid);
/*
* Now that the device has been read, apply the filters again
* which will now include filters that read data from the device.
* N.B. we've already skipped devs that were excluded by the
* no-data filters, so if the PVID exists on one of those devices
* no warning is printed.
*/
log_debug("Filtering devices (with data) for pvid search");
cmd->filter_nodata_only = 0;
cmd->filter_deviceid_skip = 1;
dm_list_iterate_items_safe(devl, devl2, found_devs) {
dev = devl->dev;
cmd->filter->wipe(cmd, cmd->filter, dev, NULL);
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, NULL)) {
log_warn("WARNING: PVID %s found on %s which is excluded: %s",
dev->pvid, dev_name(dev), dev_filtered_reason(dev));
dm_list_del(&devl->list);
}
}
}
int lvmdevices(struct cmd_context *cmd, int argc, char **argv)
{
struct dm_list search_pvids;
struct dm_list found_devs;
struct dm_list scan_devs;
struct device_id_list *dil;
struct device_list *devl;
struct device *dev;
struct dev_use *du, *du2;
const char *deviceidtype;
dm_list_init(&search_pvids);
dm_list_init(&found_devs);
dm_list_init(&scan_devs);
if (!setup_devices_file(cmd))
return ECMD_FAILED;
if (!cmd->enable_devices_file) {
log_error("Devices file not enabled.");
return ECMD_FAILED;
}
if (arg_is_set(cmd, update_ARG) ||
arg_is_set(cmd, adddev_ARG) || arg_is_set(cmd, deldev_ARG) ||
arg_is_set(cmd, addpvid_ARG) || arg_is_set(cmd, delpvid_ARG)) {
if (!lock_devices_file(cmd, LOCK_EX)) {
log_error("Failed to lock the devices file to create.");
return ECMD_FAILED;
}
if (!devices_file_exists(cmd)) {
if (!devices_file_touch(cmd)) {
log_error("Failed to create the devices file.");
return ECMD_FAILED;
}
}
/*
* The hint file is associated with the default/system devices file,
* so don't clear hints when using a different --devicesfile.
*/
if (!cmd->devicesfile)
clear_hint_file(cmd);
} else {
if (!lock_devices_file(cmd, LOCK_SH)) {
log_error("Failed to lock the devices file.");
return ECMD_FAILED;
}
if (!devices_file_exists(cmd)) {
log_error("Devices file does not exist.");
return ECMD_FAILED;
}
}
if (!device_ids_read(cmd)) {
log_error("Failed to read the devices file.");
return ECMD_FAILED;
}
prepare_open_file_limit(cmd, dm_list_size(&cmd->use_devices));
dev_cache_scan(cmd);
device_ids_match(cmd);
if (arg_is_set(cmd, check_ARG) || arg_is_set(cmd, update_ARG)) {
int update_set = arg_is_set(cmd, update_ARG);
int search_count = 0;
int update_needed = 0;
int serial_update_needed = 0;
int invalid = 0;
unlink_searched_devnames(cmd);
label_scan_setup_bcache();
dm_list_iterate_items(du, &cmd->use_devices) {
if (!du->dev)
continue;
dev = du->dev;
if (!label_read_pvid(dev, NULL))
continue;
/*
* label_read_pvid has read the first 4K of the device
* so these filters should not for the most part need
* to do any further reading of the device.
*
* We run the filters here for the first time in the
* check|update command. device_ids_validate() then
* checks the result of this filtering (by checking the
* "persistent" filter explicitly), and prints a warning
* if a devices file entry does not pass the filters.
* The !passes_filter here is log_debug instead of log_warn
* to avoid repeating the same message as device_ids_validate.
* (We could also print the warning here and then pass a
* parameter to suppress the warning in device_ids_validate.)
*/
log_debug("Checking filters with data for %s", dev_name(dev));
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, NULL)) {
log_debug("filter result: %s in devices file is excluded by filter: %s.",
dev_name(dev), dev_filtered_reason(dev));
}
}
/*
* Check that the pvid read from the lvm label matches the pvid
* for this devices file entry. Also print a warning if a dev
* from use_devices does not pass the filters that have been
* run just above.
*/
device_ids_validate(cmd, NULL, &invalid, 1);
if (invalid)
update_needed = 1;
/*
* Remove multipath components.
* Add multipath devs that had components listed.
*/
dm_list_iterate_items_safe(du, du2, &cmd->use_devices) {
dev_t mpath_devno;
struct device *mpath_dev;
if (!du->dev)
continue;
dev = du->dev;
if (!(dev->filtered_flags & DEV_FILTERED_MPATH_COMPONENT))
continue;
/* redundant given the flag check, but used to get devno */
if (!dev_is_mpath_component(cmd, dev, &mpath_devno))
continue;
update_needed = 1;
if (update_set) {
log_print("Removing multipath component %s.", dev_name(du->dev));
dm_list_del(&du->list);
}
if (!(mpath_dev = dev_cache_get_by_devt(cmd, mpath_devno)))
continue;
if (!get_du_for_dev(cmd, mpath_dev)) {
if (update_set) {
log_print("Adding multipath device %s for multipath component %s.",
dev_name(mpath_dev), dev_name(du->dev));
if (!device_id_add(cmd, mpath_dev, dev->pvid, NULL, NULL, 0))
stack;
} else {
log_print("Missing multipath device %s for multipath component %s.",
dev_name(mpath_dev), dev_name(du->dev));
}
}
}
if (!dm_list_empty(&cmd->device_ids_check_serial))
device_ids_check_serial(cmd, &scan_devs, &serial_update_needed, 1);
/*
* Find and fix any devname entries that have moved to a
* renamed device.
*/
device_ids_find_renamed_devs(cmd, &found_devs, &search_count, 1);
if (search_count && !strcmp(cmd->search_for_devnames, "none"))
log_print("Not searching for missing devnames, search_for_devnames=\"none\".");
dm_list_iterate_items(du, &cmd->use_devices) {
if (du->dev)
label_scan_invalidate(du->dev);
}
if (arg_is_set(cmd, update_ARG)) {
if (update_needed || serial_update_needed || !dm_list_empty(&found_devs)) {
if (!device_ids_write(cmd))
goto_bad;
log_print("Updated devices file to version %s", devices_file_version());
} else {
log_print("No update for devices file is needed.");
}
} else {
/*
* --check exits with an error if the devices file
* needs updates, i.e. running --update would make
* changes.
*/
if (update_needed || serial_update_needed) {
log_error("Updates needed for devices file.");
goto bad;
}
}
goto out;
}
if (arg_is_set(cmd, adddev_ARG)) {
const char *devname;
if (!(devname = arg_str_value(cmd, adddev_ARG, NULL)))
goto_bad;
/*
* addev will add a device to devices_file even if that device
* is excluded by filters.
*/
/*
* No filter applied here (only the non-data filters would
* be applied since we haven't read the device yet.
*/
if (!(dev = dev_cache_get(cmd, devname, NULL))) {
log_error("No device found for %s.", devname);
goto bad;
}
/*
* reads pvid from dev header, sets dev->pvid.
* (it's ok if the device is not a PV and has no PVID)
*/
label_scan_setup_bcache();
if (!label_read_pvid(dev, NULL)) {
log_error("Failed to read %s.", devname);
goto bad;
}
/*
* Allow filtered devices to be added to devices_file, but
* check if it's excluded by filters to print a warning.
* Since label_read_pvid has read the first 4K of the device,
* the filters should not for the most part need to do any further
* reading of the device.
*
* (This is the first time filters are being run, so we do
* not need to wipe filters of any previous result that was
* based on filter_deviceid_skip=0.)
*/
cmd->filter_deviceid_skip = 1;
if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, NULL)) {
log_warn("WARNING: adding device %s that is excluded: %s.",
dev_name(dev), dev_filtered_reason(dev));
}
/* also allow deviceid_ARG ? */
deviceidtype = arg_str_value(cmd, deviceidtype_ARG, NULL);
if (!device_id_add(cmd, dev, dev->pvid, deviceidtype, NULL, 1))
goto_bad;
if (!device_ids_write(cmd))
goto_bad;
goto out;
}
if (arg_is_set(cmd, addpvid_ARG)) {
struct id id;
char pvid[ID_LEN+1] = { 0 };
const char *pvid_arg;
label_scan_setup_bcache();
/*
* Iterate through all devs on the system, reading the
* pvid of each to check if it has this pvid.
* Devices that are excluded by no-data filters will not
* be checked for the PVID.
* addpvid will not add a device to devices_file if it's
* excluded by filters.
*/
pvid_arg = arg_str_value(cmd, addpvid_ARG, NULL);
if (!id_read_format_try(&id, pvid_arg)) {
log_error("Invalid PVID.");
goto bad;
}
memcpy(pvid, &id.uuid, ID_LEN);
if ((du = get_du_for_pvid(cmd, pvid))) {
log_error("PVID already exists in devices file for %s.", dev_name(du->dev));
goto bad;
}
if (!(dil = dm_pool_zalloc(cmd->mem, sizeof(*dil))))
goto_bad;
memcpy(dil->pvid, &pvid, ID_LEN);
dm_list_add(&search_pvids, &dil->list);
_search_devs_for_pvids(cmd, &search_pvids, &found_devs);
if (dm_list_empty(&found_devs)) {
log_error("PVID %s not found on any devices.", pvid);
goto bad;
}
dm_list_iterate_items(devl, &found_devs) {
deviceidtype = arg_str_value(cmd, deviceidtype_ARG, NULL);
if (!device_id_add(cmd, devl->dev, devl->dev->pvid, deviceidtype, NULL, 1))
goto_bad;
}
if (!device_ids_write(cmd))
goto_bad;
goto out;
}
if (arg_is_set(cmd, deldev_ARG) && !arg_is_set(cmd, deviceidtype_ARG)) {
const char *devname;
if (!(devname = arg_str_value(cmd, deldev_ARG, NULL)))
goto_bad;
if (strncmp(devname, "/dev/", 5))
log_warn("WARNING: to remove a device by device id, include --deviceidtype.");
/*
* No filter because we always want to allow removing a device
* by name from the devices file.
*/
if ((dev = dev_cache_get(cmd, devname, NULL))) {
/*
* dev_cache_scan uses sysfs to check if an LV is using each dev
* and sets this flag is so.
*/
if (dev_is_used_by_active_lv(cmd, dev, NULL, NULL, NULL, NULL)) {
if (!arg_count(cmd, yes_ARG) &&
yes_no_prompt("Device %s is used by an active LV, continue to remove? ", devname) == 'n') {
log_error("Device not removed.");
goto bad;
}
}
if ((du = get_du_for_dev(cmd, dev)))
goto dev_del;
}
if (!(du = get_du_for_devname(cmd, devname))) {
log_error("No devices file entry for %s.", devname);
goto bad;
}
dev_del:
dm_list_del(&du->list);
free_du(du);
device_ids_write(cmd);
goto out;
}
/*
* By itself, --deldev <devname> specifies a device name to remove.
* With an id type specified, --deldev specifies a device id to remove:
* --deldev <idname> --deviceidtype <idtype>
*/
if (arg_is_set(cmd, deldev_ARG) && arg_is_set(cmd, deviceidtype_ARG)) {
const char *idtype_str = arg_str_value(cmd, deviceidtype_ARG, NULL);
const char *idname = arg_str_value(cmd, deldev_ARG, NULL);
int idtype;
if (!idtype_str || !idname || !strlen(idname) || !strlen(idtype_str))
goto_bad;
if (!(idtype = idtype_from_str(idtype_str))) {
log_error("Unknown device_id type.");
goto_bad;
}
if (!strncmp(idname, "/dev/", 5))
log_warn("WARNING: to remove a device by name, do not include --deviceidtype.");
if (!(du = get_du_for_device_id(cmd, idtype, idname))) {
log_error("No devices file entry with device id %s %s.", idtype_str, idname);
goto_bad;
}
dev = du->dev;
if (dev && dev_is_used_by_active_lv(cmd, dev, NULL, NULL, NULL, NULL)) {
if (!arg_count(cmd, yes_ARG) &&
yes_no_prompt("Device %s is used by an active LV, continue to remove? ", dev_name(dev)) == 'n') {
log_error("Device not removed.");
goto bad;
}
}
dm_list_del(&du->list);
free_du(du);
device_ids_write(cmd);
goto out;
}
if (arg_is_set(cmd, delpvid_ARG)) {
struct id id;
char pvid[ID_LEN+1] = { 0 };
const char *pvid_arg;
pvid_arg = arg_str_value(cmd, delpvid_ARG, NULL);
if (!id_read_format_try(&id, pvid_arg)) {
log_error("Invalid PVID.");
goto bad;
}
memcpy(pvid, &id.uuid, ID_LEN);
if (!(du = get_du_for_pvid(cmd, pvid))) {
log_error("PVID not found in devices file.");
goto bad;
}
dm_list_del(&du->list);
if ((du2 = get_du_for_pvid(cmd, pvid))) {
log_error("Multiple devices file entries for PVID %s (%s %s), remove by device name.",
pvid, du->devname, du2->devname);
goto bad;
}
if (du->devname && (du->devname[0] != '.')) {
if ((dev = dev_cache_get(cmd, du->devname, NULL)) &&
dev_is_used_by_active_lv(cmd, dev, NULL, NULL, NULL, NULL)) {
if (!arg_count(cmd, yes_ARG) &&
yes_no_prompt("Device %s is used by an active LV, continue to remove? ", du->devname) == 'n') {
log_error("Device not removed.");
goto bad;
}
}
}
free_du(du);
device_ids_write(cmd);
goto out;
}
/* If no options, print use_devices list */
dm_list_iterate_items(du, &cmd->use_devices) {
char part_buf[64] = { 0 };
if (du->part)
snprintf(part_buf, 63, " PART=%d", du->part);
log_print("Device %s IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s%s",
du->dev ? dev_name(du->dev) : "none",
du->idtype ? idtype_to_str(du->idtype) : "none",
du->idname ? du->idname : "none",
du->devname ? du->devname : "none",
du->pvid ? (char *)du->pvid : "none",
part_buf);
}
out:
return ECMD_PROCESSED;
bad:
return ECMD_FAILED;
}