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