xref: /linux/tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c (revision 8e8cd9a4c6dc006e9bd51b8ad0079fb4576b48d3)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * An unmounted mount that would have stayed attached to its unmounted parent
4  * leaves a cover behind instead. A lookup on the parent at the mountpoint
5  * finds knullfs: an empty read-only directory that is shared by every cover
6  * and every kernel thread, so it can't be watched or locked and nothing can
7  * be mounted on it. The mount itself is a root from then on. Where a file
8  * was mounted, the stand-in is an empty regular file of the same instance.
9  */
10 #define _GNU_SOURCE
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <sched.h>
14 #include <stdint.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
19 #include <sys/fanotify.h>
20 #include <sys/file.h>
21 #include <sys/inotify.h>
22 #include <sys/mount.h>
23 #include <sys/prctl.h>
24 #include <sys/stat.h>
25 #include <sys/statvfs.h>
26 #include <sys/syscall.h>
27 #include <sys/vfs.h>
28 #include <sys/wait.h>
29 
30 #include "../../kselftest_harness.h"
31 #include "../readdir_hold.h"
32 
33 #ifndef NULL_FS_MAGIC
34 #define NULL_FS_MAGIC 0x4E554C4C
35 #endif
36 
37 #ifndef TMPFS_MAGIC
38 #define TMPFS_MAGIC 0x01021994
39 #endif
40 
41 #ifndef F_SETDELEG
42 #define F_SETDELEG	(F_SETLEASE + 16)
43 #endif
44 
45 /* struct delegation of <linux/fcntl.h>, which doesn't mix with <fcntl.h> */
46 struct delegation_req {
47 	uint32_t d_flags;
48 	uint16_t d_type;
49 	uint16_t __pad;
50 };
51 
52 #define DIR_LEN 64
53 #define PATH_LEN 128
54 
55 /* what the parent asks the child to do */
56 #define CMD_RMDIR	'r'
57 #define CMD_CLOSE_T	't'
58 #define CMD_QUIT	'q'
59 
write_file(const char * path,const char * s)60 static int write_file(const char *path, const char *s)
61 {
62 	ssize_t n = -1;
63 	int fd;
64 
65 	fd = open(path, O_WRONLY | O_CLOEXEC);
66 	if (fd >= 0) {
67 		n = write(fd, s, strlen(s));
68 		close(fd);
69 	}
70 	return n == (ssize_t)strlen(s) ? 0 : -1;
71 }
72 
touch(const char * path)73 static int touch(const char *path)
74 {
75 	int fd;
76 
77 	fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
78 	if (fd < 0)
79 		return -1;
80 	close(fd);
81 	return 0;
82 }
83 
84 /* Become root in a new user namespace with a private mount namespace. */
enter_userns(void)85 static int enter_userns(void)
86 {
87 	uid_t uid = getuid();
88 	gid_t gid = getgid();
89 	char map[32];
90 
91 	prctl(PR_SET_DUMPABLE, 1);
92 	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
93 		return -1;
94 	if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
95 		return -1;
96 	snprintf(map, sizeof(map), "0 %d 1", uid);
97 	if (write_file("/proc/self/uid_map", map))
98 		return -1;
99 	snprintf(map, sizeof(map), "0 %d 1", gid);
100 	if (write_file("/proc/self/gid_map", map))
101 		return -1;
102 	if (setgid(0) || setuid(0))
103 		return -1;
104 	return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL);
105 }
106 
107 /*
108  * The child mounts P on @base/p, C on P/covered and the file P/src on P/file
109  * in a mount namespace of its own, binds P a second time at @base/q and hands
110  * out descriptors on P and on C. rmdir() of @base/p from here unmounts P
111  * together with C and the file bind. P and C are held by the descriptors and
112  * the unmounted children leave their covers behind. On request the child
113  * removes C's mountpoint through the bind.
114  *
115  * It also mounts T on @base/t with a child on T/covered, binds T at @base/u
116  * with a second child on the same dentry and hands out descriptors on T and
117  * U. rmdir() of both from here leaves two covers on one mountpoint. On request
118  * the child lets go of T.
119  */
cover_child(const char * base,int to_parent,int from_parent)120 static int cover_child(const char *base, int to_parent, int from_parent)
121 {
122 	char p[PATH_LEN], c[PATH_LEN], q[PATH_LEN], qc[PATH_LEN];
123 	char f[PATH_LEN], src[PATH_LEN], t[PATH_LEN], u[PATH_LEN], tc[PATH_LEN];
124 	int fds[4], ret;
125 	char cmd;
126 
127 	if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL))
128 		return 1;
129 	snprintf(p, sizeof(p), "%s/p", base);
130 	if (mkdir(p, 0755) || mount("tmpfs", p, "tmpfs", 0, NULL))
131 		return 2;
132 	snprintf(c, sizeof(c), "%s/p/covered", base);
133 	if (mkdir(c, 0755) || mount("tmpfs", c, "tmpfs", 0, NULL))
134 		return 3;
135 	snprintf(f, sizeof(f), "%s/p/file", base);
136 	snprintf(src, sizeof(src), "%s/p/src", base);
137 	if (touch(src) || touch(f) || mount(src, f, NULL, MS_BIND, NULL))
138 		return 4;
139 	snprintf(q, sizeof(q), "%s/q", base);
140 	if (mkdir(q, 0755) || mount(p, q, NULL, MS_BIND, NULL))
141 		return 5;
142 	snprintf(t, sizeof(t), "%s/t", base);
143 	if (mkdir(t, 0755) || mount("tmpfs", t, "tmpfs", 0, NULL))
144 		return 6;
145 	snprintf(tc, sizeof(tc), "%s/t/covered", base);
146 	if (mkdir(tc, 0755) || mount("tmpfs", tc, "tmpfs", 0, NULL))
147 		return 7;
148 	snprintf(u, sizeof(u), "%s/u", base);
149 	if (mkdir(u, 0755) || mount(t, u, NULL, MS_BIND, NULL))
150 		return 8;
151 	snprintf(tc, sizeof(tc), "%s/u/covered", base);
152 	if (mount("tmpfs", tc, "tmpfs", 0, NULL))
153 		return 9;
154 	fds[0] = open(p, O_PATH | O_DIRECTORY | O_CLOEXEC);
155 	fds[1] = open(c, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
156 	fds[2] = open(t, O_PATH | O_DIRECTORY | O_CLOEXEC);
157 	fds[3] = open(u, O_PATH | O_DIRECTORY | O_CLOEXEC);
158 	if (fds[0] < 0 || fds[1] < 0 || fds[2] < 0 || fds[3] < 0)
159 		return 10;
160 	if (write(to_parent, fds, sizeof(fds)) != sizeof(fds))
161 		return 11;
162 
163 	snprintf(qc, sizeof(qc), "%s/q/covered", base);
164 	for (;;) {
165 		if (read(from_parent, &cmd, 1) != 1)
166 			return 12;
167 		switch (cmd) {
168 		case CMD_RMDIR:
169 			ret = rmdir(qc) ? errno : 0;
170 			if (write(to_parent, &ret, sizeof(ret)) != sizeof(ret))
171 				return 13;
172 			break;
173 		case CMD_CLOSE_T:
174 			ret = close(fds[2]) ? errno : 0;
175 			if (write(to_parent, &ret, sizeof(ret)) != sizeof(ret))
176 				return 13;
177 			break;
178 		case CMD_QUIT:
179 			return 0;
180 		default:
181 			return 14;
182 		}
183 	}
184 }
185 
FIXTURE(mount_cover)186 FIXTURE(mount_cover) {
187 	char base[DIR_LEN];
188 	pid_t child;
189 	int to_child;
190 	int from_child;
191 	int dfd;	/* P, unmounted, held */
192 	int cfd;	/* C, unmounted, held */
193 	int fd;		/* what a lookup on P finds at C's mountpoint */
194 	int tfd;	/* T, unmounted, held */
195 	int ufd;	/* U, a bind of T, unmounted, held */
196 	int fan;	/* fanotify group from before the user namespace, or -1 */
197 	struct readdir_hold hold;	/* likewise from before, uffd -1 without */
198 };
199 
200 /*
201  * Everything the setup has made. The harness does not run the teardown
202  * when an assertion of the setup fails, so the setup calls this itself.
203  */
cover_cleanup(FIXTURE_DATA (mount_cover)* self)204 static void cover_cleanup(FIXTURE_DATA(mount_cover) *self)
205 {
206 	char cmd = CMD_QUIT;
207 	int status;
208 
209 	if (self->fd >= 0)
210 		close(self->fd);
211 	if (self->cfd >= 0)
212 		close(self->cfd);
213 	if (self->dfd >= 0)
214 		close(self->dfd);
215 	if (self->tfd >= 0)
216 		close(self->tfd);
217 	if (self->ufd >= 0)
218 		close(self->ufd);
219 	if (self->fan >= 0)
220 		close(self->fan);
221 	readdir_hold_destroy(&self->hold);
222 	if (self->child > 0) {
223 		if (write(self->to_child, &cmd, 1) != 1)
224 			kill(self->child, SIGKILL);
225 		waitpid(self->child, &status, 0);
226 	}
227 	if (self->to_child >= 0)
228 		close(self->to_child);
229 	if (self->from_child >= 0)
230 		close(self->from_child);
231 	umount2(self->base, MNT_DETACH);
232 	rmdir(self->base);
233 }
234 
same_file(int fd1,int fd2)235 static int same_file(int fd1, int fd2)
236 {
237 	struct stat st1, st2;
238 
239 	if (fstat(fd1, &st1) || fstat(fd2, &st2))
240 		return 0;
241 	return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino;
242 }
243 
FIXTURE_SETUP(mount_cover)244 FIXTURE_SETUP(mount_cover)
245 {
246 	int to_parent[2], to_child[2], fds[4], pidfd, status;
247 	char dir[PATH_LEN];
248 	struct statfs sf;
249 
250 	self->child = 0;
251 	self->to_child = self->from_child = -1;
252 	self->dfd = self->cfd = self->fd = self->tfd = self->ufd = -1;
253 
254 	/*
255 	 * A group for plain events takes CAP_SYS_ADMIN in the initial user
256 	 * namespace, so get one before that is gone. An inode mark can be
257 	 * added to it from anywhere.
258 	 */
259 	self->fan = fanotify_init(FAN_CLASS_NOTIF | FAN_CLOEXEC, O_RDONLY);
260 	/* same for the userfaultfd that holds a readdir in its fault */
261 	readdir_hold_init(&self->hold);
262 
263 	snprintf(self->base, sizeof(self->base), "/tmp/mount_cover.XXXXXX");
264 	ASSERT_NE(mkdtemp(self->base), NULL);
265 	if (enter_userns()) {
266 		cover_cleanup(self);
267 		SKIP(return, "test requires user namespaces");
268 	}
269 	ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0)
270 		cover_cleanup(self);
271 
272 	snprintf(dir, sizeof(dir), "%s/p", self->base);
273 	ASSERT_EQ(pipe(to_parent), 0)
274 		cover_cleanup(self);
275 	ASSERT_EQ(pipe(to_child), 0)
276 		cover_cleanup(self);
277 	self->child = fork();
278 	ASSERT_GE(self->child, 0)
279 		cover_cleanup(self);
280 	if (self->child == 0) {
281 		close(to_parent[0]);
282 		close(to_child[1]);
283 		_exit(cover_child(self->base, to_parent[1], to_child[0]));
284 	}
285 	close(to_parent[1]);
286 	close(to_child[0]);
287 	self->to_child = to_child[1];
288 	self->from_child = to_parent[0];
289 	if (read(self->from_child, fds, sizeof(fds)) != sizeof(fds)) {
290 		pid_t pid = self->child;
291 
292 		waitpid(pid, &status, 0);
293 		self->child = 0;
294 		cover_cleanup(self);
295 		ASSERT_TRUE(false)
296 			TH_LOG("child failed to set up: %s %d",
297 			       WIFEXITED(status) ? "step" : "signal",
298 			       WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status));
299 	}
300 
301 	pidfd = syscall(__NR_pidfd_open, self->child, 0);
302 	ASSERT_GE(pidfd, 0)
303 		cover_cleanup(self);
304 	self->dfd = syscall(__NR_pidfd_getfd, pidfd, fds[0], 0);
305 	self->cfd = syscall(__NR_pidfd_getfd, pidfd, fds[1], 0);
306 	self->tfd = syscall(__NR_pidfd_getfd, pidfd, fds[2], 0);
307 	self->ufd = syscall(__NR_pidfd_getfd, pidfd, fds[3], 0);
308 	close(pidfd);
309 	ASSERT_GE(self->dfd, 0)
310 		cover_cleanup(self);
311 	ASSERT_GE(self->cfd, 0)
312 		cover_cleanup(self);
313 	ASSERT_GE(self->tfd, 0)
314 		cover_cleanup(self);
315 	ASSERT_GE(self->ufd, 0)
316 		cover_cleanup(self);
317 
318 	/* unmounts P and C, C leaves its cover behind */
319 	ASSERT_EQ(rmdir(dir), 0)
320 		cover_cleanup(self);
321 	/* unmounts T and U with their children, two covers on one mountpoint */
322 	snprintf(dir, sizeof(dir), "%s/t", self->base);
323 	ASSERT_EQ(rmdir(dir), 0)
324 		cover_cleanup(self);
325 	snprintf(dir, sizeof(dir), "%s/u", self->base);
326 	ASSERT_EQ(rmdir(dir), 0)
327 		cover_cleanup(self);
328 
329 	self->fd = openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
330 	ASSERT_GE(self->fd, 0)
331 		cover_cleanup(self);
332 	ASSERT_EQ(fstatfs(self->fd, &sf), 0)
333 		cover_cleanup(self);
334 	ASSERT_EQ(sf.f_type, NULL_FS_MAGIC)
335 		cover_cleanup(self);
336 }
337 
FIXTURE_TEARDOWN(mount_cover)338 FIXTURE_TEARDOWN(mount_cover)
339 {
340 	cover_cleanup(self);
341 }
342 
TEST_F(mount_cover,not_watchable)343 TEST_F(mount_cover, not_watchable)
344 {
345 	char p[PATH_LEN];
346 	int ifd;
347 
348 	snprintf(p, sizeof(p), "/proc/self/fd/%d", self->fd);
349 
350 	ifd = inotify_init1(IN_CLOEXEC);
351 	if (ifd < 0 && errno == ENOSYS) {
352 		TH_LOG("no inotify in this kernel, skipping that part");
353 	} else {
354 		ASSERT_GE(ifd, 0);
355 		EXPECT_EQ(inotify_add_watch(ifd, p, IN_OPEN), -1);
356 		EXPECT_EQ(errno, EINVAL);
357 		close(ifd);
358 	}
359 
360 	/* dnotify ends up at the same place */
361 	EXPECT_EQ(fcntl(self->fd, F_NOTIFY, DN_ACCESS), -1);
362 	EXPECT_EQ(errno, EINVAL);
363 
364 	if (self->fan < 0) {
365 		TH_LOG("no fanotify group without CAP_SYS_ADMIN in the initial user namespace, skipping the fanotify part");
366 		return;
367 	}
368 	EXPECT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD, FAN_OPEN, self->fd, NULL), -1);
369 	EXPECT_EQ(errno, EINVAL);
370 }
371 
TEST_F(mount_cover,not_lockable)372 TEST_F(mount_cover, not_lockable)
373 {
374 	struct flock fl = {
375 		.l_type = F_RDLCK,
376 		.l_whence = SEEK_SET,
377 	};
378 
379 	EXPECT_EQ(flock(self->fd, LOCK_EX | LOCK_NB), -1);
380 	EXPECT_EQ(errno, ENOLCK);
381 	EXPECT_EQ(fcntl(self->fd, F_SETLK, &fl), -1);
382 	EXPECT_EQ(errno, ENOLCK);
383 	EXPECT_EQ(fcntl(self->fd, F_GETLK, &fl), -1);
384 	EXPECT_EQ(errno, ENOLCK);
385 }
386 
387 /* a lease is refused for the owner and for everybody else alike */
TEST_F(mount_cover,not_leasable)388 TEST_F(mount_cover, not_leasable)
389 {
390 	struct delegation_req deleg = {
391 		.d_type = F_RDLCK,
392 	};
393 
394 	EXPECT_EQ(fcntl(self->fd, F_SETLEASE, F_RDLCK), -1);
395 	EXPECT_TRUE(errno == EINVAL || errno == EACCES);
396 	EXPECT_EQ(fcntl(self->fd, F_SETDELEG, &deleg), -1);
397 	EXPECT_TRUE(errno == EINVAL || errno == EACCES);
398 }
399 
TEST_F(mount_cover,not_mountable)400 TEST_F(mount_cover, not_mountable)
401 {
402 	char p[PATH_LEN];
403 
404 	snprintf(p, sizeof(p), "/proc/self/fd/%d", self->fd);
405 	EXPECT_EQ(mount("tmpfs", p, "tmpfs", 0, NULL), -1);
406 	EXPECT_EQ(errno, ENOENT);
407 }
408 
TEST_F(mount_cover,read_only)409 TEST_F(mount_cover, read_only)
410 {
411 	struct statvfs sv;
412 
413 	EXPECT_EQ(mkdirat(self->fd, "x", 0755), -1);
414 	EXPECT_EQ(errno, ENOENT);
415 	EXPECT_EQ(fchmod(self->fd, 0777), -1);
416 	EXPECT_EQ(errno, EROFS);
417 	/* the immutable inode is checked before the read-only mount */
418 	EXPECT_EQ(faccessat(self->fd, ".", W_OK, 0), -1);
419 	EXPECT_EQ(errno, EPERM);
420 	ASSERT_EQ(fstatvfs(self->fd, &sv), 0);
421 	EXPECT_TRUE(sv.f_flag & ST_RDONLY);
422 }
423 
424 /* the stand-in is a root of its own, ".." stays put */
TEST_F(mount_cover,island)425 TEST_F(mount_cover, island)
426 {
427 	int fd;
428 
429 	fd = openat(self->fd, "..", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
430 	ASSERT_GE(fd, 0);
431 	EXPECT_TRUE(same_file(fd, self->fd));
432 	close(fd);
433 }
434 
435 /*
436  * C is alive for as long as the child holds it, but it can't be reached
437  * through P anymore and it's a root of its own as well.
438  */
TEST_F(mount_cover,held_child_detached)439 TEST_F(mount_cover, held_child_detached)
440 {
441 	struct statfs sf;
442 	int fd;
443 
444 	ASSERT_EQ(fstatfs(self->cfd, &sf), 0);
445 	EXPECT_EQ(sf.f_type, TMPFS_MAGIC);
446 
447 	fd = openat(self->cfd, "..", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
448 	ASSERT_GE(fd, 0);
449 	EXPECT_TRUE(same_file(fd, self->cfd));
450 	close(fd);
451 
452 	fd = openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
453 	ASSERT_GE(fd, 0);
454 	ASSERT_EQ(fstatfs(fd, &sf), 0);
455 	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
456 	close(fd);
457 }
458 
459 /*
460  * The cover goes with its mountpoint: once the child has removed C's
461  * mountpoint through the bind of P, the name is gone from P as well.
462  * What was opened through the cover before stays open.
463  */
TEST_F(mount_cover,cover_goes_with_mountpoint)464 TEST_F(mount_cover, cover_goes_with_mountpoint)
465 {
466 	char cmd = CMD_RMDIR;
467 	struct statfs sf;
468 	int ret;
469 
470 	ASSERT_EQ(write(self->to_child, &cmd, 1), 1);
471 	ASSERT_EQ(read(self->from_child, &ret, sizeof(ret)), sizeof(ret));
472 	ASSERT_EQ(ret, 0);
473 
474 	EXPECT_EQ(openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC), -1);
475 	EXPECT_EQ(errno, ENOENT);
476 	ASSERT_EQ(fstatfs(self->fd, &sf), 0);
477 	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
478 }
479 
480 /*
481  * A readdir of the stand-in holds nothing that others wait for: one holder
482  * sticks in the page fault of its buffer, and a create and a lookup of
483  * another come back meanwhile.
484  */
TEST_F(mount_cover,readdir_blocks_nobody)485 TEST_F(mount_cover, readdir_blocks_nobody)
486 {
487 	bool stalled;
488 
489 	if (self->hold.uffd < 0)
490 		SKIP(return, "test requires userfaultfd");
491 	ASSERT_EQ(readdir_hold_check(&self->hold, self->fd, &stalled), 0);
492 	EXPECT_FALSE(stalled);
493 }
494 
495 /* where a file was mounted, the stand-in is an empty regular file */
TEST_F(mount_cover,file_stand_in)496 TEST_F(mount_cover, file_stand_in)
497 {
498 	char src[PATH_LEN], p[PATH_LEN];
499 	struct statfs sf;
500 	struct stat st;
501 	char c;
502 	int fd;
503 
504 	fd = openat(self->dfd, "file", O_RDONLY | O_CLOEXEC);
505 	ASSERT_GE(fd, 0);
506 	ASSERT_EQ(fstat(fd, &st), 0);
507 	EXPECT_TRUE(S_ISREG(st.st_mode));
508 	ASSERT_EQ(fstatfs(fd, &sf), 0);
509 	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
510 	/* the two stand-ins are two inodes */
511 	EXPECT_FALSE(same_file(fd, self->fd));
512 	EXPECT_EQ(read(fd, &c, 1), 0);
513 	EXPECT_EQ(flock(fd, LOCK_EX | LOCK_NB), -1);
514 	EXPECT_EQ(errno, ENOLCK);
515 
516 	snprintf(src, sizeof(src), "%s/src", self->base);
517 	ASSERT_EQ(touch(src), 0);
518 	snprintf(p, sizeof(p), "/proc/self/fd/%d", fd);
519 	EXPECT_EQ(mount(src, p, NULL, MS_BIND, NULL), -1);
520 	EXPECT_EQ(errno, ENOENT);
521 	close(fd);
522 
523 	/* writes and truncates go nowhere, like /dev/null */
524 	fd = openat(self->dfd, "file", O_WRONLY | O_TRUNC | O_CLOEXEC);
525 	ASSERT_GE(fd, 0);
526 	EXPECT_EQ(write(fd, "xy", 2), 2);
527 	EXPECT_EQ(ftruncate(fd, 4), 0);
528 	ASSERT_EQ(fstat(fd, &st), 0);
529 	EXPECT_EQ(st.st_size, 0);
530 	/* the shared inode keeps its identity */
531 	EXPECT_EQ(fchmod(fd, 0600), -1);
532 	EXPECT_EQ(errno, EPERM);
533 	close(fd);
534 	fd = openat(self->dfd, "file", O_RDONLY | O_CLOEXEC);
535 	ASSERT_GE(fd, 0);
536 	EXPECT_EQ(read(fd, &c, 1), 0);
537 	close(fd);
538 
539 	EXPECT_EQ(openat(self->dfd, "file", O_RDONLY | O_DIRECTORY | O_CLOEXEC), -1);
540 	EXPECT_EQ(errno, ENOTDIR);
541 }
542 
543 /*
544  * Two unmounted parents left covers on the same dentry. A lookup on
545  * either finds a stand-in, and U's cover stays when T goes.
546  */
TEST_F(mount_cover,shared_mountpoint)547 TEST_F(mount_cover, shared_mountpoint)
548 {
549 	char cmd = CMD_CLOSE_T;
550 	struct statfs sf;
551 	int fd, ret;
552 
553 	fd = openat(self->tfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
554 	ASSERT_GE(fd, 0);
555 	ASSERT_EQ(fstatfs(fd, &sf), 0);
556 	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
557 	close(fd);
558 
559 	fd = openat(self->ufd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
560 	ASSERT_GE(fd, 0);
561 	ASSERT_EQ(fstatfs(fd, &sf), 0);
562 	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
563 	close(fd);
564 
565 	/* the last references to T go, with T its cover */
566 	ASSERT_EQ(write(self->to_child, &cmd, 1), 1);
567 	ASSERT_EQ(read(self->from_child, &ret, sizeof(ret)), sizeof(ret));
568 	ASSERT_EQ(ret, 0);
569 	close(self->tfd);
570 	self->tfd = -1;
571 
572 	fd = openat(self->ufd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
573 	ASSERT_GE(fd, 0);
574 	ASSERT_EQ(fstatfs(fd, &sf), 0);
575 	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
576 	close(fd);
577 }
578 
579 TEST_HARNESS_MAIN
580