1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-12-21 13:34:40 +03:00
lvm2/tools/vgimportdevices.c
David Teigland c609dedc2f 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.
2024-05-21 16:29:12 -05:00

354 lines
9.7 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 "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,
const char *vg_name,
struct volume_group *vg,
struct processing_handle *handle)
{
struct vgimportdevices_params *vp = (struct vgimportdevices_params *) handle->custom_handle;
char pvid[ID_LEN + 1] __attribute__((aligned(8))) = { 0 };
struct pv_list *pvl;
struct physical_volume *pv;
int update_vg = 1;
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);
log_print("Not importing devices for VG %s with missing PV %s.",
vg->name, pvid);
return ECMD_PROCESSED;
}
}
/*
* We want to allow importing devices of foreign and shared
* VGs, but we do not want to update device_ids in those VGs.
*
* If --foreign is set, then foreign VGs will be passed
* to this function; add devices but don't update vg.
* shared VGs are passed to this function; add devices
* and do not update.
*/
if (vg_is_foreign(vg) || vg_is_shared(vg))
update_vg = 0;
dm_list_iterate_items(pvl, &vg->pvs) {
pv = pvl->pv;
idtypestr = pv->device_id_type;
memcpy(pvid, &pvl->pv->id.uuid, ID_LEN);
device_id_add(cmd, pv->dev, pvid, idtypestr, NULL, 0);
vp->added_devices++;
/* We could skip update if the device_id has not changed. */
if (!update_vg)
continue;
updated_pvs++;
}
/*
* Writes the device_id of each PV into the vg metadata.
* This is not a critial step and should not influence
* the result of the command.
*/
if (updated_pvs) {
if (!vg_write(vg) || !vg_commit(vg))
log_print("Failed to write device ids in VG metadata.");
}
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.
*
* This command adds the VG's devices to whichever
* devices_file is set in config or command line.
* If devices_file doesn't exist, it's created.
*
* If devices_file is "" then this file will scan all devices
* and show the devices that it would otherwise have added to
* the devices_file. The VG is not updated with device_ids.
*
* This command updates the VG metadata to add device_ids
* (if the metadata is missing them), unless an option is
* set to skip that, e.g. --nodeviceidupdate?
*
* If the VG found has a foreign system ID then an error
* will be printed. To import devices from a foreign VG:
* vgimportdevices --foreign -a
* vgimportdevices --foreign VG
*
* If there are duplicate VG names it will do nothing.
*
* If there are duplicate PVIDs related to VG it will do nothing,
* the user would need to add the PVs they want with lvmdevices --add.
*
* vgimportdevices -a (no vg arg) will import all accesible VGs.
*/
int vgimportdevices(struct cmd_context *cmd, int argc, char **argv)
{
struct vgimportdevices_params vp = { 0 };
struct processing_handle *handle;
int created_file = 0;
int ret = ECMD_FAILED;
if (arg_is_set(cmd, foreign_ARG))
cmd->include_foreign_vgs = 1;
cmd->include_shared_vgs = 1;
/* 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;
/*
* Prepare/create devices file preemptively because the error path for
* this case from process_each/setup_devices is not as clean.
* This means that when setup_devices is called, it the devices
* file steps will be redundant, and need to handle being repeated.
*/
if (!setup_devices_file(cmd)) {
log_error("Failed to set up devices file.");
return ECMD_FAILED;
}
if (!cmd->enable_devices_file) {
log_error("Devices file not enabled.");
return ECMD_FAILED;
}
if (!lock_devices_file(cmd, LOCK_EX)) {
log_error("Failed to lock the devices file.");
return ECMD_FAILED;
}
if (!devices_file_exists(cmd)) {
if (!devices_file_touch(cmd)) {
log_error("Failed to create devices file.");
return ECMD_FAILED;
}
created_file = 1;
}
/*
* 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);
if (!(handle = init_processing_handle(cmd, NULL))) {
log_error("Failed to initialize processing handle.");
goto out;
}
handle->custom_handle = &vp;
/*
* import is a case where we do not want to be limited by an existing
* devices file because we want to search outside the devices file for
* new devs to add to it, but we do want devices file entries on
* use_devices so we can update and write out that list.
*
* Ususally when devices file is enabled, we use filter-deviceid and
* skip filter-regex. In this import case it's reversed, and we skip
* filter-deviceid and use filter-regex.
*/
cmd->filter_deviceid_skip = 1;
cmd->filter_regex_with_devices_file = 1;
cmd->create_edit_devices_file = 1;
/*
* This helps a user bootstrap existing shared VGs into the devices
* file. Reading the vg to import devices requires locking, but
* lockstart won't find the vg before it's in the devices file.
* So, allow importing devices without an lvmlockd lock (in a
* a shared vg the vg metadata won't be updated with device ids,
* so the lvmlockd lock isn't protecting vg modification.)
*/
cmd->lockd_gl_disable = 1;
cmd->lockd_vg_disable = 1;
/*
* For each VG:
* device_id_add() each PV in the VG
* update device_ids in the VG (potentially)
*
* process_each_vg->label_scan->setup_devices
* setup_devices sees create_edit_devices_file is 1,
* so it does lock_devices_file(EX), then it creates/reads
* the devices file, then each device_id_add happens
* above, and then device_ids_write happens below.
*/
ret = process_each_vg(cmd, argc, argv, NULL, NULL, READ_FOR_UPDATE,
0, handle, _vgimportdevices_single);
if (ret == ECMD_FAILED) {
/*
* Error from setting up devices file or label_scan,
* _vgimportdevices_single does not return an error.
*/
goto_out;
}
if (!vp.added_devices) {
log_error("No devices to add.");
ret = ECMD_FAILED;
goto out;
}
if (!device_ids_write(cmd)) {
log_error("Failed to write the devices file.");
ret = ECMD_FAILED;
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)
log_sys_debug("unlink", cmd->devices_file_path);
destroy_processing_handle(cmd, handle);
return ret;
}