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 a7de7a7bc0 add activation services
New systemd services for startup:

lvm-devices-wait.service
  Used in place of systemd-udev-settle, this service waits
  for udev+pvscan to process PVs listed in system.devices.
  It runs the command "lvmdevices --wait pvsonline".
  This only waits for PVs that can be matched to a device in
  sysfs, so it only waits for devices attached to the system.
  It waits specifically for the /run/lvm/pvs_online/<pvid>
  files to be created by pvscan.  It quits waiting after a
  configurable number of seconds.  This service gives the
  first activation service a chance to activate VGs from
  PVs that are available immediately at startup.  If this
  service quits waiting before all the expected pvid files
  appear, then the VG associated with those PVs will most
  likely be activated by the -last service rather than the
  initial -main service.  If those PVs are even slower to
  complete processing than the -last service, then the VG
  will be activated by event activation whenever they are
  finally complete.

lvm-activate-vgs-main.service
  Calls "vgchange -aay", after lvm-devices-wait, to activate
  complete VGs.  It only considers PVs that have been
  processed by udev+pvscan and have pvs_online files.
  This is expected to activate VGs from basic devices
  (not virtual device types) that are present at startup.

lvm-activate-vgs-last.service
  Calls "vgchange -aay", after multipathd has started, to
  activate VGs that became available after virtual device
  services were started, e.g. VGs on multipath devices.
  Like -main, it only looks at PVs that have been processed
  by pvscan.

This vgchange in the -last service enables event activation
by creating the /run/lvm/event-activation-on file.  Event
activation will activate any further VGs that appear on the
system (or complete udev processing) after the -last service.
In the case of event activation, the udev rule will run
vgchange -aay <vgname> via a transient service
lvm-activate-<vgname>.service.  This vgchange only scans
PVs in the VG being activated, also based on the pvs_online
files from pvscan.

When there are many VGs that need activation during system
startup, the two fixed services can activate them all much
faster than activating each VG individually via events.

lvm.conf auto_activation_settings can be used to configure
the behavior (default ["service_and_event", "pvscan_hints"]).

"service_and_event" - the behavior described above, where
activation services are used first, and event activation
is used afterward.

"service_only" - only lvm-activate-vgs-* are used, and
no event-based activation occurs after the services finish.
(Equivalent to setting lvm.conf event_activation=0.)

"event_only" - the lvm-activate-vgs* services are skipped,
and all VGs are activated individually with event-based
activation.

"pvscan_hints" - the vgchange autoactivation commands
use pvs_online files created by pvscan.  This optimization
limits the devices scanned by the vgchange command to only
PVs that have been processed by pvscan.
2021-10-29 16:29:42 -05:00

563 lines
15 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"
/* coverity[unnecessary_header] needed for MuslC */
#include <sys/file.h>
#include <time.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 by filter: %s",
dev->pvid, dev_name(dev), dev_filtered_reason(dev));
dm_list_del(&devl->list);
}
}
}
static int _all_pvids_online(struct cmd_context *cmd, struct dm_list *wait_pvids)
{
struct device_id_list *dil, *dil2;
int notfound = 0;
dm_list_iterate_items_safe(dil, dil2, wait_pvids) {
if (online_pvid_file_exists(dil->pvid))
dm_list_del(&dil->list);
else
notfound++;
}
return notfound ? 0 : 1;
}
int lvmdevices(struct cmd_context *cmd, int argc, char **argv)
{
struct dm_list search_pvids;
struct dm_list wait_pvids;
struct dm_list found_devs;
struct device_id_list *dil;
struct device_list *devl;
struct device *dev;
struct dev_use *du, *du2;
const char *deviceidtype;
time_t begin;
int wait_sec;
int changes = 0;
dm_list_init(&search_pvids);
dm_list_init(&wait_pvids);
dm_list_init(&found_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, wait_ARG))
cmd->print_device_id_not_found = 0;
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 search_count = 0;
int invalid = 0;
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);
/*
* 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);
}
/*
* check du->part
*/
dm_list_iterate_items(du, &cmd->use_devices) {
int part = 0;
if (!du->dev)
continue;
dev = du->dev;
dev_get_partition_number(dev, &part);
if (part != du->part) {
log_warn("Device %s partition %u has incorrect PART in devices file (%u)",
dev_name(dev), part, du->part);
du->part = part;
changes++;
}
}
if (arg_is_set(cmd, update_ARG)) {
if (invalid || !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.");
}
}
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 by filter: %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))
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))
goto_bad;
}
if (!device_ids_write(cmd))
goto_bad;
goto out;
}
if (arg_is_set(cmd, deldev_ARG)) {
const char *devname;
if (!(devname = arg_str_value(cmd, deldev_ARG, NULL)))
goto_bad;
/*
* 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))) {
log_error("No device found for %s.", devname);
goto bad;
}
/*
* 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))) {
log_error("Device not found in devices file.");
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 (arg_is_set(cmd, wait_ARG)) {
if (strcmp("pvsonline", arg_str_value(cmd, wait_ARG, ""))) {
log_error("wait option invalid.");
goto bad;
}
/* TODO: lvm.conf lvmdevices_wait_settings "disabled" do nothing */
/* TODO: lvm.conf auto_activation_settings "event_only" do nothing */
/* TODO: if no devices file exists, what should this do?
do a udev-settle? do nothing and cause more event-based activations? */
/* for each du, if du->wwid matched, wait for /run/lvm/pvs_online/du->pvid */
dm_list_iterate_items(du, &cmd->use_devices) {
if (!du->dev)
continue;
if (!(dil = dm_pool_zalloc(cmd->mem, sizeof(*dil))))
continue;
dil->dev = du->dev;
memcpy(dil->pvid, du->pvid, ID_LEN);
dm_list_add(&wait_pvids, &dil->list);
}
log_print("Waiting for PVs online for %u matched devices file entries.", dm_list_size(&wait_pvids));
wait_sec = find_config_tree_int(cmd, devices_lvmdevices_wait_seconds_CFG, 0);
begin = time(NULL);
while (1) {
if (_all_pvids_online(cmd, &wait_pvids)) {
log_print("Found all PVs online");
goto out;
}
log_print("Waiting for PVs online for %u devices.", dm_list_size(&wait_pvids));
/* TODO: lvm.conf lvmdevices_wait_ids "sys_wwid=111", "sys_wwid=222" etc
waits for the specifically named devices even if the devices do not exist. */
if (!wait_sec || (time(NULL) - begin >= wait_sec)) {
log_print("Time out waiting for PVs online:");
dm_list_iterate_items(dil, &wait_pvids)
log_print("Need PVID %s on %s", dil->pvid, dev_name(dil->dev));
break;
}
if (dm_list_size(&wait_pvids) > 10) {
if (interruptible_usleep(1000000)) /* 1 sec */
break;
} else {
if (interruptible_usleep(500000)) /* .5 sec */
break;
}
}
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;
}