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

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 <rootvg>
- 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.
This commit is contained in:
David Teigland 2024-04-23 17:08:26 -05:00
parent 6fdc8787eb
commit c609dedc2f
11 changed files with 315 additions and 8 deletions

View File

@ -217,6 +217,7 @@ struct cmd_context {
unsigned device_ids_check_hostname:1; unsigned device_ids_check_hostname:1;
unsigned device_ids_refresh_trigger:1; unsigned device_ids_refresh_trigger:1;
unsigned device_ids_invalid:1; unsigned device_ids_invalid:1;
unsigned device_ids_auto_import:1;
unsigned get_vgname_from_options:1; /* used by lvconvert */ unsigned get_vgname_from_options:1; /* used by lvconvert */
/* /*

View File

@ -337,6 +337,8 @@
#define VGS_ONLINE_DIR DEFAULT_RUN_DIR "/vgs_online" #define VGS_ONLINE_DIR DEFAULT_RUN_DIR "/vgs_online"
#define PVS_LOOKUP_DIR DEFAULT_RUN_DIR "/pvs_lookup" #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_DEVICE_ID_SYSFS_DIR "/sys/" /* trailing / to match dm_sysfs_dir() */
#define DEFAULT_DEVICESFILE_BACKUP_LIMIT 50 #define DEFAULT_DEVICESFILE_BACKUP_LIMIT 50

View File

@ -1726,9 +1726,10 @@ int device_ids_write(struct cmd_context *cmd)
if ((fc_bytes = snprintf(fc, sizeof(fc), if ((fc_bytes = snprintf(fc, sizeof(fc),
"# LVM uses devices listed in this file.\n" \ "# 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", "# 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."); log_error("Failed to write buffer for devices file content.");
goto out; goto out;
} }

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,14 @@ arg(atversion_ARG, '\0', "atversion", string_VAL, 0, 0,
"which does not contain any newer settings for which LVM would\n" "which does not contain any newer settings for which LVM would\n"
"issue a warning message when checking the configuration.\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, arg(autoactivation_ARG, '\0', "autoactivation", string_VAL, 0, 0,
"Specify if autoactivation is being used from an event.\n" "Specify if autoactivation is being used from an event.\n"
"This allows the command to apply settings that are specific\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" "which the LV is without a complete redundant copy of the data.\n"
"See \\fBlvmraid\\fP(7) for more information.\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, arg(rows_ARG, '\0', "rows", 0, 0, 0,
"Output columns as rows.\n") "Output columns as rows.\n")

View File

@ -1911,6 +1911,11 @@ OO: --foreign, --reportformat ReportFmt
ID: vgimportdevices_all ID: vgimportdevices_all
DESC: Add devices from all accessible VGs to the devices file. 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 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); 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); log_error_pvscan(cmd, "%s: autoactivation failed.", vg->name);
pp->activate_errors++; 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); 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); log_error_pvscan(cmd, "%s: autoactivation failed.", vg->name);
pp->activate_errors++; pp->activate_errors++;
} }

View File

@ -164,7 +164,7 @@ int mirror_remove_missing(struct cmd_context *cmd,
int vgchange_activate(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 vgchange_background_polling(struct cmd_context *cmd, struct volume_group *vg); int vgchange_background_polling(struct cmd_context *cmd, struct volume_group *vg);

View File

@ -16,11 +16,14 @@
#include "tools.h" #include "tools.h"
#include "lib/device/device_id.h" #include "lib/device/device_id.h"
#include "lib/label/hints.h" #include "lib/label/hints.h"
#include "device_mapper/misc/dm-ioctl.h"
#include <mntent.h>
struct vgchange_params { struct vgchange_params {
int lock_start_count; int lock_start_count;
unsigned int lock_start_sanlock : 1; unsigned int lock_start_sanlock : 1;
unsigned int vg_complete_to_activate : 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, 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; int lv_open, active, monitored = 0, r = 1;
const struct lv_list *lvl; const struct lv_list *lvl;
@ -279,6 +282,43 @@ int vgchange_activate(struct cmd_context *cmd, struct volume_group *vg,
r = 0; 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 */ /* 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", log_print_unless_silent("%d logical volume(s) in volume group \"%s\" now active",
lvs_in_vg_activated(vg), vg->name); 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)) { if (arg_is_set(cmd, activate_ARG)) {
activate = (activation_change_t) arg_uint_value(cmd, activate_ARG, 0); 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; return_ECMD_FAILED;
} else if (arg_is_set(cmd, refresh_ARG)) { } else if (arg_is_set(cmd, refresh_ARG)) {
/* refreshes the visible LVs (which starts polling) */ /* 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; 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, static int _vgchange_autoactivation_setup(struct cmd_context *cmd,
struct vgchange_params *vp, struct vgchange_params *vp,
int *skip_command, 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_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 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 * lock_vol+rescan (READ_WITHOUT_LOCK avoids the normal lock_vol and

View File

@ -15,11 +15,16 @@
#include "tools.h" #include "tools.h"
#include "lib/cache/lvmcache.h" #include "lib/cache/lvmcache.h"
#include "lib/device/device_id.h" #include "lib/device/device_id.h"
#include "device_mapper/misc/dm-ioctl.h"
/* coverity[unnecessary_header] needed for MuslC */ /* coverity[unnecessary_header] needed for MuslC */
#include <sys/file.h> #include <sys/file.h>
#include <mntent.h>
struct vgimportdevices_params { struct vgimportdevices_params {
uint32_t added_devices; uint32_t added_devices;
int root_vg_found;
char *root_dm_uuid;
char *root_vg_name;
}; };
static int _vgimportdevices_single(struct cmd_context *cmd, static int _vgimportdevices_single(struct cmd_context *cmd,
@ -35,6 +40,13 @@ static int _vgimportdevices_single(struct cmd_context *cmd,
int updated_pvs = 0; int updated_pvs = 0;
const char *idtypestr = NULL; /* deviceidtype_ARG ? */ 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) { dm_list_iterate_items(pvl, &vg->pvs) {
if (is_missing_pv(pvl->pv) || !pvl->pv->dev) { if (is_missing_pv(pvl->pv) || !pvl->pv->dev) {
memcpy(pvid, &pvl->pv->id.uuid, ID_LEN); memcpy(pvid, &pvl->pv->id.uuid, ID_LEN);
@ -86,6 +98,87 @@ static int _vgimportdevices_single(struct cmd_context *cmd,
return ECMD_PROCESSED; 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, * This command always scans all devices on the system,
* any pre-existing devices_file does not limit the scope. * 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. */ /* So that we can warn about this. */
cmd->handles_missing_pvs = 1; 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")) if (!lock_global(cmd, "ex"))
return ECMD_FAILED; return ECMD_FAILED;
@ -230,7 +336,13 @@ int vgimportdevices(struct cmd_context *cmd, int argc, char **argv)
goto out; 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: out:
if ((ret == ECMD_FAILED) && created_file) if ((ret == ECMD_FAILED) && created_file)
if (unlink(cmd->devices_file_path) < 0) if (unlink(cmd->devices_file_path) < 0)