xref: /linux/tools/testing/selftests/filesystems/rw_hint_test.c (revision 1514faf41f7ccd64c987e1006ddebbe825a1664c)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * F_SET_RW_HINT is refused on an immutable inode. Nothing is ever written
4  * to it and it may be shared with everybody, like a namespace file or the
5  * root of an empty mount namespace.
6  */
7 #define _GNU_SOURCE
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <sched.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <sys/ioctl.h>
17 #include <sys/stat.h>
18 #include <sys/wait.h>
19 
20 #include "../kselftest_harness.h"
21 
22 /* <linux/fs.h> and <linux/fcntl.h> don't mix with the libc headers */
23 #ifndef FS_IOC_GETFLAGS
24 #define FS_IOC_GETFLAGS		_IOR('f', 1, long)
25 #define FS_IOC_SETFLAGS		_IOW('f', 2, long)
26 #endif
27 #ifndef FS_IMMUTABLE_FL
28 #define FS_IMMUTABLE_FL		0x00000010
29 #endif
30 #ifndef F_LINUX_SPECIFIC_BASE
31 #define F_LINUX_SPECIFIC_BASE	1024
32 #endif
33 #ifndef F_GET_RW_HINT
34 #define F_GET_RW_HINT		(F_LINUX_SPECIFIC_BASE + 11)
35 #define F_SET_RW_HINT		(F_LINUX_SPECIFIC_BASE + 12)
36 #endif
37 #ifndef RWH_WRITE_LIFE_SHORT
38 #define RWH_WRITE_LIFE_SHORT	2
39 #endif
40 #ifndef UNSHARE_EMPTY_MNTNS
41 #define UNSHARE_EMPTY_MNTNS	0x00100000
42 #endif
43 
44 static int set_hint(int fd, uint64_t hint)
45 {
46 	return fcntl(fd, F_SET_RW_HINT, &hint);
47 }
48 
49 static long get_hint(int fd)
50 {
51 	uint64_t hint;
52 
53 	if (fcntl(fd, F_GET_RW_HINT, &hint))
54 		return -1;
55 	return hint;
56 }
57 
58 TEST(immutable_file)
59 {
60 	char path[] = "/tmp/rw_hint.XXXXXX";
61 	int fd, flags;
62 
63 	if (geteuid())
64 		SKIP(return, "test requires root");
65 
66 	fd = mkstemp(path);
67 	ASSERT_GE(fd, 0);
68 	unlink(path);
69 	ASSERT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), 0);
70 	EXPECT_EQ(get_hint(fd), RWH_WRITE_LIFE_SHORT);
71 
72 	if (ioctl(fd, FS_IOC_GETFLAGS, &flags)) {
73 		close(fd);
74 		SKIP(return, "no file attributes on this filesystem");
75 	}
76 	flags |= FS_IMMUTABLE_FL;
77 	ASSERT_EQ(ioctl(fd, FS_IOC_SETFLAGS, &flags), 0);
78 	EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), -1);
79 	EXPECT_EQ(errno, EPERM);
80 	flags &= ~FS_IMMUTABLE_FL;
81 	ASSERT_EQ(ioctl(fd, FS_IOC_SETFLAGS, &flags), 0);
82 	EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), 0);
83 	close(fd);
84 }
85 
86 TEST(namespace_file)
87 {
88 	int fd;
89 
90 	if (geteuid())
91 		SKIP(return, "test requires root");
92 
93 	fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
94 	ASSERT_GE(fd, 0);
95 	EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), -1);
96 	EXPECT_EQ(errno, EPERM);
97 	close(fd);
98 }
99 
100 TEST(empty_mntns_root)
101 {
102 	int status;
103 	pid_t pid;
104 
105 	if (geteuid())
106 		SKIP(return, "test requires root");
107 
108 	pid = fork();
109 	ASSERT_GE(pid, 0);
110 	if (pid == 0) {
111 		int fd;
112 
113 		if (unshare(UNSHARE_EMPTY_MNTNS))
114 			_exit(errno == EINVAL ? 100 : 1);
115 		fd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
116 		if (fd < 0)
117 			_exit(2);
118 		if (set_hint(fd, RWH_WRITE_LIFE_SHORT) == 0)
119 			_exit(3);
120 		_exit(errno == EPERM ? 0 : 4);
121 	}
122 	ASSERT_EQ(waitpid(pid, &status, 0), pid);
123 	ASSERT_TRUE(WIFEXITED(status));
124 	if (WEXITSTATUS(status) == 100)
125 		SKIP(return, "UNSHARE_EMPTY_MNTNS not supported");
126 	EXPECT_EQ(WEXITSTATUS(status), 0);
127 }
128 
129 TEST_HARNESS_MAIN
130