1/*-
2 * Copyright (c) 2009 Robert N. M. Watson
3 * All rights reserved.
4 *
5 * This software was developed at the University of Cambridge Computer
6 * Laboratory with support from a grant from Google, Inc.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
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 AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD$");
32
33#include <sys/param.h>
34#include <sys/systm.h>
35#include <sys/conf.h>
36#include <sys/kernel.h>
37#include <sys/malloc.h>
38#include <sys/module.h>
39
40#include <sys/dtrace.h>
41#include <sys/dtrace_bsd.h>
42
43#include <nfs/nfsproto.h>
44
45/*
46 * dtnfsclient is a DTrace provider that tracks the intent to perform RPCs
47 * in the NFS client, as well as acess to and maintenance of the access and
48 * attribute caches.  This is not quite the same as RPCs, because NFS may
49 * issue multiple RPC transactions in the event that authentication fails,
50 * there's a jukebox error, or none at all if the access or attribute cache
51 * hits.  However, it cleanly represents the logical layer between RPC
52 * transmission and vnode/vfs operations, providing access to state linking
53 * the two.
54 */
55
56static int	dtnfsclient_unload(void);
57static void	dtnfsclient_getargdesc(void *, dtrace_id_t, void *,
58		    dtrace_argdesc_t *);
59static void	dtnfsclient_provide(void *, dtrace_probedesc_t *);
60static void	dtnfsclient_destroy(void *, dtrace_id_t, void *);
61static void	dtnfsclient_enable(void *, dtrace_id_t, void *);
62static void	dtnfsclient_disable(void *, dtrace_id_t, void *);
63static void	dtnfsclient_load(void *);
64
65static dtrace_pattr_t dtnfsclient_attr = {
66{ DTRACE_STABILITY_STABLE, DTRACE_STABILITY_STABLE, DTRACE_CLASS_COMMON },
67{ DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN },
68{ DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN },
69{ DTRACE_STABILITY_STABLE, DTRACE_STABILITY_STABLE, DTRACE_CLASS_COMMON },
70{ DTRACE_STABILITY_STABLE, DTRACE_STABILITY_STABLE, DTRACE_CLASS_COMMON },
71};
72
73/*
74 * Description of NFSv3 and (optional) NFSv2 probes for a procedure.
75 */
76struct dtnfsclient_rpc {
77	char		*nr_v3_name;
78	char		*nr_v2_name;	/* Or NULL if none. */
79
80	/*
81	 * IDs for the start and done cases, for both NFSv2 and NFSv3.
82	 */
83	uint32_t	 nr_v2_id_start, nr_v2_id_done;
84	uint32_t	 nr_v3_id_start, nr_v3_id_done;
85};
86
87/*
88 * This table is indexed by NFSv3 procedure number, but also used for NFSv2
89 * procedure names.
90 */
91static struct dtnfsclient_rpc	dtnfsclient_rpcs[NFS_NPROCS] = {
92	{ "null", "null" },
93	{ "getattr", "getattr" },
94	{ "setattr", "setattr" },
95	{ "lookup", "lookup" },
96	{ "access" },
97	{ "readlink", "readlink" },
98	{ "read", "read" },
99	{ "write", "write" },
100	{ "create", "create" },
101	{ "mkdir", "mkdir" },
102	{ "symlink", "symlink" },
103	{ "mknod" },
104	{ "remove", "remove" },
105	{ "rmdir", "rmdir" },
106	{ "rename", "rename" },
107	{ "link", "link" },
108	{ "readdir", "readdir" },
109	{ "readdirplus" },
110	{ "fsstat", "statfs" },
111	{ "fsinfo" },
112	{ "pathconf" },
113	{ "commit" },
114	{ "noop" },
115};
116
117/*
118 * Module name strings.
119 */
120static char	*dtnfsclient_accesscache_str = "accesscache";
121static char	*dtnfsclient_attrcache_str = "attrcache";
122static char	*dtnfsclient_nfs2_str = "nfs2";
123static char	*dtnfsclient_nfs3_str = "nfs3";
124
125/*
126 * Function name strings.
127 */
128static char	*dtnfsclient_flush_str = "flush";
129static char	*dtnfsclient_load_str = "load";
130static char	*dtnfsclient_get_str = "get";
131
132/*
133 * Name strings.
134 */
135static char	*dtnfsclient_done_str = "done";
136static char	*dtnfsclient_hit_str = "hit";
137static char	*dtnfsclient_miss_str = "miss";
138static char	*dtnfsclient_start_str = "start";
139
140static dtrace_pops_t dtnfsclient_pops = {
141	dtnfsclient_provide,
142	NULL,
143	dtnfsclient_enable,
144	dtnfsclient_disable,
145	NULL,
146	NULL,
147	dtnfsclient_getargdesc,
148	NULL,
149	NULL,
150	dtnfsclient_destroy
151};
152
153static dtrace_provider_id_t	dtnfsclient_id;
154
155/*
156 * Most probes are generated from the above RPC table, but for access and
157 * attribute caches, we have specific IDs we recognize and handle specially
158 * in various spots.
159 */
160extern uint32_t	nfsclient_accesscache_flush_done_id;
161extern uint32_t	nfsclient_accesscache_get_hit_id;
162extern uint32_t	nfsclient_accesscache_get_miss_id;
163extern uint32_t	nfsclient_accesscache_load_done_id;
164
165extern uint32_t	nfsclient_attrcache_flush_done_id;
166extern uint32_t	nfsclient_attrcache_get_hit_id;
167extern uint32_t	nfsclient_attrcache_get_miss_id;
168extern uint32_t	nfsclient_attrcache_load_done_id;
169
170/*
171 * When tracing on a procedure is enabled, the DTrace ID for an RPC event is
172 * stored in one of these two NFS client-allocated arrays; 0 indicates that
173 * the event is not being traced so probes should not be called.
174 *
175 * For simplicity, we allocate both v2 and v3 arrays as NFS_NPROCS, and the
176 * v2 array is simply sparse.
177 */
178extern uint32_t			nfsclient_nfs2_start_probes[NFS_NPROCS];
179extern uint32_t			nfsclient_nfs2_done_probes[NFS_NPROCS];
180
181extern uint32_t			nfsclient_nfs3_start_probes[NFS_NPROCS];
182extern uint32_t			nfsclient_nfs3_done_probes[NFS_NPROCS];
183
184/*
185 * Look up a DTrace probe ID to see if it's associated with a "done" event --
186 * if so, we will return a fourth argument type of "int".
187 */
188static int
189dtnfs23_isdoneprobe(dtrace_id_t id)
190{
191	int i;
192
193	for (i = 0; i < NFS_NPROCS; i++) {
194		if (dtnfsclient_rpcs[i].nr_v3_id_done == id ||
195		    dtnfsclient_rpcs[i].nr_v2_id_done == id)
196			return (1);
197	}
198	return (0);
199}
200
201static void
202dtnfsclient_getargdesc(void *arg, dtrace_id_t id, void *parg,
203    dtrace_argdesc_t *desc)
204{
205	const char *p = NULL;
206
207	if (id == nfsclient_accesscache_flush_done_id ||
208	    id == nfsclient_attrcache_flush_done_id ||
209	    id == nfsclient_attrcache_get_miss_id) {
210		switch (desc->dtargd_ndx) {
211		case 0:
212			p = "struct vnode *";
213			break;
214		default:
215			desc->dtargd_ndx = DTRACE_ARGNONE;
216			break;
217		}
218	} else if (id == nfsclient_accesscache_get_hit_id ||
219	    id == nfsclient_accesscache_get_miss_id) {
220		switch (desc->dtargd_ndx) {
221		case 0:
222			p = "struct vnode *";
223			break;
224		case 1:
225			p = "uid_t";
226			break;
227		case 2:
228			p = "uint32_t";
229			break;
230		default:
231			desc->dtargd_ndx = DTRACE_ARGNONE;
232			break;
233		}
234	} else if (id == nfsclient_accesscache_load_done_id) {
235		switch (desc->dtargd_ndx) {
236		case 0:
237			p = "struct vnode *";
238			break;
239		case 1:
240			p = "uid_t";
241			break;
242		case 2:
243			p = "uint32_t";
244			break;
245		case 3:
246			p = "int";
247			break;
248		default:
249			desc->dtargd_ndx = DTRACE_ARGNONE;
250			break;
251		}
252	} else if (id == nfsclient_attrcache_get_hit_id) {
253		switch (desc->dtargd_ndx) {
254		case 0:
255			p = "struct vnode *";
256			break;
257		case 1:
258			p = "struct vattr *";
259			break;
260		default:
261			desc->dtargd_ndx = DTRACE_ARGNONE;
262			break;
263		}
264	} else if (id == nfsclient_attrcache_load_done_id) {
265		switch (desc->dtargd_ndx) {
266		case 0:
267			p = "struct vnode *";
268			break;
269		case 1:
270			p = "struct vattr *";
271			break;
272		case 2:
273			p = "int";
274			break;
275		default:
276			desc->dtargd_ndx = DTRACE_ARGNONE;
277			break;
278		}
279	} else {
280		switch (desc->dtargd_ndx) {
281		case 0:
282			p = "struct vnode *";
283			break;
284		case 1:
285			p = "struct mbuf *";
286			break;
287		case 2:
288			p = "struct ucred *";
289			break;
290		case 3:
291			p = "int";
292			break;
293		case 4:
294			if (dtnfs23_isdoneprobe(id)) {
295				p = "int";
296				break;
297			}
298			/* FALLSTHROUGH */
299		default:
300			desc->dtargd_ndx = DTRACE_ARGNONE;
301			break;
302		}
303	}
304	if (p != NULL)
305		strlcpy(desc->dtargd_native, p, sizeof(desc->dtargd_native));
306}
307
308static void
309dtnfsclient_provide(void *arg, dtrace_probedesc_t *desc)
310{
311	int i;
312
313	if (desc != NULL)
314		return;
315
316	/*
317	 * Register access cache probes.
318	 */
319	if (dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_accesscache_str,
320	    dtnfsclient_flush_str, dtnfsclient_done_str) == 0) {
321		nfsclient_accesscache_flush_done_id = dtrace_probe_create(
322		    dtnfsclient_id, dtnfsclient_accesscache_str,
323		    dtnfsclient_flush_str, dtnfsclient_done_str, 0, NULL);
324	}
325	if (dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_accesscache_str,
326	    dtnfsclient_get_str, dtnfsclient_hit_str) == 0) {
327		nfsclient_accesscache_get_hit_id = dtrace_probe_create(
328		    dtnfsclient_id, dtnfsclient_accesscache_str,
329		    dtnfsclient_get_str, dtnfsclient_hit_str, 0, NULL);
330	}
331	if (dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_accesscache_str,
332	    dtnfsclient_get_str, dtnfsclient_miss_str) == 0) {
333		nfsclient_accesscache_get_miss_id = dtrace_probe_create(
334		    dtnfsclient_id, dtnfsclient_accesscache_str,
335		    dtnfsclient_get_str, dtnfsclient_miss_str, 0, NULL);
336	}
337	if (dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_accesscache_str,
338	    dtnfsclient_load_str, dtnfsclient_done_str) == 0) {
339		nfsclient_accesscache_load_done_id = dtrace_probe_create(
340		    dtnfsclient_id, dtnfsclient_accesscache_str,
341		    dtnfsclient_load_str, dtnfsclient_done_str, 0, NULL);
342	}
343
344	/*
345	 * Register attribute cache probes.
346	 */
347	if (dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_attrcache_str,
348	    dtnfsclient_flush_str, dtnfsclient_done_str) == 0) {
349		nfsclient_attrcache_flush_done_id = dtrace_probe_create(
350		    dtnfsclient_id, dtnfsclient_attrcache_str,
351		    dtnfsclient_flush_str, dtnfsclient_done_str, 0, NULL);
352	}
353	if (dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_attrcache_str,
354	    dtnfsclient_get_str, dtnfsclient_hit_str) == 0) {
355		nfsclient_attrcache_get_hit_id = dtrace_probe_create(
356		    dtnfsclient_id, dtnfsclient_attrcache_str,
357		    dtnfsclient_get_str, dtnfsclient_hit_str, 0, NULL);
358	}
359	if (dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_attrcache_str,
360	    dtnfsclient_get_str, dtnfsclient_miss_str) == 0) {
361		nfsclient_attrcache_get_miss_id = dtrace_probe_create(
362		    dtnfsclient_id, dtnfsclient_attrcache_str,
363		    dtnfsclient_get_str, dtnfsclient_miss_str, 0, NULL);
364	}
365	if (dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_attrcache_str,
366	    dtnfsclient_load_str, dtnfsclient_done_str) == 0) {
367		nfsclient_attrcache_load_done_id = dtrace_probe_create(
368		    dtnfsclient_id, dtnfsclient_attrcache_str,
369		    dtnfsclient_load_str, dtnfsclient_done_str, 0, NULL);
370	}
371
372	/*
373	 * Register NFSv2 RPC procedures; note sparseness check for each slot
374	 * in the NFSv3 procnum-indexed array.
375	 */
376	for (i = 0; i < NFS_NPROCS; i++) {
377		if (dtnfsclient_rpcs[i].nr_v2_name != NULL &&
378		    dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_nfs2_str,
379		    dtnfsclient_rpcs[i].nr_v2_name, dtnfsclient_start_str) ==
380		    0) {
381			dtnfsclient_rpcs[i].nr_v2_id_start =
382			    dtrace_probe_create(dtnfsclient_id,
383			    dtnfsclient_nfs2_str,
384			    dtnfsclient_rpcs[i].nr_v2_name,
385			    dtnfsclient_start_str, 0,
386			    &nfsclient_nfs2_start_probes[i]);
387		}
388		if (dtnfsclient_rpcs[i].nr_v2_name != NULL &&
389		    dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_nfs2_str,
390		    dtnfsclient_rpcs[i].nr_v2_name, dtnfsclient_done_str) ==
391		    0) {
392			dtnfsclient_rpcs[i].nr_v2_id_done =
393			    dtrace_probe_create(dtnfsclient_id,
394			    dtnfsclient_nfs2_str,
395			    dtnfsclient_rpcs[i].nr_v2_name,
396			    dtnfsclient_done_str, 0,
397			    &nfsclient_nfs2_done_probes[i]);
398		}
399	}
400
401	/*
402	 * Register NFSv3 RPC procedures.
403	 */
404	for (i = 0; i < NFS_NPROCS; i++) {
405		if (dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_nfs3_str,
406		    dtnfsclient_rpcs[i].nr_v3_name, dtnfsclient_start_str) ==
407		    0) {
408			dtnfsclient_rpcs[i].nr_v3_id_start =
409			    dtrace_probe_create(dtnfsclient_id,
410			    dtnfsclient_nfs3_str,
411			    dtnfsclient_rpcs[i].nr_v3_name,
412			    dtnfsclient_start_str, 0,
413			    &nfsclient_nfs3_start_probes[i]);
414		}
415		if (dtrace_probe_lookup(dtnfsclient_id, dtnfsclient_nfs3_str,
416		    dtnfsclient_rpcs[i].nr_v3_name, dtnfsclient_done_str) ==
417		    0) {
418			dtnfsclient_rpcs[i].nr_v3_id_done =
419			    dtrace_probe_create(dtnfsclient_id,
420			    dtnfsclient_nfs3_str,
421			    dtnfsclient_rpcs[i].nr_v3_name,
422			    dtnfsclient_done_str, 0,
423			    &nfsclient_nfs3_done_probes[i]);
424		}
425	}
426}
427
428static void
429dtnfsclient_destroy(void *arg, dtrace_id_t id, void *parg)
430{
431}
432
433static void
434dtnfsclient_enable(void *arg, dtrace_id_t id, void *parg)
435{
436	uint32_t *p = parg;
437	void *f = dtrace_probe;
438
439	if (id == nfsclient_accesscache_flush_done_id)
440		dtrace_nfsclient_accesscache_flush_done_probe = f;
441	else if (id == nfsclient_accesscache_get_hit_id)
442		dtrace_nfsclient_accesscache_get_hit_probe = f;
443	else if (id == nfsclient_accesscache_get_miss_id)
444		dtrace_nfsclient_accesscache_get_miss_probe = f;
445	else if (id == nfsclient_accesscache_load_done_id)
446		dtrace_nfsclient_accesscache_load_done_probe = f;
447	else if (id == nfsclient_attrcache_flush_done_id)
448		dtrace_nfsclient_attrcache_flush_done_probe = f;
449	else if (id == nfsclient_attrcache_get_hit_id)
450		dtrace_nfsclient_attrcache_get_hit_probe = f;
451	else if (id == nfsclient_attrcache_get_miss_id)
452		dtrace_nfsclient_attrcache_get_miss_probe = f;
453	else if (id == nfsclient_attrcache_load_done_id)
454		dtrace_nfsclient_attrcache_load_done_probe = f;
455	else
456		*p = id;
457}
458
459static void
460dtnfsclient_disable(void *arg, dtrace_id_t id, void *parg)
461{
462	uint32_t *p = parg;
463
464	if (id == nfsclient_accesscache_flush_done_id)
465		dtrace_nfsclient_accesscache_flush_done_probe = NULL;
466	else if (id == nfsclient_accesscache_get_hit_id)
467		dtrace_nfsclient_accesscache_get_hit_probe = NULL;
468	else if (id == nfsclient_accesscache_get_miss_id)
469		dtrace_nfsclient_accesscache_get_miss_probe = NULL;
470	else if (id == nfsclient_accesscache_load_done_id)
471		dtrace_nfsclient_accesscache_load_done_probe = NULL;
472	else if (id == nfsclient_attrcache_flush_done_id)
473		dtrace_nfsclient_attrcache_flush_done_probe = NULL;
474	else if (id == nfsclient_attrcache_get_hit_id)
475		dtrace_nfsclient_attrcache_get_hit_probe = NULL;
476	else if (id == nfsclient_attrcache_get_miss_id)
477		dtrace_nfsclient_attrcache_get_miss_probe = NULL;
478	else if (id == nfsclient_attrcache_load_done_id)
479		dtrace_nfsclient_attrcache_load_done_probe = NULL;
480	else
481		*p = 0;
482}
483
484static void
485dtnfsclient_load(void *dummy)
486{
487
488	if (dtrace_register("nfsclient", &dtnfsclient_attr,
489	    DTRACE_PRIV_USER, NULL, &dtnfsclient_pops, NULL,
490	    &dtnfsclient_id) != 0)
491		return;
492
493	dtrace_nfsclient_nfs23_start_probe =
494	    (dtrace_nfsclient_nfs23_start_probe_func_t)dtrace_probe;
495	dtrace_nfsclient_nfs23_done_probe =
496	    (dtrace_nfsclient_nfs23_done_probe_func_t)dtrace_probe;
497}
498
499
500static int
501dtnfsclient_unload()
502{
503
504	dtrace_nfsclient_nfs23_start_probe = NULL;
505	dtrace_nfsclient_nfs23_done_probe = NULL;
506
507	return (dtrace_unregister(dtnfsclient_id));
508}
509
510static int
511dtnfsclient_modevent(module_t mod __unused, int type, void *data __unused)
512{
513	int error = 0;
514
515	switch (type) {
516	case MOD_LOAD:
517		break;
518
519	case MOD_UNLOAD:
520		break;
521
522	case MOD_SHUTDOWN:
523		break;
524
525	default:
526		error = EOPNOTSUPP;
527		break;
528	}
529
530	return (error);
531}
532
533SYSINIT(dtnfsclient_load, SI_SUB_DTRACE_PROVIDER, SI_ORDER_ANY,
534    dtnfsclient_load, NULL);
535SYSUNINIT(dtnfsclient_unload, SI_SUB_DTRACE_PROVIDER, SI_ORDER_ANY,
536    dtnfsclient_unload, NULL);
537
538DEV_MODULE(dtnfsclient, dtnfsclient_modevent, NULL);
539MODULE_VERSION(dtnfsclient, 1);
540MODULE_DEPEND(dtnfsclient, dtrace, 1, 1, 1);
541MODULE_DEPEND(dtnfsclient, opensolaris, 1, 1, 1);
542MODULE_DEPEND(dtnfsclient, oldnfs, 1, 1, 1);
543