[lxc-devel] [PATCH] Track snapshot dependencies

Serge Hallyn serge.hallyn at ubuntu.com
Tue Aug 20 19:15:26 UTC 2013


lvm, btrfs, and zfs snapshots each do an ok job of handling deletions
for us - a btrfs snapshot does fine after the original is removed,
while zfs and lvm will both refuse to allow the original to be deleted
while the snapshot exists.

Overlayfs doesn't do this for us.  So, for overlayfs snapshots, track
the dependencies.

When c2 is created as an overlayfs snapshot of dir-backed c1, then

1. c2's lxc_rdepends file will contain

	c1_lxcpath
	c1_lxcname

2. c1's lxc_snapshots will contain "1"

c1 cannot be deleted so long as lxc_snapshots exists and contains
a non-zero number.

The contents of lxc_snapshots and lxc_rdepends are protected by
container_disk_lock() and at lxc_clone by the new container not yet
being accessible.

(Originally I was going to keep them in the container config, but the
problem with using $lxcpath/$name/config is that api users could end up
calling c->save_config() with a cached old value of snapshots/rdepends.)

Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>
---
 src/lxc/bdev.c         |   8 +-
 src/lxc/bdev.h         |   3 +-
 src/lxc/lxc.h          |   5 ++
 src/lxc/lxccontainer.c | 197 +++++++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 197 insertions(+), 16 deletions(-)

diff --git a/src/lxc/bdev.c b/src/lxc/bdev.c
index 60d9290..8c33b1d 100644
--- a/src/lxc/bdev.c
+++ b/src/lxc/bdev.c
@@ -1886,7 +1886,8 @@ struct bdev *bdev_init(const char *src, const char *dst, const char *data)
  */
 struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname,
 			const char *oldpath, const char *lxcpath, const char *bdevtype,
-			int snap, const char *bdevdata, unsigned long newsize)
+			int snap, const char *bdevdata, unsigned long newsize,
+			int *needs_rdep)
 {
 	struct bdev *orig, *new;
 	pid_t pid;
@@ -1928,6 +1929,11 @@ struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname,
 	if (!bdevtype && snap && strcmp(orig->type , "dir") == 0)
 		bdevtype = "overlayfs";
 
+	*needs_rdep = 0;
+	if (strcmp(orig->type, "dir") == 0 &&
+			strcmp(bdevtype, "overlayfs") == 0)
+		*needs_rdep = 1;
+
 	new = bdev_get(bdevtype ? bdevtype : orig->type);
 	if (!new) {
 		ERROR("no such block device type: %s", bdevtype ? bdevtype : orig->type);
diff --git a/src/lxc/bdev.h b/src/lxc/bdev.h
index 1d79bb2..ecd62ee 100644
--- a/src/lxc/bdev.h
+++ b/src/lxc/bdev.h
@@ -81,7 +81,8 @@ struct bdev *bdev_init(const char *src, const char *dst, const char *data);
 
 struct bdev *bdev_copy(const char *src, const char *oldname, const char *cname,
 			const char *oldpath, const char *lxcpath, const char *bdevtype,
-			int snap, const char *bdevdata, unsigned long newsize);
+			int snap, const char *bdevdata, unsigned long newsize,
+			int *needs_rdep);
 struct bdev *bdev_create(const char *dest, const char *type,
 			const char *cname, struct bdev_specs *specs);
 void bdev_put(struct bdev *bdev);
diff --git a/src/lxc/lxc.h b/src/lxc/lxc.h
index b9eb52c..694d9aa 100644
--- a/src/lxc/lxc.h
+++ b/src/lxc/lxc.h
@@ -226,6 +226,11 @@ extern int lxc_container_put(struct lxc_container *c);
  */
 extern int lxc_get_wait_states(const char **states);
 
+/*
+ * Add a dependency to a container
+ */
+extern int add_rdepend(struct lxc_conf *lxc_conf, char *rdepend);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c
index 02e3766..4ffc887 100644
--- a/src/lxc/lxccontainer.c
+++ b/src/lxc/lxccontainer.c
@@ -46,6 +46,12 @@
 #include <arpa/inet.h>
 #include <ifaddrs.h>
 
+#ifndef HAVE_GETLINE
+#ifdef HAVE_FGETLN
+#include <../include/getline.h>
+#endif
+#endif
+
 lxc_log_define(lxc_container, lxc);
 
 static bool file_exists(char *f)
@@ -1353,6 +1359,114 @@ out:
 	return ret;
 }
 
