mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-22 17:35:59 +03:00
1354 lines
34 KiB
C
1354 lines
34 KiB
C
/*
|
|
* Copyright (C) 2003-2004 Sistina Software, Inc. All rights reserved.
|
|
* Copyright (C) 2004-2007 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "lib.h"
|
|
#include "metadata.h"
|
|
#include "toolcontext.h"
|
|
#include "segtype.h"
|
|
#include "display.h"
|
|
#include "archiver.h"
|
|
#include "activate.h"
|
|
#include "lv_alloc.h"
|
|
#include "lvm-string.h"
|
|
#include "str_list.h"
|
|
#include "locking.h" /* FIXME Should not be used in this file */
|
|
|
|
#include "defaults.h" /* FIXME: should this be defaults.h? */
|
|
|
|
/* These are necessary for _write_log_header() */
|
|
#include "xlate.h"
|
|
#define MIRROR_MAGIC 0x4D695272
|
|
#define MIRROR_DISK_VERSION 2
|
|
|
|
/* These are the flags that represent the mirror failure restoration policies */
|
|
#define MIRROR_REMOVE 0
|
|
#define MIRROR_ALLOCATE 1
|
|
#define MIRROR_ALLOCATE_ANYWHERE 2
|
|
|
|
/*
|
|
* Returns true if the lv is temporary mirror layer for resync
|
|
*/
|
|
int is_temporary_mirror_layer(const struct logical_volume *lv)
|
|
{
|
|
if (lv->status & MIRROR_IMAGE
|
|
&& lv->status & MIRRORED
|
|
&& !(lv->status & LOCKED))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Returns the number of mirrors of the LV
|
|
*/
|
|
uint32_t lv_mirror_count(const struct logical_volume *lv)
|
|
{
|
|
struct lv_segment *seg;
|
|
uint32_t s, mirrors;
|
|
|
|
if (!(lv->status & MIRRORED))
|
|
return 1;
|
|
|
|
seg = first_seg(lv);
|
|
mirrors = seg->area_count;
|
|
|
|
for (s = 0; s < seg->area_count; s++) {
|
|
if (seg_type(seg, s) != AREA_LV)
|
|
continue;
|
|
if (is_temporary_mirror_layer(seg_lv(seg, s)))
|
|
mirrors += lv_mirror_count(seg_lv(seg, s)) - 1;
|
|
}
|
|
|
|
return mirrors;
|
|
}
|
|
|
|
struct lv_segment *find_mirror_seg(struct lv_segment *seg)
|
|
{
|
|
return seg->mirror_seg;
|
|
}
|
|
|
|
/*
|
|
* Reduce the region size if necessary to ensure
|
|
* the volume size is a multiple of the region size.
|
|
*/
|
|
uint32_t adjusted_mirror_region_size(uint32_t extent_size, uint32_t extents,
|
|
uint32_t region_size)
|
|
{
|
|
uint64_t region_max;
|
|
|
|
region_max = (1 << (ffs((int)extents) - 1)) * (uint64_t) extent_size;
|
|
|
|
if (region_max < UINT32_MAX && region_size > region_max) {
|
|
region_size = (uint32_t) region_max;
|
|
log_print("Using reduced mirror region size of %" PRIu32
|
|
" sectors", region_size);
|
|
}
|
|
|
|
return region_size;
|
|
}
|
|
|
|
/*
|
|
* Delete independent/orphan LV, it must acquire lock.
|
|
*/
|
|
static int _delete_lv(struct logical_volume *mirror_lv, struct logical_volume *lv)
|
|
{
|
|
struct cmd_context *cmd = mirror_lv->vg->cmd;
|
|
struct str_list *sl;
|
|
|
|
/* Inherit tags - maybe needed for activation */
|
|
if (!str_list_match_list(&mirror_lv->tags, &lv->tags)) {
|
|
list_iterate_items(sl, &mirror_lv->tags)
|
|
if (!str_list_add(cmd->mem, &lv->tags, sl->str)) {
|
|
log_error("Aborting. Unable to tag.");
|
|
return 0;
|
|
}
|
|
|
|
if (!vg_write(mirror_lv->vg) ||
|
|
!vg_commit(mirror_lv->vg)) {
|
|
log_error("Intermediate VG commit for orphan volume failed.");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!activate_lv(cmd, lv))
|
|
return_0;
|
|
|
|
if (!deactivate_lv(cmd, lv))
|
|
return_0;
|
|
|
|
if (!lv_remove(lv))
|
|
return_0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Remove num_removed images from mirrored_seg
|
|
*/
|
|
static int _remove_mirror_images(struct logical_volume *lv,
|
|
uint32_t num_removed,
|
|
struct list *removable_pvs,
|
|
unsigned remove_log, struct list *orphan_lvs)
|
|
{
|
|
uint32_t m;
|
|
uint32_t s, s1;
|
|
struct logical_volume *sub_lv;
|
|
struct logical_volume *log_lv = NULL;
|
|
struct logical_volume *lv1 = NULL;
|
|
struct physical_volume *pv;
|
|
struct lv_segment *seg, *mirrored_seg = first_seg(lv);
|
|
struct lv_segment_area area;
|
|
int all_pvs_removable, pv_found;
|
|
struct pv_list *pvl;
|
|
uint32_t old_area_count = mirrored_seg->area_count;
|
|
uint32_t new_area_count = mirrored_seg->area_count;
|
|
struct lv_list *lvl;
|
|
struct list tmp_orphan_lvs;
|
|
|
|
log_very_verbose("Reducing mirror set from %" PRIu32 " to %"
|
|
PRIu32 " image(s)%s.",
|
|
old_area_count, old_area_count - num_removed,
|
|
remove_log ? " and no log volume" : "");
|
|
|
|
/* Move removable_pvs to end of array */
|
|
if (removable_pvs) {
|
|
for (s = 0; s < mirrored_seg->area_count; s++) {
|
|
all_pvs_removable = 1;
|
|
sub_lv = seg_lv(mirrored_seg, s);
|
|
list_iterate_items(seg, &sub_lv->segments) {
|
|
for (s1 = 0; s1 < seg->area_count; s1++) {
|
|
if (seg_type(seg, s1) != AREA_PV)
|
|
/* FIXME Recurse for AREA_LV */
|
|
continue;
|
|
|
|
pv = seg_pv(seg, s1);
|
|
|
|
pv_found = 0;
|
|
list_iterate_items(pvl, removable_pvs) {
|
|
if (pv->dev->dev == pvl->pv->dev->dev) {
|
|
pv_found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!pv_found) {
|
|
all_pvs_removable = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (!all_pvs_removable)
|
|
break;
|
|
}
|
|
if (all_pvs_removable) {
|
|
/* Swap segment to end */
|
|
new_area_count--;
|
|
area = mirrored_seg->areas[new_area_count];
|
|
mirrored_seg->areas[new_area_count] = mirrored_seg->areas[s];
|
|
mirrored_seg->areas[s] = area;
|
|
}
|
|
/* Found enough matches? */
|
|
if (old_area_count - new_area_count == num_removed)
|
|
break;
|
|
}
|
|
if (old_area_count == new_area_count) {
|
|
log_error("No mirror images found using specified PVs.");
|
|
return 0;
|
|
}
|
|
} else
|
|
new_area_count = old_area_count - num_removed;
|
|
|
|
/* Remove mimage LVs from the segment */
|
|
list_init(&tmp_orphan_lvs);
|
|
for (m = new_area_count; m < mirrored_seg->area_count; m++) {
|
|
seg_lv(mirrored_seg, m)->status &= ~MIRROR_IMAGE;
|
|
seg_lv(mirrored_seg, m)->status |= VISIBLE_LV;
|
|
if (!(lvl = dm_pool_alloc(lv->vg->cmd->mem, sizeof(*lvl)))) {
|
|
log_error("lv_list alloc failed");
|
|
return 0;
|
|
}
|
|
lvl->lv = seg_lv(mirrored_seg, m);
|
|
list_add(&tmp_orphan_lvs, &lvl->list);
|
|
}
|
|
mirrored_seg->area_count = new_area_count;
|
|
|
|
/* Save log_lv as mirrored_seg may not be available after
|
|
* remove_layer_from_lv(), */
|
|
log_lv = mirrored_seg->log_lv;
|
|
|
|
/* If no more mirrors, remove mirror layer */
|
|
if (new_area_count == 1) {
|
|
lv1 = seg_lv(mirrored_seg, 0);
|
|
mirrored_seg->log_lv = NULL;
|
|
if (!remove_layer_from_lv(lv, lv1))
|
|
return_0;
|
|
lv->status &= ~MIRRORED;
|
|
lv->status &= ~MIRROR_NOTSYNCED;
|
|
remove_log = 1;
|
|
}
|
|
|
|
if (remove_log && log_lv) {
|
|
log_lv->status &= ~MIRROR_LOG;
|
|
log_lv->status |= VISIBLE_LV;
|
|
}
|
|
|
|
/*
|
|
* To successfully remove these unwanted LVs we need to
|
|
* remove the LVs from the mirror set, commit that metadata
|
|
* then deactivate and remove them fully.
|
|
*/
|
|
|
|
if (!vg_write(mirrored_seg->lv->vg)) {
|
|
log_error("intermediate VG write failed.");
|
|
return 0;
|
|
}
|
|
|
|
if (!suspend_lv(mirrored_seg->lv->vg->cmd, mirrored_seg->lv)) {
|
|
log_error("Failed to lock %s", mirrored_seg->lv->name);
|
|
vg_revert(mirrored_seg->lv->vg);
|
|
return 0;
|
|
}
|
|
|
|
if (!vg_commit(mirrored_seg->lv->vg)) {
|
|
resume_lv(mirrored_seg->lv->vg->cmd, mirrored_seg->lv);
|
|
return 0;
|
|
}
|
|
|
|
log_very_verbose("Updating \"%s\" in kernel", mirrored_seg->lv->name);
|
|
|
|
if (!resume_lv(mirrored_seg->lv->vg->cmd, mirrored_seg->lv)) {
|
|
log_error("Problem reactivating %s", mirrored_seg->lv->name);
|
|
return 0;
|
|
}
|
|
|
|
/* Save or delete the 'orphan' LVs */
|
|
if (orphan_lvs) {
|
|
*orphan_lvs = tmp_orphan_lvs;
|
|
orphan_lvs->n->p = orphan_lvs;
|
|
orphan_lvs->p->n = orphan_lvs;
|
|
} else {
|
|
list_iterate_items(lvl, &tmp_orphan_lvs)
|
|
if (!_delete_lv(lv, lvl->lv))
|
|
return 0;
|
|
}
|
|
|
|
if (lv1 && !_delete_lv(lv, lv1))
|
|
return 0;
|
|
|
|
if (remove_log && log_lv && !_delete_lv(lv, log_lv))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Remove the number of mirror images from the LV
|
|
*/
|
|
int remove_mirror_images(struct logical_volume *lv, uint32_t num_mirrors,
|
|
struct list *removable_pvs, unsigned remove_log)
|
|
{
|
|
uint32_t num_removed, removed_once;
|
|
uint32_t existing_mirrors = lv_mirror_count(lv);
|
|
|
|
num_removed = existing_mirrors - num_mirrors;
|
|
|
|
while (num_removed) {
|
|
if (num_removed < first_seg(lv)->area_count)
|
|
removed_once = num_removed;
|
|
else
|
|
removed_once = first_seg(lv)->area_count - 1;
|
|
|
|
if (!_remove_mirror_images(lv, removed_once,
|
|
removable_pvs, remove_log, NULL))
|
|
return_0;
|
|
|
|
num_removed -= removed_once;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _mirrored_lv_in_sync(struct logical_volume *lv)
|
|
{
|
|
float sync_percent;
|
|
|
|
if (!lv_mirror_percent(lv->vg->cmd, lv, 0, &sync_percent, NULL)) {
|
|
log_error("Unable to determine mirror sync status of %s/%s.",
|
|
lv->vg->name, lv->name);
|
|
return 0;
|
|
}
|
|
|
|
if (sync_percent >= 100.0)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _merge_mirror_images(struct logical_volume *lv,
|
|
const struct list *mimages)
|
|
{
|
|
uint32_t addition = list_size(mimages);
|
|
struct logical_volume **img_lvs;
|
|
struct lv_list *lvl;
|
|
int i = 0;
|
|
|
|
if (!addition)
|
|
return 1;
|
|
|
|
if (!(img_lvs = alloca(sizeof(*img_lvs) * addition)))
|
|
return_0;
|
|
|
|
list_iterate_items(lvl, mimages)
|
|
img_lvs[i++] = lvl->lv;
|
|
|
|
return lv_add_mirror_lvs(lv, img_lvs, addition,
|
|
MIRROR_IMAGE, first_seg(lv)->region_size);
|
|
}
|
|
|
|
/*
|
|
* Return a temporary LV for resyncing added mirror image.
|
|
* Add other mirror legs to lvs list.
|
|
*/
|
|
static struct logical_volume *_find_tmp_mirror(struct logical_volume *lv)
|
|
{
|
|
struct lv_segment *seg;
|
|
|
|
if (!(lv->status & MIRRORED))
|
|
return NULL;
|
|
|
|
seg = first_seg(lv);
|
|
|
|
/* Temporary mirror is always area_num == 0 */
|
|
if (seg_type(seg, 0) == AREA_LV &&
|
|
is_temporary_mirror_layer(seg_lv(seg, 0)))
|
|
return seg_lv(seg, 0);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Collapsing temporary mirror layers.
|
|
*
|
|
* When mirrors are added to already-mirrored LV, a temporary mirror layer
|
|
* is inserted at the top of the stack to reduce resync work.
|
|
* The function will remove the intermediate layer and collapse the stack
|
|
* as far as mirrors are in-sync.
|
|
*
|
|
* The function is destructive: to remove intermediate mirror layers,
|
|
* VG metadata commits and suspend/resume are necessary.
|
|
*/
|
|
int collapse_mirrored_lv(struct logical_volume *lv)
|
|
{
|
|
struct logical_volume *tmp_lv, *parent_lv;
|
|
struct list lvlist;
|
|
|
|
while ((tmp_lv = _find_tmp_mirror(lv))) {
|
|
parent_lv = find_parent_for_layer(lv, tmp_lv);
|
|
if (!_mirrored_lv_in_sync(parent_lv)) {
|
|
log_verbose("Not collapsing %s: out-of-sync",
|
|
parent_lv->name);
|
|
return 1;
|
|
}
|
|
|
|
list_init(&lvlist);
|
|
if (!_remove_mirror_images(parent_lv,
|
|
first_seg(parent_lv)->area_count - 1,
|
|
NULL, 1, &lvlist)) {
|
|
log_error("Failed to release mirror images");
|
|
return 0;
|
|
}
|
|
|
|
if (!_merge_mirror_images(parent_lv, &lvlist)) {
|
|
log_error("Failed to add mirror images");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int get_mirror_fault_policy(struct cmd_context *cmd __attribute((unused)),
|
|
int log_policy)
|
|
{
|
|
const char *policy;
|
|
|
|
if (log_policy)
|
|
policy = find_config_str(NULL, "activation/mirror_log_fault_policy",
|
|
DEFAULT_MIRROR_LOG_FAULT_POLICY);
|
|
else
|
|
policy = find_config_str(NULL, "activation/mirror_device_fault_policy",
|
|
DEFAULT_MIRROR_DEV_FAULT_POLICY);
|
|
|
|
if (!strcmp(policy, "remove"))
|
|
return MIRROR_REMOVE;
|
|
else if (!strcmp(policy, "allocate"))
|
|
return MIRROR_ALLOCATE;
|
|
else if (!strcmp(policy, "allocate_anywhere"))
|
|
return MIRROR_ALLOCATE_ANYWHERE;
|
|
|
|
if (log_policy)
|
|
log_error("Bad activation/mirror_log_fault_policy");
|
|
else
|
|
log_error("Bad activation/mirror_device_fault_policy");
|
|
|
|
return MIRROR_REMOVE;
|
|
}
|
|
|
|
static int get_mirror_log_fault_policy(struct cmd_context *cmd)
|
|
{
|
|
return get_mirror_fault_policy(cmd, 1);
|
|
}
|
|
|
|
static int get_mirror_device_fault_policy(struct cmd_context *cmd)
|
|
{
|
|
return get_mirror_fault_policy(cmd, 0);
|
|
}
|
|
|
|
/*
|
|
* replace_mirror_images
|
|
* @mirrored_seg: segment (which may be linear now) to restore
|
|
* @num_mirrors: number of copies we should end up with
|
|
* @replace_log: replace log if not present
|
|
* @in_sync: was the original mirror in-sync?
|
|
*
|
|
* in_sync will be set to 0 if new mirror devices are being added
|
|
* In other words, it is only useful if the log (and only the log)
|
|
* is being restored.
|
|
*
|
|
* Returns: 0 on failure, 1 on reconfig, -1 if no reconfig done
|
|
*/
|
|
static int replace_mirror_images(struct lv_segment *mirrored_seg,
|
|
uint32_t num_mirrors,
|
|
int log_policy, int in_sync)
|
|
{
|
|
int r = -1;
|
|
struct logical_volume *lv = mirrored_seg->lv;
|
|
|
|
/* FIXME: Use lvconvert rather than duplicating its code */
|
|
|
|
if (mirrored_seg->area_count < num_mirrors) {
|
|
log_error("WARNING: Failed to replace mirror device in %s/%s",
|
|
mirrored_seg->lv->vg->name, mirrored_seg->lv->name);
|
|
|
|
if ((mirrored_seg->area_count > 1) && !mirrored_seg->log_lv)
|
|
log_error("WARNING: Use 'lvconvert -m %d %s/%s --corelog' to replace failed devices",
|
|
num_mirrors - 1, lv->vg->name, lv->name);
|
|
else
|
|
log_error("WARNING: Use 'lvconvert -m %d %s/%s' to replace failed devices",
|
|
num_mirrors - 1, lv->vg->name, lv->name);
|
|
r = 0;
|
|
|
|
/* REMEMBER/FIXME: set in_sync to 0 if a new mirror device was added */
|
|
in_sync = 0;
|
|
}
|
|
|
|
/*
|
|
* FIXME: right now, we ignore the allocation policy specified to
|
|
* allocate the new log.
|
|
*/
|
|
if ((mirrored_seg->area_count > 1) && !mirrored_seg->log_lv &&
|
|
(log_policy != MIRROR_REMOVE)) {
|
|
log_error("WARNING: Failed to replace mirror log device in %s/%s",
|
|
lv->vg->name, lv->name);
|
|
|
|
log_error("WARNING: Use 'lvconvert -m %d %s/%s' to replace failed devices",
|
|
mirrored_seg->area_count - 1 , lv->vg->name, lv->name);
|
|
r = 0;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
int reconfigure_mirror_images(struct lv_segment *mirrored_seg, uint32_t num_mirrors,
|
|
struct list *removable_pvs, unsigned remove_log)
|
|
{
|
|
int r;
|
|
int in_sync;
|
|
int log_policy, dev_policy;
|
|
uint32_t old_num_mirrors = mirrored_seg->area_count;
|
|
int had_log = (mirrored_seg->log_lv) ? 1 : 0;
|
|
|
|
/* was the mirror in-sync before problems? */
|
|
in_sync = _mirrored_lv_in_sync(mirrored_seg->lv);
|
|
|
|
/*
|
|
* While we are only removing devices, we can have sync set.
|
|
* Setting this is only useful if we are moving to core log
|
|
* otherwise the disk log will contain the sync information
|
|
*/
|
|
init_mirror_in_sync(in_sync);
|
|
|
|
r = remove_mirror_images(mirrored_seg->lv, num_mirrors,
|
|
removable_pvs, remove_log);
|
|
if (!r)
|
|
/* Unable to remove bad devices */
|
|
return 0;
|
|
|
|
log_warn("WARNING: Bad device removed from mirror volume, %s/%s",
|
|
mirrored_seg->lv->vg->name, mirrored_seg->lv->name);
|
|
|
|
log_policy = get_mirror_log_fault_policy(mirrored_seg->lv->vg->cmd);
|
|
dev_policy = get_mirror_device_fault_policy(mirrored_seg->lv->vg->cmd);
|
|
|
|
r = replace_mirror_images(mirrored_seg,
|
|
(dev_policy != MIRROR_REMOVE) ?
|
|
old_num_mirrors : num_mirrors,
|
|
log_policy, in_sync);
|
|
|
|
if (!r)
|
|
/* Failed to replace device(s) */
|
|
log_error("WARNING: Unable to find substitute device for mirror volume, %s/%s",
|
|
mirrored_seg->lv->vg->name, mirrored_seg->lv->name);
|
|
else if (r > 0)
|
|
/* Success in replacing device(s) */
|
|
log_warn("WARNING: Mirror volume, %s/%s restored - substitute for failed device found.",
|
|
mirrored_seg->lv->vg->name, mirrored_seg->lv->name);
|
|
else
|
|
/* Bad device removed, but not replaced because of policy */
|
|
if (mirrored_seg->area_count == 1) {
|
|
log_warn("WARNING: Mirror volume, %s/%s converted to linear due to device failure.",
|
|
mirrored_seg->lv->vg->name, mirrored_seg->lv->name);
|
|
} else if (had_log && !mirrored_seg->log_lv) {
|
|
log_warn("WARNING: Mirror volume, %s/%s disk log removed due to device failure.",
|
|
mirrored_seg->lv->vg->name, mirrored_seg->lv->name);
|
|
}
|
|
/*
|
|
* If we made it here, we at least removed the bad device.
|
|
* Consider this success.
|
|
*/
|
|
return 1;
|
|
}
|
|
|
|
static int _create_mimage_lvs(struct alloc_handle *ah,
|
|
uint32_t num_mirrors,
|
|
struct logical_volume *lv,
|
|
struct logical_volume **img_lvs)
|
|
{
|
|
uint32_t m;
|
|
char *img_name;
|
|
size_t len;
|
|
|
|
len = strlen(lv->name) + 32;
|
|
if (!(img_name = alloca(len))) {
|
|
log_error("img_name allocation failed. "
|
|
"Remove new LV and retry.");
|
|
return 0;
|
|
}
|
|
|
|
if (dm_snprintf(img_name, len, "%s_mimage_%%d", lv->name) < 0) {
|
|
log_error("img_name allocation failed. "
|
|
"Remove new LV and retry.");
|
|
return 0;
|
|
}
|
|
|
|
for (m = 0; m < num_mirrors; m++) {
|
|
if (!(img_lvs[m] = lv_create_empty(img_name,
|
|
NULL, LVM_READ | LVM_WRITE,
|
|
ALLOC_INHERIT, 0, lv->vg))) {
|
|
log_error("Aborting. Failed to create mirror image LV. "
|
|
"Remove new LV and retry.");
|
|
return 0;
|
|
}
|
|
|
|
if (!lv_add_segment(ah, m, 1, img_lvs[m],
|
|
get_segtype_from_string(lv->vg->cmd,
|
|
"striped"),
|
|
0, 0, 0, NULL)) {
|
|
log_error("Aborting. Failed to add mirror image segment "
|
|
"to %s. Remove new LV and retry.",
|
|
img_lvs[m]->name);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Remove mirrors from each segment.
|
|
* 'new_mirrors' is the number of mirrors after the removal. '0' for linear.
|
|
* If 'status_mask' is non-zero, the removal happens only when all segments
|
|
* has the status bits on.
|
|
*/
|
|
int remove_mirrors_from_segments(struct logical_volume *lv,
|
|
uint32_t new_mirrors, uint32_t status_mask)
|
|
{
|
|
struct lv_segment *seg;
|
|
uint32_t s;
|
|
|
|
/* Check the segment params are compatible */
|
|
list_iterate_items(seg, &lv->segments) {
|
|
if (!seg_is_mirrored(seg)) {
|
|
log_error("Segment is not mirrored: %s:%" PRIu32,
|
|
lv->name, seg->le);
|
|
return 0;
|
|
} if ((seg->status & status_mask) != status_mask) {
|
|
log_error("Segment status does not match: %s:%" PRIu32
|
|
" status:0x%x/0x%x", lv->name, seg->le,
|
|
seg->status, status_mask);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Convert the segments */
|
|
list_iterate_items(seg, &lv->segments) {
|
|
if (!new_mirrors && seg->extents_copied == seg->area_len) {
|
|
if (!move_lv_segment_area(seg, 0, seg, 1))
|
|
return_0;
|
|
}
|
|
|
|
for (s = new_mirrors + 1; s < seg->area_count; s++)
|
|
release_lv_segment_area(seg, s, seg->area_len);
|
|
|
|
seg->area_count = new_mirrors + 1;
|
|
|
|
if (!new_mirrors)
|
|
seg->segtype = get_segtype_from_string(lv->vg->cmd,
|
|
"striped");
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
const char *get_pvmove_pvname_from_lv_mirr(struct logical_volume *lv_mirr)
|
|
{
|
|
struct lv_segment *seg;
|
|
|
|
list_iterate_items(seg, &lv_mirr->segments) {
|
|
if (!seg_is_mirrored(seg))
|
|
continue;
|
|
if (seg_type(seg, 0) != AREA_PV)
|
|
continue;
|
|
return dev_name(seg_dev(seg, 0));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const char *get_pvmove_pvname_from_lv(struct logical_volume *lv)
|
|
{
|
|
struct lv_segment *seg;
|
|
uint32_t s;
|
|
|
|
list_iterate_items(seg, &lv->segments) {
|
|
for (s = 0; s < seg->area_count; s++) {
|
|
if (seg_type(seg, s) != AREA_LV)
|
|
continue;
|
|
return get_pvmove_pvname_from_lv_mirr(seg_lv(seg, s));
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct logical_volume *find_pvmove_lv(struct volume_group *vg,
|
|
struct device *dev,
|
|
uint32_t lv_type)
|
|
{
|
|
struct lv_list *lvl;
|
|
struct logical_volume *lv;
|
|
struct lv_segment *seg;
|
|
|
|
/* Loop through all LVs */
|
|
list_iterate_items(lvl, &vg->lvs) {
|
|
lv = lvl->lv;
|
|
|
|
if (!(lv->status & lv_type))
|
|
continue;
|
|
|
|
/* Check segment origins point to pvname */
|
|
list_iterate_items(seg, &lv->segments) {
|
|
if (seg_type(seg, 0) != AREA_PV)
|
|
continue;
|
|
if (seg_dev(seg, 0) != dev)
|
|
continue;
|
|
return lv;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct logical_volume *find_pvmove_lv_from_pvname(struct cmd_context *cmd,
|
|
struct volume_group *vg,
|
|
const char *name,
|
|
uint32_t lv_type)
|
|
{
|
|
struct physical_volume *pv;
|
|
|
|
if (!(pv = find_pv_by_name(cmd, name))) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
return find_pvmove_lv(vg, pv->dev, lv_type);
|
|
}
|
|
|
|
struct list *lvs_using_lv(struct cmd_context *cmd, struct volume_group *vg,
|
|
struct logical_volume *lv)
|
|
{
|
|
struct list *lvs;
|
|
struct logical_volume *lv1;
|
|
struct lv_list *lvl, *lvl1;
|
|
struct lv_segment *seg;
|
|
uint32_t s;
|
|
|
|
if (!(lvs = dm_pool_alloc(cmd->mem, sizeof(*lvs)))) {
|
|
log_error("lvs list alloc failed");
|
|
return NULL;
|
|
}
|
|
|
|
list_init(lvs);
|
|
|
|
/* Loop through all LVs except the one supplied */
|
|
list_iterate_items(lvl1, &vg->lvs) {
|
|
lv1 = lvl1->lv;
|
|
if (lv1 == lv)
|
|
continue;
|
|
|
|
/* Find whether any segment points at the supplied LV */
|
|
list_iterate_items(seg, &lv1->segments) {
|
|
for (s = 0; s < seg->area_count; s++) {
|
|
if (seg_type(seg, s) != AREA_LV ||
|
|
seg_lv(seg, s) != lv)
|
|
continue;
|
|
if (!(lvl = dm_pool_alloc(cmd->mem, sizeof(*lvl)))) {
|
|
log_error("lv_list alloc failed");
|
|
return NULL;
|
|
}
|
|
lvl->lv = lv1;
|
|
list_add(lvs, &lvl->list);
|
|
goto next_lv;
|
|
}
|
|
}
|
|
next_lv:
|
|
;
|
|
}
|
|
|
|
return lvs;
|
|
}
|
|
|
|
float copy_percent(struct logical_volume *lv_mirr)
|
|
{
|
|
uint32_t numerator = 0u, denominator = 0u;
|
|
struct lv_segment *seg;
|
|
|
|
list_iterate_items(seg, &lv_mirr->segments) {
|
|
denominator += seg->area_len;
|
|
|
|
if (seg_is_mirrored(seg))
|
|
numerator += seg->extents_copied;
|
|
else
|
|
numerator += seg->area_len;
|
|
}
|
|
|
|
return denominator ? (float) numerator *100 / denominator : 100.0;
|
|
}
|
|
|
|
/*
|
|
* Fixup mirror pointers after single-pass segment import
|
|
*/
|
|
int fixup_imported_mirrors(struct volume_group *vg)
|
|
{
|
|
struct lv_list *lvl;
|
|
struct lv_segment *seg;
|
|
uint32_t s;
|
|
|
|
list_iterate_items(lvl, &vg->lvs) {
|
|
list_iterate_items(seg, &lvl->lv->segments) {
|
|
if (seg->segtype !=
|
|
get_segtype_from_string(vg->cmd, "mirror"))
|
|
continue;
|
|
|
|
if (seg->log_lv)
|
|
first_seg(seg->log_lv)->mirror_seg = seg;
|
|
for (s = 0; s < seg->area_count; s++)
|
|
if (seg_type(seg, s) == AREA_LV)
|
|
first_seg(seg_lv(seg, s))->mirror_seg
|
|
= seg;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Add mirrors to "linear" or "mirror" segments
|
|
*/
|
|
int add_mirrors_to_segments(struct cmd_context *cmd, struct logical_volume *lv,
|
|
uint32_t mirrors, uint32_t region_size,
|
|
struct list *allocatable_pvs, alloc_policy_t alloc)
|
|
{
|
|
struct alloc_handle *ah;
|
|
const struct segment_type *segtype;
|
|
struct list *parallel_areas;
|
|
uint32_t adjusted_region_size;
|
|
|
|
if (!(parallel_areas = build_parallel_areas_from_lv(cmd, lv)))
|
|
return_0;
|
|
|
|
if (!(segtype = get_segtype_from_string(cmd, "mirror")))
|
|
return_0;
|
|
|
|
adjusted_region_size = adjusted_mirror_region_size(lv->vg->extent_size,
|
|
lv->le_count,
|
|
region_size);
|
|
|
|
if (!(ah = allocate_extents(lv->vg, NULL, segtype, 1, mirrors, 0,
|
|
lv->le_count, allocatable_pvs, alloc,
|
|
parallel_areas))) {
|
|
log_error("Unable to allocate mirror extents for %s.", lv->name);
|
|
return 0;
|
|
}
|
|
|
|
if (!lv_add_mirror_areas(ah, lv, 0, adjusted_region_size)) {
|
|
log_error("Failed to add mirror areas to %s", lv->name);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Convert mirror log
|
|
*
|
|
* FIXME: Can't handle segment-by-segment mirror (like pvmove)
|
|
*/
|
|
int remove_mirror_log(struct cmd_context *cmd,
|
|
struct logical_volume *lv,
|
|
struct list *removable_pvs)
|
|
{
|
|
float sync_percent;
|
|
|
|
/* Unimplemented features */
|
|
if (list_size(&lv->segments) != 1) {
|
|
log_error("Multiple-segment mirror is not supported");
|
|
return 0;
|
|
}
|
|
|
|
/* Had disk log, switch to core. */
|
|
if (!lv_mirror_percent(cmd, lv, 0, &sync_percent, NULL)) {
|
|
log_error("Unable to determine mirror sync status.");
|
|
return 0;
|
|
}
|
|
|
|
if (sync_percent >= 100.0)
|
|
init_mirror_in_sync(1);
|
|
else {
|
|
/* A full resync will take place */
|
|
lv->status &= ~MIRROR_NOTSYNCED;
|
|
init_mirror_in_sync(0);
|
|
}
|
|
|
|
if (!remove_mirror_images(lv, lv_mirror_count(lv),
|
|
removable_pvs, 1U))
|
|
return_0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* This function writes a new header to the mirror log header to the lv
|
|
*
|
|
* Returns: 1 on success, 0 on failure
|
|
*/
|
|
static int _write_log_header(struct cmd_context *cmd, struct logical_volume *lv)
|
|
{
|
|
struct device *dev;
|
|
char *name;
|
|
struct { /* The mirror log header */
|
|
uint32_t magic;
|
|
uint32_t version;
|
|
uint64_t nr_regions;
|
|
} log_header;
|
|
|
|
log_header.magic = xlate32(MIRROR_MAGIC);
|
|
log_header.version = xlate32(MIRROR_DISK_VERSION);
|
|
log_header.nr_regions = xlate64((uint64_t)-1);
|
|
|
|
if (!(name = dm_pool_alloc(cmd->mem, PATH_MAX))) {
|
|
log_error("Name allocation failed - log header not written (%s)",
|
|
lv->name);
|
|
return 0;
|
|
}
|
|
|
|
if (dm_snprintf(name, PATH_MAX, "%s%s/%s", cmd->dev_dir,
|
|
lv->vg->name, lv->name) < 0) {
|
|
log_error("Name too long - log header not written (%s)", lv->name);
|
|
return 0;
|
|
}
|
|
|
|
log_verbose("Writing log header to device, %s", lv->name);
|
|
|
|
if (!(dev = dev_cache_get(name, NULL))) {
|
|
log_error("%s: not found: log header not written", name);
|
|
return 0;
|
|
}
|
|
|
|
if (!dev_open_quiet(dev))
|
|
return 0;
|
|
|
|
if (!dev_write(dev, UINT64_C(0), sizeof(log_header), &log_header)) {
|
|
log_error("Failed to write log header to %s", name);
|
|
dev_close_immediate(dev);
|
|
return 0;
|
|
}
|
|
|
|
dev_close_immediate(dev);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Initialize mirror log contents
|
|
*/
|
|
static int _init_mirror_log(struct cmd_context *cmd,
|
|
struct logical_volume *log_lv, int in_sync,
|
|
struct list *tags)
|
|
{
|
|
struct str_list *sl;
|
|
|
|
if (!activation() && in_sync) {
|
|
log_error("Aborting. Unable to create in-sync mirror log "
|
|
"while activation is disabled.");
|
|
return 0;
|
|
}
|
|
|
|
/* Temporary tag mirror log for activation */
|
|
list_iterate_items(sl, tags)
|
|
if (!str_list_add(cmd->mem, &log_lv->tags, sl->str)) {
|
|
log_error("Aborting. Unable to tag mirror log.");
|
|
return 0;
|
|
}
|
|
|
|
/* store mirror log on disk(s) */
|
|
if (!vg_write(log_lv->vg))
|
|
return_0;
|
|
|
|
backup(log_lv->vg);
|
|
|
|
if (!vg_commit(log_lv->vg))
|
|
return_0;
|
|
|
|
if (!activate_lv(cmd, log_lv)) {
|
|
log_error("Aborting. Failed to activate mirror log.");
|
|
goto revert_new_lv;
|
|
}
|
|
|
|
/* Remove the temporary tags */
|
|
list_iterate_items(sl, tags)
|
|
if (!str_list_del(&log_lv->tags, sl->str))
|
|
log_error("Failed to remove tag %s from mirror log.",
|
|
sl->str);
|
|
|
|
if (activation() && !set_lv(cmd, log_lv, log_lv->size,
|
|
in_sync ? -1 : 0)) {
|
|
log_error("Aborting. Failed to wipe mirror log.");
|
|
goto deactivate_and_revert_new_lv;
|
|
}
|
|
|
|
if (activation() && !_write_log_header(cmd, log_lv)) {
|
|
log_error("Aborting. Failed to write mirror log header.");
|
|
goto deactivate_and_revert_new_lv;
|
|
}
|
|
|
|
if (!deactivate_lv(cmd, log_lv)) {
|
|
log_error("Aborting. Failed to deactivate mirror log. "
|
|
"Manual intervention required.");
|
|
return 0;
|
|
}
|
|
|
|
log_lv->status &= ~VISIBLE_LV;
|
|
|
|
return 1;
|
|
|
|
deactivate_and_revert_new_lv:
|
|
if (!deactivate_lv(cmd, log_lv)) {
|
|
log_error("Unable to deactivate mirror log LV. "
|
|
"Manual intervention required.");
|
|
return 0;
|
|
}
|
|
|
|
revert_new_lv:
|
|
if (!lv_remove(log_lv) || !vg_write(log_lv->vg) ||
|
|
(backup(log_lv->vg), !vg_commit(log_lv->vg)))
|
|
log_error("Manual intervention may be required to remove "
|
|
"abandoned log LV before retrying.");
|
|
return 0;
|
|
}
|
|
|
|
static struct logical_volume *_create_mirror_log(struct logical_volume *lv,
|
|
struct alloc_handle *ah,
|
|
alloc_policy_t alloc,
|
|
const char *lv_name,
|
|
const char *suffix)
|
|
{
|
|
struct logical_volume *log_lv;
|
|
char *log_name;
|
|
size_t len;
|
|
|
|
len = strlen(lv_name) + 32;
|
|
if (!(log_name = alloca(len))) {
|
|
log_error("log_name allocation failed.");
|
|
return NULL;
|
|
}
|
|
|
|
if (dm_snprintf(log_name, len, "%s%s", lv->name, suffix) < 0) {
|
|
log_error("log_name allocation failed.");
|
|
return NULL;
|
|
}
|
|
|
|
if (!(log_lv = lv_create_empty(log_name, NULL,
|
|
VISIBLE_LV | LVM_READ | LVM_WRITE,
|
|
alloc, 0, lv->vg)))
|
|
return_NULL;
|
|
|
|
if (!lv_add_log_segment(ah, log_lv))
|
|
return_NULL;
|
|
|
|
return log_lv;
|
|
}
|
|
|
|
static struct logical_volume *_set_up_mirror_log(struct cmd_context *cmd,
|
|
struct alloc_handle *ah,
|
|
struct logical_volume *lv,
|
|
uint32_t log_count,
|
|
uint32_t region_size __attribute((unused)),
|
|
alloc_policy_t alloc,
|
|
int in_sync)
|
|
{
|
|
struct logical_volume *log_lv;
|
|
const char *suffix;
|
|
struct lv_segment *seg;
|
|
|
|
init_mirror_in_sync(in_sync);
|
|
|
|
if (log_count != 1) {
|
|
log_error("log_count != 1 is not supported.");
|
|
return NULL;
|
|
}
|
|
|
|
/* Check if the log is for temporary sync layer. */
|
|
seg = first_seg(lv);
|
|
if (seg_type(seg, 0) == AREA_LV &&
|
|
strstr(seg_lv(seg, 0)->name, MIRROR_SYNC_LAYER))
|
|
suffix = "_mlogtmp_%d";
|
|
else
|
|
suffix = "_mlog";
|
|
|
|
if (!(log_lv = _create_mirror_log(lv, ah, alloc, lv->name, suffix))) {
|
|
log_error("Failed to create mirror log.");
|
|
return NULL;
|
|
}
|
|
|
|
if (!_init_mirror_log(cmd, log_lv, in_sync, &lv->tags)) {
|
|
log_error("Failed to create mirror log.");
|
|
return NULL;
|
|
}
|
|
|
|
return log_lv;
|
|
}
|
|
|
|
static void _add_mirror_log(struct logical_volume *lv,
|
|
struct logical_volume *log_lv)
|
|
{
|
|
first_seg(lv)->log_lv = log_lv;
|
|
log_lv->status |= MIRROR_LOG;
|
|
first_seg(log_lv)->mirror_seg = first_seg(lv);
|
|
}
|
|
|
|
int add_mirror_log(struct cmd_context *cmd,
|
|
struct logical_volume *lv,
|
|
uint32_t log_count,
|
|
uint32_t region_size,
|
|
struct list *allocatable_pvs,
|
|
alloc_policy_t alloc)
|
|
{
|
|
struct alloc_handle *ah;
|
|
const struct segment_type *segtype;
|
|
struct list *parallel_areas;
|
|
float sync_percent;
|
|
int in_sync;
|
|
struct logical_volume *log_lv;
|
|
|
|
/* Unimplemented features */
|
|
if (log_count > 1) {
|
|
log_error("log_count > 1 is not supported");
|
|
return 0;
|
|
}
|
|
if (list_size(&lv->segments) != 1) {
|
|
log_error("Multiple-segment mirror is not supported");
|
|
return 0;
|
|
}
|
|
|
|
if (!(parallel_areas = build_parallel_areas_from_lv(cmd, lv)))
|
|
return_0;
|
|
|
|
if (!(segtype = get_segtype_from_string(cmd, "mirror")))
|
|
return_0;
|
|
|
|
if (activation() && segtype->ops->target_present &&
|
|
!segtype->ops->target_present(NULL)) {
|
|
log_error("%s: Required device-mapper target(s) not "
|
|
"detected in your kernel", segtype->name);
|
|
return 0;
|
|
}
|
|
|
|
/* allocate destination extents */
|
|
ah = allocate_extents(lv->vg, NULL, segtype,
|
|
0, 0, log_count, 0,
|
|
allocatable_pvs, alloc, parallel_areas);
|
|
if (!ah) {
|
|
log_error("Unable to allocate temporary LV for pvmove.");
|
|
return 0;
|
|
}
|
|
|
|
/* check sync status */
|
|
if (lv_mirror_percent(cmd, lv, 0, &sync_percent, NULL) &&
|
|
sync_percent >= 100.0)
|
|
in_sync = 1;
|
|
else
|
|
in_sync = 0;
|
|
|
|
if (!(log_lv = _set_up_mirror_log(cmd, ah, lv, log_count,
|
|
region_size, alloc, in_sync)))
|
|
return_0;
|
|
|
|
_add_mirror_log(lv, log_lv);
|
|
|
|
alloc_destroy(ah);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Convert "linear" LV to "mirror".
|
|
*/
|
|
int add_mirror_images(struct cmd_context *cmd, struct logical_volume *lv,
|
|
uint32_t mirrors, uint32_t stripes, uint32_t region_size,
|
|
struct list *allocatable_pvs, alloc_policy_t alloc,
|
|
uint32_t log_count)
|
|
{
|
|
struct alloc_handle *ah;
|
|
const struct segment_type *segtype;
|
|
struct list *parallel_areas;
|
|
struct logical_volume **img_lvs, *log_lv;
|
|
|
|
if (stripes > 1) {
|
|
log_error("stripes > 1 is not supported");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* allocate destination extents
|
|
*/
|
|
|
|
if (!(parallel_areas = build_parallel_areas_from_lv(cmd, lv)))
|
|
return_0;
|
|
|
|
if (!(segtype = get_segtype_from_string(cmd, "mirror")))
|
|
return_0;
|
|
|
|
ah = allocate_extents(lv->vg, NULL, segtype,
|
|
stripes, mirrors, log_count, lv->le_count,
|
|
allocatable_pvs, alloc, parallel_areas);
|
|
if (!ah) {
|
|
log_error("Unable to allocate extents for mirror(s).");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* create and initialize mirror log
|
|
*/
|
|
if (log_count &&
|
|
!(log_lv = _set_up_mirror_log(cmd, ah, lv, log_count,
|
|
region_size, alloc, 0)))
|
|
return_0;
|
|
|
|
/* The log initialization involves vg metadata commit.
|
|
So from here on, if failure occurs, the log must be explicitly
|
|
removed and the updated vg metadata should be committed. */
|
|
|
|
/*
|
|
* insert a mirror layer
|
|
*/
|
|
if (list_size(&lv->segments) != 1 ||
|
|
seg_type(first_seg(lv), 0) != AREA_LV)
|
|
if (!insert_layer_for_lv(cmd, lv, 0, "_mimage_%d"))
|
|
goto out_remove_log;
|
|
|
|
/*
|
|
* create mirror image LVs
|
|
*/
|
|
if (!(img_lvs = alloca(sizeof(*img_lvs) * mirrors))) {
|
|
log_error("img_lvs allocation failed. "
|
|
"Remove new LV and retry.");
|
|
goto out_remove_log;
|
|
}
|
|
|
|
if (!_create_mimage_lvs(ah, mirrors, lv, img_lvs))
|
|
goto out_remove_log;
|
|
|
|
if (!lv_add_mirror_lvs(lv, img_lvs, mirrors,
|
|
MIRROR_IMAGE | (lv->status & LOCKED),
|
|
region_size)) {
|
|
log_error("Aborting. Failed to add mirror segment. "
|
|
"Remove new LV and retry.");
|
|
goto out_remove_imgs;
|
|
}
|
|
|
|
if (log_count)
|
|
_add_mirror_log(lv, log_lv);
|
|
|
|
alloc_destroy(ah);
|
|
return 1;
|
|
|
|
out_remove_log:
|
|
if (!lv_remove(log_lv) || !vg_write(log_lv->vg) ||
|
|
(backup(log_lv->vg), !vg_commit(log_lv->vg)))
|
|
log_error("Manual intervention may be required to remove "
|
|
"abandoned log LV before retrying.");
|
|
|
|
out_remove_imgs:
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Generic interface for adding mirror and/or mirror log.
|
|
* 'mirror' is the number of mirrors to be added.
|
|
* 'pvs' is either allocatable pvs.
|
|
*/
|
|
int lv_add_mirrors(struct cmd_context *cmd, struct logical_volume *lv,
|
|
uint32_t mirrors, uint32_t stripes,
|
|
uint32_t region_size, uint32_t log_count,
|
|
struct list *pvs, alloc_policy_t alloc, uint32_t flags)
|
|
{
|
|
if (!mirrors && !log_count) {
|
|
log_error("No conversion is requested");
|
|
return 0;
|
|
}
|
|
|
|
if (flags & MIRROR_BY_SEG) {
|
|
if (log_count) {
|
|
log_error("Persistent log is not supported on "
|
|
"segment-by-segment mirroring");
|
|
return 0;
|
|
}
|
|
if (stripes > 1) {
|
|
log_error("Striped-mirroring is not supported on "
|
|
"segment-by-segment mirroring");
|
|
return 0;
|
|
}
|
|
|
|
return add_mirrors_to_segments(cmd, lv, mirrors,
|
|
region_size, pvs, alloc);
|
|
} else if (flags & MIRROR_BY_LV) {
|
|
if (!mirrors)
|
|
return add_mirror_log(cmd, lv, log_count,
|
|
region_size, pvs, alloc);
|
|
return add_mirror_images(cmd, lv, mirrors,
|
|
stripes, region_size,
|
|
pvs, alloc, log_count);
|
|
}
|
|
|
|
log_error("Unsupported mirror conversion type");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Generic interface for removing mirror and/or mirror log.
|
|
* 'mirror' is the number of mirrors to be removed.
|
|
* 'pvs' is removable pvs.
|
|
*/
|
|
int lv_remove_mirrors(struct cmd_context *cmd __attribute((unused)),
|
|
struct logical_volume *lv,
|
|
uint32_t mirrors, uint32_t log_count, struct list *pvs,
|
|
uint32_t status_mask)
|
|
{
|
|
uint32_t new_mirrors;
|
|
struct lv_segment *seg;
|
|
|
|
if (!mirrors && !log_count) {
|
|
log_error("No conversion is requested");
|
|
return 0;
|
|
}
|
|
|
|
seg = first_seg(lv);
|
|
if (!seg_is_mirrored(seg)) {
|
|
log_error("Not a mirror segment");
|
|
return 0;
|
|
}
|
|
|
|
if (lv_mirror_count(lv) <= mirrors) {
|
|
log_error("Removing more than existing: %d <= %d",
|
|
seg->area_count, mirrors);
|
|
return 0;
|
|
}
|
|
new_mirrors = lv_mirror_count(lv) - mirrors - 1;
|
|
|
|
/* MIRROR_BY_LV */
|
|
if (seg_type(seg, 0) == AREA_LV &&
|
|
seg_lv(seg, 0)->status & MIRROR_IMAGE) {
|
|
return remove_mirror_images(lv, new_mirrors + 1,
|
|
pvs, log_count ? 1U : 0);
|
|
}
|
|
|
|
/* MIRROR_BY_SEG */
|
|
if (log_count) {
|
|
log_error("Persistent log is not supported on "
|
|
"segment-by-segment mirroring");
|
|
return 0;
|
|
}
|
|
return remove_mirrors_from_segments(lv, new_mirrors, status_mask);
|
|
}
|
|
|