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