[5/7] mount: check that mounts tree restored without any hidden problems

Submitted by Pavel Tikhomirov on Feb. 7, 2019, 12:17 p.m.

Details

Message ID 20190207121754.19667-6-ptikhomirov@virtuozzo.com
State New
Series "mount: check mounts after restoring"
Headers show

Commit Message

Pavel Tikhomirov Feb. 7, 2019, 12:17 p.m.
Collect mount trees for each mount namespace after restoring. Sort both
trees deterministically, as we don't have sibling mounts with same
mountpoint strcmp will be enough. Compare nodes content and topology
with step-by-step dfs for these trees, and report errors.

Put it under option (--check-mounts), as it can affect performance, we
will likely enable it only in VZ7 container migration tests, zdtm (we
already have similar function in zdtm, but to test these feature itself
we need to enable it) and to debug problems with mounts.

Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
---
 criu/config.c             |   1 +
 criu/cr-restore.c         |   4 ++
 criu/crtools.c            |   3 +
 criu/include/cr_options.h |   1 +
 criu/include/mount.h      |   1 +
 criu/mount.c              | 130 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 140 insertions(+)

Patch hide | download patch | download mbox

diff --git a/criu/config.c b/criu/config.c
index 1a41b4533..161e71b96 100644
--- a/criu/config.c
+++ b/criu/config.c
@@ -507,6 +507,7 @@  int parse_options(int argc, char **argv, bool *usage_error,
 		BOOL_OPT("remote", &opts.remote),
 		{ "config",			required_argument,	0, 1089},
 		{ "no-default-config",		no_argument,		0, 1090},
+		BOOL_OPT("check-mounts", &opts.check_mounts),
 		{ },
 	};
 
diff --git a/criu/cr-restore.c b/criu/cr-restore.c
index af2ca2921..e1c20f2ba 100644
--- a/criu/cr-restore.c
+++ b/criu/cr-restore.c
@@ -3259,6 +3259,10 @@  static int sigreturn_restore(pid_t pid, struct task_restore_args *task_args, uns
 		if (root_ns_mask & CLONE_NEWNS &&
 		    remount_readonly_mounts())
 			goto err_nv;
+		if (opts.check_mounts &&
+		    root_ns_mask & CLONE_NEWNS &&
+		    check_mounts())
+			goto err_nv;
 	}
 
 	/*
diff --git a/criu/crtools.c b/criu/crtools.c
index cbee1b76a..91c4b0b14 100644
--- a/criu/crtools.c
+++ b/criu/crtools.c
@@ -423,6 +423,9 @@  int main(int argc, char *argv[], char *envp[])
 "			Namespace can be specified as either pid or file path.\n"
 "			OPTIONS can be used to specify parameters for userns:\n"
 "			    user:PID,UID,GID\n"
+"* Sanity checks:\n"
+"  --check-mounts        Check that mount trees restored fine in each mntns,\n"
+"                        check they are identical to what we have in images.\n"
 "\n"
 "Check options:\n"
 "  Without options, \"criu check\" checks availability of absolutely required\n"
diff --git a/criu/include/cr_options.h b/criu/include/cr_options.h
index 8ddbf2341..5ff82c5ef 100644
--- a/criu/include/cr_options.h
+++ b/criu/include/cr_options.h
@@ -139,6 +139,7 @@  struct cr_options {
 	pid_t			tree_id;
 	int			log_level;
 	char			*imgs_dir;
+	int			check_mounts;
 };
 
 extern struct cr_options opts;
diff --git a/criu/include/mount.h b/criu/include/mount.h
index d9b375f5d..78807783d 100644
--- a/criu/include/mount.h
+++ b/criu/include/mount.h
@@ -142,6 +142,7 @@  extern struct mount_info *parse_mountinfo(pid_t pid, struct ns_id *nsid, bool fo
 extern int check_mnt_id(void);
 
 extern int remount_readonly_mounts(void);
+extern int check_mounts(void);
 extern int try_remount_writable(struct mount_info *mi, bool ns);
 extern bool mnt_is_overmounted(struct mount_info *mi);
 
diff --git a/criu/mount.c b/criu/mount.c
index 3eb36cfdb..a5d7cc119 100644
--- a/criu/mount.c
+++ b/criu/mount.c
@@ -3822,3 +3822,133 @@  int remount_readonly_mounts(void)
 	 */
 	return call_helper_process(ns_remount_readonly_mounts, NULL);
 }
+
+enum tree_step {
+	STEP_STOP = -2,
+	STEP_CHILD = -1,
+	STEP_SIBLING = 0,
+	STEP_PARENT = 1,
+};
+
+static struct mount_info *mnt_subtree_step(struct mount_info *mi,
+					   struct mount_info *root,
+					   enum tree_step *step)
+{
+	*step = STEP_CHILD;
+
+	if (!list_empty(&mi->children))
+		return list_entry(mi->children.next, struct mount_info, siblings);
+
+	*step = STEP_SIBLING;
+
+	while (mi->parent && mi != root) {
+		if (mi->siblings.next == &mi->parent->children) {
+			mi = mi->parent;
+			*step += STEP_PARENT;
+		} else
+			return list_entry(mi->siblings.next, struct mount_info, siblings);
+	}
+
+	*step = STEP_STOP;
+	return NULL;
+}
+
+static void mnt_resort_siblings_full(struct mount_info *tree)
+{
+	struct mount_info *m, *p;
+	LIST_HEAD(list);
+
+	pr_info("\tResorting siblings full on %d\n", tree->mnt_id);
+	while (!list_empty(&tree->children)) {
+		m = list_first_entry(&tree->children, struct mount_info, siblings);
+		list_del(&m->siblings);
+
+		list_for_each_entry(p, &list, siblings)
+			if (strcmp(p->ns_mountpoint, m->ns_mountpoint) < 0)
+				break;
+
+		list_add_tail(&m->siblings, &p->siblings);
+		mnt_resort_siblings_full(m);
+	}
+
+	list_splice(&list, &tree->children);
+}
+
+static int __check_mounts(struct ns_id *ns)
+{
+	struct ns_id _new_ns = { .ns_pid = PROC_SELF, .nd = &mnt_ns_desc }, *new_ns = &_new_ns;
+	struct mount_info *mnt, *new_mnt, *new;
+	int step, new_step;
+
+	if (do_restore_task_mnt_ns(ns))
+		return -1;
+
+	pr_debug("Checking mountinfo for mntns %d:%d\n", ns->kid, ns->id);
+	new = collect_mntinfo(new_ns, true);
+	if (new == NULL)
+		return -1;
+
+
+	mnt = ns->mnt.mntinfo_tree;
+	new_mnt = new_ns->mnt.mntinfo_tree;
+	mnt_resort_siblings_full(mnt);
+	mnt_resort_siblings_full(new_mnt);
+
+	while (mnt && new_mnt) {
+		/* Consider that leading '.' was lost in collect_mnt_from_image. */
+		if (strcmp(mnt->ns_mountpoint, new_mnt->ns_mountpoint+1) ||
+		    strcmp(mnt->root, new_mnt->root) ||
+		    mnt->flags != new_mnt->flags ||
+		    mnt->sb_flags != new_mnt->sb_flags) {
+			pr_err("Mounts %s[%s,%d,%d] and %s[%s,%d,%d] does not match\n",
+			       mnt->ns_mountpoint, mnt->root, mnt->flags, mnt->sb_flags,
+			       new_mnt->ns_mountpoint+1, new_mnt->root, new_mnt->flags, new_mnt->sb_flags);
+			goto err;
+		}
+
+		mnt = mnt_subtree_step(mnt, ns->mnt.mntinfo_tree, &step);
+		new_mnt = mnt_subtree_step(new_mnt, new_ns->mnt.mntinfo_tree, &new_step);
+
+		if (step != new_step) {
+			pr_err("The restored mount tree for mntns %d:%d has wrong topology\n",
+			       ns->kid, ns->id);
+			goto err;
+		}
+	}
+
+	return 0;
+err:
+	pr_err("Old mntns tree:\n");
+	mnt_tree_show(ns->mnt.mntinfo_tree, 0);
+	pr_err("New mntns tree:\n");
+	mnt_tree_show(new_ns->mnt.mntinfo_tree, 0);
+	return -1;
+}
+
+static int ns_check_mounts(void *arg)
+{
+	struct ns_id *nsid;
+	int *ret = arg;
+
+	for (nsid = ns_ids; nsid != NULL; nsid = nsid->next) {
+		if (nsid->nd != &mnt_ns_desc)
+			continue;
+
+		*ret = __check_mounts(nsid);
+		if (*ret)
+			return 0;
+	}
+
+	return 0;
+}
+
+int check_mounts(void)
+{
+	int ret = 0;
+
+	pr_info("Check restored mount trees\n");
+	if (call_helper_process(ns_check_mounts, &ret))
+		return -1;
+
+	return ret;
+}