overlay: Test invalidate of readdir cache
[xfstests-dev.git] / src / detached_mounts_propagation.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /*
3  * Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
4  * All Rights Reserved.
5  *
6  * Regression test to verify that creating a series of detached mounts,
7  * attaching them to the filesystem, and unmounting them does not trigger an
8  * integer overflow in ns->mounts causing the kernel to block any new mounts in
9  * count_mounts() and returning ENOSPC because it falsely assumes that the
10  * maximum number of mounts in the mount namespace has been reached, i.e. it
11  * thinks it can't fit the new mounts into the mount namespace anymore.
12  */
13
14 #include <errno.h>
15 #include <fcntl.h>
16 #include <getopt.h>
17 #include <limits.h>
18 #include <sched.h>
19 #include <stdbool.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/mount.h>
24 #include <sys/stat.h>
25 #include <sys/syscall.h>
26 #include <sys/types.h>
27 #include <unistd.h>
28
29 #include "idmapped-mounts/missing.h"
30
31 static bool is_shared_mountpoint(const char *path)
32 {
33         bool shared = false;
34         FILE *f = NULL;
35         char *line = NULL;
36         int i;
37         size_t len = 0;
38
39         f = fopen("/proc/self/mountinfo", "re");
40         if (!f)
41                 return 0;
42
43         while (getline(&line, &len, f) > 0) {
44                 char *slider1, *slider2;
45
46                 for (slider1 = line, i = 0; slider1 && i < 4; i++)
47                         slider1 = strchr(slider1 + 1, ' ');
48
49                 if (!slider1)
50                         continue;
51
52                 slider2 = strchr(slider1 + 1, ' ');
53                 if (!slider2)
54                         continue;
55
56                 *slider2 = '\0';
57                 if (strcmp(slider1 + 1, path) == 0) {
58                         /* This is the path. Is it shared? */
59                         slider1 = strchr(slider2 + 1, ' ');
60                         if (slider1 && strstr(slider1, "shared:")) {
61                                 shared = true;
62                                 break;
63                         }
64                 }
65         }
66         fclose(f);
67         free(line);
68
69         return shared;
70 }
71
72 static void usage(void)
73 {
74         const char *text = "mount-new [--recursive] <base-dir>\n";
75         fprintf(stderr, "%s", text);
76         _exit(EXIT_SUCCESS);
77 }
78
79 #define exit_usage(format, ...)                              \
80         ({                                                   \
81                 fprintf(stderr, format "\n", ##__VA_ARGS__); \
82                 usage();                                     \
83         })
84
85 #define exit_log(format, ...)                                \
86         ({                                                   \
87                 fprintf(stderr, format "\n", ##__VA_ARGS__); \
88                 exit(EXIT_FAILURE);                          \
89         })
90
91 static const struct option longopts[] = {
92         {"help",        no_argument,            0,      'a'},
93         { NULL,         no_argument,            0,       0 },
94 };
95
96 int main(int argc, char *argv[])
97 {
98         int exit_code = EXIT_SUCCESS, index = 0;
99         int dfd, fd_tree, new_argc, ret, i;
100         char *base_dir;
101         char *const *new_argv;
102         char target[PATH_MAX];
103
104         while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
105                 switch (ret) {
106                 case 'a':
107                         /* fallthrough */
108                 default:
109                         usage();
110                 }
111         }
112
113         new_argv = &argv[optind];
114         new_argc = argc - optind;
115         if (new_argc < 1)
116                 exit_usage("Missing base directory\n");
117         base_dir = new_argv[0];
118
119         if (*base_dir != '/')
120                 exit_log("Please specify an absolute path");
121
122         /* Ensure that target is a shared mountpoint. */
123         if (!is_shared_mountpoint(base_dir))
124                 exit_log("Please ensure that \"%s\" is a shared mountpoint", base_dir);
125
126         ret = unshare(CLONE_NEWNS);
127         if (ret < 0)
128                 exit_log("%m - Failed to create new mount namespace");
129
130         ret = mount(NULL, base_dir, NULL, MS_REC | MS_SHARED, NULL);
131         if (ret < 0)
132                 exit_log("%m - Failed to make base_dir shared mountpoint");
133
134         dfd = open(base_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
135         if (dfd < 0)
136                 exit_log("%m - Failed to open base directory \"%s\"", base_dir);
137
138         ret = mkdirat(dfd, "detached-move-mount", 0755);
139         if (ret < 0)
140                 exit_log("%m - Failed to create required temporary directories");
141
142         ret = snprintf(target, sizeof(target), "%s/detached-move-mount", base_dir);
143         if (ret < 0 || (size_t)ret >= sizeof(target))
144                 exit_log("%m - Failed to assemble target path");
145
146         /*
147          * Having a mount table with 10000 mounts is already quite excessive
148          * and shoult account even for weird test systems.
149          */
150         for (i = 0; i < 10000; i++) {
151                 fd_tree = sys_open_tree(dfd, "detached-move-mount",
152                                         OPEN_TREE_CLONE |
153                                         OPEN_TREE_CLOEXEC |
154                                         AT_EMPTY_PATH);
155                 if (fd_tree < 0) {
156                         if (errno == ENOSYS) /* New mount API not (fully) supported. */
157                                 break;
158
159                         fprintf(stderr, "%m - Failed to open %d(detached-move-mount)", dfd);
160                         exit_code = EXIT_FAILURE;
161                         break;
162                 }
163
164                 ret = sys_move_mount(fd_tree, "", dfd, "detached-move-mount", MOVE_MOUNT_F_EMPTY_PATH);
165                 if (ret < 0) {
166                         if (errno == ENOSPC)
167                                 fprintf(stderr, "%m - Buggy mount counting");
168                         else if (errno == ENOSYS) /* New mount API not (fully) supported. */
169                                 break;
170                         else
171                                 fprintf(stderr, "%m - Failed to attach mount to %d(detached-move-mount)", dfd);
172                         exit_code = EXIT_FAILURE;
173                         break;
174                 }
175                 close(fd_tree);
176
177                 ret = umount2(target, MNT_DETACH);
178                 if (ret < 0) {
179                         fprintf(stderr, "%m - Failed to unmount %s", target);
180                         exit_code = EXIT_FAILURE;
181                         break;
182                 }
183         }
184
185         (void)unlinkat(dfd, "detached-move-mount", AT_REMOVEDIR);
186         close(dfd);
187
188         exit(exit_code);
189 }