diff options
author | Alasdair Kergon <agk@redhat.com> | 2008-09-19 06:42:00 +0000 |
---|---|---|
committer | Alasdair Kergon <agk@redhat.com> | 2008-09-19 06:42:00 +0000 |
commit | 8c5bcdabab5b40498f2051715639cf07f14d877b (patch) | |
tree | 4cadcdff3b23747c3ea869f3851bedf4d95dc3d4 | |
parent | 86fb36e2b08f667bc06ac7d0b160a0247bb382a0 (diff) | |
download | lvm2-8c5bcdabab5b40498f2051715639cf07f14d877b.tar.gz lvm2-8c5bcdabab5b40498f2051715639cf07f14d877b.tar.xz lvm2-8c5bcdabab5b40498f2051715639cf07f14d877b.zip |
Improve the way VGs with PVs missing are handled so manual intervention
is required in fewer circumstances. (mornfall)
31 files changed, 430 insertions, 180 deletions
@@ -1,5 +1,11 @@ Version 2.02.40 - ================================ + In VG with PVs missing, by default allow activation of LVs that are complete. + Track PARTIAL_LV and MISSING_PV flags internally. + Require --force with --removemissing in vgreduce to remove partial LVs. + No longer write out PARTIAL flag into metadata backups. + Treat new default activation/missing_stripe_filler "error" as an error target. + Remove internal partial_mode. Add device/md_chunk_alignment to lvm.conf. Pass struct physical_volume to pe_align and adjust for md chunk size. Store sysfs location in struct cmd_context. diff --git a/daemons/clvmd/lvm-functions.c b/daemons/clvmd/lvm-functions.c index 977cc428..c312949a 100644 --- a/daemons/clvmd/lvm-functions.c +++ b/daemons/clvmd/lvm-functions.c @@ -413,9 +413,6 @@ int do_lock_lv(unsigned char command, unsigned char lock_flags, char *resource) } } - if (lock_flags & LCK_PARTIAL_MODE) - init_partial(1); - if (lock_flags & LCK_MIRROR_NOSYNC_MODE) init_mirror_in_sync(1); @@ -454,9 +451,6 @@ int do_lock_lv(unsigned char command, unsigned char lock_flags, char *resource) break; } - if (lock_flags & LCK_PARTIAL_MODE) - init_partial(0); - if (lock_flags & LCK_MIRROR_NOSYNC_MODE) init_mirror_in_sync(0); diff --git a/doc/example.conf b/doc/example.conf index ca85d32a..16cefb2f 100644 --- a/doc/example.conf +++ b/doc/example.conf @@ -271,11 +271,13 @@ global { } activation { - # Device used in place of missing stripes if activating incomplete volume. - # For now, you need to set this up yourself first (e.g. with 'dmsetup') - # For example, you could make it return I/O errors using the 'error' - # target or make it return zeros. - missing_stripe_filler = "/dev/ioerror" + # How to fill in missing stripes if activating an incomplete volume. + # Using "error" will make inaccessible parts of the device return + # I/O errors on access. You can instead use a device path, in which + # case, that device will be used to in place of missing stripes. + # But note that using anything other than "error" with mirrored + # or snapshotted volumes is likely to result in data corruption. + missing_stripe_filler = "error" # How much stack (in KB) to reserve for use while devices suspended reserved_stack = 256 diff --git a/lib/activate/activate.c b/lib/activate/activate.c index fa1aafd5..06954d16 100644 --- a/lib/activate/activate.c +++ b/lib/activate/activate.c @@ -1027,6 +1027,12 @@ static int _lv_activate(struct cmd_context *cmd, const char *lvid_s, return 0; } + if ((!lv->vg->cmd->partial_activate) && (lv->status & PARTIAL_LV)) { + log_error("Refusing activation of partial LV %s. Use --partial to override.", + lv->name); + return_0; + } + if (test_mode()) { _skip("Activating '%s'.", lv->name); return 1; diff --git a/lib/activate/dev_manager.c b/lib/activate/dev_manager.c index a081dad7..41731922 100644 --- a/lib/activate/dev_manager.c +++ b/lib/activate/dev_manager.c @@ -47,7 +47,6 @@ struct dev_manager { struct cmd_context *cmd; - const char *stripe_filler; void *target_state; uint32_t pvmove_mirror_count; @@ -59,8 +58,6 @@ struct lv_layer { const char *old_name; }; -static const char *stripe_filler = NULL; - static char *_build_dlid(struct dm_pool *mem, const char *lvid, const char *layer) { char *dlid; @@ -443,13 +440,6 @@ struct dev_manager *dev_manager_create(struct cmd_context *cmd, dm->cmd = cmd; dm->mem = mem; - if (!stripe_filler) { - stripe_filler = find_config_tree_str(cmd, - "activation/missing_stripe_filler", - DEFAULT_STRIPE_FILLER); - } - dm->stripe_filler = stripe_filler; - if (!(dm->vg_name = dm_pool_strdup(dm->mem, vg_name))) goto_bad; @@ -699,6 +689,68 @@ bad: return NULL; } +static char *_add_error_device(struct dev_manager *dm, struct dm_tree *dtree, + struct lv_segment *seg, int s) +{ + char *id, *name; + char errid[32]; + struct dm_tree_node *node; + struct lv_segment *seg_i; + int segno = -1, i = 0;; + uint64_t size = seg->len * seg->lv->vg->extent_size; + + list_iterate_items(seg_i, &seg->lv->segments) { + if (seg == seg_i) + segno = i; + ++i; + } + + if (segno < 0) { + log_error("_add_error_device called with bad segment"); + return_NULL; + } + + sprintf(errid, "missing_%d_%d", segno, s); + + if (!(id = build_dlid(dm, seg->lv->lvid.s, errid))) + return_NULL; + + if (!(name = build_dm_name(dm->mem, seg->lv->vg->name, + seg->lv->name, errid))) + return_NULL; + if (!(node = dm_tree_add_new_dev(dtree, name, id, 0, 0, 0, 0, 0))) + return_NULL; + if (!dm_tree_node_add_error_target(node, size)) + return_NULL; + + return id; +} + +static int _add_error_area(struct dev_manager *dm, struct dm_tree_node *node, + struct lv_segment *seg, int s) +{ + char *dlid; + uint64_t extent_size = seg->lv->vg->extent_size; + + if (!strcmp(dm->cmd->stripe_filler, "error")) { + /* + * FIXME, the tree pointer is first field of dm_tree_node, but + * we don't have the struct definition available. + */ + struct dm_tree **tree = (struct dm_tree **) node; + dlid = _add_error_device(dm, *tree, seg, s); + if (!dlid) + return_0; + dm_tree_node_add_target_area(node, NULL, dlid, + extent_size * seg_le(seg, s)); + } else { + dm_tree_node_add_target_area(node, + dm->cmd->stripe_filler, + NULL, UINT64_C(0)); + } + return 1; +} + int add_areas_line(struct dev_manager *dm, struct lv_segment *seg, struct dm_tree_node *node, uint32_t start_area, uint32_t areas) @@ -712,11 +764,10 @@ int add_areas_line(struct dev_manager *dm, struct lv_segment *seg, (!seg_pvseg(seg, s) || !seg_pv(seg, s) || !seg_dev(seg, s))) || - (seg_type(seg, s) == AREA_LV && !seg_lv(seg, s))) - dm_tree_node_add_target_area(node, - dm->stripe_filler, - NULL, UINT64_C(0)); - else if (seg_type(seg, s) == AREA_PV) + (seg_type(seg, s) == AREA_LV && !seg_lv(seg, s))) { + if (!_add_error_area(dm, node, seg, s)) + return_0; + } else if (seg_type(seg, s) == AREA_PV) dm_tree_node_add_target_area(node, dev_name(seg_dev(seg, s)), NULL, diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c index cbf63df4..8f89fee3 100644 --- a/lib/commands/toolcontext.c +++ b/lib/commands/toolcontext.c @@ -197,6 +197,7 @@ static int _process_config(struct cmd_context *cmd) { mode_t old_umask; const char *read_ahead; + struct stat st; /* umask */ cmd->default_settings.umask = find_config_tree_int(cmd, @@ -263,6 +264,24 @@ static int _process_config(struct cmd_context *cmd) return 0; } + cmd->stripe_filler = find_config_tree_str(cmd, + "activation/missing_stripe_filler", + DEFAULT_STRIPE_FILLER); + if (strcmp(cmd->stripe_filler, "error")) { + if (stat(cmd->stripe_filler, &st)) { + log_warn("WARNING: activation/missing_stripe_filler = \"%s\" " + "is invalid,", cmd->stripe_filler); + log_warn(" stat failed: %s", strerror(errno)); + log_warn("Falling back to \"error\" missing_stripe_filler."); + cmd->stripe_filler = "error"; + } else if (!S_ISBLK(st.st_mode)) { + log_warn("WARNING: activation/missing_stripe_filler = \"%s\" " + "is not a block device.", cmd->stripe_filler); + log_warn("Falling back to \"error\" missing_stripe_filler."); + cmd->stripe_filler = "error"; + } + } + return 1; } @@ -981,6 +1000,7 @@ struct cmd_context *create_toolcontext(struct arg *the_args, unsigned is_static, cmd->args = the_args; cmd->is_static = is_static; cmd->is_long_lived = is_long_lived; + cmd->handles_missing_pvs = 0; cmd->hosttags = 0; list_init(&cmd->formats); list_init(&cmd->segtypes); diff --git a/lib/commands/toolcontext.h b/lib/commands/toolcontext.h index 0d9d844f..8f6cfc7a 100644 --- a/lib/commands/toolcontext.h +++ b/lib/commands/toolcontext.h @@ -66,8 +66,10 @@ struct cmd_context { struct command *command; struct arg *args; char **argv; - unsigned is_static; /* Static binary? */ - unsigned is_long_lived; /* Optimises persistent_filter handling */ + unsigned is_static:1; /* Static binary? */ + unsigned is_long_lived:1; /* Optimises persistent_filter handling */ + unsigned handles_missing_pvs:1; + unsigned partial_activate:1; struct dev_filter *filter; int dump_filter; /* Dump filter when exiting? */ @@ -81,6 +83,7 @@ struct cmd_context { struct archive_params *archive_params; struct backup_params *backup_params; + const char *stripe_filler; /* List of defined tags */ struct list tags; diff --git a/lib/config/defaults.h b/lib/config/defaults.h index 9566c6ea..605bc497 100644 --- a/lib/config/defaults.h +++ b/lib/config/defaults.h @@ -92,7 +92,7 @@ # define DEFAULT_ACTIVATION 0 #endif -#define DEFAULT_STRIPE_FILLER "/dev/ioerror" +#define DEFAULT_STRIPE_FILLER "error" #define DEFAULT_MIRROR_REGION_SIZE 512 /* KB */ #define DEFAULT_INTERVAL 15 diff --git a/lib/display/display.c b/lib/display/display.c index 6f8bdebd..d9358fb8 100644 --- a/lib/display/display.c +++ b/lib/display/display.c @@ -578,10 +578,7 @@ void vgdisplay_full(const struct volume_group *vg) struct lv_list *lvl; char uuid[64] __attribute((aligned(8))); - if (vg->status & PARTIAL_VG) - active_pvs = list_size(&vg->pvs); - else - active_pvs = vg->pv_count; + active_pvs = vg->pv_count - vg_missing_pv_count(vg); log_print("--- Volume group ---"); log_print("VG Name %s", vg->name); @@ -664,10 +661,7 @@ void vgdisplay_colons(const struct volume_group *vg) const char *access; char uuid[64] __attribute((aligned(8))); - if (vg->status & PARTIAL_VG) - active_pvs = list_size(&vg->pvs); - else - active_pvs = vg->pv_count; + active_pvs = vg->pv_count - vg_missing_pv_count(vg); list_iterate_items(lvl, &vg->lvs) if (lv_is_visible(lvl->lv) && !(lvl->lv->status & SNAPSHOT)) diff --git a/lib/format1/disk-rep.h b/lib/format1/disk-rep.h index 9a04c7b8..0ef57190 100644 --- a/lib/format1/disk-rep.h +++ b/lib/format1/disk-rep.h @@ -212,7 +212,7 @@ int export_pv(struct cmd_context *cmd, struct dm_pool *mem, struct pv_disk *pvd, struct physical_volume *pv); int import_vg(struct dm_pool *mem, - struct volume_group *vg, struct disk_list *dl, int partial); + struct volume_group *vg, struct disk_list *dl); int export_vg(struct vg_disk *vgd, struct volume_group *vg); int import_lv(struct dm_pool *mem, struct logical_volume *lv, struct lv_disk *lvd); diff --git a/lib/format1/format1.c b/lib/format1/format1.c index b38cf6f3..34207914 100644 --- a/lib/format1/format1.c +++ b/lib/format1/format1.c @@ -23,7 +23,7 @@ #include "segtype.h" /* VG consistency checks */ -static int _check_vgs(struct list *pvs, int *partial) +static int _check_vgs(struct list *pvs) { struct list *pvh, *t; struct disk_list *dl = NULL; @@ -33,8 +33,6 @@ static int _check_vgs(struct list *pvs, int *partial) uint32_t exported = 0; int first_time = 1; - *partial = 0; - /* * If there are exported and unexported PVs, ignore exported ones. * This means an active VG won't be affected if disks are inserted @@ -98,10 +96,6 @@ static int _check_vgs(struct list *pvs, int *partial) dl->vgd.pe_total, dl->vgd.pe_allocated, dl->vgd.pvg_total); list_del(pvh); - if (partial_mode()) { - *partial = 1; - continue; - } return 0; } pv_count++; @@ -111,9 +105,6 @@ static int _check_vgs(struct list *pvs, int *partial) if (pv_count != first->vgd.pv_cur) { log_error("%d PV(s) found for VG %s: expected %d", pv_count, first->pvd.vg_name, first->vgd.pv_cur); - if (!partial_mode()) - return 0; - *partial = 1; } return 1; @@ -125,7 +116,6 @@ static struct volume_group *_build_vg(struct format_instance *fid, struct dm_pool *mem = fid->fmt->cmd->mem; struct volume_group *vg = dm_pool_alloc(mem, sizeof(*vg)); struct disk_list *dl; - int partial; if (!vg) goto_bad; @@ -142,12 +132,12 @@ static struct volume_group *_build_vg(struct format_instance *fid, list_init(&vg->lvs); list_init(&vg->tags); - if (!_check_vgs(pvs, &partial)) + if (!_check_vgs(pvs)) goto_bad; dl = list_item(pvs->n, struct disk_list); - if (!import_vg(mem, vg, dl, partial)) + if (!import_vg(mem, vg, dl)) goto_bad; if (!import_pvs(fid->fmt, mem, vg, pvs, &vg->pvs, &vg->pv_count)) diff --git a/lib/format1/import-export.c b/lib/format1/import-export.c index 4bb51160..c80011d9 100644 --- a/lib/format1/import-export.c +++ b/lib/format1/import-export.c @@ -214,7 +214,7 @@ int export_pv(struct cmd_context *cmd, struct dm_pool *mem __attribute((unused)) } int import_vg(struct dm_pool *mem, - struct volume_group *vg, struct disk_list *dl, int partial) + struct volume_group *vg, struct disk_list *dl) { struct vg_disk *vgd = &dl->vgd; memcpy(vg->id.uuid, vgd->vg_uuid, ID_LEN); @@ -236,10 +236,10 @@ int import_vg(struct dm_pool *mem, if (vgd->vg_status & VG_EXTENDABLE) vg->status |= RESIZEABLE_VG; - if (partial || (vgd->vg_access & VG_READ)) + if (vgd->vg_access & VG_READ) vg->status |= LVM_READ; - if (!partial && (vgd->vg_access & VG_WRITE)) + if (vgd->vg_access & VG_WRITE) vg->status |= LVM_WRITE; if (vgd->vg_access & VG_CLUSTERED) @@ -255,9 +255,6 @@ int import_vg(struct dm_pool *mem, vg->max_pv = vgd->pv_max; vg->alloc = ALLOC_NORMAL; - if (partial) - vg->status |= PARTIAL_VG; - return 1; } diff --git a/lib/format_text/archiver.c b/lib/format_text/archiver.c index 1d2c5ede..e54535c9 100644 --- a/lib/format_text/archiver.c +++ b/lib/format_text/archiver.c @@ -134,10 +134,8 @@ int archive_display(struct cmd_context *cmd, const char *vg_name) { int r1, r2; - init_partial(1); r1 = archive_list(cmd, cmd->archive_params->dir, vg_name); r2 = backup_list(cmd, cmd->backup_params->dir, vg_name); - init_partial(0); return r1 && r2; } @@ -146,9 +144,7 @@ int archive_display_file(struct cmd_context *cmd, const char *file) { int r; - init_partial(1); r = archive_list_file(cmd, file); - init_partial(0); return r; } @@ -393,7 +389,7 @@ void check_current_backup(struct volume_group *vg) char path[PATH_MAX]; struct volume_group *vg_backup; - if ((vg->status & PARTIAL_VG) || (vg->status & EXPORTED_VG)) + if (vg->status & EXPORTED_VG) return; if (dm_snprintf(path, sizeof(path), "%s/%s", diff --git a/lib/format_text/flags.c b/lib/format_text/flags.c index 86854242..eb281b6b 100644 --- a/lib/format_text/flags.c +++ b/lib/format_text/flags.c @@ -31,12 +31,12 @@ struct flag { static struct flag _vg_flags[] = { {EXPORTED_VG, "EXPORTED", STATUS_FLAG}, {RESIZEABLE_VG, "RESIZEABLE", STATUS_FLAG}, - {PARTIAL_VG, "PARTIAL", STATUS_FLAG}, {PVMOVE, "PVMOVE", STATUS_FLAG}, {LVM_READ, "READ", STATUS_FLAG}, {LVM_WRITE, "WRITE", STATUS_FLAG}, {CLUSTERED, "CLUSTERED", STATUS_FLAG}, {SHARED, "SHARED", STATUS_FLAG}, + {PARTIAL_VG, NULL, 0}, {PRECOMMITTED, NULL, 0}, {0, NULL, 0} }; @@ -44,6 +44,7 @@ static struct flag _vg_flags[] = { static struct flag _pv_flags[] = { {ALLOCATABLE_PV, "ALLOCATABLE", STATUS_FLAG}, {EXPORTED_VG, "EXPORTED", STATUS_FLAG}, + {MISSING_PV, "MISSING", COMPATIBLE_FLAG}, {0, NULL, 0} }; @@ -62,6 +63,8 @@ static struct flag _lv_flags[] = { {SNAPSHOT, NULL, 0}, {ACTIVATE_EXCL, NULL, 0}, {CONVERTING, NULL, 0}, + {PARTIAL_LV, NULL, 0}, + {POSTORDER_FLAG, NULL, 0}, {0, NULL, 0} }; @@ -155,7 +158,16 @@ int read_flags(uint32_t *status, int type, struct config_value *cv) break; } - if (!flags[f].description && (type & STATUS_FLAG)) { + if (type == VG_FLAGS && !strcmp(cv->v.str, "PARTIAL")) { + /* + * Exception: We no longer write this flag out, but it + * might be encountered in old backup files, so restore + * it in that case. It is never part of live metadata + * though, so only vgcfgrestore needs to be concerned + * by this case. + */ + s |= PARTIAL_VG; + } else if (!flags[f].description && (type & STATUS_FLAG)) { log_err("Unknown status flag '%s'.", cv->v.str); return 0; } diff --git a/lib/format_text/import_vsn1.c b/lib/format_text/import_vsn1.c index f32573a9..653be29d 100644 --- a/lib/format_text/import_vsn1.c +++ b/lib/format_text/import_vsn1.c @@ -194,11 +194,6 @@ static int _read_pv(struct format_instance *fid, struct dm_pool *mem, else log_error("Couldn't find device with uuid '%s'.", buffer); - - if (partial_mode()) - vg->status |= PARTIAL_VG; - else - return 0; } if (!(pv->vg_name = dm_pool_strdup(mem, vg->name))) @@ -211,6 +206,9 @@ static int _read_pv(struct format_instance *fid, struct dm_pool *mem, return 0; } + if (!pv->dev) + pv->status |= MISSING_PV; + /* Late addition */ _read_int64(pvn, "dev_size", &pv->size); @@ -800,11 +798,6 @@ static struct volume_group *_read_vg(struct format_instance *fid, dm_hash_destroy(pv_hash); - if (vg->status & PARTIAL_VG) { - vg->status &= ~LVM_WRITE; - vg->status |= LVM_READ; - } - /* * Finished. */ diff --git a/lib/locking/cluster_locking.c b/lib/locking/cluster_locking.c index 3cbc9de9..94b39de5 100644 --- a/lib/locking/cluster_locking.c +++ b/lib/locking/cluster_locking.c @@ -315,9 +315,6 @@ static int _lock_for_cluster(unsigned char cmd, uint32_t flags, const char *name args[0] = flags & 0x7F; /* Maskoff lock flags */ args[1] = flags & 0xC0; /* Bitmap flags */ - if (partial_mode()) - args[1] |= LCK_PARTIAL_MODE; - if (mirror_in_sync()) args[1] |= LCK_MIRROR_NOSYNC_MODE; diff --git a/lib/locking/locking.h b/lib/locking/locking.h index 6a34a6d3..c900e54f 100644 --- a/lib/locking/locking.h +++ b/lib/locking/locking.h @@ -83,7 +83,6 @@ int check_lvm1_vg_inactive(struct cmd_context *cmd, const char *vgname); /* * Additional lock bits for cluster communication */ -#define LCK_PARTIAL_MODE 0x00000001U /* Running in partial mode */ #define LCK_MIRROR_NOSYNC_MODE 0x00000002U /* Mirrors don't require sync */ #define LCK_DMEVENTD_MONITOR_MODE 0x00000004U /* Register with dmeventd */ diff --git a/lib/log/log.c b/lib/log/log.c index 78686817..c8b80a28 100644 --- a/lib/log/log.c +++ b/lib/log/log.c @@ -29,7 +29,6 @@ static struct str_list _log_dev_alias; static int _verbose_level = VERBOSE_BASE_LEVEL; static int _test = 0; -static int _partial = 0; static int _md_filtering = 0; static int _pvmove = 0; static int _full_scan_done = 0; /* Restrict to one full scan during each cmd */ @@ -154,11 +153,6 @@ void init_test(int level) _test = level; } -void init_partial(int level) -{ - _partial = level; -} - void init_md_filtering(int level) { _md_filtering = level; @@ -254,11 +248,6 @@ int test_mode() return _test; } -int partial_mode() -{ - return _partial; -} - int md_filtering() { return _md_filtering; diff --git a/lib/log/log.h b/lib/log/log.h index 8b85758b..133b170a 100644 --- a/lib/log/log.h +++ b/lib/log/log.h @@ -64,7 +64,6 @@ void fin_syslog(void); void init_verbose(int level); void init_test(int level); -void init_partial(int level); void init_md_filtering(int level); void init_pvmove(int level); void init_full_scan_done(int level); @@ -84,7 +83,6 @@ void init_error_message_produced(int error_message_produced); void set_cmd_name(const char *cmd_name); int test_mode(void); -int partial_mode(void); int md_filtering(void); int pvmove_mode(void); int full_scan_done(void); diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h index 280646a2..6cdfdb4a 100644 --- a/lib/metadata/metadata-exported.h +++ b/lib/metadata/metadata-exported.h @@ -71,6 +71,13 @@ struct pv_segment; //#define PRECOMMITTED 0x00200000U /* VG - internal use only */ #define CONVERTING 0x00400000U /* LV */ +#define MISSING_PV 0x00800000U /* PV */ +#define PARTIAL_LV 0x01000000U /* LV - derived flag, not + written out in metadata*/ + +//#define POSTORDER_FLAG 0x02000000U /* Not a real flag, reserved for +// temporary use inside vg_read. */ + #define LVM_READ 0x00000100U /* LV VG */ #define LVM_WRITE 0x00000200U /* LV VG */ #define CLUSTERED 0x00000400U /* VG */ @@ -564,6 +571,7 @@ uint64_t pv_pe_start(const pv_t *pv); uint32_t pv_pe_count(const pv_t *pv); uint32_t pv_pe_alloc_count(const pv_t *pv); +int vg_missing_pv_count(const vg_t *vg); uint32_t vg_status(const vg_t *vg); #define vg_is_clustered(vg) (vg_status((vg)) & CLUSTERED) diff --git a/lib/metadata/metadata.c b/lib/metadata/metadata.c index ecf4152a..e9e944c5 100644 --- a/lib/metadata/metadata.c +++ b/lib/metadata/metadata.c @@ -334,9 +334,9 @@ int vg_remove_single(struct cmd_context *cmd, const char *vg_name, struct pv_list *pvl; int ret = 1; - if (!vg || !consistent || (vg_status(vg) & PARTIAL_VG)) { - log_error("Volume group \"%s\" not found or inconsistent.", - vg_name); + if (!vg || !consistent || vg_missing_pv_count(vg)) { + log_error("Volume group \"%s\" not found, is inconsistent " + "or has PVs missing.", vg_name); log_error("Consider vgreduce --removemissing if metadata " "is inconsistent."); return 0; @@ -488,19 +488,15 @@ struct volume_group *vg_create(struct cmd_context *cmd, const char *vg_name, struct volume_group *vg; struct dm_pool *mem = cmd->mem; int consistent = 0; - int old_partial; if (!(vg = dm_pool_zalloc(mem, sizeof(*vg)))) return_NULL; /* is this vg name already in use ? */ - old_partial = partial_mode(); - init_partial(1); if (vg_read(cmd, vg_name, NULL, &consistent)) { log_err("A volume group called '%s' already exists.", vg_name); goto bad; } - init_partial(old_partial); if (!id_create(&vg->id)) { log_err("Couldn't create uuid for volume group '%s'.", vg_name); @@ -1191,6 +1187,157 @@ int vgs_are_compatible(struct cmd_context *cmd __attribute((unused)), return 1; } +struct _lv_postorder_baton { + int (*fn)(struct logical_volume *lv, void *data); + void *data; +}; + +static int _lv_postorder_visit(struct logical_volume *, + int (*fn)(struct logical_volume *lv, void *data), + void *data); + +static int _lv_postorder_level(struct logical_volume *lv, void *data) +{ + struct _lv_postorder_baton *baton = data; + int r =_lv_postorder_visit(lv, baton->fn, baton->data); + lv->status |= POSTORDER_FLAG; + return r; +}; + +static int _lv_each_dependency(struct logical_volume *lv, + int (*fn)(struct logical_volume *lv, void *data), + void *data) +{ + int i, s; + struct lv_segment *lvseg; + + struct logical_volume *deps[] = { + lv->snapshot ? lv->snapshot->origin : 0, + lv->snapshot ? lv->snapshot->cow : 0 }; + for (i = 0; i < sizeof(deps) / sizeof(*deps); ++i) { + if (deps[i] && !fn(deps[i], data)) + return_0; + } + + list_iterate_items(lvseg, &lv->segments) { + if (lvseg->log_lv && !fn(lvseg->log_lv, data)) + return_0; + for (s = 0; s < lvseg->area_count; ++s) { + if (seg_type(lvseg, s) == AREA_LV && !fn(seg_lv(lvseg,s), data)) + return_0; + } + } + return 1; +} + +static int _lv_postorder_cleanup(struct logical_volume *lv, void *data) +{ + if (!(lv->status & POSTORDER_FLAG)) + return 1; + lv->status &= ~POSTORDER_FLAG; + + if (!_lv_each_dependency(lv, _lv_postorder_cleanup, data)) + return_0; + return 1; +} + +static int _lv_postorder_visit(struct logical_volume *lv, + int (*fn)(struct logical_volume *lv, void *data), + void *data) +{ + struct _lv_postorder_baton baton; + int r; + + if (lv->status & POSTORDER_FLAG) + return 1; + + baton.fn = fn; + baton.data = data; + r = _lv_each_dependency(lv, _lv_postorder_level, &baton); + if (r) { + r = fn(lv, data); + log_verbose("visited %s", lv->name); + } + return r; +} + +/* + * This will walk the LV dependency graph in depth-first order and in the + * postorder, call a callback function "fn". The void *data is passed along all + * the calls. The callback may return zero to indicate an error and terminate + * the depth-first walk. The error is propagated to return value of + * _lv_postorder. + */ +static int _lv_postorder(struct logical_volume *lv, + int (*fn)(struct logical_volume *lv, void *data), + void *data) +{ + int r; + r = _lv_postorder_visit(lv, fn, data); + _lv_postorder_cleanup(lv, 0); + return r; +} + +struct _lv_mark_if_partial_baton { + int partial; +}; + +static int _lv_mark_if_partial_collect(struct logical_volume *lv, void *data) +{ + struct _lv_mark_if_partial_baton *baton = data; + if (lv->status & PARTIAL_LV) + baton->partial = 1; + + return 1; +} + +static int _lv_mark_if_partial_single(struct logical_volume *lv, void *data) +{ + int s; + struct _lv_mark_if_partial_baton baton; + struct lv_segment *lvseg; + + list_iterate_items(lvseg, &lv->segments) { + for (s = 0; s < lvseg->area_count; ++s) { + if (seg_type(lvseg, s) == AREA_PV) { + if (seg_pv(lvseg, s)->status & MISSING_PV) + lv->status |= PARTIAL_LV; + } + } + } + + baton.partial = 0; + _lv_each_dependency(lv, _lv_mark_if_partial_collect, &baton); + + if (baton.partial) + lv->status |= PARTIAL_LV; + + return 1; +} + +static int _lv_mark_if_partial(struct logical_volume *lv) +{ + return _lv_postorder(lv, _lv_mark_if_partial_single, NULL); +} + +/* + * Mark LVs with missing PVs using PARTIAL_LV status flag. The flag is + * propagated transitively, so LVs referencing other LVs are marked + * partial as well, if any of their referenced LVs are marked partial. + */ +static int _vg_mark_partial_lvs(struct volume_group *vg) +{ + struct logical_volume *lv; + struct lv_list *lvl; + + list_iterate_items(lvl, &vg->lvs) { + lv = lvl->lv; + if (!_lv_mark_if_partial(lv)) + return_0; + } + return 1; +} + int vg_validate(struct volume_group *vg) { struct pv_list *pvl, *pvl2; @@ -1295,8 +1442,13 @@ int vg_write(struct volume_group *vg) return_0; if (vg->status & PARTIAL_VG) { - log_error("Cannot change metadata for partial volume group %s", - vg->name); + log_error("Cannot update partial volume group %s.", vg->name); + return 0; + } + + if (vg_missing_pv_count(vg) && !vg->cmd->handles_missing_pvs) { + log_error("Cannot update volume group %s while physical " + "volumes are missing.", vg->name); return 0; } @@ -1495,9 +1647,20 @@ static int _update_pv_list(struct list *all_pvs, struct volume_group *vg) return 1; } +int vg_missing_pv_count(const vg_t *vg) +{ + int ret = 0; + struct pv_list *pvl; + list_iterate_items(pvl, &vg->pvs) { + if (pvl->pv->status & MISSING_PV) + ++ ret; + } + return ret; +} + /* Caller sets consistent to 1 if it's safe for vg_read to correct * inconsistent metadata on disk (i.e. the VG write lock is held). - * This guarantees only consistent metadata is returned unless PARTIAL_VG. + * This guarantees only consistent metadata is returned. * If consistent is 0, caller must check whether consistent == 1 on return * and take appropriate action if it isn't (e.g. abort; get write lock * and call vg_read again). @@ -1536,6 +1699,11 @@ static struct volume_group *_vg_read(struct cmd_context *cmd, } if ((correct_vg = lvmcache_get_vg(vgid, precommitted))) { + if (vg_missing_pv_count(correct_vg)) { + log_verbose("There are %d physical volumes missing.", + vg_missing_pv_count(correct_vg)); + _vg_mark_partial_lvs(correct_vg); + } *consistent = 1; return correct_vg; } @@ -1631,7 +1799,8 @@ static struct volume_group *_vg_read(struct cmd_context *cmd, } } - if (list_size(&correct_vg->pvs) != list_size(pvids)) { + if (list_size(&correct_vg->pvs) != list_size(pvids) + + vg_missing_pv_count(correct_vg)) { log_debug("Cached VG %s had incorrect PV list", vgname); @@ -1640,6 +1809,8 @@ static struct volume_group *_vg_read(struct cmd_context *cmd, else correct_vg = NULL; } else list_iterate_items(pvl, &correct_vg->pvs) { + if (pvl->pv->status & MISSING_PV) + continue; if (!str_list_match_item(pvids, pvl->pv->dev->pvid)) { log_debug("Cached VG %s had incorrect PV list", vgname); @@ -1722,15 +1893,6 @@ static struct volume_group *_vg_read(struct cmd_context *cmd, if (!*consistent) return correct_vg; - /* Don't touch partial volume group metadata */ - /* Should be fixed manually with vgcfgbackup/restore etc. */ - if ((correct_vg->status & PARTIAL_VG)) { - log_error("Inconsistent metadata copies found for " - "partial volume group %s", vgname); - *consistent = 0; - return correct_vg; - } - /* Don't touch if vgids didn't match */ if (inconsistent_vgid) { log_error("Inconsistent metadata UUIDs found for " @@ -1769,6 +1931,12 @@ static struct volume_group *_vg_read(struct cmd_context *cmd, } } + if (vg_missing_pv_count(correct_vg)) { + log_verbose("There are %d physical volumes missing.", + vg_missing_pv_count(correct_vg)); + _vg_mark_partial_lvs(correct_vg); + } + if ((correct_vg->status & PVMOVE) && !pvmove_mode()) { log_error("WARNING: Interrupted pvmove detected in " "volume group %s", correct_vg->name); @@ -1828,11 +1996,10 @@ static struct volume_group *_vg_read_by_vgid(struct cmd_context *cmd, if ((vg = _vg_read(cmd, NULL, vgid, &consistent, precommitted)) && !strncmp((char *)vg->id.uuid, vgid, ID_LEN)) { + if (!consistent) { log_error("Volume group %s metadata is " "inconsistent", vg->name); - if (!partial_mode()) - return NULL; } return vg; } @@ -1860,6 +2027,7 @@ static struct volume_group *_vg_read_by_vgid(struct cmd_context *cmd, if ((vg = _vg_read(cmd, vgname, vgid, &consistent, precommitted)) && !strncmp((char *)vg->id.uuid, vgid, ID_LEN)) { + if (!consistent) { log_error("Volume group %s metadata is " "inconsistent", vgname); @@ -1993,7 +2161,6 @@ static int _get_pvs(struct cmd_context *cmd, struct list **pvslist) struct list *vgids; struct volume_group *vg; int consistent = 0; - int old_partial; int old_pvmove; lvmcache_label_scan(cmd, 0); @@ -2015,9 +2182,7 @@ static int _get_pvs(struct cmd_context *cmd, struct list **pvslist) /* Read every VG to ensure cache consistency */ /* Orphan VG is last on list */ - old_partial = partial_mode(); old_pvmove = pvmove_mode(); - init_partial(1); init_pvmove(1); list_iterate_items(strl, vgids) { vgid = strl->str; @@ -2042,7 +2207,6 @@ static int _get_pvs(struct cmd_context *cmd, struct list **pvslist) list_add(results, pvh); } init_pvmove(old_pvmove); - init_partial(old_partial); if (pvslist) *pvslist = results; diff --git a/lib/metadata/metadata.h b/lib/metadata/metadata.h index e5538b91..6d12653b 100644 --- a/lib/metadata/metadata.h +++ b/lib/metadata/metadata.h @@ -61,6 +61,14 @@ //#define MIRROR_NOTSYNCED 0x00080000U /* LV */ #define ACTIVATE_EXCL 0x00100000U /* LV - internal use only */ #define PRECOMMITTED 0x00200000U /* VG - internal use only */ +//#define CONVERTING 0x00400000U /* LV */ + +//#define MISSING_PV 0x00800000U /* PV */ +//#define PARTIAL_LV 0x01000000U /* LV - derived flag, not +// written out in metadata*/ + +#define POSTORDER_FLAG 0x02000000U /* Not a real flag, reserved for + temporary use inside vg_read. */ //#define LVM_READ 0x00000100U /* LV VG */ //#define LVM_WRITE 0x00000200U /* LV VG */ diff --git a/lib/report/report.c b/lib/report/report.c index 70e25aec..a34d6174 100644 --- a/lib/report/report.c +++ b/lib/report/report.c @@ -439,7 +439,7 @@ static int _vgstatus_disp(struct dm_report *rh __attribute((unused)), struct dm_ else repstr[2] = '-'; - if (vg->status & PARTIAL_VG) + if (vg_missing_pv_count(vg)) repstr[3] = 'p'; else repstr[3] = '-'; diff --git a/man/lvm.conf.5 b/man/lvm.conf.5 index 0bb19724..47666b4e 100644 --- a/man/lvm.conf.5 +++ b/man/lvm.conf.5 @@ -300,12 +300,16 @@ in \fBlvm\fP (8). .TP \fBactivation\fP \(em Settings affecting device-mapper activation .IP -\fBmissing_stripe_filler\fP \(em When activating an incomplete -logical volume in partial mode, this missing data is replaced -with this device. It could perhaps be a block device that always -returns an error when it is accessed, or one that always -returns zeros. See \fBlvcreate\fP (8) for how to create -such devices. +\fBmissing_stripe_filler\fP \(em When activating an incomplete logical +volume in partial mode, this option dictates how the missing data is +replaced. A value of "error" will cause activation to create error +mappings for the missing data, meaning that read access to missing +portions of the volume will result in I/O errors. You can instead also +use a device path, and in that case this device will be used in place of +missing stripes. However, note that using anything other than +"error" with mirrored or snapshotted volumes is likely to result in data +corruption. For instructions on how to create a device that always +returns zeros, see \fBlvcreate\fP (8). .IP \fBmirror_region_size\fP \(em Unit size in KB for copy operations when mirroring. diff --git a/man/vgreduce.8 b/man/vgreduce.8 index 8f4fe1b7..a382454d 100644 --- a/man/vgreduce.8 +++ b/man/vgreduce.8 @@ -18,11 +18,13 @@ See \fBlvm\fP for common options. Removes all empty physical volumes if none are given on command line. .TP .I \-\-removemissing -Removes all missing physical volumes from the volume group and makes -the volume group consistent again. +Removes all missing physical volumes from the volume group, if there are no +logical volumes allocated on those. This resumes normal operation of the volume +group (new logical volumes may again be created, changed and so on). -It's a good idea to run this option with --test first to find out what it -would remove before running it for real. +If this is not possible (there are logical volumes referencing the missing +physical volumes) and you cannot or do not want to remove them manually, you +can run this option with --force to have vgreduce remove any partial LVs. Any logical volumes and dependent snapshots that were partly on the missing disks get removed completely. This includes those parts diff --git a/tools/commands.h b/tools/commands.h index fddc6a33..7121a764 100644 --- a/tools/commands.h +++ b/tools/commands.h @@ -850,13 +850,15 @@ xx(vgreduce, "\t[-h|--help]\n" "\t[--mirrorsonly]\n" "\t[--removemissing]\n" + "\t[-f|--force]\n" "\t[-t|--test]\n" "\t[-v|--verbose]\n" "\t[--version]" "\n" "\tVolumeGroupName\n" "\t[PhysicalVolumePath...]\n", - all_ARG, autobackup_ARG, mirrorsonly_ARG, removemissing_ARG, test_ARG) + all_ARG, autobackup_ARG, mirrorsonly_ARG, removemissing_ARG, + force_ARG, test_ARG) xx(vgremove, "Remove volume group(s)", diff --git a/tools/lvmcmdline.c b/tools/lvmcmdline.c index e97cf65d..c5064166 100644 --- a/tools/lvmcmdline.c +++ b/tools/lvmcmdline.c @@ -710,13 +710,13 @@ static int _get_settings(struct cmd_context *cmd) cmd->current_settings.archive = arg_int_value(cmd, autobackup_ARG, cmd->current_settings.archive); cmd->current_settings.backup = arg_int_value(cmd, autobackup_ARG, cmd->current_settings.backup); cmd->current_settings.cache_vgmetadata = cmd->command->flags & CACHE_VGMETADATA ? 1 : 0; + cmd->partial_activate = 0; if (arg_count(cmd, partial_ARG)) { - init_partial(1); + cmd->partial_activate = 1; log_print("Partial mode. Incomplete volume groups will " "be activated read-only."); - } else - init_partial(0); + } if (arg_count(cmd, ignorelockingfailure_ARG)) init_ignorelockingfailure(1); @@ -826,6 +826,7 @@ static void _apply_settings(struct cmd_context *cmd) cmd->fmt = arg_ptr_value(cmd, metadatatype_ARG, cmd->current_settings.fmt); + cmd->handles_missing_pvs = 0; } static char *_copy_command_line(struct cmd_context *cmd, int argc, char **argv) diff --git a/tools/lvremove.c b/tools/lvremove.c index 703cf143..24278b25 100644 --- a/tools/lvremove.c +++ b/tools/lvremove.c @@ -31,6 +31,8 @@ int lvremove(struct cmd_context *cmd, int argc, char **argv) return EINVALID_CMD_LINE; } + cmd->handles_missing_pvs = 1; + return process_each_lv(cmd, argc, argv, LCK_VG_WRITE, NULL, &lvremove_single); } diff --git a/tools/pvcreate.c b/tools/pvcreate.c index b18609d2..b8af0e7b 100644 --- a/tools/pvcreate.c +++ b/tools/pvcreate.c @@ -49,7 +49,6 @@ static int pvcreate_check(struct cmd_context *cmd, const char *name, /* FIXME Check partition type is LVM unless --force is given */ /* Is there a pv here already? */ - /* FIXME Use partial mode here? */ pv = pv_read(cmd, name, NULL, NULL, 0); /* @@ -272,13 +271,11 @@ static int pvcreate_validate_params(struct cmd_context *cmd, if (arg_count(cmd, restorefile_ARG)) { pp->restorefile = arg_str_value(cmd, restorefile_ARG, ""); /* The uuid won't already exist */ - init_partial(1); if (!(vg = backup_read_vg(cmd, NULL, pp->restorefile))) { log_error("Unable to read volume group from %s", pp->restorefile); return 0; } - init_partial(0); if (!(existing_pv = find_pv_in_vg_by_uuid(vg, pp->idp))) { log_error("Can't find uuid %s in backup file %s", uuid, pp->restorefile); diff --git a/tools/vgcfgbackup.c b/tools/vgcfgbackup.c index 6418e04d..7a67e6a3 100644 --- a/tools/vgcfgbackup.c +++ b/tools/vgcfgbackup.c @@ -96,8 +96,7 @@ int vgcfgbackup(struct cmd_context *cmd, int argc, char **argv) int ret; char *last_filename = NULL; - if (partial_mode()) - init_pvmove(1); + init_pvmove(1); ret = process_each_vg(cmd, argc, argv, LCK_VG_READ, 0, &last_filename, &vg_backup_single); diff --git a/tools/vgreduce.c b/tools/vgreduce.c index c0747bfc..a32aa505 100644 --- a/tools/vgreduce.c +++ b/tools/vgreduce.c @@ -16,7 +16,7 @@ #include "tools.h" #include "lv_alloc.h" -static int _remove_pv(struct volume_group *vg, struct pv_list *pvl) +static int _remove_pv(struct volume_group *vg, struct pv_list *pvl, int silent) { char uuid[64] __attribute((aligned(8))); @@ -31,8 +31,9 @@ static int _remove_pv(struct volume_group *vg, struct pv_list *pvl) log_verbose("Removing PV with UUID %s from VG %s", uuid, vg->name); if (pvl->pv->pe_alloc_count) { - log_error("LVs still present on PV with UUID %s: Can't remove " - "from VG %s", uuid, vg->name); + if (!silent) + log_error("LVs still present on PV with UUID %s: " + "Can't remove from VG %s", uuid, vg->name); return 0; } @@ -130,11 +131,39 @@ static int _remove_lv(struct cmd_context *cmd, struct logical_volume *lv, return 1; } +static int _consolidate_vg(struct cmd_context *cmd, struct volume_group *vg) +{ + struct pv_list *pvl; + struct lv_list *lvl; + int r = 1; + + list_iterate_items(lvl, &vg->lvs) + if (lvl->lv->status & PARTIAL_LV) { + log_warn("WARNING: Partial LV %s needs to be repaired " + "or removed. ", lvl->lv->name); + r = 0; + } + + if (!r) { + cmd->handles_missing_pvs = 1; + log_warn("WARNING: There are still partial LVs in VG %s.", vg->name); + log_warn("To remove them unconditionally use: vgreduce --removemissing --force."); + log_warn("Proceeding to remove empty missing PVs."); + } + + list_iterate_items(pvl, &vg->pvs) { + if (pvl->pv->dev && !(pvl->pv->status & MISSING_PV)) + continue; + if (r && !_remove_pv(vg, pvl, 0)) + return_0; + } + + return r; +} + static int _make_vg_consistent(struct cmd_context *cmd, struct volume_group *vg) { - struct list *pvh, *pvht; struct list *lvh, *lvht; - struct pv_list *pvl; struct lv_list *lvl, *lvl2, *lvlt; struct logical_volume *lv; struct physical_volume *pv; @@ -182,20 +211,8 @@ static int _make_vg_consistent(struct cmd_context *cmd, struct volume_group *vg) return 0; } - /* Remove missing PVs */ - list_iterate_safe(pvh, pvht, &vg->pvs) { - pvl = list_item(pvh, struct pv_list); - if (pvl->pv->dev) - continue; - if (!_remove_pv(vg, pvl)) - return_0; - } - - /* VG is now consistent */ - vg->status &= ~PARTIAL_VG; - vg->status |= LVM_WRITE; - - init_partial(0); + if (!_consolidate_vg(cmd, vg)) + return_0; /* FIXME Recovery. For now people must clean up by hand. */ @@ -208,14 +225,11 @@ static int _make_vg_consistent(struct cmd_context *cmd, struct volume_group *vg) if (!test_mode()) { /* Suspend lvs_changed */ - init_partial(1); if (!suspend_lvs(cmd, &lvs_changed)) { stack; - init_partial(0); vg_revert(vg); return 0; } - init_partial(0); } if (!vg_commit(vg)) { @@ -439,26 +453,26 @@ int vgreduce(struct cmd_context *cmd, int argc, char **argv) char *vg_name; int ret = 1; int consistent = 1; + int fixed = 1; + int repairing = arg_count(cmd, removemissing_ARG); - if (!argc && !arg_count(cmd, removemissing_ARG)) { + if (!argc && !repairing) { log_error("Please give volume group name and " "physical volume paths"); return EINVALID_CMD_LINE; } - - if (!argc && arg_count(cmd, removemissing_ARG)) { + + if (!argc && repairing) { log_error("Please give volume group name"); return EINVALID_CMD_LINE; } - if (arg_count(cmd, mirrorsonly_ARG) && - !arg_count(cmd, removemissing_ARG)) { + if (arg_count(cmd, mirrorsonly_ARG) && !repairing) { log_error("--mirrorsonly requires --removemissing"); return EINVALID_CMD_LINE; } - if (argc == 1 && !arg_count(cmd, all_ARG) - && !arg_count(cmd, removemissing_ARG)) { + if (argc == 1 && !arg_count(cmd, all_ARG) && !repairing) { log_error("Please enter physical volume paths or option -a"); return EINVALID_CMD_LINE; } @@ -469,7 +483,7 @@ int vgreduce(struct cmd_context *cmd, int argc, char **argv) return EINVALID_CMD_LINE; } - if (argc > 1 && arg_count(cmd, removemissing_ARG)) { + if (argc > 1 && repairing) { log_error("Please only specify the volume group"); return EINVALID_CMD_LINE; } @@ -490,8 +504,8 @@ int vgreduce(struct cmd_context *cmd, int argc, char **argv) return ECMD_FAILED; } - if ((!(vg = vg_read(cmd, vg_name, NULL, &consistent)) || !consistent) && - !arg_count(cmd, removemissing_ARG)) { + if ((!(vg = vg_read(cmd, vg_name, NULL, &consistent)) || !consistent) + && !repairing) { log_error("Volume group \"%s\" doesn't exist", vg_name); unlock_vg(cmd, vg_name); return ECMD_FAILED; @@ -502,16 +516,15 @@ int vgreduce(struct cmd_context *cmd, int argc, char **argv) return ECMD_FAILED; } - if (arg_count(cmd, removemissing_ARG)) { - if (vg && consistent) { + if (repairing) { + if (vg && consistent && !vg_missing_pv_count(vg)) { log_error("Volume group \"%s\" is already consistent", vg_name); unlock_vg(cmd, vg_name); return ECMD_PROCESSED; } - init_partial(1); - consistent = 0; + consistent = !arg_count(cmd, force_ARG); if (!(vg = vg_read(cmd, vg_name, NULL, &consistent))) { log_error("Volume group \"%s\" not found", vg_name); unlock_vg(cmd, vg_name); @@ -522,16 +535,17 @@ int vgreduce(struct cmd_context *cmd, int argc, char **argv) return ECMD_FAILED; } if (!archive(vg)) { - init_partial(0); unlock_vg(cmd, vg_name); return ECMD_FAILED; } - if (!_make_vg_consistent(cmd, vg)) { - init_partial(0); - unlock_vg(cmd, vg_name); - return ECMD_FAILED; - } + if (arg_count(cmd, force_ARG)) { + if (!_make_vg_consistent(cmd, vg)) { + unlock_vg(cmd, vg_name); + return ECMD_FAILED; + } + } else + fixed = _consolidate_vg(cmd, vg); if (!vg_write(vg) || !vg_commit(vg)) { log_error("Failed to write out a consistent VG for %s", @@ -542,7 +556,9 @@ int vgreduce(struct cmd_context *cmd, int argc, char **argv) backup(vg); - log_print("Wrote out consistent volume group %s", vg_name); + if (fixed) + log_print("Wrote out consistent volume group %s", + vg_name); } else { if (!vg_check_status(vg, EXPORTED_VG | LVM_WRITE | RESIZEABLE_VG)) { |