Home | History | Annotate | Line # | Download | only in util
      1 /*	$NetBSD: argv.c,v 1.6 2026/05/09 18:49:22 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	argv 3
      6 /* SUMMARY
      7 /*	string array utilities
      8 /* SYNOPSIS
      9 /*	#include <argv.h>
     10 /*
     11 /*	typedef	int (*ARGV_COMPAR_FN)(const void *, const void *);
     12 /*
     13 /*	ARGV	*argv_alloc(len)
     14 /*	ssize_t	len;
     15 /*
     16 /*	ARGV    *argv_qsort(argvp, compar)
     17 /*	ARGV    *argvp;
     18 /*	ARGV_COMPAR_FN compar;
     19 /*
     20 /*	void	argv_uniq(argvp, compar)
     21 /*	ARGV	*argvp;
     22 /*	ARGV_COMPAR_FN compar;
     23 /*
     24 /*	ARGV	*argv_free(argvp)
     25 /*	ARGV	*argvp;
     26 /*
     27 /*	void	argv_add(argvp, arg, ..., ARGV_END)
     28 /*	ARGV	*argvp;
     29 /*	char	*arg;
     30 /*
     31 /*	void	argv_addn(argvp, arg, arg_len, ..., ARGV_END)
     32 /*	ARGV	*argvp;
     33 /*	char	*arg;
     34 /*	ssize_t	arg_len;
     35 /*
     36 /*	ARGV	*argv_addv(argvp, argv)
     37 /*	ARGV	*argvp;
     38 /*	const char **argv;
     39 /*
     40 /*	void	argv_terminate(argvp);
     41 /*	ARGV	*argvp;
     42 /*
     43 /*	void	argv_truncate(argvp, len);
     44 /*	ARGV	*argvp;
     45 /*	ssize_t	len;
     46 /*
     47 /*	void	argv_insert_one(argvp, pos, arg)
     48 /*	ARGV	*argvp;
     49 /*	ssize_t	pos;
     50 /*	const char *arg;
     51 /*
     52 /*	void	argv_replace_one(argvp, pos, arg)
     53 /*	ARGV	*argvp;
     54 /*	ssize_t	pos;
     55 /*	const char *arg;
     56 /*
     57 /*	void	argv_delete(argvp, pos, how_many)
     58 /*	ARGV	*argvp;
     59 /*	ssize_t	pos;
     60 /*	ssize_t	how_many;
     61 /*
     62 /*	char	*argv_join(buf, argvp, delim)
     63 /*	VSTRING	*buf;
     64 /*	ARGV	*argvp;
     65 /*	int	delim;
     66 /*
     67 /*	void	ARGV_FAKE_BEGIN(argv, arg)
     68 /*	const char *arg;
     69 /*
     70 /*	void	ARGV_FAKE2_BEGIN(argv, arg1, arg2)
     71 /*	const char *arg1;
     72 /*	const char *arg2;
     73 /*
     74 /*	void	ARGV_FAKE_END
     75 /* DESCRIPTION
     76 /*	The functions in this module manipulate arrays of string
     77 /*	pointers. An ARGV structure contains the following members:
     78 /* .IP len
     79 /*	The length of the \fIargv\fR array member.
     80 /* .IP argc
     81 /*	The number of \fIargv\fR elements used.
     82 /* .IP argv
     83 /*	An array of pointers to null-terminated strings.
     84 /* .PP
     85 /*	argv_alloc() returns an empty string array of the requested
     86 /*	length. The result is ready for use by argv_add(). The array
     87 /*	is null terminated.
     88 /*
     89 /*	argv_qsort() sorts the elements of argvp in place, and
     90 /*	returns its first argument. If the compar argument specifies
     91 /*	a null pointer, then argv_qsort() will use byte-by-byte
     92 /*	comparison.
     93 /*
     94 /*	argv_uniq() reduces adjacent same-value elements to one
     95 /*	element, and returns its first argument. If the compar
     96 /*	argument specifies a null pointer, then argv_uniq() will
     97 /*	use byte-by-byte comparison.
     98 /*
     99 /*	argv_add() copies zero or more strings and adds them to the
    100 /*	specified string array. The array is null terminated.
    101 /*	Terminate the argument list with a null pointer. The manifest
    102 /*	constant ARGV_END provides a convenient notation for this.
    103 /*
    104 /*	argv_addn() is like argv_add(), but each string is followed
    105 /*	by a string length argument.
    106 /*
    107 /*	argv_addv() optionally creates an ARGV when the first argument
    108 /*	is a null pointer, and appends a null-terminated list of
    109 /*	strings. The result is null terminated.
    110 /*
    111 /*	argv_free() releases storage for a string array, and conveniently
    112 /*	returns a null pointer.
    113 /*
    114 /*	argv_terminate() null-terminates its string array argument.
    115 /*
    116 /*	argv_truncate() truncates its argument to the specified
    117 /*	number of entries, but does not reallocate memory. The
    118 /*	result is null-terminated.
    119 /*
    120 /*	argv_insert_one() inserts one string at the specified array
    121 /*	position.
    122 /*
    123 /*	argv_replace_one() replaces one string at the specified
    124 /*	position. The old string is destroyed after the update is
    125 /*	made.
    126 /*
    127 /*	argv_delete() deletes the specified number of elements
    128 /*	starting at the specified array position. The result is
    129 /*	null-terminated.
    130 /*
    131 /*	argv_join() joins all elements in an array using the
    132 /*	specified delimiter value, and appends the result to the
    133 /*	specified buffer.
    134 /*
    135 /*	ARGV_FAKE_BEGIN/END are an optimization for the case where
    136 /*	a single string needs to be passed into an ARGV-based
    137 /*	interface.  ARGV_FAKE_BEGIN() opens a statement block and
    138 /*	allocates a stack-based ARGV structure named after the first
    139 /*	argument, that encapsulates the second argument.  This
    140 /*	implementation allocates no heap memory and creates no copy
    141 /*	of the second argument.  ARGV_FAKE_END closes the statement
    142 /*	block and thereby releases storage.
    143 /*
    144 /*	ARGV_FAKE2_BEGIN/END provide the same functionality for a pair
    145 /*	of strings.
    146 /* SEE ALSO
    147 /*	msg(3) diagnostics interface
    148 /* DIAGNOSTICS
    149 /*	Fatal errors: memory allocation problem.
    150 /* LICENSE
    151 /* .ad
    152 /* .fi
    153 /*	The Secure Mailer license must be distributed with this software.
    154 /* AUTHOR(S)
    155 /*	Wietse Venema
    156 /*	IBM T.J. Watson Research
    157 /*	P.O. Box 704
    158 /*	Yorktown Heights, NY 10598, USA
    159 /*
    160 /*	Wietse Venema
    161 /*	Google, Inc.
    162 /*	111 8th Avenue
    163 /*	New York, NY 10011, USA
    164 /*
    165 /*	Wietse Venema
    166 /*	porcupine.org
    167 /*--*/
    168 
    169 /* System libraries. */
    170 
    171 #include <sys_defs.h>
    172 #include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
    173 #include <stdarg.h>
    174 #include <string.h>
    175 
    176 /* Application-specific. */
    177 
    178 #include "mymalloc.h"
    179 #include "msg.h"
    180 #include "vstring.h"
    181 #include "argv.h"
    182 
    183 #ifdef TEST
    184 extern NORETURN PRINTFLIKE(1, 2) test_msg_panic(const char *,...);
    185 
    186 #define msg_panic test_msg_panic
    187 #endif
    188 
    189 /* argv_free - destroy string array */
    190 
    191 ARGV   *argv_free(ARGV *argvp)
    192 {
    193     char  **cpp;
    194 
    195     for (cpp = argvp->argv; cpp < argvp->argv + argvp->argc; cpp++)
    196 	myfree(*cpp);
    197     myfree((void *) argvp->argv);
    198     myfree((void *) argvp);
    199     return (0);
    200 }
    201 
    202 /* argv_alloc - initialize string array */
    203 
    204 ARGV   *argv_alloc(ssize_t len)
    205 {
    206     ARGV   *argvp;
    207     ssize_t sane_len;
    208 
    209     /*
    210      * Make sure that always argvp->argc < argvp->len.
    211      */
    212     argvp = (ARGV *) mymalloc(sizeof(*argvp));
    213     argvp->len = 0;
    214     sane_len = (len < 2 ? 2 : len);
    215     /* 202604 Claude: avoid overflowing sane_len + 1 */
    216     if (sane_len > SSIZE_MAX - 1)
    217 	msg_panic("argv_alloc: array length overflow");
    218     argvp->argv = (char **) mymalloc((sane_len + 1) * sizeof(char *));
    219     argvp->len = sane_len;
    220     argvp->argc = 0;
    221     argvp->argv[0] = 0;
    222     return (argvp);
    223 }
    224 
    225 static int argv_cmp(const void *e1, const void *e2)
    226 {
    227     const char *s1 = *(const char **) e1;
    228     const char *s2 = *(const char **) e2;
    229 
    230     return strcmp(s1, s2);
    231 }
    232 
    233 /* argv_qsort - sort array in place */
    234 
    235 ARGV   *argv_qsort(ARGV *argvp, ARGV_COMPAR_FN compar)
    236 {
    237     qsort(argvp->argv, argvp->argc, sizeof(argvp->argv[0]),
    238 	  compar ? compar : argv_cmp);
    239     return (argvp);
    240 }
    241 
    242 /* argv_sort - binary compatibility */
    243 
    244 ARGV   *argv_sort(ARGV *argvp)
    245 {
    246     qsort(argvp->argv, argvp->argc, sizeof(argvp->argv[0]), argv_cmp);
    247     return (argvp);
    248 }
    249 
    250 /* argv_uniq - deduplicate adjacent array elements */
    251 
    252 ARGV   *argv_uniq(ARGV *argvp, ARGV_COMPAR_FN compar)
    253 {
    254     char  **cpp;
    255     char  **prev;
    256 
    257     if (compar == 0)
    258 	compar = argv_cmp;
    259     for (prev = 0, cpp = argvp->argv; cpp < argvp->argv + argvp->argc; cpp++) {
    260 	if (prev != 0 && compar(prev, cpp) == 0) {
    261 	    argv_delete(argvp, cpp - argvp->argv, 1);
    262 	    cpp = prev;
    263 	} else {
    264 	    prev = cpp;
    265 	}
    266     }
    267     return (argvp);
    268 }
    269 
    270 /* argv_extend - extend array */
    271 
    272 static void argv_extend(ARGV *argvp)
    273 {
    274     ssize_t new_len;
    275 
    276     /* 202604 Claude: avoid overflowing (new_len + 1) * sizeof(char *).  */
    277     if (argvp->len > SSIZE_MAX / (2 * sizeof(char *)) - 1)
    278 	msg_panic("argv_extend: array length overflow");
    279     new_len = argvp->len * 2;
    280     argvp->argv = (char **)
    281 	myrealloc((void *) argvp->argv, (new_len + 1) * sizeof(char *));
    282     argvp->len = new_len;
    283 }
    284 
    285 /* argv_add - add string to vector */
    286 
    287 void    argv_add(ARGV *argvp,...)
    288 {
    289     char   *arg;
    290     va_list ap;
    291 
    292     /*
    293      * Make sure that always argvp->argc < argvp->len.
    294      */
    295 #define ARGV_SPACE_LEFT(a) ((a)->len - (a)->argc - 1)
    296 
    297     va_start(ap, argvp);
    298     while ((arg = va_arg(ap, char *)) != 0) {
    299 	if (ARGV_SPACE_LEFT(argvp) <= 0)
    300 	    argv_extend(argvp);
    301 	argvp->argv[argvp->argc++] = mystrdup(arg);
    302     }
    303     va_end(ap);
    304     argvp->argv[argvp->argc] = 0;
    305 }
    306 
    307 /* argv_addn - add string to vector */
    308 
    309 void    argv_addn(ARGV *argvp,...)
    310 {
    311     char   *arg;
    312     ssize_t len;
    313     va_list ap;
    314 
    315     /*
    316      * Make sure that always argvp->argc < argvp->len.
    317      */
    318     va_start(ap, argvp);
    319     while ((arg = va_arg(ap, char *)) != 0) {
    320 	if ((len = va_arg(ap, ssize_t)) < 0)
    321 	    msg_panic("argv_addn: bad string length %ld", (long) len);
    322 	if (ARGV_SPACE_LEFT(argvp) <= 0)
    323 	    argv_extend(argvp);
    324 	argvp->argv[argvp->argc++] = mystrndup(arg, len);
    325     }
    326     va_end(ap);
    327     argvp->argv[argvp->argc] = 0;
    328 }
    329 
    330 /* argv_addv - optionally create ARGV, append string vector */
    331 
    332 ARGV   *argv_addv(ARGV *argvp, const char *const * argv)
    333 {
    334     const char *const * cpp;
    335 
    336     if (argvp == 0) {
    337 	for (cpp = argv; *cpp; cpp++)
    338 	     /* void */ ;
    339 	argvp = argv_alloc(cpp - argv);
    340     }
    341     for (cpp = argv; *cpp; cpp++)
    342 	argv_add(argvp, *cpp, (char *) 0);
    343     argvp->argv[argvp->argc] = 0;
    344     return (argvp);
    345 }
    346 
    347 /* argv_terminate - terminate string array */
    348 
    349 void    argv_terminate(ARGV *argvp)
    350 {
    351 
    352     /*
    353      * Trust that argvp->argc < argvp->len.
    354      */
    355     argvp->argv[argvp->argc] = 0;
    356 }
    357 
    358 /* argv_truncate - truncate string array */
    359 
    360 void    argv_truncate(ARGV *argvp, ssize_t len)
    361 {
    362     char  **cpp;
    363 
    364     /*
    365      * Sanity check.
    366      */
    367     if (len < 0)
    368 	msg_panic("argv_truncate: bad length %ld", (long) len);
    369 
    370     if (len < argvp->argc) {
    371 	for (cpp = argvp->argv + len; cpp < argvp->argv + argvp->argc; cpp++)
    372 	    myfree(*cpp);
    373 	argvp->argc = len;
    374 	argvp->argv[argvp->argc] = 0;
    375     }
    376 }
    377 
    378 /* argv_insert_one - insert one string into array */
    379 
    380 void    argv_insert_one(ARGV *argvp, ssize_t where, const char *arg)
    381 {
    382     ssize_t pos;
    383 
    384     /*
    385      * Sanity check.
    386      */
    387     if (where < 0 || where > argvp->argc)
    388 	msg_panic("argv_insert_one bad position: %ld", (long) where);
    389 
    390     if (ARGV_SPACE_LEFT(argvp) <= 0)
    391 	argv_extend(argvp);
    392     for (pos = argvp->argc; pos >= where; pos--)
    393 	argvp->argv[pos + 1] = argvp->argv[pos];
    394     argvp->argv[where] = mystrdup(arg);
    395     argvp->argc += 1;
    396 }
    397 
    398 /* argv_replace_one - replace one string in array */
    399 
    400 void    argv_replace_one(ARGV *argvp, ssize_t where, const char *arg)
    401 {
    402     char   *temp;
    403 
    404     /*
    405      * Sanity check.
    406      */
    407     if (where < 0 || where >= argvp->argc)
    408 	msg_panic("argv_replace_one bad position: %ld", (long) where);
    409 
    410     temp = argvp->argv[where];
    411     argvp->argv[where] = mystrdup(arg);
    412     myfree(temp);
    413 }
    414 
    415 /* argv_delete - remove string(s) from array */
    416 
    417 void    argv_delete(ARGV *argvp, ssize_t first, ssize_t how_many)
    418 {
    419     ssize_t pos;
    420 
    421     /*
    422      * Sanity check. 202604 Claude: avoid expression 'first + how_many'.
    423      */
    424     if (first < 0 || how_many < 0 || first > argvp->argc
    425 	|| how_many > argvp->argc - first)
    426 	msg_panic("argv_delete bad range: (start=%ld count=%ld)",
    427 		  (long) first, (long) how_many);
    428 
    429     for (pos = first; pos < first + how_many; pos++)
    430 	myfree(argvp->argv[pos]);
    431     for (pos = first; pos <= argvp->argc - how_many; pos++)
    432 	argvp->argv[pos] = argvp->argv[pos + how_many];
    433     argvp->argc -= how_many;
    434 }
    435 
    436 /* argv_join - concatenate array elements with delimiter */
    437 
    438 char   *argv_join(VSTRING *buf, ARGV *argv, int delim)
    439 {
    440     char  **cpp;
    441 
    442     for (cpp = argv->argv; *cpp; cpp++) {
    443 	vstring_strcat(buf, *cpp);
    444 	if (cpp[1])
    445 	    VSTRING_ADDCH(buf, delim);
    446     }
    447     return (vstring_str(buf));
    448 }
    449 
    450 #ifdef TEST
    451 
    452  /*
    453   * System library.
    454   */
    455 #include <setjmp.h>
    456 
    457  /*
    458   * Utility library.
    459   */
    460 #include <msg_vstream.h>
    461 #include <stringops.h>
    462 
    463 #define ARRAY_LEN	(10)
    464 
    465 typedef struct TEST_CASE {
    466     const char *label;			/* identifies test case */
    467     const char *inputs[ARRAY_LEN];	/* input strings */
    468     int     terminate;			/* terminate result */
    469     ARGV   *(*populate_fn) (const struct TEST_CASE *, ARGV *);
    470     const char *exp_panic_msg;		/* expected panic */
    471     int     exp_argc;			/* expected array length */
    472     const char *exp_argv[ARRAY_LEN];	/* expected array content */
    473     int     join_delim;			/* argv_join() delimiter */
    474 } TEST_CASE;
    475 
    476 #define TERMINATE_ARRAY	(1)
    477 
    478 #define	PASS	(0)
    479 #define FAIL	(1)
    480 
    481 VSTRING *test_panic_str;
    482 jmp_buf test_panic_jbuf;
    483 
    484 /* test_msg_panic - does not return, and does not terminate */
    485 
    486 void    test_msg_panic(const char *fmt,...)
    487 {
    488     va_list ap;
    489 
    490     va_start(ap, fmt);
    491     test_panic_str = vstring_alloc(100);
    492     vstring_vsprintf(test_panic_str, fmt, ap);
    493     va_end(ap);
    494     longjmp(test_panic_jbuf, 1);
    495 }
    496 
    497 /* test_argv_populate - populate result, optionally terminate */
    498 
    499 static ARGV *test_argv_populate(const TEST_CASE *tp, ARGV *argvp)
    500 {
    501     const char *const * cpp;
    502 
    503     for (cpp = tp->inputs; *cpp; cpp++)
    504 	argv_add(argvp, *cpp, (char *) 0);
    505     if (tp->terminate)
    506 	argv_terminate(argvp);
    507     return (argvp);
    508 }
    509 
    510 /* test_argv_sort - populate and sort result */
    511 
    512 static ARGV *test_argv_sort(const TEST_CASE *tp, ARGV *argvp)
    513 {
    514     test_argv_populate(tp, argvp);
    515     argv_qsort(argvp, (ARGV_COMPAR_FN) 0);
    516     return (argvp);
    517 }
    518 
    519 /* test_argv_sort_uniq - populate, sort, uniq result */
    520 
    521 static ARGV *test_argv_sort_uniq(const TEST_CASE *tp, ARGV *argvp)
    522 {
    523 
    524     /*
    525      * This also tests argv_delete().
    526      */
    527     test_argv_sort(tp, argvp);
    528     argv_uniq(argvp, (ARGV_COMPAR_FN) 0);
    529     return (argvp);
    530 }
    531 
    532 /* test_argv_good_truncate - populate and truncate to good size */
    533 
    534 static ARGV *test_argv_good_truncate(const TEST_CASE *tp, ARGV *argvp)
    535 {
    536     test_argv_populate(tp, argvp);
    537     argv_truncate(argvp, tp->exp_argc);
    538     return (argvp);
    539 }
    540 
    541 /* test_argv_bad_truncate - populate and truncate to bad size */
    542 
    543 static ARGV *test_argv_bad_truncate(const TEST_CASE *tp, ARGV *argvp)
    544 {
    545     test_argv_populate(tp, argvp);
    546     argv_truncate(argvp, -1);
    547     return (argvp);
    548 }
    549 
    550 /* test_argv_good_insert - populate and insert at good position */
    551 
    552 static ARGV *test_argv_good_insert(const TEST_CASE *tp, ARGV *argvp)
    553 {
    554     test_argv_populate(tp, argvp);
    555     argv_insert_one(argvp, 1, "new");
    556     return (argvp);
    557 }
    558 
    559 /* test_argv_bad_insert1 - populate and insert at bad position */
    560 
    561 static ARGV *test_argv_bad_insert1(const TEST_CASE *tp, ARGV *argvp)
    562 {
    563     test_argv_populate(tp, argvp);
    564     argv_insert_one(argvp, -1, "new");
    565     return (argvp);
    566 }
    567 
    568 /* test_argv_bad_insert2 - populate and insert at bad position */
    569 
    570 static ARGV *test_argv_bad_insert2(const TEST_CASE *tp, ARGV *argvp)
    571 {
    572     test_argv_populate(tp, argvp);
    573     argv_insert_one(argvp, 100, "new");
    574     return (argvp);
    575 }
    576 
    577 /* test_argv_good_replace - populate and replace at good position */
    578 
    579 static ARGV *test_argv_good_replace(const TEST_CASE *tp, ARGV *argvp)
    580 {
    581     test_argv_populate(tp, argvp);
    582     argv_replace_one(argvp, 1, "new");
    583     return (argvp);
    584 }
    585 
    586 /* test_argv_bad_replace1 - populate and replace at bad position */
    587 
    588 static ARGV *test_argv_bad_replace1(const TEST_CASE *tp, ARGV *argvp)
    589 {
    590     test_argv_populate(tp, argvp);
    591     argv_replace_one(argvp, -1, "new");
    592     return (argvp);
    593 }
    594 
    595 /* test_argv_bad_replace2 - populate and replace at bad position */
    596 
    597 static ARGV *test_argv_bad_replace2(const TEST_CASE *tp, ARGV *argvp)
    598 {
    599     test_argv_populate(tp, argvp);
    600     argv_replace_one(argvp, 100, "new");
    601     return (argvp);
    602 }
    603 
    604 /* test_argv_bad_delete1 - populate and delete at bad position */
    605 
    606 static ARGV *test_argv_bad_delete1(const TEST_CASE *tp, ARGV *argvp)
    607 {
    608     test_argv_populate(tp, argvp);
    609     argv_delete(argvp, -1, 1);
    610     return (argvp);
    611 }
    612 
    613 /* test_argv_bad_delete2 - populate and delete at bad position */
    614 
    615 static ARGV *test_argv_bad_delete2(const TEST_CASE *tp, ARGV *argvp)
    616 {
    617     test_argv_populate(tp, argvp);
    618     argv_delete(argvp, 0, -1);
    619     return (argvp);
    620 }
    621 
    622 /* test_argv_bad_delete3 - populate and delete at bad position */
    623 
    624 static ARGV *test_argv_bad_delete3(const TEST_CASE *tp, ARGV *argvp)
    625 {
    626     test_argv_populate(tp, argvp);
    627     argv_delete(argvp, 100, 1);
    628     return (argvp);
    629 }
    630 
    631 /* test_argv_join - populate, join, and overwrite */
    632 
    633 static ARGV *test_argv_join(const TEST_CASE *tp, ARGV *argvp)
    634 {
    635     VSTRING *buf = vstring_alloc(100);
    636 
    637     /*
    638      * Impedance mismatch: argv_join() produces output to VSTRING, but the
    639      * test fixture wants output to ARGV.
    640      */
    641     test_argv_populate(tp, argvp);
    642     argv_join(buf, argvp, tp->join_delim);
    643     argv_delete(argvp, 0, argvp->argc);
    644     argv_add(argvp, vstring_str(buf), ARGV_END);
    645     vstring_free(buf);
    646     return (argvp);
    647 }
    648 
    649 /* test_argv_addv_appends - populate result */
    650 
    651 static ARGV *test_argv_addv_appends(const TEST_CASE *tp, ARGV *argvp)
    652 {
    653     argvp = argv_addv(argvp, tp->inputs);
    654     return (argvp);
    655 }
    656 
    657 /* test_argv_addv_creates_appends - populate result */
    658 
    659 static ARGV *test_argv_addv_creates(const TEST_CASE *tp, ARGV *argvp)
    660 {
    661     argv_free(argvp);
    662     argvp = argv_addv((ARGV *) 0, tp->inputs);
    663     return (argvp);
    664 }
    665 
    666 /* test_argv_verify - verify result */
    667 
    668 static int test_argv_verify(const TEST_CASE *tp, ARGV *argvp)
    669 {
    670     int     idx;
    671 
    672     if (tp->exp_panic_msg != 0) {
    673 	if (test_panic_str == 0) {
    674 	    msg_warn("test case '%s': got no panic, want: '%s'",
    675 		     tp->label, tp->exp_panic_msg);
    676 	    return (FAIL);
    677 	}
    678 	if (strcmp(vstring_str(test_panic_str), tp->exp_panic_msg) != 0) {
    679 	    msg_warn("test case '%s': got '%s', want: '%s'",
    680 		 tp->label, vstring_str(test_panic_str), tp->exp_panic_msg);
    681 	    return (FAIL);
    682 	}
    683 	return (PASS);
    684     }
    685     if (test_panic_str != 0) {
    686 	msg_warn("test case '%s': got '%s', want: no panic",
    687 		 tp->label, vstring_str(test_panic_str));
    688 	return (FAIL);
    689     }
    690     if (argvp->argc != tp->exp_argc) {
    691 	msg_warn("test case '%s': got argc: %ld, want: %d",
    692 		 tp->label, (long) argvp->argc, tp->exp_argc);
    693 	return (FAIL);
    694     }
    695     if (argvp->argv[argvp->argc] != 0 && tp->terminate) {
    696 	msg_warn("test case '%s': got unterminated, want: terminated", tp->label);
    697 	return (FAIL);
    698     }
    699     for (idx = 0; idx < argvp->argc; idx++) {
    700 	if (strcmp(argvp->argv[idx], tp->exp_argv[idx]) != 0) {
    701 	    msg_warn("test case '%s': index %d: got '%s', want: '%s'",
    702 		     tp->label, idx, argvp->argv[idx], tp->exp_argv[idx]);
    703 	    return (FAIL);
    704 	}
    705     }
    706     return (PASS);
    707 }
    708 
    709  /*
    710   * The test cases. TODO: argv_addn with good and bad string length.
    711   */
    712 static const TEST_CASE test_cases[] = {
    713     {"multiple strings, unterminated array",
    714 	{"foo", "baz", "bar", 0}, 0, test_argv_populate,
    715 	0, 3, {"foo", "baz", "bar", 0}
    716     },
    717     {"multiple strings, terminated array",
    718 	{"foo", "baz", "bar", 0}, TERMINATE_ARRAY, test_argv_populate,
    719 	0, 3, {"foo", "baz", "bar", 0}
    720     },
    721     {"distinct strings, sorted array",
    722 	{"foo", "baz", "bar", 0}, 0, test_argv_sort,
    723 	0, 3, {"bar", "baz", "foo", 0}
    724     },
    725     {"duplicate strings, sorted array",
    726 	{"foo", "baz", "baz", "bar", 0}, 0, test_argv_sort,
    727 	0, 4, {"bar", "baz", "baz", "foo", 0}
    728     },
    729     {"duplicate strings, sorted, uniqued-middle elements",
    730 	{"foo", "baz", "baz", "bar", 0}, 0, test_argv_sort_uniq,
    731 	0, 3, {"bar", "baz", "foo", 0}
    732     },
    733     {"duplicate strings, sorted, uniqued-first elements",
    734 	{"foo", "bar", "baz", "bar", 0}, 0, test_argv_sort_uniq,
    735 	0, 3, {"bar", "baz", "foo", 0}
    736     },
    737     {"duplicate strings, sorted, uniqued-last elements",
    738 	{"foo", "foo", "baz", "bar", 0}, 0, test_argv_sort_uniq,
    739 	0, 3, {"bar", "baz", "foo", 0}
    740     },
    741     {"multiple strings, truncate array by one",
    742 	{"foo", "baz", "bar", 0}, 0, test_argv_good_truncate,
    743 	0, 2, {"foo", "baz", 0}
    744     },
    745     {"multiple strings, truncate whole array",
    746 	{"foo", "baz", "bar", 0}, 0, test_argv_good_truncate,
    747 	0, 0, {"foo", "baz", 0}
    748     },
    749     {"multiple strings, bad truncate",
    750 	{"foo", "baz", "bar", 0}, 0, test_argv_bad_truncate,
    751 	"argv_truncate: bad length -1"
    752     },
    753     {"multiple strings, insert one at good position",
    754 	{"foo", "baz", "bar", 0}, 0, test_argv_good_insert,
    755 	0, 4, {"foo", "new", "baz", "bar", 0}
    756     },
    757     {"multiple strings, insert one at bad position",
    758 	{"foo", "baz", "bar", 0}, 0, test_argv_bad_insert1,
    759 	"argv_insert_one bad position: -1"
    760     },
    761     {"multiple strings, insert one at bad position",
    762 	{"foo", "baz", "bar", 0}, 0, test_argv_bad_insert2,
    763 	"argv_insert_one bad position: 100"
    764     },
    765     {"multiple strings, replace one at good position",
    766 	{"foo", "baz", "bar", 0}, 0, test_argv_good_replace,
    767 	0, 3, {"foo", "new", "bar", 0}
    768     },
    769     {"multiple strings, replace one at bad position",
    770 	{"foo", "baz", "bar", 0}, 0, test_argv_bad_replace1,
    771 	"argv_replace_one bad position: -1"
    772     },
    773     {"multiple strings, replace one at bad position",
    774 	{"foo", "baz", "bar", 0}, 0, test_argv_bad_replace2,
    775 	"argv_replace_one bad position: 100"
    776     },
    777     {"multiple strings, delete one at negative position",
    778 	{"foo", "baz", "bar", 0}, 0, test_argv_bad_delete1,
    779 	"argv_delete bad range: (start=-1 count=1)"
    780     },
    781     {"multiple strings, delete with bad range end",
    782 	{"foo", "baz", "bar", 0}, 0, test_argv_bad_delete2,
    783 	"argv_delete bad range: (start=0 count=-1)"
    784     },
    785     {"multiple strings, delete at too large position",
    786 	{"foo", "baz", "bar", 0}, 0, test_argv_bad_delete3,
    787 	"argv_delete bad range: (start=100 count=1)"
    788     },
    789     {"argv_join, multiple strings",
    790 	{"foo", "baz", "bar", 0}, 0, test_argv_join,
    791 	0, 1, {"foo:baz:bar", 0}, ':'
    792     },
    793     {"argv_join, one string",
    794 	{"foo", 0}, 0, test_argv_join,
    795 	0, 1, {"foo", 0}, ':'
    796     },
    797     {"argv_join, empty",
    798 	{0}, 0, test_argv_join,
    799 	0, 1, {"", 0}, ':'
    800     },
    801     {"argv_addv appends to ARGV",
    802 	{"foo", "baz", "bar", 0}, /* ignored */ 0, test_argv_addv_appends,
    803 	0, 3, {"foo", "baz", "bar", 0}
    804     },
    805     {"argv_addv creates ARGV",
    806 	{"foo", "baz", "bar", 0}, /* ignored */ 0, test_argv_addv_creates,
    807 	0, 3, {"foo", "baz", "bar", 0}
    808     },
    809     0,
    810 };
    811 
    812 int     main(int argc, char **argv)
    813 {
    814     const TEST_CASE *tp;
    815     int     pass = 0;
    816     int     fail = 0;
    817 
    818     msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
    819 
    820     for (tp = test_cases; tp->label != 0; tp++) {
    821 	int     test_failed;
    822 	ARGV   *argvp;
    823 
    824 	argvp = argv_alloc(1);
    825 	if (setjmp(test_panic_jbuf) == 0)
    826 	    argvp = tp->populate_fn(tp, argvp);
    827 	test_failed = test_argv_verify(tp, argvp);
    828 	if (test_failed) {
    829 	    msg_info("%s: FAIL", tp->label);
    830 	    fail++;
    831 	} else {
    832 	    msg_info("%s: PASS", tp->label);
    833 	    pass++;
    834 	}
    835 	argv_free(argvp);
    836 	if (test_panic_str) {
    837 	    vstring_free(test_panic_str);
    838 	    test_panic_str = 0;
    839 	}
    840     }
    841     msg_info("PASS=%d FAIL=%d", pass, fail);
    842     exit(fail != 0);
    843 }
    844 
    845 #endif
    846