+
+static int get_next_word(const char *buf, const char *delim, char **word)
+{
+ enum line_state_flags {LSF_HAVE_WORD = 1, LSF_BACKSLASH = 2,
+ LSF_SINGLE_QUOTE = 4, LSF_DOUBLE_QUOTE = 8};
+ const char *in;
+ char *out;
+ int ret, state = 0;
+
+ out = para_malloc(strlen(buf) + 1);
+ *out = '\0';
+ *word = out;
+ for (in = buf; *in; in++) {
+ const char *p;
+
+ switch (*in) {
+ case '\\':
+ if (state & LSF_BACKSLASH) /* \\ */
+ goto copy_char;
+ state |= LSF_BACKSLASH;
+ state |= LSF_HAVE_WORD;
+ continue;
+ case 'n':
+ case 't':
+ if (state & LSF_BACKSLASH) { /* \n or \t */
+ *out++ = (*in == 'n')? '\n' : '\t';
+ state &= ~LSF_BACKSLASH;
+ continue;
+ }
+ goto copy_char;
+ case '"':
+ if (state & LSF_BACKSLASH) /* \" */
+ goto copy_char;
+ if (state & LSF_SINGLE_QUOTE) /* '" */
+ goto copy_char;
+ if (state & LSF_DOUBLE_QUOTE) {
+ state &= ~LSF_DOUBLE_QUOTE;
+ continue;
+ }
+ state |= LSF_HAVE_WORD;
+ state |= LSF_DOUBLE_QUOTE;
+ continue;
+ case '\'':
+ if (state & LSF_BACKSLASH) /* \' */
+ goto copy_char;
+ if (state & LSF_DOUBLE_QUOTE) /* "' */
+ goto copy_char;
+ if (state & LSF_SINGLE_QUOTE) {
+ state &= ~LSF_SINGLE_QUOTE;
+ continue;
+ }
+ state |= LSF_HAVE_WORD;
+ state |= LSF_SINGLE_QUOTE;
+ continue;
+ }
+ for (p = delim; *p; p++) {
+ if (*in != *p)
+ continue;
+ if (state & LSF_BACKSLASH)
+ goto copy_char;
+ if (state & LSF_SINGLE_QUOTE)
+ goto copy_char;
+ if (state & LSF_DOUBLE_QUOTE)
+ goto copy_char;
+ if (state & LSF_HAVE_WORD)
+ goto success;
+ break;
+ }
+ if (*p) /* ignore delimiter at the beginning */
+ continue;
+copy_char:
+ state |= LSF_HAVE_WORD;
+ *out++ = *in;
+ state &= ~LSF_BACKSLASH;
+ }
+ ret = 0;
+ if (!(state & LSF_HAVE_WORD))
+ goto out;
+ ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+ if (state & LSF_BACKSLASH) {
+ PARA_ERROR_LOG("trailing backslash\n");
+ goto out;
+ }
+ if ((state & LSF_SINGLE_QUOTE) || (state & LSF_DOUBLE_QUOTE)) {
+ PARA_ERROR_LOG("unmatched quote character\n");
+ goto out;
+ }
+success:
+ *out = '\0';
+ return in - buf;
+out:
+ free(*word);
+ *word = NULL;
+ return ret;
+}
+
+/**
+ * Get the number of the word the cursor is on.
+ *
+ * \param buf The zero-terminated line buffer.
+ * \param delim Characters that separate words.
+ * \param point The cursor position.
+ *
+ * \return Zero-based word number.
+ */
+int compute_word_num(const char *buf, const char *delim, int point)
+{
+ int ret, num_words;
+ const char *p;
+ char *word;
+
+ for (p = buf, num_words = 0; ; p += ret, num_words++) {
+ ret = get_next_word(p, delim, &word);
+ if (ret <= 0)
+ break;
+ free(word);
+ if (p + ret >= buf + point)
+ break;
+ }
+ return num_words;
+}
+
+/**
+ * Free an array of words created by create_argv().
+ *
+ * \param argv A pointer previously obtained by \ref create_argv().
+ */
+void free_argv(char **argv)
+{
+ int i;
+
+ if (!argv)
+ return;
+ for (i = 0; argv[i]; i++)
+ free(argv[i]);
+ free(argv);
+}
+
+/**
+ * Split a buffer into words.
+ *
+ * This parser honors single and double quotes, backslash-escaped characters
+ * and special characters like \p \\n. The result contains pointers to copies
+ * of the words contained in \a buf and has to be freed by using \ref
+ * free_argv().
+ *
+ * \param buf The buffer to be split.
+ * \param delim Each character in this string is treated as a separator.
+ * \param result The array of words is returned here.
+ *
+ * \return Number of words in \a buf, negative on errors.
+ */
+int create_argv(const char *buf, const char *delim, char ***result)
+{
+ char *word, **argv = para_malloc(2 * sizeof(char *));
+ const char *p;
+ int ret, num_words;
+
+ for (p = buf, num_words = 0; ; p += ret, num_words++) {
+ ret = get_next_word(p, delim, &word);
+ if (ret < 0)
+ goto err;
+ if (!ret)
+ break;
+ argv = para_realloc(argv, (num_words + 2) * sizeof(char*));
+ argv[num_words] = word;
+ }
+ argv[num_words] = NULL;
+ *result = argv;
+ return num_words;
+err:
+ while (num_words > 0)
+ free(argv[--num_words]);
+ free(argv);
+ *result = NULL;
+ return ret;
+}
+
+/**
+ * Compile a regular expression.
+ *
+ * This simple wrapper calls regcomp() and logs a message on errors.
+ *
+ * \param preg See regcomp(3).
+ * \param regex See regcomp(3).
+ * \param cflags See regcomp(3).
+ *
+ * \return Standard.
+ */
+int para_regcomp(regex_t *preg, const char *regex, int cflags)
+{
+ char *buf;
+ size_t size;
+ int ret = regcomp(preg, regex, cflags);
+
+ if (ret == 0)
+ return 1;
+ size = regerror(ret, preg, NULL, 0);
+ buf = para_malloc(size);
+ regerror(ret, preg, buf, size);
+ PARA_ERROR_LOG("%s\n", buf);
+ free(buf);
+ return -E_REGEX;
+}
+
+/**
+ * strdup() for not necessarily zero-terminated strings.
+ *
+ * \param src The source buffer.
+ * \param len The number of bytes to be copied.
+ *
+ * \return A 0-terminated buffer of length \a len + 1.
+ *
+ * This is similar to strndup(), which is a GNU extension. However, one
+ * difference is that strndup() returns \p NULL if insufficient memory was
+ * available while this function aborts in this case.
+ *
+ * \sa strdup(), \ref para_strdup().
+ */
+char *safe_strdup(const char *src, size_t len)
+{
+ char *p;
+
+ assert(len < (size_t)-1);
+ p = para_malloc(len + 1);
+ if (len > 0)
+ memcpy(p, src, len);
+ p[len] = '\0';
+ return p;
+}
+
+/**
+ * Copy the value of a key=value pair.
+ *
+ * This checks whether the given buffer starts with "key=", ignoring case. If
+ * yes, a copy of the value is returned. The source buffer may not be
+ * zero-terminated.
+ *
+ * \param src The source buffer.
+ * \param len The number of bytes of the tag.
+ * \param key Only copy if it is the value of this key.
+ *
+ * \return A zero-terminated buffer, or \p NULL if the key was
+ * not of the given type.
+ */
+char *key_value_copy(const char *src, size_t len, const char *key)
+{
+ int keylen = strlen(key);
+
+ if (len <= keylen)
+ return NULL;
+ if (strncasecmp(src, key, keylen))
+ return NULL;
+ if (src[keylen] != '=')
+ return NULL;
+ return safe_strdup(src + keylen + 1, len - keylen - 1);
+}