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