Add lvs origin_size field.
Fix linux configure --enable-debug to exclude -O2.
Still a few rough edges, but hopefully usable now:
lvcreate -s vg1 -L 100M --virtualoriginsize 1T
Version 2.02.46 -
================================
+ Add sparse devices: lvcreate -s --virtualoriginsize (hidden zero origin).
+ Add lvs origin_size field.
+ Fix linux configure --enable-debug to exclude -O2.
Implement lvconvert --repair, for repairing partially failed mirrors.
Fix vgreduce --removemissing failure exit code.
Fix remote metadata backup for clvmd.
{CONVERTING, NULL, 0},
{PARTIAL_LV, NULL, 0},
{POSTORDER_FLAG, NULL, 0},
+ {VIRTUAL_ORIGIN, NULL, 0},
{0, NULL, 0}
};
//#define POSTORDER_FLAG 0x02000000U /* Not real flags, reserved for
//#define POSTORDER_OPEN_FLAG 0x04000000U temporary use inside vg_read_internal. */
+//#define VIRTUAL_ORIGIN 0x08000000U /* LV - internal use only */
#define LVM_READ 0x00000100U /* LV VG */
#define LVM_WRITE 0x00000200U /* LV VG */
* Useful functions for managing snapshots.
*/
int lv_is_origin(const struct logical_volume *lv);
+int lv_is_virtual_origin(const struct logical_volume *lv);
int lv_is_cow(const struct logical_volume *lv);
int lv_is_visible(const struct logical_volume *lv);
#define POSTORDER_FLAG 0x02000000U /* Not real flags, reserved for */
#define POSTORDER_OPEN_FLAG 0x04000000U /* temporary use inside vg_read_internal. */
+#define VIRTUAL_ORIGIN 0x08000000U /* LV - internal use only */
//#define LVM_READ 0x00000100U /* LV VG */
//#define LVM_WRITE 0x00000200U /* LV VG */
return (lv->status & VISIBLE_LV) || lv_is_cow(lv) ? 1 : 0;
}
+int lv_is_virtual_origin(const struct logical_volume *lv)
+{
+ return (lv->status & VIRTUAL_ORIGIN) ? 1 : 0;
+}
+
+
/* Given a cow LV, return the snapshot lv_segment that uses it */
struct lv_segment *find_cow(const struct logical_volume *lv)
{
cow->status &= ~VISIBLE_LV;
+ /* FIXME Assumes an invisible origin belongs to a sparse device */
+ if (!lv_is_visible(origin))
+ origin->status |= VIRTUAL_ORIGIN;
+
dm_list_add(&origin->snapshot_segs, &seg->origin_list);
return 1;
*/
/* *INDENT-OFF* */
-FIELD(LVS, lv, STR, "LV UUID", lvid.id[1], 38, uuid, "lv_uuid", "Unique identifier")
+FIELD(LVS, lv, STR, "LV UUID", lvid.id[1], 38, uuid, "lv_uuid", "Unique identifier.")
FIELD(LVS, lv, STR, "LV", lvid, 4, lvname, "lv_name", "Name. LVs created for internal use are enclosed in brackets.")
FIELD(LVS, lv, STR, "Attr", lvid, 4, lvstatus, "lv_attr", "Various attributes - see man page.")
FIELD(LVS, lv, NUM, "Maj", major, 3, int32, "lv_major", "Persistent major number or -1 if not persistent.")
FIELD(LVS, lv, NUM, "KRahead", lvid, 7, lvkreadahead, "lv_kernel_read_ahead", "Currently-in-use read ahead setting in current units.")
FIELD(LVS, lv, NUM, "LSize", size, 5, size64, "lv_size", "Size of LV in current units.")
FIELD(LVS, lv, NUM, "#Seg", lvid, 4, lvsegcount, "seg_count", "Number of segments in LV.")
-FIELD(LVS, lv, STR, "Origin", lvid, 6, origin, "origin", "For snapshots, the origin device of this LV")
+FIELD(LVS, lv, STR, "Origin", lvid, 6, origin, "origin", "For snapshots, the origin device of this LV.")
+FIELD(LVS, lv, NUM, "OSize", lvid, 5, originsize, "origin_size", "For snapshots, the size of the origin device of this LV.")
FIELD(LVS, lv, NUM, "Snap%", lvid, 6, snpercent, "snap_percent", "For snapshots, the percentage full if LV is active.")
FIELD(LVS, lv, NUM, "Copy%", lvid, 6, copypercent, "copy_percent", "For mirrors and pvmove, current percentage in-sync.")
-FIELD(LVS, lv, STR, "Move", lvid, 4, movepv, "move_pv", "For pvmove, Source PV of temporary LV created by pvmove")
-FIELD(LVS, lv, STR, "Convert", lvid, 7, convertlv, "convert_lv", "For lvconvert, Name of temporary LV created by lvconvert")
+FIELD(LVS, lv, STR, "Move", lvid, 4, movepv, "move_pv", "For pvmove, Source PV of temporary LV created by pvmove.")
+FIELD(LVS, lv, STR, "Convert", lvid, 7, convertlv, "convert_lv", "For lvconvert, Name of temporary LV created by lvconvert.")
FIELD(LVS, lv, STR, "LV Tags", tags, 7, tags, "lv_tags", "Tags, if any.")
FIELD(LVS, lv, STR, "Log", lvid, 3, loglv, "mirror_log", "For mirrors, the LV holding the synchronisation log.")
FIELD(LVS, lv, STR, "Modules", lvid, 7, modules, "modules", "Kernel device-mapper modules required for this LV.")
FIELD(VGS, vg, NUM, "VMdaFree", cmd, 9, vgmdafree, "vg_mda_free", "Free metadata area space for this VG in current units.")
FIELD(VGS, vg, NUM, "VMdaSize", cmd, 9, vgmdasize, "vg_mda_size", "Size of smallest metadata area for this VG in current units.")
-FIELD(SEGS, seg, STR, "Type", list, 4, segtype, "segtype", "Type of LV segment")
+FIELD(SEGS, seg, STR, "Type", list, 4, segtype, "segtype", "Type of LV segment.")
FIELD(SEGS, seg, NUM, "#Str", area_count, 4, uint32, "stripes", "Number of stripes or mirror legs.")
FIELD(SEGS, seg, NUM, "Stripe", stripe_size, 6, size32, "stripesize", "For stripes, amount of data placed on one device before switching to the next.")
FIELD(SEGS, seg, NUM, "Stripe", stripe_size, 6, size32, "stripe_size", "For stripes, amount of data placed on one device before switching to the next.")
return 1;
}
-static int _origin_disp(struct dm_report *rh, struct dm_pool *mem __attribute((unused)),
- struct dm_report_field *field,
- const void *data, void *private __attribute((unused)))
-{
- const struct logical_volume *lv = (const struct logical_volume *) data;
-
- if (lv_is_cow(lv))
- return dm_report_field_string(rh, field,
- (const char **) &origin_from_cow(lv)->name);
-
- dm_report_field_set_value(field, "", NULL);
- return 1;
-}
-
static int _loglv_disp(struct dm_report *rh, struct dm_pool *mem __attribute((unused)),
struct dm_report_field *field,
const void *data, void *private __attribute((unused)))
return 1;
}
+static int _origin_disp(struct dm_report *rh, struct dm_pool *mem,
+ struct dm_report_field *field,
+ const void *data, void *private)
+{
+ const struct logical_volume *lv = (const struct logical_volume *) data;
+
+ if (lv_is_cow(lv))
+ return _lvname_disp(rh, mem, field, origin_from_cow(lv), private);
+
+ dm_report_field_set_value(field, "", NULL);
+ return 1;
+}
+
static int _movepv_disp(struct dm_report *rh, struct dm_pool *mem __attribute((unused)),
struct dm_report_field *field,
const void *data, void *private __attribute((unused)))
if (lv_is_cow(seg->lv))
size = (uint64_t) find_cow(seg->lv)->chunk_size;
else
- size = 0;
+ size = UINT64_C(0);
+
+ return _size64_disp(rh, mem, field, &size, private);
+}
+
+static int _originsize_disp(struct dm_report *rh, struct dm_pool *mem,
+ struct dm_report_field *field,
+ const void *data, void *private)
+{
+ const struct logical_volume *lv = (const struct logical_volume *) data;
+ uint64_t size;
+
+ if (lv_is_cow(lv))
+ size = (uint64_t) find_cow(lv)->len * lv->vg->extent_size;
+ else if (lv_is_origin(lv))
+ size = lv->size;
+ else
+ size = UINT64_C(0);
return _size64_disp(rh, mem, field, &size, private);
}
{\-l|\-\-extents LogicalExtentsNumber[%{VG|FREE}] |
\-L|\-\-size LogicalVolumeSize[kKmMgGtT]}
[\-c|\-\-chunksize ChunkSize]
-\-s|\-\-snapshot \-n|\-\-name SnapshotLogicalVolumeName OriginalLogicalVolumePath
+\-n|\-\-name SnapshotLogicalVolumeName
+\-s|\-\-snapshot
+[OriginalLogicalVolumePath | VolumeGroupName \-\-virtualoriginsize VirtualOriginSize]
.SH DESCRIPTION
lvcreate creates a new logical volume in a volume group ( see
.B vgcreate(8), vgchange(8)
as well. Run
.B lvdisplay(8)
on the snapshot in order to check how much data is allocated to it.
+Note that a small amount of the space you allocate to the snapshot is
+used to track the locations of the chunks of data, so you should
+allocate slightly more space than you actually need and monitor the
+rate at which the snapshot data is growing so you can avoid running out
+of space.
+.TP
+.I \-\-virtualoriginsize VirtualOriginSize
+In conjunction with \-\-snapshot, create a sparse device of the given size
+(in MB by default). Anything written to the device will be returned when
+reading from it. Reading from other areas of the device will return
+blocks of zeros. It is implemented by creating a hidden virtual device of the
+requested size using the zero target. A suffix of _vorigin is used for
+this device.
.TP
.I \-Z, \-\-zero y|n
Controls zeroing of the first KB of data in the new logical volume.
arbitrary directory in order to access the contents of the filesystem to run
a backup while the original filesystem continues to get updated.
+"lvcreate --virtualoriginsize 1T --size 100M --snapshot --name sparse vg1"
+.br
+creates a sparse device named /dev/vg1/sparse of size 1TB with space for just
+under 100MB of actual data on it.
+
.SH SEE ALSO
.BR lvm (8),
.BR vgcreate (8),
to the default selection of columns instead of replacing it. Column names are:
lv_uuid, lv_name, lv_attr, lv_major, lv_minor, lv_kernel_major, lv_kernel_minor,
lv_size, seg_count, origin, snap_percent,
-copy_percent, move_pv, lv_tags,
+copy_percent, move_pv, lv_tags, origin_size,
segtype, stripes,
stripesize, chunksize, seg_start, seg_size, seg_tags, devices,
regionsize, mirror_log, modules.
arg(unquoted_ARG, '\0', "unquoted", NULL, 0)
arg(rows_ARG, '\0', "rows", NULL, 0)
arg(dataalignment_ARG, '\0', "dataalignment", size_kb_arg, 0)
+arg(virtualoriginsize_ARG, '\0', "virtualoriginsize", size_mb_arg, 0)
/* Allow some variations */
arg(resizable_ARG, '\0', "resizable", yes_no_arg, 0)
"\t[-t|--test]\n"
"\t[-v|--verbose]\n"
"\t[--version]\n"
- "\tOriginalLogicalVolume[Path] [PhysicalVolumePath...]\n\n",
+ "\t[OriginalLogicalVolume[Path] |\n"
+ "\t VolumeGroupName[Path] --virtualoriginsize VirtualOriginSize]]\n"
+ "\t[PhysicalVolumePath...]\n\n",
addtag_ARG, alloc_ARG, autobackup_ARG, chunksize_ARG, contiguous_ARG,
corelog_ARG, extents_ARG, major_ARG, minor_ARG, mirrorlog_ARG, mirrors_ARG,
name_ARG, nosync_ARG, permission_ARG, persistent_ARG, readahead_ARG,
regionsize_ARG, size_ARG, snapshot_ARG, stripes_ARG, stripesize_ARG,
- test_ARG, type_ARG, zero_ARG)
+ test_ARG, type_ARG, virtualoriginsize_ARG, zero_ARG)
xx(lvdisplay,
"Display information about a logical volume",
return ECMD_FAILED;
}
- if (lv_is_cow(lv)) {
+ if (lv_is_cow(lv) && !lv_is_virtual_origin(origin_from_cow(lv)) &&
+ arg_count(cmd, available_ARG)) {
log_error("Can't change snapshot logical volume \"%s\"",
lv->name);
return ECMD_FAILED;
/* size */
uint32_t extents;
uint64_t size;
+ uint32_t voriginextents;
+ uint64_t voriginsize;
percent_t percent;
uint32_t permission;
if (arg_count(cmd, name_ARG))
lp->lv_name = arg_value(cmd, name_ARG);
- if (lp->snapshot) {
+ if (lp->snapshot && !arg_count(cmd, virtualoriginsize_ARG)) {
if (!argc) {
log_err("Please specify a logical volume to act as "
"the snapshot origin.");
lp->percent = PERCENT_NONE;
}
+ /* Size returned in kilobyte units; held in sectors */
+ if (arg_count(cmd, virtualoriginsize_ARG)) {
+ if (arg_sign_value(cmd, virtualoriginsize_ARG, 0) == SIGN_MINUS) {
+ log_error("Negative virtual origin size is invalid");
+ return 0;
+ }
+ lp->voriginsize = arg_uint64_value(cmd, virtualoriginsize_ARG,
+ UINT64_C(0));
+ if (!lp->voriginsize) {
+ log_error("Virtual origin size may not be zero");
+ return 0;
+ }
+ }
+
return 1;
}
log_error("-c is only available with snapshots");
return 0;
}
+ if (arg_count(cmd, virtualoriginsize_ARG)) {
+ log_error("--virtualoriginsize is only available with snapshots");
+ return 0;
+ }
}
if (lp->mirrors > 1) {
return 1;
}
+static uint64_t _extents_from_size(struct cmd_context *cmd, uint64_t size,
+ uint32_t extent_size)
+{
+ if (size % extent_size) {
+ size += extent_size - size % extent_size;
+ log_print("Rounding up size to full physical extent %s",
+ display_size(cmd, size));
+ }
+
+ if (size > (uint64_t) UINT32_MAX * extent_size) {
+ log_error("Volume too large (%s) for extent size %s. "
+ "Upper limit is %s.",
+ display_size(cmd, size),
+ display_size(cmd, (uint64_t) extent_size),
+ display_size(cmd, (uint64_t) UINT32_MAX *
+ extent_size));
+ return 0;
+ }
+
+ return (uint64_t) size / extent_size;
+}
+
+static struct logical_volume *_create_virtual_origin(struct cmd_context *cmd,
+ struct volume_group *vg,
+ const char *lv_name,
+ uint32_t permission,
+ uint64_t voriginextents)
+{
+ const struct segment_type *segtype;
+ size_t len;
+ char *vorigin_name;
+ struct logical_volume *lv;
+
+ if (!(segtype = get_segtype_from_string(cmd, "zero"))) {
+ log_error("Zero segment type for virtual origin not found");
+ return 0;
+ }
+
+ len = strlen(lv_name) + 32;
+ if (!(vorigin_name = alloca(len)) ||
+ dm_snprintf(vorigin_name, len, "%s_vorigin", lv_name) < 0) {
+ log_error("Virtual origin name allocation failed.");
+ return 0;
+ }
+
+ if (!(lv = lv_create_empty(vorigin_name, NULL, permission,
+ ALLOC_INHERIT, 0, vg)))
+ return_0;
+
+ if (!lv_extend(lv, segtype, 1, 0, 1, voriginextents, NULL, 0u, 0u,
+ NULL, ALLOC_INHERIT))
+ return_0;
+
+ /* store vg on disk(s) */
+ if (!vg_write(vg) || !vg_commit(vg))
+ return_0;
+
+ backup(vg);
+
+ return lv;
+}
+
static int _lvcreate(struct cmd_context *cmd, struct volume_group *vg,
struct lvcreate_params *lp)
{
uint32_t size_rest;
uint32_t status = 0;
- uint64_t tmp_size;
struct logical_volume *lv, *org = NULL;
struct dm_list *pvh;
const char *tag = NULL;
return 0;
}
- if (lp->size) {
- /* No of 512-byte sectors */
- tmp_size = lp->size;
-
- if (tmp_size % vg->extent_size) {
- tmp_size += vg->extent_size - tmp_size %
- vg->extent_size;
- log_print("Rounding up size to full physical extent %s",
- display_size(cmd, tmp_size));
- }
+ if (lp->size &&
+ !(lp->extents = _extents_from_size(cmd, lp->size, vg->extent_size)))
+ return_0;
- if (tmp_size > (uint64_t) UINT32_MAX * vg->extent_size) {
- log_error("Volume too large (%s) for extent size %s. "
- "Upper limit is %s.",
- display_size(cmd, tmp_size),
- display_size(cmd, (uint64_t) vg->extent_size),
- display_size(cmd, (uint64_t) UINT32_MAX *
- vg->extent_size));
- return 0;
- }
- lp->extents = (uint64_t) tmp_size / vg->extent_size;
- }
+ if (lp->voriginsize &&
+ !(lp->voriginextents = _extents_from_size(cmd, lp->voriginsize,
+ vg->extent_size)))
+ return_0;
/*
* Create the pv list.
log_error("Clustered snapshots are not yet supported.");
return 0;
}
- if (!(org = find_lv(vg, lp->origin))) {
- log_err("Couldn't find origin volume '%s'.",
- lp->origin);
- return 0;
- }
- if (lv_is_cow(org)) {
- log_error("Snapshots of snapshots are not supported "
- "yet.");
- return 0;
- }
- if (org->status & LOCKED) {
- log_error("Snapshots of locked devices are not "
- "supported yet");
- return 0;
- }
- if (org->status & MIRROR_IMAGE ||
- org->status & MIRROR_LOG ||
- org->status & MIRRORED) {
- log_error("Snapshots and mirrors may not yet be mixed.");
- return 0;
- }
/* Must zero cow */
status |= LVM_WRITE;
- if (!lv_info(cmd, org, &info, 0, 0)) {
- log_error("Check for existence of snapshot origin "
- "'%s' failed.", org->name);
- return 0;
+ if (arg_count(cmd, virtualoriginsize_ARG))
+ origin_active = 1;
+ else {
+
+ if (!(org = find_lv(vg, lp->origin))) {
+ log_error("Couldn't find origin volume '%s'.",
+ lp->origin);
+ return 0;
+ }
+ if (lv_is_virtual_origin(lv)) {
+ log_error("Can't share virtual origins. "
+ "Use --virtualoriginsize.");
+ return 0;
+ }
+ if (lv_is_cow(org)) {
+ log_error("Snapshots of snapshots are not "
+ "supported yet.");
+ return 0;
+ }
+ if (org->status & LOCKED) {
+ log_error("Snapshots of locked devices are not "
+ "supported yet");
+ return 0;
+ }
+ if (org->status & MIRROR_IMAGE ||
+ org->status & MIRROR_LOG ||
+ org->status & MIRRORED) {
+ log_error("Snapshots and mirrors may not yet "
+ "be mixed.");
+ return 0;
+ }
+
+ if (!lv_info(cmd, org, &info, 0, 0)) {
+ log_error("Check for existence of snapshot "
+ "origin '%s' failed.", org->name);
+ return 0;
+ }
+ origin_active = info.exists;
}
- origin_active = info.exists;
}
if (!lp->extents) {
return 0;
}
+ if (lp->voriginsize &&
+ !(org = _create_virtual_origin(cmd, vg, lv->name,
+ lp->permission,
+ lp->voriginextents))) {
+ log_error("Couldn't create virtual origin for LV %s",
+ lv->name);
+ goto deactivate_and_revert_new_lv;
+ }
+
/* cow LV remains active and becomes snapshot LV */
if (!vg_add_snapshot(NULL, org, lv, NULL,
static int lvremove_single(struct cmd_context *cmd, struct logical_volume *lv,
void *handle __attribute((unused)))
{
+ struct logical_volume *origin;
+
+ /*
+ * If this is a sparse device, remove its origin too.
+ */
+ if (lv_is_cow(lv) && lv_is_virtual_origin(origin = origin_from_cow(lv)))
+ lv = origin;
+
if (!lv_remove_with_dependencies(cmd, lv, arg_count(cmd, force_ARG)))
return ECMD_FAILED;
return 0;
}
+ if (strstr(name, "_vorigin")) {
+ log_error("Names including \"_vorigin\" are reserved. "
+ "Please choose a different LV name.");
+ return 0;
+ }
+
return 1;
}