1 /*
2 ** Copyright (C) 2005-2020 by Carnegie Mellon University.
3 **
4 ** @OPENSOURCE_LICENSE_START@
5 ** See license information in ../../LICENSE.txt
6 ** @OPENSOURCE_LICENSE_END@
7 */
8 
9 #include <silk/silk.h>
10 
11 RCSIDENT("$SiLK: skstringmap.c ef14e54179be 2020-04-14 21:57:45Z mthomas $");
12 
13 #include <silk/skdllist.h>
14 #include <silk/skstringmap.h>
15 #include <silk/skvector.h>
16 #include <silk/utils.h>
17 
18 
19 /* typedef struct sk_stringmap_iter_st sk_stringmap_iter_t; */
20 struct sk_stringmap_iter_st {
21     sk_vector_t    *vec;
22     size_t          pos;
23     unsigned        has_attr :1;
24 };
25 
26 /* objects to put inside the iterator */
27 typedef struct stringmap_iter_node_st {
28     sk_stringmap_entry_t   *entry;
29     const char             *attr;
30 } stringmap_iter_node_t;
31 
32 
33 /* LOCAL VARIABLES */
34 
35 /* value to use for no attributes */
36 static const char *stringmap_no_attr = "";
37 
38 /* for returning an error to the caller */
39 static char errbuf[2 * PATH_MAX];
40 
41 
42 /* LOCAL FUNCTION DECLARATIONS */
43 
44 static sk_stringmap_status_t
45 stringMapCheckValidName(
46     sk_stringmap_t     *str_map,
47     const char         *name);
48 
49 static void
50 stringMapFreeEntry(
51     sk_stringmap_entry_t   *map_entry);
52 
53 
54 /* FUNCTION DEFINITIONS */
55 
56 /* Create a new string map */
57 sk_stringmap_status_t
skStringMapCreate(sk_stringmap_t ** out_str_map)58 skStringMapCreate(
59     sk_stringmap_t    **out_str_map)
60 {
61     *out_str_map = skDLListCreate((sk_dll_free_fn_t)&stringMapFreeEntry);
62     if (*out_str_map == NULL) {
63         return SKSTRINGMAP_ERR_MEM;
64     }
65 
66     return SKSTRINGMAP_OK;
67 }
68 
69 
70 /* Destroy a string map */
71 sk_stringmap_status_t
skStringMapDestroy(sk_stringmap_t * str_map)72 skStringMapDestroy(
73     sk_stringmap_t     *str_map)
74 {
75     skDLListDestroy(str_map);
76     return SKSTRINGMAP_OK;
77 }
78 
79 
80 /* add multiple keys to a StringMap */
81 sk_stringmap_status_t
skStringMapAddEntries(sk_stringmap_t * str_map,int entryc,const sk_stringmap_entry_t * entryv)82 skStringMapAddEntries(
83     sk_stringmap_t             *str_map,
84     int                         entryc,
85     const sk_stringmap_entry_t *entryv)
86 {
87     sk_stringmap_entry_t *map_entry = NULL;
88     sk_stringmap_entry_t *node = NULL;
89     sk_dll_iter_t map_node;
90     const sk_stringmap_entry_t *e;
91     sk_stringmap_status_t rv;
92     int i;
93     int rv_list;
94 
95     /* check inputs */
96     if (str_map == NULL || entryv == NULL) {
97         return SKSTRINGMAP_ERR_INPUT;
98     }
99 
100     /* use "i != entryc" to handle entryc < 0 */
101     for (i = 0, e = entryv; i != entryc && e->name != NULL; ++i, ++e) {
102         /* check to see if the name is valid */
103         rv = stringMapCheckValidName(str_map, e->name);
104         if (SKSTRINGMAP_OK != rv) {
105             return rv;
106         }
107     }
108     if (entryc < 0) {
109         entryc = i;
110     } else if (i < entryc) {
111         /* NULL name given within the first entryc entries */
112         return SKSTRINGMAP_ERR_INPUT;
113     }
114 
115 #if 0
116     for (i = 0, e = entryv; i < entryc; ++i, ++e) {
117         if (e->description) {
118             fprintf(stderr, "skStringMapAddEntry('%s', %u, '%s', %p)\n",
119                     e->name, e->id, (char*)e->description, e->userdata);
120         } else {
121             fprintf(stderr, "skStringMapAddEntry('%s', %u, NULL, %p)\n",
122                     e->name, e->id, e->userdata);
123         }
124     }
125 #endif  /* 0 */
126 
127     for (i = 0, e = entryv; i < entryc; ++i, ++e) {
128         assert(e->name);
129         /* allocate entry */
130         map_entry = (sk_stringmap_entry_t*)malloc(sizeof(sk_stringmap_entry_t));
131         if (NULL == map_entry) {
132             return SKSTRINGMAP_ERR_MEM;
133         }
134 
135         /* copy the entry from the caller */
136         map_entry->id = e->id;
137         map_entry->userdata = e->userdata;
138         map_entry->description = NULL;
139 
140         /* duplicate strings for our own use */
141         map_entry->name = strdup(e->name);
142         if (NULL == map_entry->name) {
143             rv = SKSTRINGMAP_ERR_MEM;
144             goto ERROR;
145         }
146         if (e->description) {
147             map_entry->description = strdup((const char*)e->description);
148             if (NULL == map_entry->description) {
149                 rv = SKSTRINGMAP_ERR_MEM;
150                 goto ERROR;
151             }
152         }
153 
154         /* if this entry has the same ID as an existing entry, add the
155          * new entry after the existing entry */
156         skDLLAssignIter(&map_node, str_map);
157         while (skDLLIterBackward(&map_node, (void **)&node) == 0) {
158             if (node->id == map_entry->id) {
159                 if (skDLLIterAddAfter(&map_node, (void*)map_entry)) {
160                     rv = SKSTRINGMAP_ERR_MEM;
161                     goto ERROR;
162                 }
163                 map_entry = NULL;
164                 break;
165             }
166         }
167 
168         if (map_entry) {
169             /* add entry to end of list */
170             rv_list = skDLListPushTail(str_map, (void *)map_entry);
171             if (rv_list != 0) {
172                 rv = SKSTRINGMAP_ERR_MEM;
173                 goto ERROR;
174             }
175         }
176     }
177 
178     return SKSTRINGMAP_OK;
179 
180   ERROR:
181     stringMapFreeEntry(map_entry);
182     return rv;
183 }
184 
185 
186 /* remove a key from a StringMap */
187 sk_stringmap_status_t
skStringMapRemoveByName(sk_stringmap_t * str_map,const char * name)188 skStringMapRemoveByName(
189     sk_stringmap_t     *str_map,
190     const char         *name)
191 {
192     int rv_list;
193     sk_dll_iter_t map_node;
194     sk_stringmap_entry_t *map_entry;
195 
196     /* check inputs */
197     if (str_map == NULL || name == NULL) {
198         return SKSTRINGMAP_ERR_INPUT;
199     }
200 
201     skDLLAssignIter(&map_node, str_map);
202     while (skDLLIterForward(&map_node, (void **)&map_entry) == 0) {
203         if (strcasecmp(map_entry->name, name) == 0) {
204             rv_list = skDLLIterDel(&map_node);
205             if (rv_list != 0) {
206                 assert(0);
207                 return SKSTRINGMAP_ERR_LIST;
208             }
209             stringMapFreeEntry(map_entry);
210         }
211     }
212 
213     return SKSTRINGMAP_OK;
214 }
215 
216 
217 /* remove all entries have given ID from a StringMap */
218 sk_stringmap_status_t
skStringMapRemoveByID(sk_stringmap_t * str_map,sk_stringmap_id_t id)219 skStringMapRemoveByID(
220     sk_stringmap_t     *str_map,
221     sk_stringmap_id_t   id)
222 {
223     int rv_list;
224     sk_dll_iter_t map_node;
225     sk_stringmap_entry_t *map_entry;
226 
227     /* check inputs */
228     if (str_map == NULL) {
229         return SKSTRINGMAP_ERR_INPUT;
230     }
231 
232     skDLLAssignIter(&map_node, str_map);
233     while (skDLLIterForward(&map_node, (void **)&map_entry) == 0) {
234         if (id == map_entry->id) {
235             rv_list = skDLLIterDel(&map_node);
236             if (rv_list != 0) {
237                 assert(0);
238                 return SKSTRINGMAP_ERR_LIST;
239             }
240             stringMapFreeEntry(map_entry);
241         }
242     }
243 
244     return SKSTRINGMAP_OK;
245 }
246 
247 
248 /* remove multiple keys from a StringMap */
249 sk_stringmap_status_t
skStringMapRemoveEntries(sk_stringmap_t * str_map,int entryc,const sk_stringmap_entry_t * entryv)250 skStringMapRemoveEntries(
251     sk_stringmap_t             *str_map,
252     int                         entryc,
253     const sk_stringmap_entry_t *entryv)
254 {
255     const sk_stringmap_entry_t *e;
256     sk_stringmap_status_t rv;
257     int i;
258 
259     /* check inputs */
260     if (str_map == NULL || entryv == NULL) {
261         return SKSTRINGMAP_ERR_INPUT;
262     }
263 
264     /* use "i != entryc" to handle entryc < 0 */
265     for (i = 0, e = entryv; i != entryc && e->name != NULL; ++i, ++e)
266         ;  /* empty */
267     if (entryc < 0) {
268         entryc = i;
269     } else if (i < entryc) {
270         /* NULL name given within the first entryc entries */
271         return SKSTRINGMAP_ERR_INPUT;
272     }
273 
274     for (i = 0, e = entryv; i < entryc; ++i, ++e) {
275         assert(e->name);
276         rv = skStringMapRemoveByName(str_map, e->name);
277         if (rv != SKSTRINGMAP_OK) {
278             return rv;
279         }
280     }
281 
282     return SKSTRINGMAP_OK;
283 }
284 
285 
286 static sk_stringmap_status_t
stringMapIterCreate(sk_stringmap_iter_t ** iter,const int with_attr)287 stringMapIterCreate(
288     sk_stringmap_iter_t   **iter,
289     const int               with_attr)
290 {
291     sk_stringmap_iter_t *map_iter;
292 
293     assert(iter);
294 
295     map_iter = (sk_stringmap_iter_t*)calloc(1, sizeof(sk_stringmap_iter_t));
296     if (NULL == map_iter) {
297         return SKSTRINGMAP_ERR_MEM;
298     }
299     if (with_attr) {
300         map_iter->vec = skVectorNew(sizeof(stringmap_iter_node_t));
301         map_iter->has_attr = 1;
302     } else {
303         map_iter->vec = skVectorNew(sizeof(sk_stringmap_entry_t*));
304     }
305     if (NULL == map_iter->vec) {
306         free(map_iter);
307         return SKSTRINGMAP_ERR_MEM;
308     }
309     *iter = map_iter;
310     return SKSTRINGMAP_OK;
311 }
312 
313 
314 size_t
skStringMapIterCountMatches(sk_stringmap_iter_t * iter)315 skStringMapIterCountMatches(
316     sk_stringmap_iter_t    *iter)
317 {
318     if (NULL == iter) {
319         return 0;
320     }
321     return skVectorGetCount(iter->vec);
322 }
323 
324 
325 void
skStringMapIterDestroy(sk_stringmap_iter_t * iter)326 skStringMapIterDestroy(
327     sk_stringmap_iter_t    *iter)
328 {
329     stringmap_iter_node_t *node;
330     size_t count;
331     size_t i;
332 
333     if (iter) {
334         if (iter->vec) {
335             if (iter->has_attr) {
336                 count = skVectorGetCount(iter->vec);
337                 for (i = 0; i < count; ++i) {
338                     node = ((stringmap_iter_node_t*)
339                             skVectorGetValuePointer(iter->vec, i));
340                     if (node->attr != stringmap_no_attr) {
341                         free((char*)node->attr);
342                     }
343                 }
344             }
345             skVectorDestroy(iter->vec);
346         }
347         memset(iter, 0, sizeof(sk_stringmap_iter_t));
348         free(iter);
349     }
350 }
351 
352 
353 int
skStringMapIterNext(sk_stringmap_iter_t * iter,sk_stringmap_entry_t ** entry,const char ** attr)354 skStringMapIterNext(
355     sk_stringmap_iter_t    *iter,
356     sk_stringmap_entry_t  **entry,
357     const char            **attr)
358 {
359     stringmap_iter_node_t *iter_node;
360 
361     assert(entry);
362 
363     if (NULL == iter) {
364         return SK_ITERATOR_NO_MORE_ENTRIES;
365     }
366     if (iter->pos >= skVectorGetCount(iter->vec)) {
367         return SK_ITERATOR_NO_MORE_ENTRIES;
368     }
369     if (!iter->has_attr) {
370         skVectorGetValue(entry, iter->vec, iter->pos);
371     } else {
372         iter_node = ((stringmap_iter_node_t*)
373                      skVectorGetValuePointer(iter->vec, iter->pos));
374         *entry = iter_node->entry;
375         if (attr) {
376             *attr = iter_node->attr;
377         }
378     }
379     ++iter->pos;
380     return SK_ITERATOR_OK;
381 }
382 
383 
384 void
skStringMapIterReset(sk_stringmap_iter_t * iter)385 skStringMapIterReset(
386     sk_stringmap_iter_t    *iter)
387 {
388     if (iter) {
389         iter->pos = 0;
390     }
391 }
392 
393 
394 /*
395  *  stringMapFind(str_map, token, token_len, &found_entry);
396  *
397  *    Search in 'str_map' for an entry that matches 'token', whose
398  *    length is 'token_len'.  'token' does not need to be NUL
399  *    terminated.
400  *
401  *    When 'token' is an exact match for an entry or is a prefix for
402  *    one and only one entry, set 'found_entry' to that entry and
403  *    return SKSTRINGMAP_OK.  If 'token' is a prefix for multiple
404  *    entries and does not match a complete entry exactly, set
405  *    'found_entry' to one of the entries and return
406  *    SKSTRINGMAP_PARSE_AMBIGUOUS.  If no match for 'token' is found,
407  *    set 'found_entry' to NULL and return SKSTRINGMAP_PARSE_NO_MATCH.
408  */
409 static sk_stringmap_status_t
stringMapFind(const sk_stringmap_t * str_map,const char * token,const size_t token_len,sk_stringmap_entry_t ** found_entry)410 stringMapFind(
411     const sk_stringmap_t   *str_map,
412     const char             *token,
413     const size_t            token_len,
414     sk_stringmap_entry_t  **found_entry)
415 {
416     sk_dll_iter_t map_node;
417     sk_stringmap_entry_t *map_entry;
418     int unique = 1;
419 
420     assert(found_entry);
421     assert(str_map);
422     assert(token);
423     assert(token_len > 0);
424 
425     *found_entry = NULL;
426 
427     /* Typecast away const.  We are still treating it as const
428      * though. */
429     skDLLAssignIter(&map_node, (sk_stringmap_t *)str_map);
430     /* check the token against each entry in the map */
431     while (skDLLIterForward(&map_node, (void **)&map_entry) == 0) {
432 
433         if (0 != strncasecmp(map_entry->name, token, token_len)) {
434             /* no match, try next entry in the map */
435             continue;
436         }
437 
438         /* first 'token_len' chars match.  check whether exact match */
439         if (map_entry->name[token_len] == '\0') {
440             /* exact match; set found_entry here and return */
441             *found_entry = map_entry;
442             return SKSTRINGMAP_OK;
443         }
444         /* else not an exact match. */
445 
446         if (isdigit((int)*token)) {
447             /* partial number match doesn't make sense; try again */
448             continue;
449         }
450         /* else partial match. */
451 
452         /* If '*found_entry' has not been set, set it to this location
453          * as a potential match.  Else if '*found_entry' is set,
454          * compare the IDs on the current entry and *found_entry and
455          * if they are different, return 'ambiguous'. */
456         if (*found_entry == NULL) {
457             *found_entry = map_entry;
458         } else if ((*found_entry)->id != map_entry->id) {
459             /* The 'token' matches two entries with different IDs;
460              * mark as possibly ambiguous.  Continue in case there is
461              * an exact match. */
462             unique = 0;
463         }
464         /* else token matches two entries that map to same ID, so
465          * allow it and don't complain. */
466     }
467 
468     if (!unique) {
469         /* Multiple matches were found, mark as ambiguous */
470         return SKSTRINGMAP_PARSE_AMBIGUOUS;
471     }
472 
473     /* did we find a match? */
474     if (*found_entry == NULL) {
475         return SKSTRINGMAP_PARSE_NO_MATCH;
476     }
477 
478     return SKSTRINGMAP_OK;
479 }
480 
481 
482 /*
483  *  stringMapFindCheckDupes(str_map, token, token_len, &found_entry, iter, handle_dupes);
484  *
485  *    Search in 'str_map' for an entry that matches 'token', whose
486  *    length is 'token_len'.  'token' does not need to be NUL
487  *    terminated.
488  *
489  *    When 'token' is an exact match for an entry or is a prefix for
490  *    one and only one entry, the result depends on the setting of
491  *    'handle_dupes'.  If 'handle_dupes' is SKSTRINGMAP_DUPES_KEEP,
492  *    'found_entry' is set to that entry and the function returns
493  *    SKSTRINGMAP_OK.  When 'handle_dupes' is SKSTRINGMAP_DUPES_KEEP,
494  *    'iter' may be NULL.  Any other value of 'handle_dupes' requires
495  *    the 'iter' parameter.  If 'handle_dups' is
496  *    SKSTRINGMAP_DUPES_ERROR, set 'found_entry' to the duplicate, put
497  *    an appropriate error message into 'errbuf', and return
498  *    SKSTRINGMAP_ERR_DUPLICATE_ENTRY.  If 'handle_dups' is
499  *    SKSTRINGMAP_DUPES_REMOVE_SILENT, set 'found_entry' to NULL and
500  *    return SKSTRINGMAP_OK.  If 'handle_dups' is
501  *    SKSTRINGMAP_DUPES_REMOVE_WARN, set 'found_entry' to NULL, put a
502  *    warning message into 'errbuf', and return
503  *    SKSTRINGMAP_OK_DUPLICATE.
504  *
505  *    If 'token' is a prefix for multiple entries and does not match a
506  *    complete entry exactly, set 'found_entry' to one of the entries,
507  *    put an appropriate error message into 'errbuf', and return
508  *    SKSTRINGMAP_PARSE_AMBIGUOUS.
509  *
510  *    If no match for 'token' is found, set 'found_entry' to NULL, put
511  *    an appropriate error message into 'errbuf', and return
512  *    SKSTRINGMAP_PARSE_NO_MATCH.
513  */
514 static sk_stringmap_status_t
stringMapFindCheckDupes(const sk_stringmap_t * str_map,const char * token,const size_t token_len,sk_stringmap_entry_t ** found_entry,const sk_stringmap_iter_t * iter,sk_stringmap_dupes_t handle_dupes)515 stringMapFindCheckDupes(
516     const sk_stringmap_t       *str_map,
517     const char                 *token,
518     const size_t                token_len,
519     sk_stringmap_entry_t      **found_entry,
520     const sk_stringmap_iter_t  *iter,
521     sk_stringmap_dupes_t        handle_dupes)
522 {
523 #define COPY_TOKEN                              \
524     if (token_len > sizeof(buf)) {              \
525         strncpy(buf, token, sizeof(buf));       \
526         buf[sizeof(buf)-1] = '\0';              \
527     } else {                                    \
528         strncpy(buf, token, token_len);         \
529         buf[token_len] = '\0';                  \
530     }
531 
532     char buf[PATH_MAX];
533     sk_stringmap_status_t rv;
534     sk_stringmap_entry_t *entry;
535     void *ptr;
536     size_t j;
537 
538     assert(found_entry);
539     assert(str_map);
540     assert(token);
541     assert(token_len > 0);
542     assert(SKSTRINGMAP_DUPES_KEEP == handle_dupes || iter);
543 
544     rv = stringMapFind(str_map, token, token_len, found_entry);
545     switch (rv) {
546       case SKSTRINGMAP_PARSE_AMBIGUOUS:
547         COPY_TOKEN;
548         snprintf(errbuf, sizeof(errbuf),
549                  "The field '%s' matches multiple keys", buf);
550         break;
551 
552       case SKSTRINGMAP_PARSE_NO_MATCH:
553         COPY_TOKEN;
554         snprintf(errbuf, sizeof(errbuf),
555                  "No match was found for the field '%s'", buf);
556         break;
557 
558       case SKSTRINGMAP_OK:
559         /* found a single match. it may be a duplicate */
560         if (SKSTRINGMAP_DUPES_KEEP == handle_dupes) {
561             /* dupes are ok. */
562             break;
563         }
564         /* else search for a duplicate */
565         for (j = 0;
566              NULL != (ptr = skVectorGetValuePointer(iter->vec, j));
567              ++j)
568         {
569             if (iter->has_attr) {
570                 entry = ((stringmap_iter_node_t*)ptr)->entry;
571             } else {
572                 entry = *((sk_stringmap_entry_t**)ptr);
573             }
574             if (entry->id != (*found_entry)->id) {
575                 continue;
576             }
577             /* found a dupe */
578             switch (handle_dupes) {
579               case SKSTRINGMAP_DUPES_ERROR:
580                 rv = SKSTRINGMAP_ERR_DUPLICATE_ENTRY;
581                 COPY_TOKEN;
582                 snprintf(errbuf, sizeof(errbuf),
583                          "Duplicate name '%s'", buf);
584                 break;
585               case SKSTRINGMAP_DUPES_REMOVE_WARN:
586                 rv = SKSTRINGMAP_OK_DUPLICATE;
587                 COPY_TOKEN;
588                 snprintf(errbuf, sizeof(errbuf),
589                          "Ignoring duplicate value '%s'", buf);
590                 /* FALLTHROUGH */
591               case SKSTRINGMAP_DUPES_REMOVE_SILENT:
592                 *found_entry = NULL;
593                 break;
594               case SKSTRINGMAP_DUPES_KEEP:
595                 /* shouldn't be in this loop at all */
596                 skAbortBadCase(handle_dupes);
597             }
598             break;
599         }
600         break;
601 
602       default:
603         snprintf(errbuf, sizeof(errbuf),
604                  "Unexpected return value from field parser (%d)", rv);
605         break;
606     }
607     return rv;
608 }
609 
610 
611 /* match a single key against a StringMap, filling out_entry with a
612  * pointer to the entry. */
613 sk_stringmap_status_t
skStringMapGetByName(const sk_stringmap_t * str_map,const char * user_string,sk_stringmap_entry_t ** out_entry)614 skStringMapGetByName(
615     const sk_stringmap_t   *str_map,
616     const char             *user_string,
617     sk_stringmap_entry_t  **out_entry)
618 {
619     sk_stringmap_status_t rv;
620     sk_stringmap_entry_t *found_entry;
621 
622     /* check inputs */
623     if (out_entry == NULL || str_map == NULL
624         || user_string == NULL || user_string[0] == '\0')
625     {
626         return SKSTRINGMAP_ERR_INPUT;
627     }
628 
629     rv = stringMapFind(str_map, user_string, strlen(user_string),&found_entry);
630     if (rv == SKSTRINGMAP_OK) {
631         *out_entry = found_entry;
632     }
633 
634     return rv;
635 }
636 
637 
638 /* match a single key against a StringMap, filling out_entry with a
639  * pointer to the entry. */
640 sk_stringmap_status_t
skStringMapGetByNameWithAttributes(const sk_stringmap_t * str_map,const char * user_string,sk_stringmap_entry_t ** out_entry,char * attributes,size_t attributes_len)641 skStringMapGetByNameWithAttributes(
642     const sk_stringmap_t   *str_map,
643     const char             *user_string,
644     sk_stringmap_entry_t  **out_entry,
645     char                   *attributes,
646     size_t                  attributes_len)
647 {
648     sk_stringmap_status_t rv;
649     sk_stringmap_entry_t *found_entry;
650     const char *cp;
651     const char *ep;
652     size_t len;
653 
654     /* check inputs */
655     if (out_entry == NULL || str_map == NULL
656         || user_string == NULL || user_string[0] == '\0'
657         || attributes == NULL || attributes_len == 0)
658     {
659         return SKSTRINGMAP_ERR_INPUT;
660     }
661 
662     /* find the start of attributes, and check for invalid
663      * characters. set 'len' to length of field */
664     cp = strpbrk(user_string, ":[]");
665     if (NULL == cp) {
666         attributes[0] = '\0';
667         len = strlen(user_string);
668     } else if ('[' == *cp || ']' == *cp) {
669         return SKSTRINGMAP_PARSE_UNPARSABLE;
670     } else {
671         len = cp - user_string;
672     }
673 
674     /* find the field; set 'out_entry' if successful */
675     rv = stringMapFind(str_map, user_string, len, &found_entry);
676     if (rv != SKSTRINGMAP_OK) {
677         return rv;
678     }
679     *out_entry = found_entry;
680 
681     /* if no attributes, return */
682     if (NULL == cp) {
683         return rv;
684     }
685 
686     /* move 'cp' onto first char in the attributes */
687     ++cp;
688     if ('\0' == *cp) {
689         return SKSTRINGMAP_PARSE_UNPARSABLE;
690     } else if ('[' != *cp) {
691         /* ensure a single attribute */
692         if (strpbrk(cp, ",:[]")) {
693             return SKSTRINGMAP_PARSE_UNPARSABLE;
694         }
695         len = strlen(cp);
696     } else {
697         /* find end of attributes and check for invalid chars */
698         ++cp;
699         ep = strpbrk(cp, ":[]");
700         if ((NULL == ep) || (cp == ep) || (':' == *ep) || ('[' == *ep)
701             || ('\0' != *(ep + 1)))
702         {
703             return SKSTRINGMAP_PARSE_UNPARSABLE;
704         }
705         len = ep - cp;
706     }
707 
708     /* copy attributes and return */
709     if (len >= attributes_len) {
710         return SKSTRINGMAP_PARSE_UNPARSABLE;
711     }
712     strncpy(attributes, cp, attributes_len);
713     attributes[len] = '\0';
714     return rv;
715 }
716 
717 
718 /*
719  *  stringMapGetToken(dest, src, dest_len);
720  *
721  *    Find the end of the current token in 'src' and copy that value
722  *    into 'dest', a buffer of size 'dest_len'.  The end of a token
723  *    normally occurs at the next ','; however, if a '[' is found
724  *    before the next comma, the token extends to the next ']'
725  *    character.  No attempt is made to handled nested '[' and ']'
726  *    characters.
727  *
728  *    If the current token is larger than 'dest_len', copy as much of
729  *    the token as possible into 'dest'.
730  */
731 static void
stringMapGetToken(char * dest,const char * src,size_t dest_len)732 stringMapGetToken(
733     char               *dest,
734     const char         *src,
735     size_t              dest_len)
736 {
737     const char *ep;
738 
739     ep = strpbrk(src, ",[");
740     if (NULL == ep) {
741         strncpy(dest, src, dest_len);
742         dest[dest_len-1] = '\0';
743         return;
744     }
745     if (*ep == '[') {
746         ep = strchr(ep, ']');
747         if (NULL == ep) {
748             strncpy(dest, src, dest_len);
749             dest[dest_len-1] = '\0';
750             return;
751         }
752     }
753     if (ep - src < (ssize_t)dest_len) {
754         dest_len = 1 + ep - src;
755     }
756     strncpy(dest, src, dest_len);
757     dest[dest_len - 1] = '\0';
758 }
759 
760 
761 /*
762  *  status = stringMapSetAttribute(iter, attr_str, attr_str_len);
763  *
764  *    Copy 'attr_str_len' characters from the string 'attr_str' into
765  *    newly allocated memory and then set the 'attr' field for the
766  *    final value in the stringmap iterator 'iter'.  Return
767  *    SKSTRINGMAP_ERR_MEM if the allocation operation fails; otherwise
768  *    return SKSTRINGMAP_OK.
769  */
770 static sk_stringmap_status_t
stringMapSetAttribute(sk_stringmap_iter_t * iter,const char * attribute,size_t len)771 stringMapSetAttribute(
772     sk_stringmap_iter_t    *iter,
773     const char             *attribute,
774     size_t                  len)
775 {
776     stringmap_iter_node_t *iter_node;
777     char *cp;
778 
779     assert(iter);
780     assert(iter->vec);
781     assert(sizeof(stringmap_iter_node_t) == skVectorGetElementSize(iter->vec));
782     assert(skVectorGetCount(iter->vec) > 0);
783     assert(attribute);
784     assert(len);
785 
786     cp = (char*)malloc(1 + len);
787     if (NULL == cp) {
788         return SKSTRINGMAP_ERR_MEM;
789     }
790     strncpy(cp, attribute, len);
791     cp[len] = '\0';
792 
793     iter_node = ((stringmap_iter_node_t*)
794                  skVectorGetValuePointer(iter->vec,
795                                          skVectorGetCount(iter->vec) - 1));
796     assert(iter_node);
797     assert(stringmap_no_attr == iter_node->attr);
798 
799     iter_node->attr = cp;
800     return SKSTRINGMAP_OK;
801 }
802 
803 
804 /*
805  *    A helper function for skStringMapMatch(), skStringMapParse(),
806  *    and skStringMapParseWithAttributes().
807  */
808 static sk_stringmap_status_t
stringMapParseHelper(const sk_stringmap_t * str_map,const char * user_string,const sk_stringmap_dupes_t handle_dupes,const int wants_attr,sk_stringmap_iter_t ** out_iter,char ** bad_token,char ** errmsg)809 stringMapParseHelper(
810     const sk_stringmap_t           *str_map,
811     const char                     *user_string,
812     const sk_stringmap_dupes_t      handle_dupes,
813     const int                       wants_attr,
814     sk_stringmap_iter_t           **out_iter,
815     char                          **bad_token,
816     char                          **errmsg)
817 {
818     char buf[PATH_MAX];
819 
820     const char *delim;
821     const char *cp;
822     const char *ep;
823     const char *attr_start;
824 
825     int saw_dupes = 0;
826 
827     sk_stringmap_status_t rv;
828     sk_stringmap_entry_t *entry;
829 
830     sk_stringmap_iter_t *iter = NULL;
831     stringmap_iter_node_t iter_node;
832 
833     uint32_t range_beg;
834     uint32_t range_end;
835     uint32_t i;
836     int parse_rv;
837     int vec_rv;
838 
839     int len;
840 
841     enum parse_state_en {
842         SM_START, SM_PARTIAL, SM_ATTR_LIST, SM_ATTR
843     } state;
844 
845     /* check inputs */
846     if (str_map == NULL || out_iter == NULL || user_string == NULL) {
847         snprintf(errbuf, sizeof(errbuf),
848                  "Programmer error: NULL passed to function");
849         rv = SKSTRINGMAP_ERR_INPUT;
850         goto END;
851     }
852 
853     cp = user_string;
854     while (isspace((int)*cp)) {
855         ++cp;
856     }
857     if (*cp == '\0') {
858         snprintf(errbuf, sizeof(errbuf), "Field list is empty");
859         rv = SKSTRINGMAP_ERR_INPUT;
860         goto END;
861     }
862 
863     /* initialize values */
864     rv = stringMapIterCreate(&iter, wants_attr);
865     if (rv) {
866         goto END;
867     }
868     if (wants_attr) {
869         delim = ":,-[]";
870     } else {
871         delim = ",-";
872     }
873     state = SM_START;
874     ep = cp;
875     attr_start = cp;
876     saw_dupes = 0;
877 
878     while (*cp) {
879         ep = strpbrk(ep, delim);
880         if (ep == NULL) {
881             /* at end of string; put 'ep' on the final \0. */
882             for (ep = cp + 1; *ep; ++ep)
883                 ; /* empty */
884         }
885         switch (state) {
886           case SM_START:
887             if (ep == cp) {
888                 if (',' == *cp) {
889                     /* double delimiter */
890                     ++cp;
891                     ep = cp;
892                     continue;
893                 }
894                 /* else error; report bad token */
895                 stringMapGetToken(buf, cp, sizeof(buf));
896                 if (bad_token) {
897                     *bad_token = strdup(buf);
898                 }
899                 snprintf(errbuf, sizeof(errbuf),
900                          "Invalid character at start of name '%s'", buf);
901                 rv = SKSTRINGMAP_PARSE_UNPARSABLE;
902                 goto END;
903             }
904             if (isdigit(*cp) && *ep == '-') {
905                 /* looks like a numeric range */
906                 parse_rv = skStringParseUint32(&range_beg, cp, 0, 0);
907                 if (parse_rv < 0 || parse_rv != ep - cp) {
908                     /* error; report bad token */
909                     stringMapGetToken(buf, cp, sizeof(buf));
910                     if (bad_token) {
911                         *bad_token = strdup(buf);
912                     }
913                     snprintf(errbuf, sizeof(errbuf),
914                              "Cannot parse start of numeric range '%s'", buf);
915                     rv = SKSTRINGMAP_PARSE_UNPARSABLE;
916                     goto END;
917                 }
918                 ++ep;
919                 parse_rv = skStringParseUint32(&range_end, ep, 0, 0);
920                 if (parse_rv < 0 || (parse_rv > 0 && *(ep + parse_rv) != ',')){
921                     stringMapGetToken(buf, cp, sizeof(buf));
922                     if (bad_token) {
923                         *bad_token = strdup(buf);
924                     }
925                     snprintf(errbuf, sizeof(errbuf),
926                              "Cannot parse end of numeric range '%s'", buf);
927                     rv = SKSTRINGMAP_PARSE_UNPARSABLE;
928                     goto END;
929                 }
930                 /* parse is good; move 'ep' to end of token */
931                 if (parse_rv) {
932                     ep += parse_rv;
933                 } else {
934                     for (++ep; *ep; ++ep)
935                         ; /* empty */
936                 }
937                 if (range_end < range_beg) {
938                     stringMapGetToken(buf, cp, sizeof(buf));
939                     if (bad_token) {
940                         *bad_token = strdup(buf);
941                     }
942                     snprintf(errbuf, sizeof(errbuf),
943                              "Invalid numeric range '%s'", buf);
944                     rv = SKSTRINGMAP_PARSE_UNPARSABLE;
945                     goto END;
946                 }
947                 for (i = range_beg; i <= range_end; ++i) {
948                     len = snprintf(buf, sizeof(buf), ("%" PRIu32), i);
949                     rv = stringMapFindCheckDupes(str_map, buf, len, &entry,
950                                                  iter, handle_dupes);
951                     if (SKSTRINGMAP_OK_DUPLICATE == rv) {
952                         saw_dupes = 1;
953                         rv = SKSTRINGMAP_OK;
954                     } else if (rv) {
955                         if (bad_token) {
956                             *bad_token = strdup(buf);
957                         }
958                         snprintf(errbuf, sizeof(errbuf),
959                                  "No match was found for the value '%s'", buf);
960                         goto END;
961                     } else if (NULL != entry) {
962                         if (wants_attr) {
963                             iter_node.entry = entry;
964                             iter_node.attr = stringmap_no_attr;
965                             vec_rv = skVectorAppendValue(iter->vec, &iter_node);
966                         } else {
967                             vec_rv = skVectorAppendValue(iter->vec, &entry);
968                         }
969                         if (vec_rv) {
970                             snprintf(errbuf, sizeof(errbuf), "Out of memory");
971                             rv = SKSTRINGMAP_ERR_MEM;
972                             goto END;
973                         }
974                     }
975                 }
976                 cp = ep;
977                 break;
978             }
979             /* FALLTHROUGH */
980 
981           case SM_PARTIAL:
982             if (*ep == '-') {
983                 /* handle a token such as "foo-bar" */
984                 ++ep;
985                 state = SM_PARTIAL;
986                 continue;
987             }
988             if (*ep == '[' || *ep == ']') {
989                 stringMapGetToken(buf, cp, sizeof(buf));
990                 if (bad_token) {
991                     *bad_token = strdup(buf);
992                 }
993                 snprintf(errbuf, sizeof(errbuf),
994                          "Invalid character '%c' in name '%s'",
995                          *ep, buf);
996                 rv = SKSTRINGMAP_PARSE_UNPARSABLE;
997                 goto END;
998             }
999             rv = stringMapFindCheckDupes(str_map, cp, ep-cp, &entry,
1000                                          iter, handle_dupes);
1001             if (SKSTRINGMAP_OK_DUPLICATE == rv) {
1002                 saw_dupes = 1;
1003                 rv = SKSTRINGMAP_OK;
1004             } else if (rv) {
1005                 if (bad_token) {
1006                     stringMapGetToken(buf, cp, sizeof(buf));
1007                     *bad_token = strdup(buf);
1008                 }
1009                 goto END;
1010             } else if (NULL != entry) {
1011                 if (wants_attr) {
1012                     /* will set the attribute later if we find one */
1013                     iter_node.entry = entry;
1014                     iter_node.attr = stringmap_no_attr;
1015                     vec_rv = skVectorAppendValue(iter->vec, &iter_node);
1016                 } else {
1017                     vec_rv = skVectorAppendValue(iter->vec, &entry);
1018                 }
1019                 if (vec_rv) {
1020                     snprintf(errbuf, sizeof(errbuf), "Out of memory");
1021                     rv = SKSTRINGMAP_ERR_MEM;
1022                     goto END;
1023                 }
1024             }
1025             if (*ep == ',' || *ep == '\0') {
1026                 /* attribute is empty */
1027                 if (*ep) {
1028                     ++ep;
1029                 }
1030                 cp = ep;
1031                 state = SM_START;
1032             } else if (*ep == ':') {
1033                 ++ep;
1034                 if (*ep == '[') {
1035                     ++ep;
1036                     state = SM_ATTR_LIST;
1037                 } else {
1038                     state = SM_ATTR;
1039                 }
1040                 attr_start = ep;
1041             } else {
1042                 skAbort();
1043             }
1044             break;
1045 
1046           case SM_ATTR:
1047             if (*ep == '-') {
1048                 ++ep;
1049                 continue;
1050             }
1051             if (*ep == ',' || *ep == '\0') {
1052                 rv = stringMapSetAttribute(iter, attr_start, ep - attr_start);
1053                 if (rv) {
1054                     snprintf(errbuf, sizeof(errbuf), "Out of memory");
1055                     goto END;
1056                 }
1057                 if (*ep) {
1058                     ++ep;
1059                 }
1060                 cp = ep;
1061                 state = SM_START;
1062             } else {
1063                 /* bad character */
1064                 stringMapGetToken(buf, cp, sizeof(buf));
1065                 if (bad_token) {
1066                     *bad_token = strdup(buf);
1067                 }
1068                 snprintf(errbuf, sizeof(errbuf),
1069                          "Invalid character '%c' in attribute for field '%s'",
1070                          *ep, buf);
1071                 rv = SKSTRINGMAP_PARSE_UNPARSABLE;
1072                 goto END;
1073                 /* else error */
1074             }
1075             break;
1076 
1077           case SM_ATTR_LIST:
1078             if (*ep == '-' || *ep == ',') {
1079                 ++ep;
1080                 continue;
1081             }
1082             if (*ep == ']') {
1083                 rv = stringMapSetAttribute(iter, attr_start, ep - attr_start);
1084                 if (rv) {
1085                     snprintf(errbuf, sizeof(errbuf), "Out of memory");
1086                     goto END;
1087                 }
1088                 ++ep;
1089                 cp = ep;
1090                 state = SM_START;
1091             } else if (*ep == '\0') {
1092                 /* error: not closed */
1093                 if (bad_token) {
1094                     *bad_token = strdup(cp);
1095                 }
1096                 snprintf(errbuf, sizeof(errbuf),
1097                          "Did not find closing ']' in attribute for field '%s'",
1098                          cp);
1099                 rv = SKSTRINGMAP_PARSE_UNPARSABLE;
1100                 goto END;
1101             } else {
1102                 /* error: bad character */
1103                 stringMapGetToken(buf, cp, sizeof(buf));
1104                 if (bad_token) {
1105                     *bad_token = strdup(buf);
1106                 }
1107                 snprintf(errbuf, sizeof(errbuf),
1108                          "Invalid character '%c' in attribute for field '%s'",
1109                          *ep, buf);
1110                 rv = SKSTRINGMAP_PARSE_UNPARSABLE;
1111                 goto END;
1112             }
1113             break;
1114         }
1115     }
1116 
1117     /* success */
1118     *out_iter = iter;
1119 
1120     if (saw_dupes) {
1121         rv = SKSTRINGMAP_OK_DUPLICATE;
1122         if (errmsg) {
1123             *errmsg = errbuf;
1124         }
1125     } else {
1126         rv = SKSTRINGMAP_OK;
1127     }
1128 
1129   END:
1130     if (rv != SKSTRINGMAP_OK && rv != SKSTRINGMAP_OK_DUPLICATE) {
1131         if (errmsg) {
1132             *errmsg = errbuf;
1133         }
1134         if (iter) {
1135             skStringMapIterDestroy(iter);
1136         }
1137     }
1138     return rv;
1139 }
1140 
1141 
1142 /* parse a user string for a list of keys, and match those keys
1143  * againts a StringMap */
1144 sk_stringmap_status_t
skStringMapMatch(const sk_stringmap_t * str_map,const char * user_string,sk_stringmap_iter_t ** iter,char ** bad_token)1145 skStringMapMatch(
1146     const sk_stringmap_t   *str_map,
1147     const char             *user_string,
1148     sk_stringmap_iter_t   **iter,
1149     char                  **bad_token)
1150 {
1151     return stringMapParseHelper(str_map, user_string, SKSTRINGMAP_DUPES_KEEP,
1152                                 0, iter, bad_token, NULL);
1153 }
1154 
1155 
1156 /* parse a user string for a list of keys, and match those keys
1157  * againts a StringMap.  Handle duplicate entries as directed.  If an
1158  * error occurs, set 'errmsg' to a static buffer containing the
1159  * error.  */
1160 sk_stringmap_status_t
skStringMapParse(const sk_stringmap_t * str_map,const char * user_string,sk_stringmap_dupes_t handle_dupes,sk_stringmap_iter_t ** iter,char ** errmsg)1161 skStringMapParse(
1162     const sk_stringmap_t   *str_map,
1163     const char             *user_string,
1164     sk_stringmap_dupes_t    handle_dupes,
1165     sk_stringmap_iter_t   **iter,
1166     char                  **errmsg)
1167 {
1168     return stringMapParseHelper(str_map, user_string, handle_dupes, 0,
1169                                 iter, NULL, errmsg);
1170 }
1171 
1172 
1173 sk_stringmap_status_t
skStringMapParseWithAttributes(const sk_stringmap_t * str_map,const char * user_string,sk_stringmap_dupes_t handle_dupes,sk_stringmap_iter_t ** iter,char ** errmsg)1174 skStringMapParseWithAttributes(
1175     const sk_stringmap_t   *str_map,
1176     const char             *user_string,
1177     sk_stringmap_dupes_t    handle_dupes,
1178     sk_stringmap_iter_t   **iter,
1179     char                  **errmsg)
1180 {
1181     return stringMapParseHelper(str_map, user_string, handle_dupes, 1,
1182                                 iter, NULL, errmsg);
1183 }
1184 
1185 
1186 /* add to a list the string names which map to a particular value */
1187 sk_stringmap_status_t
skStringMapGetByID(const sk_stringmap_t * str_map,sk_stringmap_id_t id,sk_stringmap_iter_t ** iter)1188 skStringMapGetByID(
1189     const sk_stringmap_t   *str_map,
1190     sk_stringmap_id_t       id,
1191     sk_stringmap_iter_t   **iter)
1192 {
1193     sk_dll_iter_t map_node;
1194     sk_stringmap_entry_t *map_entry;
1195 
1196     /* check inputs */
1197     if (iter == NULL || str_map == NULL) {
1198         return SKSTRINGMAP_ERR_INPUT;
1199     }
1200 
1201     if (stringMapIterCreate(iter, 0)) {
1202         return SKSTRINGMAP_ERR_MEM;
1203     }
1204 
1205     /* Typecast away const.  We are still treating it as const
1206      * though. */
1207     skDLLAssignIter(&map_node, (sk_stringmap_t *)str_map);
1208     while (skDLLIterForward(&map_node, (void **)&map_entry) == 0) {
1209         /* add name if the id matches */
1210         if (map_entry->id == id) {
1211             if (0 != skVectorAppendValue((*iter)->vec, &map_entry)) {
1212                 skStringMapIterDestroy(*iter);
1213                 *iter = NULL;
1214                 return SKSTRINGMAP_ERR_MEM;
1215             }
1216         }
1217     }
1218 
1219     return SKSTRINGMAP_OK;
1220 }
1221 
1222 
1223 /* return the name of the first entry in 'str_map' whose ID matches
1224  * 'id' */
1225 const char *
skStringMapGetFirstName(const sk_stringmap_t * str_map,sk_stringmap_id_t id)1226 skStringMapGetFirstName(
1227     const sk_stringmap_t   *str_map,
1228     sk_stringmap_id_t       id)
1229 {
1230     sk_dll_iter_t map_node;
1231     sk_stringmap_entry_t *map_entry;
1232 
1233     /* Typecast away const.  We are still treating it as const
1234      * though. */
1235     skDLLAssignIter(&map_node, (sk_stringmap_t *)str_map);
1236     while (skDLLIterForward(&map_node, (void **)&map_entry) == 0) {
1237         /* add name if the id matches */
1238         if (map_entry->id == id) {
1239             return (const char *)(map_entry->name);
1240         }
1241     }
1242 
1243     return NULL;
1244 }
1245 
1246 
1247 /*
1248  * Helper functions
1249  */
1250 
1251 /* print the StringMap to an output stream in human-readable form */
1252 sk_stringmap_status_t
skStringMapPrintMap(const sk_stringmap_t * str_map,FILE * outstream)1253 skStringMapPrintMap(
1254     const sk_stringmap_t   *str_map,
1255     FILE                   *outstream)
1256 {
1257     sk_dll_iter_t map_node;
1258     sk_stringmap_entry_t *map_entry;
1259     int first_entry_skip_comma = 1;
1260 
1261     if (str_map == NULL || outstream == NULL) {
1262         return SKSTRINGMAP_ERR_INPUT;
1263     }
1264 
1265     fprintf(outstream, "{");
1266     /* Typecast away const.  We are still treating it as const
1267      * though. */
1268     skDLLAssignIter(&map_node, (sk_stringmap_t *)str_map);
1269     while (skDLLIterForward(&map_node, (void **)&map_entry) == 0) {
1270         if (!first_entry_skip_comma) {
1271             fprintf(outstream, ", ");
1272         } else {
1273             first_entry_skip_comma = 0;
1274         }
1275 
1276         fprintf(outstream, (" \"%s\" : %" PRIu32),
1277                 map_entry->name, (uint32_t)map_entry->id);
1278     }
1279     fprintf(outstream, " }");
1280 
1281     return SKSTRINGMAP_OK;
1282 }
1283 
1284 
1285 void
skStringMapPrintUsage(const sk_stringmap_t * str_map,FILE * fh,const int indent_len)1286 skStringMapPrintUsage(
1287     const sk_stringmap_t   *str_map,
1288     FILE                   *fh,
1289     const int               indent_len)
1290 {
1291     const char column_sep = ';';
1292     const char alias_sep = ',';
1293     char line_buf[81];
1294     sk_stringmap_entry_t *entry;
1295     sk_stringmap_entry_t *old_entry;
1296     sk_dll_iter_t node;
1297     int len;
1298     int avail_len;
1299     int entry_len;
1300     int total_len;
1301     int last_field_end;
1302 
1303     assert(indent_len < (int)sizeof(line_buf));
1304 
1305     if (NULL == str_map) {
1306         fprintf(fh, "\t[Fields not available]\n");
1307         return;
1308     }
1309 
1310     fprintf(fh,
1311             "\t(Semicolon separates unique items."
1312             " Comma separates item aliases.\n"
1313             "\t Names are case-insensitive and"
1314             " may be abbreviated to the shortest\n"
1315             "\t unique prefix.)\n");
1316 
1317     /* previous value from map */
1318     old_entry = NULL;
1319 
1320     /* set indentation */
1321     memset(line_buf, ' ', sizeof(line_buf));
1322     total_len = indent_len;
1323     avail_len = sizeof(line_buf) - indent_len - 1;
1324     last_field_end = 0;
1325 
1326     /* loop through all entries in the map */
1327     skDLLAssignIter(&node, (sk_stringmap_t*)str_map);
1328     while (skDLLIterForward(&node, (void **)&entry) == 0) {
1329         entry_len = strlen(entry->name);
1330 
1331         if (last_field_end == 0) {
1332             /* very first field */
1333             last_field_end = total_len - 1;
1334         } else if ((old_entry != NULL) && (old_entry->id == entry->id)) {
1335             /* 'entry' is an alias for 'old_entry' */
1336             len = snprintf(&(line_buf[total_len]), avail_len, "%c",
1337                            alias_sep);
1338             assert(len <= avail_len);
1339             total_len += len;
1340             avail_len -= len;
1341             entry_len += len;
1342         } else {
1343             /* start of a new field */
1344             len = snprintf(&(line_buf[total_len]), avail_len, "%c ",
1345                            column_sep);
1346             assert(len <= avail_len);
1347             total_len += len;
1348             avail_len -= len;
1349             entry_len += len;
1350             last_field_end = total_len - 1;
1351         }
1352 
1353         if (entry_len >= avail_len) {
1354             /* need to start a new line */
1355             int to_move;
1356             if (last_field_end <= indent_len) {
1357                 skAppPrintErr("Too many aliases or switch names too long");
1358                 skAbort();
1359             }
1360             line_buf[last_field_end] = '\0';
1361             fprintf(fh, "%s\n", line_buf);
1362             ++last_field_end;
1363             to_move = total_len - last_field_end;
1364             if (to_move > 0) {
1365                 memmove(&(line_buf[indent_len]), &(line_buf[last_field_end]),
1366                         to_move);
1367             }
1368             avail_len = sizeof(line_buf) - indent_len - to_move - 1;
1369             total_len = indent_len + to_move;
1370             last_field_end = indent_len - 1;
1371         }
1372 
1373         old_entry = entry;
1374         len = snprintf(&(line_buf[total_len]), avail_len, "%s", entry->name);
1375         assert(len <= avail_len);
1376         total_len += len;
1377         avail_len -= len;
1378     }
1379 
1380     /* close out last line */
1381     if (last_field_end > 0) {
1382         fprintf(fh, "%s%c\n", line_buf, column_sep);
1383     }
1384 }
1385 
1386 
1387 void
skStringMapPrintDetailedUsage(const sk_stringmap_t * str_map,FILE * fh)1388 skStringMapPrintDetailedUsage(
1389     const sk_stringmap_t   *str_map,
1390     FILE                   *fh)
1391 {
1392     const int MIN_DESCRIPTION_WDITH = 20;
1393     const char *alias_sep[2] = {"Alias: ", ","};
1394     const char post_name[] = " - ";
1395     const char *cp;
1396     const char *sp;
1397     char line_buf[72];
1398     char alias_buf[512];
1399     sk_stringmap_entry_t *entry;
1400     sk_stringmap_entry_t *next_entry;
1401     sk_dll_iter_t node;
1402     int newline_description = 0;
1403     int len;
1404     int continue_len;
1405     int avail_len;
1406     int name_len;
1407     int alias_len;
1408     int descript_len;
1409     int done;
1410 
1411     if (NULL == str_map) {
1412         fprintf(fh, "\t[Fields not available]\n");
1413         return;
1414     }
1415 
1416     /* loop through all entries in the map to get the length of the
1417      * longest primary field name */
1418     skDLLAssignIter(&node, (sk_stringmap_t*)str_map);
1419     if (skDLLIterForward(&node, (void **)&entry)) {
1420         fprintf(fh, "\t[No fields defined]\n");
1421         return;
1422     }
1423     name_len = strlen(entry->name);
1424     while (skDLLIterForward(&node, (void **)&next_entry) == 0) {
1425         if (next_entry->id != entry->id) {
1426             len = strlen(next_entry->name);
1427             if (len > name_len) {
1428                 name_len = len;
1429             }
1430         }
1431         entry = next_entry;
1432     }
1433 
1434     /* continuation lines are indented by name_len plus the length of
1435      * the post_name string */
1436     continue_len = name_len + strlen(post_name);
1437 
1438     /* determine width for the descriptions */
1439     descript_len = sizeof(line_buf) - continue_len;
1440     if (descript_len < MIN_DESCRIPTION_WDITH) {
1441         newline_description = 1;
1442         continue_len = 8 + strlen(post_name);
1443         descript_len = sizeof(line_buf) - continue_len;
1444     }
1445     assert(descript_len > 0);
1446 
1447     /* print all entries in the map */
1448     skDLLAssignIter(&node, (sk_stringmap_t*)str_map);
1449     done = skDLLIterForward(&node, (void **)&next_entry);
1450     while (!done) {
1451         entry = next_entry;
1452         /* determine the list of aliases for this entry */
1453         alias_buf[0] = '\0';
1454         alias_len = 0;
1455         avail_len = sizeof(alias_buf) - 1;
1456         while (((done = skDLLIterForward(&node, (void **)&next_entry)) == 0)
1457                && (entry->id == next_entry->id))
1458         {
1459             /* 'next_entry' is an alias for 'entry' */
1460             len = snprintf(&(alias_buf[alias_len]), avail_len, "%s%s",
1461                            alias_sep[alias_len ? 1 : 0], next_entry->name);
1462             if (len > avail_len) {
1463                 skAbort();
1464             }
1465             alias_len += len;
1466             avail_len -= len;
1467         }
1468 
1469         /* print the entry's name */
1470         if (newline_description) {
1471             fprintf(fh, "\t%s\n\t%*s",
1472                     entry->name, continue_len, post_name);
1473         } else {
1474             fprintf(fh, "\t%*s%s",
1475                     -name_len, entry->name, post_name);
1476         }
1477         /* handle the description, line wrapping as needed */
1478         sp = (const char*)entry->description;
1479         if (NULL == sp) {
1480             if (0 == alias_len) {
1481                 fprintf(fh, "[No description available]\n");
1482             } else {
1483                 fprintf(fh, "%s\n", alias_buf);
1484             }
1485             continue;
1486         }
1487         for (;;) {
1488             len = strlen(sp);
1489             if (len < descript_len) {
1490                 /* (remainder of) description fits on this line */
1491                 if (0 == alias_len) {
1492                     /* no aliases to print */
1493                     fprintf(fh, "%s\n", sp);
1494                 } else if (len + 2 + alias_len < descript_len) {
1495                     /* print description and alias on one line */
1496                     fprintf(fh, "%s. %s\n", sp, alias_buf);
1497                 } else {
1498                     /* print description and alias on two lines */
1499                     fprintf(fh, "%s\n\t%*s%s\n",
1500                             sp, continue_len, "", alias_buf);
1501                 }
1502                 break;
1503             }
1504             /* find a place to break the description */
1505             cp = sp + descript_len;
1506             while (cp > sp && !isspace((int)*cp)) {
1507                 --cp;
1508             }
1509             if (cp == sp) {
1510                 /* no break point found; try going forward instead */
1511                 cp = sp + descript_len + 1;
1512                 while (*cp && !isspace((int)*cp)) {
1513                     ++cp;
1514                 }
1515                 if (!*cp) {
1516                     if (0 == alias_len) {
1517                         fprintf(fh, "%s\n", sp);
1518                     } else {
1519                         fprintf(fh, "%s\n\t%*s%s\n",
1520                                 sp, continue_len, "", alias_buf);
1521                     }
1522                     break;
1523                 }
1524                 while ((size_t)(cp - sp) >= sizeof(line_buf)) {
1525                     strncpy(line_buf, sp, sizeof(line_buf)-1);
1526                     line_buf[sizeof(line_buf)-1] = '\0';
1527                     fprintf(fh, "%s", line_buf);
1528                     sp += sizeof(line_buf)-1;
1529                 }
1530             }
1531             strncpy(line_buf, sp, (cp - sp));
1532             line_buf[(cp - sp)] = '\0';
1533             fprintf(fh, "%s\n", line_buf);
1534             fprintf(fh, "\t%*s", continue_len, "");
1535             sp = cp + 1;
1536         }
1537     }
1538 }
1539 
1540 
1541 /* Return a textual representation of the specified error code. */
1542 const char *
skStringMapStrerror(int error_code)1543 skStringMapStrerror(
1544     int                 error_code)
1545 {
1546     static char buf[256];
1547 
1548     switch ((sk_stringmap_status_t)error_code) {
1549       case SKSTRINGMAP_OK:
1550         return "Command was successful";
1551 
1552       case SKSTRINGMAP_ERR_INPUT:
1553         return "Bad input to function";
1554 
1555       case SKSTRINGMAP_ERR_MEM:
1556         return "Memory allocation failed";
1557 
1558       case SKSTRINGMAP_ERR_LIST:
1559         return "Unexpected error occured in the linked list";
1560 
1561       case SKSTRINGMAP_ERR_DUPLICATE_ENTRY:
1562         return "Name is already in use";
1563 
1564       case SKSTRINGMAP_ERR_ZERO_LENGTH_ENTRY:
1565         return "Name is the empty string";
1566 
1567       case SKSTRINGMAP_ERR_NUMERIC_START_ENTRY:
1568         return "Name cannot begin with digit";
1569 
1570       case SKSTRINGMAP_ERR_ALPHANUM_START_ENTRY:
1571         return "Name cannot begin with a non-alphanumeric";
1572 
1573       case SKSTRINGMAP_ERR_PARSER:
1574         return "Unexpected error during parsing";
1575 
1576       case SKSTRINGMAP_PARSE_NO_MATCH:
1577         return "Input does not match any names";
1578 
1579       case SKSTRINGMAP_PARSE_AMBIGUOUS:
1580         return "Input matches multiple names";
1581 
1582       case SKSTRINGMAP_PARSE_UNPARSABLE:
1583         return "Input not parsable";
1584 
1585       case SKSTRINGMAP_OK_DUPLICATE:
1586         return "Removed duplicates during parsing";
1587     }
1588 
1589     snprintf(buf, sizeof(buf), "Unrecognized string map error code %d",
1590              error_code);
1591     return buf;
1592 }
1593 
1594 
1595 
1596 /*
1597  *  stringMapCheckValidName(name, map);
1598  *
1599  *    Parse a key to be inserted into a StringMap to determine if it is
1600  *    legal or not.  Assumes arguments are non-NULL.
1601  *
1602  *  Arguments
1603  *
1604  *    const char *name - the key to check
1605  *
1606  *    sk_stringmap_t *str_map - the StringMap against whose contents the
1607  *    string key should be validated
1608  *
1609  *  Return Values
1610  *
1611  *    if name is the empty string, returns
1612  *    SKSTRINGMAP_ZERO_LENGTH_ENTRY
1613  *
1614  *    if name starts with a number, but does not contain all numeric
1615  *    characters (e.g., "34yh"), returns
1616  *    SKSTRINGMAP_ERR_NUMERIC_START_ENTRY
1617  *
1618  *    if name does not start with a alpha-numeric, return
1619  *    SKSTRINGMAP_ERR_ALPHANUM_START_ENTRY
1620  *
1621  *    if name already exists in the StringMap, returns
1622  *    SKSTRINGMAP_DUPLICATE_ENTRY
1623  *
1624  */
1625 static sk_stringmap_status_t
stringMapCheckValidName(sk_stringmap_t * str_map,const char * name)1626 stringMapCheckValidName(
1627     sk_stringmap_t     *str_map,
1628     const char         *name)
1629 {
1630     sk_dll_iter_t map_node;
1631     sk_stringmap_entry_t *map_entry;
1632     size_t i;
1633 
1634     assert(name != NULL);
1635     assert(str_map != NULL);
1636 
1637     if (name[0] == '\0') {
1638         return SKSTRINGMAP_ERR_ZERO_LENGTH_ENTRY;
1639     }
1640 
1641     if (isdigit((int)name[0])) {
1642         /* if the first character is a digit, they ALL have to be digits */
1643         for (i = strlen(name) - 1; i > 0; --i) {
1644             if ( !isdigit((int)name[i])) {
1645                 return SKSTRINGMAP_ERR_NUMERIC_START_ENTRY;
1646             }
1647         }
1648     } else if (!isalpha((int)name[0])) {
1649         return SKSTRINGMAP_ERR_ALPHANUM_START_ENTRY;
1650     }
1651 
1652     skDLLAssignIter(&map_node, str_map);
1653     while (skDLLIterForward(&map_node, (void **)&map_entry) == 0) {
1654         if (0 == strcasecmp(map_entry->name, name)) {
1655             return SKSTRINGMAP_ERR_DUPLICATE_ENTRY;
1656         }
1657     }
1658 
1659     return SKSTRINGMAP_OK;
1660 }
1661 
1662 
1663 /*
1664  *  stringMapFreeEntry(map_entry);
1665  *
1666  *    Internal helper function to free a single entry 'map_entry' from
1667  *    a StringMap.
1668  */
1669 static void
stringMapFreeEntry(sk_stringmap_entry_t * map_entry)1670 stringMapFreeEntry(
1671     sk_stringmap_entry_t   *map_entry)
1672 {
1673     if (map_entry) {
1674         /* free char string in entry */
1675         if (NULL != map_entry->name) {
1676             /* we know we allocated this ourselves, so the cast to (char*)
1677              * is safe */
1678             free((char*)map_entry->name);
1679         }
1680         if (NULL != map_entry->description) {
1681             free((char*)map_entry->description);
1682         }
1683         /* free entry itself */
1684         free(map_entry);
1685     }
1686 }
1687 
1688 
1689 /*
1690 ** Local Variables:
1691 ** mode:c
1692 ** indent-tabs-mode:nil
1693 ** c-basic-offset:4
1694 ** End:
1695 */
1696