From c609dedc2f035f770b5f645c4695924abf15c2ca Mon Sep 17 00:00:00 2001 From: David Teigland Date: Tue, 23 Apr 2024 17:08:26 -0500 Subject: [PATCH] Allow system.devices to be automatically created on first boot An OS installer can create system.devices for the system and disks, but an OS image cannot create the system-specific system.devices. The OS image can instead configure the image so that lvm will create system.devices on first boot. Image preparation steps to enable auto creation of system.devices: - create empty file /etc/lvm/devices/auto-import-rootvg - remove any existing /etc/lvm/devices/system.devices - enable lvm-devices-import.path - enable lvm-devices-import.service On first boot of the prepared image: - udev triggers vgchange -aay --autoactivation event - vgchange activates LVs in the root VG - vgchange finds the file /etc/lvm/devices/auto-import-rootvg, and no /etc/lvm/devices/system.devices, so it creates /run/lvm/lvm-devices-import - lvm-devices-import.path is run when /run/lvm/lvm-devices-import appears, and triggers lvm-devices-import.service - lvm-devices-import.service runs vgimportdevices --rootvg --auto - vgimportdevices finds /etc/lvm/devices/auto-import-rootvg, and no system.devices, so it creates system.devices containing PVs in the root VG, and removes /etc/lvm/devices/auto-import-rootvg and /run/lvm/lvm-devices-import Run directly, vgimportdevices --rootvg (without --auto), will create a new system.devices for the root VG, or will add devices for the root VG to an existing system.devices. --- lib/commands/toolcontext.h | 1 + lib/config/defaults.h | 2 + lib/device/device_id.c | 5 +- scripts/lvm-devices-import.path | 12 +++ scripts/lvm-devices-import.service | 12 +++ tools/args.h | 11 ++ tools/command-lines.in | 5 + tools/pvscan.c | 4 +- tools/tools.h | 2 +- tools/vgchange.c | 155 ++++++++++++++++++++++++++++- tools/vgimportdevices.c | 114 ++++++++++++++++++++- 11 files changed, 315 insertions(+), 8 deletions(-) create mode 100644 scripts/lvm-devices-import.path create mode 100644 scripts/lvm-devices-import.service diff --git a/lib/commands/toolcontext.h b/lib/commands/toolcontext.h index fec0a52cf..043afbf76 100644 --- a/lib/commands/toolcontext.h +++ b/lib/commands/toolcontext.h @@ -217,6 +217,7 @@ struct cmd_context { unsigned device_ids_check_hostname:1; unsigned device_ids_refresh_trigger:1; unsigned device_ids_invalid:1; + unsigned device_ids_auto_import:1; unsigned get_vgname_from_options:1; /* used by lvconvert */ /* diff --git a/lib/config/defaults.h b/lib/config/defaults.h index ed0c4f404..efe36d1fa 100644 --- a/lib/config/defaults.h +++ b/lib/config/defaults.h @@ -337,6 +337,8 @@ #define VGS_ONLINE_DIR DEFAULT_RUN_DIR "/vgs_online" #define PVS_LOOKUP_DIR DEFAULT_RUN_DIR "/pvs_lookup" +#define DEVICES_IMPORT_PATH DEFAULT_RUN_DIR "/lvm-devices-import" + #define DEFAULT_DEVICE_ID_SYSFS_DIR "/sys/" /* trailing / to match dm_sysfs_dir() */ #define DEFAULT_DEVICESFILE_BACKUP_LIMIT 50 diff --git a/lib/device/device_id.c b/lib/device/device_id.c index 2b183810a..1ce7927ed 100644 --- a/lib/device/device_id.c +++ b/lib/device/device_id.c @@ -1726,9 +1726,10 @@ int device_ids_write(struct cmd_context *cmd) if ((fc_bytes = snprintf(fc, sizeof(fc), "# LVM uses devices listed in this file.\n" \ - "# Created by LVM command %s pid %d at %s" \ + "# Created by LVM command %s%s pid %d at %s" \ "# HASH=%u\n", - cmd->name, getpid(), ctime(&t), hash)) < 0) { + cmd->name, cmd->device_ids_auto_import ? " (auto)" : "", + getpid(), ctime(&t), hash)) < 0) { log_error("Failed to write buffer for devices file content."); goto out; } diff --git a/scripts/lvm-devices-import.path b/scripts/lvm-devices-import.path new file mode 100644 index 000000000..bcf0dcd4c --- /dev/null +++ b/scripts/lvm-devices-import.path @@ -0,0 +1,12 @@ +[Unit] +Description=lvm-devices-import to create system.devices + +# /run/lvm/lvm-devices-import created by vgchange -aay + +[Path] +PathExists=/run/lvm/lvm-devices-import +Unit=lvm-devices-import.service +ConditionPathExists=!/etc/lvm/devices/system.devices + +[Install] +WantedBy=multi-user.target diff --git a/scripts/lvm-devices-import.service b/scripts/lvm-devices-import.service new file mode 100644 index 000000000..9d3bda2ee --- /dev/null +++ b/scripts/lvm-devices-import.service @@ -0,0 +1,12 @@ +[Unit] +Description=Create lvm system.devices + +[Service] +Type=oneshot +RemainAfterExit=no +ExecStart=/usr/sbin/vgimportdevices --rootvg --auto +ConditionPathExists=!/etc/lvm/devices/system.devices + +[Install] +WantedBy=multi-user.target + diff --git a/tools/args.h b/tools/args.h index 09b2ad551..ed0fb9620 100644 --- a/tools/args.h +++ b/tools/args.h @@ -94,6 +94,14 @@ arg(atversion_ARG, '\0', "atversion", string_VAL, 0, 0, "which does not contain any newer settings for which LVM would\n" "issue a warning message when checking the configuration.\n") +arg(auto_ARG, '\0', "auto", 0, 0, 0, + "This option is used when automatically importing devices for the root VG.\n" + "The auto import is intended to be done once, on first boot, to create an\n" + "initial system.devices file for the root VG.\n" + "When this option is used, the vgimportdevices --rootvg command does nothing\n" + "if system.devices exists, or the file auto-import-rootvg does not exist\n" + "(both in the /etc/lvm/devices/ directory.)\n") + arg(autoactivation_ARG, '\0', "autoactivation", string_VAL, 0, 0, "Specify if autoactivation is being used from an event.\n" "This allows the command to apply settings that are specific\n" @@ -754,6 +762,9 @@ arg(resync_ARG, '\0', "resync", 0, 0, 0, "which the LV is without a complete redundant copy of the data.\n" "See \\fBlvmraid\\fP(7) for more information.\n") +arg(rootvg_ARG, '\0', "rootvg", 0, 0, 0, + "Import devices used for the root VG.\n") + arg(rows_ARG, '\0', "rows", 0, 0, 0, "Output columns as rows.\n") diff --git a/tools/command-lines.in b/tools/command-lines.in index 1c728afa0..3ad5d3c46 100644 --- a/tools/command-lines.in +++ b/tools/command-lines.in @@ -1911,6 +1911,11 @@ OO: --foreign, --reportformat ReportFmt ID: vgimportdevices_all DESC: Add devices from all accessible VGs to the devices file. +vgimportdevices --rootvg +OO: --auto, --reportformat ReportFmt +ID: vgimportdevices_root +DESC: Add devices from root VG to the devices file. + --- vgmerge VG VG diff --git a/tools/pvscan.c b/tools/pvscan.c index f88e1b751..0a9cb59df 100644 --- a/tools/pvscan.c +++ b/tools/pvscan.c @@ -495,7 +495,7 @@ static int _pvscan_aa_single(struct cmd_context *cmd, const char *vg_name, log_debug("pvscan autoactivating VG %s.", vg_name); - if (!vgchange_activate(cmd, vg, CHANGE_AAY, 1)) { + if (!vgchange_activate(cmd, vg, CHANGE_AAY, 1, NULL)) { log_error_pvscan(cmd, "%s: autoactivation failed.", vg->name); pp->activate_errors++; } @@ -755,7 +755,7 @@ static int _pvscan_aa_quick(struct cmd_context *cmd, struct pvscan_aa_params *pp log_debug("pvscan autoactivating VG %s.", vgname); - if (!vgchange_activate(cmd, vg, CHANGE_AAY, 1)) { + if (!vgchange_activate(cmd, vg, CHANGE_AAY, 1, NULL)) { log_error_pvscan(cmd, "%s: autoactivation failed.", vg->name); pp->activate_errors++; } diff --git a/tools/tools.h b/tools/tools.h index f4a0c94d7..58708c695 100644 --- a/tools/tools.h +++ b/tools/tools.h @@ -164,7 +164,7 @@ int mirror_remove_missing(struct cmd_context *cmd, int vgchange_activate(struct cmd_context *cmd, struct volume_group *vg, - activation_change_t activate, int vg_complete_to_activate); + activation_change_t activate, int vg_complete_to_activate, char *root_dm_uuid); int vgchange_background_polling(struct cmd_context *cmd, struct volume_group *vg); diff --git a/tools/vgchange.c b/tools/vgchange.c index 378ded16e..2004d6e92 100644 --- a/tools/vgchange.c +++ b/tools/vgchange.c @@ -16,11 +16,14 @@ #include "tools.h" #include "lib/device/device_id.h" #include "lib/label/hints.h" +#include "device_mapper/misc/dm-ioctl.h" +#include struct vgchange_params { int lock_start_count; unsigned int lock_start_sanlock : 1; unsigned int vg_complete_to_activate : 1; + char *root_dm_uuid; /* dm uuid of LV under root fs */ }; /* @@ -197,7 +200,7 @@ int vgchange_background_polling(struct cmd_context *cmd, struct volume_group *vg } int vgchange_activate(struct cmd_context *cmd, struct volume_group *vg, - activation_change_t activate, int vg_complete_to_activate) + activation_change_t activate, int vg_complete_to_activate, char *root_dm_uuid) { int lv_open, active, monitored = 0, r = 1; const struct lv_list *lvl; @@ -279,6 +282,43 @@ int vgchange_activate(struct cmd_context *cmd, struct volume_group *vg, r = 0; } + /* + * Possibly trigger auto-generation of system.devices: + * - if root_dm_uuid contains vg->id, and + * - /etc/lvm/devices/auto-import-rootvg exists, and + * - /etc/lvm/devices/system.devices does not exist, then + * - create /run/lvm/lvm-devices-import to + * trigger lvm-devices-import.path and .service + * - lvm-devices-import will run vgimportdevices --rootvg + * to create system.devices + */ + if (root_dm_uuid) { + char path[PATH_MAX]; + struct stat info; + FILE *fp; + + if (memcmp(root_dm_uuid + 4, &vg->id, ID_LEN)) + goto out; + + if (cmd->enable_devices_file || devices_file_exists(cmd)) + goto out; + + if (dm_snprintf(path, sizeof(path), "%s/devices/auto-import-rootvg", cmd->system_dir) < 0) + goto out; + + if (stat(path, &info) < 0) + goto out; + + log_debug("Found %s creating %s", path, DEVICES_IMPORT_PATH); + + if (!(fp = fopen(DEVICES_IMPORT_PATH, "w"))) { + log_debug("failed to create %s", DEVICES_IMPORT_PATH); + goto out; + } + if (fclose(fp)) + stack; + } +out: /* Print message only if there was not found a missing VG */ log_print_unless_silent("%d logical volume(s) in volume group \"%s\" now active", lvs_in_vg_activated(vg), vg->name); @@ -714,7 +754,7 @@ static int _vgchange_single(struct cmd_context *cmd, const char *vg_name, if (arg_is_set(cmd, activate_ARG)) { activate = (activation_change_t) arg_uint_value(cmd, activate_ARG, 0); - if (!vgchange_activate(cmd, vg, activate, vp->vg_complete_to_activate)) + if (!vgchange_activate(cmd, vg, activate, vp->vg_complete_to_activate, vp->root_dm_uuid)) return_ECMD_FAILED; } else if (arg_is_set(cmd, refresh_ARG)) { /* refreshes the visible LVs (which starts polling) */ @@ -735,6 +775,115 @@ static int _vgchange_single(struct cmd_context *cmd, const char *vg_name, return ret; } +/* + * Automatic creation of system.devices for root VG on first boot + * is useful for OS images where the OS installer is not used to + * customize the OS for system. + * + * - OS image prep: + * . rm /etc/lvm/devices/system.devices (if it exists) + * . touch /etc/lvm/devices/auto-import-rootvg + * . enable lvm-devices-import.path + * . enable lvm-devices-import.service + * + * - lvchange -ay / + * . run by initrd so root fs can be mounted + * . does not use system.devices + * . named / comes from kernel command line rd.lvm + * . uses first device that appears containing the named root LV + * + * - vgchange -aay + * . triggered by udev when all PVs from root VG are online + * . activate LVs in root VG (in addition to the already active root LV) + * . check for /etc/lvm/devices/auto-import-rootvg (found) + * . check for /etc/lvm/devices/system.devices (not found) + * . create /run/lvm/lvm-devices-import because + * auto-import-rootvg was found and system.devices was not found + * + * - lvm-devices-import.path + * . triggered by /run/lvm/lvm-devices-import + * . start lvm-devices-import.service + * + * - lvm-devices-import.service + * . check for /etc/lvm/devices/system.devices, do nothing if found + * . run vgimportdevices --rootvg --auto + * + * - vgimportdevices --rootvg --auto + * . check for /etc/lvm/devices/auto-import-rootvg (found) + * . check for /etc/lvm/devices/system.devices (not found) + * . creates /etc/lvm/devices/system.devices for PVs in root VG + * . removes /etc/lvm/devices/auto-import-rootvg + * . removes /run/lvm/lvm-devices-import + * + * On future startup, /etc/lvm/devices/system.devices will exist, + * and /etc/lvm/devices/auto-import-rootvg will not exist, so + * vgchange -aay will not create /run/lvm/lvm-devices-import, + * and lvm-devices-import.path and lvm-device-import.service will not run. + * + * lvm-devices-import.path: + * [Path] + * PathExists=/run/lvm/lvm-devices-import + * Unit=lvm-devices-import.service + * ConditionPathExists=!/etc/lvm/devices/system.devices + * + * lvm-devices-import.service: + * [Service] + * Type=oneshot + * RemainAfterExit=no + * ExecStart=/usr/sbin/vgimportdevices --rootvg --auto + * ConditionPathExists=!/etc/lvm/devices/system.devices + */ + +static void _get_rootvg_dev(struct cmd_context *cmd, char **dm_uuid_out) +{ + char path[PATH_MAX]; + char dm_uuid[DM_UUID_LEN]; + struct stat info; + FILE *fme = NULL; + struct mntent *me; + int found = 0; + + if (cmd->enable_devices_file || devices_file_exists(cmd)) + return; + + if (dm_snprintf(path, sizeof(path), "%s/devices/auto-import-rootvg", cmd->system_dir) < 0) + return; + + if (stat(path, &info) < 0) + return; + + if (!(fme = setmntent("/etc/mtab", "r"))) + return; + + while ((me = getmntent(fme))) { + if ((me->mnt_dir[0] == '/') && (me->mnt_dir[1] == '\0')) { + found = 1; + break; + } + } + endmntent(fme); + + if (!found) + return; + + if (stat(me->mnt_dir, &info) < 0) + return; + + if (!get_dm_uuid_from_sysfs(dm_uuid, sizeof(dm_uuid), (int)MAJOR(info.st_dev), (int)MINOR(info.st_dev))) + return; + + log_debug("Found root dm_uuid %s", dm_uuid); + + /* UUID_PREFIX = "LVM-" */ + if (strncmp(dm_uuid, UUID_PREFIX, 4)) + return; + + if (strlen(dm_uuid) < 4 + ID_LEN) + return; + + *dm_uuid_out = dm_pool_strdup(cmd->mem, dm_uuid); +} + static int _vgchange_autoactivation_setup(struct cmd_context *cmd, struct vgchange_params *vp, int *skip_command, @@ -778,6 +927,8 @@ static int _vgchange_autoactivation_setup(struct cmd_context *cmd, get_single_vgname_cmd_arg(cmd, NULL, &vgname); + _get_rootvg_dev(cmd, &vp->root_dm_uuid); + /* * Lock the VG before scanning the PVs so _vg_read can avoid the normal * lock_vol+rescan (READ_WITHOUT_LOCK avoids the normal lock_vol and diff --git a/tools/vgimportdevices.c b/tools/vgimportdevices.c index bccd94f61..70d12e500 100644 --- a/tools/vgimportdevices.c +++ b/tools/vgimportdevices.c @@ -15,11 +15,16 @@ #include "tools.h" #include "lib/cache/lvmcache.h" #include "lib/device/device_id.h" +#include "device_mapper/misc/dm-ioctl.h" /* coverity[unnecessary_header] needed for MuslC */ #include +#include struct vgimportdevices_params { uint32_t added_devices; + int root_vg_found; + char *root_dm_uuid; + char *root_vg_name; }; static int _vgimportdevices_single(struct cmd_context *cmd, @@ -35,6 +40,13 @@ static int _vgimportdevices_single(struct cmd_context *cmd, int updated_pvs = 0; const char *idtypestr = NULL; /* deviceidtype_ARG ? */ + if (vp->root_dm_uuid) { + if (memcmp(vp->root_dm_uuid + 4, &vg->id, ID_LEN)) + return ECMD_PROCESSED; + vp->root_vg_found = 1; + vp->root_vg_name = dm_pool_strdup(cmd->mem, vg_name); + } + dm_list_iterate_items(pvl, &vg->pvs) { if (is_missing_pv(pvl->pv) || !pvl->pv->dev) { memcpy(pvid, &pvl->pv->id.uuid, ID_LEN); @@ -86,6 +98,87 @@ static int _vgimportdevices_single(struct cmd_context *cmd, return ECMD_PROCESSED; } +static int _get_rootvg_dev(struct cmd_context *cmd, char **dm_uuid_out, int *skip) +{ + char path[PATH_MAX]; + char dm_uuid[DM_UUID_LEN]; + struct stat info; + FILE *fme = NULL; + struct mntent *me; + int found = 0; + + /* + * When --auto is set, the command does nothing + * if /etc/lvm/devices/system.devices exists, or + * if /etc/lvm/devices/auto-import-rootvg does not exist. + */ + if (arg_is_set(cmd, auto_ARG)) { + if (devices_file_exists(cmd)) { + *skip = 1; + return 1; + } + + if (dm_snprintf(path, sizeof(path), "%s/devices/auto-import-rootvg", cmd->system_dir) < 0) + return_0; + + if (stat(path, &info) < 0) { + *skip = 1; + return 1; + } + + /* + * This flag is just used in device_ids_write to enable + * an extra comment in system.devices indicating that + * the file was auto generated for the root vg. + */ + cmd->device_ids_auto_import = 1; + } + + if (!(fme = setmntent("/etc/mtab", "r"))) + return_0; + + while ((me = getmntent(fme))) { + if ((me->mnt_dir[0] == '/') && (me->mnt_dir[1] == '\0')) { + found = 1; + break; + } + } + endmntent(fme); + + if (!found) + return_0; + + if (stat(me->mnt_dir, &info) < 0) + return_0; + + if (!get_dm_uuid_from_sysfs(dm_uuid, sizeof(dm_uuid), (int)MAJOR(info.st_dev), (int)MINOR(info.st_dev))) + return_0; + + /* UUID_PREFIX = "LVM-" */ + if (strncmp(dm_uuid, UUID_PREFIX, 4)) + return_0; + + if (strlen(dm_uuid) < 4 + ID_LEN) + return_0; + + *dm_uuid_out = dm_pool_strdup(cmd->mem, dm_uuid); + return 1; +} + +static void _clear_rootvg_auto(struct cmd_context *cmd) +{ + char path[PATH_MAX]; + + if (dm_snprintf(path, sizeof(path), "%s/devices/auto-import-rootvg", cmd->system_dir) < 0) + return; + + if (unlink(path) < 0) + log_debug("Failed to unlink %s", path); + + if (unlink(DEVICES_IMPORT_PATH) < 0) + log_debug("Failed to unlink %s", DEVICES_IMPORT_PATH); +} + /* * This command always scans all devices on the system, * any pre-existing devices_file does not limit the scope. @@ -130,6 +223,19 @@ int vgimportdevices(struct cmd_context *cmd, int argc, char **argv) /* So that we can warn about this. */ cmd->handles_missing_pvs = 1; + /* Import devices for the root VG. */ + if (arg_is_set(cmd, rootvg_ARG)) { + int skip = 0; + if (!_get_rootvg_dev(cmd, &vp.root_dm_uuid, &skip)) { + log_error("Failed to find root VG."); + return ECMD_FAILED; + } + if (skip) { + log_print("Root VG auto import is not enabled."); + return ECMD_PROCESSED; + } + } + if (!lock_global(cmd, "ex")) return ECMD_FAILED; @@ -230,7 +336,13 @@ int vgimportdevices(struct cmd_context *cmd, int argc, char **argv) goto out; } - log_print("Added %u devices to devices file.", vp.added_devices); + if (vp.root_vg_found) + log_print("Added %u devices to devices file for root VG %s.", vp.added_devices, vp.root_vg_name); + else + log_print("Added %u devices to devices file.", vp.added_devices); + + if (vp.root_vg_found && arg_is_set(cmd, auto_ARG)) + _clear_rootvg_auto(cmd); out: if ((ret == ECMD_FAILED) && created_file) if (unlink(cmd->devices_file_path) < 0) -- 2.43.5