mirror of
https://git.proxmox.com/git/mirror_lxc
synced 2025-04-28 22:34:09 +00:00
242 lines
6.2 KiB
C
242 lines
6.2 KiB
C
/*
|
|
* Copyright © 2015 Wolfgang Bumiller <w.bumiller@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.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#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 <errno.h> /* errno */
|
|
|
|
#if IS_BIONIC
|
|
#include "lxcmntent.h"
|
|
#else
|
|
#include <mntent.h>
|
|
#endif
|
|
|
|
#ifndef O_PATH
|
|
#define O_PATH 010000000
|
|
#endif
|
|
|
|
/* Define setns() if missing from the C library */
|
|
#if !HAVE_SETNS
|
|
static inline int setns(int fd, int nstype)
|
|
{
|
|
#ifdef __NR_setns
|
|
return syscall(__NR_setns, fd, nstype);
|
|
#elif defined(__NR_set_ns)
|
|
return syscall(__NR_set_ns, fd, nstype);
|
|
#else
|
|
errno = ENOSYS;
|
|
return -1;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
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 | O_CLOEXEC);
|
|
if (fd < 0) {
|
|
free(mounts);
|
|
return 0;
|
|
}
|
|
|
|
mf = fdopen(fd, "re");
|
|
if (!mf) {
|
|
int error = errno;
|
|
close(fd);
|
|
errno = error;
|
|
free(mounts);
|
|
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 | O_CLOEXEC);
|
|
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 | O_CLOEXEC);
|
|
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;
|
|
}
|