Print this page
4229 mdb hangs on exit when long umem cache names exist
Reviewed by: Robert Mustacchi <rm@joyent.com>
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/cmd/mdb/common/mdb/mdb_tab.c
+++ new/usr/src/cmd/mdb/common/mdb/mdb_tab.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
↓ open down ↓ |
13 lines elided |
↑ open up ↑ |
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) 2013 by Delphix. All rights reserved.
23 23 * Copyright (c) 2012 Joyent, Inc. All rights reserved.
24 + * Copyright (c) 2013 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
24 25 */
25 26 /*
26 27 * This file contains all of the interfaces for mdb's tab completion engine.
27 28 * Currently some interfaces are private to mdb and its internal implementation,
28 29 * those are in mdb_tab.h. Other pieces are public interfaces. Those are in
29 30 * mdb_modapi.h.
30 31 *
31 32 * Memory allocations in tab completion context have to be done very carefully.
32 33 * We need to think of ourselves as the same as any other command that is being
33 34 * executed by the user, which means we must use UM_GC to handle being
34 35 * interrupted.
35 36 */
36 37
37 38 #include <mdb/mdb_modapi.h>
38 39 #include <mdb/mdb_ctf.h>
39 40 #include <mdb/mdb_ctf_impl.h>
40 41 #include <mdb/mdb_string.h>
41 42 #include <mdb/mdb_module.h>
42 43 #include <mdb/mdb_debug.h>
43 44 #include <mdb/mdb_print.h>
44 45 #include <mdb/mdb_nv.h>
45 46 #include <mdb/mdb_tab.h>
46 47 #include <mdb/mdb_target.h>
47 48 #include <mdb/mdb.h>
48 49
49 50 #include <ctype.h>
50 51
51 52 /*
52 53 * There may be another way to do this, but this works well enough.
53 54 */
54 55 #define COMMAND_SEPARATOR "::"
55 56
56 57 /*
57 58 * find_command_start --
58 59 *
59 60 * Given a buffer find the start of the last command.
60 61 */
61 62 static char *
62 63 tab_find_command_start(char *buf)
63 64 {
64 65 char *offset = strstr(buf, COMMAND_SEPARATOR);
65 66
66 67 if (offset == NULL)
67 68 return (NULL);
68 69
69 70 for (;;) {
70 71 char *next = strstr(offset + strlen(COMMAND_SEPARATOR),
71 72 COMMAND_SEPARATOR);
72 73
73 74 if (next == NULL) {
74 75 return (offset);
75 76 }
76 77
77 78 offset = next;
78 79 }
79 80 }
80 81
81 82 /*
82 83 * get_dcmd --
83 84 *
84 85 * Given a buffer containing a command and its argument return
85 86 * the name of the command and the offset in the buffer where
86 87 * the command arguments start.
87 88 *
88 89 * Note: This will modify the buffer.
89 90 */
90 91 char *
91 92 tab_get_dcmd(char *buf, char **args, uint_t *flags)
92 93 {
93 94 char *start = buf + strlen(COMMAND_SEPARATOR);
94 95 char *separator = start;
95 96 const char *end = buf + strlen(buf);
96 97 uint_t space = 0;
97 98
98 99 while (separator < end && !isspace(*separator))
99 100 separator++;
100 101
101 102 if (separator == end) {
102 103 *args = NULL;
103 104 } else {
104 105 if (isspace(*separator))
105 106 space = 1;
106 107
107 108 *separator++ = '\0';
108 109 *args = separator;
109 110 }
110 111
111 112 if (space)
112 113 *flags |= DCMD_TAB_SPACE;
113 114
114 115 return (start);
115 116 }
116 117
117 118 /*
118 119 * count_args --
119 120 *
120 121 * Given a buffer containing dmcd arguments return the total number
121 122 * of arguments.
122 123 *
123 124 * While parsing arguments we need to keep track of whether or not the last
124 125 * arguments ends with a trailing space.
125 126 */
126 127 static int
127 128 tab_count_args(const char *input, uint_t *flags)
128 129 {
129 130 const char *index;
130 131 int argc = 0;
131 132 uint_t space = *flags & DCMD_TAB_SPACE;
132 133 index = input;
133 134
134 135 while (*index != '\0') {
135 136 while (*index != '\0' && isspace(*index)) {
136 137 index++;
137 138 space = 1;
138 139 }
139 140
140 141 if (*index != '\0' && !isspace(*index)) {
141 142 argc++;
142 143 space = 0;
143 144 while (*index != '\0' && !isspace (*index)) {
144 145 index++;
145 146 }
146 147 }
147 148 }
148 149
149 150 if (space)
150 151 *flags |= DCMD_TAB_SPACE;
151 152 else
152 153 *flags &= ~DCMD_TAB_SPACE;
153 154
154 155 return (argc);
155 156 }
156 157
157 158 /*
158 159 * copy_args --
159 160 *
160 161 * Given a buffer containing dcmd arguments and an array of mdb_arg_t's
161 162 * initialize the string value of each mdb_arg_t.
162 163 *
163 164 * Note: This will modify the buffer.
164 165 */
165 166 static int
166 167 tab_copy_args(char *input, int argc, mdb_arg_t *argv)
167 168 {
168 169 int i = 0;
169 170 char *index;
170 171
171 172 index = input;
172 173
173 174 while (*index) {
174 175 while (*index && isspace(*index)) {
175 176 index++;
176 177 }
177 178
178 179 if (*index && !isspace(*index)) {
179 180 char *end = index;
180 181
181 182 while (*end && !isspace(*end)) {
182 183 end++;
183 184 }
184 185
185 186 if (*end) {
186 187 *end++ = '\0';
187 188 }
188 189
189 190 argv[i].a_type = MDB_TYPE_STRING;
190 191 argv[i].a_un.a_str = index;
191 192
192 193 index = end;
193 194 i++;
194 195 }
195 196 }
196 197
197 198 if (i != argc)
198 199 return (-1);
199 200
200 201 return (0);
201 202 }
202 203
203 204 /*
204 205 * parse-buf --
205 206 *
206 207 * Parse the given buffer and return the specified dcmd, the number
207 208 * of arguments, and array of mdb_arg_t containing the argument
208 209 * values.
209 210 *
210 211 * Note: this will modify the specified buffer. Caller is responisble
211 212 * for freeing argvp.
212 213 */
213 214 static int
214 215 tab_parse_buf(char *buf, char **dcmdp, int *argcp, mdb_arg_t **argvp,
215 216 uint_t *flags)
216 217 {
217 218 char *data = tab_find_command_start(buf);
218 219 char *args_data = NULL;
219 220 char *dcmd = NULL;
220 221 int argc = 0;
221 222 mdb_arg_t *argv = NULL;
222 223
223 224 if (data == NULL) {
224 225 return (-1);
225 226 }
226 227
227 228 dcmd = tab_get_dcmd(data, &args_data, flags);
228 229
229 230 if (dcmd == NULL) {
230 231 return (-1);
231 232 }
232 233
233 234 if (args_data != NULL) {
234 235 argc = tab_count_args(args_data, flags);
235 236
236 237 if (argc != 0) {
237 238 argv = mdb_alloc(sizeof (mdb_arg_t) * argc,
238 239 UM_SLEEP | UM_GC);
239 240
240 241 if (tab_copy_args(args_data, argc, argv) == -1)
241 242 return (-1);
242 243 }
243 244 }
244 245
245 246 *dcmdp = dcmd;
246 247 *argcp = argc;
247 248 *argvp = argv;
248 249
249 250 return (0);
250 251 }
251 252
252 253 /*
253 254 * tab_command --
254 255 *
255 256 * This function is executed anytime a tab is entered. It checks
256 257 * the current buffer to determine if there is a valid dmcd,
257 258 * if that dcmd has a tab completion handler it will invoke it.
258 259 *
259 260 * This function returns the string (if any) that should be added to the
260 261 * existing buffer to complete it.
261 262 */
262 263 int
263 264 mdb_tab_command(mdb_tab_cookie_t *mcp, const char *buf)
264 265 {
265 266 char *data;
266 267 char *dcmd = NULL;
267 268 int argc = 0;
268 269 mdb_arg_t *argv = NULL;
269 270 int ret = 0;
270 271 mdb_idcmd_t *cp;
271 272 uint_t flags = 0;
272 273
273 274 /*
274 275 * Parsing the command and arguments will modify the buffer
275 276 * (replacing spaces with \0), so make a copy of the specified
276 277 * buffer first.
277 278 */
278 279 data = mdb_alloc(strlen(buf) + 1, UM_SLEEP | UM_GC);
279 280 (void) strcpy(data, buf);
280 281
281 282 /*
282 283 * Get the specified dcmd and arguments from the buffer.
283 284 */
284 285 ret = tab_parse_buf(data, &dcmd, &argc, &argv, &flags);
285 286
286 287 /*
287 288 * Match against global symbols if the input is not a dcmd
288 289 */
289 290 if (ret != 0) {
290 291 (void) mdb_tab_complete_global(mcp, buf);
291 292 goto out;
292 293 }
293 294
294 295 /*
295 296 * Check to see if the buffer contains a valid dcmd
296 297 */
297 298 cp = mdb_dcmd_lookup(dcmd);
298 299
299 300 /*
300 301 * When argc is zero it indicates that we are trying to tab complete
301 302 * a dcmd or a global symbol. Note, that if there isn't the start of
302 303 * a dcmd, i.e. ::, then we will have already bailed in the call to
303 304 * tab_parse_buf.
304 305 */
305 306 if (cp == NULL && argc != 0) {
306 307 goto out;
307 308 }
308 309
309 310 /*
310 311 * Invoke the command specific tab completion handler or the built in
311 312 * dcmd one if there is no dcmd.
312 313 */
313 314 if (cp == NULL)
314 315 (void) mdb_tab_complete_dcmd(mcp, dcmd);
315 316 else
316 317 mdb_call_tab(cp, mcp, flags, argc, argv);
317 318
318 319 out:
319 320 return (mdb_tab_size(mcp));
320 321 }
321 322
322 323 static int
323 324 tab_complete_dcmd(mdb_var_t *v, void *arg)
324 325 {
325 326 mdb_idcmd_t *idcp = mdb_nv_get_cookie(mdb_nv_get_cookie(v));
326 327 mdb_tab_cookie_t *mcp = (mdb_tab_cookie_t *)arg;
327 328
328 329 /*
329 330 * The way that mdb is implemented, even commands like $C will show up
330 331 * here. As such, we don't want to match anything that doesn't start
331 332 * with an alpha or number. While nothing currently appears (via a
332 333 * cursory search with mdb -k) to start with a capital letter or a
333 334 * number, we'll support them anyways.
334 335 */
335 336 if (!isalnum(idcp->idc_name[0]))
336 337 return (0);
337 338
338 339 mdb_tab_insert(mcp, idcp->idc_name);
339 340 return (0);
340 341 }
341 342
342 343 int
343 344 mdb_tab_complete_dcmd(mdb_tab_cookie_t *mcp, const char *dcmd)
344 345 {
345 346 mdb_tab_setmbase(mcp, dcmd);
346 347 mdb_nv_sort_iter(&mdb.m_dcmds, tab_complete_dcmd, mcp,
347 348 UM_GC | UM_SLEEP);
348 349 return (0);
349 350 }
350 351
351 352 static int
352 353 tab_complete_walker(mdb_var_t *v, void *arg)
353 354 {
354 355 mdb_iwalker_t *iwp = mdb_nv_get_cookie(mdb_nv_get_cookie(v));
355 356 mdb_tab_cookie_t *mcp = arg;
356 357
357 358 mdb_tab_insert(mcp, iwp->iwlk_name);
358 359 return (0);
359 360 }
360 361
361 362 int
362 363 mdb_tab_complete_walker(mdb_tab_cookie_t *mcp, const char *walker)
363 364 {
364 365 if (walker != NULL)
365 366 mdb_tab_setmbase(mcp, walker);
366 367 mdb_nv_sort_iter(&mdb.m_walkers, tab_complete_walker, mcp,
367 368 UM_GC | UM_SLEEP);
368 369
369 370 return (0);
370 371 }
371 372
372 373 mdb_tab_cookie_t *
373 374 mdb_tab_init(void)
374 375 {
375 376 mdb_tab_cookie_t *mcp;
376 377
377 378 mcp = mdb_zalloc(sizeof (mdb_tab_cookie_t), UM_SLEEP | UM_GC);
378 379 (void) mdb_nv_create(&mcp->mtc_nv, UM_SLEEP | UM_GC);
379 380
380 381 return (mcp);
381 382 }
382 383
383 384 size_t
384 385 mdb_tab_size(mdb_tab_cookie_t *mcp)
385 386 {
386 387 return (mdb_nv_size(&mcp->mtc_nv));
↓ open down ↓ |
353 lines elided |
↑ open up ↑ |
387 388 }
388 389
389 390 /*
390 391 * Determine whether the specified name is a valid tab completion for
391 392 * the given command. If the name is a valid tab completion then
392 393 * it will be saved in the mdb_tab_cookie_t.
393 394 */
394 395 void
395 396 mdb_tab_insert(mdb_tab_cookie_t *mcp, const char *name)
396 397 {
397 - size_t len, matches, index;
398 - uint_t flags;
398 + size_t matches, index;
399 399 mdb_var_t *v;
400 - char *n;
401 - const char *nvn;
402 400
403 401 /*
404 402 * If we have a match set, then we want to verify that we actually match
405 403 * it.
406 404 */
407 405 if (mcp->mtc_base != NULL &&
408 406 strncmp(name, mcp->mtc_base, strlen(mcp->mtc_base)) != 0)
409 407 return;
410 408
411 409 v = mdb_nv_lookup(&mcp->mtc_nv, name);
412 410 if (v != NULL)
413 411 return;
414 412
415 - /*
416 - * Names that we get passed in may be longer than MDB_NV_NAMELEN which
417 - * is currently 31 including the null terminator. If that is the case,
418 - * then we're going to take care of allocating a string and holding it
419 - * for our caller. Note that we don't need to free it, because we're
420 - * allocating this with UM_GC.
421 - */
422 - flags = 0;
423 - len = strlen(name);
424 - if (len > MDB_NV_NAMELEN - 1) {
425 - n = mdb_alloc(len + 1, UM_SLEEP | UM_GC);
426 - (void) strcpy(n, name);
427 - nvn = n;
428 - flags |= MDB_NV_EXTNAME;
429 - } else {
430 - nvn = name;
431 - }
432 - flags |= MDB_NV_RDONLY;
433 -
434 - (void) mdb_nv_insert(&mcp->mtc_nv, nvn, NULL, 0, flags);
413 + (void) mdb_nv_insert(&mcp->mtc_nv, name, NULL, 0, MDB_NV_RDONLY);
435 414
436 415 matches = mdb_tab_size(mcp);
437 416 if (matches == 1) {
438 - (void) strlcpy(mcp->mtc_match, nvn, MDB_SYM_NAMLEN);
417 + (void) strlcpy(mcp->mtc_match, name, MDB_SYM_NAMLEN);
439 418 } else {
440 419 index = 0;
441 420 while (mcp->mtc_match[index] &&
442 - mcp->mtc_match[index] == nvn[index])
421 + mcp->mtc_match[index] == name[index])
443 422 index++;
444 423
445 424 mcp->mtc_match[index] = '\0';
446 425 }
447 426 }
448 427
449 428 /*ARGSUSED*/
450 429 static int
451 430 tab_print_cb(mdb_var_t *v, void *ignored)
452 431 {
453 432 mdb_printf("%s\n", mdb_nv_get_name(v));
454 433 return (0);
455 434 }
456 435
457 436 void
458 437 mdb_tab_print(mdb_tab_cookie_t *mcp)
459 438 {
460 439 mdb_nv_sort_iter(&mcp->mtc_nv, tab_print_cb, NULL, UM_SLEEP | UM_GC);
461 440 }
462 441
463 442 const char *
464 443 mdb_tab_match(mdb_tab_cookie_t *mcp)
465 444 {
466 445 size_t blen;
467 446
468 447 if (mcp->mtc_base == NULL)
469 448 blen = 0;
470 449 else
471 450 blen = strlen(mcp->mtc_base);
472 451 return (mcp->mtc_match + blen);
473 452 }
474 453
475 454 void
476 455 mdb_tab_setmbase(mdb_tab_cookie_t *mcp, const char *base)
477 456 {
478 457 (void) strlcpy(mcp->mtc_base, base, MDB_SYM_NAMLEN);
479 458 }
480 459
481 460 /*
482 461 * This function is currently a no-op due to the fact that we have to GC because
483 462 * we're in command context.
484 463 */
485 464 /*ARGSUSED*/
486 465 void
487 466 mdb_tab_fini(mdb_tab_cookie_t *mcp)
488 467 {
489 468 }
490 469
491 470 /*ARGSUSED*/
492 471 static int
493 472 tab_complete_global(void *arg, const GElf_Sym *sym, const char *name,
494 473 const mdb_syminfo_t *sip, const char *obj)
495 474 {
496 475 mdb_tab_cookie_t *mcp = arg;
497 476 mdb_tab_insert(mcp, name);
498 477 return (0);
499 478 }
500 479
501 480 /*
502 481 * This function tab completes against all loaded global symbols.
503 482 */
504 483 int
505 484 mdb_tab_complete_global(mdb_tab_cookie_t *mcp, const char *name)
506 485 {
507 486 mdb_tab_setmbase(mcp, name);
508 487 (void) mdb_tgt_symbol_iter(mdb.m_target, MDB_TGT_OBJ_EVERY,
509 488 MDB_TGT_SYMTAB, MDB_TGT_BIND_ANY | MDB_TGT_TYPE_OBJECT |
510 489 MDB_TGT_TYPE_FUNC, tab_complete_global, mcp);
511 490 return (0);
512 491 }
513 492
514 493 /*
515 494 * This function takes a ctf id and determines whether or not the associated
516 495 * type should be considered as a potential match for the given tab
517 496 * completion command. We verify that the type itself is valid
518 497 * for completion given the current context of the command, resolve
519 498 * its actual name, and then pass it off to mdb_tab_insert to determine
520 499 * if it's an actual match.
521 500 */
522 501 static int
523 502 tab_complete_type(mdb_ctf_id_t id, void *arg)
524 503 {
525 504 int rkind;
526 505 char buf[MDB_SYM_NAMLEN];
527 506 mdb_ctf_id_t rid;
528 507 mdb_tab_cookie_t *mcp = arg;
529 508 uint_t flags = (uint_t)(uintptr_t)mcp->mtc_cba;
530 509
531 510 /*
532 511 * CTF data includes types that mdb commands don't understand. Before
533 512 * we resolve the actual type prune any entry that is a type we
534 513 * don't care about.
535 514 */
536 515 switch (mdb_ctf_type_kind(id)) {
537 516 case CTF_K_CONST:
538 517 case CTF_K_RESTRICT:
539 518 case CTF_K_VOLATILE:
540 519 return (0);
541 520 }
542 521
543 522 if (mdb_ctf_type_resolve(id, &rid) != 0)
544 523 return (1);
545 524
546 525 rkind = mdb_ctf_type_kind(rid);
547 526
548 527 if ((flags & MDB_TABC_MEMBERS) && rkind != CTF_K_STRUCT &&
549 528 rkind != CTF_K_UNION)
550 529 return (0);
551 530
552 531 if ((flags & MDB_TABC_NOPOINT) && rkind == CTF_K_POINTER)
553 532 return (0);
554 533
555 534 if ((flags & MDB_TABC_NOARRAY) && rkind == CTF_K_ARRAY)
556 535 return (0);
557 536
558 537 (void) mdb_ctf_type_name(id, buf, sizeof (buf));
559 538
560 539 mdb_tab_insert(mcp, buf);
561 540 return (0);
562 541 }
563 542
564 543 /*ARGSUSED*/
565 544 static int
566 545 mdb_tab_complete_module(void *data, const mdb_map_t *mp, const char *name)
567 546 {
568 547 (void) mdb_ctf_type_iter(name, tab_complete_type, data);
569 548 return (0);
570 549 }
571 550
572 551 int
573 552 mdb_tab_complete_type(mdb_tab_cookie_t *mcp, const char *name, uint_t flags)
574 553 {
575 554 mdb_tgt_t *t = mdb.m_target;
576 555
577 556 mcp->mtc_cba = (void *)(uintptr_t)flags;
578 557 if (name != NULL)
579 558 mdb_tab_setmbase(mcp, name);
580 559
581 560 (void) mdb_tgt_object_iter(t, mdb_tab_complete_module, mcp);
582 561 (void) mdb_ctf_type_iter(MDB_CTF_SYNTHETIC_ITER, tab_complete_type,
583 562 mcp);
584 563 return (0);
585 564 }
586 565
587 566 /*ARGSUSED*/
588 567 static int
589 568 tab_complete_member(const char *name, mdb_ctf_id_t id, ulong_t off, void *arg)
590 569 {
591 570 mdb_tab_cookie_t *mcp = arg;
592 571 mdb_tab_insert(mcp, name);
593 572 return (0);
594 573 }
595 574
596 575 int
597 576 mdb_tab_complete_member_by_id(mdb_tab_cookie_t *mcp, mdb_ctf_id_t id,
598 577 const char *member)
599 578 {
600 579 if (member != NULL)
601 580 mdb_tab_setmbase(mcp, member);
602 581 (void) mdb_ctf_member_iter(id, tab_complete_member, mcp);
603 582 return (0);
604 583 }
605 584
606 585 int
607 586 mdb_tab_complete_member(mdb_tab_cookie_t *mcp, const char *type,
608 587 const char *member)
609 588 {
610 589 mdb_ctf_id_t id;
611 590
612 591 if (mdb_ctf_lookup_by_name(type, &id) != 0)
613 592 return (-1);
614 593
615 594 return (mdb_tab_complete_member_by_id(mcp, id, member));
616 595 }
617 596
618 597 int
619 598 mdb_tab_complete_mt(mdb_tab_cookie_t *mcp, uint_t flags, int argc,
620 599 const mdb_arg_t *argv)
621 600 {
622 601 char tn[MDB_SYM_NAMLEN];
623 602 int ret;
624 603
625 604 if (argc == 0 && !(flags & DCMD_TAB_SPACE))
626 605 return (0);
627 606
628 607 if (argc == 0)
629 608 return (mdb_tab_complete_type(mcp, NULL, MDB_TABC_MEMBERS));
630 609
631 610 if ((ret = mdb_tab_typename(&argc, &argv, tn, sizeof (tn))) < 0)
632 611 return (ret);
633 612
634 613 if (argc == 1 && (!(flags & DCMD_TAB_SPACE) || ret == 1))
635 614 return (mdb_tab_complete_type(mcp, tn, MDB_TABC_MEMBERS));
636 615
637 616 if (argc == 1 && (flags & DCMD_TAB_SPACE))
638 617 return (mdb_tab_complete_member(mcp, tn, NULL));
639 618
640 619 if (argc == 2)
641 620 return (mdb_tab_complete_member(mcp, tn, argv[1].a_un.a_str));
642 621
643 622 return (0);
644 623 }
645 624
646 625 /*
647 626 * This is similar to mdb_print.c's args_to_typename, but it has subtle
648 627 * differences surrounding how the strings of one element are handled that have
649 628 * 'struct', 'enum', or 'union' in them and instead works with them for tab
650 629 * completion purposes.
651 630 */
652 631 int
653 632 mdb_tab_typename(int *argcp, const mdb_arg_t **argvp, char *buf, size_t len)
654 633 {
655 634 int argc = *argcp;
656 635 const mdb_arg_t *argv = *argvp;
657 636
658 637 if (argc < 1 || argv->a_type != MDB_TYPE_STRING)
659 638 return (DCMD_USAGE);
660 639
661 640 if (strcmp(argv->a_un.a_str, "struct") == 0 ||
662 641 strcmp(argv->a_un.a_str, "enum") == 0 ||
663 642 strcmp(argv->a_un.a_str, "union") == 0) {
664 643 if (argc == 1) {
665 644 (void) mdb_snprintf(buf, len, "%s ",
666 645 argv[0].a_un.a_str);
667 646 return (1);
668 647 }
669 648
670 649 if (argv[1].a_type != MDB_TYPE_STRING)
671 650 return (DCMD_USAGE);
672 651
673 652 (void) mdb_snprintf(buf, len, "%s %s",
674 653 argv[0].a_un.a_str, argv[1].a_un.a_str);
675 654
676 655 *argcp = argc - 1;
677 656 *argvp = argv + 1;
678 657 } else {
679 658 (void) mdb_snprintf(buf, len, "%s", argv[0].a_un.a_str);
680 659 }
681 660
682 661 return (0);
683 662 }
↓ open down ↓ |
231 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX