1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-12-21 13:34:40 +03:00

Allow system.devices to be automatically created on first boot

This is intended for image-based OS deployments, where an installer
is not run on the target machine to create a custom system.devices.

Instead, the OS image preparation can configure the image so that
lvm will automatically create system.devices for the root VG on
first boot.

image preparation:
- create empty file /etc/lvm/devices/auto-import-rootvg
- remove any existing /etc/lvm/devices/system.devices
- enable lvm-devices-init.path and lvm-devices-init.service

on first boot:
- udev triggers vgchange -aay --autoactivation event <rootvg>
- vgchange activates LVs in the root VG
- vgchange finds finds auto-import-rootvg, and no system.devices,
  so it creates /run/lvm/lvm-devices-init
- lvm-devices-init.path is run when /run/lvm/lvm-devices-init
  appears, and triggers lvm-devices-init.service
- lvm-devices-init.service runs vgimportdevices --rootvg --auto
- vgimportdevices finds 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-init
This commit is contained in:
David Teigland 2024-04-23 17:08:26 -05:00
parent 85b711caae
commit e9f7918c57
9 changed files with 299 additions and 6 deletions

View File

@ -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

View File

@ -0,0 +1,12 @@
[Unit]
Description=lvm-devices-import to create system.devices
# /run/lvm/lvm-devices-import created by vgchange -aay <rootvg>
[Path]
PathExists=/run/lvm/lvm-devices-import
Unit=lvm-devices-import.service
ConditionPathExists=!/etc/lvm/devices/system.devices
[Install]
WantedBy=multi-user.target

View File

@ -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

View File

@ -94,6 +94,9 @@ 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,
"Manage files for auto device import.\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 +757,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")

View File

@ -1907,6 +1907,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

View File

@ -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++;
}

View File

@ -230,7 +230,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);

View File

@ -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 <mntent.h>
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 <rootvg>/<rootlv>
* . run by initrd so root fs can be mounted
* . does not use system.devices
* . named <rootvg>/<rootlv> comes from kernel command line rd.lvm
* . uses first device that appears containing the named root LV
*
* - vgchange -aay <rootvg>
* . 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 <rootvg> 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

View File

@ -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 <sys/file.h>
#include <mntent.h>
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,80 @@ 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;
}
}
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 +216,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 +329,13 @@ int vgimportdevices(struct cmd_context *cmd, int argc, char **argv)
goto out;
}
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)