Print this page
patch mdb_var_alloc
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
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 24 */
25 25 /*
26 26 * This file contains all of the interfaces for mdb's tab completion engine.
27 27 * Currently some interfaces are private to mdb and its internal implementation,
28 28 * those are in mdb_tab.h. Other pieces are public interfaces. Those are in
29 29 * mdb_modapi.h.
30 30 *
31 31 * Memory allocations in tab completion context have to be done very carefully.
32 32 * We need to think of ourselves as the same as any other command that is being
33 33 * executed by the user, which means we must use UM_GC to handle being
34 34 * interrupted.
35 35 */
36 36
37 37 #include <mdb/mdb_modapi.h>
38 38 #include <mdb/mdb_ctf.h>
39 39 #include <mdb/mdb_ctf_impl.h>
40 40 #include <mdb/mdb_string.h>
41 41 #include <mdb/mdb_module.h>
42 42 #include <mdb/mdb_debug.h>
43 43 #include <mdb/mdb_print.h>
44 44 #include <mdb/mdb_nv.h>
45 45 #include <mdb/mdb_tab.h>
46 46 #include <mdb/mdb_target.h>
47 47 #include <mdb/mdb.h>
48 48
49 49 #include <ctype.h>
50 50
51 51 /*
52 52 * There may be another way to do this, but this works well enough.
53 53 */
54 54 #define COMMAND_SEPARATOR "::"
55 55
56 56 /*
57 57 * find_command_start --
58 58 *
59 59 * Given a buffer find the start of the last command.
60 60 */
61 61 static char *
62 62 tab_find_command_start(char *buf)
63 63 {
64 64 char *offset = strstr(buf, COMMAND_SEPARATOR);
65 65
66 66 if (offset == NULL)
67 67 return (NULL);
68 68
69 69 for (;;) {
70 70 char *next = strstr(offset + strlen(COMMAND_SEPARATOR),
71 71 COMMAND_SEPARATOR);
72 72
73 73 if (next == NULL) {
74 74 return (offset);
75 75 }
76 76
77 77 offset = next;
78 78 }
79 79 }
80 80
81 81 /*
82 82 * get_dcmd --
83 83 *
84 84 * Given a buffer containing a command and its argument return
85 85 * the name of the command and the offset in the buffer where
86 86 * the command arguments start.
87 87 *
88 88 * Note: This will modify the buffer.
89 89 */
90 90 char *
91 91 tab_get_dcmd(char *buf, char **args, uint_t *flags)
92 92 {
93 93 char *start = buf + strlen(COMMAND_SEPARATOR);
94 94 char *separator = start;
95 95 const char *end = buf + strlen(buf);
96 96 uint_t space = 0;
97 97
98 98 while (separator < end && !isspace(*separator))
99 99 separator++;
100 100
101 101 if (separator == end) {
102 102 *args = NULL;
103 103 } else {
104 104 if (isspace(*separator))
105 105 space = 1;
106 106
107 107 *separator++ = '\0';
108 108 *args = separator;
109 109 }
110 110
111 111 if (space)
112 112 *flags |= DCMD_TAB_SPACE;
113 113
114 114 return (start);
115 115 }
116 116
117 117 /*
118 118 * count_args --
119 119 *
120 120 * Given a buffer containing dmcd arguments return the total number
121 121 * of arguments.
122 122 *
123 123 * While parsing arguments we need to keep track of whether or not the last
124 124 * arguments ends with a trailing space.
125 125 */
126 126 static int
127 127 tab_count_args(const char *input, uint_t *flags)
128 128 {
129 129 const char *index;
130 130 int argc = 0;
131 131 uint_t space = *flags & DCMD_TAB_SPACE;
132 132 index = input;
133 133
134 134 while (*index != '\0') {
135 135 while (*index != '\0' && isspace(*index)) {
136 136 index++;
137 137 space = 1;
138 138 }
139 139
140 140 if (*index != '\0' && !isspace(*index)) {
141 141 argc++;
142 142 space = 0;
143 143 while (*index != '\0' && !isspace (*index)) {
144 144 index++;
145 145 }
146 146 }
147 147 }
148 148
149 149 if (space)
150 150 *flags |= DCMD_TAB_SPACE;
151 151 else
152 152 *flags &= ~DCMD_TAB_SPACE;
153 153
154 154 return (argc);
155 155 }
156 156
157 157 /*
158 158 * copy_args --
159 159 *
160 160 * Given a buffer containing dcmd arguments and an array of mdb_arg_t's
161 161 * initialize the string value of each mdb_arg_t.
162 162 *
163 163 * Note: This will modify the buffer.
164 164 */
165 165 static int
166 166 tab_copy_args(char *input, int argc, mdb_arg_t *argv)
167 167 {
168 168 int i = 0;
169 169 char *index;
170 170
171 171 index = input;
172 172
173 173 while (*index) {
174 174 while (*index && isspace(*index)) {
175 175 index++;
176 176 }
177 177
178 178 if (*index && !isspace(*index)) {
179 179 char *end = index;
180 180
181 181 while (*end && !isspace(*end)) {
182 182 end++;
183 183 }
184 184
185 185 if (*end) {
186 186 *end++ = '\0';
187 187 }
188 188
189 189 argv[i].a_type = MDB_TYPE_STRING;
190 190 argv[i].a_un.a_str = index;
191 191
192 192 index = end;
193 193 i++;
194 194 }
195 195 }
196 196
197 197 if (i != argc)
198 198 return (-1);
199 199
200 200 return (0);
201 201 }
202 202
203 203 /*
204 204 * parse-buf --
205 205 *
206 206 * Parse the given buffer and return the specified dcmd, the number
207 207 * of arguments, and array of mdb_arg_t containing the argument
208 208 * values.
209 209 *
210 210 * Note: this will modify the specified buffer. Caller is responisble
211 211 * for freeing argvp.
212 212 */
213 213 static int
214 214 tab_parse_buf(char *buf, char **dcmdp, int *argcp, mdb_arg_t **argvp,
215 215 uint_t *flags)
216 216 {
217 217 char *data = tab_find_command_start(buf);
218 218 char *args_data = NULL;
219 219 char *dcmd = NULL;
220 220 int argc = 0;
221 221 mdb_arg_t *argv = NULL;
222 222
223 223 if (data == NULL) {
224 224 return (-1);
225 225 }
226 226
227 227 dcmd = tab_get_dcmd(data, &args_data, flags);
228 228
229 229 if (dcmd == NULL) {
230 230 return (-1);
231 231 }
232 232
233 233 if (args_data != NULL) {
234 234 argc = tab_count_args(args_data, flags);
235 235
236 236 if (argc != 0) {
237 237 argv = mdb_alloc(sizeof (mdb_arg_t) * argc,
238 238 UM_SLEEP | UM_GC);
239 239
240 240 if (tab_copy_args(args_data, argc, argv) == -1)
241 241 return (-1);
242 242 }
243 243 }
244 244
245 245 *dcmdp = dcmd;
246 246 *argcp = argc;
247 247 *argvp = argv;
248 248
249 249 return (0);
250 250 }
251 251
252 252 /*
253 253 * tab_command --
254 254 *
255 255 * This function is executed anytime a tab is entered. It checks
256 256 * the current buffer to determine if there is a valid dmcd,
257 257 * if that dcmd has a tab completion handler it will invoke it.
258 258 *
259 259 * This function returns the string (if any) that should be added to the
260 260 * existing buffer to complete it.
261 261 */
262 262 int
263 263 mdb_tab_command(mdb_tab_cookie_t *mcp, const char *buf)
264 264 {
265 265 char *data;
266 266 char *dcmd = NULL;
267 267 int argc = 0;
268 268 mdb_arg_t *argv = NULL;
269 269 int ret = 0;
270 270 mdb_idcmd_t *cp;
271 271 uint_t flags = 0;
272 272
273 273 /*
274 274 * Parsing the command and arguments will modify the buffer
275 275 * (replacing spaces with \0), so make a copy of the specified
276 276 * buffer first.
277 277 */
278 278 data = mdb_alloc(strlen(buf) + 1, UM_SLEEP | UM_GC);
279 279 (void) strcpy(data, buf);
280 280
281 281 /*
282 282 * Get the specified dcmd and arguments from the buffer.
283 283 */
284 284 ret = tab_parse_buf(data, &dcmd, &argc, &argv, &flags);
285 285
286 286 /*
287 287 * Match against global symbols if the input is not a dcmd
288 288 */
289 289 if (ret != 0) {
290 290 (void) mdb_tab_complete_global(mcp, buf);
291 291 goto out;
292 292 }
293 293
294 294 /*
295 295 * Check to see if the buffer contains a valid dcmd
296 296 */
297 297 cp = mdb_dcmd_lookup(dcmd);
298 298
299 299 /*
300 300 * When argc is zero it indicates that we are trying to tab complete
301 301 * a dcmd or a global symbol. Note, that if there isn't the start of
302 302 * a dcmd, i.e. ::, then we will have already bailed in the call to
303 303 * tab_parse_buf.
304 304 */
305 305 if (cp == NULL && argc != 0) {
306 306 goto out;
307 307 }
308 308
309 309 /*
310 310 * Invoke the command specific tab completion handler or the built in
311 311 * dcmd one if there is no dcmd.
312 312 */
313 313 if (cp == NULL)
314 314 (void) mdb_tab_complete_dcmd(mcp, dcmd);
315 315 else
316 316 mdb_call_tab(cp, mcp, flags, argc, argv);
317 317
318 318 out:
319 319 return (mdb_tab_size(mcp));
320 320 }
321 321
322 322 static int
323 323 tab_complete_dcmd(mdb_var_t *v, void *arg)
324 324 {
325 325 mdb_idcmd_t *idcp = mdb_nv_get_cookie(mdb_nv_get_cookie(v));
326 326 mdb_tab_cookie_t *mcp = (mdb_tab_cookie_t *)arg;
327 327
328 328 /*
329 329 * The way that mdb is implemented, even commands like $C will show up
330 330 * here. As such, we don't want to match anything that doesn't start
331 331 * with an alpha or number. While nothing currently appears (via a
332 332 * cursory search with mdb -k) to start with a capital letter or a
333 333 * number, we'll support them anyways.
334 334 */
335 335 if (!isalnum(idcp->idc_name[0]))
336 336 return (0);
337 337
338 338 mdb_tab_insert(mcp, idcp->idc_name);
339 339 return (0);
340 340 }
341 341
342 342 int
343 343 mdb_tab_complete_dcmd(mdb_tab_cookie_t *mcp, const char *dcmd)
344 344 {
345 345 mdb_tab_setmbase(mcp, dcmd);
346 346 mdb_nv_sort_iter(&mdb.m_dcmds, tab_complete_dcmd, mcp,
347 347 UM_GC | UM_SLEEP);
348 348 return (0);
349 349 }
350 350
351 351 static int
352 352 tab_complete_walker(mdb_var_t *v, void *arg)
353 353 {
354 354 mdb_iwalker_t *iwp = mdb_nv_get_cookie(mdb_nv_get_cookie(v));
355 355 mdb_tab_cookie_t *mcp = arg;
356 356
357 357 mdb_tab_insert(mcp, iwp->iwlk_name);
358 358 return (0);
359 359 }
360 360
361 361 int
362 362 mdb_tab_complete_walker(mdb_tab_cookie_t *mcp, const char *walker)
363 363 {
364 364 if (walker != NULL)
365 365 mdb_tab_setmbase(mcp, walker);
366 366 mdb_nv_sort_iter(&mdb.m_walkers, tab_complete_walker, mcp,
367 367 UM_GC | UM_SLEEP);
368 368
369 369 return (0);
370 370 }
371 371
372 372 mdb_tab_cookie_t *
373 373 mdb_tab_init(void)
374 374 {
375 375 mdb_tab_cookie_t *mcp;
376 376
377 377 mcp = mdb_zalloc(sizeof (mdb_tab_cookie_t), UM_SLEEP | UM_GC);
378 378 (void) mdb_nv_create(&mcp->mtc_nv, UM_SLEEP | UM_GC);
379 379
380 380 return (mcp);
381 381 }
382 382
383 383 size_t
384 384 mdb_tab_size(mdb_tab_cookie_t *mcp)
385 385 {
386 386 return (mdb_nv_size(&mcp->mtc_nv));
↓ open down ↓ |
386 lines elided |
↑ open up ↑ |
387 387 }
388 388
389 389 /*
390 390 * Determine whether the specified name is a valid tab completion for
391 391 * the given command. If the name is a valid tab completion then
392 392 * it will be saved in the mdb_tab_cookie_t.
393 393 */
394 394 void
395 395 mdb_tab_insert(mdb_tab_cookie_t *mcp, const char *name)
396 396 {
397 - size_t len, matches, index;
398 - uint_t flags;
397 + size_t matches, index;
399 398 mdb_var_t *v;
400 399 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