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