| /* |
| * 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; |
| } |
| |