2 * Copyright (C) 2018, 2019 "IoT.bzh"
3 * Author: José Bollo <jose.bollo@iot.bzh>
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
25 #include "globmatch.h"
27 /*************************************************************************
29 ************************************************************************/
32 * internal structure for handling patterns
36 /* link to the next handler of the list */
39 /* the hash value for the pattern */
43 struct globset_handler handler;
47 * Structure that handles a set of global pattern handlers
51 /** linked list of global patterns */
52 struct pathndl *globs;
54 /** hash dictionary of exact matches */
55 struct pathndl **exacts;
57 /** mask used for accessing dictionary of exact matches */
60 /** count of handlers stored in the dictionary of exact matches */
65 * Normalize the string 'from' to the string 'to' and computes the hash code.
66 * The normalization translates upper characters to lower characters.
67 * The returned hash code is greater than zero for exact patterns or zero
68 * for global patterns.
70 * @param from string to normalize
71 * @param to where to store the normalization
72 * @return 0 if 'from' is a glob pattern or the hash code for exacts patterns
74 static unsigned normhash(const char *from, char *to)
80 isglob = 0; /* is glob? */
81 i = hash = 0; /* index and hash code */
83 /* copy the normalized string and compute isglob and hash */
84 while ((c = from[i])) {
86 /* it is a glob pattern */
90 /* normalize to lower */
91 if (c >= 'A' && c <= 'Z')
92 c = (char)(c + 'a' - 'A');
93 /* compute hash if not glob */
95 hash += (unsigned)(unsigned char)c;
104 /* finalize hash if not glob */
116 * Ensure that there is enough place to store one more exact pattern
119 * @param set the set that has to grow if needed
120 * @return 0 in case of success or -1 on case of error
122 static int grow(struct globset *set)
124 unsigned old_gmask, new_gmask, i;
125 struct pathndl **new_exacts, **old_exacts, *ph, *next_ph;
127 /* test if grow is needed */
128 old_gmask = set->gmask;
129 if (old_gmask - (old_gmask >> 2) > set->count + 1)
132 /* compute the new mask */
133 new_gmask = (old_gmask << 1) | 15;
134 if (new_gmask + 1 <= new_gmask) {
139 /* allocates the new array */
140 new_exacts = calloc(1 + (size_t)new_gmask, sizeof *new_exacts);
145 old_exacts = set->exacts;
146 set->exacts = new_exacts;
147 set->gmask = new_gmask;
149 for(i = 0 ; i <= old_gmask ; i++) {
153 ph->next = new_exacts[ph->hash & new_gmask];
154 new_exacts[ph->hash & new_gmask] = ph;
161 /* release the previous values */
166 * Search in set the handler for the normalized pattern 'normal' of 'has' code.
168 * @param set the set to search
169 * @param normal the normalized pattern
170 * @param hash hash code of the normalized pattern
171 * @param pprev pointer where to store the pointer pointing to the returned result
172 * @return the handler found, can be NULL
174 static struct pathndl *search(
178 struct pathndl ***pprev)
180 struct pathndl *ph, **pph;
184 else if (set->exacts)
185 pph = &set->exacts[hash & set->gmask];
188 while ((ph = *pph) && strcmp(normal, ph->handler.pattern))
195 * Allocates a new set of handlers
197 * @return the new allocated global pattern set of handlers or NULL on failure
199 struct globset *globset_create()
201 return calloc(1, sizeof(struct globset));
206 * @param set the set to destroy
208 void globset_destroy(struct globset *set)
211 struct pathndl *ph, *next_ph;
213 /* free exact pattern handlers */
215 for(i = 0 ; i <= set->gmask ; i++) {
226 /* free global pattern handlers */
239 * Add a handler for the given pattern
241 * @param pattern the pattern of the handler
242 * @param callback the handler's callback
243 * @param closure the handler's closure
244 * @return 0 in case of success or -1 in case of error (ENOMEM, EEXIST)
252 struct pathndl *ph, **pph;
258 len = strlen(pattern);
259 pat = alloca(1 + len);
260 hash = normhash(pattern, pat);
262 /* grows if needed */
263 if (hash && grow(set))
267 ph = search(set, pat, hash, &pph);
274 /* not found, create it */
275 ph = malloc(len + sizeof *ph);
282 ph->handler.callback = callback;
283 ph->handler.closure = closure;
284 strcpy(ph->handler.pattern, pat);
292 * Delete the handler for the pattern
294 * @param pattern the pattern to delete
295 * @param closure where to put the closure if not NULL
296 * @return 0 in case of success or -1 in case of error (ENOENT)
303 struct pathndl *ph, **pph;
308 pat = alloca(1 + strlen(pattern));
309 hash = normhash(pattern, pat);
312 ph = search(set, pat, hash, &pph);
319 /* found, remove it */
324 /* store the closure back */
326 *closure = ph->handler.closure;
328 /* free the memory */
334 * Search the handler of 'pattern'
336 * @param pattern the pattern to search
337 * @return the handler found or NULL
339 struct globset_handler *globset_search(
343 struct pathndl *ph, **pph;
347 /* local normalization */
348 pat = alloca(1 + strlen(pattern));
349 hash = normhash(pattern, pat);
352 ph = search(set, pat, hash, &pph);
353 return ph ? &ph->handler : NULL;
357 * Search a handler for the string 'text'
359 * @param text the text to match
360 * @return the handler found or NULL
362 const struct globset_handler *globset_match(
366 struct pathndl *ph, *iph;
370 /* local normalization */
371 txt = alloca(1 + strlen(text));
372 hash = normhash(text, txt);
374 /* first, look in dictionary of exact matches */
376 if (hash && set->exacts) {
377 ph = set->exacts[hash & set->gmask];
378 while(ph && (ph->hash != hash || strcmp(txt, ph->handler.pattern)))
382 /* then if not found */
384 /* look in glob patterns for the best match */
388 g = globmatch(iph->handler.pattern, txt);
396 return ph ? &ph->handler : NULL;