1/*-
2 * Copyright (c) 2021-2022 Juniper Networks
3 *
4 * This software was developed by Mitchell Horne <mhorne@FreeBSD.org>
5 * under sponsorship from Juniper Networks and Klara Systems.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/param.h>
30#include <sys/systm.h>
31#include <sys/conf.h>
32#include <sys/caprights.h>
33#include <sys/disk.h>
34#include <sys/eventhandler.h>
35#include <sys/fcntl.h>
36#include <sys/file.h>
37#include <sys/kerneldump.h>
38#include <sys/limits.h>
39#include <sys/malloc.h>
40#include <sys/namei.h>
41#include <sys/priv.h>
42#include <sys/proc.h>
43#include <sys/stat.h>
44#include <sys/sysctl.h>
45#include <sys/vnode.h>
46
47#include <machine/pcb.h>
48#include <machine/vmparam.h>
49
50static dumper_start_t vnode_dumper_start;
51static dumper_t vnode_dump;
52static dumper_hdr_t vnode_write_headers;
53
54static struct sx livedump_sx;
55SX_SYSINIT(livedump, &livedump_sx, "Livedump sx");
56
57/*
58 * Invoke a live minidump on the system.
59 */
60int
61livedump_start(int fd, int flags, uint8_t compression)
62{
63#if MINIDUMP_PAGE_TRACKING == 1
64	struct file *fp;
65	struct vnode *vp;
66	int error;
67
68	error = priv_check(curthread, PRIV_KMEM_READ);
69	if (error != 0)
70		return (error);
71
72	if (flags != 0)
73		return (EINVAL);
74
75	error = getvnode(curthread, fd, &cap_write_rights, &fp);
76	if (error != 0)
77		return (error);
78	vp = fp->f_vnode;
79
80	if ((fp->f_flag & FWRITE) == 0) {
81		error = EBADF;
82		goto drop;
83	}
84	error = livedump_start_vnode(vp, flags, compression);
85	if (error != 0)
86		goto drop;
87drop:
88	fdrop(fp, curthread);
89	return (error);
90#else
91	return (EOPNOTSUPP);
92#endif /* MINIDUMP_PAGE_TRACKING == 1 */
93}
94
95int
96livedump_start_vnode(struct vnode *vp, int flags, uint8_t compression)
97{
98#if MINIDUMP_PAGE_TRACKING == 1
99	struct dumperinfo di, *livedi;
100	struct diocskerneldump_arg kda;
101	void *rl_cookie;
102	int error;
103
104        /* Set up a new dumper. */
105	bzero(&di, sizeof(di));
106	di.dumper_start = vnode_dumper_start;
107	di.dumper = vnode_dump;
108	di.dumper_hdr = vnode_write_headers;
109	di.blocksize = PAGE_SIZE; /* Arbitrary. */
110	di.maxiosize = MAXDUMPPGS * PAGE_SIZE;
111
112	bzero(&kda, sizeof(kda));
113	kda.kda_compression = compression;
114	error = dumper_create(&di, "livedump", &kda, &livedi);
115	if (error != 0)
116		return (error);
117
118	/* Only allow one livedump to proceed at a time. */
119	if (sx_try_xlock(&livedump_sx) == 0) {
120		dumper_destroy(livedi);
121		error = EBUSY;
122		return (error);
123	}
124
125	/* To be used by the callback functions. */
126	livedi->priv = vp;
127
128	/* Lock the entire file range and vnode. */
129	rl_cookie = vn_rangelock_wlock(vp, 0, OFF_MAX);
130	vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
131
132	EVENTHANDLER_INVOKE(livedumper_start, &error);
133	if (error != 0)
134		goto out;
135
136	dump_savectx();
137	error = minidumpsys(livedi, true);
138
139	EVENTHANDLER_INVOKE(livedumper_finish);
140out:
141	VOP_UNLOCK(vp);
142	vn_rangelock_unlock(vp, rl_cookie);
143	sx_xunlock(&livedump_sx);
144	dumper_destroy(livedi);
145	return (error);
146#else
147	return (EOPNOTSUPP);
148#endif /* MINIDUMP_PAGE_TRACKING == 1 */
149}
150
151int
152vnode_dumper_start(struct dumperinfo *di, void *key, uint32_t keysize)
153{
154
155	/* Always begin with an offset of zero. */
156	di->dumpoff = 0;
157
158	KASSERT(keysize == 0, ("encryption not supported for livedumps"));
159	return (0);
160}
161
162/*
163 * Callback from dumpsys() to dump a chunk of memory.
164 *
165 * Parameters:
166 *	arg	 Opaque private pointer to vnode
167 *	virtual  Virtual address (where to read the data from)
168 *	offset	 Offset from start of core file
169 *	length	 Data length
170 *
171 * Return value:
172 *	0 on success
173 *	errno on error
174 */
175int
176vnode_dump(void *arg, void *virtual, off_t offset, size_t length)
177{
178	struct vnode *vp;
179	int error = 0;
180
181	vp = arg;
182	MPASS(vp != NULL);
183	ASSERT_VOP_LOCKED(vp, __func__);
184
185	EVENTHANDLER_INVOKE(livedumper_dump, virtual, offset, length, &error);
186	if (error != 0)
187		return (error);
188
189	/* Done? */
190	if (virtual == NULL)
191		return (0);
192
193	error = vn_rdwr(UIO_WRITE, vp, virtual, length, offset, UIO_SYSSPACE,
194	    IO_NODELOCKED, curthread->td_ucred, NOCRED, NULL, curthread);
195	if (error != 0)
196		uprintf("%s: error writing livedump block at offset %jx: %d\n",
197		    __func__, (uintmax_t)offset, error);
198	return (error);
199}
200
201/*
202 * Callback from dumpsys() to write out the dump header, placed at the end.
203 */
204int
205vnode_write_headers(struct dumperinfo *di, struct kerneldumpheader *kdh)
206{
207	struct vnode *vp;
208	int error;
209	off_t offset;
210
211	vp = di->priv;
212	MPASS(vp != NULL);
213	ASSERT_VOP_LOCKED(vp, __func__);
214
215	/* Compensate for compression/encryption adjustment of dumpoff. */
216	offset = roundup2(di->dumpoff, di->blocksize);
217
218	/* Write the kernel dump header to the end of the file. */
219	error = vn_rdwr(UIO_WRITE, vp, kdh, sizeof(*kdh), offset,
220	    UIO_SYSSPACE, IO_NODELOCKED, curthread->td_ucred, NOCRED, NULL,
221	    curthread);
222	if (error != 0)
223		uprintf("%s: error writing livedump header: %d\n", __func__,
224		    error);
225	return (error);
226}
227