diff options
Diffstat (limited to 'tools/lvconvert_snapshot.c')
-rw-r--r-- | tools/lvconvert_snapshot.c | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/tools/lvconvert_snapshot.c b/tools/lvconvert_snapshot.c new file mode 100644 index 000000000..51ac9dcf8 --- /dev/null +++ b/tools/lvconvert_snapshot.c @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2005-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 "polldaemon.h" +#include "lv_alloc.h" +#include "lvconvert_poll.h" +#include "command-lines-count.h" + +static int _lvconvert_snapshot(struct cmd_context *cmd, + struct logical_volume *lv, + const char *origin_name) +{ + struct logical_volume *org; + const char *snap_name = display_lvname(lv); + uint32_t chunk_size; + int zero; + + if (!(org = find_lv(lv->vg, origin_name))) { + log_error("Couldn't find origin volume %s in Volume group %s.", + origin_name, lv->vg->name); + return 0; + } + + if (org == lv) { + log_error("Unable to use %s as both snapshot and origin.", snap_name); + return 0; + } + + chunk_size = arg_uint_value(cmd, chunksize_ARG, 8); + if (chunk_size < 8 || chunk_size > 1024 || !is_power_of_2(chunk_size)) { + log_error("Chunk size must be a power of 2 in the range 4K to 512K."); + return 0; + } + log_verbose("Setting chunk size to %s.", display_size(cmd, chunk_size)); + + if (!cow_has_min_chunks(lv->vg, lv->le_count, chunk_size)) + return_0; + + /* + * check_lv_rules() checks cannot be done via command definition + * rules because this LV is not processed by process_each_lv. + */ + if (lv_is_locked(org) || lv_is_pvmove(org)) { + log_error("Unable to use LV %s as snapshot origin: LV is %s.", + display_lvname(lv), lv_is_locked(org) ? "locked" : "pvmove"); + return 0; + } + + /* + * check_lv_types() checks cannot be done via command definition + * LV_foo specification because this LV is not processed by process_each_lv. + */ + if (lv_is_cache_type(org) || + lv_is_thin_type(org) || + lv_is_mirrored(org) || + lv_is_cow(org)) { + log_error("Unable to use LV %s as snapshot origin: invald LV type.", + display_lvname(lv)); + return 0; + } + + log_warn("WARNING: Converting logical volume %s to snapshot exception store.", + snap_name); + log_warn("THIS WILL DESTROY CONTENT OF LOGICAL VOLUME (filesystem etc.)"); + + if (!arg_count(cmd, yes_ARG) && + yes_no_prompt("Do you really want to convert %s? [y/n]: ", + snap_name) == 'n') { + log_error("Conversion aborted."); + return 0; + } + + if (!deactivate_lv(cmd, lv)) { + log_error("Couldn't deactivate logical volume %s.", snap_name); + return 0; + } + + if (first_seg(lv)->segtype->flags & SEG_CANNOT_BE_ZEROED) + zero = 0; + else + zero = arg_int_value(cmd, zero_ARG, 1); + + if (!zero || !(lv->status & LVM_WRITE)) + log_warn("WARNING: %s not zeroed.", snap_name); + else { + lv->status |= LV_TEMPORARY; + if (!activate_lv_local(cmd, lv) || + !wipe_lv(lv, (struct wipe_params) { .do_zero = 1 })) { + log_error("Aborting. Failed to wipe snapshot exception store."); + return 0; + } + lv->status &= ~LV_TEMPORARY; + /* Deactivates cleared metadata LV */ + if (!deactivate_lv_local(lv->vg->cmd, lv)) { + log_error("Failed to deactivate zeroed snapshot exception store."); + return 0; + } + } + + if (!archive(lv->vg)) + return_0; + + if (!vg_add_snapshot(org, lv, NULL, org->le_count, chunk_size)) { + log_error("Couldn't create snapshot."); + return 0; + } + + /* store vg on disk(s) */ + if (!lv_update_and_reload(org)) + return_0; + + log_print_unless_silent("Logical volume %s converted to snapshot.", snap_name); + + return 1; +} + +static int _lvconvert_merge_old_snapshot(struct cmd_context *cmd, + struct logical_volume *lv, + struct logical_volume **lv_to_poll) +{ + int merge_on_activate = 0; + struct logical_volume *origin = origin_from_cow(lv); + struct lv_segment *snap_seg = find_snapshot(lv); + struct lvinfo info; + dm_percent_t snap_percent; + + if (lv_is_external_origin(origin_from_cow(lv))) { + log_error("Cannot merge snapshot \"%s\" into " + "the read-only external origin \"%s\".", + lv->name, origin_from_cow(lv)->name); + return 0; + } + + /* FIXME: test when snapshot is remotely active */ + if (lv_info(cmd, lv, 0, &info, 1, 0) + && info.exists && info.live_table && + (!lv_snapshot_percent(lv, &snap_percent) || + snap_percent == DM_PERCENT_INVALID)) { + log_error("Unable to merge invalidated snapshot LV \"%s\".", + lv->name); + return 0; + } + + if (snap_seg->segtype->ops->target_present && + !snap_seg->segtype->ops->target_present(cmd, snap_seg, NULL)) { + log_error("Can't initialize snapshot merge. " + "Missing support in kernel?"); + return 0; + } + + if (!archive(lv->vg)) + return_0; + + /* + * Prevent merge with open device(s) as it would likely lead + * to application/filesystem failure. Merge on origin's next + * activation if either the origin or snapshot LV are currently + * open. + * + * FIXME testing open_count is racey; snapshot-merge target's + * constructor and DM should prevent appropriate devices from + * being open. + */ + if (lv_is_active_locally(origin)) { + if (!lv_check_not_in_use(origin, 0)) { + log_print_unless_silent("Can't merge until origin volume is closed."); + merge_on_activate = 1; + } else if (!lv_check_not_in_use(lv, 0)) { + log_print_unless_silent("Can't merge until snapshot is closed."); + merge_on_activate = 1; + } + } else if (vg_is_clustered(origin->vg) && lv_is_active(origin)) { + /* When it's active somewhere else */ + log_print_unless_silent("Can't check whether remotely active snapshot is open."); + merge_on_activate = 1; + } + + init_snapshot_merge(snap_seg, origin); + + if (merge_on_activate) { + /* Store and commit vg but skip starting the merge */ + if (!vg_write(lv->vg) || !vg_commit(lv->vg)) + return_0; + backup(lv->vg); + } else { + /* Perform merge */ + if (!lv_update_and_reload(origin)) + return_0; + + *lv_to_poll = origin; + } + + if (merge_on_activate) + log_print_unless_silent("Merging of snapshot %s will occur on " + "next activation of %s.", + display_lvname(lv), display_lvname(origin)); + else + log_print_unless_silent("Merging of volume %s started.", + display_lvname(lv)); + + return 1; +} + +static int _lvconvert_splitsnapshot(struct cmd_context *cmd, struct logical_volume *cow) +{ + struct volume_group *vg = cow->vg; + const char *cow_name = display_lvname(cow); + + if (lv_is_virtual_origin(origin_from_cow(cow))) { + log_error("Unable to split off snapshot %s with virtual origin.", cow_name); + return 0; + } + + if (!(vg->fid->fmt->features & FMT_MDAS)) { + log_error("Unable to split off snapshot %s using old LVM1-style metadata.", cow_name); + return 0; + } + + if (is_lockd_type(vg->lock_type)) { + /* FIXME: we need to create a lock for the new LV. */ + log_error("Unable to split snapshots in VG with lock_type %s.", vg->lock_type); + return 0; + } + + if (lv_is_active_locally(cow)) { + if (!lv_check_not_in_use(cow, 1)) + return_0; + + if ((arg_count(cmd, force_ARG) == PROMPT) && + !arg_count(cmd, yes_ARG) && + lv_is_visible(cow) && + lv_is_active(cow)) { + if (yes_no_prompt("Do you really want to split off active " + "logical volume %s? [y/n]: ", cow_name) == 'n') { + log_error("Logical volume %s not split.", cow_name); + return 0; + } + } + } + + if (!archive(vg)) + return_0; + + log_verbose("Splitting snapshot %s from its origin.", cow_name); + + if (!vg_remove_snapshot(cow)) + return_0; + + backup(vg); + + log_print_unless_silent("Logical Volume %s split from its origin.", cow_name); + + return 1; +} + +/* + * Merge a COW snapshot LV into its origin. + */ +int lvconvert_merge_snapshot_single(struct cmd_context *cmd, + struct logical_volume *lv, + struct processing_handle *handle) +{ + struct lvconvert_result *lr = (struct lvconvert_result *) handle->custom_handle; + struct logical_volume *lv_to_poll = NULL; + struct convert_poll_id_list *idl; + + if (!_lvconvert_merge_old_snapshot(cmd, lv, &lv_to_poll)) + return_ECMD_FAILED; + + if (lv_to_poll) { + if (!(idl = convert_poll_id_list_create(cmd, lv_to_poll))) + return_ECMD_FAILED; + dm_list_add(&lr->poll_idls, &idl->list); + lr->need_polling = 1; + } + + return ECMD_PROCESSED; +} + +static int _lvconvert_merge_snapshot_check(struct cmd_context *cmd, struct logical_volume *lv, + struct processing_handle *handle, + int lv_is_named_arg) +{ + if (!lv_is_visible(lv)) { + log_error("Operation not permitted (%s %d) on hidden LV %s.", + cmd->command->command_line_id, cmd->command->command_line_enum, + display_lvname(lv)); + return 0; + } + + return 1; +} + +int lvconvert_merge_snapshot_cmd(struct cmd_context *cmd, int argc, char **argv) +{ + struct processing_handle *handle; + struct lvconvert_result lr = { 0 }; + struct convert_poll_id_list *idl; + int ret, poll_ret; + + dm_list_init(&lr.poll_idls); + + if (!(handle = init_processing_handle(cmd, NULL))) { + log_error("Failed to initialize processing handle."); + return ECMD_FAILED; + } + + handle->custom_handle = &lr; + + ret = process_each_lv(cmd, cmd->position_argc, cmd->position_argv, NULL, NULL, READ_FOR_UPDATE, + handle, &_lvconvert_merge_snapshot_check, &lvconvert_merge_snapshot_single); + + if (lr.need_polling) { + dm_list_iterate_items(idl, &lr.poll_idls) { + poll_ret = lvconvert_poll_by_id(cmd, idl->id, + arg_is_set(cmd, background_ARG), 1, 0); + if (poll_ret > ret) + ret = poll_ret; + } + } + + destroy_processing_handle(cmd, handle); + + return ret; +} + +/* + * Separate a COW snapshot from its origin. + * + * lvconvert --splitsnapshot LV_snapshot + * lvconvert_split_cow_snapshot + */ + +static int _lvconvert_split_snapshot_single(struct cmd_context *cmd, + struct logical_volume *lv, + struct processing_handle *handle) +{ + if (!_lvconvert_splitsnapshot(cmd, lv)) + return_ECMD_FAILED; + + return ECMD_PROCESSED; +} + +int lvconvert_split_snapshot_cmd(struct cmd_context *cmd, int argc, char **argv) +{ + return process_each_lv(cmd, 1, cmd->position_argv, NULL, NULL, READ_FOR_UPDATE, + NULL, &lvconvert_generic_check, &_lvconvert_split_snapshot_single); +} + +/* + * Combine two LVs that were once an origin/cow pair of LVs, were then + * separated with --splitsnapshot, and now with this command are combined again + * into the origin/cow pair. + * + * This is an obscure command that has little to no real uses. + * + * The command has unusual handling of position args. The first position arg + * will become the origin LV, and is not processed by process_each_lv. The + * second position arg will become the cow LV and is processed by + * process_each_lv. + * + * The single function can grab the origin LV from position_argv[0]. + * + * begin with an ordinary LV foo: + * lvcreate -n foo -L 1 vg + * + * create a cow snapshot of foo named foosnap: + * lvcreate -s -L 1 -n foosnap vg/foo + * + * now, foo is an "origin LV" and foosnap is a "cow LV" + * (foosnap matches LV_snapshot aka lv_is_cow) + * + * split the two LVs apart: + * lvconvert --splitsnapshot vg/foosnap + * + * now, foo is *not* an origin LV and foosnap is *not* a cow LV + * (foosnap does not match LV_snapshot) + * + * now, combine the two LVs again: + * lvconvert --snapshot vg/foo vg/foosnap + * + * after this, foosnap will match LV_snapshot again. + * + * FIXME: when splitsnapshot is run, the previous cow LV should be + * flagged in the metadata somehow, and then that flag should be + * required here. As it is now, the first and second args + * (origin and cow) can be swapped and nothing catches it. + */ + +static int _lvconvert_combine_split_snapshot_single(struct cmd_context *cmd, + struct logical_volume *lv, + struct processing_handle *handle) +{ + const char *origin_name = cmd->position_argv[0]; + + /* If origin_name includes VG name, the VG name is removed. */ + if (!validate_lvname_param(cmd, &lv->vg->name, &origin_name)) + return_ECMD_FAILED; + + if (!_lvconvert_snapshot(cmd, lv, origin_name)) + return_ECMD_FAILED; + + return ECMD_PROCESSED; +} + +int lvconvert_combine_split_snapshot_cmd(struct cmd_context *cmd, int argc, char **argv) +{ + return process_each_lv(cmd, 1, cmd->position_argv + 1, NULL, NULL, READ_FOR_UPDATE, + NULL, &lvconvert_generic_check, &_lvconvert_combine_split_snapshot_single); +} |