--- /dev/null
+/*
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "nls.h"
+#include "mountP.h"
+
+/*
+ * Parses the first option from @optstr. The @optstr pointer is set to begin of
+ * the next option.
+ *
+ * Returns -1 on parse error, 1 at the end of optstr and 0 on success.
+ */
+static int mnt_optstr_parse_next(char **optstr, char **name, size_t *namesz,
+ char **value, size_t *valsz)
+{
+ int open_quote = 0;
+ char *start = NULL, *stop = NULL, *p, *sep = NULL;
+ char *optstr0;
+
+ assert(optstr);
+ assert(*optstr);
+
+ optstr0 = *optstr;
+
+ if (name)
+ *name = NULL;
+ if (namesz)
+ *namesz = 0;
+ if (value)
+ *value = NULL;
+ if (valsz)
+ *valsz = 0;
+
+ for (p = optstr0; p && *p; p++) {
+ if (!start)
+ start = p; /* begin of the option item */
+ if (*p == '"')
+ open_quote ^= 1; /* reverse the status */
+ if (open_quote)
+ continue; /* still in quoted block */
+ if (!sep && *p == '=')
+ sep = p; /* name and value separator */
+ if (*p == ',')
+ stop = p; /* terminate the option item */
+ else if (*(p + 1) == '\0')
+ stop = p + 1; /* end of optstr */
+ if (!start || !stop)
+ continue;
+ if (stop <= start)
+ goto error;
+
+ if (name)
+ *name = start;
+ if (namesz)
+ *namesz = sep ? sep - start : stop - start;
+ *optstr = *stop ? stop + 1 : stop;
+
+ if (sep) {
+ if (value)
+ *value = sep + 1;
+ if (valsz)
+ *valsz = stop - sep - 1;
+ }
+ return 0;
+ }
+
+ return 1; /* end of optstr */
+
+error:
+ DBG(DEBUG_OPTIONS, fprintf(stderr,
+ "libmount: parse error: \"%s\"\n", optstr0));
+ return -1;
+}
+
+/*
+ * Locates the first option that match with @name. The @end is set to
+ * char behind the option (it means ',' or \0).
+ *
+ * Returns -1 on parse error, 1 when not found and 0 on success.
+ */
+static int mnt_optstr_locate_option(char *optstr, const char *name, char **begin,
+ char **end, char **value, size_t *valsz)
+{
+ char *n;
+ size_t namesz, nsz;
+ int rc;
+
+ assert(name);
+ assert(optstr);
+
+ namesz = strlen(name);
+
+ do {
+ rc = mnt_optstr_parse_next(&optstr, &n, &nsz, value, valsz);
+ if (rc)
+ break;
+
+ if (namesz == nsz && strncmp(n, name, nsz) == 0) {
+ if (begin)
+ *begin = n;
+ if (end)
+ *end = *(optstr - 1) == ',' ?
+ optstr - 1 : optstr;
+ return 0;
+ }
+ } while(1);
+
+ DBG(DEBUG_OPTIONS, fprintf(stderr,
+ "libmount: can't found '%s' option\n", name));
+ return rc;
+}
+
+/**
+ * mnt_optstr_next_option:
+ * @optstr: option string, returns position to next option
+ * @name: returns option name
+ * @namesz: returns option name length
+ * @value: returns option value or NULL
+ * @valuesz: returns option value length or zero
+ *
+ * Parses the first option in @optstr or -1 in case of error.
+ *
+ * Returns 0 on success, 1 at the end of @optstr or -1 in case of error.
+ */
+int mnt_optstr_next_option(char **optstr, char **name, size_t *namesz,
+ char **value, size_t *valuesz)
+{
+ if (!optstr || !*optstr)
+ return -1;
+ return mnt_optstr_parse_next(optstr, name, namesz, value, valuesz);
+}
+
+/**
+ * mnt_optstr_append_option:
+ * @optstr: option string or NULL
+ * @name: value name
+ * @value: value
+ *
+ * Returns: reallocated (or newly allocated) @optstr with ,name=value
+ */
+int mnt_optstr_append_option(char **optstr, const char *name, const char *value)
+{
+ char *p;
+ size_t sz, vsz, osz, nsz;
+
+ if (!name)
+ return -1;
+
+ osz = *optstr ? strlen(*optstr) : 0;
+ nsz = strlen(name);
+ vsz = value ? strlen(value) : 0;
+
+ sz = osz + nsz + 1; /* 1: '\0' */
+ if (osz)
+ sz++; /* ',' options separator */
+ if (vsz)
+ sz += vsz + 1; /* 1: '=' */
+
+ p = realloc(*optstr, sz);
+ if (!p)
+ return -1;
+ *optstr = p;
+
+ if (osz) {
+ p += osz;
+ *p++ = ',';
+ }
+
+ memcpy(p, name, nsz);
+ p += nsz;
+
+ if (vsz) {
+ *p++ = '=';
+ memcpy(p, value, vsz);
+ p += vsz;
+ }
+ *p = '\0';
+
+ return 0;
+}
+
+/**
+ * mnt_optstr_get_option:
+ * @optstr: string with comma separated list of options
+ * @name: requested option name
+ * @value: returns pointer to the begin of the value (e.g. name=VALUE) or NULL
+ * @valsz: returns size of the value or 0
+ *
+ * Returns: 0 on success, 1 when not found the @name or -1 in case of error.
+ */
+int mnt_optstr_get_option(char *optstr, const char *name,
+ char **value, size_t *valsz)
+{
+ return mnt_optstr_locate_option(optstr, name, NULL, NULL, value, valsz);
+}
+
+/* Removes substring located between @begin and @end from @str
+ * -- result never starts or ends with comma or contains two commas
+ * (e.g. ",aaa,bbb" or "aaa,,bbb" or "aaa,")
+ */
+static void remove_substring(char *str, char *begin, char *end)
+{
+ size_t sz = strlen(end);
+
+ if ((begin == str || *(begin - 1) == ',') && *end == ',')
+ end++;
+
+ memmove(begin, end, sz + 1);
+ if (!*begin && *(begin - 1) == ',')
+ *(begin - 1) = '\0';
+}
+
+/* insert '=substr' to @str on position @pos */
+static int insert_substring(char **str, char *pos, const char *substr)
+{
+ char *p;
+ size_t ssz = strlen(substr); /* substring size */
+
+ p = realloc(*str, strlen(*str) + 1 + ssz);
+ if (!p)
+ return -1;
+ *str = p;
+
+ memmove(pos + ssz + 1, pos, strlen(pos) + 1);
+ *pos++ = '=';
+ memcpy(pos, substr, ssz);
+ return 0;
+}
+
+/**
+ * mnt_optstr_set_option:
+ * @optstr: string with comma separated list of options
+ * @name: requested option
+ * @value: new value or NULL
+ *
+ * Set or unset option @value.
+ *
+ * Returns: 0 on success, 1 when not found the @name or -1 in case of error.
+ */
+int mnt_optstr_set_option(char **optstr, const char *name, const char *value)
+{
+ char *val = NULL, *begin, *end, *nameend;
+ size_t valsz;
+ int rc = 1;
+
+ if (!optstr)
+ return -1;
+ if (*optstr)
+ rc = mnt_optstr_locate_option(*optstr, name,
+ &begin, &end, &val, &valsz);
+ if (rc == -1)
+ /* parse error */
+ return -1;
+ if (rc == 1)
+ /* not found */
+ return mnt_optstr_append_option(optstr, name, value);
+
+ nameend = begin + strlen(name);
+
+ if (value == NULL && val && valsz)
+ /* remove unwanted "=value" */
+ remove_substring(*optstr, nameend, end);
+
+ else if (value && val == NULL)
+ /* insert "=value" */
+ rc = insert_substring(optstr, nameend, value);
+
+ else if (value && val && strlen(value) == valsz)
+ /* simply replace =value */
+ memcpy(val, value, valsz);
+
+ else if (value && val) {
+ remove_substring(*optstr, nameend, end);
+ rc = insert_substring(optstr, nameend, value);
+ }
+
+
+ return 0;
+}
+
+/**
+ * mnt_optstr_remove_option:
+ * @optstr: string with comma separated list of options
+ * @name: requested option name
+ *
+ * Returns: 0 on success, 1 when not found the @name or -1 in case of error.
+ */
+int mnt_optstr_remove_option(char **optstr, const char *name)
+{
+ char *begin, *end;
+ int rc;
+
+ rc = mnt_optstr_locate_option(*optstr, name,
+ &begin, &end, NULL, NULL);
+ if (rc != 0)
+ return rc;
+
+ remove_substring(*optstr, begin, end);
+ return 0;
+}
+
+#ifdef TEST_PROGRAM
+
+int test_append(struct mtest *ts, int argc, char *argv[])
+{
+ const char *value = NULL, *name;
+ char *optstr;
+
+ if (argc < 3)
+ goto done;
+ optstr = strdup(argv[1]);
+ name = argv[2];
+
+ if (argc == 4)
+ value = argv[3];
+
+ if (mnt_optstr_append_option(&optstr, name, value) == 0) {
+ printf("result: >%s<\n", optstr);
+ return 0;
+ }
+done:
+ return -1;
+}
+
+int test_set(struct mtest *ts, int argc, char *argv[])
+{
+ const char *value = NULL, *name;
+ char *optstr;
+
+ if (argc < 3)
+ goto done;
+ optstr = strdup(argv[1]);
+ name = argv[2];
+
+ if (argc == 4)
+ value = argv[3];
+
+ if (mnt_optstr_set_option(&optstr, name, value) == 0) {
+ printf("result: >%s<\n", optstr);
+ return 0;
+ }
+done:
+ return -1;
+}
+
+int test_get(struct mtest *ts, int argc, char *argv[])
+{
+ char *optstr;
+ const char *name;
+ char *val = NULL;
+ size_t sz = 0;
+ int rc;
+
+ if (argc < 2)
+ goto done;
+ optstr = argv[1];
+ name = argv[2];
+
+ rc = mnt_optstr_get_option(optstr, name, &val, &sz);
+ if (rc == 0) {
+ printf("found; name: %s", name);
+ if (sz) {
+ printf(", argument: size=%zd data=", sz);
+ if (fwrite(val, 1, sz, stdout) != sz)
+ goto done;
+ }
+ printf("\n");
+ return 0;
+ } else if (rc == 1)
+ printf("%s: not found\n", name);
+ else
+ printf("parse error: %s\n", optstr);
+done:
+ return -1;
+}
+
+int test_remove(struct mtest *ts, int argc, char *argv[])
+{
+ const char *name;
+ char *optstr;
+
+ if (argc < 3)
+ goto done;
+ optstr = strdup(argv[1]);
+ name = argv[2];
+
+ if (mnt_optstr_remove_option(&optstr, name) == 0) {
+ printf("result: >%s<\n", optstr);
+ return 0;
+ }
+done:
+ return -1;
+}
+
+
+int main(int argc, char *argv[])
+{
+ struct mtest tss[] = {
+ { "--append", test_append, "<optstr> <name> [<value>] append value to optstr" },
+ { "--set", test_set, "<optstr> <name> [<value>] (un)set value" },
+ { "--get", test_get, "<optstr> <name> search name in optstr" },
+ { "--remove", test_remove, "<optstr> <name> remove name in optstr" },
+ { NULL }
+ };
+ return mnt_run_test(tss, argc, argv);
+}
+#endif /* TEST_PROGRAM */