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