Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <sys/mount.h>
4 : #include <unistd.h>
5 :
6 : #include "alloc-util.h"
7 : #include "def.h"
8 : #include "fd-util.h"
9 : #include "fileio.h"
10 : #include "hashmap.h"
11 : #include "log.h"
12 : #include "mountpoint-util.h"
13 : #include "path-util.h"
14 : #include "rm-rf.h"
15 : #include "string-util.h"
16 : #include "tests.h"
17 :
18 7 : static void test_mount_propagation_flags(const char *name, int ret, unsigned long expected) {
19 : long unsigned flags;
20 :
21 7 : log_info("/* %s(%s) */", __func__, name);
22 :
23 7 : assert_se(mount_propagation_flags_from_string(name, &flags) == ret);
24 :
25 7 : if (ret >= 0) {
26 : const char *c;
27 :
28 5 : assert_se(flags == expected);
29 :
30 5 : c = mount_propagation_flags_to_string(flags);
31 5 : if (isempty(name))
32 2 : assert_se(isempty(c));
33 : else
34 3 : assert_se(streq(c, name));
35 : }
36 7 : }
37 :
38 1 : static void test_mnt_id(void) {
39 1 : _cleanup_fclose_ FILE *f = NULL;
40 1 : _cleanup_hashmap_free_free_ Hashmap *h = NULL;
41 : Iterator i;
42 : char *p;
43 : void *k;
44 : int r;
45 :
46 1 : log_info("/* %s */", __func__);
47 :
48 1 : assert_se(f = fopen("/proc/self/mountinfo", "re"));
49 1 : assert_se(h = hashmap_new(&trivial_hash_ops));
50 :
51 39 : for (;;) {
52 41 : _cleanup_free_ char *line = NULL, *path = NULL;
53 : int mnt_id;
54 :
55 40 : r = read_line(f, LONG_LINE_MAX, &line);
56 40 : if (r == 0)
57 1 : break;
58 39 : assert_se(r > 0);
59 :
60 39 : assert_se(sscanf(line, "%i %*s %*s %*s %ms", &mnt_id, &path) == 2);
61 : #if HAS_FEATURE_MEMORY_SANITIZER
62 : /* We don't know the length of the string, so we need to unpoison it one char at a time */
63 : for (const char *c = path; ;c++) {
64 : msan_unpoison(c, 1);
65 : if (!*c)
66 : break;
67 : }
68 : #endif
69 39 : log_debug("mountinfo: %s → %i", path, mnt_id);
70 :
71 39 : assert_se(hashmap_put(h, INT_TO_PTR(mnt_id), path) >= 0);
72 39 : path = NULL;
73 : }
74 :
75 40 : HASHMAP_FOREACH_KEY(p, k, h, i) {
76 39 : int mnt_id = PTR_TO_INT(k), mnt_id2;
77 :
78 39 : r = path_get_mnt_id(p, &mnt_id2);
79 39 : if (r < 0) {
80 1 : log_debug_errno(r, "Failed to get the mnt id of %s: %m\n", p);
81 38 : continue;
82 : }
83 :
84 38 : log_debug("mnt ids of %s are %i, %i\n", p, mnt_id, mnt_id2);
85 :
86 38 : if (mnt_id == mnt_id2)
87 37 : continue;
88 :
89 : /* The ids don't match? If so, then there are two mounts on the same path, let's check if
90 : * that's really the case */
91 1 : char *t = hashmap_get(h, INT_TO_PTR(mnt_id2));
92 1 : log_debug("the other path for mnt id %i is %s\n", mnt_id2, t);
93 1 : assert_se(path_equal(p, t));
94 : }
95 1 : }
96 :
97 1 : static void test_path_is_mount_point(void) {
98 : int fd;
99 1 : char tmp_dir[] = "/tmp/test-path-is-mount-point-XXXXXX";
100 1 : _cleanup_free_ char *file1 = NULL, *file2 = NULL, *link1 = NULL, *link2 = NULL;
101 1 : _cleanup_free_ char *dir1 = NULL, *dir1file = NULL, *dirlink1 = NULL, *dirlink1file = NULL;
102 1 : _cleanup_free_ char *dir2 = NULL, *dir2file = NULL;
103 :
104 1 : log_info("/* %s */", __func__);
105 :
106 1 : assert_se(path_is_mount_point("/", NULL, AT_SYMLINK_FOLLOW) > 0);
107 1 : assert_se(path_is_mount_point("/", NULL, 0) > 0);
108 1 : assert_se(path_is_mount_point("//", NULL, AT_SYMLINK_FOLLOW) > 0);
109 1 : assert_se(path_is_mount_point("//", NULL, 0) > 0);
110 :
111 1 : assert_se(path_is_mount_point("/proc", NULL, AT_SYMLINK_FOLLOW) > 0);
112 1 : assert_se(path_is_mount_point("/proc", NULL, 0) > 0);
113 1 : assert_se(path_is_mount_point("/proc/", NULL, AT_SYMLINK_FOLLOW) > 0);
114 1 : assert_se(path_is_mount_point("/proc/", NULL, 0) > 0);
115 :
116 1 : assert_se(path_is_mount_point("/proc/1", NULL, AT_SYMLINK_FOLLOW) == 0);
117 1 : assert_se(path_is_mount_point("/proc/1", NULL, 0) == 0);
118 1 : assert_se(path_is_mount_point("/proc/1/", NULL, AT_SYMLINK_FOLLOW) == 0);
119 1 : assert_se(path_is_mount_point("/proc/1/", NULL, 0) == 0);
120 :
121 1 : assert_se(path_is_mount_point("/sys", NULL, AT_SYMLINK_FOLLOW) > 0);
122 1 : assert_se(path_is_mount_point("/sys", NULL, 0) > 0);
123 1 : assert_se(path_is_mount_point("/sys/", NULL, AT_SYMLINK_FOLLOW) > 0);
124 1 : assert_se(path_is_mount_point("/sys/", NULL, 0) > 0);
125 :
126 : /* we'll create a hierarchy of different kinds of dir/file/link
127 : * layouts:
128 : *
129 : * <tmp>/file1, <tmp>/file2
130 : * <tmp>/link1 -> file1, <tmp>/link2 -> file2
131 : * <tmp>/dir1/
132 : * <tmp>/dir1/file
133 : * <tmp>/dirlink1 -> dir1
134 : * <tmp>/dirlink1file -> dirlink1/file
135 : * <tmp>/dir2/
136 : * <tmp>/dir2/file
137 : */
138 :
139 : /* file mountpoints */
140 1 : assert_se(mkdtemp(tmp_dir) != NULL);
141 1 : file1 = path_join(tmp_dir, "file1");
142 1 : assert_se(file1);
143 1 : file2 = path_join(tmp_dir, "file2");
144 1 : assert_se(file2);
145 1 : fd = open(file1, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0664);
146 1 : assert_se(fd > 0);
147 1 : close(fd);
148 1 : fd = open(file2, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0664);
149 1 : assert_se(fd > 0);
150 1 : close(fd);
151 1 : link1 = path_join(tmp_dir, "link1");
152 1 : assert_se(link1);
153 1 : assert_se(symlink("file1", link1) == 0);
154 1 : link2 = path_join(tmp_dir, "link2");
155 1 : assert_se(link1);
156 1 : assert_se(symlink("file2", link2) == 0);
157 :
158 1 : assert_se(path_is_mount_point(file1, NULL, AT_SYMLINK_FOLLOW) == 0);
159 1 : assert_se(path_is_mount_point(file1, NULL, 0) == 0);
160 1 : assert_se(path_is_mount_point(link1, NULL, AT_SYMLINK_FOLLOW) == 0);
161 1 : assert_se(path_is_mount_point(link1, NULL, 0) == 0);
162 :
163 : /* directory mountpoints */
164 1 : dir1 = path_join(tmp_dir, "dir1");
165 1 : assert_se(dir1);
166 1 : assert_se(mkdir(dir1, 0755) == 0);
167 1 : dirlink1 = path_join(tmp_dir, "dirlink1");
168 1 : assert_se(dirlink1);
169 1 : assert_se(symlink("dir1", dirlink1) == 0);
170 1 : dirlink1file = path_join(tmp_dir, "dirlink1file");
171 1 : assert_se(dirlink1file);
172 1 : assert_se(symlink("dirlink1/file", dirlink1file) == 0);
173 1 : dir2 = path_join(tmp_dir, "dir2");
174 1 : assert_se(dir2);
175 1 : assert_se(mkdir(dir2, 0755) == 0);
176 :
177 1 : assert_se(path_is_mount_point(dir1, NULL, AT_SYMLINK_FOLLOW) == 0);
178 1 : assert_se(path_is_mount_point(dir1, NULL, 0) == 0);
179 1 : assert_se(path_is_mount_point(dirlink1, NULL, AT_SYMLINK_FOLLOW) == 0);
180 1 : assert_se(path_is_mount_point(dirlink1, NULL, 0) == 0);
181 :
182 : /* file in subdirectory mountpoints */
183 1 : dir1file = path_join(dir1, "file");
184 1 : assert_se(dir1file);
185 1 : fd = open(dir1file, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0664);
186 1 : assert_se(fd > 0);
187 1 : close(fd);
188 :
189 1 : assert_se(path_is_mount_point(dir1file, NULL, AT_SYMLINK_FOLLOW) == 0);
190 1 : assert_se(path_is_mount_point(dir1file, NULL, 0) == 0);
191 1 : assert_se(path_is_mount_point(dirlink1file, NULL, AT_SYMLINK_FOLLOW) == 0);
192 1 : assert_se(path_is_mount_point(dirlink1file, NULL, 0) == 0);
193 :
194 : /* these tests will only work as root */
195 1 : if (mount(file1, file2, NULL, MS_BIND, NULL) >= 0) {
196 : int rf, rt, rdf, rdt, rlf, rlt, rl1f, rl1t;
197 : const char *file2d;
198 :
199 : /* files */
200 : /* capture results in vars, to avoid dangling mounts on failure */
201 0 : log_info("%s: %s", __func__, file2);
202 0 : rf = path_is_mount_point(file2, NULL, 0);
203 0 : rt = path_is_mount_point(file2, NULL, AT_SYMLINK_FOLLOW);
204 :
205 0 : file2d = strjoina(file2, "/");
206 0 : log_info("%s: %s", __func__, file2d);
207 0 : rdf = path_is_mount_point(file2d, NULL, 0);
208 0 : rdt = path_is_mount_point(file2d, NULL, AT_SYMLINK_FOLLOW);
209 :
210 0 : log_info("%s: %s", __func__, link2);
211 0 : rlf = path_is_mount_point(link2, NULL, 0);
212 0 : rlt = path_is_mount_point(link2, NULL, AT_SYMLINK_FOLLOW);
213 :
214 0 : assert_se(umount(file2) == 0);
215 :
216 0 : assert_se(rf == 1);
217 0 : assert_se(rt == 1);
218 0 : assert_se(rdf == -ENOTDIR);
219 0 : assert_se(rdt == -ENOTDIR);
220 0 : assert_se(rlf == 0);
221 0 : assert_se(rlt == 1);
222 :
223 : /* dirs */
224 0 : dir2file = path_join(dir2, "file");
225 0 : assert_se(dir2file);
226 0 : fd = open(dir2file, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0664);
227 0 : assert_se(fd > 0);
228 0 : close(fd);
229 :
230 0 : assert_se(mount(dir2, dir1, NULL, MS_BIND, NULL) >= 0);
231 :
232 0 : log_info("%s: %s", __func__, dir1);
233 0 : rf = path_is_mount_point(dir1, NULL, 0);
234 0 : rt = path_is_mount_point(dir1, NULL, AT_SYMLINK_FOLLOW);
235 0 : log_info("%s: %s", __func__, dirlink1);
236 0 : rlf = path_is_mount_point(dirlink1, NULL, 0);
237 0 : rlt = path_is_mount_point(dirlink1, NULL, AT_SYMLINK_FOLLOW);
238 0 : log_info("%s: %s", __func__, dirlink1file);
239 : /* its parent is a mount point, but not /file itself */
240 0 : rl1f = path_is_mount_point(dirlink1file, NULL, 0);
241 0 : rl1t = path_is_mount_point(dirlink1file, NULL, AT_SYMLINK_FOLLOW);
242 :
243 0 : assert_se(umount(dir1) == 0);
244 :
245 0 : assert_se(rf == 1);
246 0 : assert_se(rt == 1);
247 0 : assert_se(rlf == 0);
248 0 : assert_se(rlt == 1);
249 0 : assert_se(rl1f == 0);
250 0 : assert_se(rl1t == 0);
251 :
252 : } else
253 1 : printf("Skipping bind mount file test: %m\n");
254 :
255 1 : assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
256 1 : }
257 :
258 1 : int main(int argc, char *argv[]) {
259 1 : test_setup_logging(LOG_DEBUG);
260 :
261 1 : test_mount_propagation_flags("shared", 0, MS_SHARED);
262 1 : test_mount_propagation_flags("slave", 0, MS_SLAVE);
263 1 : test_mount_propagation_flags("private", 0, MS_PRIVATE);
264 1 : test_mount_propagation_flags(NULL, 0, 0);
265 1 : test_mount_propagation_flags("", 0, 0);
266 1 : test_mount_propagation_flags("xxxx", -EINVAL, 0);
267 1 : test_mount_propagation_flags(" ", -EINVAL, 0);
268 :
269 1 : test_mnt_id();
270 1 : test_path_is_mount_point();
271 :
272 1 : return 0;
273 : }
|