summaryrefslogtreecommitdiffstats
path: root/src/util/argv.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/util/argv.c721
1 files changed, 721 insertions, 0 deletions
diff --git a/src/util/argv.c b/src/util/argv.c
new file mode 100644
index 0000000..4e05fd0
--- /dev/null
+++ b/src/util/argv.c
@@ -0,0 +1,721 @@
+/*++
+/* NAME
+/* argv 3
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include <argv.h>
+/*
+/* typedef int (*ARGV_COMPAR_FN)(const void *, const void *);
+/*
+/* ARGV *argv_alloc(len)
+/* ssize_t len;
+/*
+/* ARGV *argv_qsort(argvp, compar)
+/* ARGV *argvp;
+/* ARGV_COMPAR_FN compar;
+/*
+/* void argv_uniq(argvp, compar)
+/* ARGV *argvp;
+/* ARGV_COMPAR_FN compar;
+/*
+/* ARGV *argv_free(argvp)
+/* ARGV *argvp;
+/*
+/* void argv_add(argvp, arg, ..., ARGV_END)
+/* ARGV *argvp;
+/* char *arg;
+/*
+/* void argv_addn(argvp, arg, arg_len, ..., ARGV_END)
+/* ARGV *argvp;
+/* char *arg;
+/* ssize_t arg_len;
+/*
+/* void argv_terminate(argvp);
+/* ARGV *argvp;
+/*
+/* void argv_truncate(argvp, len);
+/* ARGV *argvp;
+/* ssize_t len;
+/*
+/* void argv_insert_one(argvp, pos, arg)
+/* ARGV *argvp;
+/* ssize_t pos;
+/* const char *arg;
+/*
+/* void argv_replace_one(argvp, pos, arg)
+/* ARGV *argvp;
+/* ssize_t pos;
+/* const char *arg;
+/*
+/* void argv_delete(argvp, pos, how_many)
+/* ARGV *argvp;
+/* ssize_t pos;
+/* ssize_t how_many;
+/*
+/* void ARGV_FAKE_BEGIN(argv, arg)
+/* const char *arg;
+/*
+/* void ARGV_FAKE_END
+/* DESCRIPTION
+/* The functions in this module manipulate arrays of string
+/* pointers. An ARGV structure contains the following members:
+/* .IP len
+/* The length of the \fIargv\fR array member.
+/* .IP argc
+/* The number of \fIargv\fR elements used.
+/* .IP argv
+/* An array of pointers to null-terminated strings.
+/* .PP
+/* argv_alloc() returns an empty string array of the requested
+/* length. The result is ready for use by argv_add(). The array
+/* is null terminated.
+/*
+/* argv_qsort() sorts the elements of argvp in place, and
+/* returns its first argument. If the compar argument specifies
+/* a null pointer, then argv_qsort() will use byte-by-byte
+/* comparison.
+/*
+/* argv_uniq() reduces adjacent same-value elements to one
+/* element, and returns its first argument. If the compar
+/* argument specifies a null pointer, then argv_uniq() will
+/* use byte-by-byte comparison.
+/*
+/* argv_add() copies zero or more strings and adds them to the
+/* specified string array. The array is null terminated.
+/* Terminate the argument list with a null pointer. The manifest
+/* constant ARGV_END provides a convenient notation for this.
+/*
+/* argv_addn() is like argv_add(), but each string is followed
+/* by a string length argument.
+/*
+/* argv_free() releases storage for a string array, and conveniently
+/* returns a null pointer.
+/*
+/* argv_terminate() null-terminates its string array argument.
+/*
+/* argv_truncate() truncates its argument to the specified
+/* number of entries, but does not reallocate memory. The
+/* result is null-terminated.
+/*
+/* argv_insert_one() inserts one string at the specified array
+/* position.
+/*
+/* argv_replace_one() replaces one string at the specified
+/* position. The old string is destroyed after the update is
+/* made.
+/*
+/* argv_delete() deletes the specified number of elements
+/* starting at the specified array position. The result is
+/* null-terminated.
+/*
+/* ARGV_FAKE_BEGIN/END are an optimization for the case where
+/* a single string needs to be passed into an ARGV-based
+/* interface. ARGV_FAKE_BEGIN() opens a statement block and
+/* allocates a stack-based ARGV structure named after the first
+/* argument, that encapsulates the second argument. This
+/* implementation allocates no heap memory and creates no copy
+/* of the second argument. ARGV_FAKE_END closes the statement
+/* block and thereby releases storage.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "argv.h"
+
+#ifdef TEST
+extern NORETURN PRINTFLIKE(1, 2) test_msg_panic(const char *,...);
+
+#define msg_panic test_msg_panic
+#endif
+
+/* argv_free - destroy string array */
+
+ARGV *argv_free(ARGV *argvp)
+{
+ char **cpp;
+
+ for (cpp = argvp->argv; cpp < argvp->argv + argvp->argc; cpp++)
+ myfree(*cpp);
+ myfree((void *) argvp->argv);
+ myfree((void *) argvp);
+ return (0);
+}
+
+/* argv_alloc - initialize string array */
+
+ARGV *argv_alloc(ssize_t len)
+{
+ ARGV *argvp;
+ ssize_t sane_len;
+
+ /*
+ * Make sure that always argvp->argc < argvp->len.
+ */
+ argvp = (ARGV *) mymalloc(sizeof(*argvp));
+ argvp->len = 0;
+ sane_len = (len < 2 ? 2 : len);
+ argvp->argv = (char **) mymalloc((sane_len + 1) * sizeof(char *));
+ argvp->len = sane_len;
+ argvp->argc = 0;
+ argvp->argv[0] = 0;
+ return (argvp);
+}
+
+static int argv_cmp(const void *e1, const void *e2)
+{
+ const char *s1 = *(const char **) e1;
+ const char *s2 = *(const char **) e2;
+
+ return strcmp(s1, s2);
+}
+
+/* argv_qsort - sort array in place */
+
+ARGV *argv_qsort(ARGV *argvp, ARGV_COMPAR_FN compar)
+{
+ qsort(argvp->argv, argvp->argc, sizeof(argvp->argv[0]),
+ compar ? compar : argv_cmp);
+ return (argvp);
+}
+
+/* argv_sort - binary compatibility */
+
+ARGV *argv_sort(ARGV *argvp)
+{
+ qsort(argvp->argv, argvp->argc, sizeof(argvp->argv[0]), argv_cmp);
+ return (argvp);
+}
+
+/* argv_uniq - deduplicate adjacent array elements */
+
+ARGV *argv_uniq(ARGV *argvp, ARGV_COMPAR_FN compar)
+{
+ char **cpp;
+ char **prev;
+
+ if (compar == 0)
+ compar = argv_cmp;
+ for (prev = 0, cpp = argvp->argv; cpp < argvp->argv + argvp->argc; cpp++) {
+ if (prev != 0 && compar(prev, cpp) == 0) {
+ argv_delete(argvp, cpp - argvp->argv, 1);
+ cpp = prev;
+ } else {
+ prev = cpp;
+ }
+ }
+ return (argvp);
+}
+
+/* argv_extend - extend array */
+
+static void argv_extend(ARGV *argvp)
+{
+ ssize_t new_len;
+
+ new_len = argvp->len * 2;
+ argvp->argv = (char **)
+ myrealloc((void *) argvp->argv, (new_len + 1) * sizeof(char *));
+ argvp->len = new_len;
+}
+
+/* argv_add - add string to vector */
+
+void argv_add(ARGV *argvp,...)
+{
+ char *arg;
+ va_list ap;
+
+ /*
+ * Make sure that always argvp->argc < argvp->len.
+ */
+#define ARGV_SPACE_LEFT(a) ((a)->len - (a)->argc - 1)
+
+ va_start(ap, argvp);
+ while ((arg = va_arg(ap, char *)) != 0) {
+ if (ARGV_SPACE_LEFT(argvp) <= 0)
+ argv_extend(argvp);
+ argvp->argv[argvp->argc++] = mystrdup(arg);
+ }
+ va_end(ap);
+ argvp->argv[argvp->argc] = 0;
+}
+
+/* argv_addn - add string to vector */
+
+void argv_addn(ARGV *argvp,...)
+{
+ char *arg;
+ ssize_t len;
+ va_list ap;
+
+ /*
+ * Make sure that always argvp->argc < argvp->len.
+ */
+ va_start(ap, argvp);
+ while ((arg = va_arg(ap, char *)) != 0) {
+ if ((len = va_arg(ap, ssize_t)) < 0)
+ msg_panic("argv_addn: bad string length %ld", (long) len);
+ if (ARGV_SPACE_LEFT(argvp) <= 0)
+ argv_extend(argvp);
+ argvp->argv[argvp->argc++] = mystrndup(arg, len);
+ }
+ va_end(ap);
+ argvp->argv[argvp->argc] = 0;
+}
+
+/* argv_terminate - terminate string array */
+
+void argv_terminate(ARGV *argvp)
+{
+
+ /*
+ * Trust that argvp->argc < argvp->len.
+ */
+ argvp->argv[argvp->argc] = 0;
+}
+
+/* argv_truncate - truncate string array */
+
+void argv_truncate(ARGV *argvp, ssize_t len)
+{
+ char **cpp;
+
+ /*
+ * Sanity check.
+ */
+ if (len < 0)
+ msg_panic("argv_truncate: bad length %ld", (long) len);
+
+ if (len < argvp->argc) {
+ for (cpp = argvp->argv + len; cpp < argvp->argv + argvp->argc; cpp++)
+ myfree(*cpp);
+ argvp->argc = len;
+ argvp->argv[argvp->argc] = 0;
+ }
+}
+
+/* argv_insert_one - insert one string into array */
+
+void argv_insert_one(ARGV *argvp, ssize_t where, const char *arg)
+{
+ ssize_t pos;
+
+ /*
+ * Sanity check.
+ */
+ if (where < 0 || where > argvp->argc)
+ msg_panic("argv_insert_one bad position: %ld", (long) where);
+
+ if (ARGV_SPACE_LEFT(argvp) <= 0)
+ argv_extend(argvp);
+ for (pos = argvp->argc; pos >= where; pos--)
+ argvp->argv[pos + 1] = argvp->argv[pos];
+ argvp->argv[where] = mystrdup(arg);
+ argvp->argc += 1;
+}
+
+/* argv_replace_one - replace one string in array */
+
+void argv_replace_one(ARGV *argvp, ssize_t where, const char *arg)
+{
+ char *temp;
+
+ /*
+ * Sanity check.
+ */
+ if (where < 0 || where >= argvp->argc)
+ msg_panic("argv_replace_one bad position: %ld", (long) where);
+
+ temp = argvp->argv[where];
+ argvp->argv[where] = mystrdup(arg);
+ myfree(temp);
+}
+
+/* argv_delete - remove string(s) from array */
+
+void argv_delete(ARGV *argvp, ssize_t first, ssize_t how_many)
+{
+ ssize_t pos;
+
+ /*
+ * Sanity check.
+ */
+ if (first < 0 || how_many < 0 || first + how_many > argvp->argc)
+ msg_panic("argv_delete bad range: (start=%ld count=%ld)",
+ (long) first, (long) how_many);
+
+ for (pos = first; pos < first + how_many; pos++)
+ myfree(argvp->argv[pos]);
+ for (pos = first; pos <= argvp->argc - how_many; pos++)
+ argvp->argv[pos] = argvp->argv[pos + how_many];
+ argvp->argc -= how_many;
+}
+
+#ifdef TEST
+
+ /*
+ * System library.
+ */
+#include <setjmp.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg_vstream.h>
+#include <stringops.h>
+
+#define ARRAY_LEN (10)
+
+typedef struct TEST_CASE {
+ const char *label; /* identifies test case */
+ const char *inputs[ARRAY_LEN]; /* input strings */
+ int terminate; /* terminate result */
+ ARGV *(*populate_fn) (const struct TEST_CASE *, ARGV *);
+ const char *exp_panic_msg; /* expected panic */
+ int exp_argc; /* expected array length */
+ const char *exp_argv[ARRAY_LEN]; /* expected array content */
+} TEST_CASE;
+
+#define TERMINATE_ARRAY (1)
+
+#define PASS (0)
+#define FAIL (1)
+
+VSTRING *test_panic_str;
+jmp_buf test_panic_jbuf;
+
+/* test_msg_panic - does not return, and does not terminate */
+
+void test_msg_panic(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ test_panic_str = vstring_alloc(100);
+ vstring_vsprintf(test_panic_str, fmt, ap);
+ va_end(ap);
+ longjmp(test_panic_jbuf, 1);
+}
+
+/* test_argv_populate - populate result, optionally terminate */
+
+static ARGV *test_argv_populate(const TEST_CASE *tp, ARGV *argvp)
+{
+ const char *const * cpp;
+
+ for (cpp = tp->inputs; *cpp; cpp++)
+ argv_add(argvp, *cpp, (char *) 0);
+ if (tp->terminate)
+ argv_terminate(argvp);
+ return (argvp);
+}
+
+/* test_argv_sort - populate and sort result */
+
+static ARGV *test_argv_sort(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_qsort(argvp, (ARGV_COMPAR_FN) 0);
+ return (argvp);
+}
+
+/* test_argv_sort_uniq - populate, sort, uniq result */
+
+static ARGV *test_argv_sort_uniq(const TEST_CASE *tp, ARGV *argvp)
+{
+
+ /*
+ * This also tests argv_delete().
+ */
+ test_argv_sort(tp, argvp);
+ argv_uniq(argvp, (ARGV_COMPAR_FN) 0);
+ return (argvp);
+}
+
+/* test_argv_good_truncate - populate and truncate to good size */
+
+static ARGV *test_argv_good_truncate(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_truncate(argvp, tp->exp_argc);
+ return (argvp);
+}
+
+/* test_argv_bad_truncate - populate and truncate to bad size */
+
+static ARGV *test_argv_bad_truncate(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_truncate(argvp, -1);
+ return (argvp);
+}
+
+/* test_argv_good_insert - populate and insert at good position */
+
+static ARGV *test_argv_good_insert(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_insert_one(argvp, 1, "new");
+ return (argvp);
+}
+
+/* test_argv_bad_insert1 - populate and insert at bad position */
+
+static ARGV *test_argv_bad_insert1(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_insert_one(argvp, -1, "new");
+ return (argvp);
+}
+
+/* test_argv_bad_insert2 - populate and insert at bad position */
+
+static ARGV *test_argv_bad_insert2(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_insert_one(argvp, 100, "new");
+ return (argvp);
+}
+
+/* test_argv_good_replace - populate and replace at good position */
+
+static ARGV *test_argv_good_replace(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_replace_one(argvp, 1, "new");
+ return (argvp);
+}
+
+/* test_argv_bad_replace1 - populate and replace at bad position */
+
+static ARGV *test_argv_bad_replace1(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_replace_one(argvp, -1, "new");
+ return (argvp);
+}
+
+/* test_argv_bad_replace2 - populate and replace at bad position */
+
+static ARGV *test_argv_bad_replace2(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_replace_one(argvp, 100, "new");
+ return (argvp);
+}
+
+/* test_argv_bad_delete1 - populate and delete at bad position */
+
+static ARGV *test_argv_bad_delete1(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_delete(argvp, -1, 1);
+ return (argvp);
+}
+
+/* test_argv_bad_delete2 - populate and delete at bad position */
+
+static ARGV *test_argv_bad_delete2(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_delete(argvp, 0, -1);
+ return (argvp);
+}
+
+/* test_argv_bad_delete3 - populate and delete at bad position */
+
+static ARGV *test_argv_bad_delete3(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_delete(argvp, 100, 1);
+ return (argvp);
+}
+
+/* test_argv_verify - verify result */
+
+static int test_argv_verify(const TEST_CASE *tp, ARGV *argvp)
+{
+ int idx;
+
+ if (tp->exp_panic_msg != 0) {
+ if (test_panic_str == 0) {
+ msg_warn("test case '%s': got no panic, want: '%s'",
+ tp->label, tp->exp_panic_msg);
+ return (FAIL);
+ }
+ if (strcmp(vstring_str(test_panic_str), tp->exp_panic_msg) != 0) {
+ msg_warn("test case '%s': got '%s', want: '%s'",
+ tp->label, vstring_str(test_panic_str), tp->exp_panic_msg);
+ return (FAIL);
+ }
+ return (PASS);
+ }
+ if (test_panic_str != 0) {
+ msg_warn("test case '%s': got '%s', want: no panic",
+ tp->label, vstring_str(test_panic_str));
+ return (FAIL);
+ }
+ if (argvp->argc != tp->exp_argc) {
+ msg_warn("test case '%s': got argc: %ld, want: %d",
+ tp->label, (long) argvp->argc, tp->exp_argc);
+ return (FAIL);
+ }
+ if (argvp->argv[argvp->argc] != 0 && tp->terminate) {
+ msg_warn("test case '%s': got unterminated, want: terminated", tp->label);
+ return (FAIL);
+ }
+ for (idx = 0; idx < argvp->argc; idx++) {
+ if (strcmp(argvp->argv[idx], tp->exp_argv[idx]) != 0) {
+ msg_warn("test case '%s': index %d: got '%s', want: '%s'",
+ tp->label, idx, argvp->argv[idx], tp->exp_argv[idx]);
+ return (FAIL);
+ }
+ }
+ return (PASS);
+}
+
+ /*
+ * The test cases. TODO: argv_addn with good and bad string length.
+ */
+static const TEST_CASE test_cases[] = {
+ {"multiple strings, unterminated array",
+ {"foo", "baz", "bar", 0}, 0, test_argv_populate,
+ 0, 3, {"foo", "baz", "bar", 0}
+ },
+ {"multiple strings, terminated array",
+ {"foo", "baz", "bar", 0}, TERMINATE_ARRAY, test_argv_populate,
+ 0, 3, {"foo", "baz", "bar", 0}
+ },
+ {"distinct strings, sorted array",
+ {"foo", "baz", "bar", 0}, 0, test_argv_sort,
+ 0, 3, {"bar", "baz", "foo", 0}
+ },
+ {"duplicate strings, sorted array",
+ {"foo", "baz", "baz", "bar", 0}, 0, test_argv_sort,
+ 0, 4, {"bar", "baz", "baz", "foo", 0}
+ },
+ {"duplicate strings, sorted, uniqued-middle elements",
+ {"foo", "baz", "baz", "bar", 0}, 0, test_argv_sort_uniq,
+ 0, 3, {"bar", "baz", "foo", 0}
+ },
+ {"duplicate strings, sorted, uniqued-first elements",
+ {"foo", "bar", "baz", "bar", 0}, 0, test_argv_sort_uniq,
+ 0, 3, {"bar", "baz", "foo", 0}
+ },
+ {"duplicate strings, sorted, uniqued-last elements",
+ {"foo", "foo", "baz", "bar", 0}, 0, test_argv_sort_uniq,
+ 0, 3, {"bar", "baz", "foo", 0}
+ },
+ {"multiple strings, truncate array by one",
+ {"foo", "baz", "bar", 0}, 0, test_argv_good_truncate,
+ 0, 2, {"foo", "baz", 0}
+ },
+ {"multiple strings, truncate whole array",
+ {"foo", "baz", "bar", 0}, 0, test_argv_good_truncate,
+ 0, 0, {"foo", "baz", 0}
+ },
+ {"multiple strings, bad truncate",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_truncate,
+ "argv_truncate: bad length -1"
+ },
+ {"multiple strings, insert one at good position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_good_insert,
+ 0, 4, {"foo", "new", "baz", "bar", 0}
+ },
+ {"multiple strings, insert one at bad position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_insert1,
+ "argv_insert_one bad position: -1"
+ },
+ {"multiple strings, insert one at bad position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_insert2,
+ "argv_insert_one bad position: 100"
+ },
+ {"multiple strings, replace one at good position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_good_replace,
+ 0, 3, {"foo", "new", "bar", 0}
+ },
+ {"multiple strings, replace one at bad position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_replace1,
+ "argv_replace_one bad position: -1"
+ },
+ {"multiple strings, replace one at bad position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_replace2,
+ "argv_replace_one bad position: 100"
+ },
+ {"multiple strings, delete one at negative position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_delete1,
+ "argv_delete bad range: (start=-1 count=1)"
+ },
+ {"multiple strings, delete with bad range end",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_delete2,
+ "argv_delete bad range: (start=0 count=-1)"
+ },
+ {"multiple strings, delete at too large position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_delete3,
+ "argv_delete bad range: (start=100 count=1)"
+ },
+ 0,
+};
+
+int main(int argc, char **argv)
+{
+ const TEST_CASE *tp;
+ int pass = 0;
+ int fail = 0;
+
+ msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+ for (tp = test_cases; tp->label != 0; tp++) {
+ int test_failed;
+ ARGV *argvp;
+
+ argvp = argv_alloc(1);
+ if (setjmp(test_panic_jbuf) == 0)
+ tp->populate_fn(tp, argvp);
+ test_failed = test_argv_verify(tp, argvp);
+ if (test_failed) {
+ msg_info("%s: FAIL", tp->label);
+ fail++;
+ } else {
+ msg_info("%s: PASS", tp->label);
+ pass++;
+ }
+ argv_free(argvp);
+ if (test_panic_str) {
+ vstring_free(test_panic_str);
+ test_panic_str = 0;
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ exit(fail != 0);
+}
+
+#endif