diff --git a/.gitignore b/.gitignore index dd0bcd9..24421d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/LVM2.2.03.07.tgz +SOURCES/LVM2.2.03.09.tgz diff --git a/.lvm2.metadata b/.lvm2.metadata index 34ff4c7..e2642d8 100644 --- a/.lvm2.metadata +++ b/.lvm2.metadata @@ -1 +1 @@ -a518f89776180548cd0b10ce5e272ae9274e2935 SOURCES/LVM2.2.03.07.tgz +15a90d5039a2a1e9f67611a2a6c2faa72e8996aa SOURCES/LVM2.2.03.09.tgz diff --git a/SOURCES/0001-Merge-master-up-to-commit-53803821de16.patch b/SOURCES/0001-Merge-master-up-to-commit-53803821de16.patch new file mode 100644 index 0000000..1dbef5b --- /dev/null +++ b/SOURCES/0001-Merge-master-up-to-commit-53803821de16.patch @@ -0,0 +1,3317 @@ + base/data-struct/list.h | 4 +- + daemons/lvmlockd/lvmlockd-client.h | 1 + + daemons/lvmlockd/lvmlockd-core.c | 370 +++++++-------- + daemons/lvmlockd/lvmlockd-dlm.c | 8 +- + daemons/lvmlockd/lvmlockd-internal.h | 7 +- + lib/cache/lvmcache.c | 551 ++++++++++------------ + lib/cache/lvmcache.h | 12 +- + lib/commands/toolcontext.c | 3 +- + lib/device/dev-io.c | 3 + + lib/format_text/archiver.c | 2 +- + lib/format_text/format-text.c | 28 +- + lib/format_text/text_label.c | 8 +- + lib/label/hints.c | 6 + + lib/label/label.c | 2 +- + lib/label/label.h | 2 +- + lib/locking/lvmlockd.c | 14 +- + lib/metadata/metadata-exported.h | 3 - + lib/metadata/metadata.c | 68 +-- + lib/metadata/metadata.h | 8 +- + man/lvmlockd.8_main | 14 +- + test/shell/duplicate-vgnames.sh | 660 +++++++++++++++++++++++++++ + test/shell/duplicate-vgrename.sh | 319 +++++++++++++ + test/shell/integrity-dmeventd.sh | 6 +- + test/shell/integrity-large.sh | 6 +- + test/shell/integrity-misc.sh | 6 +- + test/shell/integrity.sh | 6 +- + test/shell/process-each-duplicate-vgnames.sh | 55 --- + test/shell/thin-foreign-repair.sh | 4 +- + tools/command.c | 12 +- + tools/lvconvert.c | 3 +- + tools/pvck.c | 8 +- + tools/pvscan.c | 2 +- + tools/toollib.c | 2 - + tools/vgchange.c | 9 +- + tools/vgimportclone.c | 4 +- + tools/vgmerge.c | 4 +- + tools/vgrename.c | 2 +- + tools/vgsplit.c | 2 +- + 39 files changed, 1571 insertions(+), 654 deletions(-) + create mode 100644 test/shell/duplicate-vgnames.sh + create mode 100644 test/shell/duplicate-vgrename.sh + delete mode 100644 test/shell/process-each-duplicate-vgnames.sh + +diff --git a/base/data-struct/list.h b/base/data-struct/list.h +index 54cb1c1..e0a6256 100644 +--- a/base/data-struct/list.h ++++ b/base/data-struct/list.h +@@ -1,7 +1,7 @@ + #ifndef BASE_DATA_STRUCT_LIST_H + #define BASE_DATA_STRUCT_LIST_H + +-#include /* offsetof */ ++#include "base/memory/container_of.h" + + //---------------------------------------------------------------- + +@@ -100,7 +100,7 @@ struct dm_list *dm_list_next(const struct dm_list *head, const struct dm_list *e + * contained in a structure of type t, return the containing structure. + */ + #define dm_list_struct_base(v, t, head) \ +- ((t *)((const char *)(v) - offsetof(t, head))) ++ container_of(v, t, head) + + /* + * Given the address v of an instance of 'struct dm_list list' contained in +diff --git a/daemons/lvmlockd/lvmlockd-client.h b/daemons/lvmlockd/lvmlockd-client.h +index 16d1613..62ffb73 100644 +--- a/daemons/lvmlockd/lvmlockd-client.h ++++ b/daemons/lvmlockd/lvmlockd-client.h +@@ -14,6 +14,7 @@ + #include "libdaemon/client/daemon-client.h" + + #define LVMLOCKD_SOCKET DEFAULT_RUN_DIR "/lvmlockd.socket" ++#define LVMLOCKD_ADOPT_FILE DEFAULT_RUN_DIR "/lvmlockd.adopt" + + /* Wrappers to open/close connection */ + +diff --git a/daemons/lvmlockd/lvmlockd-core.c b/daemons/lvmlockd/lvmlockd-core.c +index 39275fb..84272c4 100644 +--- a/daemons/lvmlockd/lvmlockd-core.c ++++ b/daemons/lvmlockd/lvmlockd-core.c +@@ -38,6 +38,8 @@ + #define EXTERN + #include "lvmlockd-internal.h" + ++static int str_to_mode(const char *str); ++ + /* + * Basic operation of lvmlockd + * +@@ -142,6 +144,8 @@ static const char *lvmlockd_protocol = "lvmlockd"; + static const int lvmlockd_protocol_version = 1; + static int daemon_quit; + static int adopt_opt; ++static uint32_t adopt_update_count; ++static const char *adopt_file; + + /* + * We use a separate socket for dumping daemon info. +@@ -812,6 +816,144 @@ int version_from_args(char *args, unsigned int *major, unsigned int *minor, unsi + } + + /* ++ * Write new info when a command exits if that command has acquired a new LV ++ * lock. If the command has released an LV lock we don't bother updating the ++ * info. When adopting, we eliminate any LV lock adoptions if there is no dm ++ * device for that LV. If lvmlockd is terminated after acquiring but before ++ * writing this file, those LV locks would not be adopted on restart. ++ */ ++ ++#define ADOPT_VERSION_MAJOR 1 ++#define ADOPT_VERSION_MINOR 0 ++ ++static void write_adopt_file(void) ++{ ++ struct lockspace *ls; ++ struct resource *r; ++ struct lock *lk; ++ time_t t; ++ FILE *fp; ++ ++ if (!(fp = fopen(adopt_file, "w"))) ++ return; ++ ++ adopt_update_count++; ++ ++ t = time(NULL); ++ fprintf(fp, "lvmlockd adopt_version %u.%u pid %d updates %u %s", ++ ADOPT_VERSION_MAJOR, ADOPT_VERSION_MINOR, getpid(), adopt_update_count, ctime(&t)); ++ ++ pthread_mutex_lock(&lockspaces_mutex); ++ list_for_each_entry(ls, &lockspaces, list) { ++ if (ls->lm_type == LD_LM_DLM && !strcmp(ls->name, gl_lsname_dlm)) ++ continue; ++ fprintf(fp, "VG: %38s %s %s %s\n", ++ ls->vg_uuid, ls->vg_name, lm_str(ls->lm_type), ls->vg_args); ++ list_for_each_entry(r, &ls->resources, list) { ++ if (r->type != LD_RT_LV) ++ continue; ++ if ((r->mode != LD_LK_EX) && (r->mode != LD_LK_SH)) ++ continue; ++ list_for_each_entry(lk, &r->locks, list) { ++ fprintf(fp, "LV: %38s %s %s %s %u\n", ++ ls->vg_uuid, r->name, r->lv_args, mode_str(r->mode), r->version); ++ } ++ } ++ } ++ pthread_mutex_unlock(&lockspaces_mutex); ++ ++ fflush(fp); ++ fclose(fp); ++} ++ ++static int read_adopt_file(struct list_head *vg_lockd) ++{ ++ char adopt_line[512]; ++ char vg_uuid[72]; ++ char lm_type_str[16]; ++ char mode[8]; ++ struct lockspace *ls, *ls2; ++ struct resource *r; ++ FILE *fp; ++ ++ if (MAX_ARGS != 64 || MAX_NAME != 64) ++ return -1; ++ ++ if (!(fp = fopen(adopt_file, "r"))) ++ return 0; ++ ++ while (fgets(adopt_line, sizeof(adopt_line), fp)) { ++ if (adopt_line[0] == '#') ++ continue; ++ else if (!strncmp(adopt_line, "lvmlockd", 8)) { ++ unsigned int v_major = 0, v_minor = 0; ++ sscanf(adopt_line, "lvmlockd adopt_version %u.%u", &v_major, &v_minor); ++ if (v_major != ADOPT_VERSION_MAJOR) ++ goto fail; ++ ++ } else if (!strncmp(adopt_line, "VG:", 3)) { ++ if (!(ls = alloc_lockspace())) ++ goto fail; ++ ++ memset(vg_uuid, 0, sizeof(vg_uuid)); ++ ++ if (sscanf(adopt_line, "VG: %63s %64s %16s %64s", ++ vg_uuid, ls->vg_name, lm_type_str, ls->vg_args) != 4) { ++ goto fail; ++ } ++ ++ memcpy(ls->vg_uuid, vg_uuid, 64); ++ ++ if ((ls->lm_type = str_to_lm(lm_type_str)) < 0) ++ goto fail; ++ ++ list_add(&ls->list, vg_lockd); ++ ++ } else if (!strncmp(adopt_line, "LV:", 3)) { ++ if (!(r = alloc_resource())) ++ goto fail; ++ ++ r->type = LD_RT_LV; ++ ++ memset(vg_uuid, 0, sizeof(vg_uuid)); ++ ++ if (sscanf(adopt_line, "LV: %64s %64s %s %8s %u", ++ vg_uuid, r->name, r->lv_args, mode, &r->version) != 5) { ++ goto fail; ++ } ++ ++ if ((r->adopt_mode = str_to_mode(mode)) == LD_LK_IV) ++ goto fail; ++ ++ if (ls && !memcmp(ls->vg_uuid, vg_uuid, 64)) { ++ list_add(&r->list, &ls->resources); ++ r = NULL; ++ } else { ++ list_for_each_entry(ls2, vg_lockd, list) { ++ if (memcmp(ls2->vg_uuid, vg_uuid, 64)) ++ continue; ++ list_add(&r->list, &ls2->resources); ++ r = NULL; ++ break; ++ } ++ } ++ ++ if (r) { ++ log_error("No lockspace found for resource %s vg_uuid %s", r->name, vg_uuid); ++ goto fail; ++ } ++ } ++ } ++ ++ fclose(fp); ++ return 0; ++ ++fail: ++ fclose(fp); ++ return -1; ++} ++ ++/* + * These are few enough that arrays of function pointers can + * be avoided. + */ +@@ -4689,6 +4831,7 @@ static void *client_thread_main(void *arg_in) + struct client *cl; + struct action *act; + struct action *act_un; ++ uint32_t lock_acquire_count = 0, lock_acquire_written = 0; + int rv; + + while (1) { +@@ -4720,6 +4863,9 @@ static void *client_thread_main(void *arg_in) + rv = -1; + } + ++ if (act->flags & LD_AF_LV_LOCK) ++ lock_acquire_count++; ++ + /* + * The client failed after we acquired an LV lock for + * it, but before getting this reply saying it's done. +@@ -4741,6 +4887,11 @@ static void *client_thread_main(void *arg_in) + continue; + } + ++ if (adopt_opt && (lock_acquire_count > lock_acquire_written)) { ++ lock_acquire_written = lock_acquire_count; ++ write_adopt_file(); ++ } ++ + /* + * Queue incoming actions for lockspace threads + */ +@@ -4814,6 +4965,8 @@ static void *client_thread_main(void *arg_in) + pthread_mutex_unlock(&client_mutex); + } + out: ++ if (adopt_opt && lock_acquire_written) ++ unlink(adopt_file); + return NULL; + } + +@@ -4846,180 +4999,6 @@ static void close_client_thread(void) + log_error("pthread_join client_thread error %d", perrno); + } + +-/* +- * Get a list of all VGs with a lockd type (sanlock|dlm). +- * We'll match this list against a list of existing lockspaces that are +- * found in the lock manager. +- * +- * For each of these VGs, also create a struct resource on ls->resources to +- * represent each LV in the VG that uses a lock. For each of these LVs +- * that are active, we'll attempt to adopt a lock. +- */ +- +-static int get_lockd_vgs(struct list_head *vg_lockd) +-{ +- /* FIXME: get VGs some other way */ +- return -1; +-#if 0 +- struct list_head update_vgs; +- daemon_reply reply; +- struct dm_config_node *cn; +- struct dm_config_node *metadata; +- struct dm_config_node *md_cn; +- struct dm_config_node *lv_cn; +- struct lockspace *ls, *safe; +- struct resource *r; +- const char *vg_name; +- const char *vg_uuid; +- const char *lv_uuid; +- const char *lock_type; +- const char *lock_args; +- char find_str_path[PATH_MAX]; +- int rv = 0; +- +- INIT_LIST_HEAD(&update_vgs); +- +- reply = send_lvmetad("vg_list", "token = %s", "skip", NULL); +- +- if (reply.error || strcmp(daemon_reply_str(reply, "response", ""), "OK")) { +- log_error("vg_list from lvmetad failed %d", reply.error); +- rv = -EINVAL; +- goto destroy; +- } +- +- if (!(cn = dm_config_find_node(reply.cft->root, "volume_groups"))) { +- log_error("get_lockd_vgs no vgs"); +- rv = -EINVAL; +- goto destroy; +- } +- +- /* create an update_vgs list of all vg uuids */ +- +- for (cn = cn->child; cn; cn = cn->sib) { +- vg_uuid = cn->key; +- +- if (!(ls = alloc_lockspace())) { +- rv = -ENOMEM; +- break; +- } +- +- strncpy(ls->vg_uuid, vg_uuid, 64); +- list_add_tail(&ls->list, &update_vgs); +- log_debug("get_lockd_vgs %s", vg_uuid); +- } +- destroy: +- daemon_reply_destroy(reply); +- +- if (rv < 0) +- goto out; +- +- /* get vg_name and lock_type for each vg uuid entry in update_vgs */ +- +- list_for_each_entry(ls, &update_vgs, list) { +- reply = send_lvmetad("vg_lookup", +- "token = %s", "skip", +- "uuid = %s", ls->vg_uuid, +- NULL); +- +- if (reply.error || strcmp(daemon_reply_str(reply, "response", ""), "OK")) { +- log_error("vg_lookup from lvmetad failed %d", reply.error); +- rv = -EINVAL; +- goto next; +- } +- +- vg_name = daemon_reply_str(reply, "name", NULL); +- if (!vg_name) { +- log_error("get_lockd_vgs %s no name", ls->vg_uuid); +- rv = -EINVAL; +- goto next; +- } +- +- strncpy(ls->vg_name, vg_name, MAX_NAME); +- +- metadata = dm_config_find_node(reply.cft->root, "metadata"); +- if (!metadata) { +- log_error("get_lockd_vgs %s name %s no metadata", +- ls->vg_uuid, ls->vg_name); +- rv = -EINVAL; +- goto next; +- } +- +- lock_type = dm_config_find_str(metadata, "metadata/lock_type", NULL); +- ls->lm_type = str_to_lm(lock_type); +- +- if ((ls->lm_type != LD_LM_SANLOCK) && (ls->lm_type != LD_LM_DLM)) { +- log_debug("get_lockd_vgs %s not lockd type", ls->vg_name); +- continue; +- } +- +- lock_args = dm_config_find_str(metadata, "metadata/lock_args", NULL); +- if (lock_args) +- strncpy(ls->vg_args, lock_args, MAX_ARGS); +- +- log_debug("get_lockd_vgs %s lock_type %s lock_args %s", +- ls->vg_name, lock_type, lock_args ?: "none"); +- +- /* +- * Make a record (struct resource) of each lv that uses a lock. +- * For any lv that uses a lock, we'll check if the lv is active +- * and if so try to adopt a lock for it. +- */ +- +- for (md_cn = metadata->child; md_cn; md_cn = md_cn->sib) { +- if (strcmp(md_cn->key, "logical_volumes")) +- continue; +- +- for (lv_cn = md_cn->child; lv_cn; lv_cn = lv_cn->sib) { +- snprintf(find_str_path, PATH_MAX, "%s/lock_args", lv_cn->key); +- lock_args = dm_config_find_str(lv_cn, find_str_path, NULL); +- if (!lock_args) +- continue; +- +- snprintf(find_str_path, PATH_MAX, "%s/id", lv_cn->key); +- lv_uuid = dm_config_find_str(lv_cn, find_str_path, NULL); +- +- if (!lv_uuid) { +- log_error("get_lock_vgs no lv id for name %s", lv_cn->key); +- continue; +- } +- +- if (!(r = alloc_resource())) { +- rv = -ENOMEM; +- goto next; +- } +- +- r->use_vb = 0; +- r->type = LD_RT_LV; +- strncpy(r->name, lv_uuid, MAX_NAME); +- if (lock_args) +- strncpy(r->lv_args, lock_args, MAX_ARGS); +- list_add_tail(&r->list, &ls->resources); +- log_debug("get_lockd_vgs %s lv %s %s (name %s)", +- ls->vg_name, r->name, lock_args ? lock_args : "", lv_cn->key); +- } +- } +- next: +- daemon_reply_destroy(reply); +- +- if (rv < 0) +- break; +- } +-out: +- /* Return lockd VG's on the vg_lockd list. */ +- +- list_for_each_entry_safe(ls, safe, &update_vgs, list) { +- list_del(&ls->list); +- +- if ((ls->lm_type == LD_LM_SANLOCK) || (ls->lm_type == LD_LM_DLM)) +- list_add_tail(&ls->list, vg_lockd); +- else +- free(ls); +- } +- +- return rv; +-#endif +-} +- + static char _dm_uuid[DM_UUID_LEN]; + + static char *get_dm_uuid(char *dm_name) +@@ -5236,9 +5215,9 @@ static void adopt_locks(void) + INIT_LIST_HEAD(&to_unlock); + + /* +- * Get list of lockspaces from lock managers. +- * Get list of VGs from lvmetad with a lockd type. +- * Get list of active lockd type LVs from /dev. ++ * Get list of lockspaces from currently running lock managers. ++ * Get list of shared VGs from file written by prior lvmlockd. ++ * Get list of active LVs (in the shared VGs) from the file. + */ + + if (lm_support_dlm() && lm_is_running_dlm()) { +@@ -5262,12 +5241,17 @@ static void adopt_locks(void) + * Adds a struct lockspace to vg_lockd for each lockd VG. + * Adds a struct resource to ls->resources for each LV. + */ +- rv = get_lockd_vgs(&vg_lockd); ++ rv = read_adopt_file(&vg_lockd); + if (rv < 0) { +- log_error("adopt_locks get_lockd_vgs failed"); ++ log_error("adopt_locks read_adopt_file failed"); + goto fail; + } + ++ if (list_empty(&vg_lockd)) { ++ log_debug("No lockspaces in adopt file"); ++ return; ++ } ++ + /* + * For each resource on each lockspace, check if the + * corresponding LV is active. If so, leave the +@@ -5506,7 +5490,7 @@ static void adopt_locks(void) + goto fail; + act->op = LD_OP_LOCK; + act->rt = LD_RT_LV; +- act->mode = LD_LK_EX; ++ act->mode = r->adopt_mode; + act->flags = (LD_AF_ADOPT | LD_AF_PERSISTENT); + act->client_id = INTERNAL_CLIENT_ID; + act->lm_type = ls->lm_type; +@@ -5604,8 +5588,9 @@ static void adopt_locks(void) + * Adopt failed because the orphan has a different mode + * than initially requested. Repeat the lock-adopt operation + * with the other mode. N.B. this logic depends on first +- * trying sh then ex for GL/VG locks, and ex then sh for +- * LV locks. ++ * trying sh then ex for GL/VG locks; for LV locks the mode ++ * from the adopt file is tried first, the alternate ++ * (if the mode in adopt file was wrong somehow.) + */ + + if ((act->rt != LD_RT_LV) && (act->mode == LD_LK_SH)) { +@@ -5613,9 +5598,12 @@ static void adopt_locks(void) + act->mode = LD_LK_EX; + rv = add_lock_action(act); + +- } else if ((act->rt == LD_RT_LV) && (act->mode == LD_LK_EX)) { +- /* LV locks: attempt to adopt sh after ex failed. */ +- act->mode = LD_LK_SH; ++ } else if (act->rt == LD_RT_LV) { ++ /* LV locks: attempt to adopt the other mode. */ ++ if (act->mode == LD_LK_EX) ++ act->mode = LD_LK_SH; ++ else if (act->mode == LD_LK_SH) ++ act->mode = LD_LK_EX; + rv = add_lock_action(act); + + } else { +@@ -5750,10 +5738,13 @@ static void adopt_locks(void) + if (count_start_fail || count_adopt_fail) + goto fail; + ++ unlink(adopt_file); ++ write_adopt_file(); + log_debug("adopt_locks done"); + return; + + fail: ++ unlink(adopt_file); + log_error("adopt_locks failed, reset host"); + } + +@@ -6028,6 +6019,8 @@ static void usage(char *prog, FILE *file) + fprintf(file, " Set path to the pid file. [%s]\n", LVMLOCKD_PIDFILE); + fprintf(file, " --socket-path | -s \n"); + fprintf(file, " Set path to the socket to listen on. [%s]\n", LVMLOCKD_SOCKET); ++ fprintf(file, " --adopt-file \n"); ++ fprintf(file, " Set path to the adopt file. [%s]\n", LVMLOCKD_ADOPT_FILE); + fprintf(file, " --syslog-priority | -S err|warning|debug\n"); + fprintf(file, " Write log messages from this level up to syslog. [%s]\n", _syslog_num_to_name(LOG_SYSLOG_PRIO)); + fprintf(file, " --gl-type | -g \n"); +@@ -6063,6 +6056,7 @@ int main(int argc, char *argv[]) + {"daemon-debug", no_argument, 0, 'D' }, + {"pid-file", required_argument, 0, 'p' }, + {"socket-path", required_argument, 0, 's' }, ++ {"adopt-file", required_argument, 0, 128 }, + {"gl-type", required_argument, 0, 'g' }, + {"host-id", required_argument, 0, 'i' }, + {"host-id-file", required_argument, 0, 'F' }, +@@ -6085,6 +6079,9 @@ int main(int argc, char *argv[]) + switch (c) { + case '0': + break; ++ case 128: ++ adopt_file = strdup(optarg); ++ break; + case 'h': + usage(argv[0], stdout); + exit(EXIT_SUCCESS); +@@ -6146,6 +6143,9 @@ int main(int argc, char *argv[]) + if (!ds.socket_path) + ds.socket_path = LVMLOCKD_SOCKET; + ++ if (!adopt_file) ++ adopt_file = LVMLOCKD_ADOPT_FILE; ++ + /* runs daemon_main/main_loop */ + daemon_start(ds); + +diff --git a/daemons/lvmlockd/lvmlockd-dlm.c b/daemons/lvmlockd/lvmlockd-dlm.c +index 75e6dee..7915cc0 100644 +--- a/daemons/lvmlockd/lvmlockd-dlm.c ++++ b/daemons/lvmlockd/lvmlockd-dlm.c +@@ -398,12 +398,18 @@ static int lm_adopt_dlm(struct lockspace *ls, struct resource *r, int ld_mode, + (void *)1, (void *)1, (void *)1, + NULL, NULL); + +- if (rv == -1 && errno == -EAGAIN) { ++ if (rv == -1 && (errno == EAGAIN)) { + log_debug("S %s R %s adopt_dlm adopt mode %d try other mode", + ls->name, r->name, ld_mode); + rv = -EUCLEAN; + goto fail; + } ++ if (rv == -1 && (errno == ENOENT)) { ++ log_debug("S %s R %s adopt_dlm adopt mode %d no lock", ++ ls->name, r->name, ld_mode); ++ rv = -ENOENT; ++ goto fail; ++ } + if (rv < 0) { + log_debug("S %s R %s adopt_dlm mode %d flags %x error %d errno %d", + ls->name, r->name, mode, flags, rv, errno); +diff --git a/daemons/lvmlockd/lvmlockd-internal.h b/daemons/lvmlockd/lvmlockd-internal.h +index 85e8caf..191c449 100644 +--- a/daemons/lvmlockd/lvmlockd-internal.h ++++ b/daemons/lvmlockd/lvmlockd-internal.h +@@ -11,6 +11,8 @@ + #ifndef _LVM_LVMLOCKD_INTERNAL_H + #define _LVM_LVMLOCKD_INTERNAL_H + ++#include "base/memory/container_of.h" ++ + #define MAX_NAME 64 + #define MAX_ARGS 64 + +@@ -145,6 +147,7 @@ struct resource { + char name[MAX_NAME+1]; /* vg name or lv name */ + int8_t type; /* resource type LD_RT_ */ + int8_t mode; ++ int8_t adopt_mode; + unsigned int sh_count; /* number of sh locks on locks list */ + uint32_t version; + uint32_t last_client_id; /* last client_id to lock or unlock resource */ +@@ -216,10 +219,6 @@ struct val_blk { + /* lm_unlock flags */ + #define LMUF_FREE_VG 0x00000001 + +-#define container_of(ptr, type, member) ({ \ +- const typeof( ((type *)0)->member ) *__mptr = (ptr); \ +- (type *)( (char *)__mptr - offsetof(type,member) );}) +- + static inline void INIT_LIST_HEAD(struct list_head *list) + { + list->next = list; +diff --git a/lib/cache/lvmcache.c b/lib/cache/lvmcache.c +index 2c8c614..6cb5ff0 100644 +--- a/lib/cache/lvmcache.c ++++ b/lib/cache/lvmcache.c +@@ -49,7 +49,7 @@ struct lvmcache_info { + + /* One per VG */ + struct lvmcache_vginfo { +- struct dm_list list; /* Join these vginfos together */ ++ struct dm_list list; /* _vginfos */ + struct dm_list infos; /* List head for lvmcache_infos */ + struct dm_list outdated_infos; /* vg_read moves info from infos to outdated_infos */ + struct dm_list pvsummaries; /* pv_list taken directly from vgsummary */ +@@ -58,7 +58,6 @@ struct lvmcache_vginfo { + uint32_t status; + char vgid[ID_LEN + 1]; + char _padding[7]; +- struct lvmcache_vginfo *next; /* Another VG with same name? */ + char *creation_host; + char *system_id; + char *lock_type; +@@ -66,8 +65,16 @@ struct lvmcache_vginfo { + size_t mda_size; + int seqno; + bool scan_summary_mismatch; /* vgsummary from devs had mismatching seqno or checksum */ ++ bool has_duplicate_local_vgname; /* this local vg and another local vg have same name */ ++ bool has_duplicate_foreign_vgname; /* this foreign vg and another foreign vg have same name */ + }; + ++/* ++ * Each VG found during scan gets a vginfo struct. ++ * Each vginfo is in _vginfos and _vgid_hash, and ++ * _vgname_hash (unless disabled due to duplicate vgnames). ++ */ ++ + static struct dm_hash_table *_pvid_hash = NULL; + static struct dm_hash_table *_vgid_hash = NULL; + static struct dm_hash_table *_vgname_hash = NULL; +@@ -262,16 +269,6 @@ void lvmcache_get_mdas(struct cmd_context *cmd, + } + } + +-static void _vginfo_attach_info(struct lvmcache_vginfo *vginfo, +- struct lvmcache_info *info) +-{ +- if (!vginfo) +- return; +- +- info->vginfo = vginfo; +- dm_list_add(&vginfo->infos, &info->list); +-} +- + static void _vginfo_detach_info(struct lvmcache_info *info) + { + if (!dm_list_empty(&info->list)) { +@@ -282,57 +279,80 @@ static void _vginfo_detach_info(struct lvmcache_info *info) + info->vginfo = NULL; + } + +-/* If vgid supplied, require a match. */ +-struct lvmcache_vginfo *lvmcache_vginfo_from_vgname(const char *vgname, const char *vgid) ++static struct lvmcache_vginfo *_search_vginfos_list(const char *vgname, const char *vgid) + { + struct lvmcache_vginfo *vginfo; + +- if (!vgname) +- return lvmcache_vginfo_from_vgid(vgid); +- +- if (!_vgname_hash) { +- log_debug_cache(INTERNAL_ERROR "Internal lvmcache is no yet initialized."); +- return NULL; +- } +- +- if (!(vginfo = dm_hash_lookup(_vgname_hash, vgname))) { +- log_debug_cache("lvmcache has no info for vgname \"%s\"%s" FMTVGID ".", +- vgname, (vgid) ? " with VGID " : "", (vgid) ? : ""); +- return NULL; +- } +- +- if (vgid) +- do +- if (!strncmp(vgid, vginfo->vgid, ID_LEN)) ++ if (vgid) { ++ dm_list_iterate_items(vginfo, &_vginfos) { ++ if (!strcmp(vgid, vginfo->vgid)) + return vginfo; +- while ((vginfo = vginfo->next)); +- +- if (!vginfo) +- log_debug_cache("lvmcache has not found vgname \"%s\"%s" FMTVGID ".", +- vgname, (vgid) ? " with VGID " : "", (vgid) ? : ""); +- +- return vginfo; ++ } ++ } else { ++ dm_list_iterate_items(vginfo, &_vginfos) { ++ if (!strcmp(vgname, vginfo->vgname)) ++ return vginfo; ++ } ++ } ++ return NULL; + } + +-struct lvmcache_vginfo *lvmcache_vginfo_from_vgid(const char *vgid) ++static struct lvmcache_vginfo *_vginfo_lookup(const char *vgname, const char *vgid) + { + struct lvmcache_vginfo *vginfo; + char id[ID_LEN + 1] __attribute__((aligned(8))); + +- if (!_vgid_hash || !vgid) { +- log_debug_cache(INTERNAL_ERROR "Internal cache cannot lookup vgid."); +- return NULL; ++ if (vgid) { ++ /* vgid not necessarily NULL-terminated */ ++ (void) dm_strncpy(id, vgid, sizeof(id)); ++ ++ if ((vginfo = dm_hash_lookup(_vgid_hash, id))) { ++ if (vgname && strcmp(vginfo->vgname, vgname)) { ++ /* should never happen */ ++ log_error(INTERNAL_ERROR "vginfo_lookup vgid %s has two names %s %s", ++ id, vginfo->vgname, vgname); ++ return NULL; ++ } ++ return vginfo; ++ } else { ++ /* lookup by vgid that doesn't exist */ ++ return NULL; ++ } + } + +- /* vgid not necessarily NULL-terminated */ +- (void) dm_strncpy(id, vgid, sizeof(id)); ++ if (vgname && !_found_duplicate_vgnames) { ++ if ((vginfo = dm_hash_lookup(_vgname_hash, vgname))) { ++ if (vginfo->has_duplicate_local_vgname) { ++ /* should never happen, found_duplicate_vgnames should be set */ ++ log_error(INTERNAL_ERROR "vginfo_lookup %s %s has_duplicate_local_vgname", vgname, vgid); ++ return NULL; ++ } ++ return vginfo; ++ } ++ } + +- if (!(vginfo = dm_hash_lookup(_vgid_hash, id))) { +- log_debug_cache("lvmcache has no info for vgid \"%s\"", id); +- return NULL; ++ if (vgname && _found_duplicate_vgnames) { ++ if ((vginfo = _search_vginfos_list(vgname, vgid))) { ++ if (vginfo->has_duplicate_local_vgname) { ++ log_debug("vginfo_lookup %s %s has_duplicate_local_vgname return none", vgname, vgid); ++ return NULL; ++ } ++ return vginfo; ++ } + } + +- return vginfo; ++ /* lookup by vgname that doesn't exist */ ++ return NULL; ++} ++ ++struct lvmcache_vginfo *lvmcache_vginfo_from_vgname(const char *vgname, const char *vgid) ++{ ++ return _vginfo_lookup(vgname, vgid); ++} ++ ++struct lvmcache_vginfo *lvmcache_vginfo_from_vgid(const char *vgid) ++{ ++ return _vginfo_lookup(NULL, vgid); + } + + const char *lvmcache_vgname_from_vgid(struct dm_pool *mem, const char *vgid) +@@ -353,17 +373,43 @@ const char *lvmcache_vgid_from_vgname(struct cmd_context *cmd, const char *vgnam + { + struct lvmcache_vginfo *vginfo; + +- if (!(vginfo = dm_hash_lookup(_vgname_hash, vgname))) +- return_NULL; ++ if (_found_duplicate_vgnames) { ++ if (!(vginfo = _search_vginfos_list(vgname, NULL))) ++ return_NULL; ++ } else { ++ if (!(vginfo = dm_hash_lookup(_vgname_hash, vgname))) ++ return_NULL; ++ } + +- if (!vginfo->next) +- return dm_pool_strdup(cmd->mem, vginfo->vgid); ++ if (vginfo->has_duplicate_local_vgname) { ++ /* ++ * return NULL if there is a local VG with the same name since ++ * we don't know which to use. ++ */ ++ return NULL; ++ } + +- /* +- * There are multiple VGs with this name to choose from. +- * Return an error because we don't know which VG is intended. +- */ +- return NULL; ++ if (vginfo->has_duplicate_foreign_vgname) ++ return NULL; ++ ++ return dm_pool_strdup(cmd->mem, vginfo->vgid); ++} ++ ++bool lvmcache_has_duplicate_local_vgname(const char *vgid, const char *vgname) ++{ ++ struct lvmcache_vginfo *vginfo; ++ ++ if (_found_duplicate_vgnames) { ++ if (!(vginfo = _search_vginfos_list(vgname, vgid))) ++ return false; ++ } else { ++ if (!(vginfo = dm_hash_lookup(_vgname_hash, vgname))) ++ return false; ++ } ++ ++ if (vginfo->has_duplicate_local_vgname) ++ return true; ++ return false; + } + + /* +@@ -986,15 +1032,6 @@ int lvmcache_label_scan(struct cmd_context *cmd) + + log_debug_cache("Finding VG info"); + +- /* FIXME: can this happen? */ +- if (!cmd->filter) { +- log_error("label scan is missing filter"); +- goto out; +- } +- +- if (!refresh_filters(cmd)) +- log_error("Scan failed to refresh device filter."); +- + /* + * Duplicates found during this label scan are added to _initial_duplicates. + */ +@@ -1057,7 +1094,6 @@ int lvmcache_label_scan(struct cmd_context *cmd) + + r = 1; + +- out: + dm_list_iterate_items(vginfo, &_vginfos) { + if (is_orphan_vg(vginfo->vgname)) + continue; +@@ -1148,49 +1184,20 @@ int lvmcache_pvid_in_unused_duplicates(const char *pvid) + return 0; + } + +-static int _free_vginfo(struct lvmcache_vginfo *vginfo) ++static void _free_vginfo(struct lvmcache_vginfo *vginfo) + { +- struct lvmcache_vginfo *primary_vginfo, *vginfo2; +- int r = 1; +- +- vginfo2 = primary_vginfo = lvmcache_vginfo_from_vgname(vginfo->vgname, NULL); +- +- if (vginfo == primary_vginfo) { +- dm_hash_remove(_vgname_hash, vginfo->vgname); +- if (vginfo->next && !dm_hash_insert(_vgname_hash, vginfo->vgname, +- vginfo->next)) { +- log_error("_vgname_hash re-insertion for %s failed", +- vginfo->vgname); +- r = 0; +- } +- } else +- while (vginfo2) { +- if (vginfo2->next == vginfo) { +- vginfo2->next = vginfo->next; +- break; +- } +- vginfo2 = vginfo2->next; +- } +- +- free(vginfo->system_id); + free(vginfo->vgname); ++ free(vginfo->system_id); + free(vginfo->creation_host); +- +- if (*vginfo->vgid && _vgid_hash && +- lvmcache_vginfo_from_vgid(vginfo->vgid) == vginfo) +- dm_hash_remove(_vgid_hash, vginfo->vgid); +- +- dm_list_del(&vginfo->list); +- ++ if (vginfo->lock_type) ++ free(vginfo->lock_type); + free(vginfo); +- +- return r; + } + + /* +- * vginfo must be info->vginfo unless info is NULL ++ * Remove vginfo from standard lists/hashes. + */ +-static int _drop_vginfo(struct lvmcache_info *info, struct lvmcache_vginfo *vginfo) ++static void _drop_vginfo(struct lvmcache_info *info, struct lvmcache_vginfo *vginfo) + { + if (info) + _vginfo_detach_info(info); +@@ -1198,12 +1205,16 @@ static int _drop_vginfo(struct lvmcache_info *info, struct lvmcache_vginfo *vgin + /* vginfo still referenced? */ + if (!vginfo || is_orphan_vg(vginfo->vgname) || + !dm_list_empty(&vginfo->infos)) +- return 1; ++ return; + +- if (!_free_vginfo(vginfo)) +- return_0; ++ if (dm_hash_lookup(_vgname_hash, vginfo->vgname) == vginfo) ++ dm_hash_remove(_vgname_hash, vginfo->vgname); + +- return 1; ++ dm_hash_remove(_vgid_hash, vginfo->vgid); ++ ++ dm_list_del(&vginfo->list); /* _vginfos list */ ++ ++ _free_vginfo(vginfo); + } + + void lvmcache_del(struct lvmcache_info *info) +@@ -1261,180 +1272,150 @@ static int _lvmcache_update_vgid(struct lvmcache_info *info, + return 1; + } + +-static int _insert_vginfo(struct lvmcache_vginfo *new_vginfo, const char *vgid, +- uint32_t vgstatus, const char *creation_host, +- struct lvmcache_vginfo *primary_vginfo) ++static int _lvmcache_update_vgname(struct cmd_context *cmd, ++ struct lvmcache_info *info, ++ const char *vgname, const char *vgid, ++ const char *system_id, ++ const struct format_type *fmt) + { +- struct lvmcache_vginfo *last_vginfo = primary_vginfo; +- char uuid_primary[64] __attribute__((aligned(8))); +- char uuid_new[64] __attribute__((aligned(8))); +- int use_new = 0; +- +- /* Pre-existing VG takes precedence. Unexported VG takes precedence. */ +- if (primary_vginfo) { +- if (!id_write_format((const struct id *)vgid, uuid_new, sizeof(uuid_new))) +- return_0; ++ char vgid_str[64] __attribute__((aligned(8))); ++ char other_str[64] __attribute__((aligned(8))); ++ struct lvmcache_vginfo *vginfo; ++ struct lvmcache_vginfo *other; ++ int vginfo_is_allowed; ++ int other_is_allowed; + +- if (!id_write_format((const struct id *)&primary_vginfo->vgid, uuid_primary, +- sizeof(uuid_primary))) +- return_0; ++ if (!vgname || (info && info->vginfo && !strcmp(info->vginfo->vgname, vgname))) ++ return 1; + +- _found_duplicate_vgnames = 1; ++ if (!id_write_format((const struct id *)vgid, vgid_str, sizeof(vgid_str))) ++ stack; + +- /* +- * vginfo is kept for each VG with the same name. +- * They are saved with the vginfo->next list. +- * These checks just decide the ordering of +- * that list. +- * +- * FIXME: it should no longer matter what order +- * the vginfo's are kept in, so we can probably +- * remove these comparisons and reordering entirely. +- * +- * If Primary not exported, new exported => keep +- * Else Primary exported, new not exported => change +- * Else Primary has hostname for this machine => keep +- * Else Primary has no hostname, new has one => change +- * Else New has hostname for this machine => change +- * Else Keep primary. +- */ +- if (!(primary_vginfo->status & EXPORTED_VG) && +- (vgstatus & EXPORTED_VG)) +- log_verbose("Cache: Duplicate VG name %s: " +- "Existing %s takes precedence over " +- "exported %s", new_vginfo->vgname, +- uuid_primary, uuid_new); +- else if ((primary_vginfo->status & EXPORTED_VG) && +- !(vgstatus & EXPORTED_VG)) { +- log_verbose("Cache: Duplicate VG name %s: " +- "%s takes precedence over exported %s", +- new_vginfo->vgname, uuid_new, +- uuid_primary); +- use_new = 1; +- } else if (primary_vginfo->creation_host && +- !strcmp(primary_vginfo->creation_host, +- primary_vginfo->fmt->cmd->hostname)) +- log_verbose("Cache: Duplicate VG name %s: " +- "Existing %s (created here) takes precedence " +- "over %s", new_vginfo->vgname, uuid_primary, +- uuid_new); +- else if (!primary_vginfo->creation_host && creation_host) { +- log_verbose("Cache: Duplicate VG name %s: " +- "%s (with creation_host) takes precedence over %s", +- new_vginfo->vgname, uuid_new, +- uuid_primary); +- use_new = 1; +- } else if (creation_host && +- !strcmp(creation_host, +- primary_vginfo->fmt->cmd->hostname)) { +- log_verbose("Cache: Duplicate VG name %s: " +- "%s (created here) takes precedence over %s", +- new_vginfo->vgname, uuid_new, +- uuid_primary); +- use_new = 1; +- } else { +- log_verbose("Cache: Duplicate VG name %s: " +- "Prefer existing %s vs new %s", +- new_vginfo->vgname, uuid_primary, uuid_new); ++ /* ++ * Add vginfo for orphan VG ++ */ ++ if (!info) { ++ if (!(vginfo = zalloc(sizeof(*vginfo)))) { ++ log_error("lvmcache adding vg list alloc failed %s", vgname); ++ return 0; + } +- +- if (!use_new) { +- while (last_vginfo->next) +- last_vginfo = last_vginfo->next; +- last_vginfo->next = new_vginfo; +- return 1; ++ if (!(vginfo->vgname = strdup(vgname))) { ++ free(vginfo); ++ log_error("lvmcache adding vg name alloc failed %s", vgname); ++ return 0; + } ++ dm_list_init(&vginfo->infos); ++ dm_list_init(&vginfo->outdated_infos); ++ dm_list_init(&vginfo->pvsummaries); ++ vginfo->fmt = fmt; + +- dm_hash_remove(_vgname_hash, primary_vginfo->vgname); +- } +- +- if (!dm_hash_insert(_vgname_hash, new_vginfo->vgname, new_vginfo)) { +- log_error("cache_update: vg hash insertion failed: %s", +- new_vginfo->vgname); +- return 0; +- } +- +- if (primary_vginfo) +- new_vginfo->next = primary_vginfo; +- +- return 1; +-} ++ if (!dm_hash_insert(_vgname_hash, vgname, vginfo)) { ++ free(vginfo->vgname); ++ free(vginfo); ++ return_0; ++ } + +-static int _lvmcache_update_vgname(struct lvmcache_info *info, +- const char *vgname, const char *vgid, +- uint32_t vgstatus, const char *creation_host, +- const struct format_type *fmt) +-{ +- struct lvmcache_vginfo *vginfo, *primary_vginfo; +- char mdabuf[32]; ++ if (!_lvmcache_update_vgid(NULL, vginfo, vgid)) { ++ free(vginfo->vgname); ++ free(vginfo); ++ return_0; ++ } + +- if (!vgname || (info && info->vginfo && !strcmp(info->vginfo->vgname, vgname))) ++ /* Ensure orphans appear last on list_iterate */ ++ dm_list_add(&_vginfos, &vginfo->list); + return 1; ++ } + +- /* Remove existing vginfo entry */ +- if (info) +- _drop_vginfo(info, info->vginfo); ++ _drop_vginfo(info, info->vginfo); + +- if (!(vginfo = lvmcache_vginfo_from_vgname(vgname, vgid))) { ++ if (!(vginfo = lvmcache_vginfo_from_vgid(vgid))) { + /* + * Create a vginfo struct for this VG and put the vginfo + * into the hash table. + */ + ++ log_debug_cache("lvmcache adding vginfo for %s %s", vgname, vgid_str); ++ + if (!(vginfo = zalloc(sizeof(*vginfo)))) { +- log_error("lvmcache_update_vgname: list alloc failed"); ++ log_error("lvmcache adding vg list alloc failed %s", vgname); + return 0; + } + if (!(vginfo->vgname = strdup(vgname))) { + free(vginfo); +- log_error("cache vgname alloc failed for %s", vgname); ++ log_error("lvmcache adding vg name alloc failed %s", vgname); + return 0; + } + dm_list_init(&vginfo->infos); + dm_list_init(&vginfo->outdated_infos); + dm_list_init(&vginfo->pvsummaries); + +- /* +- * A different VG (different uuid) can exist with the same name. +- * In this case, the two VGs will have separate vginfo structs, +- * but the second will be linked onto the existing vginfo->next, +- * not in the hash. +- */ +- primary_vginfo = lvmcache_vginfo_from_vgname(vgname, NULL); ++ if ((other = dm_hash_lookup(_vgname_hash, vgname))) { ++ log_debug_cache("lvmcache adding vginfo found duplicate VG name %s", vgname); + +- if (!_insert_vginfo(vginfo, vgid, vgstatus, creation_host, primary_vginfo)) { +- free(vginfo->vgname); +- free(vginfo); +- return 0; ++ /* ++ * A different VG (different uuid) can exist with the ++ * same name. In this case, the two VGs will have ++ * separate vginfo structs, but one will be in the ++ * vgname_hash. If both vginfos are local/accessible, ++ * then _found_duplicate_vgnames is set which will ++ * disable any further use of the vgname_hash. ++ */ ++ ++ if (!memcmp(other->vgid, vgid, ID_LEN)) { ++ /* shouldn't happen since we looked up by vgid above */ ++ log_error(INTERNAL_ERROR "lvmcache_update_vgname %s %s %s %s", ++ vgname, vgid_str, other->vgname, other->vgid); ++ free(vginfo->vgname); ++ free(vginfo); ++ return 0; ++ } ++ ++ vginfo_is_allowed = is_system_id_allowed(cmd, system_id); ++ other_is_allowed = is_system_id_allowed(cmd, other->system_id); ++ ++ if (vginfo_is_allowed && other_is_allowed) { ++ if (!id_write_format((const struct id *)other->vgid, other_str, sizeof(other_str))) ++ stack; ++ ++ vginfo->has_duplicate_local_vgname = 1; ++ other->has_duplicate_local_vgname = 1; ++ _found_duplicate_vgnames = 1; ++ ++ log_warn("WARNING: VG name %s is used by VGs %s and %s.", ++ vgname, vgid_str, other_str); ++ log_warn("Fix duplicate VG names with vgrename uuid, a device filter, or system IDs."); ++ } ++ ++ if (!vginfo_is_allowed && !other_is_allowed) { ++ vginfo->has_duplicate_foreign_vgname = 1; ++ other->has_duplicate_foreign_vgname = 1; ++ } ++ ++ if (!other_is_allowed && vginfo_is_allowed) { ++ /* the accessible vginfo must be in vgnames_hash */ ++ dm_hash_remove(_vgname_hash, vgname); ++ if (!dm_hash_insert(_vgname_hash, vgname, vginfo)) { ++ log_error("lvmcache adding vginfo to name hash failed %s", vgname); ++ return 0; ++ } ++ } ++ } else { ++ if (!dm_hash_insert(_vgname_hash, vgname, vginfo)) { ++ log_error("lvmcache adding vg to name hash failed %s", vgname); ++ free(vginfo->vgname); ++ free(vginfo); ++ return 0; ++ } + } + +- /* Ensure orphans appear last on list_iterate */ +- if (is_orphan_vg(vgname)) +- dm_list_add(&_vginfos, &vginfo->list); +- else +- dm_list_add_h(&_vginfos, &vginfo->list); ++ dm_list_add_h(&_vginfos, &vginfo->list); + } + +- if (info) +- _vginfo_attach_info(vginfo, info); +- else if (!_lvmcache_update_vgid(NULL, vginfo, vgid)) /* Orphans */ +- return_0; +- +- /* FIXME Check consistency of list! */ + vginfo->fmt = fmt; ++ info->vginfo = vginfo; ++ dm_list_add(&vginfo->infos, &info->list); + +- if (info) { +- if (info->mdas.n) +- sprintf(mdabuf, " with %u mda(s)", dm_list_size(&info->mdas)); +- else +- mdabuf[0] = '\0'; +- log_debug_cache("lvmcache %s: now in VG %s%s%s%s%s.", +- dev_name(info->dev), +- vgname, vginfo->vgid[0] ? " (" : "", +- vginfo->vgid[0] ? vginfo->vgid : "", +- vginfo->vgid[0] ? ")" : "", mdabuf); +- } else +- log_debug_cache("lvmcache: Initialised VG %s.", vgname); ++ log_debug_cache("lvmcache %s: now in VG %s %s", dev_name(info->dev), vgname, vgid_str); + + return 1; + } +@@ -1511,9 +1492,9 @@ out: + return 1; + } + +-int lvmcache_add_orphan_vginfo(const char *vgname, struct format_type *fmt) ++int lvmcache_add_orphan_vginfo(struct cmd_context *cmd, const char *vgname, struct format_type *fmt) + { +- return _lvmcache_update_vgname(NULL, vgname, vgname, 0, "", fmt); ++ return _lvmcache_update_vgname(cmd, NULL, vgname, vgname, "", fmt); + } + + static void _lvmcache_update_pvsummaries(struct lvmcache_vginfo *vginfo, struct lvmcache_vgsummary *vgsummary) +@@ -1532,7 +1513,7 @@ static void _lvmcache_update_pvsummaries(struct lvmcache_vginfo *vginfo, struct + * Returning 0 causes the caller to remove the info struct for this + * device from lvmcache, which will make it look like a missing device. + */ +-int lvmcache_update_vgname_and_id(struct lvmcache_info *info, struct lvmcache_vgsummary *vgsummary) ++int lvmcache_update_vgname_and_id(struct cmd_context *cmd, struct lvmcache_info *info, struct lvmcache_vgsummary *vgsummary) + { + const char *vgname = vgsummary->vgname; + const char *vgid = (char *)&vgsummary->vgid; +@@ -1545,6 +1526,7 @@ int lvmcache_update_vgname_and_id(struct lvmcache_info *info, struct lvmcache_vg + vgid = vgname; + } + ++ /* FIXME: remove this, it shouldn't be needed */ + /* If PV without mdas is already in a real VG, don't make it orphan */ + if (is_orphan_vg(vgname) && info->vginfo && + mdas_empty_or_ignored(&info->mdas) && +@@ -1556,7 +1538,7 @@ int lvmcache_update_vgname_and_id(struct lvmcache_info *info, struct lvmcache_vg + * and attaches the info struct for the dev to the vginfo. + * Puts the vginfo into the vgname hash table. + */ +- if (!_lvmcache_update_vgname(info, vgname, vgid, vgsummary->vgstatus, vgsummary->creation_host, info->fmt)) { ++ if (!_lvmcache_update_vgname(cmd, info, vgname, vgid, vgsummary->system_id, info->fmt)) { + /* shouldn't happen, internal error */ + log_error("Failed to update VG %s info in lvmcache.", vgname); + return 0; +@@ -1735,7 +1717,7 @@ int lvmcache_update_vg_from_write(struct volume_group *vg) + (void) dm_strncpy(pvid_s, (char *) &pvl->pv->id, sizeof(pvid_s)); + /* FIXME Could pvl->pv->dev->pvid ever be different? */ + if ((info = lvmcache_info_from_pvid(pvid_s, pvl->pv->dev, 0)) && +- !lvmcache_update_vgname_and_id(info, &vgsummary)) ++ !lvmcache_update_vgname_and_id(vg->cmd, info, &vgsummary)) + return_0; + } + +@@ -1819,7 +1801,7 @@ int lvmcache_update_vg_from_read(struct volume_group *vg, unsigned precommitted) + * info's for PVs without metadata were not connected to the + * vginfo by label_scan, so do it here. + */ +- if (!lvmcache_update_vgname_and_id(info, &vgsummary)) { ++ if (!lvmcache_update_vgname_and_id(vg->cmd, info, &vgsummary)) { + log_debug_cache("lvmcache_update_vg %s failed to update info for %s", + vg->name, dev_name(info->dev)); + } +@@ -1927,7 +1909,7 @@ static struct lvmcache_info * _create_info(struct labeller *labeller, struct dev + return info; + } + +-struct lvmcache_info *lvmcache_add(struct labeller *labeller, ++struct lvmcache_info *lvmcache_add(struct cmd_context *cmd, struct labeller *labeller, + const char *pvid, struct device *dev, uint64_t label_sector, + const char *vgname, const char *vgid, uint32_t vgstatus, + int *is_duplicate) +@@ -2042,7 +2024,7 @@ update_vginfo: + if (vgid) + strncpy((char *)&vgsummary.vgid, vgid, sizeof(vgsummary.vgid)); + +- if (!lvmcache_update_vgname_and_id(info, &vgsummary)) { ++ if (!lvmcache_update_vgname_and_id(cmd, info, &vgsummary)) { + if (created) { + dm_hash_remove(_pvid_hash, pvid_s); + strcpy(info->dev->pvid, ""); +@@ -2055,7 +2037,7 @@ update_vginfo: + return info; + } + +-static void _lvmcache_destroy_entry(struct lvmcache_info *info) ++static void _lvmcache_destroy_info(struct lvmcache_info *info) + { + _vginfo_detach_info(info); + info->dev->pvid[0] = 0; +@@ -2063,20 +2045,11 @@ static void _lvmcache_destroy_entry(struct lvmcache_info *info) + free(info); + } + +-static void _lvmcache_destroy_vgnamelist(struct lvmcache_vginfo *vginfo) +-{ +- struct lvmcache_vginfo *next; +- +- do { +- next = vginfo->next; +- if (!_free_vginfo(vginfo)) +- stack; +- } while ((vginfo = next)); +-} +- + void lvmcache_destroy(struct cmd_context *cmd, int retain_orphans, int reset) + { +- log_debug_cache("Dropping VG info"); ++ struct lvmcache_vginfo *vginfo, *vginfo2; ++ ++ log_debug_cache("Destroy lvmcache content"); + + if (_vgid_hash) { + dm_hash_destroy(_vgid_hash); +@@ -2084,20 +2057,24 @@ void lvmcache_destroy(struct cmd_context *cmd, int retain_orphans, int reset) + } + + if (_pvid_hash) { +- dm_hash_iter(_pvid_hash, (dm_hash_iterate_fn) _lvmcache_destroy_entry); ++ dm_hash_iter(_pvid_hash, (dm_hash_iterate_fn) _lvmcache_destroy_info); + dm_hash_destroy(_pvid_hash); + _pvid_hash = NULL; + } + + if (_vgname_hash) { +- dm_hash_iter(_vgname_hash, +- (dm_hash_iterate_fn) _lvmcache_destroy_vgnamelist); + dm_hash_destroy(_vgname_hash); + _vgname_hash = NULL; + } + ++ dm_list_iterate_items_safe(vginfo, vginfo2, &_vginfos) { ++ dm_list_del(&vginfo->list); ++ _free_vginfo(vginfo); ++ } ++ + if (!dm_list_empty(&_vginfos)) +- log_error(INTERNAL_ERROR "_vginfos list should be empty"); ++ log_error(INTERNAL_ERROR "vginfos list should be empty"); ++ + dm_list_init(&_vginfos); + + /* +@@ -2109,6 +2086,8 @@ void lvmcache_destroy(struct cmd_context *cmd, int retain_orphans, int reset) + * We want the same preferred devices to be chosen each time, so save + * the unpreferred devs here so that _choose_preferred_devs can use + * this to make the same choice each time. ++ * ++ * FIXME: I don't think is is needed any more. + */ + _destroy_device_list(&_prev_unused_duplicate_devs); + dm_list_splice(&_prev_unused_duplicate_devs, &_unused_duplicates); +@@ -2122,7 +2101,7 @@ void lvmcache_destroy(struct cmd_context *cmd, int retain_orphans, int reset) + stack; + + dm_list_iterate_items(fmt, &cmd->formats) { +- if (!lvmcache_add_orphan_vginfo(fmt->orphan_vg_name, fmt)) ++ if (!lvmcache_add_orphan_vginfo(cmd, fmt->orphan_vg_name, fmt)) + stack; + } + } +@@ -2567,36 +2546,6 @@ int lvmcache_vginfo_has_pvid(struct lvmcache_vginfo *vginfo, char *pvid) + return 0; + } + +-struct metadata_area *lvmcache_get_mda(struct cmd_context *cmd, +- const char *vgname, +- struct device *dev, +- int use_mda_num) +-{ +- struct lvmcache_vginfo *vginfo; +- struct lvmcache_info *info; +- struct metadata_area *mda; +- +- if (!use_mda_num) +- use_mda_num = 1; +- +- if (!(vginfo = lvmcache_vginfo_from_vgname(vgname, NULL))) +- return NULL; +- +- dm_list_iterate_items(info, &vginfo->infos) { +- if (info->dev != dev) +- continue; +- +- dm_list_iterate_items(mda, &info->mdas) { +- if ((use_mda_num == 1) && (mda->status & MDA_PRIMARY)) +- return mda; +- if ((use_mda_num == 2) && !(mda->status & MDA_PRIMARY)) +- return mda; +- } +- return NULL; +- } +- return NULL; +-} +- + /* + * This is used by the metadata repair command to check if + * the metadata on a dev needs repair because it's old. +diff --git a/lib/cache/lvmcache.h b/lib/cache/lvmcache.h +index 0c8c789..6cef4d1 100644 +--- a/lib/cache/lvmcache.h ++++ b/lib/cache/lvmcache.h +@@ -71,16 +71,16 @@ int lvmcache_label_rescan_vg(struct cmd_context *cmd, const char *vgname, const + int lvmcache_label_rescan_vg_rw(struct cmd_context *cmd, const char *vgname, const char *vgid); + + /* Add/delete a device */ +-struct lvmcache_info *lvmcache_add(struct labeller *labeller, const char *pvid, ++struct lvmcache_info *lvmcache_add(struct cmd_context *cmd, struct labeller *labeller, const char *pvid, + struct device *dev, uint64_t label_sector, + const char *vgname, const char *vgid, + uint32_t vgstatus, int *is_duplicate); +-int lvmcache_add_orphan_vginfo(const char *vgname, struct format_type *fmt); ++int lvmcache_add_orphan_vginfo(struct cmd_context *cmd, const char *vgname, struct format_type *fmt); + void lvmcache_del(struct lvmcache_info *info); + void lvmcache_del_dev(struct device *dev); + + /* Update things */ +-int lvmcache_update_vgname_and_id(struct lvmcache_info *info, ++int lvmcache_update_vgname_and_id(struct cmd_context *cmd, struct lvmcache_info *info, + struct lvmcache_vgsummary *vgsummary); + int lvmcache_update_vg_from_read(struct volume_group *vg, unsigned precommitted); + int lvmcache_update_vg_from_write(struct volume_group *vg); +@@ -161,11 +161,6 @@ struct device *lvmcache_device(struct lvmcache_info *info); + unsigned lvmcache_mda_count(struct lvmcache_info *info); + uint64_t lvmcache_smallest_mda_size(struct lvmcache_info *info); + +-struct metadata_area *lvmcache_get_mda(struct cmd_context *cmd, +- const char *vgname, +- struct device *dev, +- int use_mda_num); +- + bool lvmcache_has_duplicate_devs(void); + void lvmcache_del_dev_from_duplicates(struct device *dev); + bool lvmcache_dev_is_unused_duplicate(struct device *dev); +@@ -174,6 +169,7 @@ int lvmcache_get_unused_duplicates(struct cmd_context *cmd, struct dm_list *head + int vg_has_duplicate_pvs(struct volume_group *vg); + + int lvmcache_found_duplicate_vgnames(void); ++bool lvmcache_has_duplicate_local_vgname(const char *vgid, const char *vgname); + + int lvmcache_contains_lock_type_sanlock(struct cmd_context *cmd); + +diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c +index 88d5b3e..63b6811 100644 +--- a/lib/commands/toolcontext.c ++++ b/lib/commands/toolcontext.c +@@ -1276,7 +1276,7 @@ int init_lvmcache_orphans(struct cmd_context *cmd) + struct format_type *fmt; + + dm_list_iterate_items(fmt, &cmd->formats) +- if (!lvmcache_add_orphan_vginfo(fmt->orphan_vg_name, fmt)) ++ if (!lvmcache_add_orphan_vginfo(cmd, fmt->orphan_vg_name, fmt)) + return_0; + + return 1; +@@ -1598,6 +1598,7 @@ struct cmd_context *create_toolcontext(unsigned is_clvmd, + dm_list_init(&cmd->formats); + dm_list_init(&cmd->segtypes); + dm_list_init(&cmd->tags); ++ dm_list_init(&cmd->hints); + dm_list_init(&cmd->config_files); + label_init(); + +diff --git a/lib/device/dev-io.c b/lib/device/dev-io.c +index 735441f..33b9345 100644 +--- a/lib/device/dev-io.c ++++ b/lib/device/dev-io.c +@@ -86,6 +86,9 @@ static int _dev_get_size_dev(struct device *dev, uint64_t *size) + int fd = dev->bcache_fd; + int do_close = 0; + ++ if (dm_list_empty(&dev->aliases)) ++ return 0; ++ + if (dev->size_seqno == _dev_size_seqno) { + log_very_verbose("%s: using cached size %" PRIu64 " sectors", + name, dev->size); +diff --git a/lib/format_text/archiver.c b/lib/format_text/archiver.c +index 3a741da..733e62b 100644 +--- a/lib/format_text/archiver.c ++++ b/lib/format_text/archiver.c +@@ -315,7 +315,7 @@ struct volume_group *backup_read_vg(struct cmd_context *cmd, + } + + dm_list_iterate_items(mda, &tf->metadata_areas_in_use) { +- if (!(vg = mda->ops->vg_read(tf, vg_name, mda, NULL, NULL))) ++ if (!(vg = mda->ops->vg_read(cmd, tf, vg_name, mda, NULL, NULL))) + stack; + break; + } +diff --git a/lib/format_text/format-text.c b/lib/format_text/format-text.c +index 268bd64..e448712 100644 +--- a/lib/format_text/format-text.c ++++ b/lib/format_text/format-text.c +@@ -290,7 +290,8 @@ static int _raw_write_mda_header(const struct format_type *fmt, + * in the label scanning path. + */ + +-static struct raw_locn *_read_metadata_location_vg(struct device_area *dev_area, ++static struct raw_locn *_read_metadata_location_vg(struct cmd_context *cmd, ++ struct device_area *dev_area, + struct mda_header *mdah, int primary_mda, + const char *vgname, + int *precommitted) +@@ -369,7 +370,7 @@ static struct raw_locn *_read_metadata_location_vg(struct device_area *dev_area, + vgnamebuf, vgname); + + if ((info = lvmcache_info_from_pvid(dev_area->dev->pvid, dev_area->dev, 0)) && +- !lvmcache_update_vgname_and_id(info, &vgsummary_orphan)) ++ !lvmcache_update_vgname_and_id(cmd, info, &vgsummary_orphan)) + stack; + + return NULL; +@@ -447,7 +448,8 @@ static uint64_t _next_rlocn_offset(struct volume_group *vg, struct raw_locn *rlo + return new_start; + } + +-static struct volume_group *_vg_read_raw_area(struct format_instance *fid, ++static struct volume_group *_vg_read_raw_area(struct cmd_context *cmd, ++ struct format_instance *fid, + const char *vgname, + struct device_area *area, + struct cached_vg_fmtdata **vg_fmtdata, +@@ -468,7 +470,7 @@ static struct volume_group *_vg_read_raw_area(struct format_instance *fid, + goto out; + } + +- if (!(rlocn = _read_metadata_location_vg(area, mdah, primary_mda, vgname, &precommitted))) { ++ if (!(rlocn = _read_metadata_location_vg(cmd, area, mdah, primary_mda, vgname, &precommitted))) { + log_debug_metadata("VG %s not found on %s", vgname, dev_name(area->dev)); + goto out; + } +@@ -503,7 +505,8 @@ static struct volume_group *_vg_read_raw_area(struct format_instance *fid, + return vg; + } + +-static struct volume_group *_vg_read_raw(struct format_instance *fid, ++static struct volume_group *_vg_read_raw(struct cmd_context *cmd, ++ struct format_instance *fid, + const char *vgname, + struct metadata_area *mda, + struct cached_vg_fmtdata **vg_fmtdata, +@@ -512,12 +515,13 @@ static struct volume_group *_vg_read_raw(struct format_instance *fid, + struct mda_context *mdac = (struct mda_context *) mda->metadata_locn; + struct volume_group *vg; + +- vg = _vg_read_raw_area(fid, vgname, &mdac->area, vg_fmtdata, use_previous_vg, 0, mda_is_primary(mda)); ++ vg = _vg_read_raw_area(cmd, fid, vgname, &mdac->area, vg_fmtdata, use_previous_vg, 0, mda_is_primary(mda)); + + return vg; + } + +-static struct volume_group *_vg_read_precommit_raw(struct format_instance *fid, ++static struct volume_group *_vg_read_precommit_raw(struct cmd_context *cmd, ++ struct format_instance *fid, + const char *vgname, + struct metadata_area *mda, + struct cached_vg_fmtdata **vg_fmtdata, +@@ -526,7 +530,7 @@ static struct volume_group *_vg_read_precommit_raw(struct format_instance *fid, + struct mda_context *mdac = (struct mda_context *) mda->metadata_locn; + struct volume_group *vg; + +- vg = _vg_read_raw_area(fid, vgname, &mdac->area, vg_fmtdata, use_previous_vg, 1, mda_is_primary(mda)); ++ vg = _vg_read_raw_area(cmd, fid, vgname, &mdac->area, vg_fmtdata, use_previous_vg, 1, mda_is_primary(mda)); + + return vg; + } +@@ -1321,7 +1325,7 @@ static struct volume_group *_vg_read_file_name(struct format_instance *fid, + return vg; + } + +-static struct volume_group *_vg_read_file(struct format_instance *fid, ++static struct volume_group *_vg_read_file(struct cmd_context *cmd, struct format_instance *fid, + const char *vgname, + struct metadata_area *mda, + struct cached_vg_fmtdata **vg_fmtdata, +@@ -1332,7 +1336,7 @@ static struct volume_group *_vg_read_file(struct format_instance *fid, + return _vg_read_file_name(fid, vgname, tc->path_live); + } + +-static struct volume_group *_vg_read_precommit_file(struct format_instance *fid, ++static struct volume_group *_vg_read_precommit_file(struct cmd_context *cmd, struct format_instance *fid, + const char *vgname, + struct metadata_area *mda, + struct cached_vg_fmtdata **vg_fmtdata, +@@ -1713,7 +1717,7 @@ static int _set_ext_flags(struct physical_volume *pv, struct lvmcache_info *info + } + + /* Only for orphans - FIXME That's not true any more */ +-static int _text_pv_write(const struct format_type *fmt, struct physical_volume *pv) ++static int _text_pv_write(struct cmd_context *cmd, const struct format_type *fmt, struct physical_volume *pv) + { + struct format_instance *fid = pv->fid; + const char *pvid = (const char *) (*pv->old_id.uuid ? &pv->old_id : &pv->id); +@@ -1725,7 +1729,7 @@ static int _text_pv_write(const struct format_type *fmt, struct physical_volume + unsigned mda_index; + + /* Add a new cache entry with PV info or update existing one. */ +- if (!(info = lvmcache_add(fmt->labeller, (const char *) &pv->id, ++ if (!(info = lvmcache_add(cmd, fmt->labeller, (const char *) &pv->id, + pv->dev, pv->label_sector, pv->vg_name, + is_orphan_vg(pv->vg_name) ? pv->vg_name : pv->vg ? (const char *) &pv->vg->id : NULL, 0, NULL))) + return_0; +diff --git a/lib/format_text/text_label.c b/lib/format_text/text_label.c +index 9241eca..1674126 100644 +--- a/lib/format_text/text_label.c ++++ b/lib/format_text/text_label.c +@@ -370,7 +370,7 @@ static int _read_mda_header_and_metadata(const struct format_type *fmt, + * the metadata is at for those PVs. + */ + +-static int _text_read(struct labeller *labeller, struct device *dev, void *label_buf, ++static int _text_read(struct cmd_context *cmd, struct labeller *labeller, struct device *dev, void *label_buf, + uint64_t label_sector, int *is_duplicate) + { + struct lvmcache_vgsummary vgsummary; +@@ -410,7 +410,7 @@ static int _text_read(struct labeller *labeller, struct device *dev, void *label + * + * Other reasons for lvmcache_add to return NULL are internal errors. + */ +- if (!(info = lvmcache_add(labeller, (char *)pvhdr->pv_uuid, dev, label_sector, ++ if (!(info = lvmcache_add(cmd, labeller, (char *)pvhdr->pv_uuid, dev, label_sector, + FMT_TEXT_ORPHAN_VG_NAME, + FMT_TEXT_ORPHAN_VG_NAME, 0, is_duplicate))) + return_0; +@@ -503,7 +503,7 @@ static int _text_read(struct labeller *labeller, struct device *dev, void *label + rv1 = _read_mda_header_and_metadata(fmt, mda1, &vgsummary, &bad_fields); + + if (rv1 && !vgsummary.zero_offset && !vgsummary.mda_ignored) { +- if (!lvmcache_update_vgname_and_id(info, &vgsummary)) { ++ if (!lvmcache_update_vgname_and_id(cmd, info, &vgsummary)) { + /* I believe this is only an internal error. */ + + dm_list_del(&mda1->list); +@@ -554,7 +554,7 @@ static int _text_read(struct labeller *labeller, struct device *dev, void *label + rv2 = _read_mda_header_and_metadata(fmt, mda2, &vgsummary, &bad_fields); + + if (rv2 && !vgsummary.zero_offset && !vgsummary.mda_ignored) { +- if (!lvmcache_update_vgname_and_id(info, &vgsummary)) { ++ if (!lvmcache_update_vgname_and_id(cmd, info, &vgsummary)) { + dm_list_del(&mda2->list); + + /* Are there other cases besides mismatch and internal error? */ +diff --git a/lib/label/hints.c b/lib/label/hints.c +index 48fb661..9546f48 100644 +--- a/lib/label/hints.c ++++ b/lib/label/hints.c +@@ -351,6 +351,7 @@ static void _unlock_hints(struct cmd_context *cmd) + + void hints_exit(struct cmd_context *cmd) + { ++ free_hints(&cmd->hints); + if (_hints_fd == -1) + return; + return _unlock_hints(cmd); +@@ -419,6 +420,9 @@ static int _dev_in_hint_hash(struct cmd_context *cmd, struct device *dev) + { + uint64_t devsize = 0; + ++ if (dm_list_empty(&dev->aliases)) ++ return 0; ++ + if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "regex")) + return 0; + +@@ -1318,6 +1322,7 @@ int get_hints(struct cmd_context *cmd, struct dm_list *hints_out, int *newhints, + */ + if (!_read_hint_file(cmd, &hints_list, &needs_refresh)) { + log_debug("get_hints: read fail"); ++ free_hints(&hints_list); + _unlock_hints(cmd); + return 0; + } +@@ -1330,6 +1335,7 @@ int get_hints(struct cmd_context *cmd, struct dm_list *hints_out, int *newhints, + */ + if (needs_refresh) { + log_debug("get_hints: needs refresh"); ++ free_hints(&hints_list); + + if (!_lock_hints(cmd, LOCK_EX, NONBLOCK)) + return 0; +diff --git a/lib/label/label.c b/lib/label/label.c +index 0458313..4d37cef 100644 +--- a/lib/label/label.c ++++ b/lib/label/label.c +@@ -431,7 +431,7 @@ static int _process_block(struct cmd_context *cmd, struct dev_filter *f, + * info/vginfo structs. That lvmcache info is used later when the + * command wants to read the VG to do something to it. + */ +- ret = labeller->ops->read(labeller, dev, label_buf, sector, &is_duplicate); ++ ret = labeller->ops->read(cmd, labeller, dev, label_buf, sector, &is_duplicate); + + if (!ret) { + if (is_duplicate) { +diff --git a/lib/label/label.h b/lib/label/label.h +index 4108906..9a4b630 100644 +--- a/lib/label/label.h ++++ b/lib/label/label.h +@@ -64,7 +64,7 @@ struct label_ops { + /* + * Read a label from a volume. + */ +- int (*read) (struct labeller * l, struct device * dev, ++ int (*read) (struct cmd_context *cmd, struct labeller * l, struct device * dev, + void *label_buf, uint64_t label_sector, int *is_duplicate); + + /* +diff --git a/lib/locking/lvmlockd.c b/lib/locking/lvmlockd.c +index e378fe6..dca7954 100644 +--- a/lib/locking/lvmlockd.c ++++ b/lib/locking/lvmlockd.c +@@ -635,7 +635,6 @@ static int _init_vg_sanlock(struct cmd_context *cmd, struct volume_group *vg, in + const char *vg_lock_args = NULL; + const char *opts = NULL; + struct pv_list *pvl; +- struct device *sector_dev; + uint32_t sector_size = 0; + unsigned int physical_block_size, logical_block_size; + int num_mb = 0; +@@ -656,16 +655,11 @@ static int _init_vg_sanlock(struct cmd_context *cmd, struct volume_group *vg, in + dm_list_iterate_items(pvl, &vg->pvs) { + if (!dev_get_direct_block_sizes(pvl->pv->dev, &physical_block_size, &logical_block_size)) + continue; +- +- if (!sector_size) { +- sector_size = logical_block_size; +- sector_dev = pvl->pv->dev; +- } else if (sector_size != logical_block_size) { +- log_error("Inconsistent logical block sizes for %s and %s.", +- dev_name(pvl->pv->dev), dev_name(sector_dev)); +- return 0; +- } ++ if ((physical_block_size == 4096) || (logical_block_size == 4096)) ++ sector_size = 4096; + } ++ if (!sector_size) ++ sector_size = 512; + + log_debug("Using sector size %u for sanlock LV", sector_size); + +diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h +index 52bc776..083f74a 100644 +--- a/lib/metadata/metadata-exported.h ++++ b/lib/metadata/metadata-exported.h +@@ -744,9 +744,6 @@ struct volume_group *vg_read_for_update(struct cmd_context *cmd, const char *vg_ + const char *vgid, uint32_t read_flags, uint32_t lockd_state); + struct volume_group *vg_read_orphans(struct cmd_context *cmd, const char *orphan_vgname); + +-/* this is historical and being removed, don't use */ +-uint32_t vg_read_error(struct volume_group *vg_handle); +- + /* pe_start and pe_end relate to any existing data so that new metadata + * areas can avoid overlap */ + struct physical_volume *pv_create(const struct cmd_context *cmd, +diff --git a/lib/metadata/metadata.c b/lib/metadata/metadata.c +index 9c44388..4b8dce9 100644 +--- a/lib/metadata/metadata.c ++++ b/lib/metadata/metadata.c +@@ -3666,7 +3666,7 @@ int pv_write(struct cmd_context *cmd, + return 0; + } + +- if (!pv->fmt->ops->pv_write(pv->fmt, pv)) ++ if (!pv->fmt->ops->pv_write(cmd, pv->fmt, pv)) + return_0; + + pv->status &= ~UNLABELLED_PV; +@@ -4010,17 +4010,6 @@ static int _access_vg_exported(struct cmd_context *cmd, struct volume_group *vg) + return 0; + } + +-/* +- * Test the validity of a VG handle returned by vg_read() or vg_read_for_update(). +- */ +-uint32_t vg_read_error(struct volume_group *vg_handle) +-{ +- if (!vg_handle) +- return FAILED_ALLOCATION; +- +- return SUCCESS; +-} +- + struct format_instance *alloc_fid(const struct format_type *fmt, + const struct format_instance_ctx *fic) + { +@@ -4751,18 +4740,6 @@ static struct volume_group *_vg_read(struct cmd_context *cmd, + lvmcache_label_rescan_vg(cmd, vgname, vgid); + } + +- /* Now determine the correct vgname if none was supplied */ +- if (!vgname && !(vgname = lvmcache_vgname_from_vgid(cmd->mem, vgid))) { +- log_debug_metadata("Cache did not find VG name from vgid %s", vgid); +- return NULL; +- } +- +- /* Determine the correct vgid if none was supplied */ +- if (!vgid && !(vgid = lvmcache_vgid_from_vgname(cmd, vgname))) { +- log_debug_metadata("Cache did not find VG vgid from name %s", vgname); +- return NULL; +- } +- + /* + * A "format instance" is an abstraction for a VG location, + * i.e. where a VG's metadata exists on disk. +@@ -4841,7 +4818,7 @@ static struct volume_group *_vg_read(struct cmd_context *cmd, + log_debug_metadata("Reading VG %s precommit metadata from %s %llu", + vgname, dev_name(mda_dev), (unsigned long long)mda->header_start); + +- vg = mda->ops->vg_read_precommit(fid, vgname, mda, &vg_fmtdata, &use_previous_vg); ++ vg = mda->ops->vg_read_precommit(cmd, fid, vgname, mda, &vg_fmtdata, &use_previous_vg); + + if (!vg && !use_previous_vg) { + log_warn("WARNING: Reading VG %s precommit on %s failed.", vgname, dev_name(mda_dev)); +@@ -4852,7 +4829,7 @@ static struct volume_group *_vg_read(struct cmd_context *cmd, + log_debug_metadata("Reading VG %s metadata from %s %llu", + vgname, dev_name(mda_dev), (unsigned long long)mda->header_start); + +- vg = mda->ops->vg_read(fid, vgname, mda, &vg_fmtdata, &use_previous_vg); ++ vg = mda->ops->vg_read(cmd, fid, vgname, mda, &vg_fmtdata, &use_previous_vg); + + if (!vg && !use_previous_vg) { + log_warn("WARNING: Reading VG %s on %s failed.", vgname, dev_name(mda_dev)); +@@ -4999,6 +4976,7 @@ struct volume_group *vg_read(struct cmd_context *cmd, const char *vg_name, const + int missing_pv_dev = 0; + int missing_pv_flag = 0; + uint32_t failure = 0; ++ int original_vgid_set = vgid ? 1 : 0; + int writing = (vg_read_flags & READ_FOR_UPDATE); + int activating = (vg_read_flags & READ_FOR_ACTIVATE); + +@@ -5033,7 +5011,45 @@ struct volume_group *vg_read(struct cmd_context *cmd, const char *vg_name, const + goto bad; + } + ++ /* I belive this is unused, the name is always set. */ ++ if (!vg_name && !(vg_name = lvmcache_vgname_from_vgid(cmd->mem, vgid))) { ++ unlock_vg(cmd, NULL, vg_name); ++ log_error("VG name not found for vgid %s", vgid); ++ failure |= FAILED_NOTFOUND; ++ goto_bad; ++ } ++ ++ /* ++ * If the command is process all vgs, process_each will get a list of vgname+vgid ++ * pairs, and then call vg_read() for each vgname+vigd. In this case we know ++ * which VG to read even if there are duplicate names, and we don't fail. ++ * ++ * If the user has requested one VG by name, process_each passes only the vgname ++ * to vg_read(), and we look up the vgid from lvmcache. lvmcache finds duplicate ++ * vgnames, doesn't know which is intended, returns a NULL vgid, and we fail. ++ */ ++ ++ if (!vgid) ++ vgid = lvmcache_vgid_from_vgname(cmd, vg_name); ++ ++ if (!vgid) { ++ unlock_vg(cmd, NULL, vg_name); ++ /* Some callers don't care if the VG doesn't exist and don't want an error message. */ ++ if (!(vg_read_flags & READ_OK_NOTFOUND)) ++ log_error("Volume group \"%s\" not found", vg_name); ++ failure |= FAILED_NOTFOUND; ++ goto_bad; ++ } ++ ++ /* ++ * vgchange -ay (no vgname arg) will activate multiple local VGs with the same ++ * name, but if the vgs have the same lv name, activating those lvs will fail. ++ */ ++ if (activating && original_vgid_set && lvmcache_has_duplicate_local_vgname(vgid, vg_name)) ++ log_warn("WARNING: activating multiple VGs with the same name is dangerous and may fail."); ++ + if (!(vg = _vg_read(cmd, vg_name, vgid, 0, writing))) { ++ unlock_vg(cmd, NULL, vg_name); + /* Some callers don't care if the VG doesn't exist and don't want an error message. */ + if (!(vg_read_flags & READ_OK_NOTFOUND)) + log_error("Volume group \"%s\" not found.", vg_name); +diff --git a/lib/metadata/metadata.h b/lib/metadata/metadata.h +index f199fc4..2c22450 100644 +--- a/lib/metadata/metadata.h ++++ b/lib/metadata/metadata.h +@@ -76,12 +76,14 @@ struct cached_vg_fmtdata; + /* Per-format per-metadata area operations */ + struct metadata_area_ops { + struct dm_list list; +- struct volume_group *(*vg_read) (struct format_instance * fi, ++ struct volume_group *(*vg_read) (struct cmd_context *cmd, ++ struct format_instance * fi, + const char *vg_name, + struct metadata_area * mda, + struct cached_vg_fmtdata **vg_fmtdata, + unsigned *use_previous_vg); +- struct volume_group *(*vg_read_precommit) (struct format_instance * fi, ++ struct volume_group *(*vg_read_precommit) (struct cmd_context *cmd, ++ struct format_instance * fi, + const char *vg_name, + struct metadata_area * mda, + struct cached_vg_fmtdata **vg_fmtdata, +@@ -326,7 +328,7 @@ struct format_handler { + * Write a PV structure to disk. Fails if the PV is in a VG ie + * pv->vg_name must be a valid orphan VG name + */ +- int (*pv_write) (const struct format_type * fmt, ++ int (*pv_write) (struct cmd_context *cmd, const struct format_type * fmt, + struct physical_volume * pv); + + /* +diff --git a/man/lvmlockd.8_main b/man/lvmlockd.8_main +index 8ed5400..c21f7a9 100644 +--- a/man/lvmlockd.8_main ++++ b/man/lvmlockd.8_main +@@ -58,6 +58,10 @@ For default settings, see lvmlockd -h. + .I path + Set path to the socket to listen on. + ++.B --adopt-file ++.I path ++ Set path to the adopt file. ++ + .B --syslog-priority | -S err|warning|debug + Write log messages from this level up to syslog. + +@@ -76,6 +80,8 @@ For default settings, see lvmlockd -h. + .I seconds + Override the default sanlock I/O timeout. + ++.B --adopt | -A 0|1 ++ Enable (1) or disable (0) lock adoption. + + .SH USAGE + +@@ -548,7 +554,13 @@ necessary locks. + .B lvmlockd failure + + If lvmlockd fails or is killed while holding locks, the locks are orphaned +-in the lock manager. ++in the lock manager. Orphaned locks must be cleared or adopted before the ++associated resources can be accessed normally. If lock adoption is ++enabled, lvmlockd keeps a record of locks in the adopt-file. A subsequent ++instance of lvmlockd will then adopt locks orphaned by the previous ++instance. Adoption must be enabled in both instances (--adopt|-A 1). ++Without adoption, the lock manager or host would require a reset to clear ++orphaned lock state. + + .B dlm/corosync failure + +diff --git a/test/shell/duplicate-vgnames.sh b/test/shell/duplicate-vgnames.sh +new file mode 100644 +index 0000000..0f98f9c +--- /dev/null ++++ b/test/shell/duplicate-vgnames.sh +@@ -0,0 +1,660 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2008-2013 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. ++ ++SKIP_WITH_LVMLOCKD=1 ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux prepare_devs 7 ++ ++# test setups: ++# # local vgs named foo # foreign vg named foo ++# a. 0 1 ++# b. 0 2 ++# c. 1 1 ++# d. 1 2 ++# e. 2 0 ++# f. 2 1 ++# g. 2 2 ++# h. 3 3 ++# ++# commands to run for each test setup: ++# ++# vgs ++# all cases show all local ++# ++# vgs --foreign ++# all cases show all local and foreign ++# ++# vgs foo ++# a. not found ++# b. not found ++# c. show 1 local ++# d. show 1 local ++# e-g. dup error ++# ++# vgs --foreign foo ++# a. show 1 foreign ++# b. dup error ++# c. show 1 local ++# d. show 1 local ++# e-g. dup error ++# ++# vgchange -ay ++# a. none ++# b. none ++# c. activate 1 local ++# d. activate 1 local ++# e-g. activate 2 local ++# (if both local vgs have lvs with same name the second will fail to activate) ++# ++# vgchange -ay foo ++# a. none ++# b. none ++# c. activate 1 local ++# d. activate 1 local ++# e-g. dup error ++# ++# lvcreate foo ++# a. none ++# b. none ++# c. create 1 local ++# d. create 1 local ++# e-g. dup error ++# ++# vgremove foo ++# a. none ++# b. none ++# c. remove 1 local ++# d. remove 1 local ++# e-g. dup error ++# (in a couple cases test that vgremove -S vg_uuid=N works for local vg when local dups exist) ++ ++ ++# a. 0 local, 1 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++ ++vgs -o+uuid |tee out ++not grep $vg1 out ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++ ++not vgs -o+uuid $vg1 |tee out ++not grep $vg1 out ++vgs --foreign -o+uuid $vg1 |tee out ++grep $vg1 out ++ ++vgchange -ay ++lvs --foreign -o vguuid,active |tee out ++not grep active out ++vgchange -an ++ ++not vgchange -ay $vg1 ++lvs --foreign -o vguuid,active |tee out ++not grep active out ++vgchange -an ++ ++not lvcreate -l1 -an -n $lv2 $vg1 ++lvs --foreign -o vguuid,name |tee out ++grep $UUID1 out | not grep $lv2 ++ ++not vgremove $vg1 ++vgs --foreign -o+uuid |tee out ++grep $UUID1 out ++vgremove -y -S vg_uuid=$UUID1 ++vgs --foreign -o+uuid |tee out ++grep $UUID1 out ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++ ++# b. 0 local, 2 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other2" $vg1 ++aux enable_dev "$dev1" ++ ++vgs -o+uuid |tee out ++not grep $vg1 out ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++ ++not vgs -o+uuid $vg1 |tee out ++not grep $vg1 out ++not vgs --foreign -o+uuid $vg1 |tee out ++not grep $vg1 out ++ ++vgchange -ay ++lvs --foreign -o vguuid,active |tee out ++not grep active out ++vgchange -an ++ ++not vgchange -ay $vg1 ++lvs --foreign -o vguuid,active |tee out ++not grep active out ++vgchange -an ++ ++not lvcreate -l1 -an -n $lv2 $vg1 ++lvs --foreign -o vguuid,name |tee out ++grep $UUID1 out | not grep $lv2 ++grep $UUID2 out | not grep $lv2 ++ ++not vgremove $vg1 ++vgs --foreign -o+uuid |tee out ++grep $UUID1 out ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++aux wipefs_a "$dev3" ++ ++# c. 1 local, 1 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++aux enable_dev "$dev1" ++ ++vgs -o+uuid |tee out ++cat out ++grep $vg1 out ++grep $UUID1 out ++not grep $UUID2 out ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++ ++vgs -o+uuid $vg1 |tee out ++grep $vg1 out ++grep $UUID1 out ++not grep $UUID2 out ++vgs --foreign -o+uuid $vg1 |tee out ++grep $vg1 out ++grep $UUID1 out ++not grep $UUID2 out ++ ++vgchange -ay ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | grep active ++grep $UUID2 out | not grep active ++vgchange -an ++ ++vgchange -ay $vg1 ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | grep active ++grep $UUID2 out | not grep active ++vgchange -an ++ ++lvcreate -l1 -an -n $lv2 $vg1 ++lvs --foreign -o vguuid,name |tee out ++grep $UUID1 out | grep $lv2 ++grep $UUID2 out | not grep $lv2 ++ ++vgremove -y $vg1 ++vgs -o+uuid |tee out ++not grep $UUID1 out ++vgs --foreign -o+uuid |tee out ++grep $UUID2 out ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++aux wipefs_a "$dev3" ++ ++# d. 1 local, 2 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++aux disable_dev "$dev2" ++vgcreate $vg1 "$dev3" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID3=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other2" $vg1 ++aux enable_dev "$dev1" ++aux enable_dev "$dev2" ++ ++vgs -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++not grep $UUID2 out ++not grep $UUID3 out ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++ ++vgs -o+uuid $vg1 |tee out ++grep $vg1 out ++grep $UUID1 out ++not grep $UUID2 out ++not grep $UUID3 out ++vgs --foreign -o+uuid $vg1 |tee out ++grep $vg1 out ++grep $UUID1 out ++not grep $UUID2 out ++not grep $UUID3 out ++ ++vgchange -ay ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | grep active ++grep $UUID2 out | not grep active ++grep $UUID3 out | not grep active ++vgchange -an ++ ++vgchange -ay $vg1 ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | grep active ++grep $UUID2 out | not grep active ++grep $UUID3 out | not grep active ++vgchange -an ++ ++lvcreate -l1 -an -n $lv2 $vg1 ++lvs --foreign -o vguuid,name |tee out ++grep $UUID1 out | grep $lv2 ++grep $UUID2 out | not grep $lv2 ++grep $UUID3 out | not grep $lv2 ++ ++vgremove -y $vg1 ++vgs -o+uuid |tee out ++not grep $UUID1 out ++vgs --foreign -o+uuid |tee out ++grep $UUID2 out ++grep $UUID3 out ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++aux wipefs_a "$dev3" ++aux wipefs_a "$dev4" ++ ++# e. 2 local, 0 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++# diff lvname to prevent clash in vgchange -ay ++lvcreate -n ${lv1}_b -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux enable_dev "$dev1" ++ ++vgs -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++ ++not vgs -o+uuid $vg1 |tee out ++not grep $vg1 out ++not vgs --foreign -o+uuid $vg1 |tee out ++not grep $vg1 out ++ ++vgchange -ay ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | grep active ++grep $UUID2 out | grep active ++vgchange -an ++ ++not vgchange -ay $vg1 ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | not grep active ++grep $UUID2 out | not grep active ++vgchange -an ++ ++not lvcreate -l1 -an -n $lv2 $vg1 ++lvs --foreign -o vguuid,name |tee out ++grep $UUID1 out | not grep $lv2 ++grep $UUID2 out | not grep $lv2 ++ ++not vgremove $vg1 ++vgs -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++vgremove -y -S vg_uuid=$UUID1 ++vgs -o+uuid |tee out ++not grep $UUID1 out ++grep $UUID2 out ++vgremove -y -S vg_uuid=$UUID2 ++vgs -o+uuid |tee out ++not grep $UUID1 out ++not grep $UUID2 out ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++aux wipefs_a "$dev3" ++ ++# f. 2 local, 1 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++# diff lvname to prevent clash in vgchange -ay ++lvcreate -n ${lv1}_b -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev2" ++vgcreate $vg1 "$dev3" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID3=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++aux enable_dev "$dev1" ++aux enable_dev "$dev2" ++ ++vgs -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++not group $UUID3 out ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++ ++not vgs -o+uuid $vg1 |tee out ++not grep $vg1 out ++not vgs --foreign -o+uuid $vg1 |tee out ++not grep $vg1 out ++ ++vgchange -ay ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | grep active ++grep $UUID2 out | grep active ++grep $UUID3 out | not grep active ++vgchange -an ++ ++not vgchange -ay $vg1 ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | not grep active ++grep $UUID2 out | not grep active ++grep $UUID3 out | not grep active ++vgchange -an ++ ++not lvcreate -l1 -an -n $lv2 $vg1 ++lvs --foreign -o vguuid,name |tee out ++grep $UUID1 out | not grep $lv2 ++grep $UUID2 out | not grep $lv2 ++grep $UUID3 out | not grep $lv2 ++ ++not vgremove $vg1 ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++vgremove -y -S vg_uuid=$UUID1 ++vgs --foreign -o+uuid |tee out ++not grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++vgremove -y -S vg_uuid=$UUID2 ++vgs --foreign -o+uuid |tee out ++not grep $UUID1 out ++not grep $UUID2 out ++grep $UUID3 out ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++aux wipefs_a "$dev3" ++aux wipefs_a "$dev4" ++ ++# g. 2 local, 2 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++# diff lvname to prevent clash in vgchange -ay ++lvcreate -n ${lv1}_b -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev2" ++vgcreate $vg1 "$dev3" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID3=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++aux disable_dev "$dev3" ++vgcreate $vg1 "$dev4" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID4=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other2" $vg1 ++aux enable_dev "$dev1" ++aux enable_dev "$dev2" ++aux enable_dev "$dev3" ++ ++vgs -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++not group $UUID3 out ++not group $UUID4 out ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++grep $UUID4 out ++ ++not vgs -o+uuid $vg1 |tee out ++not grep $vg1 out ++not vgs --foreign -o+uuid $vg1 |tee out ++not grep $vg1 out ++ ++vgchange -ay ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | grep active ++grep $UUID2 out | grep active ++grep $UUID3 out | not grep active ++grep $UUID4 out | not grep active ++vgchange -an ++ ++not vgchange -ay $vg1 ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | not grep active ++grep $UUID2 out | not grep active ++grep $UUID3 out | not grep active ++grep $UUID4 out | not grep active ++vgchange -an ++ ++not lvcreate -l1 -an -n $lv2 $vg1 ++lvs --foreign -o vguuid,name |tee out ++grep $UUID1 out | not grep $lv2 ++grep $UUID2 out | not grep $lv2 ++grep $UUID3 out | not grep $lv2 ++grep $UUID4 out | not grep $lv2 ++ ++not vgremove $vg1 ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++grep $UUID4 out ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++aux wipefs_a "$dev3" ++aux wipefs_a "$dev4" ++aux wipefs_a "$dev5" ++ ++# h. 3 local, 3 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++# diff lvname to prevent clash in vgchange -ay ++lvcreate -n ${lv1}_b -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev2" ++vgcreate $vg1 "$dev3" ++# diff lvname to prevent clash in vgchange -ay ++lvcreate -n ${lv1}_bb -l1 -an $vg1 ++UUID3=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev3" ++vgcreate $vg1 "$dev4" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID4=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++aux disable_dev "$dev4" ++vgcreate $vg1 "$dev5" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID5=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other2" $vg1 ++aux disable_dev "$dev5" ++vgcreate $vg1 "$dev6" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID6=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other3" $vg1 ++aux enable_dev "$dev1" ++aux enable_dev "$dev2" ++aux enable_dev "$dev3" ++aux enable_dev "$dev4" ++aux enable_dev "$dev5" ++ ++vgs -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++not group $UUID4 out ++not group $UUID5 out ++not group $UUID6 out ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++grep $UUID4 out ++grep $UUID5 out ++grep $UUID6 out ++ ++not vgs -o+uuid $vg1 |tee out ++not grep $vg1 out ++not vgs --foreign -o+uuid $vg1 |tee out ++not grep $vg1 out ++ ++vgchange -ay ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | grep active ++grep $UUID2 out | grep active ++grep $UUID3 out | grep active ++grep $UUID4 out | not grep active ++grep $UUID5 out | not grep active ++grep $UUID6 out | not grep active ++vgchange -an ++ ++not vgchange -ay $vg1 ++lvs --foreign -o vguuid,active |tee out ++grep $UUID1 out | not grep active ++grep $UUID2 out | not grep active ++grep $UUID3 out | not grep active ++grep $UUID4 out | not grep active ++grep $UUID5 out | not grep active ++grep $UUID6 out | not grep active ++vgchange -an ++ ++not lvcreate -l1 -an -n $lv2 $vg1 ++lvs --foreign -o vguuid,name |tee out ++grep $UUID1 out | not grep $lv2 ++grep $UUID2 out | not grep $lv2 ++grep $UUID3 out | not grep $lv2 ++grep $UUID4 out | not grep $lv2 ++grep $UUID5 out | not grep $lv2 ++grep $UUID6 out | not grep $lv2 ++ ++not vgremove $vg1 ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++grep $UUID4 out ++grep $UUID5 out ++grep $UUID6 out ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++aux wipefs_a "$dev3" ++aux wipefs_a "$dev4" ++aux wipefs_a "$dev5" ++aux wipefs_a "$dev6" ++ ++# vgreduce test with 1 local and 1 foreign vg. ++# setup ++vgcreate $vg1 "$dev1" "$dev7" ++lvcreate -n $lv1 -l1 -an $vg1 "$dev1" ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++PV1UUID=$(pvs --noheading -o uuid "$dev1") ++PV7UUID=$(pvs --noheading -o uuid "$dev7") ++aux disable_dev "$dev1" ++aux disable_dev "$dev7" ++vgcreate $vg1 "$dev2" ++PV2UUID=$(pvs --noheading -o uuid "$dev2") ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++aux enable_dev "$dev1" ++aux enable_dev "$dev7" ++ ++vgs --foreign -o+uuid |tee out ++grep $vg1 out ++grep $UUID1 out ++grep $UUID2 out ++pvs --foreign -o+uuid |tee out ++grep $PV1UUID out ++grep $PV7UUID out ++grep $PV2UUID out ++ ++vgreduce $vg1 "$dev7" ++ ++pvs --foreign -o+uuid |tee out ++grep $PV1UUID out ++grep $PV7UUID out ++grep $PV2UUID out ++ ++grep $PV7UUID out >out2 ++not grep $vg1 out2 ++ ++vgremove -ff $vg1 ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++aux wipefs_a "$dev7" +diff --git a/test/shell/duplicate-vgrename.sh b/test/shell/duplicate-vgrename.sh +new file mode 100644 +index 0000000..8628220 +--- /dev/null ++++ b/test/shell/duplicate-vgrename.sh +@@ -0,0 +1,319 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2008-2013 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. ++ ++SKIP_WITH_LVMLOCKD=1 ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux prepare_devs 4 ++ ++# a. 0 local, 1 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++ ++not vgrename $vg1 $vg2 ++vgs --foreign -o+uuid |tee out ++grep $UUID1 out ++not vgrename $UUID1 $vg2 ++vgs --foreign -o+uuid |tee out ++grep $UUID1 out ++ ++lvs --foreign ++ ++aux wipefs_a "$dev1" ++ ++# b. 0 local, 2 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other2" $vg1 ++aux enable_dev "$dev1" ++ ++not vgrename $vg1 $vg2 ++vgs --foreign -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++not grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++not vgrename $UUID1 $vg2 ++vgs --foreign -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++not grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++ ++lvs --foreign ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++ ++# c. 1 local, 1 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++aux enable_dev "$dev1" ++ ++vgrename $vg1 $vg2 ++vgs --foreign -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++not vgrename $vg2 $vg1 ++vgs --foreign -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++ ++lvs --foreign ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++ ++# d. 1 local, 2 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++aux disable_dev "$dev2" ++vgcreate $vg1 "$dev3" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID3=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other2" $vg1 ++aux enable_dev "$dev1" ++aux enable_dev "$dev2" ++ ++vgrename $vg1 $vg2 ++vgs --foreign -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++not vgrename $vg2 $vg1 ++vgs --foreign -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++ ++lvs --foreign ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++aux wipefs_a "$dev3" ++ ++# e. 2 local, 0 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++lvcreate -n ${lv1}_b -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux enable_dev "$dev1" ++ ++not vgrename $vg1 $vg2 ++vgs -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++not grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++vgrename $UUID1 $vg2 ++vgs -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++not vgrename $UUID2 $vg2 ++vgs -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++ ++lvs --foreign ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++ ++# f. 2 local, 1 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++lvcreate -n ${lv1}_b -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev2" ++vgcreate $vg1 "$dev3" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID3=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++vgchange -y --systemid "other" $vg1 ++aux enable_dev "$dev1" ++aux enable_dev "$dev2" ++lvs --foreign ++ ++not vgrename $vg1 $vg2 ++vgs --foreign -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++not grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++vgrename $UUID1 $vg2 ++vgs --foreign -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++vgrename $vg1 $vg3 ++vgs --foreign -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $vg3 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++not vgrename $vg2 $vg1 ++vgs --foreign -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $vg3 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++not vgrename $vg2 $vg3 ++vgs --foreign -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $vg3 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++ ++lvs --foreign ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++aux wipefs_a "$dev3" ++ ++# g. 3 local, 0 foreign ++# setup ++vgcreate $vg1 "$dev1" ++lvcreate -n $lv1 -l1 -an $vg1 ++UUID1=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev1" ++vgcreate $vg1 "$dev2" ++lvcreate -n ${lv1}_b -l1 -an $vg1 ++UUID2=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux disable_dev "$dev2" ++vgcreate $vg1 "$dev3" ++lvcreate -n ${lv1}_c -l1 -an $vg1 ++UUID3=$(vgs --noheading -o vg_uuid $vg1 | xargs) ++aux enable_dev "$dev1" ++aux enable_dev "$dev2" ++ ++not vgrename $vg1 $vg2 ++vgs -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++not grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++vgrename $UUID1 $vg2 ++vgs -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++not vgrename $vg1 $vg2 ++vgs -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++not vgrename $vg1 $vg3 ++vgs -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++not grep $vg3 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++not vgrename $UUID2 $vg2 ++vgs -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++not grep $vg3 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++vgrename $UUID2 $vg3 ++vgs -o+uuid |tee out ++lvs --foreign ++grep $vg1 out ++grep $vg2 out ++grep $vg3 out ++grep $UUID1 out ++grep $UUID2 out ++grep $UUID3 out ++ ++lvs --foreign ++ ++aux wipefs_a "$dev1" ++aux wipefs_a "$dev2" ++aux wipefs_a "$dev3" ++ +diff --git a/test/shell/integrity-dmeventd.sh b/test/shell/integrity-dmeventd.sh +index 58899ca..ed2436a 100644 +--- a/test/shell/integrity-dmeventd.sh ++++ b/test/shell/integrity-dmeventd.sh +@@ -22,9 +22,9 @@ mkdir -p $mnt + + aux prepare_devs 6 64 + +-for i in `seq 1 16384`; do echo -n "A" >> fileA; done +-for i in `seq 1 16384`; do echo -n "B" >> fileB; done +-for i in `seq 1 16384`; do echo -n "C" >> fileC; done ++printf "%0.sA" {1..16384} >> fileA ++printf "%0.sB" {1..16384} >> fileB ++printf "%0.sC" {1..16384} >> fileC + + # generate random data + dd if=/dev/urandom of=randA bs=512K count=2 +diff --git a/test/shell/integrity-large.sh b/test/shell/integrity-large.sh +index 0c36e4d..7a333c1 100644 +--- a/test/shell/integrity-large.sh ++++ b/test/shell/integrity-large.sh +@@ -25,9 +25,9 @@ mkdir -p $mnt + # raid1 LV needs to be extended to 512MB to test imeta being exended + aux prepare_devs 4 600 + +-for i in `seq 1 16384`; do echo -n "A" >> fileA; done +-for i in `seq 1 16384`; do echo -n "B" >> fileB; done +-for i in `seq 1 16384`; do echo -n "C" >> fileC; done ++printf "%0.sA" {1..16384} >> fileA ++printf "%0.sB" {1..16384} >> fileB ++printf "%0.sC" {1..16384} >> fileC + + # generate random data + dd if=/dev/urandom of=randA bs=512K count=2 +diff --git a/test/shell/integrity-misc.sh b/test/shell/integrity-misc.sh +index 73b0a67..a176f18 100644 +--- a/test/shell/integrity-misc.sh ++++ b/test/shell/integrity-misc.sh +@@ -22,9 +22,9 @@ mkdir -p $mnt + + aux prepare_devs 5 64 + +-for i in `seq 1 16384`; do echo -n "A" >> fileA; done +-for i in `seq 1 16384`; do echo -n "B" >> fileB; done +-for i in `seq 1 16384`; do echo -n "C" >> fileC; done ++printf "%0.sA" {1..16384} >> fileA ++printf "%0.sB" {1..16384} >> fileB ++printf "%0.sC" {1..16384} >> fileC + + # generate random data + dd if=/dev/urandom of=randA bs=512K count=2 +diff --git a/test/shell/integrity.sh b/test/shell/integrity.sh +index 7e4f2cb..6baccf0 100644 +--- a/test/shell/integrity.sh ++++ b/test/shell/integrity.sh +@@ -23,9 +23,9 @@ mkdir -p $mnt + + aux prepare_devs 5 64 + +-for i in `seq 1 16384`; do echo -n "A" >> fileA; done +-for i in `seq 1 16384`; do echo -n "B" >> fileB; done +-for i in `seq 1 16384`; do echo -n "C" >> fileC; done ++printf "%0.sA" {1..16384} >> fileA ++printf "%0.sB" {1..16384} >> fileB ++printf "%0.sC" {1..16384} >> fileC + + # generate random data + dd if=/dev/urandom of=randA bs=512K count=2 +diff --git a/test/shell/process-each-duplicate-vgnames.sh b/test/shell/process-each-duplicate-vgnames.sh +deleted file mode 100644 +index a59c3bd..0000000 +--- a/test/shell/process-each-duplicate-vgnames.sh ++++ /dev/null +@@ -1,55 +0,0 @@ +-#!/usr/bin/env bash +- +-# Copyright (C) 2008-2013 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. +- +-test_description='Test vgs with duplicate vg names' +-SKIP_WITH_LVMLOCKD=1 +-SKIP_WITH_LVMPOLLD=1 +- +-. lib/inittest +- +-aux prepare_devs 2 +- +-pvcreate "$dev1" +-pvcreate "$dev2" +- +-aux disable_dev "$dev1" "$dev2" +- +-aux enable_dev "$dev1" +-vgcreate $vg1 "$dev1" +-UUID1=$(vgs --noheading -o vg_uuid $vg1) +-aux disable_dev "$dev1" +- +-aux enable_dev "$dev2" +-vgcreate $vg1 "$dev2" +-UUID2=$(vgs --noheading -o vg_uuid $vg1) +- +-aux enable_dev "$dev1" +-pvscan --cache "$dev1" +-pvs "$dev1" +-pvs "$dev2" +- +-vgs -o+vg_uuid | tee err +-grep $UUID1 err +-grep $UUID2 err +- +-# should we specify and test which should be displayed? +-# vgs --noheading -o vg_uuid $vg1 >err +-# grep $UUID1 err +- +-aux disable_dev "$dev2" +-vgs -o+vg_uuid | tee err +-grep $UUID1 err +-not grep $UUID2 err +-aux enable_dev "$dev2" +-pvscan --cache "$dev2" +- +-aux disable_dev "$dev1" +-vgs -o+vg_uuid | tee err +-grep $UUID2 err +-not grep $UUID1 err +-aux enable_dev "$dev1" +diff --git a/test/shell/thin-foreign-repair.sh b/test/shell/thin-foreign-repair.sh +index 147a9a0..8b4018e 100644 +--- a/test/shell/thin-foreign-repair.sh ++++ b/test/shell/thin-foreign-repair.sh +@@ -56,7 +56,9 @@ dmsetup create "$THIN" --table "0 40960 thin $DM_DEV_DIR/mapper/$POOL 0" + + mkfs.ext4 "$DM_DEV_DIR/mapper/$THIN" + +-dmsetup remove "$THIN" ++aux udev_wait ++ ++dmsetup remove "$THIN" || { sleep .5 ; dmsetup remove "$THIN" } + + lvchange -an $vg/pool + +diff --git a/tools/command.c b/tools/command.c +index 50791b1..511dda1 100644 +--- a/tools/command.c ++++ b/tools/command.c +@@ -2319,7 +2319,8 @@ static void _print_val_man(struct command_name *cname, int opt_enum, int val_enu + } + + if (strchr(str, '|')) { +- line = strdup(str); ++ if (!(line = strdup(str))) ++ return; + _split_line(line, &line_argc, line_argv, '|'); + for (i = 0; i < line_argc; i++) { + if (i) +@@ -3606,9 +3607,12 @@ int main(int argc, char *argv[]) + goto out_free; + } + +- if (optind < argc) +- cmdname = strdup(argv[optind++]); +- else { ++ if (optind < argc) { ++ if (!(cmdname = strdup(argv[optind++]))) { ++ log_error("Out of memory."); ++ goto out_free; ++ } ++ } else { + log_error("Missing command name."); + goto out_free; + } +diff --git a/tools/lvconvert.c b/tools/lvconvert.c +index e969b44..cf93538 100644 +--- a/tools/lvconvert.c ++++ b/tools/lvconvert.c +@@ -5589,7 +5589,8 @@ static struct logical_volume *_lv_writecache_create(struct cmd_context *cmd, + + memcpy(&seg->writecache_settings, settings, sizeof(struct writecache_settings)); + +- add_seg_to_segs_using_this_lv(lv_fast, seg); ++ if (!add_seg_to_segs_using_this_lv(lv_fast, seg)) ++ return_NULL; + + return lv_wcorig; + } +diff --git a/tools/pvck.c b/tools/pvck.c +index 71bfc1b..a0f567e 100644 +--- a/tools/pvck.c ++++ b/tools/pvck.c +@@ -3065,11 +3065,9 @@ int pvck(struct cmd_context *cmd, int argc, char **argv) + + label_scan_setup_bcache(); + +- if (arg_is_set(cmd, dump_ARG)) { ++ if ((dump = arg_str_value(cmd, dump_ARG, NULL))) { + cmd->use_hints = 0; + +- dump = arg_str_value(cmd, dump_ARG, NULL); +- + if (!strcmp(dump, "metadata")) + ret = _dump_metadata(cmd, dump, &set, labelsector, dev, def, PRINT_CURRENT, 0); + +@@ -3096,11 +3094,9 @@ int pvck(struct cmd_context *cmd, int argc, char **argv) + return ECMD_PROCESSED; + } + +- if (arg_is_set(cmd, repairtype_ARG)) { ++ if ((repair = arg_str_value(cmd, repairtype_ARG, NULL))) { + cmd->use_hints = 0; + +- repair = arg_str_value(cmd, repairtype_ARG, NULL); +- + if (!strcmp(repair, "label_header")) + ret = _repair_label_header(cmd, repair, &set, labelsector, dev); + +diff --git a/tools/pvscan.c b/tools/pvscan.c +index 1bf543c..4d811da 100644 +--- a/tools/pvscan.c ++++ b/tools/pvscan.c +@@ -582,7 +582,7 @@ static int _online_pvscan_single(struct metadata_area *mda, void *baton) + + if (mda_is_ignored(mda)) + return 1; +- vg = mda->ops->vg_read(b->fid, "", mda, NULL, NULL); ++ vg = mda->ops->vg_read(b->cmd, b->fid, "", mda, NULL, NULL); + if (!vg) { + /* + * Many or most cases of bad metadata would be found in +diff --git a/tools/toollib.c b/tools/toollib.c +index 96d0d6d..89b6374 100644 +--- a/tools/toollib.c ++++ b/tools/toollib.c +@@ -1853,8 +1853,6 @@ static int _resolve_duplicate_vgnames(struct cmd_context *cmd, + if (lvmcache_vg_is_foreign(cmd, vgnl->vg_name, vgnl->vgid)) { + if (!id_write_format((const struct id*)vgnl->vgid, uuid, sizeof(uuid))) + stack; +- log_warn("WARNING: Ignoring foreign VG with matching name %s UUID %s.", +- vgnl->vg_name, uuid); + dm_list_del(&vgnl->list); + } else { + found++; +diff --git a/tools/vgchange.c b/tools/vgchange.c +index a10bf11..58c8ddc 100644 +--- a/tools/vgchange.c ++++ b/tools/vgchange.c +@@ -991,8 +991,13 @@ static int _vgchange_locktype_single(struct cmd_context *cmd, const char *vg_nam + * deactivate it. + */ + if (vg->lock_type && !strcmp(vg->lock_type, "sanlock") && +- (cmd->command->command_enum == vgchange_locktype_CMD)) +- deactivate_lv(cmd, vg->sanlock_lv); ++ (cmd->command->command_enum == vgchange_locktype_CMD)) { ++ if (!deactivate_lv(cmd, vg->sanlock_lv)) { ++ log_error("Failed to deativate %s.", ++ display_lvname(vg->sanlock_lv)); ++ return ECMD_FAILED; ++ } ++ } + + log_print_unless_silent("Volume group \"%s\" successfully changed", vg->name); + +diff --git a/tools/vgimportclone.c b/tools/vgimportclone.c +index be01861..ee1c28f 100644 +--- a/tools/vgimportclone.c ++++ b/tools/vgimportclone.c +@@ -315,6 +315,8 @@ retry_name: + goto_out; + log_debug("Using new VG name %s.", vp.new_vgname); + ++ lvmcache_destroy(cmd, 1, 0); ++ + /* + * Create a device filter so that we are only working with the devices + * in arg_import. With the original devs hidden (that arg_import were +@@ -325,7 +327,7 @@ retry_name: + init_internal_filtering(1); + dm_list_iterate_items(vd, &vp.arg_import) + internal_filter_allow(cmd->mem, vd->dev); +- lvmcache_destroy(cmd, 1, 0); ++ refresh_filters(cmd); + + log_debug("Changing VG %s to %s.", vp.old_vgname, vp.new_vgname); + +diff --git a/tools/vgmerge.c b/tools/vgmerge.c +index 903504c..895018a 100644 +--- a/tools/vgmerge.c ++++ b/tools/vgmerge.c +@@ -21,10 +21,8 @@ static struct volume_group *_vgmerge_vg_read(struct cmd_context *cmd, + struct volume_group *vg; + log_verbose("Checking for volume group \"%s\"", vg_name); + vg = vg_read_for_update(cmd, vg_name, NULL, 0, 0); +- if (vg_read_error(vg)) { +- release_vg(vg); ++ if (!vg) + return NULL; +- } + + if (vg_is_shared(vg)) { + log_error("vgmerge not allowed for lock_type %s", vg->lock_type); +diff --git a/tools/vgrename.c b/tools/vgrename.c +index 8b76d0b..f442f73 100644 +--- a/tools/vgrename.c ++++ b/tools/vgrename.c +@@ -183,7 +183,7 @@ int vgrename(struct cmd_context *cmd, int argc, char **argv) + vg_name_new = skip_dev_dir(cmd, argv[1], NULL); + + if (!validate_vg_rename_params(cmd, vg_name_old, vg_name_new)) +- return_0; ++ return_ECMD_FAILED; + + if (!(vp.vg_name_old = dm_pool_strdup(cmd->mem, vg_name_old))) + return_ECMD_FAILED; +diff --git a/tools/vgsplit.c b/tools/vgsplit.c +index 3dc19ec..1a422e6 100644 +--- a/tools/vgsplit.c ++++ b/tools/vgsplit.c +@@ -691,7 +691,7 @@ int vgsplit(struct cmd_context *cmd, int argc, char **argv) + + vg_to = vg_read_for_update(cmd, vg_name_to, NULL, 0, 0); + +- if (vg_read_error(vg_to)) { ++ if (!vg_to) { + log_error("Volume group \"%s\" became inconsistent: " + "please fix manually", vg_name_to); + goto bad; +-- +1.8.3.1 + diff --git a/SOURCES/0002-Merge-master-up-to-commit-be61bd6ff5c6.patch b/SOURCES/0002-Merge-master-up-to-commit-be61bd6ff5c6.patch new file mode 100644 index 0000000..0f1b14a --- /dev/null +++ b/SOURCES/0002-Merge-master-up-to-commit-be61bd6ff5c6.patch @@ -0,0 +1,210 @@ +From f540a18fd7f5f65599a6c85c0bd3ba84e54f1cc8 Mon Sep 17 00:00:00 2001 +From: Marian Csontos +Date: Thu, 28 May 2020 18:02:16 +0200 +Subject: [PATCH] Merge master up to commit be61bd6ff5c6 + +--- + VERSION | 2 +- + VERSION_DM | 2 +- + test/shell/cache-single-usage.sh | 13 +++++++++++++ + test/shell/integrity-dmeventd.sh | 8 ++++++++ + test/shell/integrity-large.sh | 8 ++++++++ + test/shell/integrity-misc.sh | 8 ++++++++ + test/shell/integrity.sh | 8 ++++++++ + test/shell/thin-foreign-repair.sh | 14 ++++++++++---- + tools/lvconvert.c | 15 +++++++++++++++ + 9 files changed, 72 insertions(+), 6 deletions(-) + +diff --git a/VERSION b/VERSION +index 00618e0..9ad7a70 100644 +--- a/VERSION ++++ b/VERSION +@@ -1 +1 @@ +-2.03.09(2)-RHEL8 (2020-04-21) ++2.03.09(2)-RHEL8 (2020-05-28) +diff --git a/VERSION_DM b/VERSION_DM +index b9ec43e..bcd97de 100644 +--- a/VERSION_DM ++++ b/VERSION_DM +@@ -1 +1 @@ +-1.02.171-RHEL8 (2020-04-21) ++1.02.171-RHEL8 (2020-05-28) +diff --git a/test/shell/cache-single-usage.sh b/test/shell/cache-single-usage.sh +index a885bf7..8936aa3 100644 +--- a/test/shell/cache-single-usage.sh ++++ b/test/shell/cache-single-usage.sh +@@ -127,4 +127,17 @@ umount "$mount_dir" + lvchange -an $vg/$lv1 + lvchange -an $vg/$lv2 + ++# misc tests ++ ++lvremove $vg ++ ++lvcreate -n $lv1 -l 2 -an $vg "$dev1" ++lvcreate -n $lv2 -l 2 -an $vg "$dev1" ++lvcreate -n $lv3 -l 2 -an $vg "$dev2" ++ ++lvconvert -y --type writecache --cachevol $lv3 $vg/$lv1 ++not lvconvert -y --type writecache --cachevol ${lv3}_cvol $vg/$lv2 ++not lvconvert -y --type cache --cachevol ${lv3}_cvol $vg/$lv2 ++not lvconvert -y --type cache --cachepool ${lv3}_cvol $vg/$lv2 ++ + vgremove -ff $vg +diff --git a/test/shell/integrity-dmeventd.sh b/test/shell/integrity-dmeventd.sh +index ed2436a..296f556 100644 +--- a/test/shell/integrity-dmeventd.sh ++++ b/test/shell/integrity-dmeventd.sh +@@ -109,6 +109,14 @@ _wait_recalc() { + sleep 1 + done + ++ # TODO: There is some strange bug, first leg of RAID with integrity ++ # enabled never gets in sync. I saw this in BB, but not when executing ++ # the commands manually ++ if test -z "$sync"; then ++ echo "TEST WARNING: Resync of dm-integrity device '$checklv' failed" ++ dmsetup status "$DM_DEV_DIR/mapper/${checklv/\//-}" ++ exit ++ fi + echo "timeout waiting for recalc" + return 1 + } +diff --git a/test/shell/integrity-large.sh b/test/shell/integrity-large.sh +index 7a333c1..5aba80e 100644 +--- a/test/shell/integrity-large.sh ++++ b/test/shell/integrity-large.sh +@@ -95,6 +95,14 @@ _wait_recalc() { + sleep 1 + done + ++ # TODO: There is some strange bug, first leg of RAID with integrity ++ # enabled never gets in sync. I saw this in BB, but not when executing ++ # the commands manually ++ if test -z "$sync"; then ++ echo "TEST WARNING: Resync of dm-integrity device '$checklv' failed" ++ dmsetup status "$DM_DEV_DIR/mapper/${checklv/\//-}" ++ exit ++ fi + echo "timeout waiting for recalc" + return 1 + } +diff --git a/test/shell/integrity-misc.sh b/test/shell/integrity-misc.sh +index a176f18..0d05689 100644 +--- a/test/shell/integrity-misc.sh ++++ b/test/shell/integrity-misc.sh +@@ -109,6 +109,14 @@ _wait_recalc() { + sleep 1 + done + ++ # TODO: There is some strange bug, first leg of RAID with integrity ++ # enabled never gets in sync. I saw this in BB, but not when executing ++ # the commands manually ++ if test -z "$sync"; then ++ echo "TEST WARNING: Resync of dm-integrity device '$checklv' failed" ++ dmsetup status "$DM_DEV_DIR/mapper/${checklv/\//-}" ++ exit ++ fi + echo "timeout waiting for recalc" + return 1 + } +diff --git a/test/shell/integrity.sh b/test/shell/integrity.sh +index 6baccf0..77e9430 100644 +--- a/test/shell/integrity.sh ++++ b/test/shell/integrity.sh +@@ -204,6 +204,14 @@ _wait_recalc() { + sleep 1 + done + ++ # TODO: There is some strange bug, first leg of RAID with integrity ++ # enabled never gets in sync. I saw this in BB, but not when executing ++ # the commands manually ++ if test -z "$sync"; then ++ echo "TEST WARNING: Resync of dm-integrity device '$checklv' failed" ++ dmsetup status "$DM_DEV_DIR/mapper/${checklv/\//-}" ++ exit ++ fi + echo "timeout waiting for recalc" + return 1 + } +diff --git a/test/shell/thin-foreign-repair.sh b/test/shell/thin-foreign-repair.sh +index 8b4018e..55e9f62 100644 +--- a/test/shell/thin-foreign-repair.sh ++++ b/test/shell/thin-foreign-repair.sh +@@ -17,9 +17,15 @@ SKIP_WITH_LVMPOLLD=1 + + . lib/inittest + ++clean_thin_() ++{ ++ aux udev_wait ++ dmsetup remove "$THIN" || { sleep .5 ; dmsetup remove "$THIN" ; } ++} ++ + cleanup_mounted_and_teardown() + { +- dmsetup remove $THIN || true ++ clean_thin_ || true + vgremove -ff $vg + aux teardown + } +@@ -56,9 +62,7 @@ dmsetup create "$THIN" --table "0 40960 thin $DM_DEV_DIR/mapper/$POOL 0" + + mkfs.ext4 "$DM_DEV_DIR/mapper/$THIN" + +-aux udev_wait +- +-dmsetup remove "$THIN" || { sleep .5 ; dmsetup remove "$THIN" } ++clean_thin_ + + lvchange -an $vg/pool + +@@ -72,3 +76,5 @@ lvchange -ay $vg/pool + dmsetup create "$THIN" --table "0 40960 thin $DM_DEV_DIR/mapper/$POOL 0" + + fsck -n "$DM_DEV_DIR/mapper/$THIN" ++ ++# exit calls cleanup_mounted_and_teardown +diff --git a/tools/lvconvert.c b/tools/lvconvert.c +index cf93538..8652252 100644 +--- a/tools/lvconvert.c ++++ b/tools/lvconvert.c +@@ -4264,6 +4264,11 @@ static int _lvconvert_cachevol_attach_single(struct cmd_context *cmd, + goto out; + } + ++ if (lv_is_cache_vol(cachevol_lv)) { ++ log_error("LV %s is already used as a cachevol.", display_lvname(cachevol_lv)); ++ goto out; ++ } ++ + /* Ensure the LV is not active elsewhere. */ + if (!lockd_lv(cmd, lv, "ex", 0)) + goto_out; +@@ -4347,6 +4352,11 @@ static int _lvconvert_cachepool_attach_single(struct cmd_context *cmd, + goto out; + } + ++ if (lv_is_cache_vol(cachepool_lv)) { ++ log_error("LV %s is already used as a cachevol.", display_lvname(cachepool_lv)); ++ goto out; ++ } ++ + if (cachepool_lv == lv) { + log_error("Use a different LV for cache pool LV and cache LV %s.", + display_lvname(cachepool_lv)); +@@ -5629,6 +5639,11 @@ static int _lvconvert_writecache_attach_single(struct cmd_context *cmd, + goto bad; + } + ++ if (lv_is_cache_vol(lv_fast)) { ++ log_error("LV %s is already used as a cachevol.", display_lvname(lv_fast)); ++ goto bad; ++ } ++ + /* + * To permit this we need to check the block size of the fs using lv + * (recently in libblkid) so that we can use a matching writecache +-- +1.8.3.1 + diff --git a/SOURCES/0003-Merge-master-up-to-commit-6eb9eba59bf5.patch b/SOURCES/0003-Merge-master-up-to-commit-6eb9eba59bf5.patch new file mode 100644 index 0000000..e084090 --- /dev/null +++ b/SOURCES/0003-Merge-master-up-to-commit-6eb9eba59bf5.patch @@ -0,0 +1,4660 @@ +From 7e80b28b83fc8829cc7d4532cc77df5d66b54f4e Mon Sep 17 00:00:00 2001 +From: David Teigland +Date: Thu, 28 May 2020 15:51:59 -0500 +Subject: [PATCH] Merge master up to commit 6eb9eba59bf5 + +--- + WHATS_NEW | 1 + + device_mapper/all.h | 4 + + device_mapper/libdm-deptree.c | 12 + + lib/cache/lvmcache.c | 4 + + lib/device/bcache.c | 2 +- + lib/device/dev-cache.c | 1 + + lib/device/dev-type.c | 36 +- + lib/metadata/cache_manip.c | 4 + + lib/metadata/integrity_manip.c | 32 +- + lib/metadata/lv.c | 3 + + lib/metadata/lv_manip.c | 24 +- + lib/metadata/metadata-exported.h | 5 +- + lib/metadata/metadata.c | 4 +- + lib/metadata/snapshot_manip.c | 2 - + lib/metadata/writecache_manip.c | 366 ++++++++++++++++--- + lib/report/report.c | 16 + + lib/writecache/writecache.c | 49 +++ + man/lvmcache.7_main | 30 +- + man/lvs.8_end | 4 + + scripts/blkdeactivate.sh.in | 6 + + test/shell/cachevol-cachedevice.sh | 212 +++++++++++ + test/shell/integrity-blocksize-2.sh | 128 +++++++ + test/shell/integrity-blocksize-3.sh | 285 +++++++++++++++ + test/shell/integrity-blocksize.sh | 108 +++++- + test/shell/integrity-large.sh | 3 + + test/shell/integrity-misc.sh | 27 +- + test/shell/integrity.sh | 46 ++- + test/shell/writecache-blocksize.sh | 342 ++++++++++++++++++ + test/shell/writecache-large.sh | 153 ++++++++ + test/shell/writecache-split.sh | 34 +- + test/shell/writecache.sh | 315 +++++++++++------ + tools/args.h | 17 +- + tools/command-lines.in | 142 +++++--- + tools/command.c | 3 + + tools/lvchange.c | 85 +++++ + tools/lvconvert.c | 681 ++++++++++++++++++++++-------------- + tools/lvcreate.c | 153 +++++++- + tools/lvmcmdline.c | 8 + + tools/toollib.c | 164 +++++++++ + tools/toollib.h | 3 + + tools/tools.h | 11 + + 41 files changed, 2976 insertions(+), 549 deletions(-) + create mode 100644 test/shell/cachevol-cachedevice.sh + create mode 100644 test/shell/integrity-blocksize-2.sh + create mode 100644 test/shell/integrity-blocksize-3.sh + create mode 100644 test/shell/writecache-blocksize.sh + create mode 100644 test/shell/writecache-large.sh + +diff --git a/WHATS_NEW b/WHATS_NEW +index c0267b7..c6dad99 100644 +--- a/WHATS_NEW ++++ b/WHATS_NEW +@@ -1,5 +1,6 @@ + Version 2.03.10 - + ================================= ++ Fix running out of free buffers for async writing for larger writes. + Add integrity with raid capability. + Fix support for lvconvert --repair used by foreign apps (i.e. Docker). + +diff --git a/device_mapper/all.h b/device_mapper/all.h +index f00b6a5..c3c6219 100644 +--- a/device_mapper/all.h ++++ b/device_mapper/all.h +@@ -951,6 +951,8 @@ struct writecache_settings { + uint64_t autocommit_time; /* in milliseconds */ + uint32_t fua; + uint32_t nofua; ++ uint32_t cleaner; ++ uint32_t max_age; + + /* + * Allow an unrecognized key and its val to be passed to the kernel for +@@ -970,6 +972,8 @@ struct writecache_settings { + unsigned autocommit_time_set:1; + unsigned fua_set:1; + unsigned nofua_set:1; ++ unsigned cleaner_set:1; ++ unsigned max_age_set:1; + }; + + int dm_tree_node_add_writecache_target(struct dm_tree_node *node, +diff --git a/device_mapper/libdm-deptree.c b/device_mapper/libdm-deptree.c +index 9ba24cb..2722a2c 100644 +--- a/device_mapper/libdm-deptree.c ++++ b/device_mapper/libdm-deptree.c +@@ -2670,6 +2670,10 @@ static int _writecache_emit_segment_line(struct dm_task *dmt, + count += 1; + if (seg->writecache_settings.nofua_set) + count += 1; ++ if (seg->writecache_settings.cleaner_set && seg->writecache_settings.cleaner) ++ count += 1; ++ if (seg->writecache_settings.max_age_set) ++ count += 2; + if (seg->writecache_settings.new_key) + count += 2; + +@@ -2713,6 +2717,14 @@ static int _writecache_emit_segment_line(struct dm_task *dmt, + EMIT_PARAMS(pos, " nofua"); + } + ++ if (seg->writecache_settings.cleaner_set && seg->writecache_settings.cleaner) { ++ EMIT_PARAMS(pos, " cleaner"); ++ } ++ ++ if (seg->writecache_settings.max_age_set) { ++ EMIT_PARAMS(pos, " max_age %u", seg->writecache_settings.max_age); ++ } ++ + if (seg->writecache_settings.new_key) { + EMIT_PARAMS(pos, " %s %s", + seg->writecache_settings.new_key, +diff --git a/lib/cache/lvmcache.c b/lib/cache/lvmcache.c +index 6cb5ff0..b1d05fb 100644 +--- a/lib/cache/lvmcache.c ++++ b/lib/cache/lvmcache.c +@@ -84,6 +84,7 @@ static DM_LIST_INIT(_unused_duplicates); + static DM_LIST_INIT(_prev_unused_duplicate_devs); + static int _vgs_locked = 0; + static int _found_duplicate_vgnames = 0; ++static int _outdated_warning = 0; + + int lvmcache_init(struct cmd_context *cmd) + { +@@ -1776,6 +1777,9 @@ int lvmcache_update_vg_from_read(struct volume_group *vg, unsigned precommitted) + log_warn("WARNING: outdated PV %s seqno %u has been removed in current VG %s seqno %u.", + dev_name(info->dev), info->summary_seqno, vg->name, vginfo->seqno); + ++ if (!_outdated_warning++) ++ log_warn("See vgck --updatemetadata to clear outdated metadata."); ++ + _drop_vginfo(info, vginfo); /* remove from vginfo->infos */ + dm_list_add(&vginfo->outdated_infos, &info->list); + } +diff --git a/lib/device/bcache.c b/lib/device/bcache.c +index a7d8055..7e7e185 100644 +--- a/lib/device/bcache.c ++++ b/lib/device/bcache.c +@@ -950,7 +950,7 @@ static struct block *_new_block(struct bcache *cache, int fd, block_address i, b + struct block *b; + + b = _alloc_block(cache); +- while (!b && !dm_list_empty(&cache->clean)) { ++ while (!b) { + b = _find_unused_clean_block(cache); + if (!b) { + if (can_wait) { +diff --git a/lib/device/dev-cache.c b/lib/device/dev-cache.c +index 6af559c..c3f7c49 100644 +--- a/lib/device/dev-cache.c ++++ b/lib/device/dev-cache.c +@@ -65,6 +65,7 @@ static int _insert(const char *path, const struct stat *info, + static void _dev_init(struct device *dev) + { + dev->fd = -1; ++ dev->bcache_fd = -1; + dev->read_ahead = -1; + + dev->ext.enabled = 0; +diff --git a/lib/device/dev-type.c b/lib/device/dev-type.c +index deb5d6a..896821d 100644 +--- a/lib/device/dev-type.c ++++ b/lib/device/dev-type.c +@@ -649,37 +649,23 @@ out: + #ifdef BLKID_WIPING_SUPPORT + int get_fs_block_size(struct device *dev, uint32_t *fs_block_size) + { +- blkid_probe probe = NULL; +- const char *block_size_str = NULL; +- uint64_t block_size_val; +- int r = 0; ++ char *block_size_str = NULL; + +- *fs_block_size = 0; +- +- if (!(probe = blkid_new_probe_from_filename(dev_name(dev)))) { +- log_error("Failed to create a new blkid probe for device %s.", dev_name(dev)); +- goto out; ++ if ((block_size_str = blkid_get_tag_value(NULL, "BLOCK_SIZE", dev_name(dev)))) { ++ *fs_block_size = (uint32_t)atoi(block_size_str); ++ free(block_size_str); ++ log_debug("Found blkid BLOCK_SIZE %u for fs on %s", *fs_block_size, dev_name(dev)); ++ return 1; ++ } else { ++ log_debug("No blkid BLOCK_SIZE for fs on %s", dev_name(dev)); ++ *fs_block_size = 0; ++ return 0; + } +- +- blkid_probe_enable_partitions(probe, 1); +- +- (void) blkid_probe_lookup_value(probe, "BLOCK_SIZE", &block_size_str, NULL); +- +- if (!block_size_str) +- goto out; +- +- block_size_val = strtoull(block_size_str, NULL, 10); +- +- *fs_block_size = (uint32_t)block_size_val; +- r = 1; +-out: +- if (probe) +- blkid_free_probe(probe); +- return r; + } + #else + int get_fs_block_size(struct device *dev, uint32_t *fs_block_size) + { ++ log_debug("Disabled blkid BLOCK_SIZE for fs."); + *fs_block_size = 0; + return 0; + } +diff --git a/lib/metadata/cache_manip.c b/lib/metadata/cache_manip.c +index 49b3850..a786e8b 100644 +--- a/lib/metadata/cache_manip.c ++++ b/lib/metadata/cache_manip.c +@@ -1094,6 +1094,10 @@ int cache_vol_set_params(struct cmd_context *cmd, + if (!meta_size) { + meta_size = _cache_min_metadata_size(pool_lv->size, chunk_size); + ++ /* fix bad value from _cache_min_metadata_size */ ++ if (meta_size > (pool_lv->size / 2)) ++ meta_size = pool_lv->size / 2; ++ + if (meta_size < min_meta_size) + meta_size = min_meta_size; + +diff --git a/lib/metadata/integrity_manip.c b/lib/metadata/integrity_manip.c +index 7942be0..3344527 100644 +--- a/lib/metadata/integrity_manip.c ++++ b/lib/metadata/integrity_manip.c +@@ -278,7 +278,7 @@ int lv_remove_integrity_from_raid(struct logical_volume *lv) + return 1; + } + +-static int _set_integrity_block_size(struct cmd_context *cmd, struct logical_volume *lv, ++static int _set_integrity_block_size(struct cmd_context *cmd, struct logical_volume *lv, int is_active, + struct integrity_settings *settings, + int lbs_4k, int lbs_512, int pbs_4k, int pbs_512) + { +@@ -375,7 +375,13 @@ static int _set_integrity_block_size(struct cmd_context *cmd, struct logical_vol + } + + if (!settings->block_size) { +- if (fs_block_size <= 4096) ++ if (is_active && lbs_512) { ++ /* increasing the lbs from 512 to 4k under an active LV could cause problems ++ for an application that expects a given io size/alignment is possible. */ ++ settings->block_size = 512; ++ if (fs_block_size > 512) ++ log_print("Limiting integrity block size to 512 because the LV is active."); ++ } else if (fs_block_size <= 4096) + settings->block_size = fs_block_size; + else + settings->block_size = 4096; /* dm-integrity max is 4096 */ +@@ -587,13 +593,33 @@ int lv_add_integrity_to_raid(struct logical_volume *lv, struct integrity_setting + } + } + ++ if (!is_active) { ++ /* checking block size of fs on the lv requires the lv to be active */ ++ if (!activate_lv(cmd, lv)) { ++ log_error("Failed to activate LV to check block size %s", display_lvname(lv)); ++ goto bad; ++ } ++ if (!sync_local_dev_names(cmd)) ++ stack; ++ } ++ + /* + * Set settings->block_size which will be copied to segment settings below. + * integrity block size chosen based on device logical block size and + * file system block size. + */ +- if (!_set_integrity_block_size(cmd, lv, settings, lbs_4k, lbs_512, pbs_4k, pbs_512)) ++ if (!_set_integrity_block_size(cmd, lv, is_active, settings, lbs_4k, lbs_512, pbs_4k, pbs_512)) { ++ if (!is_active && !deactivate_lv(cmd, lv)) ++ stack; + goto_bad; ++ } ++ ++ if (!is_active) { ++ if (!deactivate_lv(cmd, lv)) { ++ log_error("Failed to deactivate LV after checking block size %s", display_lvname(lv)); ++ goto bad; ++ } ++ } + + /* + * For each rimage, move its segments to a new rimage_iorig and give +diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c +index 4ee58b4..fac47e5 100644 +--- a/lib/metadata/lv.c ++++ b/lib/metadata/lv.c +@@ -1412,6 +1412,9 @@ char *lv_attr_dup_with_info_and_seg_status(struct dm_pool *mem, const struct lv_ + } else if (lvdm->seg_status.type == SEG_STATUS_THIN) { + if (lvdm->seg_status.thin->fail) + repstr[8] = 'F'; ++ } else if (lvdm->seg_status.type == SEG_STATUS_WRITECACHE) { ++ if (lvdm->seg_status.writecache->error) ++ repstr[8] = 'E'; + } else if (lvdm->seg_status.type == SEG_STATUS_UNKNOWN) + repstr[8] = 'X'; /* Unknown */ + +diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c +index 1311f70..1642b90 100644 +--- a/lib/metadata/lv_manip.c ++++ b/lib/metadata/lv_manip.c +@@ -5066,6 +5066,7 @@ static int _lvresize_check(struct logical_volume *lv, + struct lvresize_params *lp) + { + struct volume_group *vg = lv->vg; ++ struct lv_segment *seg = first_seg(lv); + + if (lv_is_external_origin(lv)) { + /* +@@ -5089,6 +5090,12 @@ static int _lvresize_check(struct logical_volume *lv, + return 0; + } + ++ if (seg && (seg_is_raid4(seg) || seg_is_any_raid5(seg)) && seg->area_count < 3) { ++ log_error("Cannot resize %s LV %s. Convert to more stripes first.", ++ lvseg_name(seg), display_lvname(lv)); ++ return 0; ++ } ++ + if (lv_is_raid(lv) && + lp->resize == LV_REDUCE) { + unsigned attrs; +@@ -6568,7 +6575,20 @@ int lv_remove_single(struct cmd_context *cmd, struct logical_volume *lv, + } + } + +- if (lv_is_used_cache_pool(lv) || lv_is_cache_vol(lv)) { ++ if (lv_is_cache_vol(lv)) { ++ if ((cache_seg = get_only_segment_using_this_lv(lv))) { ++ /* When used with cache, lvremove on cachevol also removes the cache! */ ++ if (seg_is_cache(cache_seg)) { ++ if (!lv_cache_remove(cache_seg->lv)) ++ return_0; ++ } else if (seg_is_writecache(cache_seg)) { ++ log_error("Detach cachevol before removing."); ++ return 0; ++ } ++ } ++ } ++ ++ if (lv_is_used_cache_pool(lv)) { + /* Cache pool removal drops cache layer + * If the cache pool is not linked, we can simply remove it. */ + if (!(cache_seg = get_only_segment_using_this_lv(lv))) +@@ -6832,7 +6852,7 @@ static int _lv_update_and_reload(struct logical_volume *lv, int origin_only) + } + + if (!(origin_only ? suspend_lv_origin(vg->cmd, lock_lv) : suspend_lv(vg->cmd, lock_lv))) { +- log_error("Failed to lock logical volume %s.", ++ log_error("Failed to suspend logical volume %s.", + display_lvname(lock_lv)); + vg_revert(vg); + } else if (!(r = vg_commit(vg))) +diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h +index 083f74a..0cc5f37 100644 +--- a/lib/metadata/metadata-exported.h ++++ b/lib/metadata/metadata-exported.h +@@ -89,8 +89,7 @@ + #define PARTIAL_LV UINT64_C(0x0000000001000000) /* LV - derived flag, not + written out in metadata*/ + +-//#define POSTORDER_FLAG UINT64_C(0x0000000002000000) /* Not real flags, reserved for +-//#define POSTORDER_OPEN_FLAG UINT64_C(0x0000000004000000) temporary use inside vg_read_internal. */ ++#define WRITECACHE_ORIGIN UINT64_C(0x0000000002000000) + #define INTEGRITY_METADATA UINT64_C(0x0000000004000000) /* LV - Internal use only */ + #define VIRTUAL_ORIGIN UINT64_C(0x0000000008000000) /* LV - internal use only */ + +@@ -955,6 +954,7 @@ struct lvcreate_params { + int thin_chunk_size_calc_policy; + unsigned suppress_zero_warn : 1; + unsigned needs_lockd_init : 1; ++ unsigned ignore_type : 1; + + const char *vg_name; /* only-used when VG is not yet opened (in /tools) */ + const char *lv_name; /* all */ +@@ -1097,6 +1097,7 @@ int lv_is_cow(const struct logical_volume *lv); + int lv_is_cache_origin(const struct logical_volume *lv); + int lv_is_writecache_origin(const struct logical_volume *lv); + int lv_is_writecache_cachevol(const struct logical_volume *lv); ++int writecache_settings_to_str_list(struct writecache_settings *settings, struct dm_list *result, struct dm_pool *mem); + + int lv_is_integrity_origin(const struct logical_volume *lv); + +diff --git a/lib/metadata/metadata.c b/lib/metadata/metadata.c +index 4b8dce9..c0d4206 100644 +--- a/lib/metadata/metadata.c ++++ b/lib/metadata/metadata.c +@@ -4875,8 +4875,10 @@ static struct volume_group *_vg_read(struct cmd_context *cmd, + } + } + +- if (found_old_metadata) ++ if (found_old_metadata) { + log_warn("WARNING: Inconsistent metadata found for VG %s.", vgname); ++ log_warn("See vgck --updatemetadata to correct inconsistency."); ++ } + + vg = NULL; + +diff --git a/lib/metadata/snapshot_manip.c b/lib/metadata/snapshot_manip.c +index 3faea0e..0f48e62 100644 +--- a/lib/metadata/snapshot_manip.c ++++ b/lib/metadata/snapshot_manip.c +@@ -389,8 +389,6 @@ int validate_snapshot_origin(const struct logical_volume *origin_lv) + err = "raid subvolumes"; + } else if (lv_is_raid(origin_lv) && lv_raid_has_integrity((struct logical_volume *)origin_lv)) { + err = "raid with integrity"; +- } else if (lv_is_writecache(origin_lv)) { +- err = "writecache"; + } + + if (err) { +diff --git a/lib/metadata/writecache_manip.c b/lib/metadata/writecache_manip.c +index 31d069e..7ad8c75 100644 +--- a/lib/metadata/writecache_manip.c ++++ b/lib/metadata/writecache_manip.c +@@ -19,13 +19,24 @@ + #include "lib/commands/toolcontext.h" + #include "lib/display/display.h" + #include "lib/metadata/segtype.h" ++#include "lib/metadata/lv_alloc.h" + #include "lib/activate/activate.h" + #include "lib/config/defaults.h" ++#include "lib/datastruct/str_list.h" + + int lv_is_writecache_origin(const struct logical_volume *lv) + { + struct lv_segment *seg; + ++ /* ++ * This flag is needed when removing writecache from an origin ++ * in which case the lv connections have been destroyed and ++ * identifying a writecache origin by these connections doesn't ++ * work. ++ */ ++ if (lv->status & WRITECACHE_ORIGIN) ++ return 1; ++ + /* Make sure there's exactly one segment in segs_using_this_lv! */ + if (dm_list_empty(&lv->segs_using_this_lv) || + (dm_list_size(&lv->segs_using_this_lv) > 1)) +@@ -48,46 +59,6 @@ int lv_is_writecache_cachevol(const struct logical_volume *lv) + return 0; + } + +-static int _lv_writecache_detach(struct cmd_context *cmd, struct logical_volume *lv, +- struct logical_volume *lv_fast) +-{ +- struct lv_segment *seg = first_seg(lv); +- struct logical_volume *origin; +- +- if (!seg_is_writecache(seg)) { +- log_error("LV %s segment is not writecache.", display_lvname(lv)); +- return 0; +- } +- +- if (!seg->writecache) { +- log_error("LV %s writecache segment has no writecache.", display_lvname(lv)); +- return 0; +- } +- +- if (!(origin = seg_lv(seg, 0))) { +- log_error("LV %s writecache segment has no origin", display_lvname(lv)); +- return 0; +- } +- +- if (!remove_seg_from_segs_using_this_lv(seg->writecache, seg)) +- return_0; +- +- lv_set_visible(seg->writecache); +- +- lv->status &= ~WRITECACHE; +- seg->writecache = NULL; +- +- lv_fast->status &= ~LV_CACHE_VOL; +- +- if (!remove_layer_from_lv(lv, origin)) +- return_0; +- +- if (!lv_remove(origin)) +- return_0; +- +- return 1; +-} +- + static int _get_writecache_kernel_error(struct cmd_context *cmd, + struct logical_volume *lv, + uint32_t *kernel_error) +@@ -131,13 +102,64 @@ fail: + return 0; + } + +-int lv_detach_writecache_cachevol(struct logical_volume *lv, int noflush) ++static void _rename_detached_cvol(struct cmd_context *cmd, struct logical_volume *lv_fast) ++{ ++ struct volume_group *vg = lv_fast->vg; ++ char cvol_name[NAME_LEN]; ++ char *suffix, *cvol_name_dup; ++ ++ /* ++ * Rename lv_fast back to its original name, without the _cvol ++ * suffix that was added when lv_fast was attached for caching. ++ * If the name is in use, generate new lvol%d. ++ * Failing to rename is not really a problem, so we intentionally ++ * do not consider some things here as errors. ++ */ ++ if (!dm_strncpy(cvol_name, lv_fast->name, sizeof(cvol_name)) || ++ !(suffix = strstr(cvol_name, "_cvol"))) { ++ log_debug("LV %s has no suffix for cachevol (skipping rename).", ++ display_lvname(lv_fast)); ++ return; ++ } ++ ++ *suffix = 0; ++ if (lv_name_is_used_in_vg(vg, cvol_name, NULL) && ++ !generate_lv_name(vg, "lvol%d", cvol_name, sizeof(cvol_name))) { ++ log_warn("Failed to generate new unique name for unused LV %s", lv_fast->name); ++ return; ++ } ++ ++ if (!(cvol_name_dup = dm_pool_strdup(vg->vgmem, cvol_name))) { ++ stack; ++ return; ++ } ++ ++ lv_fast->name = cvol_name_dup; ++} ++ ++static int _lv_detach_writecache_cachevol_inactive(struct logical_volume *lv, int noflush) + { + struct cmd_context *cmd = lv->vg->cmd; ++ struct volume_group *vg = lv->vg; + struct logical_volume *lv_fast; ++ struct logical_volume *lv_wcorig; ++ struct lv_segment *seg = first_seg(lv); + uint32_t kernel_error = 0; + +- lv_fast = first_seg(lv)->writecache; ++ if (!seg_is_writecache(seg)) { ++ log_error("LV %s segment is not writecache.", display_lvname(lv)); ++ return 0; ++ } ++ ++ if (!(lv_fast = seg->writecache)) { ++ log_error("LV %s writecache segment has no writecache.", display_lvname(lv)); ++ return 0; ++ } ++ ++ if (!(lv_wcorig = seg_lv(seg, 0))) { ++ log_error("LV %s writecache segment has no origin", display_lvname(lv)); ++ return 0; ++ } + + if (noflush) + goto detach; +@@ -157,6 +179,8 @@ int lv_detach_writecache_cachevol(struct logical_volume *lv, int noflush) + + if (!sync_local_dev_names(cmd)) { + log_error("Failed to sync local devices before detaching writecache."); ++ if (!deactivate_lv(cmd, lv)) ++ log_error("Failed to deactivate %s.", display_lvname(lv)); + return 0; + } + +@@ -176,7 +200,8 @@ int lv_detach_writecache_cachevol(struct logical_volume *lv, int noflush) + + if (kernel_error) { + log_error("Failed to flush writecache (error %u) for %s.", kernel_error, display_lvname(lv)); +- deactivate_lv(cmd, lv); ++ if (!deactivate_lv(cmd, lv)) ++ log_error("Failed to deactivate %s.", display_lvname(lv)); + return 0; + } + +@@ -188,11 +213,262 @@ int lv_detach_writecache_cachevol(struct logical_volume *lv, int noflush) + lv->status &= ~LV_TEMPORARY; + + detach: +- if (!_lv_writecache_detach(cmd, lv, lv_fast)) { +- log_error("Failed to detach writecache from %s", display_lvname(lv)); ++ if (!remove_seg_from_segs_using_this_lv(lv_fast, seg)) ++ return_0; ++ ++ lv->status &= ~WRITECACHE; ++ seg->writecache = NULL; ++ ++ if (!remove_layer_from_lv(lv, lv_wcorig)) ++ return_0; ++ ++ if (!lv_remove(lv_wcorig)) ++ return_0; ++ ++ lv_set_visible(lv_fast); ++ lv_fast->status &= ~LV_CACHE_VOL; ++ ++ _rename_detached_cvol(cmd, lv_fast); ++ ++ if (!vg_write(vg) || !vg_commit(vg)) ++ return_0; ++ ++ return 1; ++} ++ ++static int _lv_detach_writecache_cachevol_active(struct logical_volume *lv, int noflush) ++{ ++ struct cmd_context *cmd = lv->vg->cmd; ++ struct volume_group *vg = lv->vg; ++ struct logical_volume *lv_fast; ++ struct logical_volume *lv_wcorig; ++ struct logical_volume *lv_old; ++ struct lv_segment *seg = first_seg(lv); ++ uint32_t kernel_error = 0; ++ ++ if (!seg_is_writecache(seg)) { ++ log_error("LV %s segment is not writecache.", display_lvname(lv)); ++ return 0; ++ } ++ ++ if (!(lv_fast = seg->writecache)) { ++ log_error("LV %s writecache segment has no writecache.", display_lvname(lv)); + return 0; + } + ++ if (!(lv_wcorig = seg_lv(seg, 0))) { ++ log_error("LV %s writecache segment has no origin", display_lvname(lv)); ++ return 0; ++ } ++ ++ if (noflush) ++ goto detach; ++ ++ if (!lv_writecache_message(lv, "flush_on_suspend")) { ++ log_error("Failed to set flush_on_suspend in writecache detach %s.", display_lvname(lv)); ++ return 0; ++ } ++ ++ detach: ++ if (!remove_seg_from_segs_using_this_lv(lv_fast, seg)) { ++ log_error("Failed to remove seg in writecache detach."); ++ return 0; ++ } ++ ++ lv->status &= ~WRITECACHE; ++ seg->writecache = NULL; ++ ++ if (!remove_layer_from_lv(lv, lv_wcorig)) { ++ log_error("Failed to remove lv layer in writecache detach."); ++ return 0; ++ } ++ ++ /* ++ * vg_write(), suspend_lv(), vg_commit(), resume_lv(). ++ * usually done by lv_update_and_reload for an active lv, ++ * but in this case we need to check for writecache errors ++ * after suspend. ++ */ ++ ++ if (!vg_write(vg)) { ++ log_error("Failed to write VG in writecache detach."); ++ return 0; ++ } ++ ++ /* ++ * The version of LV before removal of writecache. When need to ++ * check for kernel errors based on the old version of LV which ++ * is still present in the kernel. ++ */ ++ if (!(lv_old = (struct logical_volume *)lv_committed(lv))) { ++ log_error("Failed to get lv_committed in writecache detach."); ++ return 0; ++ } ++ ++ /* ++ * suspend does not use 'lv' as we know it here, but grabs the ++ * old (precommitted) version of 'lv' using lv_committed(), ++ * which is from vg->vg_comitted. ++ */ ++ log_debug("Suspending writecache to detach %s", display_lvname(lv)); ++ ++ if (!suspend_lv(cmd, lv)) { ++ log_error("Failed to suspend LV in writecache detach."); ++ vg_revert(vg); ++ return 0; ++ } ++ ++ log_debug("Checking writecache errors to detach."); ++ ++ if (!_get_writecache_kernel_error(cmd, lv_old, &kernel_error)) { ++ log_error("Failed to get writecache error status for %s.", display_lvname(lv_old)); ++ return 0; ++ } ++ ++ if (kernel_error) { ++ log_error("Failed to flush writecache (error %u) for %s.", kernel_error, display_lvname(lv)); ++ return 0; ++ } ++ ++ if (!vg_commit(vg)) { ++ log_error("Failed to commit VG in writecache detach."); ++ return 0; ++ } ++ ++ /* ++ * Since vg_commit has happened, vg->vg_committed is now the ++ * newest copy of lv, so resume uses the 'lv' that we know ++ * here. ++ */ ++ log_debug("Resuming after writecache detached %s", display_lvname(lv)); ++ ++ if (!resume_lv(cmd, lv)) { ++ log_error("Failed to resume LV in writecache detach."); ++ return 0; ++ } ++ ++ log_debug("Deactivating previous cachevol %s", display_lvname(lv_fast)); ++ ++ if (!deactivate_lv(cmd, lv_fast)) ++ log_error("Failed to deactivate previous cachevol in writecache detach."); ++ ++ /* ++ * Needed for lv_is_writecache_origin to know lv_wcorig was ++ * a writecache origin, which is needed so that the -real ++ * dm uuid suffix is applied, which is needed for deactivate to ++ * work. This is a hacky roundabout way of setting the -real ++ * uuid suffix (it would be nice to have a deactivate command ++ * that accepts a dm uuid.) ++ */ ++ lv_wcorig->status |= WRITECACHE_ORIGIN; ++ ++ log_debug("Deactivating previous wcorig %s", display_lvname(lv_wcorig)); ++ ++ if (!lv_deactivate(cmd, NULL, lv_wcorig)) ++ log_error("Failed to deactivate previous wcorig LV in writecache detach."); ++ ++ log_debug("Removing previous wcorig %s", display_lvname(lv_wcorig)); ++ ++ if (!lv_remove(lv_wcorig)) { ++ log_error("Failed to remove previous wcorig LV in writecache detach."); ++ return 0; ++ } ++ ++ lv_set_visible(lv_fast); ++ lv_fast->status &= ~LV_CACHE_VOL; ++ ++ _rename_detached_cvol(cmd, lv_fast); ++ ++ if (!vg_write(vg) || !vg_commit(vg)) { ++ log_error("Failed to write and commit VG in writecache detach."); ++ return 0; ++ } ++ ++ return 1; ++} ++ ++int lv_detach_writecache_cachevol(struct logical_volume *lv, int noflush) ++{ ++ if (lv_is_active(lv)) ++ return _lv_detach_writecache_cachevol_active(lv, noflush); ++ else ++ return _lv_detach_writecache_cachevol_inactive(lv, noflush); ++} ++ ++static int _writecache_setting_str_list_add(const char *field, uint64_t val, char *val_str, struct dm_list *result, struct dm_pool *mem) ++{ ++ char buf[128]; ++ char *list_item; ++ int len; ++ ++ if (val_str) { ++ if (dm_snprintf(buf, sizeof(buf), "%s=%s", field, val_str) < 0) ++ return_0; ++ } else { ++ if (dm_snprintf(buf, sizeof(buf), "%s=%llu", field, (unsigned long long)val) < 0) ++ return_0; ++ } ++ ++ len = strlen(buf) + 1; ++ ++ if (!(list_item = dm_pool_zalloc(mem, len))) ++ return_0; ++ ++ memcpy(list_item, buf, len); ++ ++ if (!str_list_add_no_dup_check(mem, result, list_item)) ++ return_0; ++ ++ return 1; ++} ++ ++int writecache_settings_to_str_list(struct writecache_settings *settings, struct dm_list *result, struct dm_pool *mem) ++{ ++ int errors = 0; ++ ++ if (settings->high_watermark_set) ++ if (!_writecache_setting_str_list_add("high_watermark", settings->high_watermark, NULL, result, mem)) ++ errors++; ++ ++ if (settings->low_watermark_set) ++ if (!_writecache_setting_str_list_add("low_watermark", settings->low_watermark, NULL, result, mem)) ++ errors++; ++ ++ if (settings->writeback_jobs_set) ++ if (!_writecache_setting_str_list_add("writeback_jobs", settings->writeback_jobs, NULL, result, mem)) ++ errors++; ++ ++ if (settings->autocommit_blocks_set) ++ if (!_writecache_setting_str_list_add("autocommit_blocks", settings->autocommit_blocks, NULL, result, mem)) ++ errors++; ++ ++ if (settings->autocommit_time_set) ++ if (!_writecache_setting_str_list_add("autocommit_time", settings->autocommit_time, NULL, result, mem)) ++ errors++; ++ ++ if (settings->fua_set) ++ if (!_writecache_setting_str_list_add("fua", (uint64_t)settings->fua, NULL, result, mem)) ++ errors++; ++ ++ if (settings->nofua_set) ++ if (!_writecache_setting_str_list_add("nofua", (uint64_t)settings->nofua, NULL, result, mem)) ++ errors++; ++ ++ if (settings->cleaner_set && settings->cleaner) ++ if (!_writecache_setting_str_list_add("cleaner", (uint64_t)settings->cleaner, NULL, result, mem)) ++ errors++; ++ ++ if (settings->max_age_set) ++ if (!_writecache_setting_str_list_add("max_age", (uint64_t)settings->max_age, NULL, result, mem)) ++ errors++; ++ ++ if (settings->new_key && settings->new_val) ++ if (!_writecache_setting_str_list_add(settings->new_key, 0, settings->new_val, result, mem)) ++ errors++; ++ ++ if (errors) ++ log_warn("Failed to create list of writecache settings."); ++ + return 1; + } + +diff --git a/lib/report/report.c b/lib/report/report.c +index 170df69..74ec74c 100644 +--- a/lib/report/report.c ++++ b/lib/report/report.c +@@ -1430,6 +1430,16 @@ static int _cache_settings_disp(struct dm_report *rh, struct dm_pool *mem, + struct _str_list_append_baton baton; + struct dm_list dummy_list; /* dummy list to display "nothing" */ + ++ if (seg_is_writecache(seg)) { ++ if (!(result = str_list_create(mem))) ++ return_0; ++ ++ if (!writecache_settings_to_str_list((struct writecache_settings *)&seg->writecache_settings, result, mem)) ++ return_0; ++ ++ return _field_set_string_list(rh, field, result, private, 0, NULL); ++ } ++ + if (seg_is_cache(seg) && lv_is_cache_vol(seg->pool_lv)) + setting_seg = seg; + +@@ -3802,6 +3812,12 @@ static int _lvhealthstatus_disp(struct dm_report *rh, struct dm_pool *mem, + health = "failed"; + else if (lvdm->seg_status.cache->read_only) + health = "metadata_read_only"; ++ } else if (lv_is_writecache(lv) && (lvdm->seg_status.type != SEG_STATUS_NONE)) { ++ if (lvdm->seg_status.type != SEG_STATUS_WRITECACHE) ++ return _field_set_value(field, GET_FIRST_RESERVED_NAME(health_undef), ++ GET_FIELD_RESERVED_VALUE(health_undef)); ++ if (lvdm->seg_status.writecache->error) ++ health = "error"; + } else if (lv_is_thin_pool(lv) && (lvdm->seg_status.type != SEG_STATUS_NONE)) { + if (lvdm->seg_status.type != SEG_STATUS_THIN_POOL) + return _field_set_value(field, GET_FIRST_RESERVED_NAME(health_undef), +diff --git a/lib/writecache/writecache.c b/lib/writecache/writecache.c +index 130922a..c7aea28 100644 +--- a/lib/writecache/writecache.c ++++ b/lib/writecache/writecache.c +@@ -26,6 +26,9 @@ + #include "lib/metadata/lv_alloc.h" + #include "lib/config/defaults.h" + ++static int _writecache_cleaner_supported; ++static int _writecache_max_age_supported; ++ + #define SEG_LOG_ERROR(t, p...) \ + log_error(t " segment %s of logical volume %s.", ## p, \ + dm_config_parent_name(sn), seg->lv->name), 0; +@@ -120,6 +123,18 @@ static int _writecache_text_import(struct lv_segment *seg, + seg->writecache_settings.nofua_set = 1; + } + ++ if (dm_config_has_node(sn, "cleaner")) { ++ if (!dm_config_get_uint32(sn, "cleaner", &seg->writecache_settings.cleaner)) ++ return SEG_LOG_ERROR("Unknown writecache_setting in"); ++ seg->writecache_settings.cleaner_set = 1; ++ } ++ ++ if (dm_config_has_node(sn, "max_age")) { ++ if (!dm_config_get_uint32(sn, "max_age", &seg->writecache_settings.max_age)) ++ return SEG_LOG_ERROR("Unknown writecache_setting in"); ++ seg->writecache_settings.max_age_set = 1; ++ } ++ + if (dm_config_has_node(sn, "writecache_setting_key")) { + const char *key; + const char *val; +@@ -184,6 +199,14 @@ static int _writecache_text_export(const struct lv_segment *seg, + outf(f, "nofua = %u", seg->writecache_settings.nofua); + } + ++ if (seg->writecache_settings.cleaner_set && seg->writecache_settings.cleaner) { ++ outf(f, "cleaner = %u", seg->writecache_settings.cleaner); ++ } ++ ++ if (seg->writecache_settings.max_age_set) { ++ outf(f, "max_age = %u", seg->writecache_settings.max_age); ++ } ++ + if (seg->writecache_settings.new_key && seg->writecache_settings.new_val) { + outf(f, "writecache_setting_key = \"%s\"", + seg->writecache_settings.new_key); +@@ -208,6 +231,7 @@ static int _target_present(struct cmd_context *cmd, + { + static int _writecache_checked = 0; + static int _writecache_present = 0; ++ uint32_t maj, min, patchlevel; + + if (!activation()) + return 0; +@@ -215,6 +239,19 @@ static int _target_present(struct cmd_context *cmd, + if (!_writecache_checked) { + _writecache_checked = 1; + _writecache_present = target_present(cmd, TARGET_NAME_WRITECACHE, 1); ++ ++ if (!target_version(TARGET_NAME_WRITECACHE, &maj, &min, &patchlevel)) ++ return_0; ++ ++ if (maj < 1) { ++ log_error("writecache target version older than minimum 1.0.0"); ++ return 0; ++ } ++ ++ if (min >= 2) { ++ _writecache_cleaner_supported = 1; ++ _writecache_max_age_supported = 1; ++ } + } + + return _writecache_present; +@@ -257,6 +294,18 @@ static int _writecache_add_target_line(struct dev_manager *dm, + return 0; + } + ++ if (!_writecache_cleaner_supported && seg->writecache_settings.cleaner_set && seg->writecache_settings.cleaner) { ++ log_warn("WARNING: ignoring writecache setting \"cleaner\" which is not supported by kernel for LV %s.", seg->lv->name); ++ seg->writecache_settings.cleaner = 0; ++ seg->writecache_settings.cleaner_set = 0; ++ } ++ ++ if (!_writecache_max_age_supported && seg->writecache_settings.max_age_set) { ++ log_warn("WARNING: ignoring writecache setting \"max_age\" which is not supported by kernel for LV %s.", seg->lv->name); ++ seg->writecache_settings.max_age = 0; ++ seg->writecache_settings.max_age_set = 0; ++ } ++ + if ((pmem = lv_on_pmem(seg->writecache)) < 0) + return_0; + +diff --git a/man/lvmcache.7_main b/man/lvmcache.7_main +index 425904e..244ea1e 100644 +--- a/man/lvmcache.7_main ++++ b/man/lvmcache.7_main +@@ -34,8 +34,6 @@ LVM refers to this using the LV type \fBwritecache\fP. + + .SH USAGE + +-Both kinds of caching use similar lvm commands: +- + .B 1. Identify main LV that needs caching + + The main LV may already exist, and is located on larger, slower devices. +@@ -133,6 +131,22 @@ attached. + main vg -wi------- linear /dev/slow_hhd + .fi + ++.SS Create a new LV with caching. ++ ++A new LV can be created with caching attached at the time of creation ++using the following command: ++ ++.nf ++$ lvcreate --type cache|writecache -n Name -L Size ++ --cachedevice /dev/fast_ssd vg /dev/slow_hhd ++.fi ++ ++The main LV is created with the specified Name and Size from the slow_hhd. ++A hidden fast LV is created on the fast_ssd and is then attached to the ++new main LV. If the fast_ssd is unused, the entire disk will be used as ++the cache unless the --cachesize option is used to specify a size for the ++fast LV. The --cachedevice option can be repeated to use multiple disks ++for the fast LV. + + .SH OPTIONS + +@@ -162,6 +176,18 @@ that cannot be used directly. If a standard LV is passed with this + option, lvm will first convert it to a cache pool by combining it with + another LV to use for metadata. This option can be used with dm-cache. + ++.B --cachedevice ++.I PV ++.br ++ ++This option can be used in place of --cachevol, in which case a cachevol ++LV will be created using the specified device. This option can be ++repeated to create a cachevol using multiple devices, or a tag name can be ++specified in which case the cachevol will be created using any of the ++devices with the given tag. If a named cache device is unused, the entire ++device will be used to create the cachevol. To create a cachevol of a ++specific size from the cache devices, include the --cachesize option. ++ + \& + + .SS dm-cache block size +diff --git a/man/lvs.8_end b/man/lvs.8_end +index 6efc9cb..5a4ecc8 100644 +--- a/man/lvs.8_end ++++ b/man/lvs.8_end +@@ -74,5 +74,9 @@ Related to Thin Logical Volumes: (F)ailed. + .br + (F)ailed is set when related thin pool enters Failed state and no further I/O + is permitted at all. ++.IP ++Related to writecache logical volumes: (E)rror. ++.br ++(E)rror is set dm-writecache reports an error. + .IP 10 3 + s(k)ip activation: this volume is flagged to be skipped during activation. +diff --git a/scripts/blkdeactivate.sh.in b/scripts/blkdeactivate.sh.in +index 57b3e58..7c517b8 100644 +--- a/scripts/blkdeactivate.sh.in ++++ b/scripts/blkdeactivate.sh.in +@@ -330,6 +330,12 @@ deactivate_vdo() { + test -b "$DEV_DIR/mapper/$xname" || return 0 + test -z "${SKIP_DEVICE_LIST["$kname"]}" || return 1 + ++ # Skip VDO device deactivation if VDO tools missing. ++ test "$VDO_AVAILABLE" -eq 0 && { ++ add_device_to_skip_list ++ return 1 ++ } ++ + deactivate_holders "$DEV_DIR/mapper/$xname" || return 1 + + echo -n " [VDO]: deactivating VDO volume $xname... " +diff --git a/test/shell/cachevol-cachedevice.sh b/test/shell/cachevol-cachedevice.sh +new file mode 100644 +index 0000000..11a37d9 +--- /dev/null ++++ b/test/shell/cachevol-cachedevice.sh +@@ -0,0 +1,212 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux have_cache 1 10 0 || skip ++aux have_writecache 1 0 0 || skip ++ ++aux prepare_devs 4 64 ++ ++vgcreate $SHARED $vg "$dev1" "$dev2" ++ ++## cache ++ ++# use existing cachevol ++lvcreate -n $lv1 -l8 -an $vg "$dev1" ++lvcreate --type cache -n $lv2 -L40M --cachevol $lv1 -y $vg "$dev2" ++check lv_field $vg/$lv2 segtype cache ++check lv_field $vg/${lv1}_cvol segtype linear -a ++lvremove -y $vg/$lv2 ++ ++# use entire cachedevice for cachevol ++lvcreate --type cache -n $lv2 -L40M --cachedevice "$dev1" -y $vg "$dev2" ++check lv_field $vg/$lv2 segtype cache ++check lv_field $vg/${lv2}_cache_cvol segtype linear -a ++lvremove -y $vg/$lv2 ++ ++# use part of cachedevice for cachevol ++lvcreate --type cache -n $lv2 -L20M --cachedevice "$dev1" --cachesize 16M -y $vg "$dev2" ++check lv_field $vg/$lv2 segtype cache ++check lv_field $vg/${lv2}_cache_cvol segtype linear -a ++lvcreate --type cache -n $lv3 -L20M --cachedevice "$dev1" --cachesize 16M -y $vg "$dev2" ++check lv_field $vg/$lv3 segtype cache ++check lv_field $vg/${lv3}_cache_cvol segtype linear -a ++lvremove -y $vg/$lv2 ++lvremove -y $vg/$lv3 ++ ++## writecache ++ ++# use existing cachevol ++lvcreate -n $lv1 -l8 -an $vg "$dev1" ++lvcreate --type writecache -n $lv2 -L40M --cachevol $lv1 -y $vg "$dev2" ++check lv_field $vg/$lv2 segtype writecache ++check lv_field $vg/${lv1}_cvol segtype linear -a ++lvremove -y $vg/$lv2 ++ ++# use entire cachedevice for cachevol ++lvcreate --type writecache -n $lv2 -L40M --cachedevice "$dev1" -y $vg "$dev2" ++check lv_field $vg/$lv2 segtype writecache ++check lv_field $vg/${lv2}_cache_cvol segtype linear -a ++lvremove -y $vg/$lv2 ++ ++# use part of cachedevice for cachevol ++lvcreate --type writecache -n $lv2 -L20M --cachedevice "$dev1" --cachesize 16M -y $vg "$dev2" ++check lv_field $vg/$lv2 segtype writecache ++check lv_field $vg/${lv2}_cache_cvol segtype linear -a ++lvcreate --type writecache -n $lv3 -L20M --cachedevice "$dev1" --cachesize 16M -y $vg "$dev2" ++check lv_field $vg/$lv3 segtype writecache ++check lv_field $vg/${lv3}_cache_cvol segtype linear -a ++lvremove -y $vg/$lv2 ++lvremove -y $vg/$lv3 ++ ++## multiple cachedevs ++ ++vgextend $vg "$dev3" "$dev4" ++ ++lvcreate --type writecache -n $lv2 -L100M --cachedevice "$dev1" --cachedevice "$dev3" -y $vg "$dev2" "$dev4" ++check lv_field $vg/${lv2}_cache_cvol lv_size "120.00m" ++lvremove -y $vg/$lv2 ++ ++lvcreate --type writecache -n $lv2 -L100M --cachedevice "$dev1" --cachedevice "$dev3" --cachesize 80M -y $vg "$dev2" "$dev4" ++check lv_field $vg/${lv2}_cache_cvol lv_size "80.00m" ++lvremove -y $vg/$lv2 ++ ++pvchange --addtag slow "$dev2" ++pvchange --addtag slow "$dev4" ++pvchange --addtag fast "$dev1" ++pvchange --addtag fast "$dev3" ++ ++lvcreate --type writecache -n $lv2 -L100M --cachedevice @fast --cachesize 80M -y $vg @slow ++check lv_field $vg/${lv2}_cache_cvol lv_size "80.00m" ++lvremove -y $vg/$lv2 ++ ++lvcreate --type cache -n $lv2 -L100M --cachedevice @fast --cachesize 80M -y $vg @slow ++check lv_field $vg/${lv2}_cache_cvol lv_size "80.00m" ++lvremove -y $vg/$lv2 ++ ++## error cases ++ ++# cachevol doesn't exist ++not lvcreate --type cache -n $lv2 -l8 --cachevol asdf -y $vg "$dev2" ++not lvs $vg/$lv1 ++not lvs $vg/$lv2 ++ ++# cachedevice doesn't exist ++not lvcreate --type cache -n $lv2 -l8 --cachedevice asdf -y $vg "$dev2" ++not lvs $vg/$lv1 ++not lvs $vg/$lv2 ++ ++# cachevol doesn't exist ++not lvcreate --type writecache -n $lv2 -l8 --cachevol asdf -y $vg "$dev2" ++not lvs $vg/$lv1 ++not lvs $vg/$lv2 ++ ++# cachedevice doesn't exist ++not lvcreate --type writecache -n $lv2 -l8 --cachedevice asdf -y $vg "$dev2" ++not lvs $vg/$lv1 ++not lvs $vg/$lv2 ++ ++# when cachedevice is already being used, cachesize is required to use a part of it ++lvcreate -n asdf -l1 $vg "$dev1" ++not lvcreate --type writecache -n $lv2 -l8 --cachedevice "$dev1" -y $vg "$dev2" ++not lvcreate --type writecache -n $lv2 -l8 --cachedevice "$dev1" --cachedevice "$dev3" -y $vg "$dev2" ++not lvs $vg/$lv1 ++not lvs $vg/$lv2 ++lvcreate --type writecache -n $lv2 -l8 --cachedevice "$dev1" --cachesize 8M -y $vg "$dev2" ++lvs $vg/$lv2 ++check lv_field $vg/${lv2}_cache_cvol lv_size "8.00m" ++lvremove -y $vg/$lv2 ++ ++vgremove -ff $vg ++ ++# lvconvert single step cachevol creation and attachment ++# . cache and writecache ++# . one or two cachedevices ++# . with or without --cachesize ++# . using tags for devices ++ ++vgcreate $SHARED $vg "$dev1" "$dev2" "$dev3" "$dev4" ++ ++lvcreate -n $lv1 -l8 -an $vg "$dev1" ++lvconvert -y --type cache --cachedevice "$dev2" $vg/$lv1 ++check lv_field $vg/$lv1 segtype cache ++check lv_field $vg/${lv1}_cache_cvol segtype linear -a ++check lv_field $vg/${lv1}_cache_cvol lv_size "60.00m" ++lvchange -ay $vg/$lv1 ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++lvcreate -n $lv1 -l8 -an $vg "$dev1" ++lvconvert -y --type cache --cachedevice "$dev2" --cachedevice "$dev3" $vg/$lv1 ++check lv_field $vg/$lv1 segtype cache ++check lv_field $vg/${lv1}_cache_cvol lv_size "120.00m" ++lvchange -ay $vg/$lv1 ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++lvcreate -n $lv1 -l8 -an $vg "$dev1" ++lvconvert -y --type cache --cachedevice "$dev2" --cachedevice "$dev3" --cachesize 8M $vg/$lv1 ++check lv_field $vg/$lv1 segtype cache ++check lv_field $vg/${lv1}_cache_cvol lv_size "8.00m" ++lvchange -ay $vg/$lv1 ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++lvcreate -n $lv1 -l8 -an $vg "$dev1" ++lvconvert -y --type writecache --cachedevice "$dev2" $vg/$lv1 ++check lv_field $vg/$lv1 segtype writecache ++check lv_field $vg/${lv1}_cache_cvol lv_size "60.00m" ++lvchange -ay $vg/$lv1 ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++lvcreate -n $lv1 -l8 -an $vg "$dev1" ++lvconvert -y --type writecache --cachedevice "$dev2" --cachedevice "$dev3" $vg/$lv1 ++check lv_field $vg/$lv1 segtype writecache ++check lv_field $vg/${lv1}_cache_cvol lv_size "120.00m" ++lvchange -ay $vg/$lv1 ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++lvcreate -n $lv1 -l8 -an $vg "$dev1" ++lvconvert -y --type writecache --cachedevice "$dev2" --cachedevice "$dev3" --cachesize 8M $vg/$lv1 ++check lv_field $vg/$lv1 segtype writecache ++check lv_field $vg/${lv1}_cache_cvol lv_size "8.00m" ++lvchange -ay $vg/$lv1 ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++pvchange --addtag slow "$dev1" ++pvchange --addtag fast "$dev2" ++pvchange --addtag fast "$dev3" ++ ++lvcreate -n $lv1 -l8 -an $vg @slow ++lvconvert -y --type cache --cachedevice @fast --cachesize 8M $vg/$lv1 ++check lv_field $vg/$lv1 segtype cache ++check lv_field $vg/${lv1}_cache_cvol lv_size "8.00m" ++lvchange -ay $vg/$lv1 ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++lvcreate -n $lv1 -l8 -an $vg @slow ++lvconvert -y --type writecache --cachedevice @fast --cachesize 8M $vg/$lv1 ++check lv_field $vg/$lv1 segtype writecache ++check lv_field $vg/${lv1}_cache_cvol lv_size "8.00m" ++lvchange -ay $vg/$lv1 ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++vgremove -ff $vg ++ +diff --git a/test/shell/integrity-blocksize-2.sh b/test/shell/integrity-blocksize-2.sh +new file mode 100644 +index 0000000..5e0fd9a +--- /dev/null ++++ b/test/shell/integrity-blocksize-2.sh +@@ -0,0 +1,128 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux have_integrity 1 5 0 || skip ++ ++mnt="mnt" ++mkdir -p $mnt ++ ++_sync_percent() { ++ local checklv=$1 ++ get lv_field "$checklv" sync_percent | cut -d. -f1 ++} ++ ++_wait_recalc() { ++ local checklv=$1 ++ ++ for i in $(seq 1 10) ; do ++ sync=$(_sync_percent "$checklv") ++ echo "sync_percent is $sync" ++ ++ if test "$sync" = "100"; then ++ return ++ fi ++ ++ sleep 1 ++ done ++ ++ # TODO: There is some strange bug, first leg of RAID with integrity ++ # enabled never gets in sync. I saw this in BB, but not when executing ++ # the commands manually ++ if test -z "$sync"; then ++ echo "TEST WARNING: Resync of dm-integrity device '$checklv' failed" ++ dmsetup status "$DM_DEV_DIR/mapper/${checklv/\//-}" ++ exit ++ fi ++ echo "timeout waiting for recalc" ++ return 1 ++} ++ ++# prepare_devs uses ramdisk backing which has 512 LBS and 4K PBS ++# This should cause mkfs.xfs to use 4K sector size, ++# and integrity to use 4K block size ++aux prepare_devs 2 64 ++ ++vgcreate $vg "$dev1" "$dev2" ++blockdev --getss "$dev1" ++blockdev --getpbsz "$dev1" ++blockdev --getss "$dev2" ++blockdev --getpbsz "$dev2" ++ ++# add integrity while LV is inactive ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++umount $mnt ++lvchange -an $vg ++lvconvert --raidintegrity y $vg/$lv1 ++lvchange -ay $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++# add integrity while LV is active, fs unmounted ++# lvconvert will use ribs 512 to avoid increasing LBS from 512 to 4k on active LV ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++umount $mnt ++lvchange -an $vg ++lvchange -ay $vg ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++# add integrity while LV is active, fs mounted ++# lvconvert will use ribs 512 to avoid increasing LBS from 512 to 4k on active LV ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++vgremove -ff $vg ++ +diff --git a/test/shell/integrity-blocksize-3.sh b/test/shell/integrity-blocksize-3.sh +new file mode 100644 +index 0000000..4aea972 +--- /dev/null ++++ b/test/shell/integrity-blocksize-3.sh +@@ -0,0 +1,285 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux have_integrity 1 5 0 || skip ++ ++mnt="mnt" ++mkdir -p $mnt ++ ++_sync_percent() { ++ local checklv=$1 ++ get lv_field "$checklv" sync_percent | cut -d. -f1 ++} ++ ++_wait_recalc() { ++ local checklv=$1 ++ ++ for i in $(seq 1 10) ; do ++ sync=$(_sync_percent "$checklv") ++ echo "sync_percent is $sync" ++ ++ if test "$sync" = "100"; then ++ return ++ fi ++ ++ sleep 1 ++ done ++ ++ # TODO: There is some strange bug, first leg of RAID with integrity ++ # enabled never gets in sync. I saw this in BB, but not when executing ++ # the commands manually ++ if test -z "$sync"; then ++ echo "TEST WARNING: Resync of dm-integrity device '$checklv' failed" ++ dmsetup status "$DM_DEV_DIR/mapper/${checklv/\//-}" ++ exit ++ fi ++ echo "timeout waiting for recalc" ++ return 1 ++} ++ ++# scsi_debug devices with 512 LBS 512 PBS ++aux prepare_scsi_debug_dev 256 ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/logical_block_size "512" ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/physical_block_size "512" ++aux prepare_devs 2 64 ++ ++vgcreate $vg "$dev1" "$dev2" ++blockdev --getss "$dev1" ++blockdev --getpbsz "$dev1" ++blockdev --getss "$dev2" ++blockdev --getpbsz "$dev2" ++ ++# add integrity while LV is inactive ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++umount $mnt ++lvchange -an $vg ++lvconvert --raidintegrity y $vg/$lv1 ++lvchange -ay $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++# add integrity while LV is active, fs unmounted ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++umount $mnt ++lvchange -an $vg ++lvchange -ay $vg ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++# add integrity while LV is active, fs mounted ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++vgremove -ff $vg ++aux cleanup_scsi_debug_dev ++sleep 1 ++ ++# scsi_debug devices with 4K LBS and 4K PBS ++aux prepare_scsi_debug_dev 256 sector_size=4096 ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/logical_block_size "4096" ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/physical_block_size "4096" ++aux prepare_devs 2 64 ++ ++vgcreate $vg "$dev1" "$dev2" ++blockdev --getss "$dev1" ++blockdev --getpbsz "$dev1" ++blockdev --getss "$dev2" ++blockdev --getpbsz "$dev2" ++ ++# add integrity while LV is inactive ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++umount $mnt ++lvchange -an $vg ++lvconvert --raidintegrity y $vg/$lv1 ++lvchange -ay $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++# add integrity while LV is active, fs unmounted ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++umount $mnt ++lvchange -an $vg ++lvchange -ay $vg ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++# add integrity while LV is active, fs mounted ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++vgremove -ff $vg ++aux cleanup_scsi_debug_dev ++sleep 1 ++ ++# scsi_debug devices with 512 LBS and 4K PBS ++aux prepare_scsi_debug_dev 256 sector_size=512 physblk_exp=3 ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/logical_block_size "512" ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/physical_block_size "4096" ++aux prepare_devs 2 64 ++ ++vgcreate $vg "$dev1" "$dev2" ++blockdev --getss "$dev1" ++blockdev --getpbsz "$dev1" ++blockdev --getss "$dev2" ++blockdev --getpbsz "$dev2" ++ ++# add integrity while LV is inactive ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++umount $mnt ++lvchange -an $vg ++lvconvert --raidintegrity y $vg/$lv1 ++lvchange -ay $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++# add integrity while LV is active, fs unmounted ++# lvconvert will use ribs 512 to avoid increasing LBS from 512 to 4k on active LV ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++umount $mnt ++lvchange -an $vg ++lvchange -ay $vg ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++# add integrity while LV is active, fs mounted ++# lvconvert will use ribs 512 to avoid increasing LBS from 512 to 4k on active LV ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++echo "hello world" > $mnt/hello ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++cat $mnt/hello | grep "hello world" ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++vgremove -ff $vg ++aux cleanup_scsi_debug_dev ++sleep 1 ++ +diff --git a/test/shell/integrity-blocksize.sh b/test/shell/integrity-blocksize.sh +index 444e3db..eb6a364 100644 +--- a/test/shell/integrity-blocksize.sh ++++ b/test/shell/integrity-blocksize.sh +@@ -48,9 +48,24 @@ aux extend_filter "a|$LOOP4|" + + aux lvmconf 'devices/scan = "/dev"' + ++mnt="mnt" ++mkdir -p $mnt ++ + vgcreate $vg1 $LOOP1 $LOOP2 + vgcreate $vg2 $LOOP3 $LOOP4 + ++# LOOP1/LOOP2 have LBS 512 and PBS 512 ++# LOOP3/LOOP4 have LBS 4K and PBS 4K ++ ++blockdev --getss $LOOP1 ++blockdev --getpbsz $LOOP1 ++blockdev --getss $LOOP2 ++blockdev --getpbsz $LOOP2 ++blockdev --getss $LOOP3 ++blockdev --getpbsz $LOOP3 ++blockdev --getss $LOOP4 ++blockdev --getpbsz $LOOP4 ++ + # lvcreate on dev512, result 512 + lvcreate --type raid1 -m1 --raidintegrity y -l 8 -n $lv1 $vg1 + pvck --dump metadata $LOOP1 | grep 'block_size = 512' +@@ -105,7 +120,11 @@ lvremove -y $vg2/$lv1 + lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 + aux wipefs_a /dev/$vg1/$lv1 + mkfs.xfs -f "$DM_DEV_DIR/$vg1/$lv1" ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"512\" + lvconvert --raidintegrity y $vg1/$lv1 ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"512\" ++mount "$DM_DEV_DIR/$vg1/$lv1" $mnt ++umount $mnt + pvck --dump metadata $LOOP1 | grep 'block_size = 512' + lvremove -y $vg1/$lv1 + +@@ -113,15 +132,37 @@ lvremove -y $vg1/$lv1 + lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg2 + aux wipefs_a /dev/$vg2/$lv1 + mkfs.xfs -f "$DM_DEV_DIR/$vg2/$lv1" ++blkid "$DM_DEV_DIR/$vg2/$lv1" | grep BLOCK_SIZE=\"4096\" + lvconvert --raidintegrity y $vg2/$lv1 ++blkid "$DM_DEV_DIR/$vg2/$lv1" | grep BLOCK_SIZE=\"4096\" ++mount "$DM_DEV_DIR/$vg2/$lv1" $mnt ++umount $mnt + pvck --dump metadata $LOOP3 | grep 'block_size = 4096' + lvremove -y $vg2/$lv1 + +-# lvconvert on dev512, ext4 1024, result 1024 ++# lvconvert on dev512, ext4 1024, result 1024 (LV active when adding) ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 ++aux wipefs_a /dev/$vg1/$lv1 ++mkfs.ext4 -b 1024 "$DM_DEV_DIR/$vg1/$lv1" ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"1024\" ++lvconvert --raidintegrity y $vg1/$lv1 ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"1024\" ++mount "$DM_DEV_DIR/$vg1/$lv1" $mnt ++umount $mnt ++pvck --dump metadata $LOOP1 | grep 'block_size = 512' ++lvremove -y $vg1/$lv1 ++ ++# lvconvert on dev512, ext4 1024, result 1024 (LV inactive when adding) + lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 + aux wipefs_a /dev/$vg1/$lv1 + mkfs.ext4 -b 1024 "$DM_DEV_DIR/$vg1/$lv1" ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"1024\" ++lvchange -an $vg1/$lv1 + lvconvert --raidintegrity y $vg1/$lv1 ++lvchange -ay $vg1/$lv1 ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"1024\" ++mount "$DM_DEV_DIR/$vg1/$lv1" $mnt ++umount $mnt + pvck --dump metadata $LOOP1 | grep 'block_size = 1024' + lvremove -y $vg1/$lv1 + +@@ -129,7 +170,11 @@ lvremove -y $vg1/$lv1 + lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg2 + aux wipefs_a /dev/$vg2/$lv1 + mkfs.ext4 "$DM_DEV_DIR/$vg2/$lv1" ++blkid "$DM_DEV_DIR/$vg2/$lv1" | grep BLOCK_SIZE=\"4096\" + lvconvert --raidintegrity y $vg2/$lv1 ++blkid "$DM_DEV_DIR/$vg2/$lv1" | grep BLOCK_SIZE=\"4096\" ++mount "$DM_DEV_DIR/$vg2/$lv1" $mnt ++umount $mnt + pvck --dump metadata $LOOP3 | grep 'block_size = 4096' + lvremove -y $vg2/$lv1 + +@@ -137,7 +182,11 @@ lvremove -y $vg2/$lv1 + lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 + aux wipefs_a /dev/$vg1/$lv1 + mkfs.xfs -f -s size=4096 "$DM_DEV_DIR/$vg1/$lv1" ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"4096\" + lvconvert --raidintegrity y --raidintegrityblocksize 512 $vg1/$lv1 ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"4096\" ++mount "$DM_DEV_DIR/$vg1/$lv1" $mnt ++umount $mnt + pvck --dump metadata $LOOP1 | grep 'block_size = 512' + lvremove -y $vg1/$lv1 + +@@ -145,7 +194,14 @@ lvremove -y $vg1/$lv1 + lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 + aux wipefs_a /dev/$vg1/$lv1 + mkfs.xfs -f -s size=4096 "$DM_DEV_DIR/$vg1/$lv1" ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"4096\" ++lvchange -an $vg1/$lv1 ++# lv needs to be inactive to increase LBS from 512 + lvconvert --raidintegrity y --raidintegrityblocksize 1024 $vg1/$lv1 ++lvchange -ay $vg1/$lv1 ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"4096\" ++mount "$DM_DEV_DIR/$vg1/$lv1" $mnt ++umount $mnt + pvck --dump metadata $LOOP1 | grep 'block_size = 1024' + lvremove -y $vg1/$lv1 + +@@ -153,7 +209,11 @@ lvremove -y $vg1/$lv1 + lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 + aux wipefs_a /dev/$vg1/$lv1 + mkfs.ext4 -b 1024 "$DM_DEV_DIR/$vg1/$lv1" ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"1024\" + lvconvert --raidintegrity y --raidintegrityblocksize 512 $vg1/$lv1 ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"1024\" ++mount "$DM_DEV_DIR/$vg1/$lv1" $mnt ++umount $mnt + pvck --dump metadata $LOOP1 | grep 'block_size = 512' + lvremove -y $vg1/$lv1 + +@@ -164,10 +224,48 @@ mkfs.ext4 "$DM_DEV_DIR/$vg2/$lv1" + not lvconvert --raidintegrity y --raidintegrityblocksize 512 $vg2/$lv1 + lvremove -y $vg2/$lv1 + +-# FIXME: need to use scsi_debug to create devs with LBS 512 PBS 4k +-# FIXME: lvconvert, fsunknown, LBS 512, PBS 4k: result 512 +-# FIXME: lvconvert --bs 512, fsunknown, LBS 512, PBS 4k: result 512 +-# FIXME: lvconvert --bs 4k, fsunknown, LBS 512, PBS 4k: result 4k ++# TODO: need to use scsi_debug to create devs with LBS 512 PBS 4k ++# TODO: lvconvert, fsunknown, LBS 512, PBS 4k: result 512 ++# TODO: lvconvert --bs 512, fsunknown, LBS 512, PBS 4k: result 512 ++# TODO: lvconvert --bs 4k, fsunknown, LBS 512, PBS 4k: result 4k ++ ++# lvconvert on dev512, xfs 512, result 512, (detect fs with LV inactive) ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 ++aux wipefs_a /dev/$vg1/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg1/$lv1" ++mount "$DM_DEV_DIR/$vg1/$lv1" $mnt ++echo "test" > $mnt/test ++umount $mnt ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"512\" ++lvchange -an $vg1/$lv1 ++lvconvert --raidintegrity y $vg1/$lv1 ++lvchange -ay $vg1/$lv1 ++mount "$DM_DEV_DIR/$vg1/$lv1" $mnt ++cat $mnt/test ++umount $mnt ++blkid "$DM_DEV_DIR/$vg1/$lv1" | grep BLOCK_SIZE=\"512\" ++pvck --dump metadata $LOOP1 | grep 'block_size = 512' ++lvchange -an $vg1/$lv1 ++lvremove -y $vg1/$lv1 ++ ++# lvconvert on dev4k, xfs 4096, result 4096 (detect fs with LV inactive) ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg2 ++aux wipefs_a /dev/$vg2/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg2/$lv1" ++mount "$DM_DEV_DIR/$vg2/$lv1" $mnt ++echo "test" > $mnt/test ++umount $mnt ++blkid "$DM_DEV_DIR/$vg2/$lv1" | grep BLOCK_SIZE=\"4096\" ++lvchange -an $vg2/$lv1 ++lvconvert --raidintegrity y $vg2/$lv1 ++lvchange -ay $vg2/$lv1 ++mount "$DM_DEV_DIR/$vg2/$lv1" $mnt ++cat $mnt/test ++umount $mnt ++blkid "$DM_DEV_DIR/$vg2/$lv1" | grep BLOCK_SIZE=\"4096\" ++pvck --dump metadata $LOOP3 | grep 'block_size = 4096' ++lvchange -an $vg2/$lv1 ++lvremove -y $vg2/$lv1 + + vgremove -ff $vg1 + vgremove -ff $vg2 +diff --git a/test/shell/integrity-large.sh b/test/shell/integrity-large.sh +index 5aba80e..1df9e6b 100644 +--- a/test/shell/integrity-large.sh ++++ b/test/shell/integrity-large.sh +@@ -115,7 +115,10 @@ lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg + lvchange -an $vg/$lv1 + lvchange -ay $vg/$lv1 + _add_data_to_lv ++# lv needs to be inactive when adding integrity to increase LBS from 512 and get a ribs of 4k ++lvchange -an $vg/$lv1 + lvconvert --raidintegrity y $vg/$lv1 ++lvchange -ay $vg/$lv1 + _wait_recalc $vg/${lv1}_rimage_0 + _wait_recalc $vg/${lv1}_rimage_1 + lvs -a -o+devices $vg +diff --git a/test/shell/integrity-misc.sh b/test/shell/integrity-misc.sh +index 0d05689..2dae25f 100644 +--- a/test/shell/integrity-misc.sh ++++ b/test/shell/integrity-misc.sh +@@ -95,7 +95,7 @@ _sync_percent() { + get lv_field "$checklv" sync_percent | cut -d. -f1 + } + +-_wait_recalc() { ++_wait_sync() { + local checklv=$1 + + for i in $(seq 1 10) ; do +@@ -124,8 +124,9 @@ _wait_recalc() { + # lvrename + _prepare_vg + lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg +-_wait_recalc $vg/${lv1}_rimage_0 +-_wait_recalc $vg/${lv1}_rimage_1 ++_wait_sync $vg/${lv1}_rimage_0 ++_wait_sync $vg/${lv1}_rimage_1 ++_wait_sync $vg/$lv1 + _add_new_data_to_mnt + umount $mnt + lvrename $vg/$lv1 $vg/$lv2 +@@ -141,8 +142,9 @@ vgremove -ff $vg + # lv must be active + _prepare_vg + lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg "$dev1" "$dev2" +-_wait_recalc $vg/${lv1}_rimage_0 +-_wait_recalc $vg/${lv1}_rimage_1 ++_wait_sync $vg/${lv1}_rimage_0 ++_wait_sync $vg/${lv1}_rimage_1 ++_wait_sync $vg/$lv1 + _add_new_data_to_mnt + lvconvert --replace "$dev1" $vg/$lv1 "$dev3" + lvs -a -o+devices $vg > out +@@ -162,8 +164,9 @@ vgremove -ff $vg + # same as prev but with bitmap mode + _prepare_vg + lvcreate --type raid1 -m1 --raidintegrity y --raidintegritymode bitmap -n $lv1 -l 8 $vg "$dev1" "$dev2" +-_wait_recalc $vg/${lv1}_rimage_0 +-_wait_recalc $vg/${lv1}_rimage_1 ++_wait_sync $vg/${lv1}_rimage_0 ++_wait_sync $vg/${lv1}_rimage_1 ++_wait_sync $vg/$lv1 + _add_new_data_to_mnt + lvconvert --replace "$dev1" $vg/$lv1 "$dev3" + lvs -a -o+devices $vg > out +@@ -185,8 +188,9 @@ vgremove -ff $vg + # (like lvconvert --replace does for a dev that's not missing). + _prepare_vg + lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg "$dev1" "$dev2" +-_wait_recalc $vg/${lv1}_rimage_0 +-_wait_recalc $vg/${lv1}_rimage_1 ++_wait_sync $vg/${lv1}_rimage_0 ++_wait_sync $vg/${lv1}_rimage_1 ++_wait_sync $vg/$lv1 + _add_new_data_to_mnt + aux disable_dev "$dev2" + lvs -a -o+devices $vg > out +@@ -213,8 +217,9 @@ vgremove -ff $vg + + _prepare_vg + lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg "$dev1" "$dev2" +-_wait_recalc $vg/${lv1}_rimage_0 +-_wait_recalc $vg/${lv1}_rimage_1 ++_wait_sync $vg/${lv1}_rimage_0 ++_wait_sync $vg/${lv1}_rimage_1 ++_wait_sync $vg/$lv1 + _add_new_data_to_mnt + umount $mnt + lvchange -an $vg/$lv1 +diff --git a/test/shell/integrity.sh b/test/shell/integrity.sh +index 77e9430..0143129 100644 +--- a/test/shell/integrity.sh ++++ b/test/shell/integrity.sh +@@ -78,14 +78,14 @@ _test_fs_with_error() { + dd if=$mnt/fileA of=tmp bs=1k + ls -l tmp + stat -c %s tmp +- diff fileA tmp ++ cmp -b fileA tmp + rm tmp + + # read partial fileB which was corrupted + not dd if=$mnt/fileB of=tmp bs=1k + ls -l tmp + stat -c %s tmp | grep 12288 +- not diff fileB tmp ++ not cmp -b fileB tmp + rm tmp + + umount $mnt +@@ -118,14 +118,14 @@ _test_fs_with_raid() { + dd if=$mnt/fileA of=tmp bs=1k + ls -l tmp + stat -c %s tmp | grep 16384 +- diff fileA tmp ++ cmp -b fileA tmp + rm tmp + + # read complete fileB, corruption is corrected by raid + dd if=$mnt/fileB of=tmp bs=1k + ls -l tmp + stat -c %s tmp | grep 16384 +- diff fileB tmp ++ cmp -b fileB tmp + rm tmp + + umount $mnt +@@ -161,15 +161,15 @@ _add_more_data_to_mnt() { + } + + _verify_data_on_mnt() { +- diff randA $mnt/randA +- diff randB $mnt/randB +- diff randC $mnt/randC +- diff fileA $mnt/1/fileA +- diff fileB $mnt/1/fileB +- diff fileC $mnt/1/fileC +- diff fileA $mnt/2/fileA +- diff fileB $mnt/2/fileB +- diff fileC $mnt/2/fileC ++ cmp -b randA $mnt/randA ++ cmp -b randB $mnt/randB ++ cmp -b randC $mnt/randC ++ cmp -b fileA $mnt/1/fileA ++ cmp -b fileB $mnt/1/fileB ++ cmp -b fileC $mnt/1/fileC ++ cmp -b fileA $mnt/2/fileA ++ cmp -b fileB $mnt/2/fileB ++ cmp -b fileC $mnt/2/fileC + } + + _verify_data_on_lv() { +@@ -221,6 +221,8 @@ _wait_recalc() { + + _prepare_vg + lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 + _test_fs_with_raid + lvchange -an $vg/$lv1 + lvconvert --raidintegrity n $vg/$lv1 +@@ -229,6 +231,9 @@ vgremove -ff $vg + + _prepare_vg + lvcreate --type raid1 -m2 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 + _test_fs_with_raid + lvchange -an $vg/$lv1 + lvconvert --raidintegrity n $vg/$lv1 +@@ -237,6 +242,9 @@ vgremove -ff $vg + + _prepare_vg + lvcreate --type raid4 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 + _test_fs_with_raid + lvchange -an $vg/$lv1 + lvconvert --raidintegrity n $vg/$lv1 +@@ -245,6 +253,9 @@ vgremove -ff $vg + + _prepare_vg + lvcreate --type raid5 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 + _test_fs_with_raid + lvchange -an $vg/$lv1 + lvconvert --raidintegrity n $vg/$lv1 +@@ -253,6 +264,11 @@ vgremove -ff $vg + + _prepare_vg + lvcreate --type raid6 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 ++_wait_recalc $vg/${lv1}_rimage_3 ++_wait_recalc $vg/${lv1}_rimage_4 + _test_fs_with_raid + lvchange -an $vg/$lv1 + lvconvert --raidintegrity n $vg/$lv1 +@@ -261,6 +277,10 @@ vgremove -ff $vg + + _prepare_vg + lvcreate --type raid10 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 ++_wait_recalc $vg/${lv1}_rimage_3 + _test_fs_with_raid + lvchange -an $vg/$lv1 + lvconvert --raidintegrity n $vg/$lv1 +diff --git a/test/shell/writecache-blocksize.sh b/test/shell/writecache-blocksize.sh +new file mode 100644 +index 0000000..1300176 +--- /dev/null ++++ b/test/shell/writecache-blocksize.sh +@@ -0,0 +1,342 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++# Test writecache usage ++ ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux have_writecache 1 0 0 || skip ++which mkfs.xfs || skip ++ ++# Tests with fs block sizes require a libblkid version that shows BLOCK_SIZE ++aux prepare_devs 1 ++vgcreate $vg "$dev1" ++lvcreate -n $lv1 -l8 $vg ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++blkid "$DM_DEV_DIR/$vg/$lv1" | grep BLOCK_SIZE || skip ++lvchange -an $vg ++vgremove -ff $vg ++aux cleanup_scsi_debug_dev ++ ++mnt="mnt" ++mkdir -p $mnt ++ ++for i in `seq 1 16384`; do echo -n "A" >> fileA; done ++for i in `seq 1 16384`; do echo -n "B" >> fileB; done ++for i in `seq 1 16384`; do echo -n "C" >> fileC; done ++ ++# generate random data ++dd if=/dev/urandom of=randA bs=512K count=2 ++dd if=/dev/urandom of=randB bs=512K count=3 ++dd if=/dev/urandom of=randC bs=512K count=4 ++ ++_add_new_data_to_mnt() { ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ # add original data ++ cp randA $mnt ++ cp randB $mnt ++ cp randC $mnt ++ mkdir $mnt/1 ++ cp fileA $mnt/1 ++ cp fileB $mnt/1 ++ cp fileC $mnt/1 ++ mkdir $mnt/2 ++ cp fileA $mnt/2 ++ cp fileB $mnt/2 ++ cp fileC $mnt/2 ++ sync ++} ++ ++_add_more_data_to_mnt() { ++ mkdir $mnt/more ++ cp fileA $mnt/more ++ cp fileB $mnt/more ++ cp fileC $mnt/more ++ cp randA $mnt/more ++ cp randB $mnt/more ++ cp randC $mnt/more ++ sync ++} ++ ++_verify_data_on_mnt() { ++ diff randA $mnt/randA ++ diff randB $mnt/randB ++ diff randC $mnt/randC ++ diff fileA $mnt/1/fileA ++ diff fileB $mnt/1/fileB ++ diff fileC $mnt/1/fileC ++ diff fileA $mnt/2/fileA ++ diff fileB $mnt/2/fileB ++ diff fileC $mnt/2/fileC ++} ++ ++_verify_more_data_on_mnt() { ++ diff randA $mnt/more/randA ++ diff randB $mnt/more/randB ++ diff randC $mnt/more/randC ++ diff fileA $mnt/more/fileA ++ diff fileB $mnt/more/fileB ++ diff fileC $mnt/more/fileC ++} ++ ++_verify_data_on_lv() { ++ lvchange -ay $vg/$lv1 ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ _verify_data_on_mnt ++ rm $mnt/randA ++ rm $mnt/randB ++ rm $mnt/randC ++ rm -rf $mnt/1 ++ rm -rf $mnt/2 ++ umount $mnt ++ lvchange -an $vg/$lv1 ++} ++ ++# the default is brd ram devs with 512 LBS 4K PBS ++aux prepare_devs 2 64 ++ ++blockdev --getss "$dev1" ++blockdev --getpbsz "$dev1" ++blockdev --getss "$dev2" ++blockdev --getpbsz "$dev2" ++ ++# lbs 512, pbs 4k, xfs 4k, wc 4k ++vgcreate $SHARED $vg "$dev1" ++vgextend $vg "$dev2" ++lvcreate -n $lv1 -l 8 -an $vg "$dev1" ++lvcreate -n $lv2 -l 4 -an $vg "$dev2" ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep sectsz=4096 out ++_add_new_data_to_mnt ++lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep 4096 out ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_add_more_data_to_mnt ++_verify_data_on_mnt ++lvconvert --splitcache $vg/$lv1 ++check lv_field $vg/$lv1 segtype linear ++check lv_field $vg/$lv2 segtype linear ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_verify_data_on_mnt ++_verify_more_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++lvchange -an $vg/$lv2 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++lvremove $vg/$lv2 ++vgremove $vg ++ ++# lbs 512, pbs 4k, xfs -s 512, wc 512 ++vgcreate $SHARED $vg "$dev1" ++vgextend $vg "$dev2" ++lvcreate -n $lv1 -l 8 -an $vg "$dev1" ++lvcreate -n $lv2 -l 4 -an $vg "$dev2" ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f -s size=512 "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep sectsz=512 out ++_add_new_data_to_mnt ++lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep 512 out ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_add_more_data_to_mnt ++_verify_data_on_mnt ++lvconvert --splitcache $vg/$lv1 ++check lv_field $vg/$lv1 segtype linear ++check lv_field $vg/$lv2 segtype linear ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_verify_data_on_mnt ++_verify_more_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++lvchange -an $vg/$lv2 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++lvremove $vg/$lv2 ++vgremove $vg ++ ++aux cleanup_scsi_debug_dev ++sleep 1 ++ ++ ++# scsi_debug devices with 512 LBS 512 PBS ++aux prepare_scsi_debug_dev 256 ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/logical_block_size "512" ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/physical_block_size "512" ++aux prepare_devs 2 64 ++ ++blockdev --getss "$dev1" ++blockdev --getpbsz "$dev1" ++blockdev --getss "$dev2" ++blockdev --getpbsz "$dev2" ++ ++# lbs 512, pbs 512, xfs 512, wc 512 ++vgcreate $SHARED $vg "$dev1" ++vgextend $vg "$dev2" ++lvcreate -n $lv1 -l 8 -an $vg "$dev1" ++lvcreate -n $lv2 -l 4 -an $vg "$dev2" ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep sectsz=512 out ++_add_new_data_to_mnt ++lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep 512 out ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_add_more_data_to_mnt ++_verify_data_on_mnt ++lvconvert --splitcache $vg/$lv1 ++check lv_field $vg/$lv1 segtype linear ++check lv_field $vg/$lv2 segtype linear ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_verify_data_on_mnt ++_verify_more_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++lvchange -an $vg/$lv2 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++lvremove $vg/$lv2 ++vgremove $vg ++ ++# lbs 512, pbs 512, xfs -s 4096, wc 4096 ++vgcreate $SHARED $vg "$dev1" ++vgextend $vg "$dev2" ++lvcreate -n $lv1 -l 8 -an $vg "$dev1" ++lvcreate -n $lv2 -l 4 -an $vg "$dev2" ++lvchange -ay $vg/$lv1 ++mkfs.xfs -s size=4096 -f "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep sectsz=4096 out ++_add_new_data_to_mnt ++lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep 4096 out ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_add_more_data_to_mnt ++_verify_data_on_mnt ++lvconvert --splitcache $vg/$lv1 ++check lv_field $vg/$lv1 segtype linear ++check lv_field $vg/$lv2 segtype linear ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_verify_data_on_mnt ++_verify_more_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++lvchange -an $vg/$lv2 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++lvremove $vg/$lv2 ++vgremove $vg ++ ++aux cleanup_scsi_debug_dev ++sleep 1 ++ ++ ++# scsi_debug devices with 512 LBS and 4K PBS ++aux prepare_scsi_debug_dev 256 sector_size=512 physblk_exp=3 ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/logical_block_size "512" ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/physical_block_size "4096" ++aux prepare_devs 2 64 ++ ++blockdev --getss "$dev1" ++blockdev --getpbsz "$dev1" ++blockdev --getss "$dev2" ++blockdev --getpbsz "$dev2" ++ ++# lbs 512, pbs 4k, xfs 4k, wc 4k ++vgcreate $SHARED $vg "$dev1" ++vgextend $vg "$dev2" ++lvcreate -n $lv1 -l 8 -an $vg "$dev1" ++lvcreate -n $lv2 -l 4 -an $vg "$dev2" ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep sectsz=4096 out ++_add_new_data_to_mnt ++lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep 4096 out ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_add_more_data_to_mnt ++_verify_data_on_mnt ++lvconvert --splitcache $vg/$lv1 ++check lv_field $vg/$lv1 segtype linear ++check lv_field $vg/$lv2 segtype linear ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_verify_data_on_mnt ++_verify_more_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++lvchange -an $vg/$lv2 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++lvremove $vg/$lv2 ++vgremove $vg ++ ++aux cleanup_scsi_debug_dev ++sleep 1 ++ ++ ++# scsi_debug devices with 4K LBS and 4K PBS ++aux prepare_scsi_debug_dev 256 sector_size=4096 ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/logical_block_size "4096" ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/physical_block_size "4096" ++aux prepare_devs 2 64 ++ ++blockdev --getss "$dev1" ++blockdev --getpbsz "$dev1" ++blockdev --getss "$dev2" ++blockdev --getpbsz "$dev2" ++ ++# lbs 4k, pbs 4k, xfs 4k, wc 4k ++vgcreate $SHARED $vg "$dev1" ++vgextend $vg "$dev2" ++lvcreate -n $lv1 -l 8 -an $vg "$dev1" ++lvcreate -n $lv2 -l 4 -an $vg "$dev2" ++lvchange -ay $vg/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep sectsz=4096 out ++_add_new_data_to_mnt ++lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" |tee out ++grep 4096 out ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_add_more_data_to_mnt ++_verify_data_on_mnt ++lvconvert --splitcache $vg/$lv1 ++check lv_field $vg/$lv1 segtype linear ++check lv_field $vg/$lv2 segtype linear ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_verify_data_on_mnt ++_verify_more_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++lvchange -an $vg/$lv2 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++lvremove $vg/$lv2 ++vgremove $vg ++ ++aux cleanup_scsi_debug_dev ++ ++ +diff --git a/test/shell/writecache-large.sh b/test/shell/writecache-large.sh +new file mode 100644 +index 0000000..b52eaf6 +--- /dev/null ++++ b/test/shell/writecache-large.sh +@@ -0,0 +1,153 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++# Test writecache usage ++ ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux have_writecache 1 0 0 || skip ++which mkfs.xfs || skip ++ ++# scsi_debug devices with 512 LBS 512 PBS ++aux prepare_scsi_debug_dev 1200 ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/logical_block_size "512" ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/physical_block_size "512" ++ ++aux prepare_devs 2 600 ++blockdev --getss "$dev1" ++blockdev --getpbsz "$dev1" ++blockdev --getss "$dev2" ++blockdev --getpbsz "$dev2" ++ ++mnt="mnt" ++mkdir -p $mnt ++ ++for i in `seq 1 16384`; do echo -n "A" >> fileA; done ++for i in `seq 1 16384`; do echo -n "B" >> fileB; done ++for i in `seq 1 16384`; do echo -n "C" >> fileC; done ++ ++# generate random data ++dd if=/dev/urandom of=randA bs=512K count=2 ++dd if=/dev/urandom of=randB bs=512K count=3 ++dd if=/dev/urandom of=randC bs=512K count=4 ++ ++_add_new_data_to_mnt() { ++ mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++ ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ # add original data ++ cp randA $mnt ++ cp randB $mnt ++ cp randC $mnt ++ mkdir $mnt/1 ++ cp fileA $mnt/1 ++ cp fileB $mnt/1 ++ cp fileC $mnt/1 ++ mkdir $mnt/2 ++ cp fileA $mnt/2 ++ cp fileB $mnt/2 ++ cp fileC $mnt/2 ++ sync ++} ++ ++_add_more_data_to_mnt() { ++ mkdir $mnt/more ++ cp fileA $mnt/more ++ cp fileB $mnt/more ++ cp fileC $mnt/more ++ cp randA $mnt/more ++ cp randB $mnt/more ++ cp randC $mnt/more ++ sync ++} ++ ++_verify_data_on_mnt() { ++ diff randA $mnt/randA ++ diff randB $mnt/randB ++ diff randC $mnt/randC ++ diff fileA $mnt/1/fileA ++ diff fileB $mnt/1/fileB ++ diff fileC $mnt/1/fileC ++ diff fileA $mnt/2/fileA ++ diff fileB $mnt/2/fileB ++ diff fileC $mnt/2/fileC ++} ++ ++_verify_more_data_on_mnt() { ++ diff randA $mnt/more/randA ++ diff randB $mnt/more/randB ++ diff randC $mnt/more/randC ++ diff fileA $mnt/more/fileA ++ diff fileB $mnt/more/fileB ++ diff fileC $mnt/more/fileC ++} ++ ++_verify_data_on_lv() { ++ lvchange -ay $vg/$lv1 ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ _verify_data_on_mnt ++ rm $mnt/randA ++ rm $mnt/randB ++ rm $mnt/randC ++ rm -rf $mnt/1 ++ rm -rf $mnt/2 ++ umount $mnt ++ lvchange -an $vg/$lv1 ++} ++ ++vgcreate $SHARED $vg "$dev1" ++vgextend $vg "$dev2" ++ ++# Use a large enough size so that the cleaner will not ++# finish immediately when detaching, and will require ++# a secondary check from command top level. ++ ++lvcreate -n $lv1 -L 560M -an $vg "$dev1" ++lvcreate -n $lv2 -L 500M -an $vg "$dev2" ++ ++lvchange -ay $vg/$lv1 ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++ ++lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 ++dmsetup table $vg-$lv1 ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++ ++_add_new_data_to_mnt ++_add_more_data_to_mnt ++_verify_data_on_mnt ++ ++dd if=/dev/zero of=$mnt/big1 bs=1M count=100 oflag=sync ++dd if=/dev/zero of=$mnt/big2 bs=1M count=100 oflag=sync ++dd if=/dev/zero of=$mnt/big3 bs=1M count=100 oflag=sync ++dd if=/dev/zero of=$mnt/big4 bs=1M count=100 oflag=sync ++ ++lvconvert --splitcache $vg/$lv1 ++check lv_field $vg/$lv1 segtype linear ++check lv_field $vg/$lv2 segtype linear ++dmsetup table $vg-$lv1 ++_verify_data_on_mnt ++_verify_more_data_on_mnt ++dd if=$mnt/big4 of=/dev/null bs=1M count=100 ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvchange -an $vg/$lv2 ++lvremove $vg/$lv1 ++lvremove $vg/$lv2 ++ ++vgremove -ff $vg ++ +diff --git a/test/shell/writecache-split.sh b/test/shell/writecache-split.sh +index 0f2dc47..e615e2a 100644 +--- a/test/shell/writecache-split.sh ++++ b/test/shell/writecache-split.sh +@@ -20,29 +20,21 @@ mkfs_mount_umount() + { + lvt=$1 + +- lvchange -ay $vg/$lvt +- + mkfs.xfs -f -s size=4096 "$DM_DEV_DIR/$vg/$lvt" + mount "$DM_DEV_DIR/$vg/$lvt" "$mount_dir" + cp pattern1 "$mount_dir/pattern1" + dd if=/dev/zero of="$mount_dir/zeros2M" bs=1M count=32 conv=fdatasync + umount "$mount_dir" +- +- lvchange -an $vg/$lvt + } + + mount_umount() + { + lvt=$1 + +- lvchange -ay $vg/$lvt +- + mount "$DM_DEV_DIR/$vg/$lvt" "$mount_dir" + diff pattern1 "$mount_dir/pattern1" + dd if="$mount_dir/zeros2M" of=/dev/null bs=1M count=32 + umount "$mount_dir" +- +- lvchange -an $vg/$lvt + } + + aux have_writecache 1 0 0 || skip +@@ -62,18 +54,38 @@ lvcreate -n $lv1 -l 16 -an $vg "$dev1" "$dev4" + lvcreate -n $lv2 -l 4 -an $vg "$dev2" + + # +-# split when no devs are missing ++# split while inactive + # + + lvconvert -y --type writecache --cachevol $lv2 $vg/$lv1 + ++lvchange -ay $vg/$lv1 + mkfs_mount_umount $lv1 ++lvchange -an $vg/$lv1 + + lvconvert --splitcache $vg/$lv1 + lvs -o segtype $vg/$lv1 | grep linear + lvs -o segtype $vg/$lv2 | grep linear + ++lvchange -ay $vg/$lv1 + mount_umount $lv1 ++lvchange -an $vg/$lv1 ++ ++# ++# split while active ++# ++ ++lvconvert -y --type writecache --cachevol $lv2 $vg/$lv1 ++ ++lvchange -ay $vg/$lv1 ++mkfs_mount_umount $lv1 ++ ++lvconvert --splitcache $vg/$lv1 ++lvs -o segtype $vg/$lv1 | grep linear ++lvs -o segtype $vg/$lv2 | grep linear ++ ++mount_umount $lv1 ++lvchange -an $vg/$lv1 + + # + # split while cachevol is missing +@@ -81,7 +93,9 @@ mount_umount $lv1 + + lvconvert -y --type writecache --cachevol $lv2 $vg/$lv1 + ++lvchange -ay $vg/$lv1 + mkfs_mount_umount $lv1 ++lvchange -an $vg/$lv1 + + aux disable_dev "$dev2" + +@@ -108,7 +122,9 @@ lvcreate -n $lv2 -l 14 -an $vg "$dev2" "$dev3" + + lvconvert -y --type writecache --cachevol $lv2 $vg/$lv1 + ++lvchange -ay $vg/$lv1 + mkfs_mount_umount $lv1 ++lvchange -an $vg/$lv1 + + aux disable_dev "$dev3" + +diff --git a/test/shell/writecache.sh b/test/shell/writecache.sh +index 8852e93..39ef319 100644 +--- a/test/shell/writecache.sh ++++ b/test/shell/writecache.sh +@@ -19,152 +19,251 @@ SKIP_WITH_LVMPOLLD=1 + aux have_writecache 1 0 0 || skip + which mkfs.xfs || skip + +-mount_dir="mnt" +-mkdir -p $mount_dir ++# scsi_debug devices with 512 LBS 512 PBS ++aux prepare_scsi_debug_dev 256 ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/logical_block_size "512" ++check sysfs "$(< SCSI_DEBUG_DEV)" queue/physical_block_size "512" ++aux prepare_devs 2 64 ++ ++# scsi_debug devices with 512 LBS and 4K PBS ++#aux prepare_scsi_debug_dev 256 sector_size=512 physblk_exp=3 ++#check sysfs "$(< SCSI_DEBUG_DEV)" queue/logical_block_size "512" ++#check sysfs "$(< SCSI_DEBUG_DEV)" queue/physical_block_size "4096" ++#aux prepare_devs 2 64 ++ ++# loop devs with 512 LBS and 512 PBS ++#dd if=/dev/zero of=loopa bs=$((1024*1024)) count=64 2> /dev/null ++#dd if=/dev/zero of=loopb bs=$((1024*1024)) count=64 2> /dev/null ++#LOOP1=$(losetup -f loopa --show) ++#LOOP2=$(losetup -f loopb --show) ++#aux extend_filter "a|$LOOP1|" ++#aux extend_filter "a|$LOOP2|" ++#aux lvmconf 'devices/scan = "/dev"' ++#dev1=$LOOP1 ++#dev2=$LOOP2 ++ ++# loop devs with 4096 LBS and 4096 PBS ++#dd if=/dev/zero of=loopa bs=$((1024*1024)) count=64 2> /dev/null ++#dd if=/dev/zero of=loopb bs=$((1024*1024)) count=64 2> /dev/null ++#LOOP1=$(losetup -f loopa --sector-size 4096 --show) ++#LOOP2=$(losetup -f loopb --sector-size 4096 --show) ++#aux extend_filter "a|$LOOP1|" ++#aux extend_filter "a|$LOOP2|" ++#aux lvmconf 'devices/scan = "/dev"' ++#dev1=$LOOP1 ++#dev2=$LOOP2 ++ ++# the default is brd ram devs with 512 LBS 4K PBS ++# aux prepare_devs 2 64 ++ ++blockdev --getss "$dev1" ++blockdev --getpbsz "$dev1" ++blockdev --getss "$dev2" ++blockdev --getpbsz "$dev2" ++ ++ ++mnt="mnt" ++mkdir -p $mnt ++ ++for i in `seq 1 16384`; do echo -n "A" >> fileA; done ++for i in `seq 1 16384`; do echo -n "B" >> fileB; done ++for i in `seq 1 16384`; do echo -n "C" >> fileC; done + + # generate random data +-dd if=/dev/urandom of=pattern1 bs=512K count=1 ++dd if=/dev/urandom of=randA bs=512K count=2 ++dd if=/dev/urandom of=randB bs=512K count=3 ++dd if=/dev/urandom of=randC bs=512K count=4 ++ ++_add_new_data_to_mnt() { ++ mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++ ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ # add original data ++ cp randA $mnt ++ cp randB $mnt ++ cp randC $mnt ++ mkdir $mnt/1 ++ cp fileA $mnt/1 ++ cp fileB $mnt/1 ++ cp fileC $mnt/1 ++ mkdir $mnt/2 ++ cp fileA $mnt/2 ++ cp fileB $mnt/2 ++ cp fileC $mnt/2 ++ sync ++} ++ ++_add_more_data_to_mnt() { ++ mkdir $mnt/more ++ cp fileA $mnt/more ++ cp fileB $mnt/more ++ cp fileC $mnt/more ++ cp randA $mnt/more ++ cp randB $mnt/more ++ cp randC $mnt/more ++ sync ++} ++ ++_verify_data_on_mnt() { ++ diff randA $mnt/randA ++ diff randB $mnt/randB ++ diff randC $mnt/randC ++ diff fileA $mnt/1/fileA ++ diff fileB $mnt/1/fileB ++ diff fileC $mnt/1/fileC ++ diff fileA $mnt/2/fileA ++ diff fileB $mnt/2/fileB ++ diff fileC $mnt/2/fileC ++} ++ ++_verify_more_data_on_mnt() { ++ diff randA $mnt/more/randA ++ diff randB $mnt/more/randB ++ diff randC $mnt/more/randC ++ diff fileA $mnt/more/fileA ++ diff fileB $mnt/more/fileB ++ diff fileC $mnt/more/fileC ++} ++ ++_verify_data_on_lv() { ++ lvchange -ay $vg/$lv1 ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ _verify_data_on_mnt ++ rm $mnt/randA ++ rm $mnt/randB ++ rm $mnt/randC ++ rm -rf $mnt/1 ++ rm -rf $mnt/2 ++ umount $mnt ++ lvchange -an $vg/$lv1 ++} + +-aux prepare_devs 2 64 + + vgcreate $SHARED $vg "$dev1" +- + vgextend $vg "$dev2" + +-lvcreate -n $lv1 -l 8 -an $vg "$dev1" +- +-lvcreate -n $lv2 -l 4 -an $vg "$dev2" ++blockdev --getss "$dev1" ++blockdev --getpbsz "$dev1" ++blockdev --getss "$dev2" ++blockdev --getpbsz "$dev2" + +-# test1: create fs on LV before writecache is attached ++# Test attach while inactive, detach while inactive ++# create fs on LV before writecache is attached + ++lvcreate -n $lv1 -l 8 -an $vg "$dev1" ++lvcreate -n $lv2 -l 4 -an $vg "$dev2" + lvchange -ay $vg/$lv1 +- +-mkfs.xfs -f -s size=4096 "$DM_DEV_DIR/$vg/$lv1" +- +-mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir +- +-cp pattern1 $mount_dir/pattern1 +- +-umount $mount_dir ++_add_new_data_to_mnt ++umount $mnt + lvchange -an $vg/$lv1 +- + lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 +- + check lv_field $vg/$lv1 segtype writecache +- + lvs -a $vg/${lv2}_cvol --noheadings -o segtype >out + grep linear out +- + lvchange -ay $vg/$lv1 +- +-mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir +- +-diff pattern1 $mount_dir/pattern1 +- +-cp pattern1 $mount_dir/pattern1b +- +-ls -l $mount_dir +- +-umount $mount_dir +- ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++_add_more_data_to_mnt ++_verify_data_on_mnt ++_verify_more_data_on_mnt ++umount $mnt + lvchange -an $vg/$lv1 +- + lvconvert --splitcache $vg/$lv1 +- + check lv_field $vg/$lv1 segtype linear + check lv_field $vg/$lv2 segtype linear +- + lvchange -ay $vg/$lv1 +-lvchange -ay $vg/$lv2 +- +-mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir +- +-ls -l $mount_dir +- +-diff pattern1 $mount_dir/pattern1 +-diff pattern1 $mount_dir/pattern1b +- +-umount $mount_dir ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" + lvchange -an $vg/$lv1 ++_verify_data_on_lv + lvchange -an $vg/$lv2 ++lvremove $vg/$lv1 ++lvremove $vg/$lv2 + +-# test2: create fs on LV after writecache is attached ++# Test attach while inactive, detach while inactive ++# create fs on LV after writecache is attached + ++lvcreate -n $lv1 -l 8 -an $vg "$dev1" ++lvcreate -n $lv2 -l 4 -an $vg "$dev2" + lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 +- + check lv_field $vg/$lv1 segtype writecache +- + lvs -a $vg/${lv2}_cvol --noheadings -o segtype >out + grep linear out +- + lvchange -ay $vg/$lv1 +- +-mkfs.xfs -f -s size=4096 "$DM_DEV_DIR/$vg/$lv1" +- +-mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir +- +-cp pattern1 $mount_dir/pattern1 +-ls -l $mount_dir +- +-umount $mount_dir ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_add_new_data_to_mnt ++umount $mnt + lvchange -an $vg/$lv1 +- + lvconvert --splitcache $vg/$lv1 +- +-check lv_field $vg/$lv1 segtype linear +-check lv_field $vg/$lv2 segtype linear +- + lvchange -ay $vg/$lv1 +-lvchange -ay $vg/$lv2 +- +-mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir +- +-ls -l $mount_dir +- +-diff pattern1 $mount_dir/pattern1 +- +-umount $mount_dir ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++_add_more_data_to_mnt ++_verify_data_on_mnt ++_verify_more_data_on_mnt ++umount $mnt + lvchange -an $vg/$lv1 +-lvchange -an $vg/$lv2 +- +- +-# test3: attach writecache to an active LV +- +-lvchange -ay $vg/$lv1 +- +-mkfs.xfs -f -s size=4096 "$DM_DEV_DIR/$vg/$lv1" +- +-mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir +- +-cp pattern1 $mount_dir/pattern1 +-ls -l $mount_dir +- +-# TODO BZ 1808012 - can not convert active volume to writecache: +-not lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 +- +-if false; then +-check lv_field $vg/$lv1 segtype writecache +- +-lvs -a $vg/${lv2}_cvol --noheadings -o segtype >out +-grep linear out +- +-cp pattern1 $mount_dir/pattern1.after ++_verify_data_on_lv ++lvremove $vg/$lv1 ++lvremove $vg/$lv2 + +-diff pattern1 $mount_dir/pattern1 +-diff pattern1 $mount_dir/pattern1.after ++# Test attach while active, detach while active + +-umount $mount_dir +-lvchange -an $vg/$lv1 ++lvcreate -n $lv1 -l 8 -an $vg "$dev1" ++lvcreate -n $lv2 -l 4 -an $vg "$dev2" + lvchange -ay $vg/$lv1 +-mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir +- +-diff pattern1 $mount_dir/pattern1 +-diff pattern1 $mount_dir/pattern1.after +-fi +- +-umount $mount_dir ++_add_new_data_to_mnt ++lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_add_more_data_to_mnt ++_verify_data_on_mnt ++lvconvert --splitcache $vg/$lv1 ++check lv_field $vg/$lv1 segtype linear ++check lv_field $vg/$lv2 segtype linear ++blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++_verify_data_on_mnt ++_verify_more_data_on_mnt ++umount $mnt + lvchange -an $vg/$lv1 ++lvchange -an $vg/$lv2 ++_verify_data_on_lv + lvremove $vg/$lv1 ++lvremove $vg/$lv2 + ++# FIXME: test depends on unpushed commit ++# that enables two stage flush using cleaner ++# ++# Test attach while active, detach while active, ++# skip cleaner so flush message is used instead ++# ++# lvcreate -n $lv1 -l 8 -an $vg "$dev1" ++# lvcreate -n $lv2 -l 4 -an $vg "$dev2" ++# lvchange -ay $vg/$lv1 ++# _add_new_data_to_mnt ++# lvconvert --yes --type writecache --cachevol $lv2 $vg/$lv1 ++# blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++# blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++# _add_more_data_to_mnt ++# _verify_data_on_mnt ++# lvconvert --splitcache --cachesettings cleaner=0 $vg/$lv1 ++# check lv_field $vg/$lv1 segtype linear ++# check lv_field $vg/$lv2 segtype linear ++# blockdev --getss "$DM_DEV_DIR/$vg/$lv1" ++# blockdev --getpbsz "$DM_DEV_DIR/$vg/$lv1" ++# _verify_data_on_mnt ++# _verify_more_data_on_mnt ++# umount $mnt ++# lvchange -an $vg/$lv1 ++# lvchange -an $vg/$lv2 ++# _verify_data_on_lv ++# lvremove $vg/$lv1 ++# lvremove $vg/$lv2 ++ + vgremove -ff $vg +- ++ +diff --git a/tools/args.h b/tools/args.h +index d1f604b..3a7e5d4 100644 +--- a/tools/args.h ++++ b/tools/args.h +@@ -126,6 +126,12 @@ arg(cachepool_ARG, '\0', "cachepool", lv_VAL, 0, 0, + arg(cachevol_ARG, '\0', "cachevol", lv_VAL, 0, 0, + "The name of a cache volume.\n") + ++arg(cachedevice_ARG, '\0', "cachedevice", pv_VAL, ARG_GROUPABLE, 0, ++ "The name of a device to use for a cache.\n") ++ ++arg(cachesize_ARG, '\0', "cachesize", sizemb_VAL, 0, 0, ++ "The size of cache to use.\n") ++ + arg(commandprofile_ARG, '\0', "commandprofile", string_VAL, 0, 0, + "The command profile to use for command configuration.\n" + "See \\fBlvm.conf\\fP(5) for more information about profiles.\n") +@@ -1428,7 +1434,16 @@ arg(thin_ARG, 'T', "thin", 0, 0, 0, + "See \\fBlvmthin\\fP(7) for more information about LVM thin provisioning.\n") + + arg(updatemetadata_ARG, '\0', "updatemetadata", 0, 0, 0, +- "Update VG metadata to correct problems.\n") ++ "Update VG metadata to correct problems.\n" ++ "If VG metadata was updated while a PV was missing, and the PV\n" ++ "reappears with an old version of metadata, then this option\n" ++ "(or any other command that writes metadata) will update the\n" ++ "metadata on the previously missing PV. If a PV was removed\n" ++ "from a VG while it was missing, and the PV reappears, using\n" ++ "this option will clear the outdated metadata from the previously\n" ++ "missing PV. If metadata text is damaged on one PV, using this\n" ++ "option will replace the damaged metadata text. For more severe\n" ++ "damage, e.g. with headers, see \\fBpvck\\fP(8).\n") + + arg(uuid_ARG, 'u', "uuid", 0, 0, 0, + "#pvchange\n" +diff --git a/tools/command-lines.in b/tools/command-lines.in +index ed3d041..0051b77 100644 +--- a/tools/command-lines.in ++++ b/tools/command-lines.in +@@ -247,7 +247,7 @@ RULE: --profile not --detachprofile + RULE: --metadataprofile not --detachprofile + RULE: --minrecoveryrate --maxrecoveryrate and LV_raid + RULE: --writebehind --writemostly and LV_raid1 +-RULE: --cachemode --cachepolicy --cachesettings and LV_cache LV_cachepool ++RULE: --cachemode --cachepolicy --cachesettings and LV_cache LV_cachepool LV_writecache + RULE: --errorwhenfull --discards --zero and LV_thinpool + RULE: --permission not lv_is_external_origin lv_is_raid_metadata lv_is_raid_image LV_thinpool + RULE: --alloc --contiguous --metadataprofile --permission --persistent --profile --readahead not lv_is_thick_origin +@@ -497,6 +497,20 @@ FLAGS: SECONDARY_SYNTAX + + --- + ++lvconvert --type writecache --cachedevice PV LV ++OO: OO_LVCONVERT, --cachesize SizeMB, --cachesettings String ++ID: lvconvert_to_writecache_with_device ++DESC: Add a writecache to an LV, using a specified cache device. ++RULE: all and lv_is_visible ++ ++lvconvert --type cache --cachedevice PV LV ++OO: OO_LVCONVERT, --cachesize SizeMB, --cachesettings String ++ID: lvconvert_to_cache_with_device ++DESC: Add a cache to an LV, using a specified cache device. ++RULE: all and lv_is_visible ++ ++--- ++ + lvconvert --type thin-pool LV_linear_striped_raid_cache + OO: --stripes_long Number, --stripesize SizeKB, + --discards Discards, OO_LVCONVERT_POOL, OO_LVCONVERT +@@ -1205,87 +1219,107 @@ lvcreate --type cache --size SizeMB --cachepool LV_cachepool VG + OO: --cache, OO_LVCREATE_POOL, OO_LVCREATE_CACHE, OO_LVCREATE, + --stripes Number, --stripesize SizeKB + OP: PV ... +-ID: lvcreate_cache_vol_with_new_origin +-DESC: Create a cache LV, first creating a new origin LV, +-DESC: then combining it with the existing cache pool named +-DESC: by the --cachepool arg. ++ID: lvcreate_and_attach_cachepool ++DESC: Create a new LV, then attach the specified cachepool ++DESC: which converts the new LV to type cache. + + # alternate form of lvcreate --type cache ++# (omits the --type cache option which is inferred) + lvcreate --size SizeMB --cachepool LV_cachepool VG + OO: --type cache, --cache, OO_LVCREATE_CACHE, OO_LVCREATE, + --stripes Number, --stripesize SizeKB + OP: PV ... +-ID: lvcreate_cache_vol_with_new_origin +-DESC: Create a cache LV, first creating a new origin LV, +-DESC: then combining it with the existing cache pool named +-DESC: by the --cachepool arg (variant, infers --type cache). ++ID: lvcreate_and_attach_cachepool_v2 ++DESC: Create a new LV, then attach the specified cachepool ++DESC: which converts the new LV to type cache ++DESC: (variant, infers --type cache.) + FLAGS: SECONDARY_SYNTAX + + # alternate form of lvcreate --type cache ++# (moves cachepool from option arg to position arg, ++# dropping the normal VG position arg) + lvcreate --type cache --size SizeMB LV_cachepool + OO: --cache, OO_LVCREATE_POOL, OO_LVCREATE_CACHE, OO_LVCREATE, + --stripes Number, --stripesize SizeKB + OP: PV ... +-ID: lvcreate_cache_vol_with_new_origin +-DESC: Create a cache LV, first creating a new origin LV, +-DESC: then combining it with the existing cache pool named +-DESC: in the first arg (variant, also use --cachepool). ++ID: lvcreate_and_attach_cachepool_v3 ++DESC: Create a new LV, then attach the specified cachepool ++DESC: which converts the new LV to type cache. ++DESC: (variant, also use --cachepool). + FLAGS: SECONDARY_SYNTAX + +-# This is a ridiculously crazy command which nobody could +-# understand. It should be be eliminated. It does two different +-# things depending on whether LV in pos 1 is a cachepool LV +-# or not. Both variations are unnecessary. +-# +-# 1. If LV is a cachepool, then it's an alternate form of +-# an already complicated command above. +-# +-# # alternate form for lvcreate_cache_vol_with_new_origin +-# lvcreate --cache --size SizeMB LV_cachepool +-# OO: --type cache, --cache, OO_LVCREATE_CACHE, OO_LVCREATE, --stripes Number, --stripesize SizeKB +-# OP: PV ... +-# ID: lvcreate_cache_vol_with_new_origin +-# DESC: Create a cache LV, first creating a new origin LV, +-# DESC: then combining it with the existing cache pool named +-# DESC: in the first arg (variant, infers --type cache, +-# DESC: also use --cachepool). +-# +-# 2. If LV is not a cachepool, then it's a disguised lvconvert. +-# +-# # FIXME: this should be done by lvconvert, and this command removed +-# lvcreate --type cache --size SizeMB LV +-# OO: OO_LVCREATE_POOL, OO_LVCREATE_CACHE, OO_LVCREATE +-# OP: PV ... +-# ID: lvcreate_convert_to_cache_vol_with_cachepool +-# DESC: Convert the specified LV to type cache after creating a new +-# DESC: cache pool LV to use (use lvconvert). ++# This command has two different meanings which ought to ++# have separate command defs, but since the syntax is the ++# same for both they have to share one command def with ++# an ambiguous meaning. Which command is performed depends ++# on whether the LV in the first arg position is a ++# cachepool or not (we can't have two different command ++# defs that differ only in the type of LV in the arg position ++# because when parsing commands we don't know the LV type.) ++# ++# 1. An alternate form of lvcreate_and_attach_cachepool_v3 ++# this syntax: lvcreate --cache --size SizeMB LV_cachepool ++# is alternative for: lvcreate --type cache --size SizeMB LV_cachepool ++# ++# 2. An alternative to using lvconvert to convert LV to type cache, ++# but in this case the cachepool is created internally and ++# then attached to the LV arg. + # + # Note that stripes are accepted by the first and not by the + # second, but it's not possible to validate this until after + # the LV type is known. +-# +-# So, to define this syntax we have to combine both of +-# those variants, each crazy on it's own, into one +-# ridiculous command. + +-# def1: alternate form of lvcreate --type cache, or +-# def2: it should be done by lvconvert. + lvcreate --cache --size SizeMB LV + OO: OO_LVCREATE_CACHE, OO_LVCREATE_POOL, OO_LVCREATE, + --stripes Number, --stripesize SizeKB + OP: PV ... +-ID: lvcreate_cache_vol_with_new_origin_or_convert_to_cache_vol_with_cachepool +-DESC: When LV is a cache pool, create a cache LV, +-DESC: first creating a new origin LV, then combining it with +-DESC: the existing cache pool named in the first arg +-DESC: (variant, infers --type cache, also use --cachepool). +-DESC: When LV is not a cache pool, convert the specified LV +-DESC: to type cache after creating a new cache pool LV to use +-DESC: (use lvconvert). ++ID: lvcreate_new_plus_old_cachepool_or_lvconvert_old_plus_new_cachepool ++DESC: When the LV arg is a cachepool, then create a new LV and ++DESC: attach the cachepool arg to it. ++DESC: (variant, use --type cache and --cachepool.) ++DESC: When the LV arg is not a cachepool, then create a new cachepool ++DESC: and attach it to the LV arg (alternative, use lvconvert.) + FLAGS: SECONDARY_SYNTAX + + --- + ++# These all create a new origin LV, then forwards to lvconvert ++# which combines it with a cachevol (which already exists or ++# which needs to be created from cachedevice), converting ++# the new LV to type cache or writecache. ++ ++lvcreate --type cache --size SizeMB --cachevol LV VG ++OO: OO_LVCREATE, OO_LVCREATE_CACHE, --stripes Number, --stripesize SizeKB ++OP: PV ... ++ID: lvcreate_and_attach_cachevol_for_cache ++DESC: Create a new LV, then attach the specified cachevol ++DESC: which converts the new LV to type cache. ++ ++lvcreate --type cache --size SizeMB --cachedevice PV VG ++OO: OO_LVCREATE, OO_LVCREATE_CACHE, --cachesize SizeMB, --stripes Number, --stripesize SizeKB ++OP: PV ... ++ID: lvcreate_and_attach_cachedevice_for_cache ++DESC: Create a new LV, then attach a cachevol created from ++DESC: the specified cache device, which converts the ++DESC: new LV to type cache. ++ ++lvcreate --type writecache --size SizeMB --cachevol LV VG ++OO: OO_LVCREATE, --cachesettings String, --stripes Number, --stripesize SizeKB ++OP: PV ... ++ID: lvcreate_and_attach_cachevol_for_writecache ++DESC: Create a new LV, then attach the specified cachevol ++DESC: which converts the new LV to type writecache. ++ ++lvcreate --type writecache --size SizeMB --cachedevice PV VG ++OO: OO_LVCREATE, --cachesize SizeMB, --cachesettings String, --stripes Number, --stripesize SizeKB ++OP: PV ... ++ID: lvcreate_and_attach_cachedevice_for_writecache ++DESC: Create a new LV, then attach a cachevol created from ++DESC: the specified cache device, which converts the ++DESC: new LV to type writecache. ++ ++--- ++ + lvdisplay + OO: --aligned, --all, --binary, --colon, --columns, + --configreport ConfigReport, --foreign, --history, --ignorelockingfailure, +diff --git a/tools/command.c b/tools/command.c +index 511dda1..2d01849 100644 +--- a/tools/command.c ++++ b/tools/command.c +@@ -1420,6 +1420,9 @@ int define_commands(struct cmd_context *cmdtool, const char *run_name) + if (line[0] == '\n') + break; + ++ if (!strcmp(line, "---") || !strcmp(line, "--")) ++ continue; ++ + if ((n = strchr(line, '\n'))) + *n = '\0'; + +diff --git a/tools/lvchange.c b/tools/lvchange.c +index 2d5bb32..c0adadf 100644 +--- a/tools/lvchange.c ++++ b/tools/lvchange.c +@@ -606,6 +606,88 @@ static int _lvchange_persistent(struct cmd_context *cmd, + return 1; + } + ++static int _lvchange_writecache(struct cmd_context *cmd, ++ struct logical_volume *lv, ++ uint32_t *mr) ++{ ++ struct writecache_settings settings = { 0 }; ++ uint32_t block_size_sectors = 0; ++ struct lv_segment *seg = first_seg(lv); ++ int set_count = 0; ++ ++ if (!get_writecache_settings(cmd, &settings, &block_size_sectors)) ++ return_0; ++ ++ if (block_size_sectors && (seg->writecache_block_size != (block_size_sectors * 512))) { ++ log_error("Cannot change existing block size %u bytes.", seg->writecache_block_size); ++ return 0; ++ } ++ ++ if (settings.high_watermark_set) { ++ seg->writecache_settings.high_watermark_set = settings.high_watermark_set; ++ seg->writecache_settings.high_watermark = settings.high_watermark; ++ set_count++; ++ } ++ if (settings.low_watermark_set) { ++ seg->writecache_settings.low_watermark_set = settings.low_watermark_set; ++ seg->writecache_settings.low_watermark = settings.low_watermark; ++ set_count++; ++ } ++ if (settings.writeback_jobs_set) { ++ seg->writecache_settings.writeback_jobs_set = settings.writeback_jobs_set; ++ seg->writecache_settings.writeback_jobs = settings.writeback_jobs; ++ set_count++; ++ } ++ if (settings.autocommit_blocks_set) { ++ seg->writecache_settings.autocommit_blocks_set = settings.autocommit_blocks_set; ++ seg->writecache_settings.autocommit_blocks = settings.autocommit_blocks; ++ set_count++; ++ } ++ if (settings.autocommit_time_set) { ++ seg->writecache_settings.autocommit_time_set = settings.autocommit_time_set; ++ seg->writecache_settings.autocommit_time = settings.autocommit_time; ++ set_count++; ++ } ++ if (settings.fua_set) { ++ seg->writecache_settings.fua_set = settings.fua_set; ++ seg->writecache_settings.fua = settings.fua; ++ set_count++; ++ } ++ if (settings.nofua_set) { ++ seg->writecache_settings.nofua_set = settings.nofua_set; ++ seg->writecache_settings.nofua = settings.nofua; ++ set_count++; ++ } ++ if (settings.cleaner_set) { ++ seg->writecache_settings.cleaner_set = settings.cleaner_set; ++ seg->writecache_settings.cleaner = settings.cleaner; ++ set_count++; ++ } ++ if (settings.max_age_set) { ++ seg->writecache_settings.max_age_set = settings.max_age_set; ++ seg->writecache_settings.max_age = settings.max_age; ++ set_count++; ++ } ++ ++ if (!set_count) { ++ /* ++ * Empty settings can be used to clear all current settings, ++ * lvchange --cachesettings "" vg/lv ++ */ ++ if (!arg_count(cmd, yes_ARG) && ++ yes_no_prompt("Clear all writecache settings? ") == 'n') { ++ log_print("No settings changed."); ++ return 1; ++ } ++ memset(&seg->writecache_settings, 0, sizeof(struct writecache_settings)); ++ } ++ ++ /* Request caller to commit and reload metadata */ ++ *mr |= MR_RELOAD; ++ ++ return 1; ++} ++ + static int _lvchange_cache(struct cmd_context *cmd, + struct logical_volume *lv, + uint32_t *mr) +@@ -619,6 +701,9 @@ static int _lvchange_cache(struct cmd_context *cmd, + int r = 0, is_clean; + uint32_t chunk_size = 0; /* FYI: lvchange does NOT support its change */ + ++ if (lv_is_writecache(lv)) ++ return _lvchange_writecache(cmd, lv, mr); ++ + seg = first_seg(lv); + + if (seg_is_cache(seg) && lv_is_cache_vol(seg->pool_lv)) +diff --git a/tools/lvconvert.c b/tools/lvconvert.c +index 8652252..0155fdb 100644 +--- a/tools/lvconvert.c ++++ b/tools/lvconvert.c +@@ -1319,6 +1319,7 @@ static int _raid4_conversion_supported(struct logical_volume *lv, struct lvconve + static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *lp) + { + int image_count = 0; ++ int images_reduced = 0; + struct cmd_context *cmd = lv->vg->cmd; + struct lv_segment *seg = first_seg(lv); + +@@ -1357,6 +1358,8 @@ static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *l + else + image_count = lp->mirrors + 1; + ++ images_reduced = (image_count < lv_raid_image_count(lv)); ++ + if (image_count < 1) { + log_error("Unable to %s images by specified amount.", + lp->keep_mimages ? "split" : "reduce"); +@@ -1400,7 +1403,7 @@ static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *l + lp->region_size : seg->region_size , lp->pvh)) + return_0; + +- if (lv_raid_has_integrity(lv)) { ++ if (lv_raid_has_integrity(lv) && !images_reduced) { + struct integrity_settings *isettings = NULL; + if (!lv_get_raid_integrity_settings(lv, &isettings)) + return_0; +@@ -4245,51 +4248,191 @@ int lvconvert_to_pool_cmd(struct cmd_context *cmd, int argc, char **argv) + NULL, NULL, &_lvconvert_to_pool_single); + } + +-static int _lvconvert_cachevol_attach_single(struct cmd_context *cmd, +- struct logical_volume *lv, +- struct processing_handle *handle) ++#define MAX_CACHEDEVS 8 ++ ++static int _lv_create_cachevol(struct cmd_context *cmd, ++ struct volume_group *vg, ++ struct logical_volume *lv, ++ struct logical_volume **cachevol_lv) + { +- struct volume_group *vg = lv->vg; +- struct logical_volume *cachevol_lv; +- const char *cachevol_name; ++ char cvname[NAME_LEN]; ++ struct dm_list *use_pvh; ++ struct pv_list *pvl; ++ char *dev_name; ++ struct device *dev_fast; ++ char *dev_argv[MAX_CACHEDEVS]; ++ int dev_argc = 0; ++ uint64_t cache_size_sectors = 0; ++ uint64_t full_size_sectors = 0; ++ uint64_t pv_size_sectors; ++ struct logical_volume *cachevol; ++ struct arg_value_group_list *group; ++ struct lvcreate_params lp = { ++ .activate = CHANGE_AN, ++ .alloc = ALLOC_INHERIT, ++ .major = -1, ++ .minor = -1, ++ .permission = LVM_READ | LVM_WRITE, ++ .pvh = &vg->pvs, ++ .read_ahead = DM_READ_AHEAD_NONE, ++ .stripes = 1, ++ .vg_name = vg->name, ++ .zero = 0, ++ .wipe_signatures = 0, ++ .suppress_zero_warn = 1, ++ }; + +- if (!(cachevol_name = arg_str_value(cmd, cachevol_ARG, NULL))) +- goto_out; ++ /* ++ * If cache size is not set, and all cachedevice's are unused, ++ * then the cache size is the sum of all cachedevice sizes. ++ */ ++ cache_size_sectors = arg_uint64_value(cmd, cachesize_ARG, 0); + +- if (!validate_lvname_param(cmd, &vg->name, &cachevol_name)) +- goto_out; ++ dm_list_iterate_items(group, &cmd->arg_value_groups) { ++ if (!grouped_arg_is_set(group->arg_values, cachedevice_ARG)) ++ continue; + +- if (!(cachevol_lv = find_lv(vg, cachevol_name))) { +- log_error("Cache single %s not found.", cachevol_name); +- goto out; ++ if (!(dev_name = (char *)grouped_arg_str_value(group->arg_values, cachedevice_ARG, NULL))) ++ break; ++ ++ if (dev_name[0] == '@') { ++ if (!cache_size_sectors) { ++ log_error("With tag as cachedevice, --cachesize is required."); ++ return 0; ++ } ++ goto add_dev_arg; ++ } ++ ++ if (!(dev_fast = dev_cache_get(cmd, dev_name, cmd->filter))) { ++ log_error("Device %s not found.", dev_name); ++ return 0; ++ } ++ ++ if (!(pvl = find_pv_in_vg(vg, dev_name))) { ++ log_error("PV %s not found in VG.", dev_name); ++ return 0; ++ } ++ ++ /* ++ * If the dev is used in the VG, then require a cachesize to allocate ++ * from it. If it is not used in the VG, then prompt asking if the ++ * entire dev should be used. ++ */ ++ if (!cache_size_sectors && pvl->pv->pe_alloc_count) { ++ log_error("PV %s is in use, --cachesize is required.", dev_name); ++ return 0; ++ } ++ ++ if (!cache_size_sectors) { ++ pv_size_sectors = (pvl->pv->pe_count * vg->extent_size); ++ ++ if (!arg_is_set(cmd, yes_ARG) && ++ yes_no_prompt("Use all %s from %s for cache? [y/n]: ", ++ display_size(cmd, pv_size_sectors), dev_name) == 'n') { ++ log_print("Use --cachesize SizeMB to use a part of the cachedevice."); ++ log_error("Conversion aborted."); ++ return 0; ++ } ++ full_size_sectors += pv_size_sectors; ++ } ++ add_dev_arg: ++ if (dev_argc >= MAX_CACHEDEVS) { ++ log_error("Cannot allocate from more than %u cache devices.", MAX_CACHEDEVS); ++ return 0; ++ } ++ ++ dev_argv[dev_argc++] = dev_name; + } + +- if (lv_is_cache_vol(cachevol_lv)) { +- log_error("LV %s is already used as a cachevol.", display_lvname(cachevol_lv)); +- goto out; ++ if (!cache_size_sectors) ++ cache_size_sectors = full_size_sectors; ++ ++ if (!dev_argc) { ++ log_error("No cachedevice specified to create a cachevol."); ++ return 0; + } + +- /* Ensure the LV is not active elsewhere. */ +- if (!lockd_lv(cmd, lv, "ex", 0)) +- goto_out; ++ if (!(use_pvh = create_pv_list(cmd->mem, vg, dev_argc, dev_argv, 1))) { ++ log_error("cachedevice not found in VG %s.", dev_name); ++ return 0; ++ } + +- if (!dm_list_empty(&cachevol_lv->segs_using_this_lv)) { +- log_error("LV %s is already in use.", display_lvname(cachevol_lv)); +- goto out; ++ if (dm_snprintf(cvname, NAME_LEN, "%s_cache", lv->name) < 0) { ++ log_error("Failed to create cachevol LV name."); ++ return 0; + } + +- if (!arg_is_set(cmd, yes_ARG) && +- yes_no_prompt("Erase all existing data on %s? [y/n]: ", display_lvname(cachevol_lv)) == 'n') { +- log_error("Conversion aborted."); +- goto out; ++ lp.lv_name = cvname; ++ lp.pvh = use_pvh; ++ lp.extents = cache_size_sectors / vg->extent_size; ++ ++ log_print("Creating cachevol LV %s with size %s.", ++ cvname, display_size(cmd, cache_size_sectors)); ++ ++ dm_list_init(&lp.tags); ++ ++ if (!(lp.segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_STRIPED))) ++ return_0; ++ ++ if (!(cachevol = lv_create_single(vg, &lp))) { ++ log_error("Failed to create cachevol LV"); ++ return 0; ++ } ++ ++ *cachevol_lv = cachevol; ++ return 1; ++} ++ ++int lvconvert_cachevol_attach_single(struct cmd_context *cmd, ++ struct logical_volume *lv, ++ struct processing_handle *handle) ++{ ++ struct volume_group *vg = lv->vg; ++ struct logical_volume *lv_fast; ++ const char *fast_name; ++ ++ /* ++ * User specifies an existing cachevol to use or a cachedevice ++ * to create a cachevol from. ++ */ ++ if ((fast_name = arg_str_value(cmd, cachevol_ARG, NULL))) { ++ if (!validate_lvname_param(cmd, &vg->name, &fast_name)) ++ goto_bad; ++ ++ if (!(lv_fast = find_lv(vg, fast_name))) { ++ log_error("LV %s not found.", fast_name); ++ goto bad; ++ } ++ ++ if (lv_is_cache_vol(lv_fast)) { ++ log_error("LV %s is already used as a cachevol.", display_lvname(lv_fast)); ++ goto bad; ++ } ++ ++ if (!dm_list_empty(&lv_fast->segs_using_this_lv)) { ++ log_error("LV %s is already in use.", display_lvname(lv_fast)); ++ goto bad; ++ } ++ ++ if (!arg_is_set(cmd, yes_ARG) && ++ yes_no_prompt("Erase all existing data on %s? [y/n]: ", display_lvname(lv_fast)) == 'n') { ++ log_error("Conversion aborted."); ++ goto bad; ++ } ++ ++ if (!lockd_lv(cmd, lv_fast, "ex", 0)) ++ goto_bad; ++ } else { ++ if (!_lv_create_cachevol(cmd, vg, lv, &lv_fast)) ++ goto_bad; + } + + /* Ensure the LV is not active elsewhere. */ +- if (!lockd_lv(cmd, cachevol_lv, "ex", LDLV_PERSISTENT)) +- goto_out; ++ if (!lockd_lv(cmd, lv, "ex", 0)) ++ goto_bad; + +- if (!wipe_cache_pool(cachevol_lv)) +- goto_out; ++ if (!wipe_cache_pool(lv_fast)) ++ goto_bad; + + /* When the lv arg is a thinpool, redirect command to data sub lv. */ + +@@ -4299,17 +4442,17 @@ static int _lvconvert_cachevol_attach_single(struct cmd_context *cmd, + } + + if (_raid_split_image_conversion(lv)) +- goto_out; ++ goto_bad; + + /* Attach the cache to the main LV. */ + +- if (!_cache_vol_attach(cmd, lv, cachevol_lv)) +- goto_out; ++ if (!_cache_vol_attach(cmd, lv, lv_fast)) ++ goto_bad; + + log_print_unless_silent("Logical volume %s is now cached.", display_lvname(lv)); + + return ECMD_PROCESSED; +- out: ++ bad: + return ECMD_FAILED; + } + +@@ -5308,19 +5451,8 @@ static int _lvconvert_detach_writecache(struct cmd_context *cmd, + struct logical_volume *lv, + struct logical_volume *lv_fast) + { +- char cvol_name[NAME_LEN]; +- char *c; + int noflush = 0; + +- /* +- * LV must be inactive externally before detaching cache. +- */ +- +- if (lv_info(cmd, lv, 1, NULL, 0, 0)) { +- log_error("LV %s must be inactive to detach writecache.", display_lvname(lv)); +- return 0; +- } +- + if (!archive(lv->vg)) + return_0; + +@@ -5344,36 +5476,23 @@ static int _lvconvert_detach_writecache(struct cmd_context *cmd, + noflush = 1; + } + +- if (!lv_detach_writecache_cachevol(lv, noflush)) +- return_0; +- + /* +- * Rename lv_fast back to its original name, without the _cvol +- * suffix that was added when lv_fast was attached for caching. ++ * TODO: send a message to writecache in the kernel to start writing ++ * back cache data to the origin. Then release the vg lock and monitor ++ * the progress of that writeback. When it's complete we can reacquire ++ * the vg lock, rescan the vg (ensure it hasn't changed), and do the ++ * detach which should be quick since the writeback is complete. If ++ * this command is canceled while monitoring writeback, it should just ++ * be rerun. The LV will continue to have the writecache until this ++ * command is run to completion. + */ +- if (!dm_strncpy(cvol_name, lv_fast->name, sizeof(cvol_name)) || +- !(c = strstr(cvol_name, "_cvol"))) { +- log_debug("LV %s has no suffix for cachevol (skipping rename).", +- display_lvname(lv_fast)); +- } else { +- *c = 0; +- /* If the name is in use, generate new lvol%d */ +- if (lv_name_is_used_in_vg(lv->vg, cvol_name, NULL) && +- !generate_lv_name(lv->vg, "lvol%d", cvol_name, sizeof(cvol_name))) { +- log_error("Failed to generate unique name for unused logical volume."); +- return 0; +- } +- +- if (!lv_rename_update(cmd, lv_fast, cvol_name, 0)) +- return_0; +- } + +- if (!vg_write(lv->vg) || !vg_commit(lv->vg)) ++ if (!lv_detach_writecache_cachevol(lv, noflush)) + return_0; + + backup(lv->vg); + +- log_print_unless_silent("Logical volume %s write cache has been detached.", ++ log_print_unless_silent("Logical volume %s writecache has been detached.", + display_lvname(lv)); + return 1; + } +@@ -5410,157 +5529,6 @@ static int _writecache_zero(struct cmd_context *cmd, struct logical_volume *lv) + return ret; + } + +-static int _get_one_writecache_setting(struct cmd_context *cmd, struct writecache_settings *settings, +- char *key, char *val, uint32_t *block_size_sectors) +-{ +- /* special case: block_size is not a setting but is set with the --cachesettings option */ +- if (!strncmp(key, "block_size", strlen("block_size"))) { +- uint32_t block_size = 0; +- if (sscanf(val, "%u", &block_size) != 1) +- goto_bad; +- if (block_size == 512) +- *block_size_sectors = 1; +- else if (block_size == 4096) +- *block_size_sectors = 8; +- else +- goto_bad; +- return 1; +- } +- +- if (!strncmp(key, "high_watermark", strlen("high_watermark"))) { +- if (sscanf(val, "%llu", (unsigned long long *)&settings->high_watermark) != 1) +- goto_bad; +- if (settings->high_watermark > 100) +- goto_bad; +- settings->high_watermark_set = 1; +- return 1; +- } +- +- if (!strncmp(key, "low_watermark", strlen("low_watermark"))) { +- if (sscanf(val, "%llu", (unsigned long long *)&settings->low_watermark) != 1) +- goto_bad; +- if (settings->low_watermark > 100) +- goto_bad; +- settings->low_watermark_set = 1; +- return 1; +- } +- +- if (!strncmp(key, "writeback_jobs", strlen("writeback_jobs"))) { +- if (sscanf(val, "%llu", (unsigned long long *)&settings->writeback_jobs) != 1) +- goto_bad; +- settings->writeback_jobs_set = 1; +- return 1; +- } +- +- if (!strncmp(key, "autocommit_blocks", strlen("autocommit_blocks"))) { +- if (sscanf(val, "%llu", (unsigned long long *)&settings->autocommit_blocks) != 1) +- goto_bad; +- settings->autocommit_blocks_set = 1; +- return 1; +- } +- +- if (!strncmp(key, "autocommit_time", strlen("autocommit_time"))) { +- if (sscanf(val, "%llu", (unsigned long long *)&settings->autocommit_time) != 1) +- goto_bad; +- settings->autocommit_time_set = 1; +- return 1; +- } +- +- if (!strncmp(key, "fua", strlen("fua"))) { +- if (settings->nofua_set) { +- log_error("Setting fua and nofua cannot both be set."); +- return 0; +- } +- if (sscanf(val, "%u", &settings->fua) != 1) +- goto_bad; +- settings->fua_set = 1; +- return 1; +- } +- +- if (!strncmp(key, "nofua", strlen("nofua"))) { +- if (settings->fua_set) { +- log_error("Setting fua and nofua cannot both be set."); +- return 0; +- } +- if (sscanf(val, "%u", &settings->nofua) != 1) +- goto_bad; +- settings->nofua_set = 1; +- return 1; +- } +- +- if (settings->new_key) { +- log_error("Setting %s is not recognized. Only one unrecognized setting is allowed.", key); +- return 0; +- } +- +- log_warn("Unrecognized writecache setting \"%s\" may cause activation failure.", key); +- if (yes_no_prompt("Use unrecognized writecache setting? [y/n]: ") == 'n') { +- log_error("Aborting writecache conversion."); +- return 0; +- } +- +- log_warn("Using unrecognized writecache setting: %s = %s.", key, val); +- +- settings->new_key = dm_pool_strdup(cmd->mem, key); +- settings->new_val = dm_pool_strdup(cmd->mem, val); +- return 1; +- +- bad: +- log_error("Invalid setting: %s", key); +- return 0; +-} +- +-static int _get_writecache_settings(struct cmd_context *cmd, struct writecache_settings *settings, +- uint32_t *block_size_sectors) +-{ +- struct arg_value_group_list *group; +- const char *str; +- char key[64]; +- char val[64]; +- int num; +- int pos; +- +- /* +- * "grouped" means that multiple --cachesettings options can be used. +- * Each option is also allowed to contain multiple key = val pairs. +- */ +- +- dm_list_iterate_items(group, &cmd->arg_value_groups) { +- if (!grouped_arg_is_set(group->arg_values, cachesettings_ARG)) +- continue; +- +- if (!(str = grouped_arg_str_value(group->arg_values, cachesettings_ARG, NULL))) +- break; +- +- pos = 0; +- +- while (pos < strlen(str)) { +- /* scan for "key1=val1 key2 = val2 key3= val3" */ +- +- memset(key, 0, sizeof(key)); +- memset(val, 0, sizeof(val)); +- +- if (sscanf(str + pos, " %63[^=]=%63s %n", key, val, &num) != 2) { +- log_error("Invalid setting at: %s", str+pos); +- return 0; +- } +- +- pos += num; +- +- if (!_get_one_writecache_setting(cmd, settings, key, val, block_size_sectors)) +- return_0; +- } +- } +- +- if (settings->high_watermark_set && settings->low_watermark_set && +- (settings->high_watermark <= settings->low_watermark)) { +- log_error("High watermark must be greater than low watermark."); +- return 0; +- } +- +- return 1; +-} +- + static struct logical_volume *_lv_writecache_create(struct cmd_context *cmd, + struct logical_volume *lv, + struct logical_volume *lv_fast, +@@ -5605,9 +5573,171 @@ static struct logical_volume *_lv_writecache_create(struct cmd_context *cmd, + return lv_wcorig; + } + +-#define DEFAULT_WRITECACHE_BLOCK_SIZE_SECTORS 8 /* 4K */ ++/* ++ * Currently only supports writecache block sizes 512 and 4096. ++ * This could be expanded later. ++ */ ++static int _set_writecache_block_size(struct cmd_context *cmd, ++ struct logical_volume *lv, ++ uint32_t *block_size_sectors) ++{ ++ char pathname[PATH_MAX]; ++ struct device *fs_dev; ++ struct dm_list pvs; ++ struct pv_list *pvl; ++ uint32_t fs_block_size = 0; ++ uint32_t block_size_setting = 0; ++ uint32_t block_size = 0; ++ int lbs_unknown = 0, lbs_4k = 0, lbs_512 = 0; ++ int pbs_unknown = 0, pbs_4k = 0, pbs_512 = 0; ++ int rv; ++ ++ /* This is set if the user specified a writecache block size on the command line. */ ++ if (*block_size_sectors) ++ block_size_setting = *block_size_sectors * 512; ++ ++ dm_list_init(&pvs); ++ ++ if (!get_pv_list_for_lv(cmd->mem, lv, &pvs)) { ++ log_error("Failed to build list of PVs for %s.", display_lvname(lv)); ++ goto_bad; ++ } ++ ++ dm_list_iterate_items(pvl, &pvs) { ++ unsigned int pbs = 0; ++ unsigned int lbs = 0; ++ ++ if (!dev_get_direct_block_sizes(pvl->pv->dev, &pbs, &lbs)) { ++ lbs_unknown++; ++ pbs_unknown++; ++ continue; ++ } ++ ++ if (lbs == 4096) ++ lbs_4k++; ++ else if (lbs == 512) ++ lbs_512++; ++ else ++ lbs_unknown++; ++ ++ if (pbs == 4096) ++ pbs_4k++; ++ else if (pbs == 512) ++ pbs_512++; ++ else ++ pbs_unknown++; ++ } ++ ++ if (lbs_4k && lbs_512) { ++ log_error("Writecache requires consistent logical block size for LV devices."); ++ goto_bad; ++ } ++ ++ if (lbs_4k && block_size_setting && (block_size_setting < 4096)) { ++ log_error("Writecache block size %u not allowed with device logical block size 4096.", ++ block_size_setting); ++ goto_bad; ++ } ++ ++ if (dm_snprintf(pathname, sizeof(pathname), "%s/%s/%s", cmd->dev_dir, ++ lv->vg->name, lv->name) < 0) { ++ log_error("Path name too long to get LV block size %s", display_lvname(lv)); ++ goto_bad; ++ } ++ ++ if (!sync_local_dev_names(cmd)) ++ stack; ++ ++ if (!(fs_dev = dev_cache_get(cmd, pathname, NULL))) { ++ log_error("Device for LV not found to check block size %s", pathname); ++ goto_bad; ++ } ++ ++ /* ++ * get_fs_block_size() returns the libblkid BLOCK_SIZE value, ++ * where libblkid has fs-specific code to set BLOCK_SIZE to the ++ * value we need here. ++ * ++ * The term "block size" here may not equate directly to what the fs ++ * calls the block size, e.g. xfs calls this the sector size (and ++ * something different the block size); while ext4 does call this ++ * value the block size, but it's possible values are not the same ++ * as xfs's, and do not seem to relate directly to the device LBS. ++ * ++ * With 512 LBS and 4K PBS, mkfs.xfs will use xfs sector size 4K. ++ */ ++ rv = get_fs_block_size(fs_dev, &fs_block_size); ++ if (!rv || !fs_block_size) { ++ if (lbs_4k && pbs_4k && !pbs_512) { ++ block_size = 4096; ++ } else if (lbs_512 && pbs_512 && !pbs_4k) { ++ block_size = 512; ++ } else if (lbs_512 && pbs_4k) { ++ if (block_size_setting == 4096) ++ block_size = 4096; ++ else ++ block_size = 512; ++ } else { ++ block_size = 512; ++ } ++ ++ if (block_size_setting && (block_size_setting != block_size)) { ++ log_error("Cannot use writecache block size %u with unknown file system block size, logical block size %u, physical block size %u.", ++ block_size_setting, lbs_4k ? 4096 : 512, pbs_4k ? 4096 : 512); ++ goto bad; ++ } ++ ++ if (block_size != 512) { ++ log_warn("WARNING: unable to detect a file system block size on %s", display_lvname(lv)); ++ log_warn("WARNING: using a writecache block size larger than the file system block size may corrupt the file system."); ++ if (!arg_is_set(cmd, yes_ARG) && ++ yes_no_prompt("Use writecache block size %u? [y/n]: ", block_size) == 'n') { ++ log_error("Conversion aborted."); ++ goto bad; ++ } ++ } ++ ++ log_print("Using writecache block size %u for unknown file system block size, logical block size %u, physical block size %u.", ++ block_size, lbs_4k ? 4096 : 512, pbs_4k ? 4096 : 512); ++ goto out; ++ } ++ ++ if (!block_size_setting) { ++ /* User did not specify a block size, so choose according to fs block size. */ ++ if (fs_block_size == 4096) ++ block_size = 4096; ++ else if (fs_block_size == 512) ++ block_size = 512; ++ else if (fs_block_size > 4096) ++ block_size = 4096; ++ else if (fs_block_size < 4096) ++ block_size = 512; ++ else ++ goto_bad; ++ } else { ++ if (block_size_setting <= fs_block_size) ++ block_size = block_size_setting; ++ else { ++ log_error("Writecache block size %u cannot be larger than file system block size %u.", ++ block_size_setting, fs_block_size); ++ goto_bad; ++ } ++ } ++ ++out: ++ if (block_size == 512) ++ *block_size_sectors = 1; ++ else if (block_size == 4096) ++ *block_size_sectors = 8; ++ else ++ goto_bad; ++ ++ return 1; ++bad: ++ return 0; ++} + +-static int _lvconvert_writecache_attach_single(struct cmd_context *cmd, ++int lvconvert_writecache_attach_single(struct cmd_context *cmd, + struct logical_volume *lv, + struct processing_handle *handle) + { +@@ -5616,68 +5746,91 @@ static int _lvconvert_writecache_attach_single(struct cmd_context *cmd, + struct logical_volume *lv_fast; + struct writecache_settings settings; + const char *fast_name; +- uint32_t block_size_sectors; ++ uint32_t block_size_sectors = 0; + char *lockd_fast_args = NULL; + char *lockd_fast_name = NULL; + struct id lockd_fast_id; + char cvol_name[NAME_LEN]; ++ int is_active; + +- fast_name = arg_str_value(cmd, cachevol_ARG, ""); ++ /* ++ * User specifies an existing cachevol to use or a cachedevice ++ * to create a cachevol from. ++ */ ++ if ((fast_name = arg_str_value(cmd, cachevol_ARG, NULL))) { ++ if (!validate_lvname_param(cmd, &vg->name, &fast_name)) ++ goto_bad; + +- if (!(lv_fast = find_lv(vg, fast_name))) { +- log_error("LV %s not found.", fast_name); +- goto bad; +- } ++ if (!(lv_fast = find_lv(vg, fast_name))) { ++ log_error("LV %s not found.", fast_name); ++ goto bad; ++ } + +- if (lv_fast == lv) { +- log_error("Invalid cachevol LV."); +- goto bad; +- } ++ if (lv_fast == lv) { ++ log_error("Invalid cachevol LV."); ++ goto bad; ++ } + +- if (!seg_is_linear(first_seg(lv_fast))) { +- log_error("LV %s must be linear to use as a writecache.", display_lvname(lv_fast)); +- goto bad; +- } ++ if (lv_is_cache_vol(lv_fast)) { ++ log_error("LV %s is already used as a cachevol.", display_lvname(lv_fast)); ++ goto bad; ++ } + +- if (lv_is_cache_vol(lv_fast)) { +- log_error("LV %s is already used as a cachevol.", display_lvname(lv_fast)); +- goto bad; +- } ++ if (!seg_is_linear(first_seg(lv_fast))) { ++ log_error("LV %s must be linear to use as a writecache.", display_lvname(lv_fast)); ++ goto bad; ++ } + +- /* +- * To permit this we need to check the block size of the fs using lv +- * (recently in libblkid) so that we can use a matching writecache +- * block size. We also want to do that if the lv is inactive. +- */ +- if (lv_is_active(lv)) { +- log_error("LV %s must be inactive to attach writecache.", display_lvname(lv)); +- goto bad; +- } ++ /* fast LV shouldn't generally be active by itself, but just in case. */ ++ if (lv_is_active(lv_fast)) { ++ log_error("LV %s must be inactive to attach.", display_lvname(lv_fast)); ++ goto bad; ++ } + +- /* fast LV shouldn't generally be active by itself, but just in case. */ +- if (lv_info(cmd, lv_fast, 1, NULL, 0, 0)) { +- log_error("LV %s must be inactive to attach.", display_lvname(lv_fast)); +- goto bad; ++ if (!arg_is_set(cmd, yes_ARG) && ++ yes_no_prompt("Erase all existing data on %s? [y/n]: ", display_lvname(lv_fast)) == 'n') { ++ log_error("Conversion aborted."); ++ goto bad; ++ } ++ } else { ++ if (!_lv_create_cachevol(cmd, vg, lv, &lv_fast)) ++ goto_bad; + } + ++ is_active = lv_is_active(lv); ++ + memset(&settings, 0, sizeof(settings)); +- block_size_sectors = DEFAULT_WRITECACHE_BLOCK_SIZE_SECTORS; + +- if (!_get_writecache_settings(cmd, &settings, &block_size_sectors)) { ++ if (!get_writecache_settings(cmd, &settings, &block_size_sectors)) { + log_error("Invalid writecache settings."); + goto bad; + } + +- if (!arg_is_set(cmd, yes_ARG) && +- yes_no_prompt("Erase all existing data on %s? [y/n]: ", display_lvname(lv_fast)) == 'n') { +- log_error("Conversion aborted."); +- goto bad; ++ if (!is_active) { ++ /* checking block size of fs on the lv requires the lv to be active */ ++ if (!activate_lv(cmd, lv)) { ++ log_error("Failed to activate LV to check block size %s", display_lvname(lv)); ++ goto bad; ++ } + } + +- /* Ensure the two LVs are not active elsewhere. */ ++ if (!_set_writecache_block_size(cmd, lv, &block_size_sectors)) { ++ if (!is_active && !deactivate_lv(cmd, lv)) ++ stack; ++ goto_bad; ++ } ++ ++ if (!is_active) { ++ if (!deactivate_lv(cmd, lv)) { ++ log_error("Failed to deactivate LV after checking block size %s", display_lvname(lv)); ++ goto bad; ++ } ++ } ++ ++ /* Ensure the LV is not active elsewhere. */ + if (!lockd_lv(cmd, lv, "ex", 0)) + goto_bad; +- if (!lockd_lv(cmd, lv_fast, "ex", 0)) ++ if (fast_name && !lockd_lv(cmd, lv_fast, "ex", 0)) + goto_bad; + + if (!archive(vg)) +@@ -5744,7 +5897,7 @@ static int _lvconvert_writecache_attach_single(struct cmd_context *cmd, + log_error("Failed to unlock fast LV %s/%s", vg->name, lockd_fast_name); + } + +- log_print_unless_silent("Logical volume %s now has write cache.", ++ log_print_unless_silent("Logical volume %s now has writecache.", + display_lvname(lv)); + return ECMD_PROCESSED; + bad: +@@ -5768,7 +5921,7 @@ int lvconvert_to_writecache_cmd(struct cmd_context *cmd, int argc, char **argv) + cmd->cname->flags &= ~GET_VGNAME_FROM_OPTIONS; + + ret = process_each_lv(cmd, cmd->position_argc, cmd->position_argv, NULL, NULL, READ_FOR_UPDATE, handle, NULL, +- &_lvconvert_writecache_attach_single); ++ &lvconvert_writecache_attach_single); + + destroy_processing_handle(cmd, handle); + +@@ -5791,7 +5944,7 @@ int lvconvert_to_cache_with_cachevol_cmd(struct cmd_context *cmd, int argc, char + cmd->cname->flags &= ~GET_VGNAME_FROM_OPTIONS; + + ret = process_each_lv(cmd, cmd->position_argc, cmd->position_argv, NULL, NULL, READ_FOR_UPDATE, handle, NULL, +- &_lvconvert_cachevol_attach_single); ++ &lvconvert_cachevol_attach_single); + + destroy_processing_handle(cmd, handle); + +diff --git a/tools/lvcreate.c b/tools/lvcreate.c +index 5c978b3..3357a08 100644 +--- a/tools/lvcreate.c ++++ b/tools/lvcreate.c +@@ -766,7 +766,9 @@ static int _lvcreate_params(struct cmd_context *cmd, + * + * Ordering of following type tests is IMPORTANT + */ +- if ((segtype_str = arg_str_value(cmd, type_ARG, NULL))) { ++ if (lp->ignore_type) { ++ segtype_str = SEG_TYPE_NAME_STRIPED; ++ } else if ((segtype_str = arg_str_value(cmd, type_ARG, NULL))) { + lp->type = 1; + if (!strcmp(segtype_str, "linear")) { + segtype_str = "striped"; +@@ -1799,3 +1801,152 @@ int lvcreate(struct cmd_context *cmd, int argc, char **argv) + destroy_processing_handle(cmd, handle); + return ret; + } ++ ++static int _lvcreate_and_attach_writecache_single(struct cmd_context *cmd, ++ const char *vg_name, struct volume_group *vg, struct processing_handle *handle) ++{ ++ struct processing_params *pp = (struct processing_params *) handle->custom_handle; ++ struct lvcreate_params *lp = pp->lp; ++ struct logical_volume *lv; ++ int ret; ++ ++ ret = _lvcreate_single(cmd, vg_name, vg, handle); ++ ++ if (ret == ECMD_FAILED) ++ return ret; ++ ++ if (!(lv = find_lv(vg, lp->lv_name))) { ++ log_error("Failed to find LV %s to add writecache.", lp->lv_name); ++ return ECMD_FAILED; ++ } ++ ++ ret = lvconvert_writecache_attach_single(cmd, lv, handle); ++ ++ if (ret == ECMD_FAILED) { ++ log_error("Removing new LV after failing to add writecache."); ++ if (!deactivate_lv(cmd, lv)) ++ log_error("Failed to deactivate new LV %s.", display_lvname(lv)); ++ if (!lv_remove_with_dependencies(cmd, lv, 1, 0)) ++ log_error("Failed to remove new LV %s.", display_lvname(lv)); ++ return ECMD_FAILED; ++ } ++ ++ return ECMD_PROCESSED; ++} ++ ++int lvcreate_and_attach_writecache_cmd(struct cmd_context *cmd, int argc, char **argv) ++{ ++ struct processing_handle *handle = NULL; ++ struct processing_params pp; ++ struct lvcreate_params lp = { ++ .major = -1, ++ .minor = -1, ++ }; ++ struct lvcreate_cmdline_params lcp = { 0 }; ++ int ret; ++ ++ /* ++ * Tell lvcreate to ignore --type since we are using lvcreate ++ * to create a linear LV and using lvconvert to add cache. ++ * (Would be better if lvcreate code was split up so we could ++ * call a specific function that just created a linear/striped LV.) ++ */ ++ lp.ignore_type = 1; ++ ++ if (!_lvcreate_params(cmd, argc, argv, &lp, &lcp)) { ++ stack; ++ return EINVALID_CMD_LINE; ++ } ++ ++ pp.lp = &lp; ++ pp.lcp = &lcp; ++ ++ if (!(handle = init_processing_handle(cmd, NULL))) { ++ log_error("Failed to initialize processing handle."); ++ return ECMD_FAILED; ++ } ++ ++ handle->custom_handle = &pp; ++ ++ ret = process_each_vg(cmd, 0, NULL, lp.vg_name, NULL, READ_FOR_UPDATE, 0, handle, ++ &_lvcreate_and_attach_writecache_single); ++ ++ _destroy_lvcreate_params(&lp); ++ destroy_processing_handle(cmd, handle); ++ return ret; ++} ++ ++static int _lvcreate_and_attach_cache_single(struct cmd_context *cmd, ++ const char *vg_name, struct volume_group *vg, struct processing_handle *handle) ++{ ++ struct processing_params *pp = (struct processing_params *) handle->custom_handle; ++ struct lvcreate_params *lp = pp->lp; ++ struct logical_volume *lv; ++ int ret; ++ ++ ret = _lvcreate_single(cmd, vg_name, vg, handle); ++ ++ if (ret == ECMD_FAILED) ++ return ret; ++ ++ if (!(lv = find_lv(vg, lp->lv_name))) { ++ log_error("Failed to find LV %s to add cache.", lp->lv_name); ++ return ECMD_FAILED; ++ } ++ ++ ret = lvconvert_cachevol_attach_single(cmd, lv, handle); ++ ++ if (ret == ECMD_FAILED) { ++ log_error("Removing new LV after failing to add cache."); ++ if (!deactivate_lv(cmd, lv)) ++ log_error("Failed to deactivate new LV %s.", display_lvname(lv)); ++ if (!lv_remove_with_dependencies(cmd, lv, 1, 0)) ++ log_error("Failed to remove new LV %s.", display_lvname(lv)); ++ return ECMD_FAILED; ++ } ++ ++ return ECMD_PROCESSED; ++} ++ ++int lvcreate_and_attach_cache_cmd(struct cmd_context *cmd, int argc, char **argv) ++{ ++ struct processing_handle *handle = NULL; ++ struct processing_params pp; ++ struct lvcreate_params lp = { ++ .major = -1, ++ .minor = -1, ++ }; ++ struct lvcreate_cmdline_params lcp = { 0 }; ++ int ret; ++ ++ /* ++ * Tell lvcreate to ignore --type since we are using lvcreate ++ * to create a linear LV and using lvconvert to add cache. ++ * (Would be better if lvcreate code was split up so we could ++ * call a specific function that just created a linear/striped LV.) ++ */ ++ lp.ignore_type = 1; ++ ++ if (!_lvcreate_params(cmd, argc, argv, &lp, &lcp)) { ++ stack; ++ return EINVALID_CMD_LINE; ++ } ++ ++ pp.lp = &lp; ++ pp.lcp = &lcp; ++ ++ if (!(handle = init_processing_handle(cmd, NULL))) { ++ log_error("Failed to initialize processing handle."); ++ return ECMD_FAILED; ++ } ++ ++ handle->custom_handle = &pp; ++ ++ ret = process_each_vg(cmd, 0, NULL, lp.vg_name, NULL, READ_FOR_UPDATE, 0, handle, ++ &_lvcreate_and_attach_cache_single); ++ ++ _destroy_lvcreate_params(&lp); ++ destroy_processing_handle(cmd, handle); ++ return ret; ++} ++ +diff --git a/tools/lvmcmdline.c b/tools/lvmcmdline.c +index d87a8f0..7cf4e3f 100644 +--- a/tools/lvmcmdline.c ++++ b/tools/lvmcmdline.c +@@ -124,8 +124,10 @@ static const struct command_function _command_functions[CMD_COUNT] = { + { lvconvert_to_cachepool_CMD, lvconvert_to_pool_cmd }, + { lvconvert_to_thin_with_external_CMD, lvconvert_to_thin_with_external_cmd }, + { lvconvert_to_cache_with_cachevol_CMD, lvconvert_to_cache_with_cachevol_cmd }, ++ { lvconvert_to_cache_with_device_CMD, lvconvert_to_cache_with_cachevol_cmd }, + { lvconvert_to_cache_with_cachepool_CMD, lvconvert_to_cache_with_cachepool_cmd }, + { lvconvert_to_writecache_CMD, lvconvert_to_writecache_cmd }, ++ { lvconvert_to_writecache_with_device_CMD, lvconvert_to_writecache_cmd }, + { lvconvert_swap_pool_metadata_CMD, lvconvert_swap_pool_metadata_cmd }, + { lvconvert_to_thinpool_or_swap_metadata_CMD, lvconvert_to_pool_or_swap_metadata_cmd }, + { lvconvert_to_cachepool_or_swap_metadata_CMD, lvconvert_to_pool_or_swap_metadata_cmd }, +@@ -152,6 +154,12 @@ static const struct command_function _command_functions[CMD_COUNT] = { + /* lvconvert for integrity */ + { lvconvert_integrity_CMD, lvconvert_integrity_cmd }, + ++ /* lvcreate */ ++ { lvcreate_and_attach_cachevol_for_cache_CMD, lvcreate_and_attach_cache_cmd }, ++ { lvcreate_and_attach_cachedevice_for_cache_CMD, lvcreate_and_attach_cache_cmd }, ++ { lvcreate_and_attach_cachevol_for_writecache_CMD, lvcreate_and_attach_writecache_cmd }, ++ { lvcreate_and_attach_cachedevice_for_writecache_CMD, lvcreate_and_attach_writecache_cmd }, ++ + { pvscan_display_CMD, pvscan_display_cmd }, + { pvscan_cache_CMD, pvscan_cache_cmd }, + }; +diff --git a/tools/toollib.c b/tools/toollib.c +index 89b6374..eb0de55 100644 +--- a/tools/toollib.c ++++ b/tools/toollib.c +@@ -1184,6 +1184,170 @@ out: + return ok; + } + ++static int _get_one_writecache_setting(struct cmd_context *cmd, struct writecache_settings *settings, ++ char *key, char *val, uint32_t *block_size_sectors) ++{ ++ /* special case: block_size is not a setting but is set with the --cachesettings option */ ++ if (!strncmp(key, "block_size", strlen("block_size"))) { ++ uint32_t block_size = 0; ++ if (sscanf(val, "%u", &block_size) != 1) ++ goto_bad; ++ if (block_size == 512) ++ *block_size_sectors = 1; ++ else if (block_size == 4096) ++ *block_size_sectors = 8; ++ else ++ goto_bad; ++ return 1; ++ } ++ ++ if (!strncmp(key, "high_watermark", strlen("high_watermark"))) { ++ if (sscanf(val, "%llu", (unsigned long long *)&settings->high_watermark) != 1) ++ goto_bad; ++ if (settings->high_watermark > 100) ++ goto_bad; ++ settings->high_watermark_set = 1; ++ return 1; ++ } ++ ++ if (!strncmp(key, "low_watermark", strlen("low_watermark"))) { ++ if (sscanf(val, "%llu", (unsigned long long *)&settings->low_watermark) != 1) ++ goto_bad; ++ if (settings->low_watermark > 100) ++ goto_bad; ++ settings->low_watermark_set = 1; ++ return 1; ++ } ++ ++ if (!strncmp(key, "writeback_jobs", strlen("writeback_jobs"))) { ++ if (sscanf(val, "%llu", (unsigned long long *)&settings->writeback_jobs) != 1) ++ goto_bad; ++ settings->writeback_jobs_set = 1; ++ return 1; ++ } ++ ++ if (!strncmp(key, "autocommit_blocks", strlen("autocommit_blocks"))) { ++ if (sscanf(val, "%llu", (unsigned long long *)&settings->autocommit_blocks) != 1) ++ goto_bad; ++ settings->autocommit_blocks_set = 1; ++ return 1; ++ } ++ ++ if (!strncmp(key, "autocommit_time", strlen("autocommit_time"))) { ++ if (sscanf(val, "%llu", (unsigned long long *)&settings->autocommit_time) != 1) ++ goto_bad; ++ settings->autocommit_time_set = 1; ++ return 1; ++ } ++ ++ if (!strncmp(key, "fua", strlen("fua"))) { ++ if (settings->nofua_set) { ++ log_error("Setting fua and nofua cannot both be set."); ++ return 0; ++ } ++ if (sscanf(val, "%u", &settings->fua) != 1) ++ goto_bad; ++ settings->fua_set = 1; ++ return 1; ++ } ++ ++ if (!strncmp(key, "nofua", strlen("nofua"))) { ++ if (settings->fua_set) { ++ log_error("Setting fua and nofua cannot both be set."); ++ return 0; ++ } ++ if (sscanf(val, "%u", &settings->nofua) != 1) ++ goto_bad; ++ settings->nofua_set = 1; ++ return 1; ++ } ++ ++ if (!strncmp(key, "cleaner", strlen("cleaner"))) { ++ if (sscanf(val, "%u", &settings->cleaner) != 1) ++ goto_bad; ++ settings->cleaner_set = 1; ++ return 1; ++ } ++ ++ if (!strncmp(key, "max_age", strlen("max_age"))) { ++ if (sscanf(val, "%u", &settings->max_age) != 1) ++ goto_bad; ++ settings->max_age_set = 1; ++ return 1; ++ } ++ ++ if (settings->new_key) { ++ log_error("Setting %s is not recognized. Only one unrecognized setting is allowed.", key); ++ return 0; ++ } ++ ++ log_warn("Unrecognized writecache setting \"%s\" may cause activation failure.", key); ++ if (yes_no_prompt("Use unrecognized writecache setting? [y/n]: ") == 'n') { ++ log_error("Aborting writecache conversion."); ++ return 0; ++ } ++ ++ log_warn("Using unrecognized writecache setting: %s = %s.", key, val); ++ ++ settings->new_key = dm_pool_strdup(cmd->mem, key); ++ settings->new_val = dm_pool_strdup(cmd->mem, val); ++ return 1; ++ ++ bad: ++ log_error("Invalid setting: %s", key); ++ return 0; ++} ++ ++int get_writecache_settings(struct cmd_context *cmd, struct writecache_settings *settings, ++ uint32_t *block_size_sectors) ++{ ++ struct arg_value_group_list *group; ++ const char *str; ++ char key[64]; ++ char val[64]; ++ int num; ++ int pos; ++ ++ /* ++ * "grouped" means that multiple --cachesettings options can be used. ++ * Each option is also allowed to contain multiple key = val pairs. ++ */ ++ ++ dm_list_iterate_items(group, &cmd->arg_value_groups) { ++ if (!grouped_arg_is_set(group->arg_values, cachesettings_ARG)) ++ continue; ++ ++ if (!(str = grouped_arg_str_value(group->arg_values, cachesettings_ARG, NULL))) ++ break; ++ ++ pos = 0; ++ ++ while (pos < strlen(str)) { ++ /* scan for "key1=val1 key2 = val2 key3= val3" */ ++ ++ memset(key, 0, sizeof(key)); ++ memset(val, 0, sizeof(val)); ++ ++ if (sscanf(str + pos, " %63[^=]=%63s %n", key, val, &num) != 2) { ++ log_error("Invalid setting at: %s", str+pos); ++ return 0; ++ } ++ ++ pos += num; ++ ++ if (!_get_one_writecache_setting(cmd, settings, key, val, block_size_sectors)) ++ return_0; ++ } ++ } ++ ++ if (settings->high_watermark_set && settings->low_watermark_set && ++ (settings->high_watermark <= settings->low_watermark)) { ++ log_error("High watermark must be greater than low watermark."); ++ return 0; ++ } ++ ++ return 1; ++} + + /* FIXME move to lib */ + static int _pv_change_tag(struct physical_volume *pv, const char *tag, int addtag) +diff --git a/tools/toollib.h b/tools/toollib.h +index 53a5e5b..f3a60fb 100644 +--- a/tools/toollib.h ++++ b/tools/toollib.h +@@ -217,6 +217,9 @@ int get_cache_params(struct cmd_context *cmd, + const char **name, + struct dm_config_tree **settings); + ++int get_writecache_settings(struct cmd_context *cmd, struct writecache_settings *settings, ++ uint32_t *block_size_sectors); ++ + int change_tag(struct cmd_context *cmd, struct volume_group *vg, + struct logical_volume *lv, struct physical_volume *pv, int arg); + +diff --git a/tools/tools.h b/tools/tools.h +index 7f2434d..c3d780d 100644 +--- a/tools/tools.h ++++ b/tools/tools.h +@@ -278,7 +278,18 @@ int lvconvert_to_vdopool_param_cmd(struct cmd_context *cmd, int argc, char **arg + + int lvconvert_integrity_cmd(struct cmd_context *cmd, int argc, char **argv); + ++int lvcreate_and_attach_writecache_cmd(struct cmd_context *cmd, int argc, char **argv); ++int lvcreate_and_attach_cache_cmd(struct cmd_context *cmd, int argc, char **argv); ++ + int pvscan_display_cmd(struct cmd_context *cmd, int argc, char **argv); + int pvscan_cache_cmd(struct cmd_context *cmd, int argc, char **argv); + ++ ++int lvconvert_writecache_attach_single(struct cmd_context *cmd, ++ struct logical_volume *lv, ++ struct processing_handle *handle); ++int lvconvert_cachevol_attach_single(struct cmd_context *cmd, ++ struct logical_volume *lv, ++ struct processing_handle *handle); ++ + #endif +-- +1.8.3.1 + diff --git a/SOURCES/lvm2-2_03_10-Allow-dm-integrity-to-be-used-for-raid-images.patch b/SOURCES/lvm2-2_03_10-Allow-dm-integrity-to-be-used-for-raid-images.patch new file mode 100644 index 0000000..d8dcd03 --- /dev/null +++ b/SOURCES/lvm2-2_03_10-Allow-dm-integrity-to-be-used-for-raid-images.patch @@ -0,0 +1,5052 @@ +From d51fc23f967acdf8534cdda63aa9aea0f44f9d0c Mon Sep 17 00:00:00 2001 +From: David Teigland +Date: Wed, 20 Nov 2019 16:07:27 -0600 +Subject: [PATCH 2/3] Allow dm-integrity to be used for raid images + +dm-integrity stores checksums of the data written to an +LV, and returns an error if data read from the LV does +not match the previously saved checksum. When used on +raid images, dm-raid will correct the error by reading +the block from another image, and the device user sees +no error. The integrity metadata (checksums) are stored +on an internal LV allocated by lvm for each linear image. +The internal LV is allocated on the same PV as the image. + +Create a raid LV with an integrity layer over each +raid image (for raid levels 1,4,5,6,10): + +lvcreate --type raidN --raidintegrity y [options] + +Add an integrity layer to images of an existing raid LV: + +lvconvert --raidintegrity y LV + +Remove the integrity layer from images of a raid LV: + +lvconvert --raidintegrity n LV + +Settings + +Use --raidintegritymode journal|bitmap (journal is default) +to configure the method used by dm-integrity to ensure +crash consistency. + +Initialization + +When integrity is added to an LV, the kernel needs to +initialize the integrity metadata/checksums for all blocks +in the LV. The data corruption checking performed by +dm-integrity will only operate on areas of the LV that +are already initialized. The progress of integrity +initialization is reported by the "syncpercent" LV +reporting field (and under the Cpy%Sync lvs column.) + +Example: create a raid1 LV with integrity: + +$ lvcreate --type raid1 -m1 --raidintegrity y -n rr -L1G foo + Creating integrity metadata LV rr_rimage_0_imeta with size 12.00 MiB. + Logical volume "rr_rimage_0_imeta" created. + Creating integrity metadata LV rr_rimage_1_imeta with size 12.00 MiB. + Logical volume "rr_rimage_1_imeta" created. + Logical volume "rr" created. +$ lvs -a foo + LV VG Attr LSize Origin Cpy%Sync + rr foo rwi-a-r--- 1.00g 4.93 + [rr_rimage_0] foo gwi-aor--- 1.00g [rr_rimage_0_iorig] 41.02 + [rr_rimage_0_imeta] foo ewi-ao---- 12.00m + [rr_rimage_0_iorig] foo -wi-ao---- 1.00g + [rr_rimage_1] foo gwi-aor--- 1.00g [rr_rimage_1_iorig] 39.45 + [rr_rimage_1_imeta] foo ewi-ao---- 12.00m + [rr_rimage_1_iorig] foo -wi-ao---- 1.00g + [rr_rmeta_0] foo ewi-aor--- 4.00m + [rr_rmeta_1] foo ewi-aor--- 4.00m + +(cherry picked from commit d9e8895a96539d75166c0f74e58f5ed4e729e551) +--- + configure | 27 ++ + configure.ac | 18 + + device_mapper/all.h | 39 ++ + device_mapper/ioctl/libdm-iface.c | 31 +- + device_mapper/ioctl/libdm-targets.h | 1 + + device_mapper/libdm-deptree.c | 154 ++++++- + device_mapper/libdm-targets.c | 27 ++ + include/configure.h.in | 3 + + lib/Makefile.in | 2 + + lib/activate/activate.c | 7 + + lib/activate/activate.h | 4 + + lib/activate/dev_manager.c | 18 +- + lib/commands/toolcontext.c | 5 + + lib/device/dev-type.c | 39 ++ + lib/device/dev-type.h | 2 + + lib/format_text/flags.c | 2 + + lib/integrity/integrity.c | 343 +++++++++++++++ + lib/metadata/integrity_manip.c | 821 ++++++++++++++++++++++++++++++++++++ + lib/metadata/lv.c | 18 +- + lib/metadata/lv_manip.c | 150 ++++++- + lib/metadata/merge.c | 2 + + lib/metadata/metadata-exported.h | 26 ++ + lib/metadata/raid_manip.c | 85 +++- + lib/metadata/segtype.h | 6 + + lib/metadata/snapshot_manip.c | 2 + + lib/misc/lvm-string.c | 4 +- + lib/report/report.c | 2 +- + man/lvmraid.7_main | 83 ++++ + test/lib/aux.sh | 8 + + test/shell/integrity-blocksize.sh | 183 ++++++++ + test/shell/integrity-dmeventd.sh | 289 +++++++++++++ + test/shell/integrity-large.sh | 175 ++++++++ + test/shell/integrity-misc.sh | 228 ++++++++++ + test/shell/integrity.sh | 735 ++++++++++++++++++++++++++++++++ + tools/args.h | 20 + + tools/command-lines.in | 45 +- + tools/lv_props.h | 1 + + tools/lv_types.h | 1 + + tools/lvchange.c | 5 + + tools/lvconvert.c | 137 ++++++ + tools/lvcreate.c | 15 +- + tools/lvmcmdline.c | 3 + + tools/pvmove.c | 10 + + tools/toollib.c | 47 +++ + tools/tools.h | 4 + + 45 files changed, 3790 insertions(+), 37 deletions(-) + create mode 100644 lib/integrity/integrity.c + create mode 100644 lib/metadata/integrity_manip.c + create mode 100644 test/shell/integrity-blocksize.sh + create mode 100644 test/shell/integrity-dmeventd.sh + create mode 100644 test/shell/integrity-large.sh + create mode 100644 test/shell/integrity-misc.sh + create mode 100644 test/shell/integrity.sh + +diff --git a/configure b/configure +index 6dd7eda..716ee9c 100755 +--- a/configure ++++ b/configure +@@ -918,6 +918,7 @@ enable_cache_check_needs_check + with_vdo + with_vdo_format + with_writecache ++with_integrity + enable_readline + enable_realtime + enable_ocf +@@ -1716,6 +1717,7 @@ Optional Packages: + --with-vdo=TYPE vdo support: internal/none [internal] + --with-vdo-format=PATH vdoformat tool: [autodetect] + --with-writecache=TYPE writecache support: internal/none [none] ++ --with-integrity=TYPE integrity support: internal/none [none] + --with-ocfdir=DIR install OCF files in + [PREFIX/lib/ocf/resource.d/lvm2] + --with-default-pid-dir=PID_DIR +@@ -9762,6 +9764,31 @@ $as_echo "#define WRITECACHE_INTERNAL 1" >>confdefs.h + esac + + ################################################################################ ++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to include integrity" >&5 ++$as_echo_n "checking whether to include integrity... " >&6; } ++ ++# Check whether --with-integrity was given. ++if test "${with_integrity+set}" = set; then : ++ withval=$with_integrity; INTEGRITY=$withval ++else ++ INTEGRITY="none" ++fi ++ ++ ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INTEGRITY" >&5 ++$as_echo "$INTEGRITY" >&6; } ++ ++case "$INTEGRITY" in ++ none) ;; ++ internal) ++ ++$as_echo "#define INTEGRITY_INTERNAL 1" >>confdefs.h ++ ++ ;; ++ *) as_fn_error $? "--with-integrity parameter invalid" "$LINENO" 5 ;; ++esac ++ ++################################################################################ + # Check whether --enable-readline was given. + if test "${enable_readline+set}" = set; then : + enableval=$enable_readline; READLINE=$enableval +diff --git a/configure.ac b/configure.ac +index 74ca201..9a0e41a 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -668,6 +668,24 @@ case "$WRITECACHE" in + esac + + ################################################################################ ++dnl -- integrity inclusion type ++AC_MSG_CHECKING(whether to include integrity) ++AC_ARG_WITH(integrity, ++ AC_HELP_STRING([--with-integrity=TYPE], ++ [integrity support: internal/none [none]]), ++ INTEGRITY=$withval, INTEGRITY="none") ++ ++AC_MSG_RESULT($INTEGRITY) ++ ++case "$INTEGRITY" in ++ none) ;; ++ internal) ++ AC_DEFINE([INTEGRITY_INTERNAL], 1, [Define to 1 to include built-in support for integrity.]) ++ ;; ++ *) AC_MSG_ERROR([--with-integrity parameter invalid]) ;; ++esac ++ ++################################################################################ + dnl -- Disable readline + AC_ARG_ENABLE([readline], + AC_HELP_STRING([--disable-readline], [disable readline support]), +diff --git a/device_mapper/all.h b/device_mapper/all.h +index b23485f..f00b6a5 100644 +--- a/device_mapper/all.h ++++ b/device_mapper/all.h +@@ -234,6 +234,7 @@ int dm_task_suppress_identical_reload(struct dm_task *dmt); + int dm_task_secure_data(struct dm_task *dmt); + int dm_task_retry_remove(struct dm_task *dmt); + int dm_task_deferred_remove(struct dm_task *dmt); ++void dm_task_skip_reload_params_compare(struct dm_task *dmt); + + /* + * Record timestamp immediately after the ioctl returns. +@@ -392,6 +393,15 @@ struct dm_status_writecache { + int dm_get_status_writecache(struct dm_pool *mem, const char *params, + struct dm_status_writecache **status); + ++struct dm_status_integrity { ++ uint64_t number_of_mismatches; ++ uint64_t provided_data_sectors; ++ uint64_t recalc_sector; ++}; ++ ++int dm_get_status_integrity(struct dm_pool *mem, const char *params, ++ struct dm_status_integrity **status); ++ + /* + * Parse params from STATUS call for snapshot target + * +@@ -970,6 +980,35 @@ int dm_tree_node_add_writecache_target(struct dm_tree_node *node, + uint32_t writecache_block_size, + struct writecache_settings *settings); + ++struct integrity_settings { ++ char mode[8]; ++ uint32_t tag_size; ++ uint32_t block_size; /* optional table param always set by lvm */ ++ const char *internal_hash; /* optional table param always set by lvm */ ++ ++ uint32_t journal_sectors; ++ uint32_t interleave_sectors; ++ uint32_t buffer_sectors; ++ uint32_t journal_watermark; ++ uint32_t commit_time; ++ uint32_t bitmap_flush_interval; ++ uint64_t sectors_per_bit; ++ ++ unsigned journal_sectors_set:1; ++ unsigned interleave_sectors_set:1; ++ unsigned buffer_sectors_set:1; ++ unsigned journal_watermark_set:1; ++ unsigned commit_time_set:1; ++ unsigned bitmap_flush_interval_set:1; ++ unsigned sectors_per_bit_set:1; ++}; ++ ++int dm_tree_node_add_integrity_target(struct dm_tree_node *node, ++ uint64_t size, ++ const char *origin_uuid, ++ const char *meta_uuid, ++ struct integrity_settings *settings, ++ int recalculate); + + /* + * VDO target +diff --git a/device_mapper/ioctl/libdm-iface.c b/device_mapper/ioctl/libdm-iface.c +index fe04af8..25e7d1a 100644 +--- a/device_mapper/ioctl/libdm-iface.c ++++ b/device_mapper/ioctl/libdm-iface.c +@@ -805,6 +805,11 @@ int dm_task_suppress_identical_reload(struct dm_task *dmt) + return 1; + } + ++void dm_task_skip_reload_params_compare(struct dm_task *dmt) ++{ ++ dmt->skip_reload_params_compare = 1; ++} ++ + int dm_task_set_add_node(struct dm_task *dmt, dm_add_node_t add_node) + { + switch (add_node) { +@@ -1575,11 +1580,29 @@ static int _reload_with_suppression_v4(struct dm_task *dmt) + len = strlen(t2->params); + while (len-- > 0 && t2->params[len] == ' ') + t2->params[len] = '\0'; +- if ((t1->start != t2->start) || +- (t1->length != t2->length) || +- (strcmp(t1->type, t2->type)) || +- (strcmp(t1->params, t2->params))) ++ ++ if (t1->start != t2->start) { ++ log_debug("reload %u:%u start diff", task->major, task->minor); ++ goto no_match; ++ } ++ if (t1->length != t2->length) { ++ log_debug("reload %u:%u length diff", task->major, task->minor); + goto no_match; ++ } ++ if (strcmp(t1->type, t2->type)) { ++ log_debug("reload %u:%u type diff %s %s", task->major, task->minor, t1->type, t2->type); ++ goto no_match; ++ } ++ if (strcmp(t1->params, t2->params)) { ++ if (dmt->skip_reload_params_compare) ++ log_debug("reload %u:%u skip params ignore %s %s", ++ task->major, task->minor, t1->params, t2->params); ++ else { ++ log_debug("reload %u:%u params diff", task->major, task->minor); ++ goto no_match; ++ } ++ } ++ + t1 = t1->next; + t2 = t2->next; + } +diff --git a/device_mapper/ioctl/libdm-targets.h b/device_mapper/ioctl/libdm-targets.h +index b5b20d5..9786a7e 100644 +--- a/device_mapper/ioctl/libdm-targets.h ++++ b/device_mapper/ioctl/libdm-targets.h +@@ -59,6 +59,7 @@ struct dm_task { + int skip_lockfs; + int query_inactive_table; + int suppress_identical_reload; ++ int skip_reload_params_compare; + dm_add_node_t add_node; + uint64_t existing_table_size; + int cookie_set; +diff --git a/device_mapper/libdm-deptree.c b/device_mapper/libdm-deptree.c +index 7fac6ab..9ba24cb 100644 +--- a/device_mapper/libdm-deptree.c ++++ b/device_mapper/libdm-deptree.c +@@ -38,6 +38,7 @@ enum { + SEG_STRIPED, + SEG_ZERO, + SEG_WRITECACHE, ++ SEG_INTEGRITY, + SEG_THIN_POOL, + SEG_THIN, + SEG_VDO, +@@ -78,6 +79,7 @@ static const struct { + { SEG_STRIPED, "striped" }, + { SEG_ZERO, "zero"}, + { SEG_WRITECACHE, "writecache"}, ++ { SEG_INTEGRITY, "integrity"}, + { SEG_THIN_POOL, "thin-pool"}, + { SEG_THIN, "thin"}, + { SEG_VDO, "vdo" }, +@@ -221,6 +223,11 @@ struct load_segment { + int writecache_pmem; /* writecache, 1 if pmem, 0 if ssd */ + uint32_t writecache_block_size; /* writecache, in bytes */ + struct writecache_settings writecache_settings; /* writecache */ ++ ++ uint64_t integrity_data_sectors; /* integrity (provided_data_sectors) */ ++ struct dm_tree_node *integrity_meta_node; /* integrity */ ++ struct integrity_settings integrity_settings; /* integrity */ ++ int integrity_recalculate; /* integrity */ + }; + + /* Per-device properties */ +@@ -268,6 +275,16 @@ struct load_properties { + unsigned delay_resume_if_extended; + + /* ++ * When comparing table lines to decide if a reload is ++ * needed, ignore any differences betwen the lvm device ++ * params and the kernel-reported device params. ++ * dm-integrity reports many internal parameters on the ++ * table line when lvm does not explicitly set them, ++ * causing lvm and the kernel to have differing params. ++ */ ++ unsigned skip_reload_params_compare; ++ ++ /* + * Call node_send_messages(), set to 2 if there are messages + * When != 0, it validates matching transaction id, thus thin-pools + * where transation_id is passed as 0 are never validated, this +@@ -2705,6 +2722,84 @@ static int _writecache_emit_segment_line(struct dm_task *dmt, + return 1; + } + ++static int _integrity_emit_segment_line(struct dm_task *dmt, ++ struct load_segment *seg, ++ char *params, size_t paramsize) ++{ ++ struct integrity_settings *set = &seg->integrity_settings; ++ int pos = 0; ++ int count; ++ char origin_dev[DM_FORMAT_DEV_BUFSIZE]; ++ char meta_dev[DM_FORMAT_DEV_BUFSIZE]; ++ ++ if (!_build_dev_string(origin_dev, sizeof(origin_dev), seg->origin)) ++ return_0; ++ ++ if (seg->integrity_meta_node && ++ !_build_dev_string(meta_dev, sizeof(meta_dev), seg->integrity_meta_node)) ++ return_0; ++ ++ count = 3; /* block_size, internal_hash, fix_padding options are always passed */ ++ ++ if (seg->integrity_meta_node) ++ count++; ++ ++ if (seg->integrity_recalculate) ++ count++; ++ ++ if (set->journal_sectors_set) ++ count++; ++ if (set->interleave_sectors_set) ++ count++; ++ if (set->buffer_sectors_set) ++ count++; ++ if (set->journal_watermark_set) ++ count++; ++ if (set->commit_time_set) ++ count++; ++ if (set->bitmap_flush_interval_set) ++ count++; ++ if (set->sectors_per_bit_set) ++ count++; ++ ++ EMIT_PARAMS(pos, "%s 0 %u %s %d fix_padding block_size:%u internal_hash:%s", ++ origin_dev, ++ set->tag_size, ++ set->mode, ++ count, ++ set->block_size, ++ set->internal_hash); ++ ++ if (seg->integrity_meta_node) ++ EMIT_PARAMS(pos, " meta_device:%s", meta_dev); ++ ++ if (seg->integrity_recalculate) ++ EMIT_PARAMS(pos, " recalculate"); ++ ++ if (set->journal_sectors_set) ++ EMIT_PARAMS(pos, " journal_sectors:%u", set->journal_sectors); ++ ++ if (set->interleave_sectors_set) ++ EMIT_PARAMS(pos, " ineterleave_sectors:%u", set->interleave_sectors); ++ ++ if (set->buffer_sectors_set) ++ EMIT_PARAMS(pos, " buffer_sectors:%u", set->buffer_sectors); ++ ++ if (set->journal_watermark_set) ++ EMIT_PARAMS(pos, " journal_watermark:%u", set->journal_watermark); ++ ++ if (set->commit_time_set) ++ EMIT_PARAMS(pos, " commit_time:%u", set->commit_time); ++ ++ if (set->bitmap_flush_interval_set) ++ EMIT_PARAMS(pos, " bitmap_flush_interval:%u", set->bitmap_flush_interval); ++ ++ if (set->sectors_per_bit_set) ++ EMIT_PARAMS(pos, " sectors_per_bit:%llu", (unsigned long long)set->sectors_per_bit); ++ ++ return 1; ++} ++ + static int _thin_pool_emit_segment_line(struct dm_task *dmt, + struct load_segment *seg, + char *params, size_t paramsize) +@@ -2889,6 +2984,10 @@ static int _emit_segment_line(struct dm_task *dmt, uint32_t major, + if (!_writecache_emit_segment_line(dmt, seg, params, paramsize)) + return_0; + break; ++ case SEG_INTEGRITY: ++ if (!_integrity_emit_segment_line(dmt, seg, params, paramsize)) ++ return_0; ++ break; + } + + switch(seg->type) { +@@ -2901,6 +3000,7 @@ static int _emit_segment_line(struct dm_task *dmt, uint32_t major, + case SEG_THIN: + case SEG_CACHE: + case SEG_WRITECACHE: ++ case SEG_INTEGRITY: + break; + case SEG_CRYPT: + case SEG_LINEAR: +@@ -3005,6 +3105,9 @@ static int _load_node(struct dm_tree_node *dnode) + if (!dm_task_suppress_identical_reload(dmt)) + log_warn("WARNING: Failed to suppress reload of identical tables."); + ++ if (dnode->props.skip_reload_params_compare) ++ dm_task_skip_reload_params_compare(dmt); ++ + if ((r = dm_task_run(dmt))) { + r = dm_task_get_info(dmt, &dnode->info); + if (r && !dnode->info.inactive_table) +@@ -3023,8 +3126,8 @@ static int _load_node(struct dm_tree_node *dnode) + if (!existing_table_size && dnode->props.delay_resume_if_new) + dnode->props.size_changed = 0; + +- log_debug_activation("Table size changed from %" PRIu64 " to %" +- PRIu64 " for %s.%s", existing_table_size, ++ log_debug_activation("Table size changed from %" PRIu64 " to %" PRIu64 " for %s.%s", ++ existing_table_size, + seg_start, _node_name(dnode), + dnode->props.size_changed ? "" : " (Ignoring.)"); + +@@ -3136,7 +3239,10 @@ int dm_tree_preload_children(struct dm_tree_node *dnode, + } + + /* No resume for a device without parents or with unchanged or smaller size */ +- if (!dm_tree_node_num_children(child, 1) || (child->props.size_changed <= 0)) ++ if (!dm_tree_node_num_children(child, 1)) ++ continue; ++ ++ if (child->props.size_changed <= 0) + continue; + + if (!child->info.inactive_table && !child->info.suspended) +@@ -3738,6 +3844,48 @@ int dm_tree_node_add_writecache_target(struct dm_tree_node *node, + return 1; + } + ++int dm_tree_node_add_integrity_target(struct dm_tree_node *node, ++ uint64_t size, ++ const char *origin_uuid, ++ const char *meta_uuid, ++ struct integrity_settings *settings, ++ int recalculate) ++{ ++ struct load_segment *seg; ++ ++ if (!(seg = _add_segment(node, SEG_INTEGRITY, size))) ++ return_0; ++ ++ if (!meta_uuid) { ++ log_error("No integrity meta uuid."); ++ return 0; ++ } ++ ++ if (!(seg->integrity_meta_node = dm_tree_find_node_by_uuid(node->dtree, meta_uuid))) { ++ log_error("Missing integrity's meta uuid %s.", meta_uuid); ++ return 0; ++ } ++ ++ if (!_link_tree_nodes(node, seg->integrity_meta_node)) ++ return_0; ++ ++ if (!(seg->origin = dm_tree_find_node_by_uuid(node->dtree, origin_uuid))) { ++ log_error("Missing integrity's origin uuid %s.", origin_uuid); ++ return 0; ++ } ++ ++ if (!_link_tree_nodes(node, seg->origin)) ++ return_0; ++ ++ memcpy(&seg->integrity_settings, settings, sizeof(struct integrity_settings)); ++ ++ seg->integrity_recalculate = recalculate; ++ ++ node->props.skip_reload_params_compare = 1; ++ ++ return 1; ++} ++ + int dm_tree_node_add_replicator_target(struct dm_tree_node *node, + uint64_t size, + const char *rlog_uuid, +diff --git a/device_mapper/libdm-targets.c b/device_mapper/libdm-targets.c +index 86cb847..bfe76c5 100644 +--- a/device_mapper/libdm-targets.c ++++ b/device_mapper/libdm-targets.c +@@ -380,6 +380,33 @@ int dm_get_status_writecache(struct dm_pool *mem, const char *params, + return 1; + } + ++int dm_get_status_integrity(struct dm_pool *mem, const char *params, ++ struct dm_status_integrity **status) ++{ ++ struct dm_status_integrity *s; ++ char recalc_str[16] = "\0"; ++ ++ if (!(s = dm_pool_zalloc(mem, sizeof(*s)))) ++ return_0; ++ ++ if (sscanf(params, "%llu %llu %s", ++ (unsigned long long *)&s->number_of_mismatches, ++ (unsigned long long *)&s->provided_data_sectors, ++ recalc_str) != 3) { ++ log_error("Failed to parse integrity params: %s.", params); ++ dm_pool_free(mem, s); ++ return 0; ++ } ++ ++ if (recalc_str[0] == '-') ++ s->recalc_sector = 0; ++ else ++ s->recalc_sector = strtoull(recalc_str, NULL, 0); ++ ++ *status = s; ++ return 1; ++} ++ + int parse_thin_pool_status(const char *params, struct dm_status_thin_pool *s) + { + int pos; +diff --git a/include/configure.h.in b/include/configure.h.in +index 91a3a7d..57736cc 100644 +--- a/include/configure.h.in ++++ b/include/configure.h.in +@@ -678,6 +678,9 @@ + /* Define to 1 to include built-in support for writecache. */ + #undef WRITECACHE_INTERNAL + ++/* Define to 1 to include built-in support for integrity. */ ++#undef INTEGRITY_INTERNAL ++ + /* Define to get access to GNU/Linux extension */ + #undef _GNU_SOURCE + +diff --git a/lib/Makefile.in b/lib/Makefile.in +index 2a064f3..8e50ec4 100644 +--- a/lib/Makefile.in ++++ b/lib/Makefile.in +@@ -20,6 +20,7 @@ SOURCES =\ + activate/activate.c \ + cache/lvmcache.c \ + writecache/writecache.c \ ++ integrity/integrity.c \ + cache_segtype/cache.c \ + commands/toolcontext.c \ + config/config.c \ +@@ -67,6 +68,7 @@ SOURCES =\ + log/log.c \ + metadata/cache_manip.c \ + metadata/writecache_manip.c \ ++ metadata/integrity_manip.c \ + metadata/lv.c \ + metadata/lv_manip.c \ + metadata/merge.c \ +diff --git a/lib/activate/activate.c b/lib/activate/activate.c +index a82a5cb..22c4e63 100644 +--- a/lib/activate/activate.c ++++ b/lib/activate/activate.c +@@ -2535,6 +2535,13 @@ static int _lv_activate(struct cmd_context *cmd, const char *lvid_s, + goto out; + } + ++ if ((cmd->partial_activation || cmd->degraded_activation) && ++ lv_is_partial(lv) && lv_is_raid(lv) && lv_raid_has_integrity((struct logical_volume *)lv)) { ++ cmd->partial_activation = 0; ++ cmd->degraded_activation = 0; ++ log_print("No degraded or partial activation for raid with integrity."); ++ } ++ + if ((!lv->vg->cmd->partial_activation) && lv_is_partial(lv)) { + if (!lv_is_raid_type(lv) || !partial_raid_lv_supports_degraded_activation(lv)) { + log_error("Refusing activation of partial LV %s. " +diff --git a/lib/activate/activate.h b/lib/activate/activate.h +index a5ee438..e3c1bb3 100644 +--- a/lib/activate/activate.h ++++ b/lib/activate/activate.h +@@ -39,6 +39,7 @@ typedef enum { + SEG_STATUS_THIN_POOL, + SEG_STATUS_VDO_POOL, + SEG_STATUS_WRITECACHE, ++ SEG_STATUS_INTEGRITY, + SEG_STATUS_UNKNOWN + } lv_seg_status_type_t; + +@@ -53,6 +54,7 @@ struct lv_seg_status { + struct dm_status_thin *thin; + struct dm_status_thin_pool *thin_pool; + struct dm_status_writecache *writecache; ++ struct dm_status_integrity *integrity; + struct lv_status_vdo vdo_pool; + }; + }; +@@ -260,6 +262,7 @@ void fs_unlock(void); + + #define TARGET_NAME_CACHE "cache" + #define TARGET_NAME_WRITECACHE "writecache" ++#define TARGET_NAME_INTEGRITY "integrity" + #define TARGET_NAME_ERROR "error" + #define TARGET_NAME_ERROR_OLD "erro" /* Truncated in older kernels */ + #define TARGET_NAME_LINEAR "linear" +@@ -277,6 +280,7 @@ void fs_unlock(void); + #define MODULE_NAME_CLUSTERED_MIRROR "clog" + #define MODULE_NAME_CACHE TARGET_NAME_CACHE + #define MODULE_NAME_WRITECACHE TARGET_NAME_WRITECACHE ++#define MODULE_NAME_INTEGRITY TARGET_NAME_INTEGRITY + #define MODULE_NAME_ERROR TARGET_NAME_ERROR + #define MODULE_NAME_LOG_CLUSTERED "log-clustered" + #define MODULE_NAME_LOG_USERSPACE "log-userspace" +diff --git a/lib/activate/dev_manager.c b/lib/activate/dev_manager.c +index 75d4df0..a626b00 100644 +--- a/lib/activate/dev_manager.c ++++ b/lib/activate/dev_manager.c +@@ -46,7 +46,7 @@ typedef enum { + } action_t; + + /* This list must match lib/misc/lvm-string.c:build_dm_uuid(). */ +-const char *uuid_suffix_list[] = { "pool", "cdata", "cmeta", "cvol", "tdata", "tmeta", "vdata", "vpool", NULL}; ++const char *uuid_suffix_list[] = { "pool", "cdata", "cmeta", "cvol", "tdata", "tmeta", "vdata", "vpool", "imeta", NULL}; + + struct dlid_list { + struct dm_list list; +@@ -222,6 +222,10 @@ static int _get_segment_status_from_target_params(const char *target_name, + if (!dm_get_status_writecache(seg_status->mem, params, &(seg_status->writecache))) + return_0; + seg_status->type = SEG_STATUS_WRITECACHE; ++ } else if (segtype_is_integrity(segtype)) { ++ if (!dm_get_status_integrity(seg_status->mem, params, &(seg_status->integrity))) ++ return_0; ++ seg_status->type = SEG_STATUS_INTEGRITY; + } else + /* + * TODO: Add support for other segment types too! +@@ -299,6 +303,9 @@ static int _info_run(const char *dlid, struct dm_info *dminfo, + if (lv_is_vdo_pool(seg_status->seg->lv)) + length = get_vdo_pool_virtual_size(seg_status->seg); + ++ if (lv_is_integrity(seg_status->seg->lv)) ++ length = seg_status->seg->integrity_data_sectors; ++ + do { + target = dm_get_next_target(dmt, target, &target_start, + &target_length, &target_name, &target_params); +@@ -2620,6 +2627,10 @@ static int _add_lv_to_dtree(struct dev_manager *dm, struct dm_tree *dtree, + if (!_add_lv_to_dtree(dm, dtree, seg->writecache, dm->activation ? origin_only : 1)) + return_0; + } ++ if (seg->integrity_meta_dev && seg_is_integrity(seg)) { ++ if (!_add_lv_to_dtree(dm, dtree, seg->integrity_meta_dev, dm->activation ? origin_only : 1)) ++ return_0; ++ } + if (seg->pool_lv && + (lv_is_cache_pool(seg->pool_lv) || lv_is_cache_vol(seg->pool_lv) || dm->track_external_lv_deps) && + /* When activating and not origin_only detect linear 'overlay' over pool */ +@@ -3076,6 +3087,11 @@ static int _add_segment_to_dtree(struct dev_manager *dm, + lv_layer(seg->writecache))) + return_0; + ++ if (seg->integrity_meta_dev && !laopts->origin_only && ++ !_add_new_lv_to_dtree(dm, dtree, seg->integrity_meta_dev, laopts, ++ lv_layer(seg->integrity_meta_dev))) ++ return_0; ++ + /* Add any LVs used by this segment */ + for (s = 0; s < seg->area_count; ++s) { + if ((seg_type(seg, s) == AREA_LV) && +diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c +index 479d499..88d5b3e 100644 +--- a/lib/commands/toolcontext.c ++++ b/lib/commands/toolcontext.c +@@ -1362,6 +1362,11 @@ static int _init_segtypes(struct cmd_context *cmd) + return 0; + #endif + ++#ifdef INTEGRITY_INTERNAL ++ if (!init_integrity_segtypes(cmd, &seglib)) ++ return 0; ++#endif ++ + return 1; + } + +diff --git a/lib/device/dev-type.c b/lib/device/dev-type.c +index d225d4d..deb5d6a 100644 +--- a/lib/device/dev-type.c ++++ b/lib/device/dev-type.c +@@ -647,6 +647,45 @@ out: + } + + #ifdef BLKID_WIPING_SUPPORT ++int get_fs_block_size(struct device *dev, uint32_t *fs_block_size) ++{ ++ blkid_probe probe = NULL; ++ const char *block_size_str = NULL; ++ uint64_t block_size_val; ++ int r = 0; ++ ++ *fs_block_size = 0; ++ ++ if (!(probe = blkid_new_probe_from_filename(dev_name(dev)))) { ++ log_error("Failed to create a new blkid probe for device %s.", dev_name(dev)); ++ goto out; ++ } ++ ++ blkid_probe_enable_partitions(probe, 1); ++ ++ (void) blkid_probe_lookup_value(probe, "BLOCK_SIZE", &block_size_str, NULL); ++ ++ if (!block_size_str) ++ goto out; ++ ++ block_size_val = strtoull(block_size_str, NULL, 10); ++ ++ *fs_block_size = (uint32_t)block_size_val; ++ r = 1; ++out: ++ if (probe) ++ blkid_free_probe(probe); ++ return r; ++} ++#else ++int get_fs_block_size(struct device *dev, uint32_t *fs_block_size) ++{ ++ *fs_block_size = 0; ++ return 0; ++} ++#endif ++ ++#ifdef BLKID_WIPING_SUPPORT + + static inline int _type_in_flag_list(const char *type, uint32_t flag_list) + { +diff --git a/lib/device/dev-type.h b/lib/device/dev-type.h +index e090050..fdf7791 100644 +--- a/lib/device/dev-type.h ++++ b/lib/device/dev-type.h +@@ -97,4 +97,6 @@ int dev_is_pmem(struct device *dev); + + int dev_is_lv(struct device *dev); + ++int get_fs_block_size(struct device *dev, uint32_t *fs_block_size); ++ + #endif +diff --git a/lib/format_text/flags.c b/lib/format_text/flags.c +index 2873ba6..bc93a5d 100644 +--- a/lib/format_text/flags.c ++++ b/lib/format_text/flags.c +@@ -104,6 +104,8 @@ static const struct flag _lv_flags[] = { + {LV_VDO_POOL, NULL, 0}, + {LV_VDO_POOL_DATA, NULL, 0}, + {WRITECACHE, NULL, 0}, ++ {INTEGRITY, NULL, 0}, ++ {INTEGRITY_METADATA, NULL, 0}, + {LV_PENDING_DELETE, NULL, 0}, /* FIXME Display like COMPATIBLE_FLAG */ + {LV_REMOVED, NULL, 0}, + {0, NULL, 0} +diff --git a/lib/integrity/integrity.c b/lib/integrity/integrity.c +new file mode 100644 +index 0000000..d5ad86b +--- /dev/null ++++ b/lib/integrity/integrity.c +@@ -0,0 +1,343 @@ ++/* ++ * Copyright (C) 2013-2016 Red Hat, Inc. All rights reserved. ++ * ++ * This file is part of LVM2. ++ * ++ * This copyrighted material is made available to anyone wishing to use, ++ * modify, copy, or redistribute it subject to the terms and conditions ++ * of the GNU Lesser General Public License v.2.1. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include "base/memory/zalloc.h" ++#include "lib/misc/lib.h" ++#include "lib/commands/toolcontext.h" ++#include "lib/metadata/segtype.h" ++#include "lib/display/display.h" ++#include "lib/format_text/text_export.h" ++#include "lib/config/config.h" ++#include "lib/datastruct/str_list.h" ++#include "lib/misc/lvm-string.h" ++#include "lib/activate/activate.h" ++#include "lib/metadata/metadata.h" ++#include "lib/metadata/lv_alloc.h" ++#include "lib/config/defaults.h" ++ ++#define SEG_LOG_ERROR(t, p...) \ ++ log_error(t " segment %s of logical volume %s.", ## p, \ ++ dm_config_parent_name(sn), seg->lv->name), 0; ++ ++static void _integrity_display(const struct lv_segment *seg) ++{ ++ /* TODO: lvdisplay segments */ ++} ++ ++static int _integrity_text_import(struct lv_segment *seg, ++ const struct dm_config_node *sn, ++ struct dm_hash_table *pv_hash __attribute__((unused))) ++{ ++ struct integrity_settings *set; ++ struct logical_volume *origin_lv = NULL; ++ struct logical_volume *meta_lv = NULL; ++ const char *origin_name = NULL; ++ const char *meta_dev = NULL; ++ const char *mode = NULL; ++ const char *hash = NULL; ++ ++ memset(&seg->integrity_settings, 0, sizeof(struct integrity_settings)); ++ set = &seg->integrity_settings; ++ ++ /* origin always set */ ++ ++ if (!dm_config_has_node(sn, "origin")) ++ return SEG_LOG_ERROR("origin not specified in"); ++ ++ if (!dm_config_get_str(sn, "origin", &origin_name)) ++ return SEG_LOG_ERROR("origin must be a string in"); ++ ++ if (!(origin_lv = find_lv(seg->lv->vg, origin_name))) ++ return SEG_LOG_ERROR("Unknown LV specified for integrity origin %s in", origin_name); ++ ++ if (!set_lv_segment_area_lv(seg, 0, origin_lv, 0, 0)) ++ return_0; ++ ++ /* data_sectors always set */ ++ ++ if (!dm_config_get_uint64(sn, "data_sectors", &seg->integrity_data_sectors)) ++ return SEG_LOG_ERROR("integrity data_sectors must be set in"); ++ ++ /* mode always set */ ++ ++ if (!dm_config_get_str(sn, "mode", &mode)) ++ return SEG_LOG_ERROR("integrity mode must be set in"); ++ ++ if (strlen(mode) > 7) ++ return SEG_LOG_ERROR("integrity mode invalid in"); ++ ++ strncpy(set->mode, mode, 7); ++ ++ /* tag_size always set */ ++ ++ if (!dm_config_get_uint32(sn, "tag_size", &set->tag_size)) ++ return SEG_LOG_ERROR("integrity tag_size must be set in"); ++ ++ /* block_size always set */ ++ ++ if (!dm_config_get_uint32(sn, "block_size", &set->block_size)) ++ return SEG_LOG_ERROR("integrity block_size invalid in"); ++ ++ /* internal_hash always set */ ++ ++ if (!dm_config_get_str(sn, "internal_hash", &hash)) ++ return SEG_LOG_ERROR("integrity internal_hash must be set in"); ++ ++ if (!(set->internal_hash = dm_pool_strdup(seg->lv->vg->vgmem, hash))) ++ return SEG_LOG_ERROR("integrity internal_hash failed to be set in"); ++ ++ /* meta_dev optional */ ++ ++ if (dm_config_has_node(sn, "meta_dev")) { ++ if (!dm_config_get_str(sn, "meta_dev", &meta_dev)) ++ return SEG_LOG_ERROR("meta_dev must be a string in"); ++ ++ if (!(meta_lv = find_lv(seg->lv->vg, meta_dev))) ++ return SEG_LOG_ERROR("Unknown logical volume %s specified for integrity in", meta_dev); ++ } ++ ++ if (dm_config_has_node(sn, "recalculate")) { ++ if (!dm_config_get_uint32(sn, "recalculate", &seg->integrity_recalculate)) ++ return SEG_LOG_ERROR("integrity recalculate error in"); ++ } ++ ++ /* the rest are optional */ ++ ++ if (dm_config_has_node(sn, "journal_sectors")) { ++ if (!dm_config_get_uint32(sn, "journal_sectors", &set->journal_sectors)) ++ return SEG_LOG_ERROR("Unknown integrity_setting in"); ++ set->journal_sectors_set = 1; ++ } ++ ++ if (dm_config_has_node(sn, "interleave_sectors")) { ++ if (!dm_config_get_uint32(sn, "interleave_sectors", &set->interleave_sectors)) ++ return SEG_LOG_ERROR("Unknown integrity_setting in"); ++ set->interleave_sectors_set = 1; ++ } ++ ++ if (dm_config_has_node(sn, "buffer_sectors")) { ++ if (!dm_config_get_uint32(sn, "buffer_sectors", &set->buffer_sectors)) ++ return SEG_LOG_ERROR("Unknown integrity_setting in"); ++ set->buffer_sectors_set = 1; ++ } ++ ++ if (dm_config_has_node(sn, "journal_watermark")) { ++ if (!dm_config_get_uint32(sn, "journal_watermark", &set->journal_watermark)) ++ return SEG_LOG_ERROR("Unknown integrity_setting in"); ++ set->journal_watermark_set = 1; ++ } ++ ++ if (dm_config_has_node(sn, "commit_time")) { ++ if (!dm_config_get_uint32(sn, "commit_time", &set->commit_time)) ++ return SEG_LOG_ERROR("Unknown integrity_setting in"); ++ set->commit_time_set = 1; ++ } ++ ++ if (dm_config_has_node(sn, "bitmap_flush_interval")) { ++ if (!dm_config_get_uint32(sn, "bitmap_flush_interval", &set->bitmap_flush_interval)) ++ return SEG_LOG_ERROR("Unknown integrity_setting in"); ++ set->bitmap_flush_interval_set = 1; ++ } ++ ++ if (dm_config_has_node(sn, "sectors_per_bit")) { ++ if (!dm_config_get_uint64(sn, "sectors_per_bit", &set->sectors_per_bit)) ++ return SEG_LOG_ERROR("Unknown integrity_setting in"); ++ set->sectors_per_bit_set = 1; ++ } ++ ++ seg->origin = origin_lv; ++ seg->integrity_meta_dev = meta_lv; ++ seg->lv->status |= INTEGRITY; ++ ++ if (meta_lv) ++ meta_lv->status |= INTEGRITY_METADATA; ++ ++ if (meta_lv && !add_seg_to_segs_using_this_lv(meta_lv, seg)) ++ return_0; ++ ++ return 1; ++} ++ ++static int _integrity_text_import_area_count(const struct dm_config_node *sn, ++ uint32_t *area_count) ++{ ++ *area_count = 1; ++ ++ return 1; ++} ++ ++static int _integrity_text_export(const struct lv_segment *seg, ++ struct formatter *f) ++{ ++ const struct integrity_settings *set = &seg->integrity_settings; ++ ++ outf(f, "origin = \"%s\"", seg_lv(seg, 0)->name); ++ outf(f, "data_sectors = %llu", (unsigned long long)seg->integrity_data_sectors); ++ ++ outf(f, "mode = \"%s\"", set->mode); ++ outf(f, "tag_size = %u", set->tag_size); ++ outf(f, "block_size = %u", set->block_size); ++ outf(f, "internal_hash = \"%s\"", set->internal_hash); ++ ++ if (seg->integrity_meta_dev) ++ outf(f, "meta_dev = \"%s\"", seg->integrity_meta_dev->name); ++ ++ if (seg->integrity_recalculate) ++ outf(f, "recalculate = %u", seg->integrity_recalculate); ++ ++ if (set->journal_sectors_set) ++ outf(f, "journal_sectors = %u", set->journal_sectors); ++ ++ if (set->interleave_sectors_set) ++ outf(f, "interleave_sectors = %u", set->interleave_sectors); ++ ++ if (set->buffer_sectors_set) ++ outf(f, "buffer_sectors = %u", set->buffer_sectors); ++ ++ if (set->journal_watermark_set) ++ outf(f, "journal_watermark = %u", set->journal_watermark); ++ ++ if (set->commit_time_set) ++ outf(f, "commit_time = %u", set->commit_time); ++ ++ if (set->bitmap_flush_interval) ++ outf(f, "bitmap_flush_interval = %u", set->bitmap_flush_interval); ++ ++ if (set->sectors_per_bit) ++ outf(f, "sectors_per_bit = %llu", (unsigned long long)set->sectors_per_bit); ++ ++ return 1; ++} ++ ++static void _destroy(struct segment_type *segtype) ++{ ++ free((void *) segtype); ++} ++ ++#ifdef DEVMAPPER_SUPPORT ++ ++static int _target_present(struct cmd_context *cmd, ++ const struct lv_segment *seg __attribute__((unused)), ++ unsigned *attributes __attribute__((unused))) ++{ ++ static int _integrity_checked = 0; ++ static int _integrity_present = 0; ++ uint32_t maj, min, patchlevel; ++ ++ if (!activation()) ++ return 0; ++ ++ if (!_integrity_checked) { ++ _integrity_checked = 1; ++ _integrity_present = target_present(cmd, TARGET_NAME_INTEGRITY, 1); ++ ++ if (!target_version(TARGET_NAME_INTEGRITY, &maj, &min, &patchlevel)) ++ return 0; ++ ++ if (maj < 1 || min < 6) { ++ log_error("Integrity target version older than minimum 1.6.0"); ++ return 0; ++ } ++ } ++ ++ return _integrity_present; ++} ++ ++static int _modules_needed(struct dm_pool *mem, ++ const struct lv_segment *seg __attribute__((unused)), ++ struct dm_list *modules) ++{ ++ if (!str_list_add(mem, modules, MODULE_NAME_INTEGRITY)) { ++ log_error("String list allocation failed for integrity module."); ++ return 0; ++ } ++ ++ return 1; ++} ++#endif /* DEVMAPPER_SUPPORT */ ++ ++#ifdef DEVMAPPER_SUPPORT ++static int _integrity_add_target_line(struct dev_manager *dm, ++ struct dm_pool *mem, ++ struct cmd_context *cmd __attribute__((unused)), ++ void **target_state __attribute__((unused)), ++ struct lv_segment *seg, ++ const struct lv_activate_opts *laopts, ++ struct dm_tree_node *node, uint64_t len, ++ uint32_t *pvmove_mirror_count __attribute__((unused))) ++{ ++ char *origin_uuid; ++ char *meta_uuid = NULL; ++ ++ if (!seg_is_integrity(seg)) { ++ log_error(INTERNAL_ERROR "Passed segment is not integrity."); ++ return 0; ++ } ++ ++ if (!(origin_uuid = build_dm_uuid(mem, seg_lv(seg, 0), NULL))) ++ return_0; ++ ++ if (seg->integrity_meta_dev) { ++ if (!(meta_uuid = build_dm_uuid(mem, seg->integrity_meta_dev, NULL))) ++ return_0; ++ } ++ ++ if (!seg->integrity_data_sectors) { ++ log_error("_integrity_add_target_line zero size"); ++ return_0; ++ } ++ ++ if (!dm_tree_node_add_integrity_target(node, seg->integrity_data_sectors, ++ origin_uuid, meta_uuid, ++ &seg->integrity_settings, ++ seg->integrity_recalculate)) ++ return_0; ++ ++ return 1; ++} ++#endif /* DEVMAPPER_SUPPORT */ ++ ++static struct segtype_handler _integrity_ops = { ++ .display = _integrity_display, ++ .text_import = _integrity_text_import, ++ .text_import_area_count = _integrity_text_import_area_count, ++ .text_export = _integrity_text_export, ++#ifdef DEVMAPPER_SUPPORT ++ .add_target_line = _integrity_add_target_line, ++ .target_present = _target_present, ++ .modules_needed = _modules_needed, ++#endif ++ .destroy = _destroy, ++}; ++ ++int init_integrity_segtypes(struct cmd_context *cmd, ++ struct segtype_library *seglib) ++{ ++ struct segment_type *segtype = zalloc(sizeof(*segtype)); ++ ++ if (!segtype) { ++ log_error("Failed to allocate memory for integrity segtype"); ++ return 0; ++ } ++ ++ segtype->name = SEG_TYPE_NAME_INTEGRITY; ++ segtype->flags = SEG_INTEGRITY; ++ segtype->ops = &_integrity_ops; ++ ++ if (!lvm_register_segtype(seglib, segtype)) ++ return_0; ++ log_very_verbose("Initialised segtype: %s", segtype->name); ++ ++ return 1; ++} +diff --git a/lib/metadata/integrity_manip.c b/lib/metadata/integrity_manip.c +new file mode 100644 +index 0000000..7942be0 +--- /dev/null ++++ b/lib/metadata/integrity_manip.c +@@ -0,0 +1,821 @@ ++/* ++ * Copyright (C) 2014-2015 Red Hat, Inc. All rights reserved. ++ * ++ * This file is part of LVM2. ++ * ++ * This copyrighted material is made available to anyone wishing to use, ++ * modify, copy, or redistribute it subject to the terms and conditions ++ * of the GNU Lesser General Public License v.2.1. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include "lib/misc/lib.h" ++#include "lib/metadata/metadata.h" ++#include "lib/locking/locking.h" ++#include "lib/misc/lvm-string.h" ++#include "lib/commands/toolcontext.h" ++#include "lib/display/display.h" ++#include "lib/metadata/segtype.h" ++#include "lib/activate/activate.h" ++#include "lib/config/defaults.h" ++#include "lib/activate/dev_manager.h" ++ ++#define DEFAULT_TAG_SIZE 4 /* bytes */ ++#define DEFAULT_MODE 'J' ++#define DEFAULT_INTERNAL_HASH "crc32c" ++#define DEFAULT_BLOCK_SIZE 512 ++ ++#define ONE_MB_IN_BYTES 1048576 ++ ++int lv_is_integrity_origin(const struct logical_volume *lv) ++{ ++ struct seg_list *sl; ++ ++ dm_list_iterate_items(sl, &lv->segs_using_this_lv) { ++ if (!sl->seg || !sl->seg->lv || !sl->seg->origin) ++ continue; ++ if (lv_is_integrity(sl->seg->lv) && (sl->seg->origin == lv)) ++ return 1; ++ } ++ return 0; ++} ++ ++/* ++ * Every 500M of data needs 4M of metadata. ++ * (From trial and error testing.) ++ */ ++static uint64_t _lv_size_bytes_to_integrity_meta_bytes(uint64_t lv_size_bytes) ++{ ++ return ((lv_size_bytes / (500 * ONE_MB_IN_BYTES)) + 1) * (4 * ONE_MB_IN_BYTES); ++} ++ ++/* ++ * The user wants external metadata, but did not specify an existing ++ * LV to hold metadata, so create an LV for metadata. ++ */ ++static int _lv_create_integrity_metadata(struct cmd_context *cmd, ++ struct volume_group *vg, ++ struct lvcreate_params *lp, ++ struct logical_volume **meta_lv) ++{ ++ char metaname[NAME_LEN]; ++ uint64_t lv_size_bytes, meta_bytes, meta_sectors; ++ struct logical_volume *lv; ++ struct lvcreate_params lp_meta = { ++ .activate = CHANGE_AN, ++ .alloc = ALLOC_INHERIT, ++ .major = -1, ++ .minor = -1, ++ .permission = LVM_READ | LVM_WRITE, ++ .pvh = &vg->pvs, ++ .read_ahead = DM_READ_AHEAD_NONE, ++ .stripes = 1, ++ .vg_name = vg->name, ++ .zero = 0, ++ .wipe_signatures = 0, ++ .suppress_zero_warn = 1, ++ }; ++ ++ if (lp->lv_name && ++ dm_snprintf(metaname, NAME_LEN, "%s_imeta", lp->lv_name) < 0) { ++ log_error("Failed to create metadata LV name."); ++ return 0; ++ } ++ ++ lp_meta.lv_name = metaname; ++ lp_meta.pvh = lp->pvh; ++ ++ lv_size_bytes = (uint64_t)lp->extents * (uint64_t)vg->extent_size * 512; ++ meta_bytes = _lv_size_bytes_to_integrity_meta_bytes(lv_size_bytes); ++ meta_sectors = meta_bytes / 512; ++ lp_meta.extents = meta_sectors / vg->extent_size; ++ ++ log_print_unless_silent("Creating integrity metadata LV %s with size %s.", ++ metaname, display_size(cmd, meta_sectors)); ++ ++ dm_list_init(&lp_meta.tags); ++ ++ if (!(lp_meta.segtype = get_segtype_from_string(vg->cmd, SEG_TYPE_NAME_STRIPED))) ++ return_0; ++ ++ if (!(lv = lv_create_single(vg, &lp_meta))) { ++ log_error("Failed to create integrity metadata LV"); ++ return 0; ++ } ++ ++ if (dm_list_size(&lv->segments) > 1) { ++ log_error("Integrity metadata uses more than one segment."); ++ return 0; ++ } ++ ++ *meta_lv = lv; ++ return 1; ++} ++ ++int lv_extend_integrity_in_raid(struct logical_volume *lv, struct dm_list *pvh) ++{ ++ struct cmd_context *cmd = lv->vg->cmd; ++ struct volume_group *vg = lv->vg; ++ const struct segment_type *segtype; ++ struct lv_segment *seg_top, *seg_image; ++ struct logical_volume *lv_image; ++ struct logical_volume *lv_iorig; ++ struct logical_volume *lv_imeta; ++ struct dm_list allocatable_pvs; ++ struct dm_list *use_pvh; ++ uint64_t lv_size_bytes, meta_bytes, meta_sectors, prev_meta_sectors; ++ uint32_t meta_extents, prev_meta_extents; ++ uint32_t area_count, s; ++ ++ seg_top = first_seg(lv); ++ ++ if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_STRIPED))) ++ return_0; ++ ++ area_count = seg_top->area_count; ++ ++ for (s = 0; s < area_count; s++) { ++ lv_image = seg_lv(seg_top, s); ++ seg_image = first_seg(lv_image); ++ ++ if (!(lv_imeta = seg_image->integrity_meta_dev)) { ++ log_error("LV %s segment has no integrity metadata device.", display_lvname(lv)); ++ return 0; ++ } ++ ++ if (!(lv_iorig = seg_lv(seg_image, 0))) { ++ log_error("LV %s integrity segment has no origin", display_lvname(lv)); ++ return 0; ++ } ++ ++ lv_size_bytes = lv_iorig->size * 512; ++ meta_bytes = _lv_size_bytes_to_integrity_meta_bytes(lv_size_bytes); ++ meta_sectors = meta_bytes / 512; ++ meta_extents = meta_sectors / vg->extent_size; ++ ++ prev_meta_sectors = lv_imeta->size; ++ prev_meta_extents = prev_meta_sectors / vg->extent_size; ++ ++ if (meta_extents <= prev_meta_extents) { ++ log_debug("extend not needed for imeta LV %s", lv_imeta->name); ++ continue; ++ } ++ ++ /* ++ * We only allow lv_imeta to exist on a single PV (for now), ++ * so the allocatable_pvs is the one PV currently used by ++ * lv_imeta. ++ */ ++ dm_list_init(&allocatable_pvs); ++ ++ if (!get_pv_list_for_lv(cmd->mem, lv_imeta, &allocatable_pvs)) { ++ log_error("Failed to build list of PVs for extending %s.", display_lvname(lv_imeta)); ++ return 0; ++ } ++ ++ use_pvh = &allocatable_pvs; ++ ++ if (!lv_extend(lv_imeta, segtype, 1, 0, 0, 0, ++ meta_extents - prev_meta_extents, ++ use_pvh, lv_imeta->alloc, 0)) { ++ log_error("Failed to extend integrity metadata LV %s", lv_imeta->name); ++ return 0; ++ } ++ } ++ ++ return 1; ++} ++ ++int lv_remove_integrity_from_raid(struct logical_volume *lv) ++{ ++ struct logical_volume *iorig_lvs[DEFAULT_RAID_MAX_IMAGES]; ++ struct logical_volume *imeta_lvs[DEFAULT_RAID_MAX_IMAGES]; ++ struct cmd_context *cmd = lv->vg->cmd; ++ struct volume_group *vg = lv->vg; ++ struct lv_segment *seg_top, *seg_image; ++ struct logical_volume *lv_image; ++ struct logical_volume *lv_iorig; ++ struct logical_volume *lv_imeta; ++ uint32_t area_count, s; ++ int is_active = lv_is_active(lv); ++ ++ seg_top = first_seg(lv); ++ ++ if (!seg_is_raid1(seg_top) && !seg_is_raid4(seg_top) && ++ !seg_is_any_raid5(seg_top) && !seg_is_any_raid6(seg_top) && ++ !seg_is_any_raid10(seg_top)) { ++ log_error("LV %s segment is unsupported raid for integrity.", display_lvname(lv)); ++ return 0; ++ } ++ ++ area_count = seg_top->area_count; ++ ++ for (s = 0; s < area_count; s++) { ++ lv_image = seg_lv(seg_top, s); ++ seg_image = first_seg(lv_image); ++ ++ if (!(lv_imeta = seg_image->integrity_meta_dev)) { ++ log_error("LV %s segment has no integrity metadata device.", display_lvname(lv)); ++ return 0; ++ } ++ ++ if (!(lv_iorig = seg_lv(seg_image, 0))) { ++ log_error("LV %s integrity segment has no origin", display_lvname(lv)); ++ return 0; ++ } ++ ++ if (!remove_seg_from_segs_using_this_lv(seg_image->integrity_meta_dev, seg_image)) ++ return_0; ++ ++ iorig_lvs[s] = lv_iorig; ++ imeta_lvs[s] = lv_imeta; ++ ++ lv_image->status &= ~INTEGRITY; ++ seg_image->integrity_meta_dev = NULL; ++ seg_image->integrity_data_sectors = 0; ++ memset(&seg_image->integrity_settings, 0, sizeof(seg_image->integrity_settings)); ++ ++ if (!remove_layer_from_lv(lv_image, lv_iorig)) ++ return_0; ++ } ++ ++ if (is_active) { ++ /* vg_write(), suspend_lv(), vg_commit(), resume_lv() */ ++ if (!lv_update_and_reload(lv)) { ++ log_error("Failed to update and reload LV after integrity remove."); ++ return 0; ++ } ++ } ++ ++ for (s = 0; s < area_count; s++) { ++ lv_iorig = iorig_lvs[s]; ++ lv_imeta = imeta_lvs[s]; ++ ++ if (is_active) { ++ if (!deactivate_lv(cmd, lv_iorig)) ++ log_error("Failed to deactivate unused iorig LV %s.", lv_iorig->name); ++ ++ if (!deactivate_lv(cmd, lv_imeta)) ++ log_error("Failed to deactivate unused imeta LV %s.", lv_imeta->name); ++ } ++ ++ lv_imeta->status &= ~INTEGRITY_METADATA; ++ lv_set_visible(lv_imeta); ++ ++ if (!lv_remove(lv_iorig)) ++ log_error("Failed to remove unused iorig LV %s.", lv_iorig->name); ++ ++ if (!lv_remove(lv_imeta)) ++ log_error("Failed to remove unused imeta LV %s.", lv_imeta->name); ++ } ++ ++ if (!vg_write(vg) || !vg_commit(vg)) ++ return_0; ++ ++ return 1; ++} ++ ++static int _set_integrity_block_size(struct cmd_context *cmd, struct logical_volume *lv, ++ struct integrity_settings *settings, ++ int lbs_4k, int lbs_512, int pbs_4k, int pbs_512) ++{ ++ char pathname[PATH_MAX]; ++ struct device *fs_dev; ++ uint32_t fs_block_size = 0; ++ int rv; ++ ++ if (lbs_4k && lbs_512) { ++ log_error("Integrity requires consistent logical block size for LV devices."); ++ goto_bad; ++ } ++ ++ if (settings->block_size && ++ (settings->block_size != 512 && settings->block_size != 1024 && ++ settings->block_size != 2048 && settings->block_size != 4096)) { ++ log_error("Invalid integrity block size, possible values are 512, 1024, 2048, 4096"); ++ goto_bad; ++ } ++ ++ if (lbs_4k && settings->block_size && (settings->block_size < 4096)) { ++ log_error("Integrity block size %u not allowed with device logical block size 4096.", ++ settings->block_size); ++ goto_bad; ++ } ++ ++ if (!strcmp(cmd->name, "lvcreate")) { ++ if (lbs_4k) { ++ settings->block_size = 4096; ++ } else if (lbs_512 && pbs_4k && !pbs_512) { ++ settings->block_size = 4096; ++ } else if (lbs_512) { ++ if (!settings->block_size) ++ settings->block_size = 512; ++ } else if (!lbs_4k && !lbs_512) { ++ if (!settings->block_size) ++ settings->block_size = 512; ++ log_print("Using integrity block size %u with unknown device logical block size.", ++ settings->block_size); ++ } else { ++ goto_bad; ++ } ++ ++ } else if (!strcmp(cmd->name, "lvconvert")) { ++ if (dm_snprintf(pathname, sizeof(pathname), "%s%s/%s", cmd->dev_dir, ++ lv->vg->name, lv->name) < 0) { ++ log_error("Path name too long to get LV block size %s", display_lvname(lv)); ++ goto_bad; ++ } ++ if (!(fs_dev = dev_cache_get(cmd, pathname, NULL))) { ++ log_error("Device for LV not found to check block size %s", display_lvname(lv)); ++ goto_bad; ++ } ++ ++ /* ++ * get_fs_block_size() returns the libblkid BLOCK_SIZE value, ++ * where libblkid has fs-specific code to set BLOCK_SIZE to the ++ * value we need here. ++ * ++ * The term "block size" here may not equate directly to what the fs ++ * calls the block size, e.g. xfs calls this the sector size (and ++ * something different the block size); while ext4 does call this ++ * value the block size, but it's possible values are not the same ++ * as xfs's, and do not seem to relate directly to the device LBS. ++ */ ++ rv = get_fs_block_size(fs_dev, &fs_block_size); ++ if (!rv || !fs_block_size) { ++ int use_bs; ++ ++ if (lbs_4k && pbs_4k) { ++ use_bs = 4096; ++ } else if (lbs_512 && pbs_512) { ++ use_bs = 512; ++ } else if (lbs_512 && pbs_4k) { ++ if (settings->block_size == 4096) ++ use_bs = 4096; ++ else ++ use_bs = 512; ++ } else { ++ use_bs = 512; ++ } ++ ++ if (settings->block_size && (settings->block_size != use_bs)) { ++ log_error("Cannot use integrity block size %u with unknown file system block size, logical block size %u, physical block size %u.", ++ settings->block_size, lbs_4k ? 4096 : 512, pbs_4k ? 4096 : 512); ++ goto bad; ++ } ++ ++ settings->block_size = use_bs; ++ ++ log_print("Using integrity block size %u for unknown file system block size, logical block size %u, physical block size %u.", ++ settings->block_size, lbs_4k ? 4096 : 512, pbs_4k ? 4096 : 512); ++ goto out; ++ } ++ ++ if (!settings->block_size) { ++ if (fs_block_size <= 4096) ++ settings->block_size = fs_block_size; ++ else ++ settings->block_size = 4096; /* dm-integrity max is 4096 */ ++ log_print("Using integrity block size %u for file system block size %u.", ++ settings->block_size, fs_block_size); ++ } else { ++ /* let user specify integrity block size that is less than fs block size */ ++ if (settings->block_size > fs_block_size) { ++ log_error("Integrity block size %u cannot be larger than file system block size %u.", ++ settings->block_size, fs_block_size); ++ goto_bad; ++ } ++ log_print("Using integrity block size %u for file system block size %u.", ++ settings->block_size, fs_block_size); ++ } ++ } ++out: ++ return 1; ++bad: ++ return 0; ++} ++ ++/* ++ * Add integrity to each raid image. ++ * ++ * for each rimage_N: ++ * . create and allocate a new linear LV rimage_N_imeta ++ * . move the segments from rimage_N to a new rimage_N_iorig ++ * . add an integrity segment to rimage_N with ++ * origin=rimage_N_iorig, meta_dev=rimage_N_imeta ++ * ++ * Before: ++ * rimage_0 ++ * segment1: striped: pv0:A ++ * rimage_1 ++ * segment1: striped: pv1:B ++ * ++ * After: ++ * rimage_0 ++ * segment1: integrity: rimage_0_iorig, rimage_0_imeta ++ * rimage_1 ++ * segment1: integrity: rimage_1_iorig, rimage_1_imeta ++ * rimage_0_iorig ++ * segment1: striped: pv0:A ++ * rimage_1_iorig ++ * segment1: striped: pv1:B ++ * rimage_0_imeta ++ * segment1: striped: pv2:A ++ * rimage_1_imeta ++ * segment1: striped: pv2:B ++ * ++ */ ++ ++int lv_add_integrity_to_raid(struct logical_volume *lv, struct integrity_settings *settings, ++ struct dm_list *pvh, struct logical_volume *lv_imeta_0) ++{ ++ char imeta_name[NAME_LEN]; ++ char *imeta_name_dup; ++ struct lvcreate_params lp; ++ struct dm_list allocatable_pvs; ++ struct logical_volume *imeta_lvs[DEFAULT_RAID_MAX_IMAGES]; ++ struct cmd_context *cmd = lv->vg->cmd; ++ struct volume_group *vg = lv->vg; ++ struct logical_volume *lv_image, *lv_imeta, *lv_iorig; ++ struct lv_segment *seg_top, *seg_image; ++ struct pv_list *pvl; ++ const struct segment_type *segtype; ++ struct integrity_settings *set = NULL; ++ struct dm_list *use_pvh = NULL; ++ uint32_t area_count, s; ++ uint32_t revert_meta_lvs = 0; ++ int lbs_4k = 0, lbs_512 = 0, lbs_unknown = 0; ++ int pbs_4k = 0, pbs_512 = 0, pbs_unknown = 0; ++ int is_active; ++ ++ memset(imeta_lvs, 0, sizeof(imeta_lvs)); ++ ++ is_active = lv_is_active(lv); ++ ++ if (dm_list_size(&lv->segments) != 1) ++ return_0; ++ ++ if (!dm_list_empty(&lv->segs_using_this_lv)) { ++ log_error("Integrity can only be added to top level raid LV."); ++ return 0; ++ } ++ ++ if (lv_is_origin(lv)) { ++ log_error("Integrity cannot be added to snapshot origins."); ++ return 0; ++ } ++ ++ seg_top = first_seg(lv); ++ area_count = seg_top->area_count; ++ ++ if (!seg_is_raid1(seg_top) && !seg_is_raid4(seg_top) && ++ !seg_is_any_raid5(seg_top) && !seg_is_any_raid6(seg_top) && ++ !seg_is_any_raid10(seg_top)) { ++ log_error("Integrity can only be added to raid1,4,5,6,10."); ++ return 0; ++ } ++ ++ /* ++ * For each rimage, create an _imeta LV for integrity metadata. ++ * Each needs to be zeroed. ++ */ ++ for (s = 0; s < area_count; s++) { ++ struct logical_volume *meta_lv; ++ struct wipe_params wipe = { .do_zero = 1, .zero_sectors = 8 }; ++ ++ if (s >= DEFAULT_RAID_MAX_IMAGES) ++ goto_bad; ++ ++ lv_image = seg_lv(seg_top, s); ++ ++ /* ++ * This function is used to add integrity to new images added ++ * to the raid, in which case old images will already be ++ * integrity. ++ */ ++ if (seg_is_integrity(first_seg(lv_image))) ++ continue; ++ ++ if (!seg_is_striped(first_seg(lv_image))) { ++ log_error("raid image must be linear to add integrity"); ++ goto_bad; ++ } ++ ++ /* ++ * Use an existing lv_imeta from previous linear+integrity LV. ++ * FIXME: is it guaranteed that lv_image_0 is the existing? ++ */ ++ if (!s && lv_imeta_0) { ++ if (dm_snprintf(imeta_name, sizeof(imeta_name), "%s_imeta", lv_image->name) > 0) { ++ if ((imeta_name_dup = dm_pool_strdup(vg->vgmem, imeta_name))) ++ lv_imeta_0->name = imeta_name_dup; ++ } ++ imeta_lvs[0] = lv_imeta_0; ++ continue; ++ } ++ ++ dm_list_init(&allocatable_pvs); ++ ++ if (!get_pv_list_for_lv(cmd->mem, lv_image, &allocatable_pvs)) { ++ log_error("Failed to build list of PVs for %s.", display_lvname(lv_image)); ++ goto_bad; ++ } ++ ++ dm_list_iterate_items(pvl, &allocatable_pvs) { ++ unsigned int pbs = 0; ++ unsigned int lbs = 0; ++ ++ if (!dev_get_direct_block_sizes(pvl->pv->dev, &pbs, &lbs)) { ++ lbs_unknown++; ++ pbs_unknown++; ++ continue; ++ } ++ if (lbs == 4096) ++ lbs_4k++; ++ else if (lbs == 512) ++ lbs_512++; ++ else ++ lbs_unknown++; ++ if (pbs == 4096) ++ pbs_4k++; ++ else if (pbs == 512) ++ pbs_512++; ++ else ++ pbs_unknown++; ++ } ++ ++ use_pvh = &allocatable_pvs; ++ ++ /* ++ * allocate a new linear LV NAME_rimage_N_imeta ++ */ ++ memset(&lp, 0, sizeof(lp)); ++ lp.lv_name = lv_image->name; ++ lp.pvh = use_pvh; ++ lp.extents = lv_image->size / vg->extent_size; ++ ++ if (!_lv_create_integrity_metadata(cmd, vg, &lp, &meta_lv)) ++ goto_bad; ++ ++ revert_meta_lvs++; ++ ++ /* Used below to set up the new integrity segment. */ ++ imeta_lvs[s] = meta_lv; ++ ++ /* ++ * dm-integrity requires the metadata LV header to be zeroed. ++ */ ++ ++ if (!activate_lv(cmd, meta_lv)) { ++ log_error("Failed to activate LV %s to zero", display_lvname(meta_lv)); ++ goto_bad; ++ } ++ ++ if (!wipe_lv(meta_lv, wipe)) { ++ log_error("Failed to zero LV for integrity metadata %s", display_lvname(meta_lv)); ++ if (deactivate_lv(cmd, meta_lv)) ++ log_error("Failed to deactivate LV %s after zero", display_lvname(meta_lv)); ++ goto_bad; ++ } ++ ++ if (!deactivate_lv(cmd, meta_lv)) { ++ log_error("Failed to deactivate LV %s after zero", display_lvname(meta_lv)); ++ goto_bad; ++ } ++ } ++ ++ /* ++ * Set settings->block_size which will be copied to segment settings below. ++ * integrity block size chosen based on device logical block size and ++ * file system block size. ++ */ ++ if (!_set_integrity_block_size(cmd, lv, settings, lbs_4k, lbs_512, pbs_4k, pbs_512)) ++ goto_bad; ++ ++ /* ++ * For each rimage, move its segments to a new rimage_iorig and give ++ * the rimage a new integrity segment. ++ */ ++ for (s = 0; s < area_count; s++) { ++ lv_image = seg_lv(seg_top, s); ++ ++ /* Not adding integrity to this image. */ ++ if (!imeta_lvs[s]) ++ continue; ++ ++ if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_INTEGRITY))) ++ goto_bad; ++ ++ log_debug("Adding integrity to raid image %s", lv_image->name); ++ ++ /* ++ * "lv_iorig" is a new LV with new id, but with the segments ++ * from "lv_image". "lv_image" keeps the existing name and id, ++ * but gets a new integrity segment, in place of the segments ++ * that were moved to lv_iorig. ++ */ ++ if (!(lv_iorig = insert_layer_for_lv(cmd, lv_image, INTEGRITY, "_iorig"))) ++ goto_bad; ++ ++ lv_image->status |= INTEGRITY; ++ ++ /* ++ * Set up the new first segment of lv_image as integrity. ++ */ ++ seg_image = first_seg(lv_image); ++ seg_image->segtype = segtype; ++ ++ lv_imeta = imeta_lvs[s]; ++ lv_imeta->status |= INTEGRITY_METADATA; ++ lv_set_hidden(lv_imeta); ++ seg_image->integrity_data_sectors = lv_image->size; ++ seg_image->integrity_meta_dev = lv_imeta; ++ seg_image->integrity_recalculate = 1; ++ ++ memcpy(&seg_image->integrity_settings, settings, sizeof(struct integrity_settings)); ++ set = &seg_image->integrity_settings; ++ ++ if (!set->mode[0]) ++ set->mode[0] = DEFAULT_MODE; ++ ++ if (!set->tag_size) ++ set->tag_size = DEFAULT_TAG_SIZE; ++ ++ if (!set->block_size) ++ set->block_size = DEFAULT_BLOCK_SIZE; ++ ++ if (!set->internal_hash) ++ set->internal_hash = DEFAULT_INTERNAL_HASH; ++ } ++ ++ if (is_active) { ++ log_debug("Writing VG and updating LV with new integrity LV %s", lv->name); ++ ++ /* vg_write(), suspend_lv(), vg_commit(), resume_lv() */ ++ if (!lv_update_and_reload(lv)) { ++ log_error("LV update and reload failed"); ++ goto_bad; ++ } ++ revert_meta_lvs = 0; ++ ++ } else { ++ log_debug("Writing VG with new integrity LV %s", lv->name); ++ ++ if (!vg_write(vg) || !vg_commit(vg)) ++ goto_bad; ++ ++ revert_meta_lvs = 0; ++ ++ /* ++ * This first activation includes "recalculate" which starts the ++ * kernel's recalculating (initialization) process. ++ */ ++ ++ log_debug("Activating to start integrity initialization for LV %s", lv->name); ++ ++ if (!activate_lv(cmd, lv)) { ++ log_error("Failed to activate integrity LV to initialize."); ++ goto_bad; ++ } ++ } ++ ++ /* ++ * Now that the device is being initialized, update the VG to clear ++ * integrity_recalculate so that subsequent activations will not ++ * include "recalculate" and restart initialization. ++ */ ++ ++ log_debug("Writing VG with initialized integrity LV %s", lv->name); ++ ++ for (s = 0; s < area_count; s++) { ++ lv_image = seg_lv(seg_top, s); ++ seg_image = first_seg(lv_image); ++ seg_image->integrity_recalculate = 0; ++ } ++ ++ if (!vg_write(vg) || !vg_commit(vg)) ++ goto_bad; ++ ++ return 1; ++ ++bad: ++ log_error("Failed to add integrity."); ++ ++ for (s = 0; s < revert_meta_lvs; s++) { ++ if (!lv_remove(imeta_lvs[s])) ++ log_error("New integrity metadata LV may require manual removal."); ++ } ++ ++ if (!vg_write(vg) || !vg_commit(vg)) ++ log_error("New integrity metadata LV may require manual removal."); ++ ++ return 0; ++} ++ ++/* ++ * This should rarely if ever be used. A command that adds integrity ++ * to an LV will activate and then clear the flag. If it fails before ++ * clearing the flag, then this function will be used by a subsequent ++ * activation to clear the flag. ++ */ ++void lv_clear_integrity_recalculate_metadata(struct logical_volume *lv) ++{ ++ struct volume_group *vg = lv->vg; ++ struct logical_volume *lv_image; ++ struct lv_segment *seg, *seg_image; ++ uint32_t s; ++ ++ seg = first_seg(lv); ++ ++ if (seg_is_raid(seg)) { ++ for (s = 0; s < seg->area_count; s++) { ++ lv_image = seg_lv(seg, s); ++ seg_image = first_seg(lv_image); ++ seg_image->integrity_recalculate = 0; ++ } ++ } else if (seg_is_integrity(seg)) { ++ seg->integrity_recalculate = 0; ++ } else { ++ log_error("Invalid LV type for clearing integrity"); ++ return; ++ } ++ ++ if (!vg_write(vg) || !vg_commit(vg)) { ++ log_warn("WARNING: failed to clear integrity recalculate flag for %s", ++ display_lvname(lv)); ++ } ++} ++ ++int lv_has_integrity_recalculate_metadata(struct logical_volume *lv) ++{ ++ struct logical_volume *lv_image; ++ struct lv_segment *seg, *seg_image; ++ uint32_t s; ++ int ret = 0; ++ ++ seg = first_seg(lv); ++ ++ if (seg_is_raid(seg)) { ++ for (s = 0; s < seg->area_count; s++) { ++ lv_image = seg_lv(seg, s); ++ seg_image = first_seg(lv_image); ++ ++ if (!seg_is_integrity(seg_image)) ++ continue; ++ if (seg_image->integrity_recalculate) ++ ret = 1; ++ } ++ } else if (seg_is_integrity(seg)) { ++ ret = seg->integrity_recalculate; ++ } ++ ++ return ret; ++} ++ ++int lv_raid_has_integrity(struct logical_volume *lv) ++{ ++ struct logical_volume *lv_image; ++ struct lv_segment *seg, *seg_image; ++ uint32_t s; ++ ++ seg = first_seg(lv); ++ ++ if (seg_is_raid(seg)) { ++ for (s = 0; s < seg->area_count; s++) { ++ lv_image = seg_lv(seg, s); ++ seg_image = first_seg(lv_image); ++ ++ if (seg_is_integrity(seg_image)) ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ ++int lv_get_raid_integrity_settings(struct logical_volume *lv, struct integrity_settings **isettings) ++{ ++ struct logical_volume *lv_image; ++ struct lv_segment *seg, *seg_image; ++ uint32_t s; ++ ++ seg = first_seg(lv); ++ ++ if (seg_is_raid(seg)) { ++ for (s = 0; s < seg->area_count; s++) { ++ lv_image = seg_lv(seg, s); ++ seg_image = first_seg(lv_image); ++ ++ if (seg_is_integrity(seg_image)) { ++ *isettings = &seg_image->integrity_settings; ++ return 1; ++ } ++ } ++ } ++ ++ return 0; ++} ++ +diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c +index 17d4907..4ee58b4 100644 +--- a/lib/metadata/lv.c ++++ b/lib/metadata/lv.c +@@ -385,6 +385,17 @@ dm_percent_t lvseg_percent_with_info_and_seg_status(const struct lv_with_info_an + * Esentially rework _target_percent API for segtype. + */ + switch (s->type) { ++ case SEG_STATUS_INTEGRITY: ++ if (type != PERCENT_GET_DIRTY) ++ p = DM_PERCENT_INVALID; ++ else if (!s->integrity->recalc_sector) ++ p = DM_PERCENT_INVALID; ++ else if (s->integrity->recalc_sector == s->integrity->provided_data_sectors) ++ p = DM_PERCENT_100; ++ else ++ p = dm_make_percent(s->integrity->recalc_sector, ++ s->integrity->provided_data_sectors); ++ break; + case SEG_STATUS_CACHE: + if (s->cache->fail || s->cache->error) + p = DM_PERCENT_INVALID; +@@ -593,6 +604,8 @@ struct logical_volume *lv_origin_lv(const struct logical_volume *lv) + origin = first_seg(lv)->external_lv; + else if (lv_is_writecache(lv) && first_seg(lv)->origin) + origin = first_seg(lv)->origin; ++ else if (lv_is_integrity(lv) && first_seg(lv)->origin) ++ origin = first_seg(lv)->origin; + + return origin; + } +@@ -1208,10 +1221,13 @@ char *lv_attr_dup_with_info_and_seg_status(struct dm_pool *mem, const struct lv_ + repstr[0] = (lv_is_merging_origin(lv)) ? 'O' : 'o'; + else if (lv_is_pool_metadata(lv) || + lv_is_pool_metadata_spare(lv) || +- lv_is_raid_metadata(lv)) ++ lv_is_raid_metadata(lv) || ++ lv_is_integrity_metadata(lv)) + repstr[0] = 'e'; + else if (lv_is_cache_type(lv) || lv_is_writecache(lv)) + repstr[0] = 'C'; ++ else if (lv_is_integrity(lv)) ++ repstr[0] = 'g'; + else if (lv_is_raid(lv)) + repstr[0] = (lv_is_not_synced(lv)) ? 'R' : 'r'; + else if (lv_is_mirror(lv)) +diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c +index 3090a93..1311f70 100644 +--- a/lib/metadata/lv_manip.c ++++ b/lib/metadata/lv_manip.c +@@ -134,7 +134,9 @@ enum { + LV_TYPE_SANLOCK, + LV_TYPE_CACHEVOL, + LV_TYPE_WRITECACHE, +- LV_TYPE_WRITECACHEORIGIN ++ LV_TYPE_WRITECACHEORIGIN, ++ LV_TYPE_INTEGRITY, ++ LV_TYPE_INTEGRITYORIGIN + }; + + static const char *_lv_type_names[] = { +@@ -190,6 +192,8 @@ static const char *_lv_type_names[] = { + [LV_TYPE_CACHEVOL] = "cachevol", + [LV_TYPE_WRITECACHE] = "writecache", + [LV_TYPE_WRITECACHEORIGIN] = "writecacheorigin", ++ [LV_TYPE_INTEGRITY] = "integrity", ++ [LV_TYPE_INTEGRITYORIGIN] = "integrityorigin", + }; + + static int _lv_layout_and_role_mirror(struct dm_pool *mem, +@@ -461,6 +465,43 @@ bad: + return 0; + } + ++static int _lv_layout_and_role_integrity(struct dm_pool *mem, ++ const struct logical_volume *lv, ++ struct dm_list *layout, ++ struct dm_list *role, ++ int *public_lv) ++{ ++ int top_level = 0; ++ ++ /* non-top-level LVs */ ++ if (lv_is_integrity_metadata(lv)) { ++ if (!str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_INTEGRITY]) || ++ !str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_METADATA])) ++ goto_bad; ++ } else if (lv_is_integrity_origin(lv)) { ++ if (!str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_INTEGRITY]) || ++ !str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_ORIGIN]) || ++ !str_list_add_no_dup_check(mem, role, _lv_type_names[LV_TYPE_INTEGRITYORIGIN])) ++ goto_bad; ++ } else ++ top_level = 1; ++ ++ if (!top_level) { ++ *public_lv = 0; ++ return 1; ++ } ++ ++ /* top-level LVs */ ++ if (lv_is_integrity(lv)) { ++ if (!str_list_add_no_dup_check(mem, layout, _lv_type_names[LV_TYPE_INTEGRITY])) ++ goto_bad; ++ } ++ ++ return 1; ++bad: ++ return 0; ++} ++ + static int _lv_layout_and_role_thick_origin_snapshot(struct dm_pool *mem, + const struct logical_volume *lv, + struct dm_list *layout, +@@ -577,6 +618,11 @@ int lv_layout_and_role(struct dm_pool *mem, const struct logical_volume *lv, + !_lv_layout_and_role_cache(mem, lv, *layout, *role, &public_lv)) + goto_bad; + ++ /* Integrity related */ ++ if ((lv_is_integrity(lv) || lv_is_integrity_origin(lv) || lv_is_integrity_metadata(lv)) && ++ !_lv_layout_and_role_integrity(mem, lv, *layout, *role, &public_lv)) ++ goto_bad; ++ + /* VDO and related */ + if (lv_is_vdo_type(lv) && + !_lv_layout_and_role_vdo(mem, lv, *layout, *role, &public_lv)) +@@ -1457,6 +1503,15 @@ static int _lv_reduce(struct logical_volume *lv, uint32_t extents, int delete) + return_0; + } + ++ if (delete && seg_is_integrity(seg)) { ++ /* Remove integrity origin in addition to integrity layer. */ ++ if (!lv_remove(seg_lv(seg, 0))) ++ return_0; ++ /* Remove integrity metadata. */ ++ if (seg->integrity_meta_dev && !lv_remove(seg->integrity_meta_dev)) ++ return_0; ++ } ++ + if ((pool_lv = seg->pool_lv)) { + if (!detach_pool_lv(seg)) + return_0; +@@ -4111,11 +4166,14 @@ static int _lv_extend_layered_lv(struct alloc_handle *ah, + uint32_t extents, uint32_t first_area, + uint32_t mirrors, uint32_t stripes, uint32_t stripe_size) + { ++ struct logical_volume *sub_lvs[DEFAULT_RAID_MAX_IMAGES]; + const struct segment_type *segtype; +- struct logical_volume *sub_lv, *meta_lv; ++ struct logical_volume *meta_lv, *sub_lv; + struct lv_segment *seg = first_seg(lv); ++ struct lv_segment *sub_lv_seg; + uint32_t fa, s; + int clear_metadata = 0; ++ int integrity_sub_lvs = 0; + uint32_t area_multiple = 1; + + if (!(segtype = get_segtype_from_string(lv->vg->cmd, SEG_TYPE_NAME_STRIPED))) +@@ -4133,16 +4191,28 @@ static int _lv_extend_layered_lv(struct alloc_handle *ah, + area_multiple = seg->area_count; + } + ++ for (s = 0; s < seg->area_count; s++) { ++ sub_lv = seg_lv(seg, s); ++ sub_lv_seg = sub_lv ? first_seg(sub_lv) : NULL; ++ ++ if (sub_lv_seg && seg_is_integrity(sub_lv_seg)) { ++ sub_lvs[s] = seg_lv(sub_lv_seg, 0); ++ integrity_sub_lvs = 1; ++ } else ++ sub_lvs[s] = sub_lv; ++ } ++ + for (fa = first_area, s = 0; s < seg->area_count; s++) { +- if (is_temporary_mirror_layer(seg_lv(seg, s))) { +- if (!_lv_extend_layered_lv(ah, seg_lv(seg, s), extents / area_multiple, ++ sub_lv = sub_lvs[s]; ++ ++ if (is_temporary_mirror_layer(sub_lv)) { ++ if (!_lv_extend_layered_lv(ah, sub_lv, extents / area_multiple, + fa, mirrors, stripes, stripe_size)) + return_0; +- fa += lv_mirror_count(seg_lv(seg, s)); ++ fa += lv_mirror_count(sub_lv); + continue; + } + +- sub_lv = seg_lv(seg, s); + if (!lv_add_segment(ah, fa, stripes, sub_lv, segtype, + stripe_size, sub_lv->status, 0)) { + log_error("Aborting. Failed to extend %s in %s.", +@@ -4184,6 +4254,41 @@ static int _lv_extend_layered_lv(struct alloc_handle *ah, + fa += stripes; + } + ++ /* ++ * In raid+integrity, the lv_iorig raid images have been extended above. ++ * Now propagate the new lv_iorig sizes up to the integrity LV layers ++ * that are referencing the lv_iorig. ++ */ ++ if (integrity_sub_lvs) { ++ for (s = 0; s < seg->area_count; s++) { ++ struct logical_volume *lv_image; ++ struct logical_volume *lv_iorig; ++ struct logical_volume *lv_imeta; ++ struct lv_segment *seg_image; ++ ++ lv_image = seg_lv(seg, s); ++ seg_image = first_seg(lv_image); ++ ++ if (!(lv_imeta = seg_image->integrity_meta_dev)) { ++ log_error("1"); ++ return_0; ++ } ++ ++ if (!(lv_iorig = seg_lv(seg_image, 0))) { ++ log_error("2"); ++ return_0; ++ } ++ ++ /* new size in sectors */ ++ lv_image->size = lv_iorig->size; ++ seg_image->integrity_data_sectors = lv_iorig->size; ++ /* new size in extents */ ++ lv_image->le_count = lv_iorig->le_count; ++ seg_image->len = lv_iorig->le_count; ++ seg_image->area_len = lv_iorig->le_count; ++ } ++ } ++ + seg->len += extents; + if (seg_is_raid(seg)) + seg->area_len = seg->len; +@@ -4345,6 +4450,13 @@ int lv_extend(struct logical_volume *lv, + mirrors, stripes, stripe_size))) + goto_out; + ++ if (lv_raid_has_integrity(lv)) { ++ if (!lv_extend_integrity_in_raid(lv, allocatable_pvs)) { ++ r = 0; ++ goto_out; ++ } ++ } ++ + /* + * If we are expanding an existing mirror, we can skip the + * resync of the extension if the LV is currently in-sync +@@ -4538,6 +4650,9 @@ static int _for_each_sub_lv(struct logical_volume *lv, int level, + if (!_for_each_sub_lv(seg->writecache, level, fn, data)) + return_0; + ++ if (!_for_each_sub_lv(seg->integrity_meta_dev, level, fn, data)) ++ return_0; ++ + for (s = 0; s < seg->area_count; s++) { + if (seg_type(seg, s) != AREA_LV) + continue; +@@ -5064,6 +5179,12 @@ static int _lvresize_check(struct logical_volume *lv, + return 0; + } + ++ if (lv_is_integrity(lv) || lv_raid_has_integrity(lv)) { ++ if (lp->resize == LV_REDUCE) { ++ log_error("Cannot reduce LV with integrity."); ++ return 0; ++ } ++ } + return 1; + } + +@@ -5613,6 +5734,9 @@ static int _lvresize_prepare(struct logical_volume **lv, + if (lv_is_thin_pool(*lv) || lv_is_vdo_pool(*lv)) + *lv = seg_lv(first_seg(*lv), 0); /* switch to data LV */ + ++ if (lv_is_integrity(*lv)) ++ *lv = seg_lv(first_seg(*lv), 0); ++ + /* Resolve extents from size */ + if (lp->size && !_lvresize_adjust_size(vg, lp->size, lp->sign, &lp->extents)) + return_0; +@@ -7948,6 +8072,11 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg, + /* FIXME Eventually support raid/mirrors with -m */ + if (!(create_segtype = get_segtype_from_string(vg->cmd, SEG_TYPE_NAME_STRIPED))) + return_0; ++ ++ } else if (seg_is_integrity(lp)) { ++ if (!(create_segtype = get_segtype_from_string(vg->cmd, SEG_TYPE_NAME_STRIPED))) ++ return_0; ++ + } else if (seg_is_mirrored(lp) || (seg_is_raid(lp) && !seg_is_any_raid0(lp))) { + if (!(lp->region_size = adjusted_mirror_region_size(vg->cmd, + vg->extent_size, +@@ -8198,6 +8327,15 @@ static struct logical_volume *_lv_create_an_lv(struct volume_group *vg, + goto out; + } + ++ if (seg_is_raid(lp) && lp->raidintegrity) { ++ log_debug("Adding integrity to new LV"); ++ ++ if (!lv_add_integrity_to_raid(lv, &lp->integrity_settings, lp->pvh, NULL)) ++ goto revert_new_lv; ++ ++ backup(vg); ++ } ++ + /* Do not scan this LV until properly zeroed/wiped. */ + if (_should_wipe_lv(lp, lv, 0)) + lv->status |= LV_NOSCAN; +diff --git a/lib/metadata/merge.c b/lib/metadata/merge.c +index 11b26b4..ecd55ef 100644 +--- a/lib/metadata/merge.c ++++ b/lib/metadata/merge.c +@@ -742,6 +742,8 @@ int check_lv_segments(struct logical_volume *lv, int complete_vg) + seg_found++; + if (seg->metadata_lv == lv || seg->pool_lv == lv || seg->writecache == lv) + seg_found++; ++ if (seg->integrity_meta_dev == lv) ++ seg_found++; + if (seg_is_thin_volume(seg) && (seg->origin == lv || seg->external_lv == lv)) + seg_found++; + +diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h +index 35c1231..52bc776 100644 +--- a/lib/metadata/metadata-exported.h ++++ b/lib/metadata/metadata-exported.h +@@ -84,12 +84,14 @@ + #define CONVERTING UINT64_C(0x0000000000400000) /* LV */ + + #define MISSING_PV UINT64_C(0x0000000000800000) /* PV */ ++#define INTEGRITY UINT64_C(0x0000000000800000) /* LV - Internal use only */ + #define PV_MOVED_VG UINT64_C(0x4000000000000000) /* PV - Moved to a new VG */ + #define PARTIAL_LV UINT64_C(0x0000000001000000) /* LV - derived flag, not + written out in metadata*/ + + //#define POSTORDER_FLAG UINT64_C(0x0000000002000000) /* Not real flags, reserved for + //#define POSTORDER_OPEN_FLAG UINT64_C(0x0000000004000000) temporary use inside vg_read_internal. */ ++#define INTEGRITY_METADATA UINT64_C(0x0000000004000000) /* LV - Internal use only */ + #define VIRTUAL_ORIGIN UINT64_C(0x0000000008000000) /* LV - internal use only */ + + #define MERGING UINT64_C(0x0000000010000000) /* LV SEG */ +@@ -261,6 +263,8 @@ + #define lv_is_pool_metadata_spare(lv) (((lv)->status & POOL_METADATA_SPARE) ? 1 : 0) + #define lv_is_lockd_sanlock_lv(lv) (((lv)->status & LOCKD_SANLOCK_LV) ? 1 : 0) + #define lv_is_writecache(lv) (((lv)->status & WRITECACHE) ? 1 : 0) ++#define lv_is_integrity(lv) (((lv)->status & INTEGRITY) ? 1 : 0) ++#define lv_is_integrity_metadata(lv) (((lv)->status & INTEGRITY_METADATA) ? 1 : 0) + + #define lv_is_vdo(lv) (((lv)->status & LV_VDO) ? 1 : 0) + #define lv_is_vdo_pool(lv) (((lv)->status & LV_VDO_POOL) ? 1 : 0) +@@ -272,9 +276,11 @@ + /* Recognize component LV (matching lib/misc/lvm-string.c _lvname_has_reserved_component_string()) */ + #define lv_is_component(lv) (lv_is_cache_origin(lv) || \ + lv_is_writecache_origin(lv) || \ ++ lv_is_integrity_origin(lv) || \ + ((lv)->status & (\ + CACHE_POOL_DATA |\ + CACHE_POOL_METADATA |\ ++ INTEGRITY_METADATA |\ + LV_CACHE_VOL |\ + LV_VDO_POOL_DATA |\ + MIRROR_IMAGE |\ +@@ -519,6 +525,11 @@ struct lv_segment { + uint32_t writecache_block_size; /* For writecache */ + struct writecache_settings writecache_settings; /* For writecache */ + ++ uint64_t integrity_data_sectors; ++ struct logical_volume *integrity_meta_dev; ++ struct integrity_settings integrity_settings; ++ uint32_t integrity_recalculate; ++ + struct dm_vdo_target_params vdo_params; /* For VDO-pool */ + uint32_t vdo_pool_header_size; /* For VDO-pool */ + uint32_t vdo_pool_virtual_extents; /* For VDO-pool */ +@@ -992,6 +1003,10 @@ struct lvcreate_params { + alloc_policy_t alloc; /* all */ + struct dm_vdo_target_params vdo_params; /* vdo */ + ++ int raidintegrity; ++ const char *raidintegritymode; ++ struct integrity_settings integrity_settings; ++ + struct dm_list tags; /* all */ + + int yes; +@@ -1086,6 +1101,8 @@ int lv_is_cache_origin(const struct logical_volume *lv); + int lv_is_writecache_origin(const struct logical_volume *lv); + int lv_is_writecache_cachevol(const struct logical_volume *lv); + ++int lv_is_integrity_origin(const struct logical_volume *lv); ++ + int lv_is_merging_cow(const struct logical_volume *cow); + uint32_t cow_max_extents(const struct logical_volume *origin, uint32_t chunk_size); + int cow_has_min_chunks(const struct volume_group *vg, uint32_t cow_extents, uint32_t chunk_size); +@@ -1389,4 +1406,13 @@ struct dm_list *create_pv_list(struct dm_pool *mem, struct volume_group *vg, int + char **argv, int allocatable_only); + struct dm_list *clone_pv_list(struct dm_pool *mem, struct dm_list *pvsl); + ++int lv_add_integrity_to_raid(struct logical_volume *lv, struct integrity_settings *settings, struct dm_list *pvh, ++ struct logical_volume *lv_imeta_0); ++int lv_remove_integrity_from_raid(struct logical_volume *lv); ++void lv_clear_integrity_recalculate_metadata(struct logical_volume *lv); ++int lv_has_integrity_recalculate_metadata(struct logical_volume *lv); ++int lv_raid_has_integrity(struct logical_volume *lv); ++int lv_extend_integrity_in_raid(struct logical_volume *lv, struct dm_list *pvh); ++int lv_get_raid_integrity_settings(struct logical_volume *lv, struct integrity_settings **isettings); ++ + #endif +diff --git a/lib/metadata/raid_manip.c b/lib/metadata/raid_manip.c +index fa1b91a..3b3e1d3 100644 +--- a/lib/metadata/raid_manip.c ++++ b/lib/metadata/raid_manip.c +@@ -3119,6 +3119,11 @@ static int _raid_remove_images(struct logical_volume *lv, int yes, + + /* Convert to linear? */ + if (new_count == 1) { ++ if (lv_raid_has_integrity(lv)) { ++ log_error("Integrity must be removed before converting raid to linear."); ++ return 0; ++ } ++ + if (!yes && yes_no_prompt("Are you sure you want to convert %s LV %s to type %s losing all resilience? [y/n]: ", + lvseg_name(first_seg(lv)), display_lvname(lv), SEG_TYPE_NAME_LINEAR) == 'n') { + log_error("Logical volume %s NOT converted to \"%s\".", +@@ -3265,6 +3270,11 @@ int lv_raid_split(struct logical_volume *lv, int yes, const char *split_name, + return 0; + } + ++ if (lv_raid_has_integrity(lv)) { ++ log_error("Integrity must be removed before splitting."); ++ return 0; ++ } ++ + if ((old_count - new_count) != 1) { + log_error("Unable to split more than one image from %s.", + display_lvname(lv)); +@@ -3328,9 +3338,11 @@ int lv_raid_split(struct logical_volume *lv, int yes, const char *split_name, + } + + /* Convert to linear? */ +- if ((new_count == 1) && !_raid_remove_top_layer(lv, &removal_lvs)) { +- log_error("Failed to remove RAID layer after linear conversion."); +- return 0; ++ if (new_count == 1) { ++ if (!_raid_remove_top_layer(lv, &removal_lvs)) { ++ log_error("Failed to remove RAID layer after linear conversion."); ++ return 0; ++ } + } + + /* Get first item */ +@@ -3432,6 +3444,11 @@ int lv_raid_split_and_track(struct logical_volume *lv, + return 0; + } + ++ if (lv_raid_has_integrity(lv)) { ++ log_error("Integrity must be removed before splitting."); ++ return 0; ++ } ++ + if (!seg_is_mirrored(seg)) { + log_error("Unable to split images from non-mirrored RAID."); + return 0; +@@ -6727,7 +6744,17 @@ static int _lv_raid_rebuild_or_replace(struct logical_volume *lv, + struct lv_segment *raid_seg = first_seg(lv); + struct lv_list *lvl; + char *tmp_names[raid_seg->area_count * 2]; ++ char tmp_name_buf[NAME_LEN]; ++ char *tmp_name_dup; + const char *action_str = rebuild ? "rebuild" : "replace"; ++ int has_integrity; ++ ++ if ((has_integrity = lv_raid_has_integrity(lv))) { ++ if (rebuild) { ++ log_error("Can't rebuild raid with integrity."); ++ return 0; ++ } ++ } + + if (seg_is_any_raid0(raid_seg)) { + log_error("Can't replace any devices in %s LV %s.", +@@ -6992,6 +7019,15 @@ try_again: + tmp_names[s] = tmp_names[sd] = NULL; + } + ++ /* Add integrity layer to any new images. */ ++ if (has_integrity) { ++ struct integrity_settings *isettings = NULL; ++ if (!lv_get_raid_integrity_settings(lv, &isettings)) ++ return_0; ++ if (!lv_add_integrity_to_raid(lv, isettings, NULL, NULL)) ++ return_0; ++ } ++ + skip_alloc: + if (!lv_update_and_reload_origin(lv)) + return_0; +@@ -7014,9 +7050,43 @@ skip_alloc: + if (!rebuild) + for (s = 0; s < raid_seg->area_count; s++) { + sd = s + raid_seg->area_count; ++ + if (tmp_names[s] && tmp_names[sd]) { +- seg_metalv(raid_seg, s)->name = tmp_names[s]; +- seg_lv(raid_seg, s)->name = tmp_names[sd]; ++ struct logical_volume *lv_image = seg_lv(raid_seg, s); ++ struct logical_volume *lv_rmeta = seg_metalv(raid_seg, s); ++ ++ lv_rmeta->name = tmp_names[s]; ++ lv_image->name = tmp_names[sd]; ++ ++ if (lv_is_integrity(lv_image)) { ++ struct logical_volume *lv_imeta; ++ struct logical_volume *lv_iorig; ++ struct lv_segment *seg_image; ++ ++ seg_image = first_seg(lv_image); ++ lv_imeta = seg_image->integrity_meta_dev; ++ lv_iorig = seg_lv(seg_image, 0); ++ ++ if (dm_snprintf(tmp_name_buf, NAME_LEN, "%s_imeta", lv_image->name) < 0) { ++ stack; ++ continue; ++ } ++ if (!(tmp_name_dup = dm_pool_strdup(lv->vg->vgmem, tmp_name_buf))) { ++ stack; ++ continue; ++ } ++ lv_imeta->name = tmp_name_dup; ++ ++ if (dm_snprintf(tmp_name_buf, NAME_LEN, "%s_iorig", lv_image->name) < 0) { ++ stack; ++ continue; ++ } ++ if (!(tmp_name_dup = dm_pool_strdup(lv->vg->vgmem, tmp_name_buf))) { ++ stack; ++ continue; ++ } ++ lv_iorig->name = tmp_name_dup; ++ } + } + } + +@@ -7192,6 +7262,11 @@ int partial_raid_lv_supports_degraded_activation(const struct logical_volume *cl + { + int not_capable = 0; + struct logical_volume * lv = (struct logical_volume *)clv; /* drop const */ ++ ++ if (lv_raid_has_integrity(lv)) { ++ log_error("Integrity must be removed before degraded or partial activation of raid."); ++ return 0; ++ } + + if (!_lv_may_be_activated_in_degraded_mode(lv, ¬_capable) || not_capable) + return_0; +diff --git a/lib/metadata/segtype.h b/lib/metadata/segtype.h +index 22a511e..08ddc35 100644 +--- a/lib/metadata/segtype.h ++++ b/lib/metadata/segtype.h +@@ -67,6 +67,7 @@ struct dev_manager; + #define SEG_RAID6_N_6 (1ULL << 35) + #define SEG_RAID6 SEG_RAID6_ZR + #define SEG_WRITECACHE (1ULL << 36) ++#define SEG_INTEGRITY (1ULL << 37) + + #define SEG_STRIPED_TARGET (1ULL << 39) + #define SEG_LINEAR_TARGET (1ULL << 40) +@@ -84,6 +85,7 @@ struct dev_manager; + #define SEG_TYPE_NAME_CACHE "cache" + #define SEG_TYPE_NAME_CACHE_POOL "cache-pool" + #define SEG_TYPE_NAME_WRITECACHE "writecache" ++#define SEG_TYPE_NAME_INTEGRITY "integrity" + #define SEG_TYPE_NAME_ERROR "error" + #define SEG_TYPE_NAME_FREE "free" + #define SEG_TYPE_NAME_ZERO "zero" +@@ -117,6 +119,7 @@ struct dev_manager; + #define segtype_is_cache(segtype) ((segtype)->flags & SEG_CACHE ? 1 : 0) + #define segtype_is_cache_pool(segtype) ((segtype)->flags & SEG_CACHE_POOL ? 1 : 0) + #define segtype_is_writecache(segtype) ((segtype)->flags & SEG_WRITECACHE ? 1 : 0) ++#define segtype_is_integrity(segtype) ((segtype)->flags & SEG_INTEGRITY ? 1 : 0) + #define segtype_is_mirrored(segtype) ((segtype)->flags & SEG_AREAS_MIRRORED ? 1 : 0) + #define segtype_is_mirror(segtype) ((segtype)->flags & SEG_MIRROR ? 1 : 0) + #define segtype_is_pool(segtype) ((segtype)->flags & (SEG_CACHE_POOL | SEG_THIN_POOL) ? 1 : 0) +@@ -179,6 +182,7 @@ struct dev_manager; + #define seg_is_cache(seg) segtype_is_cache((seg)->segtype) + #define seg_is_cache_pool(seg) segtype_is_cache_pool((seg)->segtype) + #define seg_is_writecache(seg) segtype_is_writecache((seg)->segtype) ++#define seg_is_integrity(seg) segtype_is_integrity((seg)->segtype) + #define seg_is_used_cache_pool(seg) (seg_is_cache_pool(seg) && (!dm_list_empty(&(seg->lv)->segs_using_this_lv))) + #define seg_is_linear(seg) (seg_is_striped(seg) && ((seg)->area_count == 1)) + #define seg_is_mirror(seg) segtype_is_mirror((seg)->segtype) +@@ -347,6 +351,8 @@ int init_vdo_segtypes(struct cmd_context *cmd, struct segtype_library *seglib); + + int init_writecache_segtypes(struct cmd_context *cmd, struct segtype_library *seglib); + ++int init_integrity_segtypes(struct cmd_context *cmd, struct segtype_library *seglib); ++ + #define CACHE_FEATURE_POLICY_MQ (1U << 0) + #define CACHE_FEATURE_POLICY_SMQ (1U << 1) + #define CACHE_FEATURE_METADATA2 (1U << 2) +diff --git a/lib/metadata/snapshot_manip.c b/lib/metadata/snapshot_manip.c +index 64e27ae..3faea0e 100644 +--- a/lib/metadata/snapshot_manip.c ++++ b/lib/metadata/snapshot_manip.c +@@ -387,6 +387,8 @@ int validate_snapshot_origin(const struct logical_volume *origin_lv) + } + } else if (lv_is_raid_type(origin_lv) && !lv_is_raid(origin_lv)) { + err = "raid subvolumes"; ++ } else if (lv_is_raid(origin_lv) && lv_raid_has_integrity((struct logical_volume *)origin_lv)) { ++ err = "raid with integrity"; + } else if (lv_is_writecache(origin_lv)) { + err = "writecache"; + } +diff --git a/lib/misc/lvm-string.c b/lib/misc/lvm-string.c +index 0ee3403..959a6a1 100644 +--- a/lib/misc/lvm-string.c ++++ b/lib/misc/lvm-string.c +@@ -166,7 +166,9 @@ static const char *_lvname_has_reserved_component_string(const char *lvname) + "_rmeta", + "_tdata", + "_tmeta", +- "_vdata" ++ "_vdata", ++ "_imeta", ++ "_iorig" + }; + unsigned i; + +diff --git a/lib/report/report.c b/lib/report/report.c +index d379e2a..170df69 100644 +--- a/lib/report/report.c ++++ b/lib/report/report.c +@@ -3173,7 +3173,7 @@ static int _copypercent_disp(struct dm_report *rh, + dm_percent_t percent = DM_PERCENT_INVALID; + + /* TODO: just cache passes through lvseg_percent... */ +- if (lv_is_cache(lv) || lv_is_used_cache_pool(lv) || ++ if (lv_is_integrity(lv) || lv_is_cache(lv) || lv_is_used_cache_pool(lv) || + (!lv_is_merging_origin(lv) && lv_is_raid(lv) && !seg_is_any_raid0(first_seg(lv)))) + percent = lvseg_percent_with_info_and_seg_status(lvdm, PERCENT_GET_DIRTY); + else if (lv_is_raid(lv) && !seg_is_any_raid0(first_seg(lv))) +diff --git a/man/lvmraid.7_main b/man/lvmraid.7_main +index 498de90..aedd16a 100644 +--- a/man/lvmraid.7_main ++++ b/man/lvmraid.7_main +@@ -785,6 +785,89 @@ configuration file itself. + activation_mode + + ++.SH Data Integrity ++ ++The device mapper integrity target can be used in combination with RAID ++levels 1,4,5,6,10 to detect and correct data corruption in RAID images. A ++dm-integrity layer is placed above each RAID image, and an extra sub LV is ++created to hold integrity metadata (data checksums) for each RAID image. ++When data is read from an image, integrity checksums are used to detect ++corruption. If detected, dm-raid reads the data from another (good) image ++to return to the caller. dm-raid will also automatically write the good ++data back to the image with bad data to correct the corruption. ++ ++When creating a RAID LV with integrity, or adding integrity, space is ++required for integrity metadata. Every 500MB of LV data requires an ++additional 4MB to be allocated for integrity metadata, for each RAID ++image. ++ ++Create a RAID LV with integrity: ++ ++.B lvcreate \-\-type raidN \-\-raidintegrity y ++ ++Add integrity to an existing RAID LV: ++ ++.B lvconvert --raidintegrity y ++.I LV ++ ++Remove integrity from a RAID LV: ++ ++.B lvconvert --raidintegrity n ++.I LV ++ ++.SS Integrity options ++ ++.B --raidintegritymode journal|bitmap ++ ++Use a journal (default) or bitmap for keeping integrity checksums ++consistent in case of a crash. The bitmap areas are recalculated after a ++crash, so corruption in those areas would not be detected. A journal does ++not have this problem. The journal mode doubles writes to storage, but ++can improve performance for scattered writes packed into a single journal ++write. bitmap mode can in theory achieve full write throughput of the ++device, but would not benefit from the potential scattered write ++optimization. ++ ++.B --raidintegrityblocksize 512|1024|2048|4096 ++ ++The block size to use for dm-integrity on raid images. The integrity ++block size should usually match the device logical block size, or the file ++system sector/block sizes. It may be less than the file system ++sector/block size, but not less than the device logical block size. ++Possible values: 512, 1024, 2048, 4096. ++ ++.SS Integrity initialization ++ ++When integrity is added to an LV, the kernel needs to initialize the ++integrity metadata (checksums) for all blocks in the LV. The data ++corruption checking performed by dm-integrity will only operate on areas ++of the LV that are already initialized. The progress of integrity ++initialization is reported by the "syncpercent" LV reporting field (and ++under the Cpy%Sync lvs column.) ++ ++.SS Integrity limitations ++ ++To work around some limitations, it is possible to remove integrity from ++the LV, make the change, then add integrity again. (Integrity metadata ++would need to initialized when added again.) ++ ++LVM must be able to allocate the integrity metadata sub LV on a single PV ++that is already in use by the associated RAID image. This can potentially ++cause a problem during lvextend if the original PV holding the image and ++integrity metadata is full. To work around this limitation, remove ++integrity, extend the LV, and add integrity again. ++ ++Additional RAID images can be added to raid1 LVs, but not to other raid ++levels. ++ ++A raid1 LV with integrity cannot be converted to linear (remove integrity ++to do this.) ++ ++RAID LVs with integrity cannot yet be used as sub LVs with other LV types. ++ ++The following are not yet permitted on RAID LVs with integrity: lvreduce, ++pvmove, snapshots, splitmirror, raid syncaction commands, raid rebuild. ++ + .SH RAID1 Tuning + + A RAID1 LV can be tuned so that certain devices are avoided for reading +diff --git a/test/lib/aux.sh b/test/lib/aux.sh +index 83a88a6..e40da95 100644 +--- a/test/lib/aux.sh ++++ b/test/lib/aux.sh +@@ -1563,6 +1563,14 @@ have_writecache() { + target_at_least dm-writecache "$@" + } + ++have_integrity() { ++ lvm segtypes 2>/dev/null | grep -q integrity$ || { ++ echo "integrity is not built-in." >&2 ++ return 1 ++ } ++ target_at_least dm-integrity "$@" ++} ++ + have_raid() { + target_at_least dm-raid "$@" + +diff --git a/test/shell/integrity-blocksize.sh b/test/shell/integrity-blocksize.sh +new file mode 100644 +index 0000000..444e3db +--- /dev/null ++++ b/test/shell/integrity-blocksize.sh +@@ -0,0 +1,183 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux have_integrity 1 5 0 || skip ++ ++losetup -h | grep sector-size || skip ++ ++# Tests with fs block sizes require a libblkid version that shows BLOCK_SIZE ++aux prepare_devs 1 ++vgcreate $vg "$dev1" ++lvcreate -n $lv1 -l8 $vg ++mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++blkid "$DM_DEV_DIR/$vg/$lv1" | grep BLOCK_SIZE || skip ++lvchange -an $vg ++vgremove -ff $vg ++ ++dd if=/dev/zero of=loopa bs=$((1024*1024)) count=64 2> /dev/null ++dd if=/dev/zero of=loopb bs=$((1024*1024)) count=64 2> /dev/null ++dd if=/dev/zero of=loopc bs=$((1024*1024)) count=64 2> /dev/null ++dd if=/dev/zero of=loopd bs=$((1024*1024)) count=64 2> /dev/null ++LOOP1=$(losetup -f loopa --show) ++LOOP2=$(losetup -f loopb --show) ++LOOP3=$(losetup -f loopc --sector-size 4096 --show) ++LOOP4=$(losetup -f loopd --sector-size 4096 --show) ++ ++echo $LOOP1 ++echo $LOOP2 ++echo $LOOP3 ++echo $LOOP4 ++ ++aux extend_filter "a|$LOOP1|" ++aux extend_filter "a|$LOOP2|" ++aux extend_filter "a|$LOOP3|" ++aux extend_filter "a|$LOOP4|" ++ ++aux lvmconf 'devices/scan = "/dev"' ++ ++vgcreate $vg1 $LOOP1 $LOOP2 ++vgcreate $vg2 $LOOP3 $LOOP4 ++ ++# lvcreate on dev512, result 512 ++lvcreate --type raid1 -m1 --raidintegrity y -l 8 -n $lv1 $vg1 ++pvck --dump metadata $LOOP1 | grep 'block_size = 512' ++lvremove -y $vg1/$lv1 ++ ++# lvcreate on dev4k, result 4k ++lvcreate --type raid1 -m1 --raidintegrity y -l 8 -n $lv1 $vg2 ++pvck --dump metadata $LOOP3 | grep 'block_size = 4096' ++lvremove -y $vg2/$lv1 ++ ++# lvcreate --bs 512 on dev4k, result fail ++not lvcreate --type raid1 -m1 --raidintegrity y --raidintegrityblocksize 512 -l 8 -n $lv1 $vg2 ++ ++# lvcreate --bs 4096 on dev512, result 4k ++lvcreate --type raid1 -m1 --raidintegrity y --raidintegrityblocksize 4096 -l 8 -n $lv1 $vg1 ++pvck --dump metadata $LOOP1 | grep 'block_size = 4096' ++lvremove -y $vg1/$lv1 ++ ++# Test an unknown fs block size by simply not creating a fs on the lv. ++ ++# lvconvert on dev512, fsunknown, result 512 ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 ++# clear any residual fs so that libblkid cannot find an fs block size ++aux wipefs_a /dev/$vg1/$lv1 ++lvconvert --raidintegrity y $vg1/$lv1 ++pvck --dump metadata $LOOP1 | grep 'block_size = 512' ++lvremove -y $vg1/$lv1 ++ ++# lvconvert on dev4k, fsunknown, result 4k ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg2 ++# clear any residual fs so that libblkid cannot find an fs block size ++aux wipefs_a /dev/$vg2/$lv1 ++lvconvert --raidintegrity y $vg2/$lv1 ++pvck --dump metadata $LOOP3 | grep 'block_size = 4096' ++lvremove -y $vg2/$lv1 ++ ++# lvconvert --bs 4k on dev512, fsunknown, result fail ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 ++# clear any residual fs so that libblkid cannot find an fs block size ++aux wipefs_a /dev/$vg1/$lv1 ++not lvconvert --raidintegrity y --raidintegrityblocksize 4096 $vg1/$lv1 ++lvremove -y $vg1/$lv1 ++ ++# lvconvert --bs 512 on dev4k, fsunknown, result fail ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg2 ++# clear any residual fs so that libblkid cannot find an fs block size ++aux wipefs_a /dev/$vg2/$lv1 ++not lvconvert --raidintegrity y --raidintegrityblocksize 512 $vg2/$lv1 ++lvremove -y $vg2/$lv1 ++ ++# lvconvert on dev512, xfs 512, result 512 ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 ++aux wipefs_a /dev/$vg1/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg1/$lv1" ++lvconvert --raidintegrity y $vg1/$lv1 ++pvck --dump metadata $LOOP1 | grep 'block_size = 512' ++lvremove -y $vg1/$lv1 ++ ++# lvconvert on dev4k, xfs 4096, result 4096 ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg2 ++aux wipefs_a /dev/$vg2/$lv1 ++mkfs.xfs -f "$DM_DEV_DIR/$vg2/$lv1" ++lvconvert --raidintegrity y $vg2/$lv1 ++pvck --dump metadata $LOOP3 | grep 'block_size = 4096' ++lvremove -y $vg2/$lv1 ++ ++# lvconvert on dev512, ext4 1024, result 1024 ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 ++aux wipefs_a /dev/$vg1/$lv1 ++mkfs.ext4 -b 1024 "$DM_DEV_DIR/$vg1/$lv1" ++lvconvert --raidintegrity y $vg1/$lv1 ++pvck --dump metadata $LOOP1 | grep 'block_size = 1024' ++lvremove -y $vg1/$lv1 ++ ++# lvconvert on dev4k, ext4 4096, result 4096 ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg2 ++aux wipefs_a /dev/$vg2/$lv1 ++mkfs.ext4 "$DM_DEV_DIR/$vg2/$lv1" ++lvconvert --raidintegrity y $vg2/$lv1 ++pvck --dump metadata $LOOP3 | grep 'block_size = 4096' ++lvremove -y $vg2/$lv1 ++ ++# lvconvert --bs 512 on dev512, xfs 4096, result 512 ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 ++aux wipefs_a /dev/$vg1/$lv1 ++mkfs.xfs -f -s size=4096 "$DM_DEV_DIR/$vg1/$lv1" ++lvconvert --raidintegrity y --raidintegrityblocksize 512 $vg1/$lv1 ++pvck --dump metadata $LOOP1 | grep 'block_size = 512' ++lvremove -y $vg1/$lv1 ++ ++# lvconvert --bs 1024 on dev512, xfs 4096, result 1024 ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 ++aux wipefs_a /dev/$vg1/$lv1 ++mkfs.xfs -f -s size=4096 "$DM_DEV_DIR/$vg1/$lv1" ++lvconvert --raidintegrity y --raidintegrityblocksize 1024 $vg1/$lv1 ++pvck --dump metadata $LOOP1 | grep 'block_size = 1024' ++lvremove -y $vg1/$lv1 ++ ++# lvconvert --bs 512 on dev512, ext4 1024, result 512 ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg1 ++aux wipefs_a /dev/$vg1/$lv1 ++mkfs.ext4 -b 1024 "$DM_DEV_DIR/$vg1/$lv1" ++lvconvert --raidintegrity y --raidintegrityblocksize 512 $vg1/$lv1 ++pvck --dump metadata $LOOP1 | grep 'block_size = 512' ++lvremove -y $vg1/$lv1 ++ ++# lvconvert --bs 512 on dev4k, ext4 4096, result fail ++lvcreate --type raid1 -m1 -l 8 -n $lv1 $vg2 ++aux wipefs_a /dev/$vg2/$lv1 ++mkfs.ext4 "$DM_DEV_DIR/$vg2/$lv1" ++not lvconvert --raidintegrity y --raidintegrityblocksize 512 $vg2/$lv1 ++lvremove -y $vg2/$lv1 ++ ++# FIXME: need to use scsi_debug to create devs with LBS 512 PBS 4k ++# FIXME: lvconvert, fsunknown, LBS 512, PBS 4k: result 512 ++# FIXME: lvconvert --bs 512, fsunknown, LBS 512, PBS 4k: result 512 ++# FIXME: lvconvert --bs 4k, fsunknown, LBS 512, PBS 4k: result 4k ++ ++vgremove -ff $vg1 ++vgremove -ff $vg2 ++ ++losetup -d $LOOP1 ++losetup -d $LOOP2 ++losetup -d $LOOP3 ++losetup -d $LOOP4 ++rm loopa ++rm loopb ++rm loopc ++rm loopd ++ +diff --git a/test/shell/integrity-dmeventd.sh b/test/shell/integrity-dmeventd.sh +new file mode 100644 +index 0000000..58899ca +--- /dev/null ++++ b/test/shell/integrity-dmeventd.sh +@@ -0,0 +1,289 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux have_integrity 1 5 0 || skip ++which mkfs.xfs || skip ++ ++mnt="mnt" ++mkdir -p $mnt ++ ++aux prepare_devs 6 64 ++ ++for i in `seq 1 16384`; do echo -n "A" >> fileA; done ++for i in `seq 1 16384`; do echo -n "B" >> fileB; done ++for i in `seq 1 16384`; do echo -n "C" >> fileC; done ++ ++# generate random data ++dd if=/dev/urandom of=randA bs=512K count=2 ++dd if=/dev/urandom of=randB bs=512K count=3 ++dd if=/dev/urandom of=randC bs=512K count=4 ++ ++_prepare_vg() { ++ vgcreate $SHARED $vg "$dev1" "$dev2" "$dev3" "$dev4" ++ pvs ++} ++ ++_add_new_data_to_mnt() { ++ mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++ ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ # add original data ++ cp randA $mnt ++ cp randB $mnt ++ cp randC $mnt ++ mkdir $mnt/1 ++ cp fileA $mnt/1 ++ cp fileB $mnt/1 ++ cp fileC $mnt/1 ++ mkdir $mnt/2 ++ cp fileA $mnt/2 ++ cp fileB $mnt/2 ++ cp fileC $mnt/2 ++} ++ ++_add_more_data_to_mnt() { ++ mkdir $mnt/more ++ cp fileA $mnt/more ++ cp fileB $mnt/more ++ cp fileC $mnt/more ++ cp randA $mnt/more ++ cp randB $mnt/more ++ cp randC $mnt/more ++} ++ ++_verify_data_on_mnt() { ++ diff randA $mnt/randA ++ diff randB $mnt/randB ++ diff randC $mnt/randC ++ diff fileA $mnt/1/fileA ++ diff fileB $mnt/1/fileB ++ diff fileC $mnt/1/fileC ++ diff fileA $mnt/2/fileA ++ diff fileB $mnt/2/fileB ++ diff fileC $mnt/2/fileC ++} ++ ++_verify_data_on_lv() { ++ lvchange -ay $vg/$lv1 ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ _verify_data_on_mnt ++ rm $mnt/randA ++ rm $mnt/randB ++ rm $mnt/randC ++ rm -rf $mnt/1 ++ rm -rf $mnt/2 ++ umount $mnt ++ lvchange -an $vg/$lv1 ++} ++ ++_sync_percent() { ++ local checklv=$1 ++ get lv_field "$checklv" sync_percent | cut -d. -f1 ++} ++ ++_wait_recalc() { ++ local checklv=$1 ++ ++ for i in $(seq 1 10) ; do ++ sync=$(_sync_percent "$checklv") ++ echo "sync_percent is $sync" ++ ++ if test "$sync" = "100"; then ++ return ++ fi ++ ++ sleep 1 ++ done ++ ++ echo "timeout waiting for recalc" ++ return 1 ++} ++ ++aux lvmconf \ ++ 'activation/raid_fault_policy = "allocate"' ++ ++aux prepare_dmeventd ++ ++# raid1, one device fails, dmeventd calls repair ++ ++vgcreate $SHARED $vg "$dev1" "$dev2" "$dev3" "$dev4" ++lvcreate --type raid1 -m 2 --raidintegrity y --ignoremonitoring -l 8 -n $lv1 $vg "$dev1" "$dev2" "$dev3" ++lvchange --monitor y $vg/$lv1 ++lvs -a -o+devices $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 ++aux wait_for_sync $vg $lv1 ++_add_new_data_to_mnt ++ ++aux disable_dev "$dev2" ++ ++# wait for dmeventd to call lvconvert --repair which should ++# replace dev2 with dev4 ++sleep 5 ++ ++lvs -a -o+devices $vg > out ++cat out ++not grep "$dev2" out ++grep "$dev4" out ++ ++_add_more_data_to_mnt ++_verify_data_on_mnt ++ ++aux enable_dev "$dev2" ++ ++lvs -a -o+devices $vg > out ++cat out ++not grep "$dev2" out ++grep "$dev4" out ++grep "$dev1" out ++grep "$dev3" out ++ ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# raid1, two devices fail, dmeventd calls repair ++ ++vgcreate $SHARED $vg "$dev1" "$dev2" "$dev3" "$dev4" "$dev5" ++lvcreate --type raid1 -m 2 --raidintegrity y --ignoremonitoring -l 8 -n $lv1 $vg "$dev1" "$dev2" "$dev3" ++lvchange --monitor y $vg/$lv1 ++lvs -a -o+devices $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 ++aux wait_for_sync $vg $lv1 ++_add_new_data_to_mnt ++ ++aux disable_dev "$dev2" ++aux disable_dev "$dev1" ++ ++# wait for dmeventd to call lvconvert --repair which should ++# replace dev1 and dev2 with dev4 and dev5 ++sleep 5 ++ ++lvs -a -o+devices $vg > out ++cat out ++not grep "$dev1" out ++not grep "$dev2" out ++grep "$dev4" out ++grep "$dev5" out ++grep "$dev3" out ++ ++_add_more_data_to_mnt ++_verify_data_on_mnt ++ ++aux enable_dev "$dev1" ++aux enable_dev "$dev2" ++ ++lvs -a -o+devices $vg > out ++cat out ++not grep "$dev1" out ++not grep "$dev2" out ++grep "$dev4" out ++grep "$dev5" out ++grep "$dev3" out ++ ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# raid6, one device fails, dmeventd calls repair ++ ++vgcreate $SHARED $vg "$dev1" "$dev2" "$dev3" "$dev4" "$dev5" "$dev6" ++lvcreate --type raid6 --raidintegrity y --ignoremonitoring -l 8 -n $lv1 $vg "$dev1" "$dev2" "$dev3" "$dev4" "$dev5" ++lvchange --monitor y $vg/$lv1 ++lvs -a -o+devices $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 ++_wait_recalc $vg/${lv1}_rimage_3 ++_wait_recalc $vg/${lv1}_rimage_4 ++aux wait_for_sync $vg $lv1 ++_add_new_data_to_mnt ++ ++aux disable_dev "$dev2" ++ ++# wait for dmeventd to call lvconvert --repair which should ++# replace dev2 with dev6 ++sleep 5 ++ ++lvs -a -o+devices $vg > out ++cat out ++not grep "$dev2" out ++grep "$dev6" out ++ ++_add_more_data_to_mnt ++_verify_data_on_mnt ++ ++aux enable_dev "$dev2" ++ ++lvs -a -o+devices $vg > out ++cat out ++not grep "$dev2" out ++grep "$dev6" out ++ ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# raid10, one device fails, dmeventd calls repair ++ ++vgcreate $SHARED $vg "$dev1" "$dev2" "$dev3" "$dev4" "$dev5" ++lvcreate --type raid10 --raidintegrity y --ignoremonitoring -l 8 -n $lv1 $vg "$dev1" "$dev2" "$dev3" "$dev4" ++lvchange --monitor y $vg/$lv1 ++lvs -a -o+devices $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 ++_wait_recalc $vg/${lv1}_rimage_3 ++aux wait_for_sync $vg $lv1 ++_add_new_data_to_mnt ++ ++aux disable_dev "$dev1" ++ ++# wait for dmeventd to call lvconvert --repair which should ++# replace dev1 with dev5 ++sleep 5 ++ ++lvs -a -o+devices $vg > out ++cat out ++not grep "$dev1" out ++grep "$dev5" out ++ ++_add_more_data_to_mnt ++_verify_data_on_mnt ++ ++aux enable_dev "$dev1" ++ ++lvs -a -o+devices $vg > out ++cat out ++not grep "$dev1" out ++grep "$dev5" out ++ ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ +diff --git a/test/shell/integrity-large.sh b/test/shell/integrity-large.sh +new file mode 100644 +index 0000000..0c36e4d +--- /dev/null ++++ b/test/shell/integrity-large.sh +@@ -0,0 +1,175 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++# Test writecache usage ++ ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux have_integrity 1 5 0 || skip ++which mkfs.xfs || skip ++ ++mnt="mnt" ++mkdir -p $mnt ++ ++# raid1 LV needs to be extended to 512MB to test imeta being exended ++aux prepare_devs 4 600 ++ ++for i in `seq 1 16384`; do echo -n "A" >> fileA; done ++for i in `seq 1 16384`; do echo -n "B" >> fileB; done ++for i in `seq 1 16384`; do echo -n "C" >> fileC; done ++ ++# generate random data ++dd if=/dev/urandom of=randA bs=512K count=2 ++dd if=/dev/urandom of=randB bs=512K count=3 ++dd if=/dev/urandom of=randC bs=512K count=4 ++ ++_prepare_vg() { ++ vgcreate $SHARED $vg "$dev1" "$dev2" ++ pvs ++} ++ ++_add_data_to_lv() { ++ mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++ ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ # add original data ++ cp randA $mnt ++ cp randB $mnt ++ cp randC $mnt ++ mkdir $mnt/1 ++ cp fileA $mnt/1 ++ cp fileB $mnt/1 ++ cp fileC $mnt/1 ++ mkdir $mnt/2 ++ cp fileA $mnt/2 ++ cp fileB $mnt/2 ++ cp fileC $mnt/2 ++ ++ umount $mnt ++} ++ ++_verify_data_on_lv() { ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ diff randA $mnt/randA ++ diff randB $mnt/randB ++ diff randC $mnt/randC ++ diff fileA $mnt/1/fileA ++ diff fileB $mnt/1/fileB ++ diff fileC $mnt/1/fileC ++ diff fileA $mnt/2/fileA ++ diff fileB $mnt/2/fileB ++ diff fileC $mnt/2/fileC ++ ++ umount $mnt ++} ++ ++_sync_percent() { ++ local checklv=$1 ++ get lv_field "$checklv" sync_percent | cut -d. -f1 ++} ++ ++_wait_recalc() { ++ local checklv=$1 ++ ++ for i in $(seq 1 10) ; do ++ sync=$(_sync_percent "$checklv") ++ echo "sync_percent is $sync" ++ ++ if test "$sync" = "100"; then ++ return ++ fi ++ ++ sleep 1 ++ done ++ ++ echo "timeout waiting for recalc" ++ return 1 ++} ++ ++# lvextend to 512MB is needed for the imeta LV to ++# be extended from 4MB to 8MB. ++ ++_prepare_vg ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvchange -an $vg/$lv1 ++lvchange -ay $vg/$lv1 ++_add_data_to_lv ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_verify_data_on_lv ++lvchange -an $vg/$lv1 ++lvextend -L 512M $vg/$lv1 ++lvs -a -o+devices $vg ++lvchange -ay $vg/$lv1 ++_verify_data_on_lv ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++check lv_field $vg/${lv1}_rimage_0_imeta size "8.00m" ++check lv_field $vg/${lv1}_rimage_1_imeta size "8.00m" ++ ++# provide space to extend the images onto new devs ++vgextend $vg "$dev3" "$dev4" ++ ++# extending the images is possible using dev3,dev4 ++# but extending imeta on the existing dev1,dev2 fails ++not lvextend -L +512M $vg/$lv1 ++ ++# removing integrity will permit extending the images ++# using dev3,dev4 since imeta limitation is gone ++lvconvert --raidintegrity n $vg/$lv1 ++lvextend -L +512M $vg/$lv1 ++lvs -a -o+devices $vg ++ ++# adding integrity again will allocate new 12MB imeta LVs ++# on dev3,dev4 ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++check lv_field $vg/${lv1}_rimage_0_imeta size "12.00m" ++check lv_field $vg/${lv1}_rimage_1_imeta size "12.00m" ++ ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++ ++# this succeeds because dev1,dev2 can hold rmeta+rimage ++lvcreate --type raid1 -n $lv1 -L 592M -an $vg "$dev1" "$dev2" ++ ++# this fails because dev1,dev2 can hold rmeta+rimage, but not imeta ++# and we require imeta to be on same devs as rmeta/rimeta ++not lvcreate --type raid1 --raidintegrity y -n $lv1 -L 592M -an $vg "$dev1" "$dev2" ++lvs -a -o+devices $vg ++lvremove $vg/$lv1 ++ ++# this can allocate from more devs so there's enough space for imeta to ++# be allocated in the vg, but lvcreate fails because rmeta+rimage are ++# allocated from dev1,dev2, we restrict imeta to being allocated on the ++# same devs as rmeta/rimage, and dev1,dev2 can't fit imeta. ++not lvcreate --type raid1 --raidintegrity y -n $lv1 -L 592M -an $vg ++lvs -a -o+devices $vg ++ ++# counterintuitively, increasing the size will allow lvcreate to succeed ++# because rmeta+rimage are pushed to being allocated on dev1,dev2,dev3,dev4 ++# which means imeta is now free to be allocated from dev3,dev4 which have ++# plenty of space ++lvcreate --type raid1 --raidintegrity y -n $lv1 -L 600M -an $vg ++lvs -a -o+devices $vg ++ ++vgremove -ff $vg ++ +diff --git a/test/shell/integrity-misc.sh b/test/shell/integrity-misc.sh +new file mode 100644 +index 0000000..73b0a67 +--- /dev/null ++++ b/test/shell/integrity-misc.sh +@@ -0,0 +1,228 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux have_integrity 1 5 0 || skip ++which mkfs.xfs || skip ++ ++mnt="mnt" ++mkdir -p $mnt ++ ++aux prepare_devs 5 64 ++ ++for i in `seq 1 16384`; do echo -n "A" >> fileA; done ++for i in `seq 1 16384`; do echo -n "B" >> fileB; done ++for i in `seq 1 16384`; do echo -n "C" >> fileC; done ++ ++# generate random data ++dd if=/dev/urandom of=randA bs=512K count=2 ++dd if=/dev/urandom of=randB bs=512K count=3 ++dd if=/dev/urandom of=randC bs=512K count=4 ++ ++_prepare_vg() { ++ vgcreate $SHARED $vg "$dev1" "$dev2" "$dev3" "$dev4" "$dev5" ++ pvs ++} ++ ++_add_new_data_to_mnt() { ++ mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++ ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ # add original data ++ cp randA $mnt ++ cp randB $mnt ++ cp randC $mnt ++ mkdir $mnt/1 ++ cp fileA $mnt/1 ++ cp fileB $mnt/1 ++ cp fileC $mnt/1 ++ mkdir $mnt/2 ++ cp fileA $mnt/2 ++ cp fileB $mnt/2 ++ cp fileC $mnt/2 ++} ++ ++_add_more_data_to_mnt() { ++ mkdir $mnt/more ++ cp fileA $mnt/more ++ cp fileB $mnt/more ++ cp fileC $mnt/more ++ cp randA $mnt/more ++ cp randB $mnt/more ++ cp randC $mnt/more ++} ++ ++_verify_data_on_mnt() { ++ diff randA $mnt/randA ++ diff randB $mnt/randB ++ diff randC $mnt/randC ++ diff fileA $mnt/1/fileA ++ diff fileB $mnt/1/fileB ++ diff fileC $mnt/1/fileC ++ diff fileA $mnt/2/fileA ++ diff fileB $mnt/2/fileB ++ diff fileC $mnt/2/fileC ++} ++ ++_verify_data_on_lv() { ++ lvchange -ay $vg/$lv1 ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ _verify_data_on_mnt ++ rm $mnt/randA ++ rm $mnt/randB ++ rm $mnt/randC ++ rm -rf $mnt/1 ++ rm -rf $mnt/2 ++ umount $mnt ++ lvchange -an $vg/$lv1 ++} ++ ++_sync_percent() { ++ local checklv=$1 ++ get lv_field "$checklv" sync_percent | cut -d. -f1 ++} ++ ++_wait_recalc() { ++ local checklv=$1 ++ ++ for i in $(seq 1 10) ; do ++ sync=$(_sync_percent "$checklv") ++ echo "sync_percent is $sync" ++ ++ if test "$sync" = "100"; then ++ return ++ fi ++ ++ sleep 1 ++ done ++ ++ echo "timeout waiting for recalc" ++ return 1 ++} ++ ++# lvrename ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_new_data_to_mnt ++umount $mnt ++lvrename $vg/$lv1 $vg/$lv2 ++mount "$DM_DEV_DIR/$vg/$lv2" $mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv2 ++lvremove $vg/$lv2 ++vgremove -ff $vg ++ ++# lvconvert --replace ++# an existing dev is replaced with another dev ++# lv must be active ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg "$dev1" "$dev2" ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_new_data_to_mnt ++lvconvert --replace "$dev1" $vg/$lv1 "$dev3" ++lvs -a -o+devices $vg > out ++cat out ++grep "$dev2" out ++grep "$dev3" out ++not grep "$dev1" out ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# lvconvert --replace ++# same as prev but with bitmap mode ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y --raidintegritymode bitmap -n $lv1 -l 8 $vg "$dev1" "$dev2" ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_new_data_to_mnt ++lvconvert --replace "$dev1" $vg/$lv1 "$dev3" ++lvs -a -o+devices $vg > out ++cat out ++grep "$dev2" out ++grep "$dev3" out ++not grep "$dev1" out ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# lvconvert --repair ++# while lv is active a device goes missing (with rimage,rmeta,imeta,orig). ++# lvconvert --repair should replace the missing dev with another, ++# (like lvconvert --replace does for a dev that's not missing). ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg "$dev1" "$dev2" ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_new_data_to_mnt ++aux disable_dev "$dev2" ++lvs -a -o+devices $vg > out ++cat out ++grep unknown out ++lvconvert -vvvv -y --repair $vg/$lv1 ++lvs -a -o+devices $vg > out ++cat out ++not grep "$dev2" out ++not grep unknown out ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++aux enable_dev "$dev2" ++vgremove -ff $vg ++ ++# lvchange activationmode ++# a device is missing (with rimage,rmeta,imeta,iorig), the lv ++# is already inactive, and it cannot be activated, with ++# activationmode degraded or partial, or in any way, ++# until integrity is removed. ++ ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg "$dev1" "$dev2" ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_new_data_to_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++aux disable_dev "$dev2" ++lvs -a -o+devices $vg ++not lvchange -ay $vg/$lv1 ++not lvchange -ay --activationmode degraded $vg/$lv1 ++not lvchange -ay --activationmode partial $vg/$lv1 ++lvconvert --raidintegrity n $vg/$lv1 ++lvchange -ay --activationmode degraded $vg/$lv1 ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++lvremove $vg/$lv1 ++aux enable_dev "$dev2" ++vgremove -ff $vg ++ +diff --git a/test/shell/integrity.sh b/test/shell/integrity.sh +new file mode 100644 +index 0000000..7e4f2cb +--- /dev/null ++++ b/test/shell/integrity.sh +@@ -0,0 +1,735 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++aux have_integrity 1 5 0 || skip ++which mkfs.xfs || skip ++which xfs_growfs || skip ++ ++mnt="mnt" ++mkdir -p $mnt ++ ++aux prepare_devs 5 64 ++ ++for i in `seq 1 16384`; do echo -n "A" >> fileA; done ++for i in `seq 1 16384`; do echo -n "B" >> fileB; done ++for i in `seq 1 16384`; do echo -n "C" >> fileC; done ++ ++# generate random data ++dd if=/dev/urandom of=randA bs=512K count=2 ++dd if=/dev/urandom of=randB bs=512K count=3 ++dd if=/dev/urandom of=randC bs=512K count=4 ++ ++_prepare_vg() { ++ # zero devs so we are sure to find the correct file data ++ # on the underlying devs when corrupting it ++ dd if=/dev/zero of="$dev1" || true ++ dd if=/dev/zero of="$dev2" || true ++ dd if=/dev/zero of="$dev3" || true ++ dd if=/dev/zero of="$dev4" || true ++ dd if=/dev/zero of="$dev5" || true ++ vgcreate $SHARED $vg "$dev1" "$dev2" "$dev3" "$dev4" "$dev5" ++ pvs ++} ++ ++_test_fs_with_error() { ++ mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++ ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ # add original data ++ cp fileA $mnt ++ cp fileB $mnt ++ cp fileC $mnt ++ ++ umount $mnt ++ lvchange -an $vg/$lv1 ++ ++ # corrupt the original data on the underying dev ++ # flip one bit in fileB, changing a 0x42 to 0x43 ++ # the bit is changed in the last 4096 byte block ++ # of the file, so when reading back the file we ++ # will get the first three 4096 byte blocks, for ++ # a total of 12288 bytes before getting an error ++ # on the last 4096 byte block. ++ xxd "$dev1" > dev1.txt ++ tac dev1.txt > dev1.rev ++ sed -e '0,/4242 4242 4242 4242 4242 4242 4242 4242/ s/4242 4242 4242 4242 4242 4242 4242 4242/4242 4242 4242 4242 4242 4242 4242 4243/' dev1.rev > dev1.rev.bad ++ tac dev1.rev.bad > dev1.bad ++ xxd -r dev1.bad > "$dev1" ++ rm dev1.txt dev1.rev dev1.rev.bad dev1.bad ++ ++ lvchange -ay $vg/$lv1 ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ # read complete fileA which was not corrupted ++ dd if=$mnt/fileA of=tmp bs=1k ++ ls -l tmp ++ stat -c %s tmp ++ diff fileA tmp ++ rm tmp ++ ++ # read partial fileB which was corrupted ++ not dd if=$mnt/fileB of=tmp bs=1k ++ ls -l tmp ++ stat -c %s tmp | grep 12288 ++ not diff fileB tmp ++ rm tmp ++ ++ umount $mnt ++} ++ ++_test_fs_with_raid() { ++ mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++ ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ # add original data ++ cp fileA $mnt ++ cp fileB $mnt ++ cp fileC $mnt ++ ++ umount $mnt ++ lvchange -an $vg/$lv1 ++ ++ xxd "$dev1" > dev1.txt ++ tac dev1.txt > dev1.rev ++ sed -e '0,/4242 4242 4242 4242 4242 4242 4242 4242/ s/4242 4242 4242 4242 4242 4242 4242 4242/4242 4242 4242 4242 4242 4242 4242 4243/' dev1.rev > dev1.rev.bad ++ tac dev1.rev.bad > dev1.bad ++ xxd -r dev1.bad > "$dev1" ++ rm dev1.txt dev1.rev dev1.rev.bad dev1.bad ++ ++ lvchange -ay $vg/$lv1 ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ # read complete fileA which was not corrupted ++ dd if=$mnt/fileA of=tmp bs=1k ++ ls -l tmp ++ stat -c %s tmp | grep 16384 ++ diff fileA tmp ++ rm tmp ++ ++ # read complete fileB, corruption is corrected by raid ++ dd if=$mnt/fileB of=tmp bs=1k ++ ls -l tmp ++ stat -c %s tmp | grep 16384 ++ diff fileB tmp ++ rm tmp ++ ++ umount $mnt ++} ++ ++_add_new_data_to_mnt() { ++ mkfs.xfs -f "$DM_DEV_DIR/$vg/$lv1" ++ ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ ++ # add original data ++ cp randA $mnt ++ cp randB $mnt ++ cp randC $mnt ++ mkdir $mnt/1 ++ cp fileA $mnt/1 ++ cp fileB $mnt/1 ++ cp fileC $mnt/1 ++ mkdir $mnt/2 ++ cp fileA $mnt/2 ++ cp fileB $mnt/2 ++ cp fileC $mnt/2 ++} ++ ++_add_more_data_to_mnt() { ++ mkdir $mnt/more ++ cp fileA $mnt/more ++ cp fileB $mnt/more ++ cp fileC $mnt/more ++ cp randA $mnt/more ++ cp randB $mnt/more ++ cp randC $mnt/more ++} ++ ++_verify_data_on_mnt() { ++ diff randA $mnt/randA ++ diff randB $mnt/randB ++ diff randC $mnt/randC ++ diff fileA $mnt/1/fileA ++ diff fileB $mnt/1/fileB ++ diff fileC $mnt/1/fileC ++ diff fileA $mnt/2/fileA ++ diff fileB $mnt/2/fileB ++ diff fileC $mnt/2/fileC ++} ++ ++_verify_data_on_lv() { ++ lvchange -ay $vg/$lv1 ++ mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++ _verify_data_on_mnt ++ rm $mnt/randA ++ rm $mnt/randB ++ rm $mnt/randC ++ rm -rf $mnt/1 ++ rm -rf $mnt/2 ++ umount $mnt ++ lvchange -an $vg/$lv1 ++} ++ ++_sync_percent() { ++ local checklv=$1 ++ get lv_field "$checklv" sync_percent | cut -d. -f1 ++} ++ ++_wait_recalc() { ++ local checklv=$1 ++ ++ for i in $(seq 1 10) ; do ++ sync=$(_sync_percent "$checklv") ++ echo "sync_percent is $sync" ++ ++ if test "$sync" = "100"; then ++ return ++ fi ++ ++ sleep 1 ++ done ++ ++ echo "timeout waiting for recalc" ++ return 1 ++} ++ ++# Test corrupting data on an image and verifying that ++# it is detected by integrity and corrected by raid. ++ ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg ++_test_fs_with_raid ++lvchange -an $vg/$lv1 ++lvconvert --raidintegrity n $vg/$lv1 ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid1 -m2 --raidintegrity y -n $lv1 -l 8 $vg ++_test_fs_with_raid ++lvchange -an $vg/$lv1 ++lvconvert --raidintegrity n $vg/$lv1 ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid4 --raidintegrity y -n $lv1 -l 8 $vg ++_test_fs_with_raid ++lvchange -an $vg/$lv1 ++lvconvert --raidintegrity n $vg/$lv1 ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid5 --raidintegrity y -n $lv1 -l 8 $vg ++_test_fs_with_raid ++lvchange -an $vg/$lv1 ++lvconvert --raidintegrity n $vg/$lv1 ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid6 --raidintegrity y -n $lv1 -l 8 $vg ++_test_fs_with_raid ++lvchange -an $vg/$lv1 ++lvconvert --raidintegrity n $vg/$lv1 ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid10 --raidintegrity y -n $lv1 -l 8 $vg ++_test_fs_with_raid ++lvchange -an $vg/$lv1 ++lvconvert --raidintegrity n $vg/$lv1 ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# Test removing integrity from an active LV ++ ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_new_data_to_mnt ++lvconvert --raidintegrity n $vg/$lv1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid4 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_new_data_to_mnt ++lvconvert --raidintegrity n $vg/$lv1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid5 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_new_data_to_mnt ++lvconvert --raidintegrity n $vg/$lv1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid6 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_new_data_to_mnt ++lvconvert --raidintegrity n $vg/$lv1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid10 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_new_data_to_mnt ++lvconvert --raidintegrity n $vg/$lv1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# Test adding integrity to an active LV ++ ++_prepare_vg ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++_add_new_data_to_mnt ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid4 -n $lv1 -l 8 $vg ++_add_new_data_to_mnt ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid5 -n $lv1 -l 8 $vg ++_add_new_data_to_mnt ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid6 -n $lv1 -l 8 $vg ++_add_new_data_to_mnt ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid10 -n $lv1 -l 8 $vg ++_add_new_data_to_mnt ++lvconvert --raidintegrity y $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# Test lvextend while inactive ++ ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_new_data_to_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++lvextend -l 16 $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++xfs_growfs $mnt ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid6 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_new_data_to_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++lvextend -l 16 $vg/$lv1 ++lvchange -ay $vg/$lv1 ++mount "$DM_DEV_DIR/$vg/$lv1" $mnt ++xfs_growfs $mnt ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# Test lvextend while active ++ ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_new_data_to_mnt ++lvextend -l 16 $vg/$lv1 ++xfs_growfs $mnt ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid5 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_new_data_to_mnt ++lvextend -l 16 $vg/$lv1 ++xfs_growfs $mnt ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid10 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_new_data_to_mnt ++lvextend -l 16 $vg/$lv1 ++xfs_growfs $mnt ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# Test adding image to raid1 ++ ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_new_data_to_mnt ++lvconvert -y -m+1 $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 ++lvs -a -o+devices $vg ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# Test removing image from raid1 ++ ++_prepare_vg ++lvcreate --type raid1 -m2 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 ++lvs -a -o+devices $vg ++_add_new_data_to_mnt ++lvconvert -y -m-1 $vg/$lv1 ++lvs -a -o+devices $vg ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# Test disallowed operations on raid+integrity ++ ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_new_data_to_mnt ++not lvconvert -y -m-1 $vg/$lv1 ++not lvconvert --splitmirrors 1 -n tmp -y $vg/$lv1 ++not lvconvert --splitmirrors 1 --trackchanges -y $vg/$lv1 ++not lvchange --syncaction check $vg/$lv1 ++not lvchange --syncaction repair $vg/$lv1 ++not lvreduce -L4M $vg/$lv1 ++not lvcreate -s -n snap -L4M $vg/$lv1 ++not pvmove -n $vg/$lv1 "$dev1" ++not pvmove "$dev1" ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# Repeat many of the tests above using bitmap mode ++ ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y --raidintegritymode bitmap -n $lv1 -l 8 $vg ++_test_fs_with_raid ++lvchange -an $vg/$lv1 ++lvconvert --raidintegrity n $vg/$lv1 ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++_prepare_vg ++lvcreate --type raid6 --raidintegrity y --raidintegritymode bitmap -n $lv1 -l 8 $vg ++_test_fs_with_raid ++lvchange -an $vg/$lv1 ++lvconvert --raidintegrity n $vg/$lv1 ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# remove from active lv ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y --raidintegritymode bitmap -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_new_data_to_mnt ++lvconvert --raidintegrity n $vg/$lv1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# add to active lv ++_prepare_vg ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++_add_new_data_to_mnt ++lvconvert --raidintegrity y --raidintegritymode bitmap $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# lvextend active ++_prepare_vg ++lvcreate --type raid1 --raidintegrity y --raidintegritymode bitmap -m1 -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_new_data_to_mnt ++lvextend -l 16 $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++xfs_growfs $mnt ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# add image to raid1 ++_prepare_vg ++lvcreate --type raid1 -m1 --raidintegrity y --raidintegritymode bitmap -n $lv1 -l 8 $vg ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++lvs -a -o+devices $vg ++_add_new_data_to_mnt ++lvconvert -y -m+1 $vg/$lv1 ++_wait_recalc $vg/${lv1}_rimage_0 ++_wait_recalc $vg/${lv1}_rimage_1 ++_wait_recalc $vg/${lv1}_rimage_2 ++lvs -a -o+devices $vg ++_add_more_data_to_mnt ++_verify_data_on_mnt ++umount $mnt ++lvchange -an $vg/$lv1 ++_verify_data_on_lv ++lvremove $vg/$lv1 ++vgremove -ff $vg ++ ++# Test that raid+integrity cannot be a sublv ++# part1: cannot add integrity to a raid LV that is already a sublv ++ ++_prepare_vg ++ ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvconvert -y --type thin-pool $vg/$lv1 ++not lvconvert --raidintegrity y $vg/$lv1 ++not lvconvert --raidintegrity y $vg/${lv1}_tdata ++not lvconvert --raidintegrity y $vg/${lv1}_tmeta ++lvremove -y $vg/$lv1 ++ ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvconvert -y --type cache-pool $vg/$lv1 ++not lvconvert --raidintegrity y $vg/$lv1 ++not lvconvert --raidintegrity y $vg/${lv1}_cdata ++not lvconvert --raidintegrity y $vg/${lv1}_cmeta ++lvremove -y $vg/$lv1 ++ ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvcreate --type cache-pool -n cpool -l 8 $vg ++lvconvert -y --type cache --cachepool cpool $vg/$lv1 ++not lvconvert --raidintegrity y $vg/$lv1 ++not lvconvert --raidintegrity y $vg/${lv1}_corig ++lvremove -y $vg/$lv1 ++ ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvcreate --type raid1 -m1 -n cvol -l 8 $vg ++lvconvert -y --type cache --cachevol cvol $vg/$lv1 ++not lvconvert --raidintegrity y $vg/$lv1 ++not lvconvert --raidintegrity y $vg/${lv1}_corig ++not lvconvert --raidintegrity y $vg/cvol ++lvremove -y $vg/$lv1 ++ ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvcreate -n cvol -l 8 $vg ++lvchange -an $vg ++lvconvert -y --type writecache --cachevol cvol $vg/$lv1 ++not lvconvert --raidintegrity y $vg/$lv1 ++not lvconvert --raidintegrity y $vg/${lv1}_wcorig ++lvremove -y $vg/$lv1 ++ ++# Test that raid+integrity cannot be a sublv ++# part2: cannot convert an existing raid+integrity LV into a sublv ++ ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvconvert -y --type thin-pool $vg/$lv1 ++not lvconvert --raidintegrity y $vg/${lv1}_tdata ++lvremove -y $vg/$lv1 ++ ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvcreate --type raid1 -m1 -n $lv2 -l 8 $vg ++lvconvert -y --type cache --cachevol $lv2 $vg/$lv1 ++not lvconvert --raidintegrity y $vg/${lv1}_corig ++not lvconvert --raidintegrity y $vg/${lv2}_vol ++lvremove -y $vg/$lv1 ++ ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvcreate --type raid1 -m1 -n $lv2 -l 8 $vg ++lvconvert -y --type cache --cachepool $lv2 $vg/$lv1 ++not lvconvert --raidintegrity y $vg/${lv1}_corig ++not lvconvert --raidintegrity y $vg/${lv2}_cpool_cdata ++lvremove -y $vg/$lv1 ++ ++# cannot add integrity to raid that has a snapshot ++ ++lvcreate --type raid1 -m1 -n $lv1 -l 8 $vg ++lvcreate -s -n $lv2 -l 8 $vg/$lv1 ++not lvconvert --raidintegrity y $vg/$lv1 ++lvremove -y $vg/$lv1 ++ ++vgremove -ff $vg +diff --git a/tools/args.h b/tools/args.h +index 999d891..d1f604b 100644 +--- a/tools/args.h ++++ b/tools/args.h +@@ -512,6 +512,26 @@ arg(pvmetadatacopies_ARG, '\0', "pvmetadatacopies", pvmetadatacopies_VAL, 0, 0, + "This may be useful in VGs containing many PVs (this places limitations\n" + "on the ability to use vgsplit later.)\n") + ++arg(raidintegrity_ARG, '\0', "raidintegrity", bool_VAL, 0, 0, ++ "Enable or disable data integrity checksums for raid images.\n") ++ ++arg(raidintegrityblocksize_ARG, '\0', "raidintegrityblocksize", number_VAL, 0, 0, ++ "The block size to use for dm-integrity on raid images.\n" ++ "The integrity block size should usually match the device\n" ++ "logical block size, or the file system block size.\n" ++ "It may be less than the file system block size, but not\n" ++ "less than the device logical block size.\n" ++ "Possible values: 512, 1024, 2048, 4096.\n") ++ ++arg(raidintegritymode_ARG, '\0', "raidintegritymode", string_VAL, 0, 0, ++ "Use a journal (default) or bitmap for keeping integrity checksums consistent\n" ++ "in case of a crash. The bitmap areas are recalculated after a crash, so corruption\n" ++ "in those areas would not be detected. A journal does not have this problem.\n" ++ "The journal mode doubles writes to storage, but can improve performance for\n" ++ "scattered writes packed into a single journal write.\n" ++ "bitmap mode can in theory achieve full write throughput of the device,\n" ++ "but would not benefit from the potential scattered write optimization.\n") ++ + arg(readonly_ARG, '\0', "readonly", 0, 0, 0, + "Run the command in a special read-only mode which will read on-disk\n" + "metadata without needing to take any locks. This can be used to peek\n" +diff --git a/tools/command-lines.in b/tools/command-lines.in +index 37a01cb..ed3d041 100644 +--- a/tools/command-lines.in ++++ b/tools/command-lines.in +@@ -262,7 +262,7 @@ IO: --ignoreskippedcluster + ID: lvchange_resync + DESC: Resyncronize a mirror or raid LV. + DESC: Use to reset 'R' attribute on a not initially synchronized LV. +-RULE: all not lv_is_pvmove lv_is_locked ++RULE: all not lv_is_pvmove lv_is_locked lv_is_raid_with_integrity + RULE: all not LV_raid0 + + lvchange --syncaction SyncAction VG|LV_raid|Tag|Select ... +@@ -359,7 +359,7 @@ OP: PV ... + ID: lvconvert_raid_types + DESC: Convert LV to raid or change raid layout + DESC: (a specific raid level must be used, e.g. raid1). +-RULE: all not lv_is_locked lv_is_pvmove ++RULE: all not lv_is_locked lv_is_pvmove lv_is_raid_with_integrity + + lvconvert --mirrors SNumber LV + OO: --regionsize RegionSize, --interval Number, --mirrorlog MirrorLog, OO_LVCONVERT +@@ -373,21 +373,21 @@ OO: OO_LVCONVERT, --interval Number, --regionsize RegionSize, --stripesize SizeK + OP: PV ... + ID: lvconvert_raid_types + DESC: Convert raid LV to change number of stripe images. +-RULE: all not lv_is_locked lv_is_pvmove ++RULE: all not lv_is_locked lv_is_pvmove lv_is_raid_with_integrity + RULE: all not LV_raid0 LV_raid1 + + lvconvert --stripesize SizeKB LV_raid + OO: OO_LVCONVERT, --interval Number, --regionsize RegionSize + ID: lvconvert_raid_types + DESC: Convert raid LV to change the stripe size. +-RULE: all not lv_is_locked lv_is_pvmove ++RULE: all not lv_is_locked lv_is_pvmove lv_is_raid_with_integrity + RULE: all not LV_raid0 LV_raid1 + + lvconvert --regionsize RegionSize LV_raid + OO: OO_LVCONVERT + ID: lvconvert_change_region_size + DESC: Change the region size of an LV. +-RULE: all not lv_is_locked lv_is_pvmove ++RULE: all not lv_is_locked lv_is_pvmove lv_is_raid_with_integrity + RULE: all not LV_raid0 + FLAGS: SECONDARY_SYNTAX + +@@ -401,20 +401,20 @@ OO: OO_LVCONVERT + OP: PV ... + ID: lvconvert_split_mirror_images + DESC: Split images from a raid1 or mirror LV and use them to create a new LV. +-RULE: all not lv_is_locked lv_is_pvmove ++RULE: all not lv_is_locked lv_is_pvmove lv_is_raid_with_integrity + + lvconvert --splitmirrors Number --trackchanges LV_raid1_cache + OO: OO_LVCONVERT + OP: PV ... + ID: lvconvert_split_mirror_images + DESC: Split images from a raid1 LV and track changes to origin for later merge. +-RULE: all not lv_is_locked lv_is_pvmove ++RULE: all not lv_is_locked lv_is_pvmove lv_is_raid_with_integrity + + lvconvert --mergemirrors LV_linear_raid|VG|Tag ... + OO: OO_LVCONVERT + ID: lvconvert_merge_mirror_images + DESC: Merge LV images that were split from a raid1 LV. +-RULE: all not lv_is_locked lv_is_pvmove lv_is_merging_origin lv_is_virtual_origin lv_is_external_origin lv_is_merging_cow ++RULE: all not lv_is_locked lv_is_pvmove lv_is_merging_origin lv_is_virtual_origin lv_is_external_origin lv_is_merging_cow lv_is_raid_with_integrity + + lvconvert --mirrorlog MirrorLog LV_mirror + OO: OO_LVCONVERT +@@ -434,7 +434,7 @@ OO: --thin, --originname LV_new, OO_LVCONVERT_POOL, OO_LVCONVERT + ID: lvconvert_to_thin_with_external + DESC: Convert LV to a thin LV, using the original LV as an external origin. + RULE: all and lv_is_visible +-RULE: all not lv_is_locked ++RULE: all not lv_is_locked lv_is_raid_with_integrity + RULE: --poolmetadata not --readahead --stripesize --stripes_long + + # alternate form of lvconvert --type thin +@@ -445,7 +445,7 @@ DESC: Convert LV to a thin LV, using the original LV as an external origin + DESC: (infers --type thin). + FLAGS: SECONDARY_SYNTAX + RULE: all and lv_is_visible +-RULE: all not lv_is_locked ++RULE: all not lv_is_locked lv_is_raid_with_integrity + RULE: --poolmetadata not --readahead --stripesize --stripes_long + + --- +@@ -455,6 +455,7 @@ OO: --cache, OO_LVCONVERT_CACHE, OO_LVCONVERT_POOL, OO_LVCONVERT + ID: lvconvert_to_cache_with_cachepool + DESC: Attach a cache pool to an LV, converts the LV to type cache. + RULE: all and lv_is_visible ++RULE: all not lv_is_raid_with_integrity + RULE: --poolmetadata not --readahead --stripesize --stripes_long + + # alternate form of lvconvert --type cache +@@ -463,6 +464,7 @@ OO: --type cache, OO_LVCONVERT_CACHE, OO_LVCONVERT_POOL, OO_LVCONVERT + ID: lvconvert_to_cache_with_cachepool + DESC: Attach a cache pool to an LV (infers --type cache). + RULE: all and lv_is_visible ++RULE: all not lv_is_raid_with_integrity + RULE: --poolmetadata not --readahead --stripesize --stripes_long + FLAGS: SECONDARY_SYNTAX + +@@ -473,6 +475,7 @@ OO: OO_LVCONVERT, --cachesettings String + ID: lvconvert_to_writecache + DESC: Attach a writecache to an LV, converts the LV to type writecache. + RULE: all and lv_is_visible ++RULE: all not lv_is_raid_with_integrity + + --- + +@@ -481,6 +484,7 @@ OO: --cache, OO_LVCONVERT_CACHE, OO_LVCONVERT, --poolmetadatasize SizeMB, --chun + ID: lvconvert_to_cache_with_cachevol + DESC: Attach a cache to an LV, converts the LV to type cache. + RULE: all and lv_is_visible ++RULE: all not lv_is_raid_with_integrity + + # alternate form of lvconvert --type cache + lvconvert --cache --cachevol LV LV_linear_striped_raid_thinpool +@@ -488,6 +492,7 @@ OO: OO_LVCONVERT_CACHE, OO_LVCONVERT, --poolmetadatasize SizeMB, --chunksize Siz + ID: lvconvert_to_cache_with_cachevol + DESC: Attach a cache to an LV, converts the LV to type cache. + RULE: all and lv_is_visible ++RULE: all not lv_is_raid_with_integrity + FLAGS: SECONDARY_SYNTAX + + --- +@@ -499,7 +504,7 @@ OP: PV ... + ID: lvconvert_to_thinpool + DESC: Convert LV to type thin-pool. + RULE: all and lv_is_visible +-RULE: all not lv_is_locked lv_is_origin lv_is_merging_origin lv_is_external_origin lv_is_virtual ++RULE: all not lv_is_locked lv_is_origin lv_is_merging_origin lv_is_external_origin lv_is_virtual lv_is_raid_with_integrity + RULE: --poolmetadata not --readahead --stripesize --stripes_long + + # This command syntax has two different meanings depending on +@@ -533,6 +538,7 @@ DESC: Convert LV to type thin-pool (variant, use --type thin-pool). + DESC: Swap metadata LV in a thin pool (variant, use --swapmetadata). + FLAGS: PREVIOUS_SYNTAX + RULE: all and lv_is_visible ++RULE: all not lv_is_raid_with_integrity + RULE: --poolmetadata not --readahead --stripesize --stripes_long + + --- +@@ -543,6 +549,7 @@ OP: PV ... + ID: lvconvert_to_cachepool + DESC: Convert LV to type cache-pool. + RULE: --poolmetadata not --readahead --stripesize --stripes_long ++RULE: all not lv_is_raid_with_integrity + + # This command syntax has two different meanings depending on + # whether the LV pos arg is already a cache pool or not. +@@ -574,6 +581,7 @@ DESC: Convert LV to type cache-pool (variant, use --type cache-pool). + DESC: Swap metadata LV in a cache pool (variant, use --swapmetadata). + FLAGS: PREVIOUS_SYNTAX + RULE: all and lv_is_visible ++RULE: all not lv_is_raid_with_integrity + RULE: --poolmetadata not --readahead --stripesize --stripes_long + + --- +@@ -583,7 +591,7 @@ OO: --name LV_new, --virtualsize SizeMB, --compression Bool, --deduplication Boo + ID: lvconvert_to_vdopool + DESC: Convert LV to type vdopool. + RULE: all and lv_is_visible +-RULE: all not lv_is_locked lv_is_origin lv_is_merging_origin lv_is_external_origin lv_is_virtual ++RULE: all not lv_is_locked lv_is_origin lv_is_merging_origin lv_is_external_origin lv_is_virtual lv_is_raid_with_integrity + + lvconvert --vdopool LV_linear_striped_raid_cache + OO: --type vdo-pool, OO_LVCONVERT, +@@ -591,7 +599,7 @@ OO: --type vdo-pool, OO_LVCONVERT, + ID: lvconvert_to_vdopool_param + DESC: Convert LV to type vdopool. + RULE: all and lv_is_visible +-RULE: all not lv_is_locked lv_is_origin lv_is_merging_origin lv_is_external_origin lv_is_virtual ++RULE: all not lv_is_locked lv_is_origin lv_is_merging_origin lv_is_external_origin lv_is_virtual lv_is_raid_with_integrity + FLAGS: SECONDARY_SYNTAX + + --- +@@ -757,6 +765,14 @@ FLAGS: SECONDARY_SYNTAX + + --- + ++lvconvert --raidintegrity Bool LV_raid ++OO: --raidintegritymode String, --raidintegrityblocksize Number, OO_LVCONVERT ++OP: PV ... ++ID: lvconvert_integrity ++DESC: Add or remove data integrity checksums to raid images. ++ ++--- ++ + # --extents is not specified; it's an automatic alternative for --size + + OO_LVCREATE: --addtag Tag, --alloc Alloc, --autobackup Bool, --activate Active, +@@ -870,7 +886,8 @@ DESC: Create a raid1 or mirror LV (infers --type raid1|mirror). + # R9,R10,R11,R12 (--type raid with any use of --stripes/--mirrors) + lvcreate --type raid --size SizeMB VG + OO: --mirrors PNumber, --stripes Number, --stripesize SizeKB, +---regionsize RegionSize, --minrecoveryrate SizeKB, --maxrecoveryrate SizeKB, OO_LVCREATE ++--regionsize RegionSize, --minrecoveryrate SizeKB, --maxrecoveryrate SizeKB, ++--raidintegrity Bool, --raidintegritymode String, --raidintegrityblocksize Number, OO_LVCREATE + OP: PV ... + ID: lvcreate_raid_any + DESC: Create a raid LV (a specific raid level must be used, e.g. raid1). +diff --git a/tools/lv_props.h b/tools/lv_props.h +index 2925028..60c8c73 100644 +--- a/tools/lv_props.h ++++ b/tools/lv_props.h +@@ -52,5 +52,6 @@ lvp(is_cow_covering_origin_LVP, "lv_is_cow_covering_origin", NULL) + lvp(is_visible_LVP, "lv_is_visible", NULL) + lvp(is_historical_LVP, "lv_is_historical", NULL) + lvp(is_raid_with_tracking_LVP, "lv_is_raid_with_tracking", NULL) ++lvp(is_raid_with_integrity_LVP, "lv_is_raid_with_integrity", NULL) + lvp(LVP_COUNT, "", NULL) + +diff --git a/tools/lv_types.h b/tools/lv_types.h +index 778cd54..d1c94cc 100644 +--- a/tools/lv_types.h ++++ b/tools/lv_types.h +@@ -34,5 +34,6 @@ lvt(raid10_LVT, "raid10", NULL) + lvt(error_LVT, "error", NULL) + lvt(zero_LVT, "zero", NULL) + lvt(writecache_LVT, "writecache", NULL) ++lvt(integrity_LVT, "integrity", NULL) + lvt(LVT_COUNT, "", NULL) + +diff --git a/tools/lvchange.c b/tools/lvchange.c +index 5f0fcab..2d5bb32 100644 +--- a/tools/lvchange.c ++++ b/tools/lvchange.c +@@ -1573,6 +1573,11 @@ static int _lvchange_syncaction_single(struct cmd_context *cmd, + struct logical_volume *lv, + struct processing_handle *handle) + { ++ if (lv_raid_has_integrity(lv)) { ++ log_error("Integrity must be removed to use syncaction commands."); ++ return_ECMD_FAILED; ++ } ++ + /* If LV is inactive here, ensure it's not active elsewhere. */ + if (!lockd_lv(cmd, lv, "ex", 0)) + return_ECMD_FAILED; +diff --git a/tools/lvconvert.c b/tools/lvconvert.c +index bb40930..e969b44 100644 +--- a/tools/lvconvert.c ++++ b/tools/lvconvert.c +@@ -1391,11 +1391,23 @@ static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *l + DEFAULT_RAID1_MAX_IMAGES, lp->segtype->name, display_lvname(lv)); + return 0; + } ++ if (!seg_is_raid1(seg) && lv_raid_has_integrity(lv)) { ++ log_error("Cannot add raid images with integrity for this raid level."); ++ return 0; ++ } + if (!lv_raid_change_image_count(lv, lp->yes, image_count, + (lp->region_size_supplied || !seg->region_size) ? + lp->region_size : seg->region_size , lp->pvh)) + return_0; + ++ if (lv_raid_has_integrity(lv)) { ++ struct integrity_settings *isettings = NULL; ++ if (!lv_get_raid_integrity_settings(lv, &isettings)) ++ return_0; ++ if (!lv_add_integrity_to_raid(lv, isettings, lp->pvh, NULL)) ++ return_0; ++ } ++ + log_print_unless_silent("Logical volume %s successfully converted.", + display_lvname(lv)); + +@@ -1425,6 +1437,12 @@ static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *l + return 0; + } + ++ if (lv_raid_has_integrity(lv)) { ++ /* FIXME: which conversions are happening here? */ ++ log_error("This conversion is not supported for raid with integrity."); ++ return 0; ++ } ++ + /* FIXME This needs changing globally. */ + if (!arg_is_set(cmd, stripes_long_ARG)) + lp->stripes = 0; +@@ -1444,6 +1462,12 @@ static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *l + } + + try_new_takeover_or_reshape: ++ if (lv_raid_has_integrity(lv)) { ++ /* FIXME: which conversions are happening here? */ ++ log_error("This conversion is not supported for raid with integrity."); ++ return 0; ++ } ++ + if (!_raid4_conversion_supported(lv, lp)) + return 0; + +@@ -5758,6 +5782,119 @@ int lvconvert_to_cache_with_cachevol_cmd(struct cmd_context *cmd, int argc, char + return ret; + } + ++static int _lvconvert_integrity_remove(struct cmd_context *cmd, struct logical_volume *lv) ++{ ++ struct volume_group *vg = lv->vg; ++ int ret = 0; ++ ++ if (!lv_is_integrity(lv) && !lv_is_raid(lv)) { ++ log_error("LV does not have integrity."); ++ return 0; ++ } ++ ++ /* ensure it's not active elsewhere. */ ++ if (!lockd_lv(cmd, lv, "ex", 0)) ++ return_0; ++ ++ if (!archive(vg)) ++ return_0; ++ ++ if (lv_is_raid(lv)) ++ ret = lv_remove_integrity_from_raid(lv); ++ if (!ret) ++ return_0; ++ ++ backup(vg); ++ ++ log_print_unless_silent("Logical volume %s has removed integrity.", display_lvname(lv)); ++ return 1; ++} ++ ++static int _lvconvert_integrity_add(struct cmd_context *cmd, struct logical_volume *lv, ++ struct integrity_settings *set) ++{ ++ struct volume_group *vg = lv->vg; ++ struct dm_list *use_pvh; ++ int ret = 0; ++ ++ /* ensure it's not active elsewhere. */ ++ if (!lockd_lv(cmd, lv, "ex", 0)) ++ return_0; ++ ++ if (cmd->position_argc > 1) { ++ /* First pos arg is required LV, remaining are optional PVs. */ ++ if (!(use_pvh = create_pv_list(cmd->mem, vg, cmd->position_argc - 1, cmd->position_argv + 1, 0))) ++ return_0; ++ } else ++ use_pvh = &vg->pvs; ++ ++ if (!archive(vg)) ++ return_0; ++ ++ if (lv_is_partial(lv)) { ++ log_error("Cannot add integrity while LV is missing PVs."); ++ return 0; ++ } ++ ++ if (lv_is_raid(lv)) ++ ret = lv_add_integrity_to_raid(lv, set, use_pvh, NULL); ++ if (!ret) ++ return_0; ++ ++ backup(vg); ++ ++ log_print_unless_silent("Logical volume %s has added integrity.", display_lvname(lv)); ++ return 1; ++} ++ ++static int _lvconvert_integrity_single(struct cmd_context *cmd, ++ struct logical_volume *lv, ++ struct processing_handle *handle) ++{ ++ struct integrity_settings settings; ++ int ret = 0; ++ ++ memset(&settings, 0, sizeof(settings)); ++ ++ if (!integrity_mode_set(arg_str_value(cmd, raidintegritymode_ARG, NULL), &settings)) ++ return_ECMD_FAILED; ++ ++ if (arg_is_set(cmd, raidintegrityblocksize_ARG)) ++ settings.block_size = arg_int_value(cmd, raidintegrityblocksize_ARG, 0); ++ ++ if (arg_int_value(cmd, raidintegrity_ARG, 0)) ++ ret = _lvconvert_integrity_add(cmd, lv, &settings); ++ else ++ ret = _lvconvert_integrity_remove(cmd, lv); ++ ++ if (!ret) ++ return ECMD_FAILED; ++ return ECMD_PROCESSED; ++} ++ ++int lvconvert_integrity_cmd(struct cmd_context *cmd, int argc, char **argv) ++{ ++ struct processing_handle *handle; ++ int ret; ++ ++ if (!(handle = init_processing_handle(cmd, NULL))) { ++ log_error("Failed to initialize processing handle."); ++ return ECMD_FAILED; ++ } ++ ++ /* Want to be able to remove integrity from partial LV */ ++ cmd->handles_missing_pvs = 1; ++ ++ cmd->cname->flags &= ~GET_VGNAME_FROM_OPTIONS; ++ ++ ret = process_each_lv(cmd, cmd->position_argc, cmd->position_argv, NULL, NULL, READ_FOR_UPDATE, handle, NULL, ++ &_lvconvert_integrity_single); ++ ++ destroy_processing_handle(cmd, handle); ++ ++ return ret; ++} ++ + /* + * All lvconvert command defs have their own function, + * so the generic function name is unused. +diff --git a/tools/lvcreate.c b/tools/lvcreate.c +index 448f125..5c978b3 100644 +--- a/tools/lvcreate.c ++++ b/tools/lvcreate.c +@@ -858,7 +858,10 @@ static int _lvcreate_params(struct cmd_context *cmd, + maxrecoveryrate_ARG,\ + minrecoveryrate_ARG,\ + raidmaxrecoveryrate_ARG,\ +- raidminrecoveryrate_ARG ++ raidminrecoveryrate_ARG, \ ++ raidintegrity_ARG, \ ++ raidintegritymode_ARG, \ ++ raidintegrityblocksize_ARG + + #define SIZE_ARGS \ + extents_ARG,\ +@@ -1227,6 +1230,16 @@ static int _lvcreate_params(struct cmd_context *cmd, + } + } + ++ if (seg_is_raid(lp) && arg_int_value(cmd, raidintegrity_ARG, 0)) { ++ lp->raidintegrity = 1; ++ if (arg_is_set(cmd, raidintegrityblocksize_ARG)) ++ lp->integrity_settings.block_size = arg_int_value(cmd, raidintegrityblocksize_ARG, 0); ++ if (arg_is_set(cmd, raidintegritymode_ARG)) { ++ if (!integrity_mode_set(arg_str_value(cmd, raidintegritymode_ARG, NULL), &lp->integrity_settings)) ++ return_0; ++ } ++ } ++ + lcp->pv_count = argc; + lcp->pvs = argv; + +diff --git a/tools/lvmcmdline.c b/tools/lvmcmdline.c +index f147be3..d87a8f0 100644 +--- a/tools/lvmcmdline.c ++++ b/tools/lvmcmdline.c +@@ -149,6 +149,9 @@ static const struct command_function _command_functions[CMD_COUNT] = { + { lvconvert_to_vdopool_CMD, lvconvert_to_vdopool_cmd }, + { lvconvert_to_vdopool_param_CMD, lvconvert_to_vdopool_param_cmd }, + ++ /* lvconvert for integrity */ ++ { lvconvert_integrity_CMD, lvconvert_integrity_cmd }, ++ + { pvscan_display_CMD, pvscan_display_cmd }, + { pvscan_cache_CMD, pvscan_cache_cmd }, + }; +diff --git a/tools/pvmove.c b/tools/pvmove.c +index 0419a3d..a346b53 100644 +--- a/tools/pvmove.c ++++ b/tools/pvmove.c +@@ -381,6 +381,11 @@ static struct logical_volume *_set_up_pvmove_lv(struct cmd_context *cmd, + return NULL; + } + ++ if (lv_is_raid(lv) && lv_raid_has_integrity(lv)) { ++ log_error("Unable to pvmove device used for raid with integrity."); ++ return NULL; ++ } ++ + seg = first_seg(lv); + if (!needs_exclusive) { + /* Presence of exclusive LV decides whether pvmove must be also exclusive */ +@@ -625,6 +630,11 @@ static int _pvmove_setup_single(struct cmd_context *cmd, + log_error("pvmove not allowed on LV using writecache."); + return ECMD_FAILED; + } ++ ++ if (lv_is_raid(lv) && lv_raid_has_integrity(lv)) { ++ log_error("pvmove not allowed on raid LV with integrity."); ++ return ECMD_FAILED; ++ } + } + + /* +diff --git a/tools/toollib.c b/tools/toollib.c +index 6386a69..96d0d6d 100644 +--- a/tools/toollib.c ++++ b/tools/toollib.c +@@ -718,11 +718,26 @@ int vgcreate_params_set_from_args(struct cmd_context *cmd, + return 1; + } + ++int integrity_mode_set(const char *mode, struct integrity_settings *settings) ++{ ++ if (!mode || !strcmp(mode, "bitmap") || !strcmp(mode, "B")) ++ settings->mode[0] = 'B'; ++ else if (!strcmp(mode, "journal") || !strcmp(mode, "J")) ++ settings->mode[0] = 'J'; ++ else { ++ /* FIXME: the kernel has other modes, should we allow any of those? */ ++ log_error("Invalid raid integrity mode (use \"bitmap\" or \"journal\")"); ++ return 0; ++ } ++ return 1; ++} ++ + /* Shared code for changing activation state for vgchange/lvchange */ + int lv_change_activate(struct cmd_context *cmd, struct logical_volume *lv, + activation_change_t activate) + { + int r = 1; ++ int integrity_recalculate; + struct logical_volume *snapshot_lv; + + if (lv_is_cache_pool(lv)) { +@@ -780,9 +795,34 @@ int lv_change_activate(struct cmd_context *cmd, struct logical_volume *lv, + return 0; + } + ++ if ((integrity_recalculate = lv_has_integrity_recalculate_metadata(lv))) { ++ /* Don't want pvscan to write VG while running from systemd service. */ ++ if (!strcmp(cmd->name, "pvscan")) { ++ log_error("Cannot activate uninitialized integrity LV %s from pvscan.", ++ display_lvname(lv)); ++ return 0; ++ } ++ ++ if (vg_is_shared(lv->vg)) { ++ uint32_t lockd_state = 0; ++ if (!lockd_vg(cmd, lv->vg->name, "ex", 0, &lockd_state)) { ++ log_error("Cannot activate uninitialized integrity LV %s without lock.", ++ display_lvname(lv)); ++ return 0; ++ } ++ } ++ } ++ + if (!lv_active_change(cmd, lv, activate)) + return_0; + ++ /* Write VG metadata to clear the integrity recalculate flag. */ ++ if (integrity_recalculate && lv_is_active(lv)) { ++ log_print_unless_silent("Updating VG to complete initialization of integrity LV %s.", ++ display_lvname(lv)); ++ lv_clear_integrity_recalculate_metadata(lv); ++ } ++ + set_lv_notify(lv->vg->cmd); + + return r; +@@ -1144,6 +1184,7 @@ out: + return ok; + } + ++ + /* FIXME move to lib */ + static int _pv_change_tag(struct physical_volume *pv, const char *tag, int addtag) + { +@@ -2255,6 +2296,8 @@ static int _lv_is_prop(struct cmd_context *cmd, struct logical_volume *lv, int l + return lv_is_historical(lv); + case is_raid_with_tracking_LVP: + return lv_is_raid_with_tracking(lv); ++ case is_raid_with_integrity_LVP: ++ return lv_raid_has_integrity(lv); + default: + log_error(INTERNAL_ERROR "unknown lv property value lvp_enum %d", lvp_enum); + } +@@ -2309,6 +2352,8 @@ static int _lv_is_type(struct cmd_context *cmd, struct logical_volume *lv, int l + return seg_is_raid10(seg); + case writecache_LVT: + return seg_is_writecache(seg); ++ case integrity_LVT: ++ return seg_is_integrity(seg); + case error_LVT: + return !strcmp(seg->segtype->name, SEG_TYPE_NAME_ERROR); + case zero_LVT: +@@ -2367,6 +2412,8 @@ int get_lvt_enum(struct logical_volume *lv) + return raid10_LVT; + if (seg_is_writecache(seg)) + return writecache_LVT; ++ if (seg_is_integrity(seg)) ++ return integrity_LVT; + + if (!strcmp(seg->segtype->name, SEG_TYPE_NAME_ERROR)) + return error_LVT; +diff --git a/tools/tools.h b/tools/tools.h +index 3cf4293..7f2434d 100644 +--- a/tools/tools.h ++++ b/tools/tools.h +@@ -212,6 +212,8 @@ unsigned grouped_arg_is_set(const struct arg_values *av, int a); + const char *grouped_arg_str_value(const struct arg_values *av, int a, const char *def); + int32_t grouped_arg_int_value(const struct arg_values *av, int a, const int32_t def); + ++int integrity_mode_set(const char *mode, struct integrity_settings *settings); ++ + const char *command_name(struct cmd_context *cmd); + + int pvmove_poll(struct cmd_context *cmd, const char *pv_name, const char *uuid, +@@ -274,6 +276,8 @@ int lvconvert_merge_cmd(struct cmd_context *cmd, int argc, char **argv); + int lvconvert_to_vdopool_cmd(struct cmd_context *cmd, int argc, char **argv); + int lvconvert_to_vdopool_param_cmd(struct cmd_context *cmd, int argc, char **argv); + ++int lvconvert_integrity_cmd(struct cmd_context *cmd, int argc, char **argv); ++ + int pvscan_display_cmd(struct cmd_context *cmd, int argc, char **argv); + int pvscan_cache_cmd(struct cmd_context *cmd, int argc, char **argv); + +-- +1.8.3.1 + diff --git a/SOURCES/lvm2-2_03_10-Fix-scripts-lvmlocks.service.in-using-nonexistent-lock-opt-autowait.patch b/SOURCES/lvm2-2_03_10-Fix-scripts-lvmlocks.service.in-using-nonexistent-lock-opt-autowait.patch new file mode 100644 index 0000000..2eec67f --- /dev/null +++ b/SOURCES/lvm2-2_03_10-Fix-scripts-lvmlocks.service.in-using-nonexistent-lock-opt-autowait.patch @@ -0,0 +1,31 @@ +From 48105f492f7f8c157ba714217ae55c6fb50e76c0 Mon Sep 17 00:00:00 2001 +From: Maxim Plotnikov +Date: Wed, 22 Apr 2020 00:16:29 +0300 +Subject: [PATCH] Fix scripts/lvmlocks.service.in using nonexistent --lock-opt + autowait + +The --lock-opt autowait was dropped back in 9ab6bdce01, +and attempting to specify it has quite an opposite effect: +no waiting is done, which makes the unit almost useless. + +(cherry picked from commit a509776588a5c0c0bfc2394e4d1ed717531b0257) +--- + scripts/lvmlocks.service.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/scripts/lvmlocks.service.in b/scripts/lvmlocks.service.in +index f6a951f..a3d0bd4 100644 +--- a/scripts/lvmlocks.service.in ++++ b/scripts/lvmlocks.service.in +@@ -8,7 +8,7 @@ Type=oneshot + RemainAfterExit=yes + + # start lockspaces and wait for them to finish starting +-ExecStart=@SBINDIR@/lvm vgchange --lock-start --lock-opt autowait ++ExecStart=@SBINDIR@/lvm vgchange --lock-start --lock-opt auto + + # stop lockspaces and wait for them to finish stopping + ExecStop=@SBINDIR@/lvmlockctl --stop-lockspaces --wait 1 +-- +1.8.3.1 + diff --git a/SOURCES/lvm2-2_03_10-WHATS_NEW-integrity-with-raid.patch b/SOURCES/lvm2-2_03_10-WHATS_NEW-integrity-with-raid.patch new file mode 100644 index 0000000..a0d68f4 --- /dev/null +++ b/SOURCES/lvm2-2_03_10-WHATS_NEW-integrity-with-raid.patch @@ -0,0 +1,24 @@ +From a08afc8d0d18b2547176e731852b816df76c63eb Mon Sep 17 00:00:00 2001 +From: David Teigland +Date: Wed, 15 Apr 2020 11:04:12 -0500 +Subject: [PATCH 3/3] WHATS_NEW: integrity with raid + +(cherry picked from commit 211eaa284c4df992916e0a523d0ff932aa790a98) +--- + WHATS_NEW | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/WHATS_NEW b/WHATS_NEW +index 89583f7..c0267b7 100644 +--- a/WHATS_NEW ++++ b/WHATS_NEW +@@ -1,5 +1,6 @@ + Version 2.03.10 - + ================================= ++ Add integrity with raid capability. + Fix support for lvconvert --repair used by foreign apps (i.e. Docker). + + Version 2.03.09 - 26th March 2020 +-- +1.8.3.1 + diff --git a/SOURCES/lvm2-2_03_10-WHATS_NEWS-update.patch b/SOURCES/lvm2-2_03_10-WHATS_NEWS-update.patch new file mode 100644 index 0000000..82eb395 --- /dev/null +++ b/SOURCES/lvm2-2_03_10-WHATS_NEWS-update.patch @@ -0,0 +1,15 @@ + WHATS_NEW | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/WHATS_NEW b/WHATS_NEW +index db914c0..89583f7 100644 +--- a/WHATS_NEW ++++ b/WHATS_NEW +@@ -1,3 +1,7 @@ ++Version 2.03.10 - ++================================= ++ Fix support for lvconvert --repair used by foreign apps (i.e. Docker). ++ + Version 2.03.09 - 26th March 2020 + ================================= + Fix formating of vdopool (vdo_slab_size_mb was smaller by 2 bits). diff --git a/SOURCES/lvm2-2_03_10-blkdeactivate-add-support-for-VDO-in-blkdeactivate-script.patch b/SOURCES/lvm2-2_03_10-blkdeactivate-add-support-for-VDO-in-blkdeactivate-script.patch new file mode 100644 index 0000000..6abd26b --- /dev/null +++ b/SOURCES/lvm2-2_03_10-blkdeactivate-add-support-for-VDO-in-blkdeactivate-script.patch @@ -0,0 +1,179 @@ + WHATS_NEW_DM | 4 ++++ + man/blkdeactivate.8_main | 11 +++++++++++ + scripts/blkdeactivate.sh.in | 48 ++++++++++++++++++++++++++++++++++++++++++++- + 3 files changed, 62 insertions(+), 1 deletion(-) + +diff --git a/WHATS_NEW_DM b/WHATS_NEW_DM +index 12bdcea..3ec9c3c 100644 +--- a/WHATS_NEW_DM ++++ b/WHATS_NEW_DM +@@ -1,3 +1,7 @@ ++Version 1.02.173 - ++================================== ++ Add support for VDO in blkdeactivate script. ++ + Version 1.02.171 - 26th March 2020 + ================================== + Fix dm_list interators with gcc 10 optimization (-ftree-pta). +diff --git a/man/blkdeactivate.8_main b/man/blkdeactivate.8_main +index f3c19a8..06af52e 100644 +--- a/man/blkdeactivate.8_main ++++ b/man/blkdeactivate.8_main +@@ -9,6 +9,7 @@ blkdeactivate \(em utility to deactivate block devices + .RB [ -l \ \fIlvm_options\fP ] + .RB [ -m \ \fImpath_options\fP ] + .RB [ -r \ \fImdraid_options\fP ] ++.RB [ -o \ \fIvdo_options\fP ] + .RB [ -u ] + .RB [ -v ] + .RI [ device ] +@@ -70,6 +71,15 @@ Comma-separated list of MD RAID specific options: + Wait MD device's resync, recovery or reshape action to complete + before deactivation. + .RE ++ ++.TP ++.BR -o ", " --vdooptions \ \fIvdo_options\fP ++Comma-separated list of VDO specific options: ++.RS ++.IP \fIconfigfile=file\fP ++Use specified VDO configuration file. ++.RE ++ + .TP + .BR -u ", " --umount + Unmount a mounted device before trying to deactivate it. +@@ -120,4 +130,5 @@ of a device-mapper device fails, retry it and force removal. + .BR lvm (8), + .BR mdadm (8), + .BR multipathd (8), ++.BR vdo (8), + .BR umount (8) +diff --git a/scripts/blkdeactivate.sh.in b/scripts/blkdeactivate.sh.in +index a4b8a8f..57b3e58 100644 +--- a/scripts/blkdeactivate.sh.in ++++ b/scripts/blkdeactivate.sh.in +@@ -1,6 +1,6 @@ + #!/bin/bash + # +-# Copyright (C) 2012-2017 Red Hat, Inc. All rights reserved. ++# Copyright (C) 2012-2020 Red Hat, Inc. All rights reserved. + # + # This file is part of LVM2. + # +@@ -38,6 +38,7 @@ MDADM="/sbin/mdadm" + MOUNTPOINT="/bin/mountpoint" + MPATHD="/sbin/multipathd" + UMOUNT="/bin/umount" ++VDO="/bin/vdo" + + sbindir="@SBINDIR@" + DMSETUP="$sbindir/dmsetup" +@@ -54,6 +55,7 @@ DMSETUP_OPTS="" + LVM_OPTS="" + MDADM_OPTS="" + MPATHD_OPTS="" ++VDO_OPTS="" + + LSBLK="/bin/lsblk -r --noheadings -o TYPE,KNAME,NAME,MOUNTPOINT" + LSBLK_VARS="local devtype local kname local name local mnt" +@@ -124,6 +126,7 @@ usage() { + echo " -l | --lvmoptions LVM_OPTIONS Comma separated LVM specific options" + echo " -m | --mpathoptions MPATH_OPTIONS Comma separated DM-multipath specific options" + echo " -r | --mdraidoptions MDRAID_OPTIONS Comma separated MD RAID specific options" ++ echo " -o | --vdooptions VDO_OPTIONS Comma separated VDO specific options" + echo " -u | --umount Unmount the device if mounted" + echo " -v | --verbose Verbose mode (also implies -e)" + echo +@@ -138,6 +141,8 @@ usage() { + echo " wait wait for resync, recovery or reshape to complete first" + echo " MPATH_OPTIONS:" + echo " disablequeueing disable queueing on all DM-multipath devices first" ++ echo " VDO_OPTIONS:" ++ echo " configfile=file use specified VDO configuration file" + + exit + } +@@ -319,6 +324,23 @@ deactivate_md () { + fi + } + ++deactivate_vdo() { ++ local xname ++ xname=$(printf "%s" "$name") ++ test -b "$DEV_DIR/mapper/$xname" || return 0 ++ test -z "${SKIP_DEVICE_LIST["$kname"]}" || return 1 ++ ++ deactivate_holders "$DEV_DIR/mapper/$xname" || return 1 ++ ++ echo -n " [VDO]: deactivating VDO volume $xname... " ++ if eval "$VDO" stop $VDO_OPTS --name="$xname" "$OUT" "$ERR"; then ++ echo "done" ++ else ++ echo "skipping" ++ add_device_to_skip_list ++ fi ++} ++ + deactivate () { + ###################################################################### + # DEACTIVATION HOOKS FOR NEW DEVICE TYPES GO HERE! # +@@ -335,6 +357,8 @@ deactivate () { + ###################################################################### + if test "$devtype" = "lvm"; then + deactivate_lvm ++ elif test "$devtype" = "vdo"; then ++ deactivate_vdo + elif test "${kname:0:3}" = "dm-"; then + deactivate_dm + elif test "${kname:0:2}" = "md"; then +@@ -479,6 +503,20 @@ get_mpathopts() { + IFS=$ORIG_IFS + } + ++get_vdoopts() { ++ ORIG_IFS=$IFS; IFS=',' ++ ++ for opt in $1; do ++ case "$opt" in ++ "") ;; ++ configfile=*) tmp=${opt#*=}; VDO_OPTS+="--confFile=${tmp%%,*} " ;; ++ *) echo "$opt: unknown VDO option" ++ esac ++ done ++ ++ IFS=$ORIG_IFS ++} ++ + set_env() { + if test "$ERRORS" -eq "1"; then + unset ERR +@@ -493,6 +531,7 @@ set_env() { + LVM_OPTS+="-vvvv" + MDADM_OPTS+="-vv" + MPATHD_OPTS+="-v 3" ++ VDO_OPTS+="--verbose " + else + OUT="1>$DEV_DIR/null" + fi +@@ -509,6 +548,12 @@ set_env() { + MDADM_AVAILABLE=0 + fi + ++ if test -f $VDO; then ++ VDO_AVAILABLE=1 ++ else ++ VDO_AVAILABLE=0 ++ fi ++ + MPATHD_RUNNING=0 + test "$MPATHD_DO_DISABLEQUEUEING" -eq 1 && { + if test -f "$MPATHD"; then +@@ -528,6 +573,7 @@ while test $# -ne 0; do + "-l"|"--lvmoptions") get_lvmopts "$2" ; shift ;; + "-m"|"--mpathoptions") get_mpathopts "$2" ; shift ;; + "-r"|"--mdraidoptions") get_mdraidopts "$2"; shift ;; ++ "-o"|"--vdooptions") get_vdoopts "$2"; shift ;; + "-u"|"--umount") DO_UMOUNT=1 ;; + "-v"|"--verbose") VERBOSE=1 ; ERRORS=1 ;; + "-vv") VERBOSE=1 ; ERRORS=1 ; set -x ;; diff --git a/SOURCES/lvm2-2_03_10-build-make-generate.patch b/SOURCES/lvm2-2_03_10-build-make-generate.patch new file mode 100644 index 0000000..4b76f58 --- /dev/null +++ b/SOURCES/lvm2-2_03_10-build-make-generate.patch @@ -0,0 +1,180 @@ +From 7def94164ae6c18d84e40f00db2e2b74a7662b35 Mon Sep 17 00:00:00 2001 +From: Marian Csontos +Date: Tue, 5 May 2020 10:20:18 +0200 +Subject: [PATCH] build: make generate + +(cherry picked from commit bcc149048440dce5fc7962f88ed523469dd39a32) +--- + man/lvconvert.8_pregen | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ + man/lvcreate.8_pregen | 53 ++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 116 insertions(+) + +diff --git a/man/lvconvert.8_pregen b/man/lvconvert.8_pregen +index b676e72..7440984 100644 +--- a/man/lvconvert.8_pregen ++++ b/man/lvconvert.8_pregen +@@ -163,6 +163,18 @@ lvconvert - Change logical volume layout + .ad b + .br + .ad l ++ \fB--raidintegrity\fP \fBy\fP|\fBn\fP ++.ad b ++.br ++.ad l ++ \fB--raidintegrityblocksize\fP \fINumber\fP ++.ad b ++.br ++.ad l ++ \fB--raidintegritymode\fP \fIString\fP ++.ad b ++.br ++.ad l + \fB-r\fP|\fB--readahead\fP \fBauto\fP|\fBnone\fP|\fINumber\fP + .ad b + .br +@@ -982,6 +994,28 @@ Poll LV to continue conversion. + .br + - + ++Add or remove data integrity checksums to raid images. ++.br ++.P ++\fBlvconvert\fP \fB--raidintegrity\fP \fBy\fP|\fBn\fP \fILV\fP\fI_raid\fP ++.br ++.RS 4 ++.ad l ++[ \fB--raidintegritymode\fP \fIString\fP ] ++.ad b ++.br ++.ad l ++[ \fB--raidintegrityblocksize\fP \fINumber\fP ] ++.ad b ++.br ++[ COMMON_OPTIONS ] ++.RE ++.br ++.RS 4 ++[ \fIPV\fP ... ] ++.RE ++- ++ + Common options for command: + . + .RS 4 +@@ -1405,6 +1439,35 @@ Repeat once to also suppress any prompts with answer 'no'. + .ad b + .HP + .ad l ++\fB--raidintegrity\fP \fBy\fP|\fBn\fP ++.br ++Enable or disable data integrity checksums for raid images. ++.ad b ++.HP ++.ad l ++\fB--raidintegrityblocksize\fP \fINumber\fP ++.br ++The block size to use for dm-integrity on raid images. ++The integrity block size should usually match the device ++logical block size, or the file system block size. ++It may be less than the file system block size, but not ++less than the device logical block size. ++Possible values: 512, 1024, 2048, 4096. ++.ad b ++.HP ++.ad l ++\fB--raidintegritymode\fP \fIString\fP ++.br ++Use a journal (default) or bitmap for keeping integrity checksums consistent ++in case of a crash. The bitmap areas are recalculated after a crash, so corruption ++in those areas would not be detected. A journal does not have this problem. ++The journal mode doubles writes to storage, but can improve performance for ++scattered writes packed into a single journal write. ++bitmap mode can in theory achieve full write throughput of the device, ++but would not benefit from the potential scattered write optimization. ++.ad b ++.HP ++.ad l + \fB-r\fP|\fB--readahead\fP \fBauto\fP|\fBnone\fP|\fINumber\fP + .br + Sets read ahead sector count of an LV. +diff --git a/man/lvcreate.8_pregen b/man/lvcreate.8_pregen +index a80f9f5..be8e783 100644 +--- a/man/lvcreate.8_pregen ++++ b/man/lvcreate.8_pregen +@@ -187,6 +187,18 @@ lvcreate - Create a logical volume + .ad b + .br + .ad l ++ \fB--raidintegrity\fP \fBy\fP|\fBn\fP ++.ad b ++.br ++.ad l ++ \fB--raidintegrityblocksize\fP \fINumber\fP ++.ad b ++.br ++.ad l ++ \fB--raidintegritymode\fP \fIString\fP ++.ad b ++.br ++.ad l + \fB-r\fP|\fB--readahead\fP \fBauto\fP|\fBnone\fP|\fINumber\fP + .ad b + .br +@@ -425,6 +437,18 @@ Create a raid LV (a specific raid level must be used, e.g. raid1). + [ \fB--[raid]maxrecoveryrate\fP \fISize\fP[k|UNIT] ] + .ad b + .br ++.ad l ++[ \fB--raidintegrity\fP \fBy\fP|\fBn\fP ] ++.ad b ++.br ++.ad l ++[ \fB--raidintegritymode\fP \fIString\fP ] ++.ad b ++.br ++.ad l ++[ \fB--raidintegrityblocksize\fP \fINumber\fP ] ++.ad b ++.br + [ COMMON_OPTIONS ] + .RE + .br +@@ -1420,6 +1444,35 @@ Repeat once to also suppress any prompts with answer 'no'. + .ad b + .HP + .ad l ++\fB--raidintegrity\fP \fBy\fP|\fBn\fP ++.br ++Enable or disable data integrity checksums for raid images. ++.ad b ++.HP ++.ad l ++\fB--raidintegrityblocksize\fP \fINumber\fP ++.br ++The block size to use for dm-integrity on raid images. ++The integrity block size should usually match the device ++logical block size, or the file system block size. ++It may be less than the file system block size, but not ++less than the device logical block size. ++Possible values: 512, 1024, 2048, 4096. ++.ad b ++.HP ++.ad l ++\fB--raidintegritymode\fP \fIString\fP ++.br ++Use a journal (default) or bitmap for keeping integrity checksums consistent ++in case of a crash. The bitmap areas are recalculated after a crash, so corruption ++in those areas would not be detected. A journal does not have this problem. ++The journal mode doubles writes to storage, but can improve performance for ++scattered writes packed into a single journal write. ++bitmap mode can in theory achieve full write throughput of the device, ++but would not benefit from the potential scattered write optimization. ++.ad b ++.HP ++.ad l + \fB-r\fP|\fB--readahead\fP \fBauto\fP|\fBnone\fP|\fINumber\fP + .br + Sets read ahead sector count of an LV. +-- +1.8.3.1 + diff --git a/SOURCES/lvm2-2_03_10-lvconvert-no-validation-for-thin-pools-not-used-by-lvm.patch b/SOURCES/lvm2-2_03_10-lvconvert-no-validation-for-thin-pools-not-used-by-lvm.patch new file mode 100644 index 0000000..90ca054 --- /dev/null +++ b/SOURCES/lvm2-2_03_10-lvconvert-no-validation-for-thin-pools-not-used-by-lvm.patch @@ -0,0 +1,17 @@ + tools/lvconvert.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/tools/lvconvert.c b/tools/lvconvert.c +index f6d9a29..bb40930 100644 +--- a/tools/lvconvert.c ++++ b/tools/lvconvert.c +@@ -2388,7 +2388,8 @@ static int _lvconvert_thin_pool_repair(struct cmd_context *cmd, + goto deactivate_mlv; + } + +- if (thin_dump[0]) { ++ /* Check matching transactionId when thin-pool is used by lvm2 (transactionId != 0) */ ++ if (first_seg(pool_lv)->transaction_id && thin_dump[0]) { + argv[0] = thin_dump; + argv[1] = pms_path; + argv[2] = NULL; diff --git a/SOURCES/lvm2-2_03_10-move-pv_list-code-into-lib.patch b/SOURCES/lvm2-2_03_10-move-pv_list-code-into-lib.patch new file mode 100644 index 0000000..ad900bb --- /dev/null +++ b/SOURCES/lvm2-2_03_10-move-pv_list-code-into-lib.patch @@ -0,0 +1,641 @@ +From 945de675c47d891d1f181f15971d26ff959ac631 Mon Sep 17 00:00:00 2001 +From: David Teigland +Date: Tue, 14 Jan 2020 14:12:20 -0600 +Subject: [PATCH 1/3] move pv_list code into lib + +(cherry picked from commit b6b4ad8e28eff7476cb04c4cb93312b06605b82f) +--- + lib/Makefile.in | 1 + + lib/metadata/metadata-exported.h | 4 + + lib/metadata/pv_list.c | 291 +++++++++++++++++++++++++++++++++++++++ + tools/toollib.c | 270 ------------------------------------ + tools/toollib.h | 9 -- + 5 files changed, 296 insertions(+), 279 deletions(-) + create mode 100644 lib/metadata/pv_list.c + +diff --git a/lib/Makefile.in b/lib/Makefile.in +index c037b41..2a064f3 100644 +--- a/lib/Makefile.in ++++ b/lib/Makefile.in +@@ -74,6 +74,7 @@ SOURCES =\ + metadata/mirror.c \ + metadata/pool_manip.c \ + metadata/pv.c \ ++ metadata/pv_list.c \ + metadata/pv_manip.c \ + metadata/pv_map.c \ + metadata/raid_manip.c \ +diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h +index c61c85c..35c1231 100644 +--- a/lib/metadata/metadata-exported.h ++++ b/lib/metadata/metadata-exported.h +@@ -1385,4 +1385,8 @@ int vg_is_foreign(struct volume_group *vg); + + void vg_write_commit_bad_mdas(struct cmd_context *cmd, struct volume_group *vg); + ++struct dm_list *create_pv_list(struct dm_pool *mem, struct volume_group *vg, int argc, ++ char **argv, int allocatable_only); ++struct dm_list *clone_pv_list(struct dm_pool *mem, struct dm_list *pvsl); ++ + #endif +diff --git a/lib/metadata/pv_list.c b/lib/metadata/pv_list.c +new file mode 100644 +index 0000000..143b573 +--- /dev/null ++++ b/lib/metadata/pv_list.c +@@ -0,0 +1,291 @@ ++/* ++ * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. ++ * Copyright (C) 2004-2017 Red Hat, Inc. All rights reserved. ++ * ++ * This file is part of LVM2. ++ * ++ * This copyrighted material is made available to anyone wishing to use, ++ * modify, copy, or redistribute it subject to the terms and conditions ++ * of the GNU Lesser General Public License v.2.1. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with this program; if not, write to the Free Software Foundation, ++ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include "lib/misc/lib.h" ++#include "lib/misc/lvm-string.h" ++#include "lib/datastruct/str_list.h" ++#include "lib/device/device.h" ++#include "lib/metadata/metadata.h" ++ ++/* ++ * Process physical extent range specifiers ++ */ ++static int _add_pe_range(struct dm_pool *mem, const char *pvname, ++ struct dm_list *pe_ranges, uint32_t start, uint32_t count) ++{ ++ struct pe_range *per; ++ ++ log_debug("Adding PE range: start PE " FMTu32 " length " FMTu32 " on %s.", ++ start, count, pvname); ++ ++ /* Ensure no overlap with existing areas */ ++ dm_list_iterate_items(per, pe_ranges) { ++ if (((start < per->start) && (start + count - 1 >= per->start)) || ++ ((start >= per->start) && ++ (per->start + per->count - 1) >= start)) { ++ log_error("Overlapping PE ranges specified (" FMTu32 ++ "-" FMTu32 ", " FMTu32 "-" FMTu32 ") on %s.", ++ start, start + count - 1, per->start, ++ per->start + per->count - 1, pvname); ++ return 0; ++ } ++ } ++ ++ if (!(per = dm_pool_alloc(mem, sizeof(*per)))) { ++ log_error("Allocation of list failed."); ++ return 0; ++ } ++ ++ per->start = start; ++ per->count = count; ++ dm_list_add(pe_ranges, &per->list); ++ ++ return 1; ++} ++ ++static int _xstrtouint32(const char *s, char **p, int base, uint32_t *result) ++{ ++ unsigned long ul; ++ ++ errno = 0; ++ ul = strtoul(s, p, base); ++ ++ if (errno || *p == s || ul > UINT32_MAX) ++ return 0; ++ ++ *result = ul; ++ ++ return 1; ++} ++ ++static int _parse_pes(struct dm_pool *mem, char *c, struct dm_list *pe_ranges, ++ const char *pvname, uint32_t size) ++{ ++ char *endptr; ++ uint32_t start, end, len; ++ ++ /* Default to whole PV */ ++ if (!c) { ++ if (!_add_pe_range(mem, pvname, pe_ranges, UINT32_C(0), size)) ++ return_0; ++ return 1; ++ } ++ ++ while (*c) { ++ if (*c != ':') ++ goto error; ++ ++ c++; ++ ++ /* Disallow :: and :\0 */ ++ if (*c == ':' || !*c) ++ goto error; ++ ++ /* Default to whole range */ ++ start = UINT32_C(0); ++ end = size - 1; ++ ++ /* Start extent given? */ ++ if (isdigit(*c)) { ++ if (!_xstrtouint32(c, &endptr, 10, &start)) ++ goto error; ++ c = endptr; ++ /* Just one number given? */ ++ if (!*c || *c == ':') ++ end = start; ++ } ++ /* Range? */ ++ if (*c == '-') { ++ c++; ++ if (isdigit(*c)) { ++ if (!_xstrtouint32(c, &endptr, 10, &end)) ++ goto error; ++ c = endptr; ++ } ++ } else if (*c == '+') { /* Length? */ ++ c++; ++ if (isdigit(*c)) { ++ if (!_xstrtouint32(c, &endptr, 10, &len)) ++ goto error; ++ c = endptr; ++ end = start + (len ? (len - 1) : 0); ++ } ++ } ++ ++ if (*c && *c != ':') ++ goto error; ++ ++ if ((start > end) || (end > size - 1)) { ++ log_error("PE range error: start extent %" PRIu32 " to " ++ "end extent %" PRIu32 ".", start, end); ++ return 0; ++ } ++ ++ if (!_add_pe_range(mem, pvname, pe_ranges, start, end - start + 1)) ++ return_0; ++ ++ } ++ ++ return 1; ++ ++ error: ++ log_error("Physical extent parsing error at %s.", c); ++ return 0; ++} ++ ++static int _create_pv_entry(struct dm_pool *mem, struct pv_list *pvl, ++ char *colon, int allocatable_only, struct dm_list *r) ++{ ++ const char *pvname; ++ struct pv_list *new_pvl = NULL, *pvl2; ++ struct dm_list *pe_ranges; ++ ++ pvname = pv_dev_name(pvl->pv); ++ if (allocatable_only && !(pvl->pv->status & ALLOCATABLE_PV)) { ++ log_warn("WARNING: Physical volume %s not allocatable.", pvname); ++ return 1; ++ } ++ ++ if (allocatable_only && is_missing_pv(pvl->pv)) { ++ log_warn("WARNING: Physical volume %s is missing.", pvname); ++ return 1; ++ } ++ ++ if (allocatable_only && ++ (pvl->pv->pe_count == pvl->pv->pe_alloc_count)) { ++ log_warn("WARNING: No free extents on physical volume \"%s\".", pvname); ++ return 1; ++ } ++ ++ dm_list_iterate_items(pvl2, r) ++ if (pvl->pv->dev == pvl2->pv->dev) { ++ new_pvl = pvl2; ++ break; ++ } ++ ++ if (!new_pvl) { ++ if (!(new_pvl = dm_pool_alloc(mem, sizeof(*new_pvl)))) { ++ log_error("Unable to allocate physical volume list."); ++ return 0; ++ } ++ ++ memcpy(new_pvl, pvl, sizeof(*new_pvl)); ++ ++ if (!(pe_ranges = dm_pool_alloc(mem, sizeof(*pe_ranges)))) { ++ log_error("Allocation of pe_ranges list failed."); ++ return 0; ++ } ++ dm_list_init(pe_ranges); ++ new_pvl->pe_ranges = pe_ranges; ++ dm_list_add(r, &new_pvl->list); ++ } ++ ++ /* Determine selected physical extents */ ++ if (!_parse_pes(mem, colon, new_pvl->pe_ranges, pv_dev_name(pvl->pv), ++ pvl->pv->pe_count)) ++ return_0; ++ ++ return 1; ++} ++ ++struct dm_list *create_pv_list(struct dm_pool *mem, struct volume_group *vg, int argc, ++ char **argv, int allocatable_only) ++{ ++ struct dm_list *r; ++ struct pv_list *pvl; ++ struct dm_list tagsl, arg_pvnames; ++ char *pvname = NULL; ++ char *colon, *at_sign, *tagname; ++ int i; ++ ++ /* Build up list of PVs */ ++ if (!(r = dm_pool_alloc(mem, sizeof(*r)))) { ++ log_error("Allocation of list failed."); ++ return NULL; ++ } ++ dm_list_init(r); ++ ++ dm_list_init(&tagsl); ++ dm_list_init(&arg_pvnames); ++ ++ for (i = 0; i < argc; i++) { ++ dm_unescape_colons_and_at_signs(argv[i], &colon, &at_sign); ++ ++ if (at_sign && (at_sign == argv[i])) { ++ tagname = at_sign + 1; ++ if (!validate_tag(tagname)) { ++ log_error("Skipping invalid tag %s.", tagname); ++ continue; ++ } ++ dm_list_iterate_items(pvl, &vg->pvs) { ++ if (str_list_match_item(&pvl->pv->tags, ++ tagname)) { ++ if (!_create_pv_entry(mem, pvl, NULL, ++ allocatable_only, ++ r)) ++ return_NULL; ++ } ++ } ++ continue; ++ } ++ ++ pvname = argv[i]; ++ ++ if (colon && !(pvname = dm_pool_strndup(mem, pvname, ++ (unsigned) (colon - pvname)))) { ++ log_error("Failed to clone PV name."); ++ return NULL; ++ } ++ ++ if (!(pvl = find_pv_in_vg(vg, pvname))) { ++ log_error("Physical Volume \"%s\" not found in " ++ "Volume Group \"%s\".", pvname, vg->name); ++ return NULL; ++ } ++ if (!_create_pv_entry(mem, pvl, colon, allocatable_only, r)) ++ return_NULL; ++ } ++ ++ if (dm_list_empty(r)) ++ log_error("No specified PVs have space available."); ++ ++ return dm_list_empty(r) ? NULL : r; ++} ++ ++struct dm_list *clone_pv_list(struct dm_pool *mem, struct dm_list *pvsl) ++{ ++ struct dm_list *r; ++ struct pv_list *pvl, *new_pvl; ++ ++ /* Build up list of PVs */ ++ if (!(r = dm_pool_alloc(mem, sizeof(*r)))) { ++ log_error("Allocation of list failed."); ++ return NULL; ++ } ++ dm_list_init(r); ++ ++ dm_list_iterate_items(pvl, pvsl) { ++ if (!(new_pvl = dm_pool_zalloc(mem, sizeof(*new_pvl)))) { ++ log_error("Unable to allocate physical volume list."); ++ return NULL; ++ } ++ ++ memcpy(new_pvl, pvl, sizeof(*new_pvl)); ++ dm_list_add(r, &new_pvl->list); ++ } ++ ++ return r; ++} ++ +diff --git a/tools/toollib.c b/tools/toollib.c +index a5304bf..6386a69 100644 +--- a/tools/toollib.c ++++ b/tools/toollib.c +@@ -457,276 +457,6 @@ const char *extract_vgname(struct cmd_context *cmd, const char *lv_name) + return vg_name; + } + +-/* +- * Process physical extent range specifiers +- */ +-static int _add_pe_range(struct dm_pool *mem, const char *pvname, +- struct dm_list *pe_ranges, uint32_t start, uint32_t count) +-{ +- struct pe_range *per; +- +- log_debug("Adding PE range: start PE " FMTu32 " length " FMTu32 " on %s.", +- start, count, pvname); +- +- /* Ensure no overlap with existing areas */ +- dm_list_iterate_items(per, pe_ranges) { +- if (((start < per->start) && (start + count - 1 >= per->start)) || +- ((start >= per->start) && +- (per->start + per->count - 1) >= start)) { +- log_error("Overlapping PE ranges specified (" FMTu32 +- "-" FMTu32 ", " FMTu32 "-" FMTu32 ") on %s.", +- start, start + count - 1, per->start, +- per->start + per->count - 1, pvname); +- return 0; +- } +- } +- +- if (!(per = dm_pool_alloc(mem, sizeof(*per)))) { +- log_error("Allocation of list failed."); +- return 0; +- } +- +- per->start = start; +- per->count = count; +- dm_list_add(pe_ranges, &per->list); +- +- return 1; +-} +- +-static int _xstrtouint32(const char *s, char **p, int base, uint32_t *result) +-{ +- unsigned long ul; +- +- errno = 0; +- ul = strtoul(s, p, base); +- +- if (errno || *p == s || ul > UINT32_MAX) +- return 0; +- +- *result = ul; +- +- return 1; +-} +- +-static int _parse_pes(struct dm_pool *mem, char *c, struct dm_list *pe_ranges, +- const char *pvname, uint32_t size) +-{ +- char *endptr; +- uint32_t start, end, len; +- +- /* Default to whole PV */ +- if (!c) { +- if (!_add_pe_range(mem, pvname, pe_ranges, UINT32_C(0), size)) +- return_0; +- return 1; +- } +- +- while (*c) { +- if (*c != ':') +- goto error; +- +- c++; +- +- /* Disallow :: and :\0 */ +- if (*c == ':' || !*c) +- goto error; +- +- /* Default to whole range */ +- start = UINT32_C(0); +- end = size - 1; +- +- /* Start extent given? */ +- if (isdigit(*c)) { +- if (!_xstrtouint32(c, &endptr, 10, &start)) +- goto error; +- c = endptr; +- /* Just one number given? */ +- if (!*c || *c == ':') +- end = start; +- } +- /* Range? */ +- if (*c == '-') { +- c++; +- if (isdigit(*c)) { +- if (!_xstrtouint32(c, &endptr, 10, &end)) +- goto error; +- c = endptr; +- } +- } else if (*c == '+') { /* Length? */ +- c++; +- if (isdigit(*c)) { +- if (!_xstrtouint32(c, &endptr, 10, &len)) +- goto error; +- c = endptr; +- end = start + (len ? (len - 1) : 0); +- } +- } +- +- if (*c && *c != ':') +- goto error; +- +- if ((start > end) || (end > size - 1)) { +- log_error("PE range error: start extent %" PRIu32 " to " +- "end extent %" PRIu32 ".", start, end); +- return 0; +- } +- +- if (!_add_pe_range(mem, pvname, pe_ranges, start, end - start + 1)) +- return_0; +- +- } +- +- return 1; +- +- error: +- log_error("Physical extent parsing error at %s.", c); +- return 0; +-} +- +-static int _create_pv_entry(struct dm_pool *mem, struct pv_list *pvl, +- char *colon, int allocatable_only, struct dm_list *r) +-{ +- const char *pvname; +- struct pv_list *new_pvl = NULL, *pvl2; +- struct dm_list *pe_ranges; +- +- pvname = pv_dev_name(pvl->pv); +- if (allocatable_only && !(pvl->pv->status & ALLOCATABLE_PV)) { +- log_warn("WARNING: Physical volume %s not allocatable.", pvname); +- return 1; +- } +- +- if (allocatable_only && is_missing_pv(pvl->pv)) { +- log_warn("WARNING: Physical volume %s is missing.", pvname); +- return 1; +- } +- +- if (allocatable_only && +- (pvl->pv->pe_count == pvl->pv->pe_alloc_count)) { +- log_warn("WARNING: No free extents on physical volume \"%s\".", pvname); +- return 1; +- } +- +- dm_list_iterate_items(pvl2, r) +- if (pvl->pv->dev == pvl2->pv->dev) { +- new_pvl = pvl2; +- break; +- } +- +- if (!new_pvl) { +- if (!(new_pvl = dm_pool_alloc(mem, sizeof(*new_pvl)))) { +- log_error("Unable to allocate physical volume list."); +- return 0; +- } +- +- memcpy(new_pvl, pvl, sizeof(*new_pvl)); +- +- if (!(pe_ranges = dm_pool_alloc(mem, sizeof(*pe_ranges)))) { +- log_error("Allocation of pe_ranges list failed."); +- return 0; +- } +- dm_list_init(pe_ranges); +- new_pvl->pe_ranges = pe_ranges; +- dm_list_add(r, &new_pvl->list); +- } +- +- /* Determine selected physical extents */ +- if (!_parse_pes(mem, colon, new_pvl->pe_ranges, pv_dev_name(pvl->pv), +- pvl->pv->pe_count)) +- return_0; +- +- return 1; +-} +- +-struct dm_list *create_pv_list(struct dm_pool *mem, struct volume_group *vg, int argc, +- char **argv, int allocatable_only) +-{ +- struct dm_list *r; +- struct pv_list *pvl; +- struct dm_list tagsl, arg_pvnames; +- char *pvname = NULL; +- char *colon, *at_sign, *tagname; +- int i; +- +- /* Build up list of PVs */ +- if (!(r = dm_pool_alloc(mem, sizeof(*r)))) { +- log_error("Allocation of list failed."); +- return NULL; +- } +- dm_list_init(r); +- +- dm_list_init(&tagsl); +- dm_list_init(&arg_pvnames); +- +- for (i = 0; i < argc; i++) { +- dm_unescape_colons_and_at_signs(argv[i], &colon, &at_sign); +- +- if (at_sign && (at_sign == argv[i])) { +- tagname = at_sign + 1; +- if (!validate_tag(tagname)) { +- log_error("Skipping invalid tag %s.", tagname); +- continue; +- } +- dm_list_iterate_items(pvl, &vg->pvs) { +- if (str_list_match_item(&pvl->pv->tags, +- tagname)) { +- if (!_create_pv_entry(mem, pvl, NULL, +- allocatable_only, +- r)) +- return_NULL; +- } +- } +- continue; +- } +- +- pvname = argv[i]; +- +- if (colon && !(pvname = dm_pool_strndup(mem, pvname, +- (unsigned) (colon - pvname)))) { +- log_error("Failed to clone PV name."); +- return NULL; +- } +- +- if (!(pvl = find_pv_in_vg(vg, pvname))) { +- log_error("Physical Volume \"%s\" not found in " +- "Volume Group \"%s\".", pvname, vg->name); +- return NULL; +- } +- if (!_create_pv_entry(mem, pvl, colon, allocatable_only, r)) +- return_NULL; +- } +- +- if (dm_list_empty(r)) +- log_error("No specified PVs have space available."); +- +- return dm_list_empty(r) ? NULL : r; +-} +- +-struct dm_list *clone_pv_list(struct dm_pool *mem, struct dm_list *pvsl) +-{ +- struct dm_list *r; +- struct pv_list *pvl, *new_pvl; +- +- /* Build up list of PVs */ +- if (!(r = dm_pool_alloc(mem, sizeof(*r)))) { +- log_error("Allocation of list failed."); +- return NULL; +- } +- dm_list_init(r); +- +- dm_list_iterate_items(pvl, pvsl) { +- if (!(new_pvl = dm_pool_zalloc(mem, sizeof(*new_pvl)))) { +- log_error("Unable to allocate physical volume list."); +- return NULL; +- } +- +- memcpy(new_pvl, pvl, sizeof(*new_pvl)); +- dm_list_add(r, &new_pvl->list); +- } +- +- return r; +-} +- + const char _pe_size_may_not_be_negative_msg[] = "Physical extent size may not be negative."; + + int vgcreate_params_set_defaults(struct cmd_context *cmd, +diff --git a/tools/toollib.h b/tools/toollib.h +index 9102f55..53a5e5b 100644 +--- a/tools/toollib.h ++++ b/tools/toollib.h +@@ -182,15 +182,6 @@ void opt_array_to_str(struct cmd_context *cmd, int *opts, int count, + int pvcreate_params_from_args(struct cmd_context *cmd, struct pvcreate_params *pp); + int pvcreate_each_device(struct cmd_context *cmd, struct processing_handle *handle, struct pvcreate_params *pp); + +-/* +- * Builds a list of pv's from the names in argv. Used in +- * lvcreate/extend. +- */ +-struct dm_list *create_pv_list(struct dm_pool *mem, struct volume_group *vg, int argc, +- char **argv, int allocatable_only); +- +-struct dm_list *clone_pv_list(struct dm_pool *mem, struct dm_list *pvs); +- + int vgcreate_params_set_defaults(struct cmd_context *cmd, + struct vgcreate_params *vp_def, + struct volume_group *vg); +-- +1.8.3.1 + diff --git a/SOURCES/lvm2-2_03_10-test-repair-of-thin-pool-used-by-foreign-apps.patch b/SOURCES/lvm2-2_03_10-test-repair-of-thin-pool-used-by-foreign-apps.patch new file mode 100644 index 0000000..c05d18d --- /dev/null +++ b/SOURCES/lvm2-2_03_10-test-repair-of-thin-pool-used-by-foreign-apps.patch @@ -0,0 +1,82 @@ + test/shell/thin-foreign-repair.sh | 72 +++++++++++++++++++++++++++++++++++++++ + 1 file changed, 72 insertions(+) + create mode 100644 test/shell/thin-foreign-repair.sh + +diff --git a/test/shell/thin-foreign-repair.sh b/test/shell/thin-foreign-repair.sh +new file mode 100644 +index 0000000..147a9a0 +--- /dev/null ++++ b/test/shell/thin-foreign-repair.sh +@@ -0,0 +1,72 @@ ++#!/usr/bin/env bash ++ ++# Copyright (C) 2020 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++# test foreing user of thin-pool ++ ++ ++SKIP_WITH_LVMPOLLD=1 ++ ++. lib/inittest ++ ++cleanup_mounted_and_teardown() ++{ ++ dmsetup remove $THIN || true ++ vgremove -ff $vg ++ aux teardown ++} ++ ++# ++# Main ++# ++aux have_thin 1 0 0 || skip ++which mkfs.ext4 || skip ++ ++# Use our mkfs config file to get approximately same results ++# TODO: maybe use it for all test via some 'prepare' function ++export MKE2FS_CONFIG="$TESTOLDPWD/lib/mke2fs.conf" ++ ++aux prepare_vg 2 64 ++ ++# Create named pool only ++lvcreate -L2 -T $vg/pool ++ ++POOL="$vg-pool" ++THIN="${PREFIX}_thin" ++ ++# Foreing user is using own ioctl command to create thin devices ++dmsetup message $POOL 0 "create_thin 0" ++dmsetup message $POOL 0 "set_transaction_id 0 2" ++ ++# Once the transaction id has changed, lvm2 shall not be able to create thinLV ++fail lvcreate -V10 $vg/pool ++ ++trap 'cleanup_mounted_and_teardown' EXIT ++ ++# 20M thin device ++dmsetup create "$THIN" --table "0 40960 thin $DM_DEV_DIR/mapper/$POOL 0" ++ ++mkfs.ext4 "$DM_DEV_DIR/mapper/$THIN" ++ ++dmsetup remove "$THIN" ++ ++lvchange -an $vg/pool ++ ++# Repair thin-pool used by 'foreing' apps (setting their own tid) ++lvconvert --repair $vg/pool 2>&1 | tee out ++ ++not grep "Transaction id" out ++ ++lvchange -ay $vg/pool ++ ++dmsetup create "$THIN" --table "0 40960 thin $DM_DEV_DIR/mapper/$POOL 0" ++ ++fsck -n "$DM_DEV_DIR/mapper/$THIN" diff --git a/SOURCES/lvm2-rhel8.patch b/SOURCES/lvm2-rhel8.patch index 8b6e101..5a2a411 100644 --- a/SOURCES/lvm2-rhel8.patch +++ b/SOURCES/lvm2-rhel8.patch @@ -3,17 +3,16 @@ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION -index 717c4cf..b7fddf4 100644 +index 8c4a9a8..00618e0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ --2.03.07(2) (2019-11-30) -+2.03.07(2)-RHEL8 (2019-11-30) +-2.03.09(2) (2020-03-26) ++2.03.09(2)-RHEL8 (2020-04-21) diff --git a/VERSION_DM b/VERSION_DM -index a230ed0..fc0ec81 100644 +index 0ae62fd..b9ec43e 100644 --- a/VERSION_DM +++ b/VERSION_DM @@ -1 +1 @@ --1.02.167 (2019-11-30) -+1.02.167-RHEL8 (2019-11-30) - +-1.02.171 (2020-03-26) ++1.02.171-RHEL8 (2020-04-21) diff --git a/SOURCES/lvm2-set-default-preferred_names.patch b/SOURCES/lvm2-set-default-preferred_names.patch index 853cf51..ece62c4 100644 --- a/SOURCES/lvm2-set-default-preferred_names.patch +++ b/SOURCES/lvm2-set-default-preferred_names.patch @@ -1,3 +1,9 @@ +From 6a078fe01b47fa165226a15263c8bd6350b1c307 Mon Sep 17 00:00:00 2001 +From: Marian Csontos +Date: Thu, 3 Jan 2019 13:49:08 +0100 +Subject: [PATCH 2/8] lvm2: set default preferred_names + +--- conf/example.conf.in | 3 ++- lib/config/config_settings.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) @@ -29,3 +35,6 @@ index 2bb72ba..dce9705 100644 "Select which path name to display for a block device.\n" "If multiple path names exist for a block device, and LVM needs to\n" "display a name for the device, the path names are matched against\n" +-- +1.8.3.1 + diff --git a/SOURCES/lvm2-test-skip-problematic-tests.patch b/SOURCES/lvm2-test-skip-problematic-tests.patch index 4f55e7c..80edd99 100644 --- a/SOURCES/lvm2-test-skip-problematic-tests.patch +++ b/SOURCES/lvm2-test-skip-problematic-tests.patch @@ -1,12 +1,18 @@ +From 74f05f17ea3d1a3639a65ba337f2b7df7f4981bf Mon Sep 17 00:00:00 2001 +From: Marian Csontos +Date: Sun, 18 Aug 2019 17:31:30 +0200 +Subject: [PATCH 3/8] lvm2: test: skip-problematic-tests + +--- test/dbus/lvmdbustest.py | 1 + test/shell/lvcreate-usage.sh | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/dbus/lvmdbustest.py b/test/dbus/lvmdbustest.py -index 9bc1683..1b5608c 100755 +index 8753e65..b2986bf 100755 --- a/test/dbus/lvmdbustest.py +++ b/test/dbus/lvmdbustest.py -@@ -1885,6 +1885,7 @@ class TestDbusService(unittest.TestCase): +@@ -1821,6 +1821,7 @@ class TestDbusService(unittest.TestCase): # path to it. Additionally, we will take the symlink and do a lookup # (Manager.LookUpByLvmId) using it and the original device path to # ensure that we can find the PV. @@ -15,24 +21,28 @@ index 9bc1683..1b5608c 100755 pv = self.objs[PV_INT][0] diff --git a/test/shell/lvcreate-usage.sh b/test/shell/lvcreate-usage.sh -index b1cd515..8358218 100644 +index 6d46939..9e00f1c 100644 --- a/test/shell/lvcreate-usage.sh +++ b/test/shell/lvcreate-usage.sh -@@ -182,14 +182,14 @@ DEVICE=$(dmsetup deps -o blkdevname "$dev1" | sed -e "s,.*:\ (\(.*\)),/dev/\1,") +@@ -181,15 +181,15 @@ check lv_field $vg/$lv4 lv_read_ahead "auto" + DEVICE=$(dmsetup deps -o blkdevname "$dev1" | sed -e "s,.*:\ (\(.*\)),/dev/\1,") RASZ=$(( $(blockdev --getra "$DEVICE" ) / 2 )) - SZ="$RASZ.00k" - test "$RASZ" -ge 128 || SZ="128.00k" --check lv_field $vg/$lv4 lv_kernel_read_ahead "$SZ" --units k -+should check lv_field $vg/$lv4 lv_kernel_read_ahead "$SZ" --units k - lvcreate -L 8 -n $lv5 -i2 --stripesize 16k --readahead auto $vg + test "$RASZ" -ge 128 || RASZ="128" +-check lv_field $vg/$lv4 lv_kernel_read_ahead "${RASZ}.00k" --units k ++should check lv_field $vg/$lv4 lv_kernel_read_ahead "${RASZ}.00k" --units k + lvcreate -vvvvv -L 8 -n $lv5 -i2 --stripesize 16k --readahead auto $vg check lv_field $vg/$lv5 lv_read_ahead "auto" --check lv_field $vg/$lv5 lv_kernel_read_ahead "$SZ" --units k -+should check lv_field $vg/$lv5 lv_kernel_read_ahead "$SZ" --units k + # For 16k stripe we set '128k' as the is the minimum size we get when creating DM device +-check lv_field $vg/$lv5 lv_kernel_read_ahead "128.00k" --units k ++should check lv_field $vg/$lv5 lv_kernel_read_ahead "128.00k" --units k lvcreate -L 8 -n $lv6 -i2 --stripesize 128k --readahead auto $vg check lv_field $vg/$lv6 lv_read_ahead "auto" - test "$RASZ" -ge 512 || SZ="512.00k" --check lv_field $vg/$lv6 lv_kernel_read_ahead "$SZ" --units k -+should check lv_field $vg/$lv6 lv_kernel_read_ahead "$SZ" --units k + # For striped device we set double of strip size unrelated to underlaying dev RA size +-check lv_field $vg/$lv6 lv_kernel_read_ahead "512.00k" --units k ++should check lv_field $vg/$lv6 lv_kernel_read_ahead "512.00k" --units k lvremove -ff $vg # +-- +1.8.3.1 + diff --git a/SPECS/lvm2.spec b/SPECS/lvm2.spec index c0839d8..19a9537 100644 --- a/SPECS/lvm2.spec +++ b/SPECS/lvm2.spec @@ -1,4 +1,4 @@ -%global device_mapper_version 1.02.167 +%global device_mapper_version 1.02.171 %global enable_cache 1 %global enable_cluster 1 @@ -11,6 +11,7 @@ %global enable_testsuite 1 %global enable_vdo 1 %global enable_writecache 1 +%global enable_integrity 1 %global system_release_version 23 %global systemd_version 189-3 @@ -56,14 +57,26 @@ Name: lvm2 %if 0%{?rhel} Epoch: %{rhel} %endif -Version: 2.03.07 -Release: 1%{?dist} +Version: 2.03.09 +Release: 3%{?dist} License: GPLv2 URL: http://sourceware.org/lvm2 Source0: ftp://sourceware.org/pub/lvm2/releases/LVM2.%{version}.tgz Patch0: lvm2-rhel8.patch Patch1: lvm2-set-default-preferred_names.patch Patch2: lvm2-test-skip-problematic-tests.patch +Patch3: lvm2-2_03_10-lvconvert-no-validation-for-thin-pools-not-used-by-lvm.patch +Patch4: lvm2-2_03_10-test-repair-of-thin-pool-used-by-foreign-apps.patch +Patch5: lvm2-2_03_10-WHATS_NEWS-update.patch +Patch6: lvm2-2_03_10-blkdeactivate-add-support-for-VDO-in-blkdeactivate-script.patch +Patch7: lvm2-2_03_10-Fix-scripts-lvmlocks.service.in-using-nonexistent-lock-opt-autowait.patch +Patch8: lvm2-2_03_10-move-pv_list-code-into-lib.patch +Patch9: lvm2-2_03_10-Allow-dm-integrity-to-be-used-for-raid-images.patch +Patch10: lvm2-2_03_10-WHATS_NEW-integrity-with-raid.patch +Patch11: lvm2-2_03_10-build-make-generate.patch +Patch12: 0001-Merge-master-up-to-commit-53803821de16.patch +Patch13: 0002-Merge-master-up-to-commit-be61bd6ff5c6.patch +Patch14: 0003-Merge-master-up-to-commit-6eb9eba59bf5.patch BuildRequires: gcc %if %{enable_testsuite} @@ -122,6 +135,18 @@ or more physical volumes and creating one or more logical volumes %patch0 -p1 -b .backup0 %patch1 -p1 -b .backup1 %patch2 -p1 -b .backup2 +%patch3 -p1 -b .backup3 +%patch4 -p1 -b .backup4 +%patch5 -p1 -b .backup5 +%patch6 -p1 -b .backup6 +%patch7 -p1 -b .backup7 +%patch8 -p1 -b .backup8 +%patch9 -p1 -b .backup9 +%patch10 -p1 -b .backup10 +%patch11 -p1 -b .backup11 +%patch12 -p1 -b .backup12 +%patch13 -p1 -b .backup13 +%patch14 -p1 -b .backup14 %build %global _default_pid_dir /run @@ -168,7 +193,11 @@ or more physical volumes and creating one or more logical volumes %global configure_writecache --with-writecache=internal %endif -%configure --with-default-dm-run-dir=%{_default_dm_run_dir} --with-default-run-dir=%{_default_run_dir} --with-default-pid-dir=%{_default_pid_dir} --with-default-locking-dir=%{_default_locking_dir} --with-usrlibdir=%{_libdir} --enable-fsadm --enable-write_install --with-user= --with-group= --with-device-uid=0 --with-device-gid=6 --with-device-mode=0660 --enable-pkgconfig --enable-cmdlib --enable-dmeventd --enable-blkid_wiping %{?configure_cluster} %{?configure_cmirror} %{?configure_udev} %{?configure_thin} %{?configure_cache} %{?configure_lvmpolld} %{?configure_lockd_dlm} %{?configure_lockd_sanlock} %{?configure_lvmdbusd} %{?configure_dmfilemapd} %{?configure_writecache} %{?configure_vdo} --disable-silent-rules +%if %{enable_integrity} +%global configure_integrity --with-integrity=internal +%endif + +%configure --with-default-dm-run-dir=%{_default_dm_run_dir} --with-default-run-dir=%{_default_run_dir} --with-default-pid-dir=%{_default_pid_dir} --with-default-locking-dir=%{_default_locking_dir} --with-usrlibdir=%{_libdir} --enable-fsadm --enable-write_install --with-user= --with-group= --with-device-uid=0 --with-device-gid=6 --with-device-mode=0660 --enable-pkgconfig --enable-cmdlib --enable-dmeventd --enable-blkid_wiping %{?configure_cluster} %{?configure_cmirror} %{?configure_udev} %{?configure_thin} %{?configure_cache} %{?configure_lvmpolld} %{?configure_lockd_dlm} %{?configure_lockd_sanlock} %{?configure_lvmdbusd} %{?configure_dmfilemapd} %{?configure_writecache} %{?configure_vdo} %{?configure_integrity} --disable-silent-rules make %{?_smp_mflags} @@ -722,6 +751,31 @@ An extensive functional testsuite for LVM2. %endif %changelog +* Mon Jun 29 2020 Marian Csontos - 2.03.09-3 +- Merge fixes from upstream. + +* Thu May 21 2020 Marian Csontos - 2.03.09-2 +- Merge fixes from upstream. + +* Fri Apr 24 2020 Marian Csontos - 2.03.09-1 +- Fix support for lvconvert --repair of pools used by third party applications. +- Fix formating of vdopool (vdo_slab_size_mb was smaller by 2 bits). +- Fix busy loop in dmeventd. +- Fix lvmlocks.service using incorrect option. + +* Wed Mar 18 2020 Marian Csontos - 2.03.08-3 +- Attaching writecache require inactive LV. + +* Mon Feb 24 2020 Marian Csontos - 2.03.08-2 +- Writecache bug fixes. + +* Tue Feb 11 2020 Marian Csontos - 2.03.08-1 +- VDO and writecache volume improvements. +- Prohibit reshaping of stacked raid LVs to prevent corruption. + +* Fri Feb 07 2020 Marian Csontos - 2.03.07-2 +- VDO: Adapt for changed vdo_format output. + * Sat Nov 30 2019 Marian Csontos - 2.03.07-1 - Ensure minimum required region size on striped RaidLV creation. - Fix resize of thin-pool with data and metadata of different segtype.