| /* |
| * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. |
| * Copyright (C) 2004-2009,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" |
| |
| static int _lv_is_in_vg(struct volume_group *vg, struct logical_volume *lv) |
| { |
| if (!lv || lv->vg != vg) |
| return 0; |
| |
| return 1; |
| } |
| |
| static struct dm_list *_lvh_in_vg(struct logical_volume *lv, struct volume_group *vg) |
| { |
| struct dm_list *lvh; |
| |
| dm_list_iterate(lvh, &vg->lvs) |
| if (lv == dm_list_item(lvh, struct lv_list)->lv) |
| return lvh; |
| |
| return NULL; |
| } |
| |
| static int _lv_tree_move(struct dm_list *lvh, |
| struct dm_list **lvht, |
| struct volume_group *vg_from, |
| struct volume_group *vg_to) |
| { |
| uint32_t s; |
| struct logical_volume *lv = dm_list_item(lvh, struct lv_list)->lv; |
| struct lv_segment *seg = first_seg(lv); |
| struct dm_list *lvh1; |
| |
| /* Update the list pointer refering to the item moving to @vg_to. */ |
| if (lvh == *lvht) |
| *lvht = dm_list_next(lvh, lvh); |
| |
| dm_list_move(&vg_to->lvs, lvh); |
| lv->vg = vg_to; |
| lv->lvid.id[0] = lv->vg->id; |
| |
| if (seg) |
| for (s = 0; s < seg->area_count; s++) |
| if (seg_type(seg, s) == AREA_LV && seg_lv(seg, s)) { |
| if ((lvh1 = _lvh_in_vg(seg_lv(seg, s), vg_from))) { |
| if (!_lv_tree_move(lvh1, lvht, vg_from, vg_to)) |
| return 0; |
| } else if (!_lvh_in_vg(seg_lv(seg, s), vg_to)) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int _move_one_lv(struct volume_group *vg_from, |
| struct volume_group *vg_to, |
| struct dm_list *lvh, |
| struct dm_list **lvht) |
| { |
| struct logical_volume *lv = dm_list_item(lvh, struct lv_list)->lv; |
| struct logical_volume *parent_lv; |
| |
| if (lv_is_active(lv)) { |
| if ((parent_lv = lv_parent(lv))) |
| log_error("Logical volume %s (part of %s) must be inactive.", display_lvname(lv), parent_lv->name); |
| else |
| log_error("Logical volume %s must be inactive.", display_lvname(lv)); |
| return 0; |
| } |
| |
| /* Bail out, if any allocations of @lv are still on PVs of @vg_from */ |
| if (lv_is_on_pvs(lv, &vg_from->pvs)) { |
| log_error("Can't split LV %s between " |
| "two Volume Groups", lv->name); |
| return 0; |
| } |
| |
| if (!_lv_tree_move(lvh, lvht, vg_from, vg_to)) |
| return 0; |
| |
| /* Moved pool metadata spare LV */ |
| if (vg_from->pool_metadata_spare_lv == lv) { |
| vg_to->pool_metadata_spare_lv = lv; |
| vg_from->pool_metadata_spare_lv = NULL; |
| } |
| |
| return 1; |
| } |
| |
| static int _move_lvs(struct volume_group *vg_from, struct volume_group *vg_to) |
| { |
| struct dm_list *lvh, *lvht; |
| struct logical_volume *lv; |
| struct lv_segment *seg; |
| struct physical_volume *pv; |
| struct volume_group *vg_with; |
| unsigned s; |
| |
| dm_list_iterate_safe(lvh, lvht, &vg_from->lvs) { |
| lv = dm_list_item(lvh, struct lv_list)->lv; |
| |
| if ((lv->status & SNAPSHOT)) |
| continue; |
| |
| if (lv_is_raid(lv)) |
| continue; |
| |
| if (lv_is_mirrored(lv)) |
| continue; |
| |
| if (lv_is_thin_pool(lv) || |
| lv_is_thin_volume(lv)) |
| continue; |
| |
| if (lv_is_cache(lv) || lv_is_cache_pool(lv)) |
| /* further checks by _move_cache() */ |
| continue; |
| |
| /* Ensure all the PVs used by this LV remain in the same */ |
| /* VG as each other */ |
| vg_with = NULL; |
| dm_list_iterate_items(seg, &lv->segments) { |
| for (s = 0; s < seg->area_count; s++) { |
| /* FIXME Check AREA_LV too */ |
| if (seg_type(seg, s) != AREA_PV) |
| continue; |
| |
| pv = seg_pv(seg, s); |
| if (vg_with) { |
| if (!pv_is_in_vg(vg_with, pv)) { |
| log_error("Can't split Logical " |
| "Volume %s between " |
| "two Volume Groups", |
| lv->name); |
| return 0; |
| } |
| continue; |
| } |
| |
| if (pv_is_in_vg(vg_from, pv)) { |
| vg_with = vg_from; |
| continue; |
| } |
| if (pv_is_in_vg(vg_to, pv)) { |
| vg_with = vg_to; |
| continue; |
| } |
| log_error("Physical Volume %s not found", |
| pv_dev_name(pv)); |
| return 0; |
| } |
| |
| } |
| |
| if (vg_with == vg_from) |
| continue; |
| |
| /* Move this LV */ |
| if (!_move_one_lv(vg_from, vg_to, lvh, &lvht)) |
| return_0; |
| } |
| |
| /* FIXME Ensure no LVs contain segs pointing at LVs in the other VG */ |
| |
| return 1; |
| } |
| |
| /* |
| * Move the hidden / internal "snapshotN" LVs.from 'vg_from' to 'vg_to'. |
| */ |
| static int _move_snapshots(struct volume_group *vg_from, |
| struct volume_group *vg_to) |
| { |
| struct dm_list *lvh, *lvht; |
| struct logical_volume *lv; |
| struct lv_segment *seg; |
| int cow_from = 0; |
| int origin_from = 0; |
| |
| dm_list_iterate_safe(lvh, lvht, &vg_from->lvs) { |
| lv = dm_list_item(lvh, struct lv_list)->lv; |
| |
| if (!(lv->status & SNAPSHOT)) |
| continue; |
| |
| dm_list_iterate_items(seg, &lv->segments) { |
| cow_from = _lv_is_in_vg(vg_from, seg->cow); |
| origin_from = _lv_is_in_vg(vg_from, seg->origin); |
| |
| if (cow_from && origin_from) |
| continue; |
| if ((!cow_from && origin_from) || |
| (cow_from && !origin_from)) { |
| log_error("Can't split snapshot %s between" |
| " two Volume Groups", seg->cow->name); |
| return 0; |
| } |
| |
| /* |
| * At this point, the cow and origin should already be |
| * in vg_to. |
| */ |
| if (_lv_is_in_vg(vg_to, seg->cow) && |
| _lv_is_in_vg(vg_to, seg->origin)) { |
| if (!_move_one_lv(vg_from, vg_to, lvh, &lvht)) |
| return_0; |
| } |
| } |
| |
| } |
| |
| return 1; |
| } |
| |
| static int _move_mirrors(struct volume_group *vg_from, |
| struct volume_group *vg_to) |
| { |
| struct dm_list *lvh, *lvht; |
| struct logical_volume *lv; |
| struct lv_segment *seg, *log_seg; |
| unsigned s, seg_in, log_in; |
| |
| dm_list_iterate_safe(lvh, lvht, &vg_from->lvs) { |
| lv = dm_list_item(lvh, struct lv_list)->lv; |
| |
| if (lv_is_raid(lv)) |
| continue; |
| |
| if (!lv_is_mirrored(lv)) |
| continue; |
| |
| /* Ignore, if no allocations on PVs of @vg_to */ |
| if (!lv_is_on_pvs(lv, &vg_to->pvs)) |
| continue; |
| |
| seg = first_seg(lv); |
| |
| seg_in = 0; |
| for (s = 0; s < seg->area_count; s++) |
| if (_lv_is_in_vg(vg_to, seg_lv(seg, s))) |
| seg_in++; |
| |
| log_in = !seg->log_lv; |
| if (seg->log_lv) { |
| log_seg = first_seg(seg->log_lv); |
| if (seg_is_mirrored(log_seg)) { |
| log_in = 1; |
| |
| /* Ensure each log dev is in vg_to */ |
| for (s = 0; s < log_seg->area_count; s++) |
| log_in = log_in && |
| _lv_is_in_vg(vg_to, |
| seg_lv(log_seg, s)); |
| } else |
| log_in = _lv_is_in_vg(vg_to, seg->log_lv); |
| } |
| |
| if ((seg_in && seg_in < seg->area_count) || |
| (seg_in && seg->log_lv && !log_in) || |
| (!seg_in && seg->log_lv && log_in)) { |
| log_error("Can't split mirror %s between " |
| "two Volume Groups", lv->name); |
| return 0; |
| } |
| |
| if (seg_in == seg->area_count && log_in) { |
| if (!_move_one_lv(vg_from, vg_to, lvh, &lvht)) |
| return_0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * Check for any RAID LVs with allocations on PVs of @vg_to. |
| * |
| * If these don't have any allocations on PVs of @vg_from, |
| * move their whole lv stack across to @vg_to including the |
| * top-level RAID LV. |
| */ |
| static int _move_raids(struct volume_group *vg_from, |
| struct volume_group *vg_to) |
| { |
| struct dm_list *lvh, *lvht; |
| struct logical_volume *lv; |
| |
| dm_list_iterate_safe(lvh, lvht, &vg_from->lvs) { |
| lv = dm_list_item(lvh, struct lv_list)->lv; |
| |
| if (!lv_is_raid(lv)) |
| continue; |
| |
| /* Ignore, if no allocations on PVs of @vg_to */ |
| if (!lv_is_on_pvs(lv, &vg_to->pvs)) |
| continue; |
| |
| /* If allocations are on PVs of @vg_to -> move RAID LV stack across */ |
| if (!_move_one_lv(vg_from, vg_to, lvh, &lvht)) |
| return_0; |
| } |
| |
| return 1; |
| } |
| |
| static int _move_thins(struct volume_group *vg_from, |
| struct volume_group *vg_to) |
| { |
| struct dm_list *lvh, *lvht; |
| struct logical_volume *lv, *data_lv; |
| struct lv_segment *seg; |
| |
| dm_list_iterate_safe(lvh, lvht, &vg_from->lvs) { |
| lv = dm_list_item(lvh, struct lv_list)->lv; |
| |
| if (lv_is_thin_volume(lv)) { |
| seg = first_seg(lv); |
| data_lv = seg_lv(first_seg(seg->pool_lv), 0); |
| |
| /* Ignore, if no allocations on PVs of @vg_to */ |
| if (!lv_is_on_pvs(data_lv, &vg_to->pvs) || |
| (seg->external_lv && !lv_is_on_pvs(seg->external_lv, &vg_to->pvs))) |
| continue; |
| |
| if ((_lv_is_in_vg(vg_to, data_lv) || |
| _lv_is_in_vg(vg_to, seg->external_lv))) { |
| if (_lv_is_in_vg(vg_from, seg->external_lv) || |
| _lv_is_in_vg(vg_from, data_lv)) { |
| log_error("Can't split external origin %s " |
| "and pool %s between two Volume Groups.", |
| seg->external_lv->name, |
| seg->pool_lv->name); |
| return 0; |
| } |
| if (!_move_one_lv(vg_from, vg_to, lvh, &lvht)) |
| return_0; |
| } |
| } else if (lv_is_thin_pool(lv)) { |
| seg = first_seg(lv); |
| data_lv = seg_lv(seg, 0); |
| |
| /* Ignore, if no allocations on PVs of @vg_to */ |
| if (!lv_is_on_pvs(data_lv, &vg_to->pvs)) |
| continue; |
| |
| if (_lv_is_in_vg(vg_to, data_lv) || |
| _lv_is_in_vg(vg_to, seg->metadata_lv)) { |
| if (_lv_is_in_vg(vg_from, seg->metadata_lv) || |
| _lv_is_in_vg(vg_from, data_lv)) { |
| log_error("Can't split pool data and metadata %s " |
| "between two Volume Groups.", |
| lv->name); |
| return 0; |
| } |
| if (!_move_one_lv(vg_from, vg_to, lvh, &lvht)) |
| return_0; |
| } |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int _move_cache(struct volume_group *vg_from, |
| struct volume_group *vg_to) |
| { |
| int is_moving; |
| struct dm_list *lvh, *lvht; |
| struct logical_volume *lv, *data, *meta, *orig; |
| struct lv_segment *seg, *cache_seg; |
| |
| dm_list_iterate_safe(lvh, lvht, &vg_from->lvs) { |
| lv = dm_list_item(lvh, struct lv_list)->lv; |
| data = meta = orig = NULL; |
| seg = first_seg(lv); |
| |
| if (!lv_is_cache(lv) && !lv_is_cache_pool(lv)) |
| continue; |
| |
| /* |
| * FIXME: The code seems to move cache LVs fine, but it |
| * hasn't been well tested and it causes problems |
| * when just splitting PVs that don't contain |
| * cache LVs. |
| * Waiting for next release before fixing and enabling. |
| */ |
| log_error("Unable to split VG while it contains cache LVs"); |
| return 0; |
| |
| /* NOTREACHED */ |
| |
| if (lv_is_cache(lv)) { |
| orig = seg_lv(seg, 0); |
| data = seg_lv(first_seg(seg->pool_lv), 0); |
| meta = first_seg(seg->pool_lv)->metadata_lv; |
| /* Ensure all components are coming along */ |
| is_moving = _lv_is_in_vg(vg_to, orig); |
| } else { |
| if (!dm_list_empty(&seg->lv->segs_using_this_lv) && |
| !(cache_seg = get_only_segment_using_this_lv(seg->lv))) |
| return_0; |
| orig = seg_lv(cache_seg, 0); |
| data = seg_lv(seg, 0); |
| meta = seg->metadata_lv; |
| |
| if (_lv_is_in_vg(vg_to, data) || |
| _lv_is_in_vg(vg_to, meta)) |
| is_moving = 1; |
| } |
| |
| if (!lv_is_on_pvs(data, &vg_to->pvs)) |
| continue; |
| |
| if (!lv_is_on_pvs(meta, &vg_to->pvs)) |
| continue; |
| |
| if (orig && (_lv_is_in_vg(vg_to, orig) != is_moving)) { |
| log_error("Can't split %s and its origin (%s)" |
| " into separate VGs", lv->name, orig->name); |
| return 0; |
| } |
| |
| if (data && (_lv_is_in_vg(vg_to, data) != is_moving)) { |
| log_error("Can't split %s and its cache pool" |
| " data LV (%s) into separate VGs", |
| lv->name, data->name); |
| return 0; |
| } |
| |
| if (meta && (_lv_is_in_vg(vg_to, meta) != is_moving)) { |
| log_error("Can't split %s and its cache pool" |
| " metadata LV (%s) into separate VGs", |
| lv->name, meta->name); |
| return 0; |
| } |
| if (!_move_one_lv(vg_from, vg_to, lvh, &lvht)) |
| return_0; |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * Create or open the destination of the vgsplit operation. |
| * Returns |
| * - non-NULL: VG handle w/VG lock held |
| * - NULL: no VG lock held |
| */ |
| static struct volume_group *_vgsplit_to(struct cmd_context *cmd, |
| const char *vg_name_to, |
| int *existing_vg) |
| { |
| struct volume_group *vg_to = NULL; |
| |
| log_verbose("Checking for new volume group \"%s\"", vg_name_to); |
| /* |
| * First try to create a new VG. If we cannot create it, |
| * and we get FAILED_EXIST (we will not be holding a lock), |
| * a VG must already exist with this name. We then try to |
| * read the existing VG - the vgsplit will be into an existing VG. |
| * |
| * Otherwise, if the lock was successful, it must be the case that |
| * we obtained a WRITE lock and could not find the vgname in the |
| * system. Thus, the split will be into a new VG. |
| */ |
| vg_to = vg_lock_and_create(cmd, vg_name_to); |
| if (vg_read_error(vg_to) == FAILED_LOCKING) { |
| log_error("Can't get lock for %s", vg_name_to); |
| release_vg(vg_to); |
| return NULL; |
| } |
| if (vg_read_error(vg_to) == FAILED_EXIST) { |
| *existing_vg = 1; |
| release_vg(vg_to); |
| vg_to = vg_read_for_update(cmd, vg_name_to, NULL, 0, 0); |
| |
| if (vg_read_error(vg_to)) { |
| release_vg(vg_to); |
| return_NULL; |
| } |
| |
| } else if (vg_read_error(vg_to) == SUCCESS) { |
| *existing_vg = 0; |
| } |
| return vg_to; |
| } |
| |
| /* |
| * Open the source of the vgsplit operation. |
| * Returns |
| * - non-NULL: VG handle w/VG lock held |
| * - NULL: no VG lock held |
| */ |
| static struct volume_group *_vgsplit_from(struct cmd_context *cmd, |
| const char *vg_name_from) |
| { |
| struct volume_group *vg_from; |
| |
| log_verbose("Checking for volume group \"%s\"", vg_name_from); |
| |
| vg_from = vg_read_for_update(cmd, vg_name_from, NULL, 0, 0); |
| if (vg_read_error(vg_from)) { |
| release_vg(vg_from); |
| return NULL; |
| } |
| |
| if (is_lockd_type(vg_from->lock_type)) { |
| log_error("vgsplit not allowed for lock_type %s", vg_from->lock_type); |
| unlock_and_release_vg(cmd, vg_from, vg_name_from); |
| return NULL; |
| } |
| |
| return vg_from; |
| } |
| |
| /* |
| * Has the user given an option related to a new vg as the split destination? |
| */ |
| static int new_vg_option_specified(struct cmd_context *cmd) |
| { |
| return(arg_is_set(cmd, clustered_ARG) || |
| arg_is_set(cmd, alloc_ARG) || |
| arg_is_set(cmd, maxphysicalvolumes_ARG) || |
| arg_is_set(cmd, maxlogicalvolumes_ARG) || |
| arg_is_set(cmd, vgmetadatacopies_ARG)); |
| } |
| |
| int vgsplit(struct cmd_context *cmd, int argc, char **argv) |
| { |
| struct vgcreate_params vp_new; |
| struct vgcreate_params vp_def; |
| const char *vg_name_from, *vg_name_to; |
| struct volume_group *vg_to = NULL, *vg_from = NULL; |
| int opt; |
| int existing_vg = 0; |
| int r = ECMD_FAILED; |
| const char *lv_name; |
| int lock_vg_from_first = 1; |
| |
| if ((arg_is_set(cmd, name_ARG) + argc) < 3) { |
| log_error("Existing VG, new VG and either physical volumes " |
| "or logical volume required."); |
| return EINVALID_CMD_LINE; |
| } |
| |
| if (arg_is_set(cmd, name_ARG) && (argc > 2)) { |
| log_error("A logical volume name cannot be given with " |
| "physical volumes."); |
| return ECMD_FAILED; |
| } |
| |
| /* Needed change the global VG namespace. */ |
| if (!lockd_gl(cmd, "ex", LDGL_UPDATE_NAMES)) |
| return_ECMD_FAILED; |
| |
| if (arg_is_set(cmd, name_ARG)) |
| lv_name = arg_value(cmd, name_ARG); |
| else |
| lv_name = NULL; |
| |
| vg_name_from = skip_dev_dir(cmd, argv[0], NULL); |
| vg_name_to = skip_dev_dir(cmd, argv[1], NULL); |
| argc -= 2; |
| argv += 2; |
| |
| if (!strcmp(vg_name_to, vg_name_from)) { |
| log_error("Duplicate volume group name \"%s\"", vg_name_from); |
| return ECMD_FAILED; |
| } |
| |
| if (strcmp(vg_name_to, vg_name_from) < 0) |
| lock_vg_from_first = 0; |
| |
| if (lock_vg_from_first) { |
| if (!(vg_from = _vgsplit_from(cmd, vg_name_from))) |
| return_ECMD_FAILED; |
| /* |
| * Set metadata format of original VG. |
| * NOTE: We must set the format before calling vg_lock_and_create() |
| * since vg_lock_and_create() calls the per-format constructor. |
| */ |
| cmd->fmt = vg_from->fid->fmt; |
| |
| if (!(vg_to = _vgsplit_to(cmd, vg_name_to, &existing_vg))) { |
| unlock_and_release_vg(cmd, vg_from, vg_name_from); |
| return_ECMD_FAILED; |
| } |
| } else { |
| if (!(vg_to = _vgsplit_to(cmd, vg_name_to, &existing_vg))) |
| return_ECMD_FAILED; |
| |
| if (!(vg_from = _vgsplit_from(cmd, vg_name_from))) { |
| unlock_and_release_vg(cmd, vg_to, vg_name_to); |
| return_ECMD_FAILED; |
| } |
| |
| if (cmd->fmt != vg_from->fid->fmt) { |
| /* In this case we don't know the vg_from->fid->fmt */ |
| log_error("Unable to set new VG metadata type based on " |
| "source VG format - use -M option."); |
| goto bad; |
| } |
| } |
| |
| if (existing_vg) { |
| if (new_vg_option_specified(cmd)) { |
| log_error("Volume group \"%s\" exists, but new VG " |
| "option specified", vg_name_to); |
| goto bad; |
| } |
| if (!vgs_are_compatible(cmd, vg_from,vg_to)) |
| goto_bad; |
| } else { |
| if (!vgcreate_params_set_defaults(cmd, &vp_def, vg_from)) { |
| r = EINVALID_CMD_LINE; |
| goto_bad; |
| } |
| vp_def.vg_name = vg_name_to; |
| if (!vgcreate_params_set_from_args(cmd, &vp_new, &vp_def)) { |
| r = EINVALID_CMD_LINE; |
| goto_bad; |
| } |
| |
| if (!vgcreate_params_validate(cmd, &vp_new)) { |
| r = EINVALID_CMD_LINE; |
| goto_bad; |
| } |
| |
| if (!vg_set_extent_size(vg_to, vp_new.extent_size) || |
| !vg_set_max_lv(vg_to, vp_new.max_lv) || |
| !vg_set_max_pv(vg_to, vp_new.max_pv) || |
| !vg_set_alloc_policy(vg_to, vp_new.alloc) || |
| !vg_set_clustered(vg_to, vp_new.clustered) || |
| !vg_set_system_id(vg_to, vp_new.system_id) || |
| !vg_set_mda_copies(vg_to, vp_new.vgmetadatacopies)) |
| goto_bad; |
| } |
| |
| /* Archive vg_from before changing it */ |
| if (!archive(vg_from)) |
| goto_bad; |
| |
| /* Move PVs across to new structure */ |
| for (opt = 0; opt < argc; opt++) { |
| dm_unescape_colons_and_at_signs(argv[opt], NULL, NULL); |
| if (!move_pv(vg_from, vg_to, argv[opt])) |
| goto_bad; |
| } |
| |
| /* If an LV given on the cmdline, move used_by PVs */ |
| if (lv_name && !move_pvs_used_by_lv(vg_from, vg_to, lv_name)) |
| goto_bad; |
| |
| /* |
| * First move any required RAID LVs across recursively. |
| * Reject if they get split between VGs. |
| * |
| * This moves the whole LV stack across, thus _move_lvs() below |
| * ain't hit any of their MetaLVs/DataLVs any more but'll still |
| * work for all other type specific moves following it. |
| */ |
| if (!(_move_raids(vg_from, vg_to))) |
| goto_bad; |
| |
| /* Move required sub LVs across, checking consistency */ |
| if (!(_move_lvs(vg_from, vg_to))) |
| goto_bad; |
| |
| /* Move required mirrors across */ |
| if (!(_move_mirrors(vg_from, vg_to))) |
| goto_bad; |
| |
| /* Move required pools across */ |
| if (!(_move_thins(vg_from, vg_to))) |
| goto_bad; |
| |
| /* Move required cache LVs across */ |
| if (!(_move_cache(vg_from, vg_to))) |
| goto_bad; |
| |
| /* Move required snapshots across */ |
| if (!(_move_snapshots(vg_from, vg_to))) |
| goto_bad; |
| |
| /* Split metadata areas and check if both vgs have at least one area */ |
| if (!(vg_split_mdas(cmd, vg_from, vg_to)) && vg_from->pv_count) { |
| log_error("Cannot split: Nowhere to store metadata for new Volume Group"); |
| goto bad; |
| } |
| |
| /* Set proper name for all PVs in new VG */ |
| if (!vg_rename(cmd, vg_to, vg_name_to)) |
| goto_bad; |
| |
| /* store it on disks */ |
| log_verbose("Writing out updated volume groups"); |
| |
| /* |
| * First, write out the new VG as EXPORTED. We do this first in case |
| * there is a crash - we will still have the new VG information, in an |
| * exported state. Recovery after this point would importing and removal |
| * of the new VG and redoing the vgsplit. |
| * FIXME: recover automatically or instruct the user? |
| */ |
| vg_to->status |= EXPORTED_VG; |
| |
| if (!archive(vg_to)) |
| goto_bad; |
| |
| if (!vg_write(vg_to) || !vg_commit(vg_to)) |
| goto_bad; |
| |
| lvmetad_vg_update_finish(vg_to); |
| |
| backup(vg_to); |
| |
| /* |
| * Next, write out the updated old VG. If we crash after this point, |
| * recovery is a vgimport on the new VG. |
| * FIXME: recover automatically or instruct the user? |
| */ |
| if (vg_from->pv_count) { |
| if (!vg_write(vg_from) || !vg_commit(vg_from)) |
| goto_bad; |
| |
| lvmetad_vg_update_finish(vg_from); |
| |
| backup(vg_from); |
| } |
| |
| /* |
| * Finally, remove the EXPORTED flag from the new VG and write it out. |
| */ |
| if (!test_mode()) { |
| release_vg(vg_to); |
| vg_to = vg_read_for_update(cmd, vg_name_to, NULL, |
| READ_ALLOW_EXPORTED, 0); |
| if (vg_read_error(vg_to)) { |
| log_error("Volume group \"%s\" became inconsistent: " |
| "please fix manually", vg_name_to); |
| goto bad; |
| } |
| } |
| |
| vg_to->status &= ~EXPORTED_VG; |
| |
| if (!vg_write(vg_to) || !vg_commit(vg_to)) |
| goto_bad; |
| |
| lvmetad_vg_update_finish(vg_to); |
| |
| backup(vg_to); |
| |
| log_print_unless_silent("%s volume group \"%s\" successfully split from \"%s\"", |
| existing_vg ? "Existing" : "New", |
| vg_to->name, vg_from->name); |
| |
| r = ECMD_PROCESSED; |
| |
| bad: |
| /* |
| * vg_to references elements moved from vg_from |
| * so vg_to has to be freed first. |
| */ |
| unlock_and_release_vg(cmd, vg_to, vg_name_to); |
| unlock_and_release_vg(cmd, vg_from, vg_name_from); |
| |
| return r; |
| } |