xref: /openbsd-src/regress/sys/uvm/vnode/vnode.c (revision d2c5a4743fb945f45b034a3a830a96f7e1bc695d)
1 /*
2  * Copyright (c) 2020 Anton Lindqvist <anton@openbsd.org>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include <sys/types.h>
18 #include <sys/param.h>	/* PAGE_SIZE */
19 #include <sys/mman.h>
20 #include <sys/uio.h>
21 #include <sys/wait.h>
22 
23 #include <err.h>
24 #include <fcntl.h>
25 #include <signal.h>
26 #include <signal.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 
32 static void growshrink(const char *);
33 static void writer(const char *, int);
34 
35 static void sighandler(int);
36 static void __dead usage(void);
37 
38 static int gotsig;
39 static int npages = 2;
40 
41 /*
42  * Trigger a deadlock between uvn_flush() and uvn_io() caused by uvn_flush()
43  * holding the vnode lock while waiting for pages to become unbusy; the busy
44  * pages are allocated and marked as busy by uvn_io() which in turn is trying to
45  * lock the same vnode while populating the page(s) using I/O on the vnode.
46  */
47 int
48 main(int argc, char *argv[])
49 {
50 	const char *path;
51 	pid_t pid;
52 	int killpip[2];
53 	int status;
54 	int iterations = 100;
55 	char ch;
56 
57 	while ((ch = getopt(argc, argv, "I")) != -1) {
58 		switch (ch) {
59 		case 'I':
60 			iterations = -1;
61 			break;
62 		default:
63 			usage();
64 		}
65 	}
66 	argc -= optind;
67 	argv += optind;
68 	if (argc != 1)
69 		usage();
70 	path = argv[0];
71 
72 	if (pipe2(killpip, O_NONBLOCK) == -1)
73 		err(1, "pipe2");
74 
75 	if (signal(SIGINT, sighandler) == SIG_ERR)
76 		err(1, "signal");
77 
78 	pid = fork();
79 	if (pid == -1)
80 		err(1, "fork");
81 	if (pid == 0) {
82 		close(killpip[1]);
83 		writer(path, killpip[0]);
84 		return 0;
85 	}
86 	close(killpip[0]);
87 
88 	fprintf(stderr, "parent = %d, child = %d\n", getpid(), pid);
89 
90 	while (gotsig == 0) {
91 		if (iterations > 0 && --iterations == 0)
92 			break;
93 
94 		growshrink(path);
95 	}
96 
97 	/* Signal shutdown to writer() process. */
98 	write(killpip[1], &ch, 1);
99 	close(killpip[1]);
100 
101 	if (waitpid(pid, &status, 0) == -1)
102 		err(1, "waitpid");
103 	if (WIFSIGNALED(status))
104 		return 128 + WTERMSIG(status);
105 	if (WIFEXITED(status))
106 		return WEXITSTATUS(status);
107 	return 0;
108 }
109 
110 static void
111 growshrink(const char *path)
112 {
113 	char *p;
114 	int fd;
115 
116 	/*
117 	 * Open and truncate the file causing uvn_flush() to try evict pages
118 	 * which are currently being populated by the writer() process.
119 	 */
120 	fd = open(path, O_RDWR | O_TRUNC);
121 	if (fd == -1)
122 		err(1, "open: %s", path);
123 
124 	/* Grow the file again. */
125 	if (ftruncate(fd, npages * PAGE_SIZE) == -1)
126 		err(1, "ftruncate");
127 
128 	p = mmap(NULL, npages * PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
129 	    fd, 0);
130 	if (p == MAP_FAILED)
131 		err(1, "mmap");
132 
133 	/* Populate the last page. */
134 	memset(&p[(npages - 1) * PAGE_SIZE], 'x', PAGE_SIZE);
135 
136 	if (munmap(p, npages * PAGE_SIZE) == -1)
137 		err(1, "munmap");
138 	close(fd);
139 }
140 
141 static void
142 writer(const char *path, int killfd)
143 {
144 	char *p;
145 	int fd;
146 	char c;
147 
148 	fd = open(path, O_RDONLY);
149 	if (fd == -1)
150 		err(1, "open: %s", path);
151 	p = mmap(NULL, npages * PAGE_SIZE, PROT_READ, MAP_SHARED,
152 	    fd, 0);
153 	if (p == MAP_FAILED)
154 		err(1, "mmap");
155 
156 	for (;;) {
157 		const struct iovec *iov = (const struct iovec *)p;
158 
159 		if (read(killfd, &c, 1) == 1)
160 			break;
161 
162 		/*
163 		 * This write should never succeed since the file descriptor is
164 		 * invalid. However, it should cause a page fault during
165 		 * copyin() which in turn will invoke uvn_io() while trying to
166 		 * populate the page. At this point, it will try to lock the
167 		 * vnode, which is potentially already locked by the
168 		 * growshrink() process.
169 		 */
170 		pwritev(-1, iov, 1, 0);
171 	}
172 }
173 
174 static void
175 sighandler(int signo)
176 {
177 	gotsig = signo;
178 }
179 
180 static void __dead
181 usage(void)
182 {
183 	fprintf(stderr, "usage: vnode [-I] path\n");
184 	exit(1);
185 }
186