1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
/*
* SPDX-License-Identifier: LGPL-2.1+
*
* --
*
* 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 */
#include <mntent.h>
#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;
}
|