mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-21 13:34:40 +03:00
c609dedc2f
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.
354 lines
9.7 KiB
C
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;
|
|
}
|
|
|