diff -Nru LVM2.2.02.39/daemons/clvmd/lvm-functions.c LVM2.2.02.39-working//daemons/clvmd/lvm-functions.c --- LVM2.2.02.39/daemons/clvmd/lvm-functions.c 2010-03-26 16:20:02.000000000 +0800 +++ LVM2.2.02.39-working//daemons/clvmd/lvm-functions.c 2010-03-26 15:09:05.000000000 +0800 @@ -413,9 +413,6 @@ } } - 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 @@ break; } - if (lock_flags & LCK_PARTIAL_MODE) - init_partial(0); - if (lock_flags & LCK_MIRROR_NOSYNC_MODE) init_mirror_in_sync(0); diff -Nru LVM2.2.02.39/doc/example.conf LVM2.2.02.39-working//doc/example.conf --- LVM2.2.02.39/doc/example.conf 2008-04-11 02:50:37.000000000 +0800 +++ LVM2.2.02.39-working//doc/example.conf 2010-03-26 15:09:05.000000000 +0800 @@ -266,11 +266,13 @@ } 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 -Nru LVM2.2.02.39/lib/activate/activate.c LVM2.2.02.39-working//lib/activate/activate.c --- LVM2.2.02.39/lib/activate/activate.c 2010-03-26 16:20:03.000000000 +0800 +++ LVM2.2.02.39-working//lib/activate/activate.c 2010-03-26 15:09:05.000000000 +0800 @@ -1057,6 +1057,12 @@ 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 -Nru LVM2.2.02.39/lib/activate/dev_manager.c LVM2.2.02.39-working//lib/activate/dev_manager.c --- LVM2.2.02.39/lib/activate/dev_manager.c 2010-03-26 16:20:03.000000000 +0800 +++ LVM2.2.02.39-working//lib/activate/dev_manager.c 2010-03-26 15:09:05.000000000 +0800 @@ -47,7 +47,6 @@ struct cmd_context *cmd; - const char *stripe_filler; void *target_state; uint32_t pvmove_mirror_count; int flush_required; @@ -60,8 +59,6 @@ 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; @@ -445,13 +442,6 @@ 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; @@ -701,6 +691,68 @@ 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) @@ -714,11 +766,10 @@ (!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 -Nru LVM2.2.02.39/lib/commands/toolcontext.c LVM2.2.02.39-working//lib/commands/toolcontext.c --- LVM2.2.02.39/lib/commands/toolcontext.c 2008-06-26 00:52:26.000000000 +0800 +++ LVM2.2.02.39-working//lib/commands/toolcontext.c 2010-03-26 15:09:05.000000000 +0800 @@ -154,6 +154,7 @@ { mode_t old_umask; const char *read_ahead; + struct stat st; /* umask */ cmd->default_settings.umask = find_config_tree_int(cmd, @@ -218,6 +219,24 @@ 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; } @@ -936,6 +955,7 @@ 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 -Nru LVM2.2.02.39/lib/commands/toolcontext.h LVM2.2.02.39-working//lib/commands/toolcontext.h --- LVM2.2.02.39/lib/commands/toolcontext.h 2008-04-08 20:49:20.000000000 +0800 +++ LVM2.2.02.39-working//lib/commands/toolcontext.h 2010-03-26 15:09:05.000000000 +0800 @@ -66,8 +66,10 @@ 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 archive_params *archive_params; struct backup_params *backup_params; + const char *stripe_filler; /* List of defined tags */ struct list tags; diff -Nru LVM2.2.02.39/lib/config/defaults.h LVM2.2.02.39-working//lib/config/defaults.h --- LVM2.2.02.39/lib/config/defaults.h 2008-06-25 06:48:53.000000000 +0800 +++ LVM2.2.02.39-working//lib/config/defaults.h 2010-03-26 15:09:05.000000000 +0800 @@ -91,7 +91,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 -Nru LVM2.2.02.39/lib/display/display.c LVM2.2.02.39-working//lib/display/display.c --- LVM2.2.02.39/lib/display/display.c 2008-04-11 03:16:35.000000000 +0800 +++ LVM2.2.02.39-working//lib/display/display.c 2010-03-26 15:09:05.000000000 +0800 @@ -578,10 +578,7 @@ 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 @@ 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 -Nru LVM2.2.02.39/lib/format1/disk-rep.h LVM2.2.02.39-working//lib/format1/disk-rep.h --- LVM2.2.02.39/lib/format1/disk-rep.h 2007-08-21 04:55:25.000000000 +0800 +++ LVM2.2.02.39-working//lib/format1/disk-rep.h 2010-03-26 15:09:05.000000000 +0800 @@ -212,7 +212,7 @@ 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 -Nru LVM2.2.02.39/lib/format1/format1.c LVM2.2.02.39-working//lib/format1/format1.c --- LVM2.2.02.39/lib/format1/format1.c 2008-03-18 00:51:31.000000000 +0800 +++ LVM2.2.02.39-working//lib/format1/format1.c 2010-03-26 15:38:50.000000000 +0800 @@ -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 @@ 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 @@ 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,21 +105,17 @@ 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; } static struct volume_group *_build_vg(struct format_instance *fid, - struct list *pvs) + struct list *pvs, + struct dm_pool *mem) { - 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; @@ -136,18 +126,19 @@ memset(vg, 0, sizeof(*vg)); vg->cmd = fid->fmt->cmd; + vg->vgmem = mem; vg->fid = fid; vg->seqno = 0; list_init(&vg->pvs); 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)) @@ -173,7 +164,7 @@ const char *vg_name, struct metadata_area *mda __attribute((unused))) { - struct dm_pool *mem = dm_pool_create("lvm1 vg_read", 1024 * 10); + struct dm_pool *mem = dm_pool_create("lvm1 vg_read", VG_MEMPOOL_CHUNK); struct list pvs; struct volume_group *vg = NULL; list_init(&pvs); @@ -188,12 +179,13 @@ (fid->fmt, vg_name, fid->fmt->cmd->filter, mem, &pvs)) goto_bad; - if (!(vg = _build_vg(fid, &pvs))) + if (!(vg = _build_vg(fid, &pvs, mem))) goto_bad; - bad: - dm_pool_destroy(mem); return vg; +bad: + dm_pool_destroy(mem); + return NULL; } static struct disk_list *_flatten_pv(struct format_instance *fid, @@ -250,7 +242,7 @@ static int _format1_vg_write(struct format_instance *fid, struct volume_group *vg, struct metadata_area *mda __attribute((unused))) { - struct dm_pool *mem = dm_pool_create("lvm1 vg_write", 1024 * 10); + struct dm_pool *mem = dm_pool_create("lvm1 vg_write", VG_MEMPOOL_CHUNK); struct list pvds; int r = 0; diff -Nru LVM2.2.02.39/lib/format1/import-export.c LVM2.2.02.39-working//lib/format1/import-export.c --- LVM2.2.02.39/lib/format1/import-export.c 2010-03-26 16:20:03.000000000 +0800 +++ LVM2.2.02.39-working//lib/format1/import-export.c 2010-03-26 15:09:05.000000000 +0800 @@ -213,7 +213,7 @@ } 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); @@ -235,10 +235,10 @@ 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) @@ -254,9 +254,6 @@ vg->max_pv = vgd->pv_max; vg->alloc = ALLOC_NORMAL; - if (partial) - vg->status |= PARTIAL_VG; - return 1; } diff -Nru LVM2.2.02.39/lib/format_pool/format_pool.c LVM2.2.02.39-working//lib/format_pool/format_pool.c --- LVM2.2.02.39/lib/format_pool/format_pool.c 2008-06-11 21:14:41.000000000 +0800 +++ LVM2.2.02.39-working//lib/format_pool/format_pool.c 2010-03-26 15:36:38.000000000 +0800 @@ -113,6 +113,7 @@ } vg->cmd = fid->fmt->cmd; + vg->vgmem = mem; vg->fid = fid; vg->name = NULL; vg->status = 0; @@ -160,7 +161,7 @@ const char *vg_name, struct metadata_area *mda __attribute((unused))) { - struct dm_pool *mem = dm_pool_create("pool vg_read", 1024); + struct dm_pool *mem = dm_pool_create("pool vg_read", VG_MEMPOOL_CHUNK); struct list pds; struct volume_group *vg = NULL; @@ -182,9 +183,10 @@ if (!(vg = _build_vg_from_pds(fid, mem, &pds))) goto_out; - out: - dm_pool_destroy(mem); return vg; +out: + dm_pool_destroy(mem); + return NULL; } static int _pool_pv_setup(const struct format_type *fmt __attribute((unused)), diff -Nru LVM2.2.02.39/lib/format_text/archiver.c LVM2.2.02.39-working//lib/format_text/archiver.c --- LVM2.2.02.39/lib/format_text/archiver.c 2008-01-30 21:59:59.000000000 +0800 +++ LVM2.2.02.39-working//lib/format_text/archiver.c 2010-03-26 15:09:05.000000000 +0800 @@ -134,10 +134,8 @@ { 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 r; - init_partial(1); r = archive_list_file(cmd, file); - init_partial(0); return r; } @@ -393,7 +389,7 @@ 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 -Nru LVM2.2.02.39/lib/format_text/flags.c LVM2.2.02.39-working//lib/format_text/flags.c --- LVM2.2.02.39/lib/format_text/flags.c 2008-01-30 21:59:59.000000000 +0800 +++ LVM2.2.02.39-working//lib/format_text/flags.c 2010-03-26 15:15:59.000000000 +0800 @@ -30,12 +30,12 @@ static struct flag _vg_flags[] = { {EXPORTED_VG, "EXPORTED"}, {RESIZEABLE_VG, "RESIZEABLE"}, - {PARTIAL_VG, "PARTIAL"}, {PVMOVE, "PVMOVE"}, {LVM_READ, "READ"}, {LVM_WRITE, "WRITE"}, {CLUSTERED, "CLUSTERED"}, {SHARED, "SHARED"}, + {PARTIAL_VG, NULL}, {PRECOMMITTED, NULL}, {0, NULL} }; @@ -43,6 +43,7 @@ static struct flag _pv_flags[] = { {ALLOCATABLE_PV, "ALLOCATABLE"}, {EXPORTED_VG, "EXPORTED"}, + {MISSING_PV, "MISSING"}, {0, NULL} }; @@ -61,6 +62,8 @@ {SNAPSHOT, NULL}, {ACTIVATE_EXCL, NULL}, {CONVERTING, NULL}, + {PARTIAL_LV, NULL}, + {POSTORDER_FLAG, NULL}, {0, NULL} }; @@ -151,7 +154,16 @@ break; } - if (!flags[f].description) { + 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) { log_err("Unknown status flag '%s'.", cv->v.str); return 0; } diff -Nru LVM2.2.02.39/lib/format_text/import_vsn1.c LVM2.2.02.39-working//lib/format_text/import_vsn1.c --- LVM2.2.02.39/lib/format_text/import_vsn1.c 2010-03-26 16:20:03.000000000 +0800 +++ LVM2.2.02.39-working//lib/format_text/import_vsn1.c 2010-03-26 15:25:11.000000000 +0800 @@ -169,11 +169,6 @@ 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))) @@ -191,6 +186,9 @@ return 0; } + if (!pv->dev) + pv->status |= MISSING_PV; + /* Late addition */ _read_int64(pvn, "dev_size", &pv->size); @@ -647,18 +645,23 @@ struct config_node *vgn, *cn; struct volume_group *vg; struct dm_hash_table *pv_hash = NULL; - struct dm_pool *mem = fid->fmt->cmd->mem; + struct dm_pool *mem = dm_pool_create("lvm2 vg_read", VG_MEMPOOL_CHUNK); + + if (!mem) + return_NULL; /* skip any top-level values */ for (vgn = cft->root; (vgn && vgn->v); vgn = vgn->sib) ; if (!vgn) { log_error("Couldn't find volume group in file."); - return NULL; + goto bad; } if (!(vg = dm_pool_zalloc(mem, sizeof(*vg)))) - return_NULL; + goto_bad; + + vg->vgmem = mem; vg->cmd = fid->fmt->cmd; /* FIXME Determine format type from file contents */ @@ -789,11 +792,6 @@ dm_hash_destroy(pv_hash); - if (vg->status & PARTIAL_VG) { - vg->status &= ~LVM_WRITE; - vg->status |= LVM_READ; - } - /* * Finished. */ @@ -803,7 +801,7 @@ if (pv_hash) dm_hash_destroy(pv_hash); - dm_pool_free(mem, vg); + dm_pool_destroy(mem); return NULL; } diff -Nru LVM2.2.02.39/lib/locking/cluster_locking.c LVM2.2.02.39-working//lib/locking/cluster_locking.c --- LVM2.2.02.39/lib/locking/cluster_locking.c 2010-03-26 16:20:03.000000000 +0800 +++ LVM2.2.02.39-working//lib/locking/cluster_locking.c 2010-03-26 15:09:05.000000000 +0800 @@ -315,9 +315,6 @@ 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 -Nru LVM2.2.02.39/lib/locking/locking.h LVM2.2.02.39-working//lib/locking/locking.h --- LVM2.2.02.39/lib/locking/locking.h 2008-05-10 03:26:58.000000000 +0800 +++ LVM2.2.02.39-working//lib/locking/locking.h 2010-03-26 15:25:11.000000000 +0800 @@ -83,7 +83,6 @@ /* * 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 */ @@ -116,6 +115,9 @@ lock_vol(cmd, (lv)->lvid.s, flags | LCK_LV_CLUSTERED(lv)) #define unlock_vg(cmd, vol) lock_vol(cmd, vol, LCK_VG_UNLOCK) +#define unlock_release_vg(cmd, vg, vol) do { unlock_vg(cmd, vol); \ + vg_release(vg); \ + } while (0) #define resume_lv(cmd, lv) lock_lv_vol(cmd, lv, LCK_LV_RESUME) #define suspend_lv(cmd, lv) lock_lv_vol(cmd, lv, LCK_LV_SUSPEND | LCK_HOLD) diff -Nru LVM2.2.02.39/lib/log/log.c LVM2.2.02.39-working//lib/log/log.c --- LVM2.2.02.39/lib/log/log.c 2008-06-17 22:14:00.000000000 +0800 +++ LVM2.2.02.39-working//lib/log/log.c 2010-03-26 15:09:05.000000000 +0800 @@ -29,7 +29,6 @@ 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 @@ _test = level; } -void init_partial(int level) -{ - _partial = level; -} - void init_md_filtering(int level) { _md_filtering = level; @@ -254,11 +248,6 @@ return _test; } -int partial_mode() -{ - return _partial; -} - int md_filtering() { return _md_filtering; diff -Nru LVM2.2.02.39/lib/log/log.h LVM2.2.02.39-working//lib/log/log.h --- LVM2.2.02.39/lib/log/log.h 2008-06-07 03:28:34.000000000 +0800 +++ LVM2.2.02.39-working//lib/log/log.h 2010-03-26 15:09:05.000000000 +0800 @@ -64,7 +64,6 @@ 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 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 -Nru LVM2.2.02.39/lib/metadata/metadata.c LVM2.2.02.39-working//lib/metadata/metadata.c --- LVM2.2.02.39/lib/metadata/metadata.c 2008-06-27 23:18:31.000000000 +0800 +++ LVM2.2.02.39-working//lib/metadata/metadata.c 2010-03-26 15:36:03.000000000 +0800 @@ -314,9 +314,9 @@ 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; @@ -466,21 +466,22 @@ int pv_count, char **pv_names) { 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; + struct dm_pool *mem; /* is this vg name already in use ? */ - old_partial = partial_mode(); - init_partial(1); - if (vg_read(cmd, vg_name, NULL, &consistent)) { + if ((vg = vg_read(cmd, vg_name, NULL, &consistent))) { log_err("A volume group called '%s' already exists.", vg_name); - goto bad; + vg_release(vg); + return NULL; } - init_partial(old_partial); + + if (!(mem = dm_pool_create("lvm2 vg_create", VG_MEMPOOL_CHUNK))) + return_NULL; + + if (!(vg = dm_pool_zalloc(mem, sizeof(*vg)))) + goto_bad; if (!id_create(&vg->id)) { log_err("Couldn't create uuid for volume group '%s'.", vg_name); @@ -490,6 +491,7 @@ /* Strip dev_dir if present */ vg_name = strip_dir(vg_name, cmd->dev_dir); + vg->vgmem = mem; vg->cmd = cmd; if (!(vg->name = dm_pool_strdup(mem, vg_name))) @@ -542,7 +544,7 @@ return vg; bad: - dm_pool_free(mem, vg); + dm_pool_destroy(mem); return NULL; } @@ -1171,6 +1173,157 @@ 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; @@ -1268,8 +1421,13 @@ 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; } @@ -1400,23 +1558,28 @@ struct pv_list *pvl; struct volume_group *vg; struct physical_volume *pv; + struct dm_pool *mem; lvmcache_label_scan(cmd, 0); if (!(vginfo = vginfo_from_vgname(orphan_vgname, NULL))) return_NULL; - if (!(vg = dm_pool_zalloc(cmd->mem, sizeof(*vg)))) { + if (!(mem = dm_pool_create("vg_read orphan", VG_MEMPOOL_CHUNK))) + return_NULL; + + if (!(vg = dm_pool_zalloc(mem, sizeof(*vg)))) { log_error("vg allocation failed"); return NULL; } list_init(&vg->pvs); list_init(&vg->lvs); list_init(&vg->tags); + vg->vgmem = mem; vg->cmd = cmd; - if (!(vg->name = dm_pool_strdup(cmd->mem, orphan_vgname))) { + if (!(vg->name = dm_pool_strdup(mem, orphan_vgname))) { log_error("vg name allocation failed"); - return NULL; + goto bad; } /* create format instance with appropriate metadata area */ @@ -1424,17 +1587,16 @@ orphan_vgname, NULL, NULL))) { log_error("Failed to create format instance"); - dm_pool_free(cmd->mem, vg); - return NULL; + goto bad; } list_iterate_items(info, &vginfo->infos) { if (!(pv = _pv_read(cmd, dev_name(info->dev), NULL, NULL, 1))) { continue; } - if (!(pvl = dm_pool_zalloc(cmd->mem, sizeof(*pvl)))) { + if (!(pvl = dm_pool_zalloc(mem, sizeof(*pvl)))) { log_error("pv_list allocation failed"); - return NULL; + goto bad; } pvl->pv = pv; list_add(&vg->pvs, &pvl->list); @@ -1442,6 +1604,9 @@ } return vg; +bad: + dm_pool_destroy(mem); + return NULL; } static int _update_pv_list(struct list *all_pvs, struct volume_group *vg) @@ -1468,9 +1633,20 @@ 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). @@ -1509,6 +1685,11 @@ } 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; } @@ -1604,7 +1785,8 @@ } } - 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); @@ -1613,6 +1795,8 @@ 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); @@ -1695,15 +1879,6 @@ 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 " @@ -1742,6 +1917,12 @@ } } + 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); @@ -1780,6 +1961,18 @@ return vg; } +void vg_release(struct volume_group *vg) +{ + if (!vg || !vg->vgmem) + return; + + if (vg->vgmem == vg->cmd->mem) + log_error("Internal error: global memory pool used for VG %s", + vg->name); + + dm_pool_destroy(vg->vgmem); +} + /* This is only called by lv_from_lvid, which is only called from * activate.c so we know the appropriate VG lock is already held and * the vg_read is therefore safe. @@ -1801,11 +1994,10 @@ 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; } @@ -1833,6 +2025,7 @@ 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); @@ -1966,7 +2159,6 @@ struct list *vgids; struct volume_group *vg; int consistent = 0; - int old_partial; int old_pvmove; lvmcache_label_scan(cmd, 0); @@ -1988,9 +2180,7 @@ /* 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; @@ -2015,7 +2205,6 @@ list_add(results, pvh); } init_pvmove(old_pvmove); - init_partial(old_partial); if (pvslist) *pvslist = results; diff -Nru LVM2.2.02.39/lib/metadata/metadata-exported.h LVM2.2.02.39-working//lib/metadata/metadata-exported.h --- LVM2.2.02.39/lib/metadata/metadata-exported.h 2008-06-25 04:10:31.000000000 +0800 +++ LVM2.2.02.39-working//lib/metadata/metadata-exported.h 2010-03-26 15:25:11.000000000 +0800 @@ -71,6 +71,13 @@ //#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 */ @@ -181,6 +188,7 @@ struct volume_group { struct cmd_context *cmd; + struct dm_pool *vgmem; struct format_instance *fid; uint32_t seqno; /* Metadata sequence number */ @@ -380,6 +388,7 @@ int vg_split_mdas(struct cmd_context *cmd, struct volume_group *vg_from, struct volume_group *vg_to); +void vg_release(struct volume_group *vg); /* Manipulate LVs */ struct logical_volume *lv_create_empty(const char *name, union lvid *lvid, @@ -559,6 +568,7 @@ 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 -Nru LVM2.2.02.39/lib/metadata/metadata.h LVM2.2.02.39-working//lib/metadata/metadata.h --- LVM2.2.02.39/lib/metadata/metadata.h 2008-04-02 06:40:13.000000000 +0800 +++ LVM2.2.02.39-working//lib/metadata/metadata.h 2010-03-26 15:26:51.000000000 +0800 @@ -35,6 +35,7 @@ //#define PV_MIN_SIZE ( 512L * 1024L >> SECTOR_SHIFT) /* 512 KB in sectors */ //#define MAX_RESTRICTED_LVS 255 /* Used by FMT_RESTRICTED_LVIDS */ #define MIRROR_LOG_SIZE 1 /* Extents */ +#define VG_MEMPOOL_CHUNK 10240 /* in bytes, hint only */ /* Various flags */ /* Note that the bits no longer necessarily correspond to LVM1 disk format */ @@ -61,6 +62,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 -Nru LVM2.2.02.39/lib/report/report.c LVM2.2.02.39-working//lib/report/report.c --- LVM2.2.02.39/lib/report/report.c 2008-06-26 00:52:27.000000000 +0800 +++ LVM2.2.02.39-working//lib/report/report.c 2010-03-26 15:09:05.000000000 +0800 @@ -439,7 +439,7 @@ else repstr[2] = '-'; - if (vg->status & PARTIAL_VG) + if (vg_missing_pv_count(vg)) repstr[3] = 'p'; else repstr[3] = '-'; diff -Nru LVM2.2.02.39/man/lvconvert.8 LVM2.2.02.39-working//man/lvconvert.8 --- LVM2.2.02.39/man/lvconvert.8 2007-12-22 20:13:29.000000000 +0800 +++ LVM2.2.02.39-working//man/lvconvert.8 2010-03-26 15:44:44.000000000 +0800 @@ -29,7 +29,7 @@ .SH OPTIONS See \fBlvm\fP for common options. .br -Exactly one of \-\-mirrors or \-\-snapshot arguments required. +Exactly one of \-\-mirrors, \-\-repair or \-\-snapshot arguments required. .br .TP .I \-m, \-\-mirrors Mirrors @@ -61,6 +61,12 @@ Report progress as a percentage at regular intervals. .br .TP +.I \-\-repair +Repair a mirror that has suffered a disk failure. The mirror will be brought +back into a consistent state, and if possible, the original number of +mirrors will be restored. +.br +.TP .I \-s, \-\-snapshot Create a snapshot from existing logical volume using another existing logical volume as its origin. diff -Nru LVM2.2.02.39/man/lvm.conf.5 LVM2.2.02.39-working//man/lvm.conf.5 --- LVM2.2.02.39/man/lvm.conf.5 2008-04-11 02:50:02.000000000 +0800 +++ LVM2.2.02.39-working//man/lvm.conf.5 2010-03-26 15:09:05.000000000 +0800 @@ -300,12 +300,16 @@ .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 -Nru LVM2.2.02.39/man/vgreduce.8 LVM2.2.02.39-working//man/vgreduce.8 --- LVM2.2.02.39/man/vgreduce.8 2003-01-18 05:04:26.000000000 +0800 +++ LVM2.2.02.39-working//man/vgreduce.8 2010-03-26 15:09:05.000000000 +0800 @@ -18,11 +18,13 @@ 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 -Nru LVM2.2.02.39/test/t-lvconvert-repair.sh LVM2.2.02.39-working//test/t-lvconvert-repair.sh --- LVM2.2.02.39/test/t-lvconvert-repair.sh 1970-01-01 08:00:00.000000000 +0800 +++ LVM2.2.02.39-working//test/t-lvconvert-repair.sh 2010-03-26 15:40:01.000000000 +0800 @@ -0,0 +1,46 @@ +#!/bin/bash +# Copyright (C) 2008 Red Hat, Inc. All rights reserved. +# +# 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 General Public License v.2. +# +# You should have received a copy of the GNU 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 + +. ./test-utils.sh + +prepare_vg 4 + +vgreduce $vg $dev4 +lvcreate -m 1 -L 1 -n mirror $vg + +lvchange -a n $vg/mirror +vgextend $vg $dev4 +disable_dev $dev1 +lvchange --partial -a y $vg/mirror + +not vgreduce -v --removemissing $vg +lvconvert -i 1 --repair $vg/mirror +vgreduce --removemissing $vg + +enable_dev $dev1 +vgextend $vg $dev1 +disable_dev $dev2 +lvconvert -i 1 --repair $vg/mirror +vgreduce --removemissing $vg + +enable_dev $dev2 +vgextend $vg $dev2 +disable_dev $dev3 +lvconvert -i 1 --repair $vg/mirror +vgreduce --removemissing $vg + +enable_dev $dev3 +vgextend $vg $dev3 +lvcreate -m 2 -l 1 -n mirror2 $vg $dev1 $dev2 $dev3 $dev4 +vgchange -a n $vg +pvremove -ff -y $dev4 +echo 'y' | not lvconvert -i 1 --repair $vg/mirror2 +vgs diff -Nru LVM2.2.02.39/tools/args.h LVM2.2.02.39-working//tools/args.h --- LVM2.2.02.39/tools/args.h 2008-06-25 06:48:53.000000000 +0800 +++ LVM2.2.02.39-working//tools/args.h 2010-03-26 15:40:01.000000000 +0800 @@ -49,6 +49,7 @@ arg(resync_ARG, '\0', "resync", NULL, 0) arg(corelog_ARG, '\0', "corelog", NULL, 0) arg(mirrorlog_ARG, '\0', "mirrorlog", string_arg, 0) +arg(repair_ARG, '\0', "repair", NULL, 0) arg(monitor_ARG, '\0', "monitor", yes_no_arg, 0) arg(config_ARG, '\0', "config", string_arg, 0) arg(trustcache_ARG, '\0', "trustcache", NULL, 0) diff -Nru LVM2.2.02.39/tools/commands.h LVM2.2.02.39-working//tools/commands.h --- LVM2.2.02.39/tools/commands.h 2008-06-25 06:48:53.000000000 +0800 +++ LVM2.2.02.39-working//tools/commands.h 2010-03-26 15:40:01.000000000 +0800 @@ -94,6 +94,7 @@ 0, "lvconvert " "[-m|--mirrors Mirrors [{--mirrorlog {disk|core}|--corelog}]]\n" + "\t[--repair]\n" "\t[-R|--regionsize MirrorLogRegionSize]\n" "\t[--alloc AllocationPolicy]\n" "\t[-b|--background]\n" @@ -115,7 +116,8 @@ "\tOriginalLogicalVolume[Path] SnapshotLogicalVolume[Path]\n", alloc_ARG, background_ARG, chunksize_ARG, corelog_ARG, interval_ARG, - mirrorlog_ARG, mirrors_ARG, regionsize_ARG, snapshot_ARG, test_ARG, zero_ARG) + mirrorlog_ARG, mirrors_ARG, regionsize_ARG, repair_ARG, snapshot_ARG, + test_ARG, zero_ARG) xx(lvcreate, "Create a logical volume", @@ -850,13 +852,15 @@ "\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 -Nru LVM2.2.02.39/tools/lvconvert.c LVM2.2.02.39-working//tools/lvconvert.c --- LVM2.2.02.39/tools/lvconvert.c 2008-06-27 07:05:11.000000000 +0800 +++ LVM2.2.02.39-working//tools/lvconvert.c 2010-03-26 15:47:27.000000000 +0800 @@ -364,6 +364,60 @@ return 1; } +static int _area_missing(struct lv_segment *lvseg, int s) +{ + if (seg_type(lvseg, s) == AREA_LV) { + if (seg_lv(lvseg, s)->status & PARTIAL_LV) + return 1; + } else if (seg_type(lvseg, s) == AREA_PV) { + if (seg_pv(lvseg, s)->status & MISSING_PV) + return 1; + } + return 0; +} + +/* FIXME we want to handle mirror stacks here... */ +static int _count_failed_mirrors(struct logical_volume *lv) +{ + struct lv_segment *lvseg; + int ret = 0; + int s; + list_iterate_items(lvseg, &lv->segments) { + if (!seg_is_mirrored(lvseg)) + return -1; + for(s = 0; s < lvseg->area_count; ++s) { + if (_area_missing(lvseg, s)) + ++ ret; + } + } + return ret; +} + +static struct list *_failed_pv_list(struct volume_group *vg) +{ + struct list *r; + struct pv_list *pvl, *new_pvl; + + if (!(r = dm_pool_alloc(vg->vgmem, sizeof(*r)))) { + log_error("Allocation of list failed"); + return_0; + } + + list_init(r); + list_iterate_items(pvl, &vg->pvs) { + if (!(pvl->pv->status & MISSING_PV)) + continue; + + if (!(new_pvl = dm_pool_alloc(vg->vgmem, sizeof(*new_pvl)))) { + log_error("Unable to allocate physical volume list."); + return_0; + } + new_pvl->pv = pvl->pv; + list_add(r, &new_pvl->list); + } + return r; +} + /* walk down the stacked mirror LV to the original mirror LV */ static struct logical_volume *_original_lv(struct logical_volume *lv) { @@ -383,17 +437,26 @@ const char *mirrorlog; unsigned corelog = 0; struct logical_volume *original_lv; + struct logical_volume *log_lv; + int failed_mirrors = 0, failed_log = 0; + struct list* old_pvh, *remove_pvs = NULL; seg = first_seg(lv); existing_mirrors = lv_mirror_count(lv); /* If called with no argument, try collapsing the resync layers */ if (!arg_count(cmd, mirrors_ARG) && !arg_count(cmd, mirrorlog_ARG) && - !arg_count(cmd, corelog_ARG) && !arg_count(cmd, regionsize_ARG)) { + !arg_count(cmd, corelog_ARG) && !arg_count(cmd, regionsize_ARG) && + !arg_count(cmd, repair_ARG)) { lp->need_polling = 1; return 1; } + if (arg_count(cmd, mirrors_ARG) && arg_count(cmd, repair_ARG)) { + log_error("You can only use one of -m, --repair."); + return 0; + } + /* * Adjust required number of mirrors * @@ -411,38 +474,59 @@ else lp->mirrors += 1; - /* - * Did the user try to subtract more legs than available? - */ - if (lp->mirrors < 1) { - log_error("Logical volume %s only has %" PRIu32 " mirrors.", - lv->name, existing_mirrors); - return 0; - } - - /* - * Adjust log type - */ - if (arg_count(cmd, corelog_ARG)) - corelog = 1; - - mirrorlog = arg_str_value(cmd, mirrorlog_ARG, - corelog ? "core" : DEFAULT_MIRRORLOG); - if (!strcmp("disk", mirrorlog)) { - if (corelog) { - log_error("--mirrorlog disk and --corelog " - "are incompatible"); + if (arg_count(cmd,repair_ARG)) { + cmd->handles_missing_pvs = 1; + cmd->partial_activate = 1; + lp->need_polling = 0; + if (!(lv->status & PARTIAL_LV)) { + log_error("The mirror is consistent, nothing to repair."); + return 0; + } + if ((failed_mirrors = _count_failed_mirrors(lv)) < 0) + return_0; + lp->mirrors -= failed_mirrors; + log_error("Mirror status: %d/%d legs failed.", + failed_mirrors, existing_mirrors); + old_pvh = lp->pvh; + if (!(lp->pvh = _failed_pv_list(lv->vg))) + return_0; + log_lv=first_seg(lv)->log_lv; + if (!log_lv || log_lv->status & PARTIAL_LV) + failed_log = corelog = 1; + } else { + /* + * Did the user try to subtract more legs than available? + */ + if (lp->mirrors < 1) { + log_error("Logical volume %s only has %" PRIu32 " mirrors.", + lv->name, existing_mirrors); + return 0; + } + + /* + * Adjust log type + */ + if (arg_count(cmd, corelog_ARG)) + corelog = 1; + + mirrorlog = arg_str_value(cmd, mirrorlog_ARG, + corelog ? "core" : DEFAULT_MIRRORLOG); + if (!strcmp("disk", mirrorlog)) { + if (corelog) { + log_error("--mirrorlog disk and --corelog " + "are incompatible"); + return 0; + } + corelog = 0; + } else if (!strcmp("core", mirrorlog)) + corelog = 1; + else { + log_error("Unknown mirrorlog type: %s", mirrorlog); return 0; } - corelog = 0; - } else if (!strcmp("core", mirrorlog)) - corelog = 1; - else { - log_error("Unknown mirrorlog type: %s", mirrorlog); - return 0; - } - log_verbose("Setting logging type to %s", mirrorlog); + log_verbose("Setting logging type to %s", mirrorlog); + } /* * Region size must not change on existing mirrors @@ -455,6 +539,18 @@ } /* + * FIXME This check used to precede mirror->mirror conversion + * but didn't affect mirror->linear or linear->mirror. I do + * not understand what is its intention, in fact. + */ + if (list_size(&lv->segments) != 1) { + log_error("Logical volume %s has multiple " + "mirror segments.", lv->name); + return 0; + } + + restart: + /* * Converting from mirror to linear */ if ((lp->mirrors == 1)) { @@ -463,17 +559,24 @@ lv->name); return 1; } - - if (!lv_remove_mirrors(cmd, lv, existing_mirrors - 1, 1, - lp->pv_count ? lp->pvh : NULL, 0)) - return_0; - goto commit_changes; } /* - * Converting from linear to mirror + * Downconversion. */ - if (!(lv->status & MIRRORED)) { + if (lp->mirrors < existing_mirrors) { + /* Reduce number of mirrors */ + if (arg_count(cmd, repair_ARG) || lp->pv_count) + remove_pvs = lp->pvh; + if (!lv_remove_mirrors(cmd, lv, existing_mirrors - lp->mirrors, + (corelog || lp->mirrors == 1) ? 1U : 0U, + remove_pvs, 0)) + return_0; + } else if (!(lv->status & MIRRORED)) { + /* + * Converting from linear to mirror + */ + /* FIXME Share code with lvcreate */ /* FIXME Why is this restriction here? Fix it! */ @@ -484,6 +587,11 @@ } } + /* + * FIXME should we give not only lp->pvh, but also all PVs + * currently taken by the mirror? Would make more sense from + * user perspective. + */ if (!lv_add_mirrors(cmd, lv, lp->mirrors - 1, 1, adjusted_mirror_region_size( lv->vg->extent_size, @@ -494,46 +602,7 @@ return_0; if (lp->wait_completion) lp->need_polling = 1; - goto commit_changes; - } - - /* - * Converting from mirror to mirror with different leg count, - * or different log type. - */ - if (list_size(&lv->segments) != 1) { - log_error("Logical volume %s has multiple " - "mirror segments.", lv->name); - return 0; - } - - if (lp->mirrors == existing_mirrors) { - /* - * Convert Mirror log type - */ - original_lv = _original_lv(lv); - if (!first_seg(original_lv)->log_lv && !corelog) { - if (!add_mirror_log(cmd, original_lv, 1, - adjusted_mirror_region_size( - lv->vg->extent_size, - lv->le_count, - lp->region_size), - lp->pvh, lp->alloc)) - return_0; - } else if (first_seg(original_lv)->log_lv && corelog) { - if (!remove_mirror_log(cmd, original_lv, - lp->pv_count ? lp->pvh : NULL)) - return_0; - } else { - /* No change */ - log_error("Logical volume %s already has %" - PRIu32 " mirror(s).", lv->name, - lp->mirrors - 1); - if (lv->status & CONVERTING) - lp->need_polling = 1; - return 1; - } - } else if (lp->mirrors > existing_mirrors) { + } else if (lp->mirrors > existing_mirrors || failed_mirrors) { if (lv->status & MIRROR_NOTSYNCED) { log_error("Not adding mirror to mirrored LV " "without initial resync"); @@ -575,15 +644,36 @@ return_0; lv->status |= CONVERTING; lp->need_polling = 1; - } else { - /* Reduce number of mirrors */ - if (!lv_remove_mirrors(cmd, lv, existing_mirrors - lp->mirrors, - corelog ? 1U : 0U, - lp->pv_count ? lp->pvh : NULL, 0)) - return_0; } -commit_changes: + if (lp->mirrors == existing_mirrors) { + /* + * Convert Mirror log type + */ + original_lv = _original_lv(lv); + if (!first_seg(original_lv)->log_lv && !corelog) { + if (!add_mirror_log(cmd, original_lv, 1, + adjusted_mirror_region_size( + lv->vg->extent_size, + lv->le_count, + lp->region_size), + lp->pvh, lp->alloc)) + return_0; + } else if (first_seg(original_lv)->log_lv && corelog) { + if (!remove_mirror_log(cmd, original_lv, + lp->pv_count ? lp->pvh : NULL)) + return_0; + } else { + /* No change */ + log_error("Logical volume %s already has %" + PRIu32 " mirror(s).", lv->name, + lp->mirrors - 1); + if (lv->status & CONVERTING) + lp->need_polling = 1; + return 1; + } + } + log_very_verbose("Updating logical volume \"%s\" on disk(s)", lv->name); if (!vg_write(lv->vg)) @@ -609,6 +699,17 @@ return 0; } + if (failed_log || failed_mirrors) { + lp->pvh = old_pvh; + if (failed_log) + failed_log = corelog = 0; + lp->mirrors += failed_mirrors; + failed_mirrors = 0; + existing_mirrors = lv_mirror_count(lv); + /* Now replace missing devices. */ + goto restart; + } + if (!lp->need_polling) log_print("Logical volume %s converted.", lv->name); diff -Nru LVM2.2.02.39/tools/lvmcmdline.c LVM2.2.02.39-working//tools/lvmcmdline.c --- LVM2.2.02.39/tools/lvmcmdline.c 2010-03-26 16:20:02.000000000 +0800 +++ LVM2.2.02.39-working//tools/lvmcmdline.c 2010-03-26 15:09:05.000000000 +0800 @@ -710,13 +710,13 @@ 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 @@ 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 -Nru LVM2.2.02.39/tools/lvremove.c LVM2.2.02.39-working//tools/lvremove.c --- LVM2.2.02.39/tools/lvremove.c 2008-01-30 22:00:02.000000000 +0800 +++ LVM2.2.02.39-working//tools/lvremove.c 2010-03-26 15:09:05.000000000 +0800 @@ -31,6 +31,8 @@ return EINVALID_CMD_LINE; } + cmd->handles_missing_pvs = 1; + return process_each_lv(cmd, argc, argv, LCK_VG_WRITE, NULL, &lvremove_single); } diff -Nru LVM2.2.02.39/tools/pvchange.c LVM2.2.02.39-working//tools/pvchange.c --- LVM2.2.02.39/tools/pvchange.c 2010-03-26 16:20:02.000000000 +0800 +++ LVM2.2.02.39-working//tools/pvchange.c 2010-03-26 15:20:48.000000000 +0800 @@ -57,20 +57,14 @@ struct physical_volume *pv; char *pvuuid; char *pvuuid_link; - int old_partial; pvuuid_link = malloc(70); if (pvuuid_link == NULL) return NULL; - old_partial = partial_mode(); - - init_partial(1); if (!(pv = pv_read(cmd, name, NULL, NULL, 1))) { free(pvuuid_link); - init_partial(old_partial); return NULL; } - init_partial(old_partial); pvuuid = malloc(sizeof(char)*40); if (pvuuid == NULL) { diff -Nru LVM2.2.02.39/tools/pvcreate.c LVM2.2.02.39-working//tools/pvcreate.c --- LVM2.2.02.39/tools/pvcreate.c 2010-03-26 16:20:02.000000000 +0800 +++ LVM2.2.02.39-working//tools/pvcreate.c 2010-03-26 15:22:20.000000000 +0800 @@ -66,20 +66,14 @@ struct physical_volume *pv; char *pvuuid; char *pvuuid_link; - int old_partial; pvuuid_link = malloc(70); if (pvuuid_link == NULL) return NULL; - old_partial = partial_mode(); - - init_partial(1); if (!(pv = pv_read(cmd, name, NULL, NULL, 1))) { free(pvuuid_link); - init_partial(old_partial); return NULL; } - init_partial(old_partial); pvuuid = malloc(sizeof(char)*40); if (pvuuid == NULL) { @@ -130,8 +124,6 @@ } /* Is there a pv here already? */ - /* FIXME Use partial mode here? */ - init_partial(1); pv = pv_read(cmd, name, NULL, NULL, 0); /* @@ -145,7 +137,6 @@ return_0; pv = pv_read(cmd, name, NULL, NULL, 0); } - init_partial(0); /* Allow partial & exported VGs to be destroyed. */ /* We must have -ff to overwrite a non orphan */ @@ -260,13 +251,11 @@ if (arg_count(cmd, restorefile_ARG)) { restorefile = arg_str_value(cmd, restorefile_ARG, ""); /* The uuid won't already exist */ - init_partial(1); if (!(vg = backup_read_vg(cmd, NULL, restorefile))) { log_error("Unable to read volume group from %s", restorefile); return ECMD_FAILED; } - init_partial(0); if (!(existing_pv = find_pv_in_vg_by_uuid(vg, idp))) { log_error("Can't find uuid %s in backup file %s", uuid, restorefile); diff -Nru LVM2.2.02.39/tools/pvremove.c LVM2.2.02.39-working//tools/pvremove.c --- LVM2.2.02.39/tools/pvremove.c 2010-03-26 16:20:02.000000000 +0800 +++ LVM2.2.02.39-working//tools/pvremove.c 2010-03-26 15:22:30.000000000 +0800 @@ -24,11 +24,9 @@ char *pvuuid; char pvuuid_link[70]; - init_partial(1); if (!(pv = pv_read(cmd, name, NULL, NULL, 1))) { return NULL; } - init_partial(0); pvuuid = malloc(sizeof(char)*40); if (pvuuid == NULL) { diff -Nru LVM2.2.02.39/tools/vgcfgbackup.c LVM2.2.02.39-working//tools/vgcfgbackup.c --- LVM2.2.02.39/tools/vgcfgbackup.c 2008-01-30 22:00:02.000000000 +0800 +++ LVM2.2.02.39-working//tools/vgcfgbackup.c 2010-03-26 15:09:05.000000000 +0800 @@ -96,8 +96,7 @@ 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 -Nru LVM2.2.02.39/tools/vgreduce.c LVM2.2.02.39-working//tools/vgreduce.c --- LVM2.2.02.39/tools/vgreduce.c 2008-04-08 20:49:21.000000000 +0800 +++ LVM2.2.02.39-working//tools/vgreduce.c 2010-03-26 15:09:05.000000000 +0800 @@ -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 @@ 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 @@ 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; @@ -183,20 +212,8 @@ 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. */ @@ -209,14 +226,11 @@ 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)) { @@ -441,26 +455,26 @@ 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; } @@ -471,7 +485,7 @@ 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; } @@ -492,8 +506,8 @@ 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; @@ -504,16 +518,15 @@ 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); @@ -524,16 +537,17 @@ 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", @@ -544,7 +558,9 @@ 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)) {