blob: 09b3a7c3f5eca5d13a95c799aed875e51731ea73 [file] [log] [blame]
/*
* Copyright (C) 2016 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 "lvmcache.h"
#include "lvmetad-client.h"
#include "filter.h"
struct vgimportclone_params {
unsigned done;
unsigned total;
int import_vg;
int found_args;
struct dm_list arg_import;
const char *base_vgname;
const char *old_vgname;
const char *new_vgname;
};
struct vgimportclone_device {
struct dm_list list;
struct device *dev;
unsigned found_in_vg : 1;
};
static int _vgimportclone_pv_single(struct cmd_context *cmd, struct volume_group *vg,
struct physical_volume *pv, struct processing_handle *handle)
{
struct vgimportclone_params *vp = (struct vgimportclone_params *) handle->custom_handle;
struct vgimportclone_device *vd;
if (vg && is_orphan_vg(vg->name)) {
log_error("Cannot import clone of orphan PV %s.", dev_name(pv->dev));
return ECMD_FAILED;
}
if (!(vd = dm_pool_zalloc(cmd->mem, sizeof(*vd)))) {
log_error("alloc failed.");
return ECMD_FAILED;
}
vd->dev = pv->dev;
dm_list_add(&vp->arg_import, &vd->list);
log_debug("vgimportclone dev %s VG %s found to import",
dev_name(vd->dev), vg ? vg->name : "<none>");
vp->found_args++;
return ECMD_PROCESSED;
}
static int _vgimportclone_vg_single(struct cmd_context *cmd, const char *vg_name,
struct volume_group *vg, struct processing_handle *handle)
{
char uuid[64] __attribute__((aligned(8)));
struct vgimportclone_params *vp = (struct vgimportclone_params *) handle->custom_handle;
struct vgimportclone_device *vd;
struct pv_list *pvl, *new_pvl;
struct lv_list *lvl;
int devs_used_for_lv = 0;
int found;
if (vg_is_exported(vg) && !vp->import_vg) {
log_error("VG %s is exported, use the --import option.", vg->name);
goto_bad;
}
if (vg_status(vg) & PARTIAL_VG) {
log_error("VG %s is partial, it must be complete.", vg->name);
goto_bad;
}
/*
* N.B. lvs_in_vg_activated() is not smart enough to distinguish
* between LVs that are active in the original VG vs the cloned VG
* that's being imported, so check DEV_USED_FOR_LV.
*/
dm_list_iterate_items(pvl, &vg->pvs) {
if (pvl->pv->dev->flags & DEV_USED_FOR_LV) {
log_error("Device %s has active LVs, deactivate first.", dev_name(pvl->pv->dev));
devs_used_for_lv++;
}
}
if (devs_used_for_lv)
goto_bad;
/*
* The arg_import list must match the PVs in VG.
*/
dm_list_iterate_items(pvl, &vg->pvs) {
found = 0;
dm_list_iterate_items(vd, &vp->arg_import) {
if (pvl->pv->dev != vd->dev)
continue;
vd->found_in_vg = 1;
found = 1;
break;
}
if (!found) {
if (!id_write_format(&pvl->pv->id, uuid, sizeof(uuid)))
goto_bad;
/* all PVs in the VG must be imported together, pvl is missing from args. */
log_error("PV with UUID %s is part of VG %s, but is not included in the devices to import.",
uuid, vg->name);
log_error("All PVs in the VG must be imported together.");
goto_bad;
}
}
dm_list_iterate_items(vd, &vp->arg_import) {
if (!vd->found_in_vg) {
/* device arg is not in the VG. */
log_error("Device %s was not found in VG %s.", dev_name(vd->dev), vg->name);
log_error("The devices to import must match the devices in the VG.");
goto_bad;
}
}
/*
* Write changes.
*/
if (!archive(vg))
return_ECMD_FAILED;
if (vp->import_vg)
vg->status &= ~EXPORTED_VG;
if (!id_create(&vg->id))
goto_bad;
/* Low level vg_write code needs old_name to be set! */
vg->old_name = vg->name;
if (!(vg->name = dm_pool_strdup(vg->vgmem, vp->new_vgname)))
goto_bad;
dm_list_iterate_items(pvl, &vg->pvs) {
if (!(new_pvl = dm_pool_zalloc(vg->vgmem, sizeof(*new_pvl))))
goto_bad;
new_pvl->pv = pvl->pv;
if (!(pvl->pv->vg_name = dm_pool_strdup(vg->vgmem, vp->new_vgname)))
goto_bad;
if (vp->import_vg)
new_pvl->pv->status &= ~EXPORTED_VG;
/* Low level pv_write code needs old_id to be set! */
memcpy(&new_pvl->pv->old_id, &new_pvl->pv->id, sizeof(new_pvl->pv->id));
if (!id_create(&new_pvl->pv->id))
goto_bad;
dm_list_add(&vg->pv_write_list, &new_pvl->list);
}
dm_list_iterate_items(lvl, &vg->lvs)
memcpy(&lvl->lv->lvid, &vg->id, sizeof(vg->id));
if (!vg_write(vg) || !vg_commit(vg))
goto_bad;
return ECMD_PROCESSED;
bad:
return ECMD_FAILED;
}
int vgimportclone(struct cmd_context *cmd, int argc, char **argv)
{
struct vgimportclone_params vp = { 0 };
struct processing_handle *handle = NULL;
struct dm_list vgnameids_on_system; /* vgnameid_list */
struct vgnameid_list *vgnl;
struct vgimportclone_device *vd;
struct lvmcache_info *info;
const char *vgname;
char base_vgname[NAME_LEN] = { 0 };
char tmp_vgname[NAME_LEN] = { 0 };
unsigned int vgname_count;
int lvmetad_rescan = 0;
int ret = ECMD_FAILED;
if (!argc) {
log_error("PV names required.");
return EINVALID_CMD_LINE;
}
dm_list_init(&vgnameids_on_system);
dm_list_init(&vp.arg_import);
set_pv_notify(cmd);
vp.import_vg = arg_is_set(cmd, import_ARG);
if (lvmetad_used()) {
lvmetad_set_disabled(cmd, LVMETAD_DISABLE_REASON_DUPLICATES);
lvmetad_disconnect();
lvmetad_rescan = 1;
}
if (!(handle = init_processing_handle(cmd, NULL))) {
log_error("Failed to initialize processing handle.");
return ECMD_FAILED;
}
handle->custom_handle = &vp;
if (!lock_vol(cmd, VG_GLOBAL, LCK_VG_WRITE, NULL)) {
log_error("Unable to obtain global lock.");
destroy_processing_handle(cmd, handle);
return ECMD_FAILED;
}
if (!lockd_gl(cmd, "ex", 0))
goto_out;
cmd->lockd_gl_disable = 1;
/*
* Find the devices being imported which are named on the command line.
* They may be in the list of unchosen duplicates.
*/
log_debug("Finding devices to import.");
cmd->command->flags |= ENABLE_DUPLICATE_DEVS;
process_each_pv(cmd, argc, argv, NULL, 0, READ_ALLOW_EXPORTED, handle, _vgimportclone_pv_single);
if (vp.found_args != argc) {
log_error("Failed to find all devices.");
goto_out;
}
/*
* Find the VG name of the PVs being imported, save as old_vgname.
* N.B. If vd->dev is a duplicate, then it may not match info->dev.
*/
dm_list_iterate_items(vd, &vp.arg_import) {
if (!(info = lvmcache_info_from_pvid(vd->dev->pvid, NULL, 0))) {
log_error("Failed to find PVID for device %s in lvmcache.", dev_name(vd->dev));
goto_out;
}
if (!(vgname = lvmcache_vgname_from_info(info))) {
log_error("Failed to find VG name for device %s in lvmcache.", dev_name(vd->dev));
goto_out;
}
if (!vp.old_vgname) {
if (!(vp.old_vgname = dm_pool_strdup(cmd->mem, vgname)))
goto_out;
} else {
if (strcmp(vp.old_vgname, vgname)) {
log_error("Devices must be from the same VG.");
goto_out;
}
}
}
/*
* Pick a new VG name, save as new_vgname. The new name begins with
* the basevgname or old_vgname, plus a $i suffix, if necessary, to
* make it unique. This requires comparing the old_vgname with all the
* VG names on the system.
*/
if (arg_is_set(cmd, basevgname_ARG)) {
snprintf(base_vgname, sizeof(base_vgname) - 1, "%s", arg_str_value(cmd, basevgname_ARG, ""));
memcpy(tmp_vgname, base_vgname, NAME_LEN);
vgname_count = 0;
} else {
snprintf(base_vgname, sizeof(base_vgname) - 1, "%s", vp.old_vgname);
snprintf(tmp_vgname, sizeof(tmp_vgname) - 1, "%s1", vp.old_vgname);
vgname_count = 1;
}
if (!get_vgnameids(cmd, &vgnameids_on_system, NULL, 0))
goto_out;
retry_name:
dm_list_iterate_items(vgnl, &vgnameids_on_system) {
if (!strcmp(vgnl->vg_name, tmp_vgname)) {
vgname_count++;
snprintf(tmp_vgname, sizeof(tmp_vgname) - 1, "%s%u", base_vgname, vgname_count);
goto retry_name;
}
}
if (!(vp.new_vgname = dm_pool_strdup(cmd->mem, tmp_vgname)))
goto_out;
log_debug("Using new VG name %s.", vp.new_vgname);
/*
* Create a device filter so that we are only working with the devices
* in arg_import. With the original devs hidden (that arg_import were
* cloned from), we can read and write the cloned PVs and VG without
* touching the original PVs/VG.
*/
init_internal_filtering(1);
dm_list_iterate_items(vd, &vp.arg_import)
internal_filter_allow(cmd->mem, vd->dev);
lvmcache_destroy(cmd, 1, 0);
dev_cache_full_scan(cmd->full_filter);
log_debug("Changing VG %s to %s.", vp.old_vgname, vp.new_vgname);
/* We don't care if the new name comes before the old in lock order. */
lvmcache_lock_ordering(0);
if (!lock_vol(cmd, vp.new_vgname, LCK_VG_WRITE, NULL)) {
log_error("Can't get lock for new VG name %s", vp.new_vgname);
goto out;
}
ret = process_each_vg(cmd, 0, NULL, vp.old_vgname, NULL, READ_FOR_UPDATE | READ_ALLOW_EXPORTED, 0, handle, _vgimportclone_vg_single);
unlock_vg(cmd, NULL, vp.new_vgname);
out:
unlock_vg(cmd, NULL, VG_GLOBAL);
internal_filter_clear();
init_internal_filtering(0);
lvmcache_lock_ordering(1);
destroy_processing_handle(cmd, handle);
/* Enable lvmetad again if no duplicates are left. */
if (lvmetad_rescan) {
if (!lvmetad_connect(cmd)) {
log_warn("WARNING: Failed to connect to lvmetad.");
log_warn("WARNING: Update lvmetad with pvscan --cache.");
return ret;
}
if (!refresh_filters(cmd))
stack;
if (!lvmetad_pvscan_all_devs(cmd, 1)) {
log_warn("WARNING: Failed to scan devices.");
log_warn("WARNING: Update lvmetad with pvscan --cache.");
}
}
return ret;
}