From 12aa71dfdeb75c9ef0708a36aafcf534abd419aa Mon Sep 17 00:00:00 2001 From: Manuel VIVES Date: Thu, 23 Jan 2014 10:28:29 +0100 Subject: [PATCH] Add virStringSearch method for regex matching Add a virStringSearch method to virstring.{c,h} which performs a regex match against a string and returns the matching substrings. Signed-off-by: Daniel P. Berrange --- po/POTFILES.in | 1 + src/libvirt_private.syms | 1 + src/util/virstring.c | 104 +++++++++++++++++++++++++++++++++++++++ src/util/virstring.h | 7 +++ tests/virstringtest.c | 100 +++++++++++++++++++++++++++++++++++++ 5 files changed, 213 insertions(+) diff --git a/po/POTFILES.in b/po/POTFILES.in index 5d02062cd3..c565771880 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -192,6 +192,7 @@ src/util/virscsi.c src/util/virsocketaddr.c src/util/virstatslinux.c src/util/virstoragefile.c +src/util/virstring.c src/util/virsysinfo.c src/util/virerror.c src/util/virerror.h diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 21c6b660dc..c7c8c6a3bf 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1798,6 +1798,7 @@ virStringArrayHasString; virStringFreeList; virStringJoin; virStringListLength; +virStringSearch; virStringSortCompare; virStringSortRevCompare; virStringSplit; diff --git a/src/util/virstring.c b/src/util/virstring.c index 8d0ca70b25..008293f5a9 100644 --- a/src/util/virstring.c +++ b/src/util/virstring.c @@ -23,12 +23,14 @@ #include #include +#include #include "c-ctype.h" #include "virstring.h" #include "viralloc.h" #include "virbuffer.h" #include "virerror.h" +#include "virlog.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -645,3 +647,105 @@ int virStringSortRevCompare(const void *a, const void *b) return strcmp(*sb, *sa); } + +/** + * virStringSearch: + * @str: string to search + * @regexp: POSIX Extended regular expression pattern used for matching + * @max_matches: maximum number of substrings to return + * @result: pointer to an array to be filled with NULL terminated list of matches + * + * Performs a POSIX extended regex search against a string and return all matching substrings. + * The @result value should be freed with virStringFreeList() when no longer + * required. + * + * @code + * char *source = "6853a496-1c10-472e-867a-8244937bd6f0 + * 773ab075-4cd7-4fc2-8b6e-21c84e9cb391 + * bbb3c75c-d60f-43b0-b802-fd56b84a4222 + * 60c04aa1-0375-4654-8d9f-e149d9885273 + * 4548d465-9891-4c34-a184-3b1c34a26aa8"; + * char **matches = NULL; + * virStringSearch(source, + * "([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})", + * 3, + * &matches); + * + * // matches[0] == "6853a496-1c10-472e-867a-8244937bd6f0"; + * // matches[1] == "773ab075-4cd7-4fc2-8b6e-21c84e9cb391"; + * // matches[2] == "bbb3c75c-d60f-43b0-b802-fd56b84a4222" + * // matches[3] == NULL; + * + * virStringFreeList(matches); + * @endcode + * + * Returns: -1 on error, or number of matches + */ +ssize_t +virStringSearch(const char *str, + const char *regexp, + size_t max_matches, + char ***matches) +{ + regex_t re; + regmatch_t rem; + size_t nmatches = 0; + ssize_t ret = -1; + int rv = -1; + + *matches = NULL; + + VIR_DEBUG("search '%s' for '%s'", str, regexp); + + if ((rv = regcomp(&re, regexp, REG_EXTENDED)) != 0) { + char error[100]; + regerror(rv, &re, error, sizeof(error)); + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Error while compiling regular expression '%s': %s"), + regexp, error); + return -1; + } + + if (re.re_nsub != 1) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Regular expression '%s' must have exactly 1 match group, not %zu"), + regexp, re.re_nsub); + goto cleanup; + } + + /* '*matches' must always be NULL terminated in every iteration + * of the loop, so start by allocating 1 element + */ + if (VIR_EXPAND_N(*matches, nmatches, 1) < 0) + goto cleanup; + + while ((nmatches - 1) < max_matches) { + char *match; + + if (regexec(&re, str, 1, &rem, 0) != 0) + break; + + if (VIR_EXPAND_N(*matches, nmatches, 1) < 0) + goto cleanup; + + if (VIR_STRNDUP(match, str + rem.rm_so, + rem.rm_eo - rem.rm_so) < 0) + goto cleanup; + + VIR_DEBUG("Got '%s'", match); + + (*matches)[nmatches-2] = match; + + str = str + rem.rm_eo; + } + + ret = nmatches - 1; /* don't count the trailing null */ + +cleanup: + regfree(&re); + if (ret < 0) { + virStringFreeList(*matches); + *matches = NULL; + } + return ret; +} diff --git a/src/util/virstring.h b/src/util/virstring.h index 13a6e5a70c..8efc80cdc7 100644 --- a/src/util/virstring.h +++ b/src/util/virstring.h @@ -226,4 +226,11 @@ size_t virStringListLength(char **strings); int virStringSortCompare(const void *a, const void *b); int virStringSortRevCompare(const void *a, const void *b); +ssize_t virStringSearch(const char *str, + const char *regexp, + size_t max_results, + char ***matches) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(4); + + #endif /* __VIR_STRING_H__ */ diff --git a/tests/virstringtest.c b/tests/virstringtest.c index c6adf9fddf..f6c44053cb 100644 --- a/tests/virstringtest.c +++ b/tests/virstringtest.c @@ -274,6 +274,70 @@ testStringSortCompare(const void *opaque ATTRIBUTE_UNUSED) } +struct stringSearchData { + const char *str; + const char *regexp; + size_t maxMatches; + size_t expectNMatches; + const char **expectMatches; + bool expectError; +}; + +static int +testStringSearch(const void *opaque ATTRIBUTE_UNUSED) +{ + const struct stringSearchData *data = opaque; + char **matches = NULL; + ssize_t nmatches; + int ret = -1; + + nmatches = virStringSearch(data->str, data->regexp, + data->maxMatches, &matches); + + if (data->expectError) { + if (nmatches != -1) { + fprintf(stderr, "expected error on %s but got %zd matches\n", + data->str, nmatches); + goto cleanup; + } + } else { + size_t i; + + if (nmatches < 0) { + fprintf(stderr, "expected %zu matches on %s but got error\n", + data->expectNMatches, data->str); + goto cleanup; + } + + if (nmatches != data->expectNMatches) { + fprintf(stderr, "expected %zu matches on %s but got %zd\n", + data->expectNMatches, data->str, nmatches); + goto cleanup; + } + + if (virStringListLength(matches) != nmatches) { + fprintf(stderr, "expected %zu matches on %s but got %zd matches\n", + data->expectNMatches, data->str, + virStringListLength(matches)); + goto cleanup; + } + + for (i = 0; i < nmatches; i++) { + if (STRNEQ(matches[i], data->expectMatches[i])) { + fprintf(stderr, "match %zu expected '%s' but got '%s'\n", + i, data->expectMatches[i], matches[i]); + goto cleanup; + } + } + } + + ret = 0; + + cleanup: + virStringFreeList(matches); + return ret; +} + static int mymain(void) { @@ -328,6 +392,42 @@ mymain(void) if (virtTestRun("virStringSortCompare", testStringSortCompare, NULL) < 0) ret = -1; + +#define TEST_SEARCH(s, r, x, n, m, e) \ + do { \ + struct stringSearchData data = { \ + .str = s, \ + .maxMatches = x, \ + .regexp = r, \ + .expectNMatches = n, \ + .expectMatches = m, \ + .expectError = e, \ + }; \ + if (virtTestRun("virStringSearch " s, testStringSearch, &data) < 0) \ + ret = -1; \ + } while (0) + + /* error due to missing () in regexp */ + TEST_SEARCH("foo", "bar", 10, 0, NULL, true); + + /* error due to too many () in regexp */ + TEST_SEARCH("foo", "(b)(a)(r)", 10, 0, NULL, true); + + /* None matching */ + TEST_SEARCH("foo", "(bar)", 10, 0, NULL, false); + + /* Full match */ + const char *matches1[] = { "foo" }; + TEST_SEARCH("foo", "(foo)", 10, 1, matches1, false); + + /* Multi matches */ + const char *matches2[] = { "foo", "bar", "eek" }; + TEST_SEARCH("1foo2bar3eek", "([a-z]+)", 10, 3, matches2, false); + + /* Multi matches, limited returns */ + const char *matches3[] = { "foo", "bar" }; + TEST_SEARCH("1foo2bar3eek", "([a-z]+)", 2, 2, matches3, false); + return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE; } -- GitLab