Signed-off-by: Wolfgang Bumiller <w.bumil...@proxmox.com> --- hooks/Makefile.am | 6 ++ hooks/unmount-namespace.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 hooks/unmount-namespace.c
diff --git a/hooks/Makefile.am b/hooks/Makefile.am index be55601..ef82083 100644 --- a/hooks/Makefile.am +++ b/hooks/Makefile.am @@ -6,4 +6,10 @@ hooks_SCRIPTS = \ ubuntu-cloud-prep \ squid-deb-proxy-client +hooks_PROGRAMS = \ + unmount-namespace + +unmount_namespace_SOURCES = \ + unmount-namespace.c + EXTRA_DIST=$(hooks_SCRIPTS) diff --git a/hooks/unmount-namespace.c b/hooks/unmount-namespace.c new file mode 100644 index 0000000..488c9cc --- /dev/null +++ b/hooks/unmount-namespace.c @@ -0,0 +1,213 @@ +/* + * Copyright © 2015 Wolfgang Bumiller <w.bumil...@proxmox.com>. + * Copyright © 2015 Proxmox Server Solutions GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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. + * + * -- + * + * This stop-hook unmounts everything in the container's namespace, and thereby + * waits for all calls commands to finish. This is useful when one needs to be + * sure that network filesystems are finished unmounting in the namespace + * before continuing with other tasks. Without this hook the cleanup of mounts + * is done by the kernel in the background after all the references to the + * namespaces are gone. + */ + +#define _GNU_SOURCE /* setns */ +#include <stdio.h> /* fdopen, getmntent, endmntent */ +#include <stdlib.h> /* malloc, qsort */ +#include <unistd.h> /* close */ +#include <string.h> /* strcmp, strncmp, strdup, strerror */ +#include <sched.h> /* setns */ +#include <sys/mount.h> /* umount2 */ +#include <sys/types.h> /* openat, open */ +#include <sys/stat.h> /* openat, open */ +#include <fcntl.h> /* openat, open */ +#include <mntent.h> /* getmntent, endmntent */ +#include <errno.h> /* errno */ + +struct mount { + char *src; /* currently not used */ + char *dst; + char *fs; /* currently not used */ +}; + +static void mount_free(struct mount *mnt) { + free(mnt->src); + free(mnt->dst); + free(mnt->fs); +} + +static int mount_cmp_dst(const void *a_, const void *b_) { + struct mount *a = (struct mount*)a_; + struct mount *b = (struct mount*)b_; + return strcmp(b->dst, a->dst); /* swapped order */ +} + +/* Unmounting /dev/pts fails, and so /dev also fails, but /dev is not what + * we're interested in. (There might also still be /dev/cgroup mounts). + */ +static int mount_should_error(const struct mount *mnt) { + const char *dst = mnt->dst; + return !(strncmp(dst, "/dev", 4) == 0 && (dst[4] == 0 || dst[4] == '/')); +} + +/* Read mounts from 'self/mounts' relative to a directory filedescriptor. + * Before entering the container we open a handle to /proc on the host as we + * need to access /proc/self/mounts and the container's /proc doesn't contain + * our /self. We then use openat(2) to avoid having to mount a temporary /proc. + */ +static int read_mounts(int procfd, struct mount **mp, size_t *countp) { + int fd; + struct mntent *ent; + FILE *mf; + size_t capacity = 32; + size_t count = 0; + struct mount *mounts = (struct mount*)malloc(capacity * sizeof(*mounts)); + + if (!mounts) { + errno = ENOMEM; + return 0; + } + + *mp = NULL; + *countp = 0; + + fd = openat(procfd, "self/mounts", O_RDONLY); + if (fd < 0) + return 0; + + mf = fdopen(fd, "r"); + if (!mf) { + int error = errno; + close(fd); + errno = error; + return 0; + } + while ((ent = getmntent(mf))) { + struct mount *new; + if (count == capacity) { + capacity *= 2; + new = (struct mount*)realloc(mounts, capacity * sizeof(*mounts)); + if (!new) + goto out_alloc_entry; + mounts = new; + } + new = &mounts[count++]; + new->src = strdup(ent->mnt_fsname); + new->dst = strdup(ent->mnt_dir); + new->fs = strdup(ent->mnt_type); + if (!new->src || !new->dst || !new->fs) + goto out_alloc_entry; + } + endmntent(mf); + + *mp = mounts; + *countp = count; + + return 1; + +out_alloc_entry: + endmntent(mf); + while (count--) { + free(mounts[count].src); + free(mounts[count].dst); + free(mounts[count].fs); + } + free(mounts); + errno = ENOMEM; + return 0; +} + +int main(int argc, char **argv) { + int i, procfd, ctmntfd; + struct mount *mounts; + size_t zi, count = 0; + const char *mntns = NULL; + + if (argc < 4 || strcmp(argv[2], "lxc") != 0) { + fprintf(stderr, "%s: usage error, expected LXC hook arguments\n", argv[0]); + return 2; + } + + if (strcmp(argv[3], "stop") != 0) + return 0; + + for (i = 4; i != argc; ++i) { + if (!strncmp(argv[i], "mnt:", 4)) { + mntns = argv[i] + 4; + break; + } + } + + if (!mntns) { + fprintf(stderr, "%s: no mount namespace provided\n", argv[0]); + return 3; + } + + /* Open a handle to /proc on the host as we need to access /proc/self/mounts + * and the container's /proc doesn't contain our /self. See read_mounts(). + */ + procfd = open("/proc", O_RDONLY | O_DIRECTORY | O_PATH); + if (procfd < 0) { + fprintf(stderr, "%s: failed to open /proc: %s\n", argv[0], strerror(errno)); + return 4; + } + + /* Open the mount namespace and enter it. */ + ctmntfd = open(mntns, O_RDONLY); + if (ctmntfd < 0) { + fprintf(stderr, "%s: failed to open mount namespace: %s\n", + argv[0], strerror(errno)); + close(procfd); + return 5; + } + + if (setns(ctmntfd, CLONE_NEWNS) != 0) { + fprintf(stderr, "%s: failed to attach to namespace: %s\n", + argv[0], strerror(errno)); + close(ctmntfd); + close(procfd); + return 6; + } + close(ctmntfd); + + /* Now read [[procfd]]/self/mounts */ + if (!read_mounts(procfd, &mounts, &count)) { + fprintf(stderr, "%s: failed to read mountpoints: %s\n", + argv[0], strerror(errno)); + close(procfd); + return 7; + } + close(procfd); + + /* Just sort to get a sane unmount-order... */ + qsort(mounts, count, sizeof(*mounts), &mount_cmp_dst); + + for (zi = 0; zi != count; ++zi) { + /* fprintf(stderr, "Unmount: %s\n", mounts[zi].dst); */ + if (umount2(mounts[zi].dst, 0) != 0) { + int error = errno; + if (mount_should_error(&mounts[zi])) { + fprintf(stderr, "%s: failed to unmount %s: %s\n", + argv[0], mounts[zi].dst, strerror(error)); + } + } + mount_free(&mounts[zi]); + } + free(mounts); + + return 0; +} -- 2.1.4 _______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel