1 /*
2  * Copyright (c) 2015 Fujitsu Ltd.
3  * Author: Guangwen Feng <fenggw-fnst@cn.fujitsu.com>
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of version 2 of the GNU General Public License as
7  * published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it would be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12  *
13  * You should have received a copy of the GNU General Public License
14  * alone with this program.
15  */
16 
17 /*
18  * DESCRIPTION
19  *  Test for feature F_SETLEASE of fcntl(2).
20  *  "F_SETLEASE is used to establish a lease which provides a mechanism:
21  *   When a process (the lease breaker) performs an open(2) or truncate(2)
22  *   that conflicts with the lease, the system call will be blocked by
23  *   kernel, meanwhile the kernel notifies the lease holder by sending
24  *   it a signal (SIGIO by default), after the lease holder successes
25  *   to downgrade or remove the lease, the kernel permits the system
26  *   call of the lease breaker to proceed."
27  */
28 
29 #include <errno.h>
30 
31 #include "test.h"
32 #include "safe_macros.h"
33 
34 /*
35  * MIN_TIME_LIMIT is defined to 5 senconds as a minimal acceptable
36  * amount of time for the lease breaker waiting for unblock via
37  * lease holder voluntarily downgrade or remove the lease, if the
38  * lease breaker is unblocked within MIN_TIME_LIMIT we may consider
39  * that the feature of the lease mechanism works well.
40  *
41  * The lease-break-time is set to 45 seconds for timeout in kernel.
42  */
43 #define MIN_TIME_LIMIT	5
44 
45 #define OP_OPEN_RDONLY	0
46 #define OP_OPEN_WRONLY	1
47 #define OP_OPEN_RDWR	2
48 #define OP_TRUNCATE	3
49 
50 #define FILE_MODE	(S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID)
51 #define PATH_LS_BRK_T	"/proc/sys/fs/lease-break-time"
52 
53 static void setup(void);
54 static void do_test(int);
55 static int do_child(int);
56 static void cleanup(void);
57 
58 static int fd;
59 static int ls_brk_t;
60 static long type;
61 static sigset_t newset, oldset;
62 
63 /* Time limit for lease holder to receive SIGIO. */
64 static struct timespec timeout = {.tv_sec = 5};
65 
66 static struct test_case_t {
67 	int lease_type;
68 	int op_type;
69 	char *desc;
70 } test_cases[] = {
71 	{F_WRLCK, OP_OPEN_RDONLY,
72 		"open(O_RDONLY) conflicts with fcntl(F_SETLEASE, F_WRLCK)"},
73 	{F_WRLCK, OP_OPEN_WRONLY,
74 		"open(O_WRONLY) conflicts with fcntl(F_SETLEASE, F_WRLCK)"},
75 	{F_WRLCK, OP_OPEN_RDWR,
76 		"open(O_RDWR) conflicts with fcntl(F_SETLEASE, F_WRLCK)"},
77 	{F_WRLCK, OP_TRUNCATE,
78 		"truncate() conflicts with fcntl(F_SETLEASE, F_WRLCK)"},
79 	{F_RDLCK, OP_OPEN_WRONLY,
80 		"open(O_WRONLY) conflicts with fcntl(F_SETLEASE, F_RDLCK)"},
81 	{F_RDLCK, OP_OPEN_RDWR,
82 		"open(O_RDWR) conflicts with fcntl(F_SETLEASE, F_RDLCK)"},
83 	{F_RDLCK, OP_TRUNCATE,
84 		"truncate() conflicts with fcntl(F_SETLEASE, F_RDLCK)"},
85 };
86 
87 char *TCID = "fcntl33";
88 int TST_TOTAL = ARRAY_SIZE(test_cases);
89 
90 int main(int ac, char **av)
91 {
92 	int lc;
93 	int tc;
94 
95 	tst_parse_opts(ac, av, NULL, NULL);
96 
97 	setup();
98 
99 	for (lc = 0; TEST_LOOPING(lc); lc++) {
100 		tst_count = 0;
101 
102 		for (tc = 0; tc < TST_TOTAL; tc++)
103 			do_test(tc);
104 	}
105 
106 	cleanup();
107 	tst_exit();
108 }
109 
110 static void setup(void)
111 {
112 	tst_sig(FORK, DEF_HANDLER, cleanup);
113 
114 	tst_require_root();
115 
116 	tst_timer_check(CLOCK_MONOTONIC);
117 
118 	/* Backup and set the lease-break-time. */
119 	SAFE_FILE_SCANF(NULL, PATH_LS_BRK_T, "%d", &ls_brk_t);
120 	SAFE_FILE_PRINTF(NULL, PATH_LS_BRK_T, "%d", 45);
121 
122 	tst_tmpdir();
123 
124 	switch ((type = tst_fs_type(cleanup, "."))) {
125 	case TST_NFS_MAGIC:
126 	case TST_RAMFS_MAGIC:
127 	case TST_TMPFS_MAGIC:
128 		tst_brkm(TCONF, cleanup,
129 			 "Cannot do fcntl(F_SETLEASE, F_WRLCK) "
130 			 "on %s filesystem",
131 			 tst_fs_type_name(type));
132 	default:
133 		break;
134 	}
135 
136 	SAFE_TOUCH(cleanup, "file", FILE_MODE, NULL);
137 
138 	sigemptyset(&newset);
139 	sigaddset(&newset, SIGIO);
140 
141 	if (sigprocmask(SIG_SETMASK, &newset, &oldset) < 0)
142 		tst_brkm(TBROK | TERRNO, cleanup, "sigprocmask() failed");
143 
144 	TEST_PAUSE;
145 }
146 
147 static void do_test(int i)
148 {
149 	fd = SAFE_OPEN(cleanup, "file", O_RDONLY);
150 
151 	pid_t cpid = tst_fork();
152 
153 	if (cpid < 0)
154 		tst_brkm(TBROK | TERRNO, cleanup, "fork() failed");
155 
156 	if (cpid == 0) {
157 		SAFE_CLOSE(NULL, fd);
158 		do_child(i);
159 	}
160 
161 	TEST(fcntl(fd, F_SETLEASE, test_cases[i].lease_type));
162 	if (TEST_RETURN == -1) {
163 		tst_resm(TFAIL | TTERRNO, "fcntl() failed to set lease");
164 		SAFE_WAITPID(cleanup, cpid, NULL, 0);
165 		SAFE_CLOSE(cleanup, fd);
166 		fd = 0;
167 		return;
168 	}
169 
170 	/* Wait for SIGIO caused by lease breaker. */
171 	TEST(sigtimedwait(&newset, NULL, &timeout));
172 	if (TEST_RETURN == -1) {
173 		if (TEST_ERRNO == EAGAIN) {
174 			tst_resm(TFAIL | TTERRNO, "failed to receive SIGIO "
175 				 "within %lis", timeout.tv_sec);
176 			SAFE_WAITPID(cleanup, cpid, NULL, 0);
177 			SAFE_CLOSE(cleanup, fd);
178 			fd = 0;
179 			return;
180 		}
181 		tst_brkm(TBROK | TTERRNO, cleanup, "sigtimedwait() failed");
182 	}
183 
184 	/* Try to downgrade or remove the lease. */
185 	switch (test_cases[i].lease_type) {
186 	case F_WRLCK:
187 		TEST(fcntl(fd, F_SETLEASE, F_RDLCK));
188 		if (TEST_RETURN == 0)
189 			break;
190 	case F_RDLCK:
191 		TEST(fcntl(fd, F_SETLEASE, F_UNLCK));
192 		if (TEST_RETURN == -1) {
193 			tst_resm(TFAIL | TTERRNO,
194 				 "fcntl() failed to remove the lease");
195 		}
196 		break;
197 	default:
198 		break;
199 	}
200 
201 	tst_record_childstatus(cleanup, cpid);
202 
203 	SAFE_CLOSE(cleanup, fd);
204 	fd = 0;
205 }
206 
207 static int do_child(int i)
208 {
209 	long long elapsed_ms;
210 
211 	if (tst_process_state_wait2(getppid(), 'S') != 0) {
212 		tst_brkm(TBROK | TERRNO, NULL,
213 			 "failed to wait for parent process's state");
214 	}
215 
216 	tst_timer_start(CLOCK_MONOTONIC);
217 
218 	switch (test_cases[i].op_type) {
219 	case OP_OPEN_RDONLY:
220 		SAFE_OPEN(NULL, "file", O_RDONLY);
221 		break;
222 	case OP_OPEN_WRONLY:
223 		SAFE_OPEN(NULL, "file", O_WRONLY);
224 		break;
225 	case OP_OPEN_RDWR:
226 		SAFE_OPEN(NULL, "file", O_RDWR);
227 		break;
228 	case OP_TRUNCATE:
229 		SAFE_TRUNCATE(NULL, "file", 0);
230 		break;
231 	default:
232 		break;
233 	}
234 
235 	tst_timer_stop();
236 
237 	elapsed_ms = tst_timer_elapsed_ms();
238 
239 	if (elapsed_ms < MIN_TIME_LIMIT * 1000) {
240 		tst_resm(TPASS, "%s, unblocked within %ds",
241 			 test_cases[i].desc, MIN_TIME_LIMIT);
242 	} else {
243 		tst_resm(TFAIL, "%s, blocked too long %llims, "
244 			 "expected within %ds",
245 			 test_cases[i].desc, elapsed_ms, MIN_TIME_LIMIT);
246 	}
247 
248 	tst_exit();
249 }
250 
251 static void cleanup(void)
252 {
253 	if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0)
254 		tst_resm(TWARN | TERRNO, "sigprocmask restore oldset failed");
255 
256 	if (fd > 0 && close(fd))
257 		tst_resm(TWARN | TERRNO, "failed to close file");
258 
259 	tst_rmdir();
260 
261 	/* Restore the lease-break-time. */
262 	FILE_PRINTF(PATH_LS_BRK_T, "%d", ls_brk_t);
263 }
264