+static bool mod_rdep(struct lxc_container *c, bool inc)
+{
+	char path[MAXPATHLEN];
+	int ret, v = 0;
+	FILE *f;
+	bool bret = false;
+
+	if (container_disk_lock(c))
+		return false;
+	ret = snprintf(path, MAXPATHLEN, "%s/%s/lxc_snapshots", c->config_path,
+			c->name);
+	if (ret < 0 || ret > MAXPATHLEN)
+		goto out;
+	f = fopen(path, "r");
+	if (f) {
+		ret = fscanf(f, "%d", &v);
+		fclose(f);
+		if (ret != 1) {
+			ERROR("Corrupted file %s", path);
+			goto out;
+		}
+	}
+	v += inc ? 1 : -1;
+	f = fopen(path, "w");
+	if (!f)
+		goto out;
+	fprintf(f, "%d\n", v);
+	fclose(f);
+
+	bret = true;
+
+out:
+	container_disk_unlock(c);
+	return bret;
+}
+
+static void strip_newline(char *p)
+{
+	size_t len = strlen(p);
+	if (len < 1)
+		return;
+	if (p[len-1] == '\n')
+		p[len-1] = '\0';
+}
+
+static void mod_all_rdeps(struct lxc_container *c, bool inc)
+{
+	struct lxc_container *p;
+	char *lxcpath = NULL, *lxcname = NULL, path[MAXPATHLEN];
+	size_t pathlen = 0, namelen = 0;
+	FILE *f;
+	int ret;
+
+	ret = snprintf(path, MAXPATHLEN, "%s/%s/lxc_rdepends",
+		c->config_path, c->name);
+	if (ret < 0 || ret >= MAXPATHLEN) {
+		ERROR("Path name too long");
+		return;
+	}
+	if ((f = fopen(path, "r")) == NULL)
+		return;
+	while (getline(&lxcpath, &pathlen, f) != -1) {
+		if (getline(&lxcname, &namelen, f) == -1) {
+			ERROR("badly formatted file %s\n", path);
+			goto out;
+		}
+		strip_newline(lxcpath);
+		strip_newline(lxcname);
+		if ((p = lxc_container_new(lxcname, lxcpath)) == NULL) {
+			ERROR("Unable to find dependent container %s:%s",
+				lxcpath, lxcname);
+			continue;
+		}
+		if (!mod_rdep(p, inc))
+			ERROR("Failed to increase numsnapshots for %s:%s",
+				lxcpath, lxcname);
+		lxc_container_put(p);
+	}
+out:
+	if (lxcpath) free(lxcpath);
+	if (lxcname) free(lxcname);
+	fclose(f);
+}
+
+static bool has_snapshots(struct lxc_container *c)
+{
+	char path[MAXPATHLEN];
+	int ret, v;
+	FILE *f;
+	bool bret = false;
+
+	ret = snprintf(path, MAXPATHLEN, "%s/%s/lxc_snapshots", c->config_path,
+			c->name);
+	if (ret < 0 || ret > MAXPATHLEN)
+		goto out;
+	f = fopen(path, "r");
+	if (!f)
+		goto out;
+	ret = fscanf(f, "%d", &v);
+	fclose(f);
+	if (ret != 1)
+		goto out;
+	bret = v != 0;
+
+out:
+	return bret;
+}
+
 // do we want the api to support --force, or leave that to the caller?
 static bool lxcapi_destroy(struct lxc_container *c)
 {
@@ -1371,6 +1485,11 @@ static bool lxcapi_destroy(struct lxc_container *c)
 		goto out;
 	}
 
+	if (c->lxc_conf && has_snapshots(c)) {
+		ERROR("conatiner %s has dependent snapshots", c->name);
+		goto out;
+	}
+
 	if (c->lxc_conf && c->lxc_conf->rootfs.path && c->lxc_conf->rootfs.mount)
 		r = bdev_init(c->lxc_conf->rootfs.path, c->lxc_conf->rootfs.mount, NULL);
 	if (r) {
@@ -1380,6 +1499,8 @@ static bool lxcapi_destroy(struct lxc_container *c)
 		}
 	}
 
+	mod_all_rdeps(c, false);
+
 	const char *p1 = lxcapi_get_config_path(c);
 	char *path = alloca(strlen(p1) + strlen(c->name) + 2);
 	sprintf(path, "%s/%s", p1, c->name);
@@ -1583,18 +1704,18 @@ static int copy_file(char *old, char *new)
 	}
 	ret = stat(old, &sbuf);
 	if (ret < 0) {
-		SYSERROR("stat'ing %s", old);
+		INFO("Error stat'ing %s", old);
 		return -1;
 	}
 
 	in = open(old, O_RDONLY);
 	if (in < 0) {
-		SYSERROR("opening original file %s", old);
+		SYSERROR("Error opening original file %s", old);
 		return -1;
 	}
 	out = open(new, O_CREAT | O_EXCL | O_WRONLY, 0644);
 	if (out < 0) {
-		SYSERROR("opening new file %s", new);
+		SYSERROR("Error opening new file %s", new);
 		close(in);
 		return -1;
 	}
@@ -1602,14 +1723,14 @@ static int copy_file(char *old, char *new)
 	while (1) {
 		len = read(in, buf, 8096);
 		if (len < 0) {
-			SYSERROR("reading old file %s", old);
+			SYSERROR("Error reading old file %s", old);
 			goto err;
 		}
 		if (len == 0)
 			break;
 		ret = write(out, buf, len);
 		if (ret < len) {  // should we retry?
-			SYSERROR("write to new file %s was interrupted", new);
+			SYSERROR("Error: write to new file %s was interrupted", new);
 			goto err;
 		}
 	}
@@ -1619,7 +1740,7 @@ static int copy_file(char *old, char *new)
 	// we set mode, but not owner/group
 	ret = chmod(new, sbuf.st_mode);
 	if (ret) {
-		SYSERROR("setting mode on %s", new);
+		SYSERROR("Error setting mode on %s", new);
 		return -1;
 	}
 
@@ -1729,28 +1850,76 @@ static int copy_fstab(struct lxc_container *oldc, struct lxc_container *c)
 	return 0;
 }
 
+static void copy_rdepends(struct lxc_container *c, struct lxc_container *c0)
+{
+	char path0[MAXPATHLEN], path1[MAXPATHLEN];
+	int ret;
+
+	ret = snprintf(path0, MAXPATHLEN, "%s/%s/lxc_rdepends", c0->config_path,
+		c0->name);
+	if (ret < 0 || ret >= MAXPATHLEN) {
+		WARN("Error copying reverse dependencies");
+		return;
+	}
+	ret = snprintf(path1, MAXPATHLEN, "%s/%s/lxc_rdepends", c->config_path,
+		c->name);
+	if (ret < 0 || ret >= MAXPATHLEN) {
+		WARN("Error copying reverse dependencies");
+		return;
+	}
+	if (copy_file(path0, path1) < 0) {
+		INFO("Error copying reverse dependencies");
+		return;
+	}
+}
+
+static bool add_rdepends(struct lxc_container *c, struct lxc_container *c0)
+{
+	int ret;
+	char path[MAXPATHLEN];
+	FILE *f;
+
+	ret = snprintf(path, MAXPATHLEN, "%s/%s/lxc_rdepends", c->config_path,
+		c->name);
+	if (ret < 0 || ret >= MAXPATHLEN)
+		return false;
+	f = fopen(path, "a");
+	if (!f)
+		return false;
+	fprintf(f, "%s\n%s\n", c0->config_path, c0->name);
+	fclose(f);
+	return true;
+}
+
 static int copy_storage(struct lxc_container *c0, struct lxc_container *c,
 		const char *newtype, int flags, const char *bdevdata, unsigned long newsize)
 {
 	struct bdev *bdev;
+	int need_rdep;
 
 	bdev = bdev_copy(c0->lxc_conf->rootfs.path, c0->name, c->name,
 			c0->config_path, c->config_path, newtype, !!(flags & LXC_CLONE_SNAPSHOT),
-			bdevdata, newsize);
+			bdevdata, newsize, &need_rdep);
 	if (!bdev) {
-		ERROR("error copying storage");
+		ERROR("Error copying storage");
 		return -1;
 	}
 	free(c->lxc_conf->rootfs.path);
 	c->lxc_conf->rootfs.path = strdup(bdev->src);
 	bdev_put(bdev);
-	if (!c->lxc_conf->rootfs.path)
+	if (!c->lxc_conf->rootfs.path) {
+		ERROR("Out of memory while setting storage path");
 		return -1;
-	// here we could also update all lxc.mount.entries or even
-	// items in the lxc.mount fstab list.  As discussed on m-l,
-	// we could do either any source paths starting with the
-	// lxcpath/oldname, or simply anythign which is not a virtual
-	// fs or a bind mount.
+	}
+	copy_rdepends(c, c0);
+	if (need_rdep) {
+		if (!add_rdepends(c, c0))
+			WARN("Error adding reverse dependency from %s to %s",
+				c->name, c0->name);
+	}
+
+	mod_all_rdeps(c, true);
+
 	return 0;
 }
 
-- 
1.8.1.2





More information about the lxc-devel mailing list