unmount-namespace.c revision 36dd8426cbfc3262eddf38c0480c7ed0a8d811e9
2ronwalf/*
2ronwalf * Copyright © 2015 Wolfgang Bumiller <w.bumiller@proxmox.com>.
2ronwalf * Copyright © 2015 Proxmox Server Solutions GmbH
2ronwalf *
2ronwalf * This program is free software; you can redistribute it and/or modify
2ronwalf * it under the terms of the GNU General Public License version 2, as
2ronwalf * published by the Free Software Foundation.
2ronwalf *
2ronwalf * This program is distributed in the hope that it will be useful,
2ronwalf * but WITHOUT ANY WARRANTY; without even the implied warranty of
2ronwalf * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2ronwalf * GNU General Public License for more details.
2ronwalf *
2ronwalf * You should have received a copy of the GNU General Public License along
2ronwalf * with this program; if not, write to the Free Software Foundation, Inc.,
2ronwalf * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2ronwalf *
2ronwalf * --
2ronwalf *
2ronwalf * This stop-hook unmounts everything in the container's namespace, and thereby
2ronwalf * waits for all calls commands to finish. This is useful when one needs to be
2ronwalf * sure that network filesystems are finished unmounting in the namespace
2ronwalf * before continuing with other tasks. Without this hook the cleanup of mounts
2ronwalf * is done by the kernel in the background after all the references to the
2ronwalf * namespaces are gone.
2ronwalf */
2ronwalf
2ronwalf#define _GNU_SOURCE /* setns */
2ronwalf#include <stdio.h> /* fdopen, getmntent, endmntent */
2ronwalf#include <stdlib.h> /* malloc, qsort */
2ronwalf#include <unistd.h> /* close */
2ronwalf#include <string.h> /* strcmp, strncmp, strdup, strerror */
2ronwalf#include <sched.h> /* setns */
2ronwalf#include <sys/mount.h> /* umount2 */
2ronwalf#include <sys/types.h> /* openat, open */
2ronwalf#include <sys/stat.h> /* openat, open */
2ronwalf#include <fcntl.h> /* openat, open */
2ronwalf#include <errno.h> /* errno */
2ronwalf
2ronwalf#include <../src/config.h>
2ronwalf
2ronwalf#if IS_BIONIC
2ronwalf#include <../src/include/lxcmntent.h>
2ronwalf#else
2ronwalf#include <mntent.h>
2ronwalf#endif
2ronwalf
2ronwalf#ifndef O_PATH
2ronwalf#define O_PATH 010000000
2ronwalf#endif
2ronwalf
2ronwalf/* Define setns() if missing from the C library */
2ronwalf#ifndef HAVE_SETNS
2ronwalfstatic inline int setns(int fd, int nstype)
2ronwalf{
2ronwalf#ifdef __NR_setns
2ronwalf return syscall(__NR_setns, fd, nstype);
2ronwalf#elif defined(__NR_set_ns)
2ronwalf return syscall(__NR_set_ns, fd, nstype);
2ronwalf#else
2ronwalf errno = ENOSYS;
2ronwalf return -1;
2ronwalf#endif
2ronwalf}
2ronwalf#endif
2ronwalf
2ronwalfstruct mount {
2ronwalf char *src; /* currently not used */
2ronwalf char *dst;
2ronwalf char *fs; /* currently not used */
2ronwalf};
2ronwalf
2ronwalfstatic void mount_free(struct mount *mnt) {
2ronwalf free(mnt->src);
2ronwalf free(mnt->dst);
2ronwalf free(mnt->fs);
2ronwalf}
2ronwalf
2ronwalfstatic int mount_cmp_dst(const void *a_, const void *b_) {
2ronwalf struct mount *a = (struct mount*)a_;
2ronwalf struct mount *b = (struct mount*)b_;
2ronwalf return strcmp(b->dst, a->dst); /* swapped order */
2ronwalf}
2ronwalf
2ronwalf/* Unmounting /dev/pts fails, and so /dev also fails, but /dev is not what
2ronwalf * we're interested in. (There might also still be /dev/cgroup mounts).
2ronwalf */
2ronwalfstatic 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;
}