aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJose Bollo <jose.bollo@iot.bzh>2018-11-22 09:03:33 +0100
committerJosé Bollo <jose.bollo@iot.bzh>2018-11-22 17:39:50 +0100
commitbc247d4c9e16e548c84466d8975529568e7c395d (patch)
tree757edd27e14a08b7122062febd01e34d425f41fd
parent153a7c9c44ac84f32a0869ed14e4f08563e6d97c (diff)
globset: Introduce globset for event handlingguppy_6.99.1guppy/6.99.16.99.1
It optimises the event handling that was slow. It also fix few bugs: - at most one event handler is called now - the handler call has the most specific pattern Change-Id: Ic13a0258b5743579ab15e0e953ec62206d982850 Signed-off-by: Jose Bollo <jose.bollo@iot.bzh>
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/afb-export.c130
-rw-r--r--src/globset.c449
-rw-r--r--src/globset.h56
-rw-r--r--src/tests/CMakeLists.txt1
-rw-r--r--src/tests/globset/CMakeLists.txt23
-rw-r--r--src/tests/globset/globset.in30
-rw-r--r--src/tests/globset/globset.out30
-rw-r--r--src/tests/globset/test-globset.c60
9 files changed, 693 insertions, 87 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7038205c..e1a1073a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -67,6 +67,7 @@ SET(AFB_LIB_SOURCES
fdev.c
fdev-epoll.c
fdev-systemd.c
+ globset.c
jobs.c
locale-root.c
pearson.c
diff --git a/src/afb-export.c b/src/afb-export.c
index cabb4858..bfd76539 100644
--- a/src/afb-export.c
+++ b/src/afb-export.c
@@ -50,6 +50,7 @@
#include "afb-calls.h"
#include "jobs.h"
#include "verbose.h"
+#include "globset.h"
#include "sig-monitor.h"
#include "wrap-json.h"
@@ -58,24 +59,6 @@
************************************************************************/
/*
- * structure for handling events
- */
-struct event_handler
-{
- /* link to the next event handler of the list */
- struct event_handler *next;
-
- /* function to call on the case of the event */
- void (*callback)(void *, const char*, struct json_object*, struct afb_api_x3*);
-
- /* closure for the callback */
- void *closure;
-
- /* the handled pattern */
- char pattern[1];
-};
-
-/*
* Actually supported versions
*/
enum afb_api_version
@@ -138,7 +121,7 @@ struct afb_export
struct afb_evt_listener *listener;
/* event handler list */
- struct event_handler *event_handlers;
+ struct globset *event_handlers;
/* creator if any */
struct afb_export *creator;
@@ -1161,7 +1144,8 @@ static const struct afb_api_x3_itf hooked_api_x3_itf = {
*/
static void listener_of_events(void *closure, const char *event, int eventid, struct json_object *object)
{
- struct event_handler *handler;
+ const struct globset_handler *handler;
+ void (*callback)(void *, const char*, struct json_object*, struct afb_api_x3*);
struct afb_export *export = from_api_x3(closure);
/* hook the event before */
@@ -1170,26 +1154,24 @@ static void listener_of_events(void *closure, const char *event, int eventid, st
/* transmit to specific handlers */
/* search the handler */
- handler = export->event_handlers;
- while (handler) {
- if (!fnmatch(handler->pattern, event, 0)) {
- if (!(export->hooksvc & afb_hook_flag_api_on_event_handler))
- handler->callback(handler->closure, event, object, to_api_x3(export));
- else {
- afb_hook_api_on_event_handler_before(export, event, eventid, object, handler->pattern);
- handler->callback(handler->closure, event, object, to_api_x3(export));
- afb_hook_api_on_event_handler_after(export, event, eventid, object, handler->pattern);
- }
+ handler = export->event_handlers ? globset_match(export->event_handlers, event) : NULL;
+ if (handler) {
+ callback = handler->callback;
+ if (!(export->hooksvc & afb_hook_flag_api_on_event_handler))
+ callback(handler->closure, event, object, to_api_x3(export));
+ else {
+ afb_hook_api_on_event_handler_before(export, event, eventid, object, handler->pattern);
+ callback(handler->closure, event, object, to_api_x3(export));
+ afb_hook_api_on_event_handler_after(export, event, eventid, object, handler->pattern);
}
- handler = handler->next;
+ } else {
+ /* transmit to default handler */
+ if (export->on_any_event_v3)
+ export->on_any_event_v3(to_api_x3(export), event, object);
+ else if (export->on_any_event_v12)
+ export->on_any_event_v12(event, object);
}
- /* transmit to default handler */
- if (export->on_any_event_v3)
- export->on_any_event_v3(to_api_x3(export), event, object);
- else if (export->on_any_event_v12)
- export->on_any_event_v12(event, object);
-
/* hook the event after */
if (export->hooksvc & afb_hook_flag_api_on_event)
afb_hook_api_on_event_after(export, event, eventid, object);
@@ -1220,40 +1202,32 @@ int afb_export_event_handler_add(
void *closure)
{
int rc;
- struct event_handler *handler, **previous;
+ /* ensure the listener */
rc = ensure_listener(export);
if (rc < 0)
return rc;
- /* search the handler */
- previous = &export->event_handlers;
- while ((handler = *previous) && strcasecmp(handler->pattern, pattern))
- previous = &handler->next;
-
- /* error if found */
- if (handler) {
- ERROR("[API %s] event handler %s already exists", export->api.apiname, pattern);
- errno = EEXIST;
- return -1;
+ /* ensure the globset for event handling */
+ if (!export->event_handlers) {
+ export->event_handlers = globset_create();
+ if (!export->event_handlers)
+ goto oom_error;
}
- /* create the event */
- handler = malloc(strlen(pattern) + sizeof * handler);
- if (!handler) {
- ERROR("[API %s] can't allocate event handler %s", export->api.apiname, pattern);
- errno = ENOMEM;
+ /* add the handler */
+ rc = globset_add(export->event_handlers, pattern, callback, closure);
+ if (rc == 0)
+ return 0;
+
+ if (errno == EEXIST) {
+ ERROR("[API %s] event handler %s already exists", export->api.apiname, pattern);
return -1;
}
- /* init and record */
- handler->next = NULL;
- handler->callback = callback;
- handler->closure = closure;
- strcpy(handler->pattern, pattern);
- *previous = handler;
-
- return 0;
+oom_error:
+ ERROR("[API %s] can't allocate event handler %s", export->api.apiname, pattern);
+ return -1;
}
int afb_export_event_handler_del(
@@ -1261,27 +1235,13 @@ int afb_export_event_handler_del(
const char *pattern,
void **closure)
{
- struct event_handler *handler, **previous;
-
- /* search the handler */
- previous = &export->event_handlers;
- while ((handler = *previous) && strcasecmp(handler->pattern, pattern))
- previous = &handler->next;
-
- /* error if found */
- if (!handler) {
- ERROR("[API %s] event handler %s already exists", export->api.apiname, pattern);
- errno = ENOENT;
- return -1;
- }
-
- /* remove the found event */
- if (closure)
- *closure = handler->closure;
+ if (export->event_handlers
+ && !globset_del(export->event_handlers, pattern, closure))
+ return 0;
- *previous = handler->next;
- free(handler);
- return 0;
+ ERROR("[API %s] event handler %s not found", export->api.apiname, pattern);
+ errno = ENOENT;
+ return -1;
}
/******************************************************************************
@@ -1346,13 +1306,9 @@ void afb_export_unref(struct afb_export *export)
void afb_export_destroy(struct afb_export *export)
{
- struct event_handler *handler;
-
if (export) {
- while ((handler = export->event_handlers)) {
- export->event_handlers = handler->next;
- free(handler);
- }
+ if (export->event_handlers)
+ globset_destroy(export->event_handlers);
if (export->listener != NULL)
afb_evt_listener_unref(export->listener);
afb_session_unref(export->session);
diff --git a/src/globset.c b/src/globset.c
new file mode 100644
index 00000000..228d8523
--- /dev/null
+++ b/src/globset.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author: José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "globset.h"
+
+#define GLOB '*'
+
+/*************************************************************************
+ * internal types
+ ************************************************************************/
+
+/**
+ * internal structure for handling patterns
+ */
+struct pathndl
+{
+ /* link to the next handler of the list */
+ struct pathndl *next;
+
+ /* the hash value for the pattern */
+ size_t hash;
+
+ /* the handler */
+ struct globset_handler handler;
+};
+
+/**
+ * Structure that handles a set of global pattern handlers
+ */
+struct globset
+{
+ /** linked list of global patterns */
+ struct pathndl *globs;
+
+ /** hash dictionary of exact matches */
+ struct pathndl **exacts;
+
+ /** mask used for accessing dictionary of exact matches */
+ unsigned gmask;
+
+ /** count of handlers stored in the dictionary of exact matches */
+ unsigned count;
+};
+
+/**
+ * Matches whether the string 'str' matches the pattern 'pat'
+ * and returns its matching score.
+ *
+ * @param pat the glob pattern
+ * @param str the string to match
+ * @return 0 if no match or number representing the matching score
+ */
+static unsigned match(const char *pat, const char *str)
+{
+ unsigned r, rs, rr;
+ char c;
+
+ /* scan the prefix without glob */
+ r = 1;
+ while ((c = *pat++) != GLOB) {
+ if (c != *str++)
+ return 0; /* no match */
+ if (!c)
+ return r; /* match up to end */
+ r++;
+ }
+
+ /* glob found */
+ c = *pat++;
+ if (!c)
+ return r; /* not followed by pattern */
+
+ /* evaluate the best score for following pattern */
+ rs = 0;
+ while (*str) {
+ if (*str++ == c) {
+ /* first char matches, check remaining string */
+ rr = match(pat, str);
+ if (rr > rs)
+ rs = rr;
+ }
+ }
+
+ /* best score or not match if rs == 0 */
+ return rs ? rs + r : 0;
+}
+
+/**
+ * Normalize the string 'from' to the string 'to' and computes the hash code.
+ * The normalization translates upper characters to lower characters.
+ * The returned hash code is greater than zero for exact patterns or zero
+ * for global patterns.
+ *
+ * @param from string to normalize
+ * @param to where to store the normalization
+ * @return 0 if 'from' is a glob pattern or the hash code for exacts patterns
+ */
+static unsigned normhash(const char *from, char *to)
+{
+ unsigned i, hash;
+ int isglob;
+ char c;
+
+ isglob = 0; /* is glob? */
+ i = hash = 0; /* index and hash code */
+
+ /* copy the normalized string and compute isglob and hash */
+ while ((c = from[i])) {
+ if (c == GLOB) {
+ /* it is a glob pattern */
+ isglob = 1;
+ hash = 0;
+ } else {
+ /* normalize to lower */
+ if (c >= 'A' && c <= 'Z')
+ c = (char)(c + 'a' - 'A');
+ /* compute hash if not glob */
+ if (!isglob) {
+ hash += (unsigned)(unsigned char)c;
+ hash += hash << 10;
+ hash ^= hash >> 6;
+ }
+ }
+ to[i++] = c;
+ }
+ to[i] = c;
+ if (!isglob) {
+ /* finalize hash if not glob */
+ hash += i;
+ hash += hash << 3;
+ hash ^= hash >> 11;
+ hash += hash << 15;
+ if (hash == 0)
+ hash = 1 + i;
+ }
+ return hash;
+}
+
+/**
+ * Ensure that there is enough place to store one more exact pattern
+ * in the dictionary.
+ *
+ * @param set the set that has to grow if needed
+ * @return 0 in case of success or -1 on case of error
+ */
+static int grow(struct globset *set)
+{
+ unsigned old_gmask, new_gmask, i;
+ struct pathndl **new_exacts, **old_exacts, *ph, *next_ph;
+
+ /* test if grow is needed */
+ old_gmask = set->gmask;
+ if (old_gmask - (old_gmask >> 2) > set->count + 1)
+ return 0;
+
+ /* compute the new mask */
+ new_gmask = (old_gmask << 1) | 15;
+ if (new_gmask + 1 <= new_gmask) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ /* allocates the new array */
+ new_exacts = calloc(1 + (size_t)new_gmask, sizeof *new_exacts);
+ if (!new_exacts)
+ return -1;
+
+ /* copy values */
+ old_exacts = set->exacts;
+ set->exacts = new_exacts;
+ set->gmask = new_gmask;
+ if (old_gmask) {
+ for(i = 0 ; i <= old_gmask ; i++) {
+ ph = old_exacts[i];
+ while (ph) {
+ next_ph = ph->next;
+ ph->next = new_exacts[ph->hash & new_gmask];
+ new_exacts[ph->hash & new_gmask] = ph;
+ ph = next_ph;
+ }
+ }
+ free(old_exacts);
+ }
+
+ /* release the previous values */
+ return 0;
+}
+
+/**
+ * Search in set the handler for the normalized pattern 'normal' of 'has' code.
+ *
+ * @param set the set to search
+ * @param normal the normalized pattern
+ * @param hash hash code of the normalized pattern
+ * @param pprev pointer where to store the pointer pointing to the returned result
+ * @return the handler found, can be NULL
+ */
+static struct pathndl *search(
+ struct globset *set,
+ const char *normal,
+ unsigned hash,
+ struct pathndl ***pprev)
+{
+ struct pathndl *ph, **pph;
+
+ if (!hash)
+ pph = &set->globs;
+ else if (set->exacts)
+ pph = &set->exacts[hash & set->gmask];
+ else
+ return NULL;
+ while ((ph = *pph) && strcmp(normal, ph->handler.pattern))
+ pph = &ph->next;
+ *pprev = pph;
+ return ph;
+}
+
+
+
+
+
+
+
+
+/**
+ * Allocates a new set of handlers
+ *
+ * @return the new allocated global pattern set of handlers or NULL on failure
+ */
+struct globset *globset_create()
+{
+ return calloc(1, sizeof(struct globset));
+}
+
+/**
+ * Destroy the set
+ * @param set the set to destroy
+ */
+void globset_destroy(struct globset *set)
+{
+ unsigned i;
+ struct pathndl *ph, *next_ph;
+
+ /* free exact pattern handlers */
+ if (set->gmask) {
+ for(i = 0 ; i <= set->gmask ; i++) {
+ ph = set->exacts[i];
+ while (ph) {
+ next_ph = ph->next;
+ free(ph);
+ ph = next_ph;
+ }
+ }
+ free(set->exacts);
+ }
+
+ /* free global pattern handlers */
+ ph = set->globs;
+ while (ph) {
+ next_ph = ph->next;
+ free(ph);
+ ph = next_ph;
+ }
+
+ /* free the set */
+ free(set);
+}
+
+/**
+ * Add a handler for the given pattern
+ * @param set the set
+ * @param pattern the pattern of the handler
+ * @param callback the handler's callback
+ * @param closure the handler's closure
+ * @return 0 in case of success or -1 in case of error (ENOMEM, EEXIST)
+ */
+int globset_add(
+ struct globset *set,
+ const char *pattern,
+ void *callback,
+ void *closure)
+{
+ struct pathndl *ph, **pph;
+ unsigned hash;
+ size_t len;
+ char *pat;
+
+ /* normalize */
+ len = strlen(pattern);
+ pat = alloca(1 + len);
+ hash = normhash(pattern, pat);
+
+ /* grows if needed */
+ if (hash && grow(set))
+ return -1;
+
+ /* search */
+ ph = search(set, pat, hash, &pph);
+ if (ph) {
+ /* found */
+ errno = EEXIST;
+ return -1;
+ }
+
+ /* not found, create it */
+ ph = malloc(len + sizeof *ph);
+ if (!ph)
+ return -1;
+
+ /* initialize it */
+ ph->next = NULL;
+ ph->hash = hash;
+ ph->handler.callback = callback;
+ ph->handler.closure = closure;
+ strcpy(ph->handler.pattern, pat);
+ *pph = ph;
+ if (hash)
+ set->count++;
+ return 0;
+}
+
+/**
+ * Delete the handler for the pattern
+ * @param set the set
+ * @param pattern the pattern to delete
+ * @param closure where to put the closure if not NULL
+ * @return 0 in case of success or -1 in case of error (ENOENT)
+ */
+int globset_del(
+ struct globset *set,
+ const char *pattern,
+ void **closure)
+{
+ struct pathndl *ph, **pph;
+ unsigned hash;
+ char *pat;
+
+ /* normalize */
+ pat = alloca(1 + strlen(pattern));
+ hash = normhash(pattern, pat);
+
+ /* search */
+ ph = search(set, pat, hash, &pph);
+ if (!ph) {
+ /* not found */
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* found, remove it */
+ if (hash)
+ set->count--;
+ *pph = ph->next;
+
+ /* store the closure back */
+ if (closure)
+ *closure = ph->handler.closure;
+
+ /* free the memory */
+ free(ph);
+ return 0;
+}
+
+/**
+ * Search the handler of 'pattern'
+ * @param set the set
+ * @param pattern the pattern to search
+ * @return the handler found or NULL
+ */
+struct globset_handler *globset_search(
+ struct globset *set,
+ const char *pattern)
+{
+ struct pathndl *ph, **pph;
+ unsigned hash;
+ char *pat;
+
+ /* local normalization */
+ pat = alloca(1 + strlen(pattern));
+ hash = normhash(pattern, pat);
+
+ /* search */
+ ph = search(set, pat, hash, &pph);
+ return ph ? &ph->handler : NULL;
+}
+
+/**
+ * Search a handler for the string 'text'
+ * @param set the set
+ * @param text the text to match
+ * @return the handler found or NULL
+ */
+const struct globset_handler *globset_match(
+ struct globset *set,
+ const char *text)
+{
+ struct pathndl *ph, *iph;
+ unsigned hash, g, s;
+ char *txt;
+
+ /* local normalization */
+ txt = alloca(1 + strlen(text));
+ hash = normhash(text, txt);
+
+ /* first, look in dictionary of exact matches */
+ ph = NULL;
+ if (hash && set->exacts) {
+ ph = set->exacts[hash & set->gmask];
+ while(ph && (ph->hash != hash || strcmp(txt, ph->handler.pattern)))
+ ph = ph->next;
+ }
+
+ /* then if not found */
+ if (ph == NULL) {
+ /* look in glob patterns for the best match */
+ s = 0;
+ iph = set->globs;
+ while(iph) {
+ g = match(iph->handler.pattern, txt);
+ if (g > s) {
+ s = g;
+ ph = iph;
+ }
+ iph = iph->next;
+ }
+ }
+ return ph ? &ph->handler : NULL;
+}
+
diff --git a/src/globset.h b/src/globset.h
new file mode 100644
index 00000000..40ed2fad
--- /dev/null
+++ b/src/globset.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author: José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+struct globset_handler
+{
+ /* callback of the handler */
+ void *callback;
+
+ /* closure of the handler */
+ void *closure;
+
+ /* the pattern */
+ char pattern[1];
+};
+
+struct globset;
+
+extern struct globset *globset_create();
+
+extern void globset_destroy(struct globset *set);
+
+extern int globset_add(
+ struct globset *set,
+ const char *pattern,
+ void *callback,
+ void *closure);
+
+extern int globset_del(
+ struct globset *set,
+ const char *pattern,
+ void **closure);
+
+extern struct globset_handler *globset_search(
+ struct globset *set,
+ const char *pattern);
+
+extern const struct globset_handler *globset_match(
+ struct globset *set,
+ const char *text);
+
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index b516a9db..b0ddbdbf 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -25,6 +25,7 @@ if(check_FOUND)
add_subdirectory(apiset)
add_subdirectory(apiv3)
add_subdirectory(wrap-json)
+ add_subdirectory(globset)
else(check_FOUND)
MESSAGE(WARNING "check not found! no test!")
endif(check_FOUND)
diff --git a/src/tests/globset/CMakeLists.txt b/src/tests/globset/CMakeLists.txt
new file mode 100644
index 00000000..6bc9e600
--- /dev/null
+++ b/src/tests/globset/CMakeLists.txt
@@ -0,0 +1,23 @@
+###########################################################################
+# Copyright (C) 2017, 2018 "IoT.bzh"
+#
+# author: José Bollo <jose.bollo@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+add_executable(test-globset test-globset.c)
+target_include_directories(test-globset PRIVATE ../..)
+target_link_libraries(test-globset afb-lib ${link_libraries})
+add_test(NAME globset COMMAND test-globset)
+
diff --git a/src/tests/globset/globset.in b/src/tests/globset/globset.in
new file mode 100644
index 00000000..3aa5bd52
--- /dev/null
+++ b/src/tests/globset/globset.in
@@ -0,0 +1,30 @@
+kilil
+?kilil
++kilil
+kilil
+?kilil
+kil
+-error
++kilil
+-kilil
++kilil
++kilil
++kil*
+kilil
+kililo
++*/*
+ryjt
+rthetrfh
+trhzrt/rztyrt
++zer*o*
+zero
+zerksdjfhkjdsffbhqkdbfdozz
+zer/o
++a*
++az*
++aze*
+alij
+azdfbgfhnb
+az
+azefgnjfghn,dfnh
+qsd
diff --git a/src/tests/globset/globset.out b/src/tests/globset/globset.out
new file mode 100644
index 00000000..e0f4c074
--- /dev/null
+++ b/src/tests/globset/globset.out
@@ -0,0 +1,30 @@
+match [kilil]: NOT FOUND
+search [kilil]: NOT FOUND
+add [kilil]: 0, Success
+match [kilil]: found by kilil
+search [kilil]: found
+match [kil]: NOT FOUND
+del [error]: -1, No such file or directory
+add [kilil]: -1, File exists
+del [kilil]: 0, Success
+add [kilil]: 0, Success
+add [kilil]: -1, File exists
+add [kil*]: 0, Success
+match [kilil]: found by kilil
+match [kililo]: found by kil*
+add [*/*]: 0, Success
+match [ryjt]: NOT FOUND
+match [rthetrfh]: NOT FOUND
+match [trhzrt/rztyrt]: found by */*
+add [zer*o*]: 0, Success
+match [zero]: found by zer*o*
+match [zerksdjfhkjdsffbhqkdbfdozz]: found by zer*o*
+match [zer/o]: found by zer*o*
+add [a*]: 0, Success
+add [az*]: 0, Success
+add [aze*]: 0, Success
+match [alij]: found by a*
+match [azdfbgfhnb]: found by az*
+match [az]: found by az*
+match [azefgnjfghn,dfnh]: found by aze*
+match [qsd]: NOT FOUND
diff --git a/src/tests/globset/test-globset.c b/src/tests/globset/test-globset.c
new file mode 100644
index 00000000..6369332a
--- /dev/null
+++ b/src/tests/globset/test-globset.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author: José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "globset.h"
+
+int main()
+{
+ int rc;
+ char buffer[1024], *str;
+ const struct globset_handler *gh;
+ struct globset *set;
+
+ setvbuf(stdout, NULL, _IOLBF, 0);
+ set = globset_create();
+ while (fgets(buffer, sizeof buffer, stdin)) {
+ str = strchr(buffer,'\n');
+ if (str) *str = 0;
+ errno = 0;
+ switch (buffer[0]) {
+ case '+':
+ rc = globset_add(set, &buffer[1], NULL, NULL);
+ printf("add [%s]: %d, %m\n", &buffer[1], rc);
+ break;
+ case '-':
+ rc = globset_del(set, &buffer[1], NULL);
+ printf("del [%s]: %d, %m\n", &buffer[1], rc);
+ break;
+ case '?':
+ gh = globset_search(set, &buffer[1]);
+ printf("search [%s]: %s%s\n", &buffer[1], gh ? "found " : "NOT FOUND", gh ? gh->pattern : "");
+ break;
+ default:
+ gh = globset_match(set, buffer);
+ printf("match [%s]: %s%s\n", buffer, gh ? "found by " : "NOT FOUND", gh ? gh->pattern : "");
+ break;
+ }
+ }
+ globset_destroy(set);
+ return 0;
+}