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