Print this page
6638 ::pfiles walks out of bounds on array of vnode types
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/cmd/mdb/common/modules/genunix/vfs.c
+++ new/usr/src/cmd/mdb/common/modules/genunix/vfs.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21 /*
22 22 * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
23 23 */
24 24
25 25 #include <mdb/mdb_modapi.h>
26 26 #include <mdb/mdb_ks.h>
27 27
28 28 #include <sys/types.h>
29 29 #include <sys/systm.h>
30 30 #include <sys/door.h>
31 31 #include <sys/file.h>
32 32 #include <sys/mount.h>
33 33 #include <sys/proc.h>
34 34 #include <sys/procfs.h>
35 35 #include <sys/proc/prdata.h>
36 36 #include <sys/stat.h>
37 37 #include <sys/vfs.h>
38 38 #include <sys/vnode.h>
39 39 #include <sys/fs/snode.h>
40 40 #include <sys/fs/fifonode.h>
41 41 #include <sys/fs/namenode.h>
42 42 #include <sys/socket.h>
43 43 #include <sys/stropts.h>
44 44 #include <sys/socketvar.h>
45 45 #include <sys/strsubr.h>
46 46 #include <sys/un.h>
47 47 #include <fs/sockfs/socktpi_impl.h>
48 48 #include <inet/ipclassifier.h>
49 49 #include <inet/ip_if.h>
50 50 #include <inet/sctp/sctp_impl.h>
51 51 #include <inet/sctp/sctp_addr.h>
52 52
53 53 int
54 54 vfs_walk_init(mdb_walk_state_t *wsp)
55 55 {
56 56 if (wsp->walk_addr == NULL &&
57 57 mdb_readvar(&wsp->walk_addr, "rootvfs") == -1) {
58 58 mdb_warn("failed to read 'rootvfs'");
59 59 return (WALK_ERR);
60 60 }
61 61
62 62 wsp->walk_data = (void *)wsp->walk_addr;
63 63 return (WALK_NEXT);
64 64 }
65 65
66 66 int
67 67 vfs_walk_step(mdb_walk_state_t *wsp)
68 68 {
69 69 vfs_t vfs;
70 70 int status;
71 71
72 72 if (mdb_vread(&vfs, sizeof (vfs), wsp->walk_addr) == -1) {
73 73 mdb_warn("failed to read vfs_t at %p", wsp->walk_addr);
74 74 return (WALK_DONE);
75 75 }
76 76
77 77 status = wsp->walk_callback(wsp->walk_addr, &vfs, wsp->walk_cbdata);
78 78
79 79 if (vfs.vfs_next == wsp->walk_data)
80 80 return (WALK_DONE);
81 81
82 82 wsp->walk_addr = (uintptr_t)vfs.vfs_next;
83 83
84 84 return (status);
85 85 }
86 86
87 87 /*
88 88 * Utility routine to read in a filesystem name given a vfs pointer. If
89 89 * no vfssw entry for the vfs is available (as is the case with some pseudo-
90 90 * filesystems), we check against some known problem fs's: doorfs and
91 91 * portfs. If that fails, we try to guess the filesystem name using
92 92 * symbol names. fsname should be a buffer of size _ST_FSTYPSZ.
93 93 */
94 94 static int
95 95 read_fsname(uintptr_t vfsp, char *fsname)
96 96 {
97 97 vfs_t vfs;
98 98 struct vfssw vfssw_entry;
99 99 GElf_Sym vfssw_sym, test_sym;
100 100 char testname[MDB_SYM_NAMLEN];
101 101
102 102 if (mdb_vread(&vfs, sizeof (vfs), vfsp) == -1) {
103 103 mdb_warn("failed to read vfs %p", vfsp);
104 104 return (-1);
105 105 }
106 106
107 107 if (mdb_lookup_by_name("vfssw", &vfssw_sym) == -1) {
108 108 mdb_warn("failed to find vfssw");
109 109 return (-1);
110 110 }
111 111
112 112 /*
113 113 * vfssw is an array; we need vfssw[vfs.vfs_fstype].
114 114 */
115 115 if (mdb_vread(&vfssw_entry, sizeof (vfssw_entry),
116 116 vfssw_sym.st_value + (sizeof (struct vfssw) * vfs.vfs_fstype))
117 117 == -1) {
118 118 mdb_warn("failed to read vfssw index %d", vfs.vfs_fstype);
119 119 return (-1);
120 120 }
121 121
122 122 if (vfs.vfs_fstype != 0) {
123 123 if (mdb_readstr(fsname, _ST_FSTYPSZ,
124 124 (uintptr_t)vfssw_entry.vsw_name) == -1) {
125 125 mdb_warn("failed to find fs name %p",
126 126 vfssw_entry.vsw_name);
127 127 return (-1);
128 128 }
129 129 return (0);
130 130 }
131 131
132 132 /*
133 133 * Do precise detection for certain filesystem types that we
134 134 * know do not appear in vfssw[], and that we depend upon in other
135 135 * parts of the code: doorfs and portfs.
136 136 */
137 137 if (mdb_lookup_by_name("door_vfs", &test_sym) != -1) {
138 138 if (test_sym.st_value == vfsp) {
139 139 strcpy(fsname, "doorfs");
140 140 return (0);
141 141 }
142 142 }
143 143 if (mdb_lookup_by_name("port_vfs", &test_sym) != -1) {
144 144 if (test_sym.st_value == vfsp) {
145 145 strcpy(fsname, "portfs");
146 146 return (0);
147 147 }
148 148 }
149 149
150 150 /*
151 151 * Heuristic detection for other filesystems that don't have a
152 152 * vfssw[] entry. These tend to be named <fsname>_vfs, so we do a
153 153 * lookup_by_addr and see if we find a symbol of that name.
154 154 */
155 155 if (mdb_lookup_by_addr(vfsp, MDB_SYM_EXACT, testname, sizeof (testname),
156 156 &test_sym) != -1) {
157 157 if ((strlen(testname) > 4) &&
158 158 (strcmp(testname + strlen(testname) - 4, "_vfs") == 0)) {
159 159 testname[strlen(testname) - 4] = '\0';
160 160 strncpy(fsname, testname, _ST_FSTYPSZ);
161 161 return (0);
162 162 }
163 163 }
164 164
165 165 mdb_warn("unknown filesystem type for vfs %p", vfsp);
166 166 return (-1);
167 167 }
168 168
169 169 /*
170 170 * Column widths for mount point display in ::fsinfo output.
171 171 */
172 172 #ifdef _LP64
173 173 #define FSINFO_MNTLEN 48
174 174 #else
175 175 #define FSINFO_MNTLEN 56
176 176 #endif
177 177
178 178 /* ARGSUSED */
179 179 int
180 180 fsinfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
181 181 {
182 182 vfs_t vfs;
183 183 int len;
184 184 int opt_v = 0;
185 185 char buf[MAXPATHLEN];
186 186 char fsname[_ST_FSTYPSZ];
187 187 mntopt_t *mntopts;
188 188 size_t size;
189 189 int i;
190 190 int first = 1;
191 191 char opt[MAX_MNTOPT_STR];
192 192 uintptr_t global_zone;
193 193
194 194 if (!(flags & DCMD_ADDRSPEC)) {
195 195 if (mdb_walk_dcmd("vfs", "fsinfo", argc, argv) == -1) {
196 196 mdb_warn("failed to walk file system list");
197 197 return (DCMD_ERR);
198 198 }
199 199 return (DCMD_OK);
200 200 }
201 201
202 202 if (mdb_getopts(argc, argv,
203 203 'v', MDB_OPT_SETBITS, TRUE, &opt_v, NULL) != argc)
204 204 return (DCMD_USAGE);
205 205
206 206 if (DCMD_HDRSPEC(flags))
207 207 mdb_printf("%<u>%?s %-15s %s%</u>\n",
208 208 "VFSP", "FS", "MOUNT");
209 209
210 210 if (mdb_vread(&vfs, sizeof (vfs), addr) == -1) {
211 211 mdb_warn("failed to read vfs_t %p", addr);
212 212 return (DCMD_ERR);
213 213 }
214 214
215 215 if ((len = mdb_read_refstr((uintptr_t)vfs.vfs_mntpt, buf,
216 216 sizeof (buf))) <= 0)
217 217 strcpy(buf, "??");
218 218
219 219 else if (!opt_v && (len >= FSINFO_MNTLEN))
220 220 /*
221 221 * In normal mode, we truncate the path to keep the output
222 222 * clean. In -v mode, we just print the full path.
223 223 */
224 224 strcpy(&buf[FSINFO_MNTLEN - 4], "...");
225 225
226 226 if (read_fsname(addr, fsname) == -1)
227 227 return (DCMD_ERR);
228 228
229 229 mdb_printf("%0?p %-15s %s\n", addr, fsname, buf);
230 230
231 231 if (!opt_v)
232 232 return (DCMD_OK);
233 233
234 234 /*
235 235 * Print 'resource' string; this shows what we're mounted upon.
236 236 */
237 237 if (mdb_read_refstr((uintptr_t)vfs.vfs_resource, buf,
238 238 MAXPATHLEN) <= 0)
239 239 strcpy(buf, "??");
240 240
241 241 mdb_printf("%?s %s\n", "R:", buf);
242 242
243 243 /*
244 244 * Print mount options array; it sucks to be a mimic, but we copy
245 245 * the same logic as in mntvnops.c for adding zone= tags, and we
246 246 * don't bother with the obsolete dev= option.
247 247 */
248 248 size = vfs.vfs_mntopts.mo_count * sizeof (mntopt_t);
249 249 mntopts = mdb_alloc(size, UM_SLEEP | UM_GC);
250 250
251 251 if (mdb_vread(mntopts, size,
252 252 (uintptr_t)vfs.vfs_mntopts.mo_list) == -1) {
253 253 mdb_warn("failed to read mntopts %p", vfs.vfs_mntopts.mo_list);
254 254 return (DCMD_ERR);
255 255 }
256 256
257 257 for (i = 0; i < vfs.vfs_mntopts.mo_count; i++) {
258 258 if (mntopts[i].mo_flags & MO_SET) {
259 259 if (mdb_readstr(opt, sizeof (opt),
260 260 (uintptr_t)mntopts[i].mo_name) == -1) {
261 261 mdb_warn("failed to read mntopt name %p",
262 262 mntopts[i].mo_name);
263 263 return (DCMD_ERR);
264 264 }
265 265 if (first) {
266 266 mdb_printf("%?s ", "O:");
267 267 first = 0;
268 268 } else {
269 269 mdb_printf(",");
270 270 }
271 271 mdb_printf("%s", opt);
272 272 if (mntopts[i].mo_flags & MO_HASVALUE) {
273 273 if (mdb_readstr(opt, sizeof (opt),
274 274 (uintptr_t)mntopts[i].mo_arg) == -1) {
275 275 mdb_warn("failed to read mntopt "
276 276 "value %p", mntopts[i].mo_arg);
277 277 return (DCMD_ERR);
278 278 }
279 279 mdb_printf("=%s", opt);
280 280 }
281 281 }
282 282 }
283 283
284 284 if (mdb_readvar(&global_zone, "global_zone") == -1) {
285 285 mdb_warn("failed to locate global_zone");
286 286 return (DCMD_ERR);
287 287 }
288 288
289 289 if ((vfs.vfs_zone != NULL) &&
290 290 ((uintptr_t)vfs.vfs_zone != global_zone)) {
291 291 zone_t z;
292 292
293 293 if (mdb_vread(&z, sizeof (z), (uintptr_t)vfs.vfs_zone) == -1) {
294 294 mdb_warn("failed to read zone");
295 295 return (DCMD_ERR);
296 296 }
297 297 /*
298 298 * zone names are much shorter than MAX_MNTOPT_STR
299 299 */
300 300 if (mdb_readstr(opt, sizeof (opt),
301 301 (uintptr_t)z.zone_name) == -1) {
302 302 mdb_warn("failed to read zone name");
303 303 return (DCMD_ERR);
304 304 }
305 305 if (first) {
306 306 mdb_printf("%?s ", "O:");
307 307 } else {
308 308 mdb_printf(",");
309 309 }
310 310 mdb_printf("zone=%s", opt);
311 311 }
312 312 return (DCMD_OK);
313 313 }
314 314
315 315
316 316 #define REALVP_DONE 0
317 317 #define REALVP_ERR 1
318 318 #define REALVP_CONTINUE 2
319 319
320 320 static int
321 321 next_realvp(uintptr_t invp, struct vnode *outvn, uintptr_t *outvp)
322 322 {
323 323 char fsname[_ST_FSTYPSZ];
324 324
325 325 *outvp = invp;
326 326 if (mdb_vread(outvn, sizeof (struct vnode), invp) == -1) {
327 327 mdb_warn("failed to read vnode at %p", invp);
328 328 return (REALVP_ERR);
329 329 }
330 330
331 331 if (read_fsname((uintptr_t)outvn->v_vfsp, fsname) == -1)
332 332 return (REALVP_ERR);
333 333
334 334 /*
335 335 * We know how to do 'realvp' for as many filesystems as possible;
336 336 * for all other filesystems, we assume that the vp we are given
337 337 * is the realvp. In the kernel, a realvp operation will sometimes
338 338 * dig through multiple layers. Here, we only fetch the pointer
339 339 * to the next layer down. This allows dcmds to print out the
340 340 * various layers.
341 341 */
342 342 if (strcmp(fsname, "fifofs") == 0) {
343 343 fifonode_t fn;
344 344 if (mdb_vread(&fn, sizeof (fn),
345 345 (uintptr_t)outvn->v_data) == -1) {
346 346 mdb_warn("failed to read fifonode");
347 347 return (REALVP_ERR);
348 348 }
349 349 *outvp = (uintptr_t)fn.fn_realvp;
350 350
351 351 } else if (strcmp(fsname, "namefs") == 0) {
352 352 struct namenode nn;
353 353 if (mdb_vread(&nn, sizeof (nn),
354 354 (uintptr_t)outvn->v_data) == -1) {
355 355 mdb_warn("failed to read namenode");
356 356 return (REALVP_ERR);
357 357 }
358 358 *outvp = (uintptr_t)nn.nm_filevp;
359 359
360 360 } else if (outvn->v_type == VSOCK && outvn->v_stream != NULL) {
361 361 struct stdata stream;
362 362
363 363 /*
364 364 * Sockets have a strange and different layering scheme; we
365 365 * hop over into the sockfs vnode (accessible via the stream
366 366 * head) if possible.
367 367 */
368 368 if (mdb_vread(&stream, sizeof (stream),
369 369 (uintptr_t)outvn->v_stream) == -1) {
370 370 mdb_warn("failed to read stream data");
371 371 return (REALVP_ERR);
372 372 }
373 373 *outvp = (uintptr_t)stream.sd_vnode;
374 374 }
375 375
376 376 if (*outvp == invp || *outvp == NULL)
377 377 return (REALVP_DONE);
378 378
379 379 return (REALVP_CONTINUE);
380 380 }
381 381
382 382 static void
383 383 pfiles_print_addr(struct sockaddr *addr)
384 384 {
385 385 struct sockaddr_in *s_in;
386 386 struct sockaddr_un *s_un;
387 387 struct sockaddr_in6 *s_in6;
388 388 in_port_t port;
389 389
390 390 switch (addr->sa_family) {
391 391 case AF_INET:
392 392 /* LINTED: alignment */
393 393 s_in = (struct sockaddr_in *)addr;
394 394 mdb_nhconvert(&port, &s_in->sin_port, sizeof (port));
395 395 mdb_printf("AF_INET %I %d ", s_in->sin_addr.s_addr, port);
396 396 break;
397 397
398 398 case AF_INET6:
399 399 /* LINTED: alignment */
400 400 s_in6 = (struct sockaddr_in6 *)addr;
401 401 mdb_nhconvert(&port, &s_in6->sin6_port, sizeof (port));
402 402 mdb_printf("AF_INET6 %N %d ", &(s_in6->sin6_addr), port);
403 403 break;
404 404
405 405 case AF_UNIX:
406 406 s_un = (struct sockaddr_un *)addr;
407 407 mdb_printf("AF_UNIX %s ", s_un->sun_path);
408 408 break;
409 409 default:
410 410 mdb_printf("AF_?? (%d) ", addr->sa_family);
411 411 break;
412 412 }
413 413 }
414 414
415 415 static int
416 416 pfiles_get_sonode(vnode_t *v_sock, struct sonode *sonode)
417 417 {
418 418 if (mdb_vread(sonode, sizeof (struct sonode),
419 419 (uintptr_t)v_sock->v_data) == -1) {
420 420 mdb_warn("failed to read sonode");
421 421 return (-1);
422 422 }
423 423
424 424 return (0);
425 425 }
426 426
427 427 static int
428 428 pfiles_get_tpi_sonode(vnode_t *v_sock, sotpi_sonode_t *sotpi_sonode)
429 429 {
430 430
431 431 struct stdata stream;
432 432
433 433 if (mdb_vread(&stream, sizeof (stream),
434 434 (uintptr_t)v_sock->v_stream) == -1) {
435 435 mdb_warn("failed to read stream data");
436 436 return (-1);
437 437 }
438 438
439 439 if (mdb_vread(v_sock, sizeof (vnode_t),
440 440 (uintptr_t)stream.sd_vnode) == -1) {
441 441 mdb_warn("failed to read stream vnode");
442 442 return (-1);
443 443 }
444 444
445 445 if (mdb_vread(sotpi_sonode, sizeof (sotpi_sonode_t),
446 446 (uintptr_t)v_sock->v_data) == -1) {
447 447 mdb_warn("failed to read sotpi_sonode");
448 448 return (-1);
449 449 }
450 450
451 451 return (0);
452 452 }
453 453
454 454 /*
455 455 * Do some digging to get a reasonable pathname for this vnode. 'path'
456 456 * should point at a buffer of MAXPATHLEN in size.
457 457 */
458 458 static int
459 459 pfiles_dig_pathname(uintptr_t vp, char *path)
460 460 {
461 461 vnode_t v;
462 462
463 463 bzero(path, MAXPATHLEN);
464 464
465 465 if (mdb_vread(&v, sizeof (v), vp) == -1) {
466 466 mdb_warn("failed to read vnode");
467 467 return (-1);
468 468 }
469 469
470 470 if (v.v_path == NULL) {
471 471 /*
472 472 * fifo's and doors are special. Some have pathnames, and
473 473 * some do not. And for these, it is pointless to go off to
474 474 * mdb_vnode2path, which is very slow.
475 475 *
476 476 * Event ports never have a pathname.
477 477 */
478 478 if (v.v_type == VFIFO || v.v_type == VDOOR || v.v_type == VPORT)
479 479 return (0);
480 480
481 481 /*
482 482 * For sockets, we won't find a path unless we print the path
483 483 * associated with transport's STREAM device.
484 484 */
485 485 if (v.v_type == VSOCK) {
486 486 struct sonode sonode;
487 487 struct sockparams sockparams;
488 488
489 489 if (pfiles_get_sonode(&v, &sonode) == -1) {
490 490 return (-1);
491 491 }
492 492 if (mdb_vread(&sockparams, sizeof (sockparams),
493 493 (uintptr_t)sonode.so_sockparams) == -1) {
494 494 mdb_warn("failed to read sockparams");
495 495 return (-1);
496 496 }
497 497
498 498 if (!SOCK_IS_NONSTR(&sonode)) {
499 499 vp = (uintptr_t)
500 500 sockparams.sp_sdev_info.sd_vnode;
501 501 } else {
502 502 vp = NULL;
503 503 }
504 504 }
505 505 }
506 506
507 507
508 508 /*
509 509 * mdb_vnode2path will print an error for us as needed, but not
510 510 * finding a pathname is not really an error, so we plow on.
511 511 */
512 512 (void) mdb_vnode2path(vp, path, MAXPATHLEN);
513 513
514 514 /*
515 515 * A common problem is that device pathnames are prefixed with
516 516 * /dev/../devices/. We just clean those up slightly:
517 517 * /dev/../devices/<mumble> --> /devices/<mumble>
518 518 * /dev/pts/../../devices/<mumble> --> /devices/<mumble>
519 519 */
520 520 if (strncmp("/dev/../devices/", path, strlen("/dev/../devices/")) == 0)
↓ open down ↓ |
520 lines elided |
↑ open up ↑ |
521 521 strcpy(path, path + 7);
522 522
523 523 if (strncmp("/dev/pts/../../devices/", path,
524 524 strlen("/dev/pts/../../devices/")) == 0)
525 525 strcpy(path, path + 14);
526 526
527 527 return (0);
528 528 }
529 529
530 530 const struct fs_type {
531 - int type;
531 + vtype_t type;
532 532 const char *name;
533 533 } fs_types[] = {
534 534 { VNON, "NON" },
535 535 { VREG, "REG" },
536 536 { VDIR, "DIR" },
537 537 { VBLK, "BLK" },
538 538 { VCHR, "CHR" },
539 539 { VLNK, "LNK" },
540 540 { VFIFO, "FIFO" },
541 541 { VDOOR, "DOOR" },
542 542 { VPROC, "PROC" },
543 543 { VSOCK, "SOCK" },
544 544 { VPORT, "PORT" },
545 545 { VBAD, "BAD" }
546 546 };
547 547
548 548 #define NUM_FS_TYPES (sizeof (fs_types) / sizeof (struct fs_type))
549 549
550 550 struct pfiles_cbdata {
551 551 int opt_p;
552 552 int fd;
553 553 };
554 554
555 555 #define list_d2l(a, obj) ((list_node_t *)(((char *)obj) + (a)->list_offset))
556 556 #define list_object(a, node) ((void *)(((char *)node) - (a)->list_offset))
557 557
558 558 /*
559 559 * SCTP interface for geting the first source address of a sctp_t.
560 560 */
561 561 int
562 562 sctp_getsockaddr(sctp_t *sctp, struct sockaddr *addr)
563 563 {
564 564 int err = -1;
565 565 int i;
566 566 int l;
567 567 sctp_saddr_ipif_t *pobj;
568 568 sctp_saddr_ipif_t obj;
569 569 size_t added = 0;
570 570 sin6_t *sin6;
571 571 sin_t *sin4;
572 572 int scanned = 0;
573 573 boolean_t skip_lback = B_FALSE;
574 574 conn_t *connp = sctp->sctp_connp;
575 575
576 576 addr->sa_family = connp->conn_family;
577 577 if (sctp->sctp_nsaddrs == 0)
578 578 goto done;
579 579
580 580 /*
581 581 * Skip loopback addresses for non-loopback assoc.
582 582 */
583 583 if (sctp->sctp_state >= SCTPS_ESTABLISHED && !sctp->sctp_loopback) {
584 584 skip_lback = B_TRUE;
585 585 }
586 586
587 587 for (i = 0; i < SCTP_IPIF_HASH; i++) {
588 588 if (sctp->sctp_saddrs[i].ipif_count == 0)
589 589 continue;
590 590
591 591 pobj = list_object(&sctp->sctp_saddrs[i].sctp_ipif_list,
592 592 sctp->sctp_saddrs[i].sctp_ipif_list.list_head.list_next);
593 593 if (mdb_vread(&obj, sizeof (sctp_saddr_ipif_t),
594 594 (uintptr_t)pobj) == -1) {
595 595 mdb_warn("failed to read sctp_saddr_ipif_t");
596 596 return (err);
597 597 }
598 598
599 599 for (l = 0; l < sctp->sctp_saddrs[i].ipif_count; l++) {
600 600 sctp_ipif_t ipif;
601 601 in6_addr_t laddr;
602 602 list_node_t *pnode;
603 603 list_node_t node;
604 604
605 605 if (mdb_vread(&ipif, sizeof (sctp_ipif_t),
606 606 (uintptr_t)obj.saddr_ipifp) == -1) {
607 607 mdb_warn("failed to read sctp_ipif_t");
608 608 return (err);
609 609 }
610 610 laddr = ipif.sctp_ipif_saddr;
611 611
612 612 scanned++;
613 613 if ((ipif.sctp_ipif_state == SCTP_IPIFS_CONDEMNED) ||
614 614 SCTP_DONT_SRC(&obj) ||
615 615 (ipif.sctp_ipif_ill->sctp_ill_flags &
616 616 PHYI_LOOPBACK) && skip_lback) {
617 617 if (scanned >= sctp->sctp_nsaddrs)
618 618 goto done;
619 619
620 620 /* LINTED: alignment */
621 621 pnode = list_d2l(&sctp->sctp_saddrs[i].
622 622 sctp_ipif_list, pobj);
623 623 if (mdb_vread(&node, sizeof (list_node_t),
624 624 (uintptr_t)pnode) == -1) {
625 625 mdb_warn("failed to read list_node_t");
626 626 return (err);
627 627 }
628 628 pobj = list_object(&sctp->sctp_saddrs[i].
629 629 sctp_ipif_list, node.list_next);
630 630 if (mdb_vread(&obj, sizeof (sctp_saddr_ipif_t),
631 631 (uintptr_t)pobj) == -1) {
632 632 mdb_warn("failed to read "
633 633 "sctp_saddr_ipif_t");
634 634 return (err);
635 635 }
636 636 continue;
637 637 }
638 638
639 639 switch (connp->conn_family) {
640 640 case AF_INET:
641 641 /* LINTED: alignment */
642 642 sin4 = (sin_t *)addr;
643 643 if ((sctp->sctp_state <= SCTPS_LISTEN) &&
644 644 sctp->sctp_bound_to_all) {
645 645 sin4->sin_addr.s_addr = INADDR_ANY;
646 646 sin4->sin_port = connp->conn_lport;
647 647 } else {
648 648 sin4 += added;
649 649 sin4->sin_family = AF_INET;
650 650 sin4->sin_port = connp->conn_lport;
651 651 IN6_V4MAPPED_TO_INADDR(&laddr,
652 652 &sin4->sin_addr);
653 653 }
654 654 break;
655 655
656 656 case AF_INET6:
657 657 /* LINTED: alignment */
658 658 sin6 = (sin6_t *)addr;
659 659 if ((sctp->sctp_state <= SCTPS_LISTEN) &&
660 660 sctp->sctp_bound_to_all) {
661 661 bzero(&sin6->sin6_addr,
662 662 sizeof (sin6->sin6_addr));
663 663 sin6->sin6_port = connp->conn_lport;
664 664 } else {
665 665 sin6 += added;
666 666 sin6->sin6_family = AF_INET6;
667 667 sin6->sin6_port = connp->conn_lport;
668 668 sin6->sin6_addr = laddr;
669 669 }
670 670 sin6->sin6_flowinfo = connp->conn_flowinfo;
671 671 sin6->sin6_scope_id = 0;
672 672 sin6->__sin6_src_id = 0;
673 673 break;
674 674 }
675 675 added++;
676 676 if (added >= 1) {
677 677 err = 0;
678 678 goto done;
679 679 }
680 680 if (scanned >= sctp->sctp_nsaddrs)
681 681 goto done;
682 682
683 683 /* LINTED: alignment */
684 684 pnode = list_d2l(&sctp->sctp_saddrs[i].sctp_ipif_list,
685 685 pobj);
686 686 if (mdb_vread(&node, sizeof (list_node_t),
687 687 (uintptr_t)pnode) == -1) {
688 688 mdb_warn("failed to read list_node_t");
689 689 return (err);
690 690 }
691 691 pobj = list_object(&sctp->sctp_saddrs[i].
692 692 sctp_ipif_list, node.list_next);
693 693 if (mdb_vread(&obj, sizeof (sctp_saddr_ipif_t),
694 694 (uintptr_t)pobj) == -1) {
695 695 mdb_warn("failed to read sctp_saddr_ipif_t");
696 696 return (err);
697 697 }
698 698 }
699 699 }
700 700 done:
701 701 return (err);
702 702 }
703 703
704 704 /*
705 705 * SCTP interface for geting the primary peer address of a sctp_t.
706 706 */
707 707 static int
708 708 sctp_getpeeraddr(sctp_t *sctp, struct sockaddr *addr)
709 709 {
710 710 struct sockaddr_in *sin4;
711 711 struct sockaddr_in6 *sin6;
712 712 sctp_faddr_t sctp_primary;
713 713 in6_addr_t faddr;
714 714 conn_t *connp = sctp->sctp_connp;
715 715
716 716 if (sctp->sctp_faddrs == NULL)
717 717 return (-1);
718 718
719 719 addr->sa_family = connp->conn_family;
720 720 if (mdb_vread(&sctp_primary, sizeof (sctp_faddr_t),
721 721 (uintptr_t)sctp->sctp_primary) == -1) {
722 722 mdb_warn("failed to read sctp primary faddr");
723 723 return (-1);
724 724 }
725 725 faddr = sctp_primary.sf_faddr;
726 726
727 727 switch (connp->conn_family) {
728 728 case AF_INET:
729 729 /* LINTED: alignment */
730 730 sin4 = (struct sockaddr_in *)addr;
731 731 IN6_V4MAPPED_TO_INADDR(&faddr, &sin4->sin_addr);
732 732 sin4->sin_port = connp->conn_fport;
733 733 sin4->sin_family = AF_INET;
734 734 break;
735 735
736 736 case AF_INET6:
737 737 /* LINTED: alignment */
738 738 sin6 = (struct sockaddr_in6 *)addr;
739 739 sin6->sin6_addr = faddr;
740 740 sin6->sin6_port = connp->conn_fport;
741 741 sin6->sin6_family = AF_INET6;
742 742 sin6->sin6_flowinfo = 0;
743 743 sin6->sin6_scope_id = 0;
744 744 sin6->__sin6_src_id = 0;
745 745 break;
746 746 }
747 747
748 748 return (0);
749 749 }
750 750
751 751 static int
752 752 tpi_sock_print(sotpi_sonode_t *sotpi_sonode)
753 753 {
754 754 if (sotpi_sonode->st_info.sti_laddr_valid == 1) {
755 755 struct sockaddr *laddr =
756 756 mdb_alloc(sotpi_sonode->st_info.sti_laddr_len, UM_SLEEP);
757 757 if (mdb_vread(laddr, sotpi_sonode->st_info.sti_laddr_len,
758 758 (uintptr_t)sotpi_sonode->st_info.sti_laddr_sa) == -1) {
759 759 mdb_warn("failed to read sotpi_sonode socket addr");
760 760 return (-1);
761 761 }
762 762
763 763 mdb_printf("socket: ");
764 764 pfiles_print_addr(laddr);
765 765 }
766 766
767 767 if (sotpi_sonode->st_info.sti_faddr_valid == 1) {
768 768 struct sockaddr *faddr =
769 769 mdb_alloc(sotpi_sonode->st_info.sti_faddr_len, UM_SLEEP);
770 770 if (mdb_vread(faddr, sotpi_sonode->st_info.sti_faddr_len,
771 771 (uintptr_t)sotpi_sonode->st_info.sti_faddr_sa) == -1) {
772 772 mdb_warn("failed to read sotpi_sonode remote addr");
773 773 return (-1);
774 774 }
775 775
776 776 mdb_printf("remote: ");
777 777 pfiles_print_addr(faddr);
778 778 }
779 779
780 780 return (0);
781 781 }
782 782
783 783 static int
784 784 tcpip_sock_print(struct sonode *socknode)
785 785 {
786 786 switch (socknode->so_family) {
787 787 case AF_INET:
788 788 {
789 789 conn_t conn_t;
790 790 in_port_t port;
791 791
792 792 if (mdb_vread(&conn_t, sizeof (conn_t),
793 793 (uintptr_t)socknode->so_proto_handle) == -1) {
794 794 mdb_warn("failed to read conn_t V4");
795 795 return (-1);
796 796 }
797 797
798 798 mdb_printf("socket: ");
799 799 mdb_nhconvert(&port, &conn_t.conn_lport, sizeof (port));
800 800 mdb_printf("AF_INET %I %d ", conn_t.conn_laddr_v4, port);
801 801
802 802 /*
803 803 * If this is a listening socket, we don't print
804 804 * the remote address.
805 805 */
806 806 if (IPCL_IS_TCP(&conn_t) && IPCL_IS_BOUND(&conn_t) == 0 ||
807 807 IPCL_IS_UDP(&conn_t) && IPCL_IS_CONNECTED(&conn_t)) {
808 808 mdb_printf("remote: ");
809 809 mdb_nhconvert(&port, &conn_t.conn_fport, sizeof (port));
810 810 mdb_printf("AF_INET %I %d ", conn_t.conn_faddr_v4,
811 811 port);
812 812 }
813 813
814 814 break;
815 815 }
816 816
817 817 case AF_INET6:
818 818 {
819 819 conn_t conn_t;
820 820 in_port_t port;
821 821
822 822 if (mdb_vread(&conn_t, sizeof (conn_t),
823 823 (uintptr_t)socknode->so_proto_handle) == -1) {
824 824 mdb_warn("failed to read conn_t V6");
825 825 return (-1);
826 826 }
827 827
828 828 mdb_printf("socket: ");
829 829 mdb_nhconvert(&port, &conn_t.conn_lport, sizeof (port));
830 830 mdb_printf("AF_INET6 %N %d ", &conn_t.conn_laddr_v4, port);
831 831
832 832 /*
833 833 * If this is a listening socket, we don't print
834 834 * the remote address.
835 835 */
836 836 if (IPCL_IS_TCP(&conn_t) && IPCL_IS_BOUND(&conn_t) == 0 ||
837 837 IPCL_IS_UDP(&conn_t) && IPCL_IS_CONNECTED(&conn_t)) {
838 838 mdb_printf("remote: ");
839 839 mdb_nhconvert(&port, &conn_t.conn_fport, sizeof (port));
840 840 mdb_printf("AF_INET6 %N %d ", &conn_t.conn_faddr_v6,
841 841 port);
842 842 }
843 843
844 844 break;
845 845 }
846 846
847 847 default:
848 848 mdb_printf("AF_?? (%d)", socknode->so_family);
849 849 break;
850 850 }
851 851
852 852 return (0);
853 853 }
854 854
855 855 static int
856 856 sctp_sock_print(struct sonode *socknode)
857 857 {
858 858 sctp_t sctp_t;
859 859 conn_t conns;
860 860
861 861 struct sockaddr *laddr = mdb_alloc(sizeof (struct sockaddr), UM_SLEEP);
862 862 struct sockaddr *faddr = mdb_alloc(sizeof (struct sockaddr), UM_SLEEP);
863 863
864 864 if (mdb_vread(&sctp_t, sizeof (sctp_t),
865 865 (uintptr_t)socknode->so_proto_handle) == -1) {
866 866 mdb_warn("failed to read sctp_t");
867 867 return (-1);
868 868 }
869 869
870 870 if (mdb_vread(&conns, sizeof (conn_t),
871 871 (uintptr_t)sctp_t.sctp_connp) == -1) {
872 872 mdb_warn("failed to read conn_t at %p",
873 873 (uintptr_t)sctp_t.sctp_connp);
874 874 return (-1);
875 875 }
876 876 sctp_t.sctp_connp = &conns;
877 877
878 878 if (sctp_getsockaddr(&sctp_t, laddr) == 0) {
879 879 mdb_printf("socket:");
880 880 pfiles_print_addr(laddr);
881 881 }
882 882 if (sctp_getpeeraddr(&sctp_t, faddr) == 0) {
883 883 mdb_printf("remote:");
884 884 pfiles_print_addr(faddr);
885 885 }
886 886
887 887 return (0);
888 888 }
889 889
890 890 /* ARGSUSED */
891 891 static int
892 892 sdp_sock_print(struct sonode *socknode)
893 893 {
894 894 return (0);
895 895 }
896 896
897 897 struct sock_print {
898 898 int family;
899 899 int type;
900 900 int pro;
901 901 int (*print)(struct sonode *socknode);
902 902 } sock_prints[] = {
903 903 { 2, 2, 0, tcpip_sock_print }, /* /dev/tcp */
904 904 { 2, 2, 6, tcpip_sock_print }, /* /dev/tcp */
905 905 { 26, 2, 0, tcpip_sock_print }, /* /dev/tcp6 */
906 906 { 26, 2, 6, tcpip_sock_print }, /* /dev/tcp6 */
907 907 { 2, 1, 0, tcpip_sock_print }, /* /dev/udp */
908 908 { 2, 1, 17, tcpip_sock_print }, /* /dev/udp */
909 909 { 26, 1, 0, tcpip_sock_print }, /* /dev/udp6 */
910 910 { 26, 1, 17, tcpip_sock_print }, /* /dev/udp6 */
911 911 { 2, 4, 0, tcpip_sock_print }, /* /dev/rawip */
912 912 { 26, 4, 0, tcpip_sock_print }, /* /dev/rawip6 */
913 913 { 2, 2, 132, sctp_sock_print }, /* /dev/sctp */
914 914 { 26, 2, 132, sctp_sock_print }, /* /dev/sctp6 */
915 915 { 2, 6, 132, sctp_sock_print }, /* /dev/sctp */
916 916 { 26, 6, 132, sctp_sock_print }, /* /dev/sctp6 */
917 917 { 24, 4, 0, tcpip_sock_print }, /* /dev/rts */
918 918 { 2, 2, 257, sdp_sock_print }, /* /dev/sdp */
919 919 { 26, 2, 257, sdp_sock_print }, /* /dev/sdp */
920 920 };
921 921
922 922 #define NUM_SOCK_PRINTS \
923 923 (sizeof (sock_prints) / sizeof (struct sock_print))
924 924
925 925 static int
926 926 pfile_callback(uintptr_t addr, const struct file *f, struct pfiles_cbdata *cb)
927 927 {
928 928 vnode_t v, layer_vn;
929 929 int myfd = cb->fd;
930 930 const char *type;
931 931 char path[MAXPATHLEN];
932 932 uintptr_t top_vnodep, realvpp;
933 933 char fsname[_ST_FSTYPSZ];
934 934 int err, i;
935 935
936 936 cb->fd++;
937 937
938 938 if (addr == NULL) {
939 939 return (WALK_NEXT);
↓ open down ↓ |
398 lines elided |
↑ open up ↑ |
940 940 }
941 941
942 942 top_vnodep = realvpp = (uintptr_t)f->f_vnode;
943 943
944 944 if (mdb_vread(&v, sizeof (v), realvpp) == -1) {
945 945 mdb_warn("failed to read vnode");
946 946 return (DCMD_ERR);
947 947 }
948 948
949 949 type = "?";
950 - for (i = 0; i <= NUM_FS_TYPES; i++) {
951 - if (fs_types[i].type == v.v_type)
950 + for (i = 0; i < NUM_FS_TYPES; i++) {
951 + if (fs_types[i].type == v.v_type) {
952 952 type = fs_types[i].name;
953 + break;
954 + }
953 955 }
954 956
955 957 do {
956 958 uintptr_t next_realvpp;
957 959
958 960 err = next_realvp(realvpp, &layer_vn, &next_realvpp);
959 961 if (next_realvpp != NULL)
960 962 realvpp = next_realvpp;
961 963
962 964 } while (err == REALVP_CONTINUE);
963 965
964 966 if (err == REALVP_ERR) {
965 967 mdb_warn("failed to do realvp() for %p", realvpp);
966 968 return (DCMD_ERR);
967 969 }
968 970
969 971 if (read_fsname((uintptr_t)layer_vn.v_vfsp, fsname) == -1)
970 972 return (DCMD_ERR);
971 973
972 974 mdb_printf("%4d %4s %?0p ", myfd, type, top_vnodep);
973 975
974 976 if (cb->opt_p) {
975 977 if (pfiles_dig_pathname(top_vnodep, path) == -1)
976 978 return (DCMD_ERR);
977 979
978 980 mdb_printf("%s\n", path);
979 981 return (DCMD_OK);
980 982 }
981 983
982 984 /*
983 985 * Sockets generally don't have interesting pathnames; we only
984 986 * show those in the '-p' view.
985 987 */
986 988 path[0] = '\0';
987 989 if (v.v_type != VSOCK) {
988 990 if (pfiles_dig_pathname(top_vnodep, path) == -1)
989 991 return (DCMD_ERR);
990 992 }
991 993 mdb_printf("%s%s", path, path[0] == '\0' ? "" : " ");
992 994
993 995 switch (v.v_type) {
994 996 case VDOOR:
995 997 {
996 998 door_node_t doornode;
997 999 proc_t pr;
998 1000
999 1001 if (mdb_vread(&doornode, sizeof (doornode),
1000 1002 (uintptr_t)layer_vn.v_data) == -1) {
1001 1003 mdb_warn("failed to read door_node");
1002 1004 return (DCMD_ERR);
1003 1005 }
1004 1006
1005 1007 if (mdb_vread(&pr, sizeof (pr),
1006 1008 (uintptr_t)doornode.door_target) == -1) {
1007 1009 mdb_warn("failed to read door server process %p",
1008 1010 doornode.door_target);
1009 1011 return (DCMD_ERR);
1010 1012 }
1011 1013 mdb_printf("[door to '%s' (proc=%p)]", pr.p_user.u_comm,
1012 1014 doornode.door_target);
1013 1015 break;
1014 1016 }
1015 1017
1016 1018 case VSOCK:
1017 1019 {
1018 1020 vnode_t v_sock;
1019 1021 struct sonode so;
1020 1022
1021 1023 if (mdb_vread(&v_sock, sizeof (v_sock), realvpp) == -1) {
1022 1024 mdb_warn("failed to read socket vnode");
1023 1025 return (DCMD_ERR);
1024 1026 }
1025 1027
1026 1028 /*
1027 1029 * Sockets can be non-stream or stream, they have to be dealed
1028 1030 * with differently.
1029 1031 */
1030 1032 if (v_sock.v_stream == NULL) {
1031 1033 if (pfiles_get_sonode(&v_sock, &so) == -1)
1032 1034 return (DCMD_ERR);
1033 1035
1034 1036 /* Pick the proper methods. */
1035 1037 for (i = 0; i <= NUM_SOCK_PRINTS; i++) {
1036 1038 if ((sock_prints[i].family == so.so_family &&
1037 1039 sock_prints[i].type == so.so_type &&
1038 1040 sock_prints[i].pro == so.so_protocol) ||
1039 1041 (sock_prints[i].family == so.so_family &&
1040 1042 sock_prints[i].type == so.so_type &&
1041 1043 so.so_type == SOCK_RAW)) {
1042 1044 if ((*sock_prints[i].print)(&so) == -1)
1043 1045 return (DCMD_ERR);
1044 1046 }
1045 1047 }
1046 1048 } else {
1047 1049 sotpi_sonode_t sotpi_sonode;
1048 1050
1049 1051 if (pfiles_get_sonode(&v_sock, &so) == -1)
1050 1052 return (DCMD_ERR);
1051 1053
1052 1054 /*
1053 1055 * If the socket is a fallback socket, read its related
1054 1056 * information separately; otherwise, read it as a whole
1055 1057 * tpi socket.
1056 1058 */
1057 1059 if (so.so_state & SS_FALLBACK_COMP) {
1058 1060 sotpi_sonode.st_sonode = so;
1059 1061
1060 1062 if (mdb_vread(&(sotpi_sonode.st_info),
1061 1063 sizeof (sotpi_info_t),
1062 1064 (uintptr_t)so.so_priv) == -1)
1063 1065 return (DCMD_ERR);
1064 1066 } else {
1065 1067 if (pfiles_get_tpi_sonode(&v_sock,
1066 1068 &sotpi_sonode) == -1)
1067 1069 return (DCMD_ERR);
1068 1070 }
1069 1071
1070 1072 if (tpi_sock_print(&sotpi_sonode) == -1)
1071 1073 return (DCMD_ERR);
1072 1074 }
1073 1075
1074 1076 break;
1075 1077 }
1076 1078
1077 1079 case VPORT:
1078 1080 mdb_printf("[event port (port=%p)]", v.v_data);
1079 1081 break;
1080 1082
1081 1083 case VPROC:
1082 1084 {
1083 1085 prnode_t prnode;
1084 1086 prcommon_t prcommon;
1085 1087
1086 1088 if (mdb_vread(&prnode, sizeof (prnode),
1087 1089 (uintptr_t)layer_vn.v_data) == -1) {
1088 1090 mdb_warn("failed to read prnode");
1089 1091 return (DCMD_ERR);
1090 1092 }
1091 1093
1092 1094 if (mdb_vread(&prcommon, sizeof (prcommon),
1093 1095 (uintptr_t)prnode.pr_common) == -1) {
1094 1096 mdb_warn("failed to read prcommon %p",
1095 1097 prnode.pr_common);
1096 1098 return (DCMD_ERR);
1097 1099 }
1098 1100
1099 1101 mdb_printf("(proc=%p)", prcommon.prc_proc);
1100 1102 break;
1101 1103 }
1102 1104
1103 1105 default:
1104 1106 break;
1105 1107 }
1106 1108
1107 1109 mdb_printf("\n");
1108 1110
1109 1111 return (WALK_NEXT);
1110 1112 }
1111 1113
1112 1114 static int
1113 1115 file_t_callback(uintptr_t addr, const struct file *f, struct pfiles_cbdata *cb)
1114 1116 {
1115 1117 int myfd = cb->fd;
1116 1118
1117 1119 cb->fd++;
1118 1120
1119 1121 if (addr == NULL) {
1120 1122 return (WALK_NEXT);
1121 1123 }
1122 1124
1123 1125 /*
1124 1126 * We really need 20 digits to print a 64-bit offset_t, but this
1125 1127 * is exceedingly rare, so we cheat and assume a column width of 10
1126 1128 * digits, in order to fit everything cleanly into 80 columns.
1127 1129 */
1128 1130 mdb_printf("%?0p %4d %8x %?0p %10lld %?0p %4d\n",
1129 1131 addr, myfd, f->f_flag, f->f_vnode, f->f_offset, f->f_cred,
1130 1132 f->f_count);
1131 1133
1132 1134 return (WALK_NEXT);
1133 1135 }
1134 1136
1135 1137 int
1136 1138 pfiles(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
1137 1139 {
1138 1140 int opt_f = 0;
1139 1141
1140 1142 struct pfiles_cbdata cb;
1141 1143
1142 1144 bzero(&cb, sizeof (cb));
1143 1145
1144 1146 if (!(flags & DCMD_ADDRSPEC))
1145 1147 return (DCMD_USAGE);
1146 1148
1147 1149 if (mdb_getopts(argc, argv,
1148 1150 'p', MDB_OPT_SETBITS, TRUE, &cb.opt_p,
1149 1151 'f', MDB_OPT_SETBITS, TRUE, &opt_f, NULL) != argc)
1150 1152 return (DCMD_USAGE);
1151 1153
1152 1154 if (opt_f) {
1153 1155 mdb_printf("%<u>%?s %4s %8s %?s %10s %?s %4s%</u>\n", "FILE",
1154 1156 "FD", "FLAG", "VNODE", "OFFSET", "CRED", "CNT");
1155 1157 if (mdb_pwalk("allfile", (mdb_walk_cb_t)file_t_callback, &cb,
1156 1158 addr) == -1) {
1157 1159 mdb_warn("failed to walk 'allfile'");
1158 1160 return (DCMD_ERR);
1159 1161 }
1160 1162 } else {
1161 1163 mdb_printf("%<u>%-4s %4s %?s ", "FD", "TYPE", "VNODE");
1162 1164 if (cb.opt_p)
1163 1165 mdb_printf("PATH");
1164 1166 else
1165 1167 mdb_printf("INFO");
1166 1168 mdb_printf("%</u>\n");
1167 1169
1168 1170 if (mdb_pwalk("allfile", (mdb_walk_cb_t)pfile_callback, &cb,
1169 1171 addr) == -1) {
1170 1172 mdb_warn("failed to walk 'allfile'");
1171 1173 return (DCMD_ERR);
1172 1174 }
1173 1175 }
1174 1176
1175 1177
1176 1178 return (DCMD_OK);
1177 1179 }
1178 1180
1179 1181 void
1180 1182 pfiles_help(void)
1181 1183 {
1182 1184 mdb_printf(
1183 1185 "Given the address of a process, print information about files\n"
1184 1186 "which the process has open. By default, this includes decoded\n"
1185 1187 "information about the file depending on file and filesystem type\n"
1186 1188 "\n"
1187 1189 "\t-p\tPathnames; omit decoded information. Only display "
1188 1190 "pathnames\n"
1189 1191 "\t-f\tfile_t view; show the file_t structure corresponding to "
1190 1192 "the fd\n");
1191 1193 }
↓ open down ↓ |
229 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX