xref: /illumos-gate/usr/src/cmd/mdb/common/modules/nfs/nfssrv.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 /*
12  * Copyright 2021 Tintri by DDN, Inc. All rights reserved.
13  */
14 
15 #include <sys/mdb_modapi.h>
16 #include <nfs/export.h>
17 #include <sys/pkp_hash.h>
18 #include <limits.h>
19 
20 #include "nfssrv.h"
21 #include "common.h"
22 
23 /*
24  * nfs_expvis dcmd implementation
25  */
26 
27 static const mdb_bitmask_t sec_flag_bits[] = {
28 	{"M_RO", M_RO, M_RO},
29 	{"M_ROL", M_ROL, M_ROL},
30 	{"M_RW", M_RW, M_RW},
31 	{"M_RWL", M_RWL, M_RWL},
32 	{"M_ROOT", M_ROOT, M_ROOT},
33 	{"M_EXP", M_4SEC_EXPORTED, M_4SEC_EXPORTED},
34 	{NULL, 0, 0}
35 };
36 
37 static int
38 print_sec(int cnt, uintptr_t addr)
39 {
40 	int i;
41 
42 	if (cnt == 0)
43 		return (DCMD_OK);
44 
45 	mdb_printf("Security Flavors :\n");
46 	mdb_inc_indent(4);
47 
48 	for (i = 0; i < cnt; i++) {
49 		struct secinfo si;
50 		const char *s;
51 
52 		if (mdb_vread(&si, sizeof (si), addr) == -1) {
53 			mdb_warn("can't read struct secinfo");
54 			return (DCMD_ERR);
55 		}
56 
57 		switch (si.s_secinfo.sc_nfsnum) {
58 		case AUTH_NONE:
59 			s = "none";
60 			break;
61 		case AUTH_SYS:
62 			s = "sys";
63 			break;
64 		case AUTH_DH:
65 			s = "dh";
66 			break;
67 		case 390003:
68 			s = "krb5";
69 			break;
70 		case 390004:
71 			s = "krb5i";
72 			break;
73 		case 390005:
74 			s = "krb5p";
75 			break;
76 		default:
77 			s = "???";
78 			break;
79 		}
80 
81 		mdb_printf("%-8s ref: %-8i flag: %#x (%b)\n", s, si.s_refcnt,
82 		    si.s_flags, si.s_flags, sec_flag_bits);
83 
84 		addr = (uintptr_t)((struct secinfo *)addr + 1);
85 	}
86 
87 	mdb_dec_indent(4);
88 
89 	return (DCMD_OK);
90 }
91 
92 int
93 nfs_expvis_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
94 {
95 	exp_visible_t expvis;
96 	uintptr_t vp;
97 	char *s;
98 	int status;
99 
100 	if (argc > 0)
101 		return (DCMD_USAGE);
102 
103 	if ((flags & DCMD_ADDRSPEC) == 0) {
104 		mdb_printf("requires address of exp_visible_t\n");
105 		return (DCMD_USAGE);
106 	}
107 
108 	if (mdb_vread(&expvis, sizeof (expvis), addr) == -1) {
109 		mdb_warn("can't read exp_visible_t");
110 		return (DCMD_ERR);
111 	}
112 
113 	/* vp = expvis.vis_vp->v_path */
114 	if (mdb_vread(&vp, sizeof (vp), (uintptr_t)expvis.vis_vp
115 	    + OFFSETOF(vnode_t, v_path)) == -1) {
116 		mdb_warn("can't read vnode_t");
117 		return (DCMD_ERR);
118 	}
119 
120 	s = mdb_alloc(PATH_MAX, UM_SLEEP | UM_GC);
121 	if (mdb_readstr(s, PATH_MAX, vp) == -1) {
122 		mdb_warn("can't read v_path");
123 		return (DCMD_ERR);
124 	}
125 
126 	mdb_printf("%s\n", s);
127 
128 	mdb_inc_indent(4);
129 
130 	mdb_printf("addr: %?p   exp : %i    ref: %i\n", addr,
131 	    expvis.vis_exported, expvis.vis_count);
132 	mdb_printf("vp  : %?p   ino : %llu (%#llx)\n", expvis.vis_vp,
133 	    expvis.vis_ino, expvis.vis_ino);
134 	mdb_printf("seci: %?p   nsec: %i\n", expvis.vis_secinfo,
135 	    expvis.vis_seccnt);
136 
137 	status = print_sec(expvis.vis_seccnt, (uintptr_t)expvis.vis_secinfo);
138 
139 	mdb_dec_indent(4);
140 
141 	return (status);
142 }
143 
144 
145 /*
146  * nfs_expinfo dcmd implementation
147  */
148 
149 static const mdb_bitmask_t exp_flag_bits[] = {
150 	{"EX_NOSUID", EX_NOSUID, EX_NOSUID},
151 	{"EX_ACLOK", EX_ACLOK, EX_ACLOK},
152 	{"EX_PUBLIC", EX_PUBLIC, EX_PUBLIC},
153 	{"EX_NOSUB", EX_NOSUB, EX_NOSUB},
154 	{"EX_INDEX", EX_INDEX, EX_INDEX},
155 	{"EX_LOG", EX_LOG, EX_LOG},
156 	{"EX_LOG_ALLOPS", EX_LOG_ALLOPS, EX_LOG_ALLOPS},
157 	{"EX_PSEUDO", EX_PSEUDO, EX_PSEUDO},
158 	{NULL, 0, 0}
159 };
160 
161 int
162 nfs_expinfo_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
163 {
164 	struct exportinfo exi;
165 	char *path;
166 	int status;
167 	uint_t v_flag;
168 
169 	if (argc > 0)
170 		return (DCMD_USAGE);
171 
172 	if ((flags & DCMD_ADDRSPEC) == 0) {
173 		mdb_printf("requires address of struct exporinfo\n");
174 		return (DCMD_USAGE);
175 	}
176 
177 	if (mdb_vread(&exi, sizeof (exi), addr) == -1) {
178 		mdb_warn("can't read struct exportinfo");
179 		return (DCMD_ERR);
180 	}
181 
182 	if (mdb_vread(&v_flag, sizeof (v_flag), (uintptr_t)exi.exi_vp
183 	    + OFFSETOF(vnode_t, v_flag)) == -1) {
184 		mdb_warn("can't read v_flag");
185 		return (DCMD_ERR);
186 	}
187 
188 	path = mdb_alloc(exi.exi_export.ex_pathlen + 1, UM_SLEEP | UM_GC);
189 	if (mdb_readstr(path, exi.exi_export.ex_pathlen + 1,
190 	    (uintptr_t)exi.exi_export.ex_path) == -1) {
191 		mdb_warn("can't read ex_path");
192 		return (DCMD_ERR);
193 	}
194 
195 	mdb_printf("\n%s    %p\n", path, addr);
196 
197 	mdb_inc_indent(4);
198 
199 	mdb_printf("rtvp: %?p    ref : %-8u flag: %#x (%b)%s\n", exi.exi_vp,
200 	    exi.exi_count, exi.exi_export.ex_flags, exi.exi_export.ex_flags,
201 	    exp_flag_bits, v_flag & VROOT ? " VROOT" : "");
202 
203 	mdb_printf("dvp : %?p    anon: %-8u logb: %p\n", exi.exi_dvp,
204 	    exi.exi_export.ex_anon, exi.exi_logbuffer);
205 	mdb_printf("seci: %?p    nsec: %-8i fsid: (%#x %#x)\n",
206 	    exi.exi_export.ex_secinfo, exi.exi_export.ex_seccnt,
207 	    exi.exi_fsid.val[0], exi.exi_fsid.val[1]);
208 
209 	status = print_sec(exi.exi_export.ex_seccnt,
210 	    (uintptr_t)exi.exi_export.ex_secinfo);
211 	if (status != DCMD_OK)
212 		return (status);
213 
214 	if (exi.exi_visible) {
215 		mdb_printf("PseudoFS Nodes:\n");
216 		mdb_inc_indent(4);
217 
218 		if (mdb_pwalk_dcmd("nfs_expvis", "nfs_expvis", 0, NULL,
219 		    (uintptr_t)exi.exi_visible) == -1) {
220 			mdb_warn("walk through exi_visible failed");
221 			return (DCMD_ERR);
222 		}
223 
224 		mdb_dec_indent(4);
225 	}
226 
227 	mdb_dec_indent(4);
228 
229 	return (DCMD_OK);
230 }
231 
232 /*
233  * nfs_exptable dcmd implementation
234  */
235 
236 int
237 nfs_exptable_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
238 {
239 	uintptr_t	glbls;
240 	uintptr_t	zonep;
241 	nfs_globals_t	nfsglbls;
242 
243 	if (argc > 0)
244 		return (DCMD_USAGE);
245 
246 	if ((flags & DCMD_ADDRSPEC) != 0) {
247 		zonep = addr;
248 	} else {
249 		if (mdb_readsym(
250 		    &zonep, sizeof (uintptr_t), "global_zone") == -1) {
251 			mdb_warn("Failed to find global_zone");
252 			return (DCMD_ERR);
253 		}
254 	}
255 	mdb_printf("The zone is %p\n", zonep);
256 
257 	if (zoned_get_nfs_globals(zonep, &glbls) != DCMD_OK) {
258 		mdb_warn("failed to get zoned specific NFS globals");
259 		return (DCMD_ERR);
260 	}
261 
262 	mdb_printf("The nfs zone globals are %p\n", glbls);
263 
264 	if (mdb_vread(&nfsglbls, sizeof (nfs_globals_t), glbls) == -1) {
265 		mdb_warn("can't read zone globals");
266 		return (DCMD_ERR);
267 	}
268 	mdb_printf("The nfs globals are %p\n", nfsglbls);
269 	mdb_printf("The address of nfsglbls.nfs_export is %p\n",
270 	    nfsglbls.nfs_export);
271 	mdb_printf("The exptable address is %p\n",
272 	    nfsglbls.nfs_export->exptable);
273 
274 	if (mdb_pwalk_dcmd("nfs_expinfo", "nfs_expinfo", 0, NULL,
275 	    (uintptr_t)nfsglbls.nfs_export->exptable) == -1) {
276 		mdb_warn("exptable walk failed");
277 		return (DCMD_ERR);
278 	}
279 
280 	return (DCMD_OK);
281 }
282 
283 /*
284  * nfs_exptable_path dcmd implementation
285  */
286 
287 int
288 nfs_exptable_path_dcmd(uintptr_t addr, uint_t flags, int argc,
289     const mdb_arg_t *argv)
290 {
291 	uintptr_t	glbls;
292 	uintptr_t	zonep;
293 	nfs_globals_t	nfsglbls;
294 
295 	if (argc > 0)
296 		return (DCMD_USAGE);
297 
298 	if ((flags & DCMD_ADDRSPEC) != 0) {
299 		zonep = addr;
300 	} else {
301 		if (mdb_readsym(&zonep, sizeof (uintptr_t),
302 		    "global_zone") == -1) {
303 			mdb_warn("Failed to find global_zone");
304 			return (DCMD_ERR);
305 		}
306 	}
307 
308 	if (zoned_get_nfs_globals(zonep, &glbls) != DCMD_OK) {
309 		mdb_warn("failed to get zoned specific NFS globals");
310 		return (DCMD_ERR);
311 	}
312 
313 	if (mdb_vread(&nfsglbls, sizeof (nfs_globals_t), glbls) == -1) {
314 		mdb_warn("can't read zone globals");
315 		return (DCMD_ERR);
316 	}
317 
318 	if (mdb_pwalk_dcmd("nfs_expinfo_path", "nfs_expinfo", 0, NULL,
319 	    (uintptr_t)nfsglbls.nfs_export->exptable) == -1) {
320 		mdb_warn("exptable walk failed");
321 		return (DCMD_ERR);
322 	}
323 
324 	return (DCMD_OK);
325 }
326 
327 /*
328  * nfs_nstree dcmd implementation
329  */
330 
331 static int
332 print_tree(uintptr_t addr, uint_t opt_v, treenode_t *tn, char *s)
333 {
334 	while (addr != 0) {
335 		uintptr_t a;
336 
337 		if (mdb_vread(tn, sizeof (*tn), addr) == -1) {
338 			mdb_warn("can't read treenode");
339 			return (DCMD_ERR);
340 		}
341 
342 		/* a = tn->tree_exi->exi_vp */
343 		if (mdb_vread(&a, sizeof (a), (uintptr_t)tn->tree_exi
344 		    + OFFSETOF(struct exportinfo, exi_vp)) == -1) {
345 			mdb_warn("can't read exi_vp");
346 			return (DCMD_ERR);
347 		}
348 		/* a = ((vnode_t *)a)->v_path */
349 		if (mdb_vread(&a, sizeof (a),
350 		    a + OFFSETOF(vnode_t, v_path)) == -1) {
351 			mdb_warn("can't read v_path");
352 			return (DCMD_ERR);
353 		}
354 		if (mdb_readstr(s, PATH_MAX, a) == -1) {
355 			mdb_warn("can't read v_path string");
356 			return (DCMD_ERR);
357 		}
358 
359 		mdb_printf("\n\nTREENODE:\n%s\n", s);
360 
361 		mdb_inc_indent(2);
362 
363 		if (opt_v)
364 			mdb_printf("\nDump treenode:\n\n");
365 
366 		mdb_printf("addr:             %p\n", addr);
367 		mdb_printf("tree_parent:      %p\n", tn->tree_parent);
368 		mdb_printf("tree_child_first: %p\n", tn->tree_child_first);
369 		mdb_printf("tree_sibling:     %p\n", tn->tree_sibling);
370 		mdb_printf("tree_exi:         %p\n", tn->tree_exi);
371 		mdb_printf("tree_vis:         %p\n", tn->tree_vis);
372 
373 		if (opt_v) {
374 			mdb_printf("\nDump exportinfo:\n");
375 			if (mdb_call_dcmd("nfs_expinfo",
376 			    (uintptr_t)tn->tree_exi, DCMD_ADDRSPEC, 0, NULL)
377 			    == -1)
378 				return (DCMD_ERR);
379 
380 			if (tn->tree_vis) {
381 				mdb_printf("\nDump exp_visible:\n\n");
382 				if (mdb_call_dcmd("nfs_expvis",
383 				    (uintptr_t)tn->tree_vis, DCMD_ADDRSPEC, 0,
384 				    NULL) == -1)
385 					return (DCMD_ERR);
386 			}
387 		}
388 
389 		addr = (uintptr_t)tn->tree_sibling;
390 
391 		if (tn->tree_child_first != NULL) {
392 			int status;
393 
394 			mdb_inc_indent(2);
395 			status = print_tree((uintptr_t)tn->tree_child_first,
396 			    opt_v, tn, s);
397 			if (status != DCMD_OK)
398 				return (status);
399 			mdb_dec_indent(2);
400 		}
401 
402 		mdb_dec_indent(2);
403 	}
404 
405 	return (DCMD_OK);
406 }
407 
408 int
409 nfs_nstree_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
410 {
411 	uintptr_t	glbls;
412 	uintptr_t	zonep;
413 	nfs_globals_t	nfsglbls;
414 	nfs_export_t	exp;
415 
416 	uint_t opt_v = FALSE;
417 	treenode_t tn;
418 	char *s;
419 
420 	if (mdb_getopts(argc, argv,
421 	    'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc)
422 		return (DCMD_USAGE);
423 
424 	if ((flags & DCMD_ADDRSPEC) != 0) {
425 		zonep = addr;
426 	} else {
427 		if (mdb_readsym(&zonep, sizeof (uintptr_t),
428 		    "global_zone") == -1) {
429 			mdb_warn("Failed to find global_zone");
430 			return (DCMD_ERR);
431 		}
432 	}
433 
434 	if (zoned_get_nfs_globals(zonep, &glbls) != DCMD_OK) {
435 		mdb_warn("failed to get zoned specific NFS globals");
436 		return (DCMD_ERR);
437 	}
438 
439 	if (mdb_vread(&nfsglbls, sizeof (nfs_globals_t), glbls) == -1) {
440 		mdb_warn("can't read zone globals");
441 		return (DCMD_ERR);
442 	}
443 
444 	if (mdb_vread(&exp, sizeof (nfs_export_t),
445 	    (uintptr_t)nfsglbls.nfs_export) == -1) {
446 		mdb_warn("can't read nfs_export");
447 		return (DCMD_ERR);
448 	}
449 
450 	s = mdb_alloc(PATH_MAX, UM_SLEEP | UM_GC);
451 
452 	return (print_tree((uintptr_t)exp.ns_root, opt_v, &tn, s));
453 }
454 
455 void
456 nfs_nstree_help(void)
457 {
458 	mdb_printf(
459 	    "-v       dump also exportinfo and exp_visible structures\n");
460 }
461 
462 /*
463  * nfs_fid_hashdist dcmd implementation
464  */
465 
466 static int
467 calc_hashdist(struct exp_walk_arg *arg, uint_t opt_v, uintptr_t tbladdr)
468 {
469 	struct exportinfo **table;
470 	int i;
471 	u_longlong_t min = 0;
472 	u_longlong_t max = 0;
473 	u_longlong_t sum = 0;
474 	u_longlong_t sum_sqr = 0;
475 
476 	table = mdb_alloc(arg->size * sizeof (struct exportinfo *),
477 	    UM_SLEEP | UM_GC);
478 	if (mdb_vread(table, arg->size * sizeof (struct exportinfo *),
479 	    tbladdr) == -1) {
480 		mdb_warn("can't vreadsym exptable");
481 		return (DCMD_ERR);
482 	}
483 
484 
485 	for (i = 0; i < arg->size; i++) {
486 		u_longlong_t len;
487 		uintptr_t addr;
488 
489 		for (addr = (uintptr_t)table[i], len = 0; addr; len++)
490 			if (mdb_vread(&addr, sizeof (addr), addr + arg->offset)
491 			    == -1) {
492 				mdb_warn("unable to read pointer to next "
493 				    "exportinfo struct");
494 				return (DCMD_ERR);
495 			}
496 
497 		if (i == 0 || len < min)
498 			min = len;
499 		if (len > max)
500 			max = len;
501 		sum += len;
502 		sum_sqr += len * len;
503 
504 		if (opt_v)
505 			mdb_printf("%u\n", len);
506 	}
507 
508 	mdb_printf("TABLE: %s\n", arg->name);
509 	mdb_printf("items/size = %u/%u\n", sum, arg->size);
510 	mdb_printf("min/avg/max/variance = %u/%u/%u/%u\n", min, sum / arg->size,
511 	    max, (sum_sqr - (sum * sum) / arg->size) / arg->size);
512 
513 	return (DCMD_OK);
514 }
515 
516 int
517 nfs_fid_hashdist_dcmd(uintptr_t addr, uint_t flags, int argc,
518     const mdb_arg_t *argv)
519 {
520 	uint_t opt_v = FALSE;
521 
522 	if (mdb_getopts(argc, argv,
523 	    'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc)
524 		return (DCMD_USAGE);
525 
526 	if ((flags & DCMD_ADDRSPEC) == 0) {
527 		mdb_printf("requires address of export table\n");
528 		return (DCMD_USAGE);
529 	}
530 
531 	return (calc_hashdist(&nfs_expinfo_arg, opt_v, addr));
532 }
533 
534 void
535 nfs_hashdist_help(void)
536 {
537 	mdb_printf(
538 	    "-v       displays individual bucket lengths\n");
539 }
540 
541 /*
542  * nfs_path_hashdist dcmd implementation
543  */
544 
545 int
546 nfs_path_hashdist_dcmd(uintptr_t addr, uint_t flags, int argc,
547     const mdb_arg_t *argv)
548 {
549 	uint_t opt_v = FALSE;
550 
551 	if (mdb_getopts(argc, argv,
552 	    'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc)
553 		return (DCMD_USAGE);
554 
555 	if ((flags & DCMD_ADDRSPEC) == 0) {
556 		mdb_printf("requires address of export table\n");
557 		return (DCMD_USAGE);
558 	}
559 
560 	return (calc_hashdist(&nfs_expinfo_path_arg, opt_v, addr));
561 }
562 
563 /*
564  * nfs_expinfo/nfs_expinfo_path walkers implementation
565  */
566 
567 struct exp_walk_arg nfs_expinfo_arg = {
568 	"exptable", EXPTABLESIZE,
569 	OFFSETOF(struct exportinfo, fid_hash) + OFFSETOF(struct exp_hash, next)
570 };
571 
572 struct exp_walk_arg nfs_expinfo_path_arg = {
573 	"exptable_path_hash", PKP_HASH_SIZE,
574 	OFFSETOF(struct exportinfo, path_hash) + OFFSETOF(struct exp_hash, next)
575 };
576 
577 int
578 nfs_expinfo_walk_init(mdb_walk_state_t *wsp)
579 {
580 	struct exp_walk_arg *exp_arg = wsp->walk_arg;
581 	hash_table_walk_arg_t *arg;
582 	int status;
583 
584 	if (wsp->walk_addr == 0) {
585 		mdb_warn("global walk not supported");
586 		return (WALK_ERR);
587 	}
588 
589 	arg = mdb_alloc(sizeof (hash_table_walk_arg_t), UM_SLEEP);
590 	arg->array_addr = wsp->walk_addr;
591 	arg->array_len = exp_arg->size;
592 	arg->head_size = sizeof (struct exportinfo *);
593 	arg->first_name = "exportinfo pointer";
594 	arg->first_offset = 0;
595 	arg->member_type_name = "struct exportinfo";
596 	arg->member_size = sizeof (struct exportinfo);
597 	arg->next_offset = exp_arg->offset;
598 
599 	wsp->walk_arg = arg;
600 
601 	status = hash_table_walk_init(wsp);
602 	if (status != WALK_NEXT)
603 		mdb_free(wsp->walk_arg, sizeof (hash_table_walk_arg_t));
604 	return (status);
605 }
606 
607 void
608 nfs_expinfo_walk_fini(mdb_walk_state_t *wsp)
609 {
610 	hash_table_walk_fini(wsp);
611 	mdb_free(wsp->walk_arg, sizeof (hash_table_walk_arg_t));
612 }
613 
614 /*
615  * nfs_expvis walker implementation
616  */
617 
618 int
619 nfs_expvis_walk_init(mdb_walk_state_t *wsp)
620 {
621 	if (wsp->walk_addr == 0) {
622 		mdb_warn("global walk not supported");
623 		return (WALK_ERR);
624 	}
625 
626 	return (WALK_NEXT);
627 }
628 
629 int
630 nfs_expvis_walk_step(mdb_walk_state_t *wsp)
631 {
632 	exp_visible_t vis;
633 	uintptr_t addr = wsp->walk_addr;
634 
635 	if (addr == 0)
636 		return (WALK_DONE);
637 
638 	if (mdb_vread(&vis, sizeof (vis), addr) == -1) {
639 		mdb_warn("failed to read exp_visible_t at %p", addr);
640 		return (WALK_ERR);
641 	}
642 
643 	wsp->walk_addr = (uintptr_t)vis.vis_next;
644 	return (wsp->walk_callback(addr, &vis, wsp->walk_cbdata));
645 }
646 
647 /*
648  * nfssrv_globals walker, gets the nfs globals for each zone
649  *
650  * Note: Most of the NFS dcmds take a zone pointer, at some point we may
651  * want to change that to take the nfs globals address and aviod the zone
652  * key lookup. This walker could be helpful in that change.
653  */
654 int
655 nfssrv_globals_walk_init(mdb_walk_state_t *wsp)
656 {
657 	GElf_Sym sym;
658 
659 	if (wsp->walk_addr == 0) {
660 		if (mdb_lookup_by_name("nfssrv_globals_list", &sym) == -1) {
661 			mdb_warn("failed to find 'nfssrv_globals_list'");
662 			return (WALK_ERR);
663 		}
664 		wsp->walk_addr = (uintptr_t)sym.st_value;
665 	} else {
666 		mdb_printf("nfssrv_globals walk only supports global walks\n");
667 		return (WALK_ERR);
668 	}
669 
670 	if (mdb_layered_walk("list", wsp) == -1) {
671 		mdb_warn("couldn't walk 'list'");
672 		return (WALK_ERR);
673 	}
674 
675 	wsp->walk_data = (void *)wsp->walk_addr;
676 	return (WALK_NEXT);
677 }
678 
679 int
680 nfssrv_globals_walk_step(mdb_walk_state_t *wsp)
681 {
682 	return (wsp->walk_callback(wsp->walk_addr, wsp->walk_layer,
683 	    wsp->walk_cbdata));
684 }
685