qLibc
qlisttbl.c
Go to the documentation of this file.
1/******************************************************************************
2 * qLibc
3 *
4 * Copyright (c) 2010-2026 Seungyoung Kim.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 *****************************************************************************/
28
29/**
30 * @file qlisttbl.c Linked-list-table implementation.
31 *
32 * qlisttbl container is a Linked-List-Table implementation that maps keys to
33 * values. Keys are strings, and values are any non-null objects. These
34 * elements are stored sequentially in a Doubly-Linked-List data structure.
35 *
36 * Compared to Hash-Table, List-Table is efficient when you need to keep
37 * duplicated keys since Hash-Table only keep unique keys. Of course, qlisttbl
38 * supports both unique keys and key duplication.
39 *
40 * @code
41 * [Conceptional Data Structure Diagram]
42 *
43 * last~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
44 * |
45 * +-----------+ doubly +-----------+ doubly +-v---------+
46 * first~~~|~> 0 <~|~~~~~~~~~~|~> 1 <~|~~~~~~~~~~|~> N |
47 * +--|-----|--+ linked +--|-----|--+ linked +--|-----|--+
48 * | | | | | |
49 * +-----v-+ +-v-------+ | | +-----v-+ +-v-------+
50 * | KEY A | | VALUE A | | | | KEY N | | VALUE N |
51 * +-------+ +---------+ | | +-------+ +---------+
52 * +-----v-+ +-v-------+
53 * | KEY B | | VALUE B |
54 * +-------+ +---------+
55 * @endcode
56 *
57 * @code
58 * // create a list table.
59 * qlisttbl_t *tbl = qlisttbl(QLISTTBL_THREADSAFE);
60 *
61 * // insert elements (key duplication allowed)
62 * tbl->put(tbl, "e1", "a", strlen("e1")+1); // equal to putstr();
63 * tbl->putstr(tbl, "e2", "b");
64 * tbl->putstr(tbl, "e2", "c");
65 * tbl->putstr(tbl, "e3", "d");
66 *
67 * // debug output
68 * tbl->debug(tbl, stdout, true);
69 *
70 * // get element
71 * printf("get('e2') : %s\n", (char*)tbl->get(tbl, "e2", NULL, false));
72 * printf("getstr('e2') : %s\n", tbl->getstr(tbl, "e2", false));
73 *
74 * // get all matching elements
75 * qlisttbl_data_t *objs = tbl->getmulti(tbl, "e2", true, NULL);
76 * for (i = 0; objs[i].data != NULL; i++) {
77 * printf("getmulti('e2')[%d] : %s (size=%d)\n",
78 * i, (char *)objs[i].data, (int)objs[i].size);
79 * }
80 * tbl->freemulti(objs);
81 *
82 * // find every 'e2' elements
83 * qlisttbl_obj_t obj;
84 * memset((void*)&obj, 0, sizeof(obj)); // must be cleared before call
85 * while(tbl->getnext(tbl, &obj, "e2", false) == true) {
86 * printf("NAME=%s, DATA=%s, SIZE=%zu\n",
87 * obj.name, (char*)obj.data, obj.size);
88 * }
89 *
90 * // free object
91 * tbl->free(tbl);
92 * @endcode
93 */
94
95#include <stdio.h>
96#include <stdlib.h>
97#include <stdbool.h>
98#include <stdint.h>
99#include <inttypes.h>
100#include <string.h>
101#include <stdarg.h>
102#include <unistd.h>
103#include <fcntl.h>
104#include <sys/stat.h>
105#include <assert.h>
106#include <errno.h>
107#include "qinternal.h"
108#include "utilities/qencode.h"
109#include "utilities/qfile.h"
110#include "utilities/qhash.h"
111#include "utilities/qio.h"
112#include "utilities/qstring.h"
113#include "utilities/qtime.h"
114#include "containers/qlisttbl.h"
115
116#ifndef _DOXYGEN_SKIP
117
118static qlisttbl_obj_t *newobj(const char *name, const void *data, size_t size);
119static bool insertobj(qlisttbl_t *tbl, qlisttbl_obj_t *obj);
120static qlisttbl_obj_t *findobj(qlisttbl_t *tbl, const char *name, qlisttbl_obj_t *retobj);
121
122static bool namematch(qlisttbl_obj_t *obj, const char *name, uint32_t hash);
123static bool namecasematch(qlisttbl_obj_t *obj, const char *name, uint32_t hash);
124
125#endif
126
127/**
128 * Create a new Q_LIST linked-list container.
129 *
130 * @param options combination of initialization options
131 *
132 * @return allocated qlisttbl_t pointer on success, or NULL on failure.
133 * @retval errno will be set in error condition.
134 * - ENOMEM : Memory allocation failure.
135 *
136 * @code
137 * qlisttbl_t *tbl = qlisttbl(0);
138 * qlisttbl_t *threadsafe_tbl = qlisttbl(QLISTTBL_THREADSAFE);
139 * @endcode
140 *
141 * @note
142 * Available options:
143 * - QLISTTBL_THREADSAFE - make it thread-safe
144 * - QLISTTBL_UNIQUE - replace entries with the same key
145 * - QLISTTBL_CASEINSENSITIVE - treat keys as case-insensitive
146 * - QLISTTBL_INSERTTOP - insert a new key at the top
147 * - QLISTTBL_LOOKUPFORWARD - search from the top
148 */
149qlisttbl_t *qlisttbl(int options) {
150 qlisttbl_t *tbl = (qlisttbl_t *)calloc(1, sizeof(qlisttbl_t));
151 if (tbl == NULL) {
152 errno = ENOMEM;
153 return NULL;
154 }
155
156 // assign member methods.
157 tbl->put = qlisttbl_put;
158 tbl->putstr = qlisttbl_putstr;
159 tbl->putstrf = qlisttbl_putstrf;
160 tbl->putint = qlisttbl_putint;
161
162 tbl->get = qlisttbl_get;
163 tbl->getstr = qlisttbl_getstr;
164 tbl->getint = qlisttbl_getint;
165
166 tbl->getmulti = qlisttbl_getmulti;
167 tbl->freemulti = qlisttbl_freemulti;
168
169 tbl->remove = qlisttbl_remove;
170 tbl->removeobj = qlisttbl_removeobj;
171
172 tbl->getnext = qlisttbl_getnext;
173
174 tbl->size = qlisttbl_size;
175 tbl->sort = qlisttbl_sort;
176 tbl->clear = qlisttbl_clear;
177 tbl->save = qlisttbl_save;
178 tbl->load = qlisttbl_load;
179
180 tbl->debug = qlisttbl_debug;
181
182 tbl->lock = qlisttbl_lock;
183 tbl->unlock = qlisttbl_unlock;
184
185 tbl->free = qlisttbl_free;
186
187 // assign private methods.
188 tbl->namematch = namematch;
189 tbl->namecmp = strcmp;
190
191 // handle options.
192 if (options & QLISTTBL_THREADSAFE) {
193 Q_MUTEX_NEW(tbl->qmutex, true);
194 if (tbl->qmutex == NULL) {
195 errno = ENOMEM;
196 free(tbl);
197 return NULL;
198 }
199 }
200 if (options & QLISTTBL_UNIQUE) {
201 tbl->unique = true;
202 }
203 if (options & QLISTTBL_CASEINSENSITIVE) {
204 tbl->namematch = namecasematch;
205 tbl->namecmp = strcasecmp;
206 }
207 if (options & QLISTTBL_INSERTTOP) {
208 tbl->inserttop = true;
209 }
210 if (options & QLISTTBL_LOOKUPFORWARD) {
211 tbl->lookupforward = true;
212 }
213
214 return tbl;
215}
216
217/**
218 * qlisttbl->put(): Put an element to this table.
219 *
220 * @param tbl qlisttbl container pointer.
221 * @param name element name.
222 * @param data a pointer which points data memory.
223 * @param size size of the data.
224 *
225 * @return true on success, otherwise false.
226 * @retval errno will be set in error condition.
227 * - ENOMEM : Memory allocation failure.
228 * - EINVAL : Invalid argument.
229 *
230 * @code
231 * struct my_obj {
232 * int a, b, c;
233 * };
234 *
235 * // create a sample object.
236 * struct my_obj obj;
237 *
238 * // create a table and add a sample object.
239 * qlisttbl_t *tbl = qlisttbl();
240 * tbl->put(tbl, "obj1", &obj, sizeof(struct my_obj));
241 * @endcode
242 *
243 * @note
244 * The default behavior is adding an object at the end of this table
245 * unless QLISTTBL_INSERTTOP option was given.
246 */
247bool qlisttbl_put(qlisttbl_t *tbl, const char *name, const void *data, size_t size) {
248 // make new object table
249 qlisttbl_obj_t *obj = newobj(name, data, size);
250 if (obj == NULL) {
251 return false;
252 }
253
254 // lock table
255 qlisttbl_lock(tbl);
256
257 // if unique flag is set, remove same key
258 if (tbl->unique == true) qlisttbl_remove(tbl, name);
259
260 // insert into table
261 if (tbl->num == 0) {
262 obj->prev = NULL;
263 obj->next = NULL;
264 } else {
265 if (tbl->inserttop == false) {
266 obj->prev = tbl->last;
267 obj->next = NULL;
268 } else {
269 obj->prev = NULL;
270 obj->next = tbl->first;
271 }
272 }
273 insertobj(tbl, obj);
274
275 // unlock table
276 qlisttbl_unlock(tbl);
277
278 return true;
279}
280
281/**
282 * qlisttbl->putstr(): Put a string into this table.
283 *
284 * @param tbl qlisttbl container pointer.
285 * @param name element name.
286 * @param str string data.
287 *
288 * @return true on success, otherwise false.
289 * @retval errno will be set in error condition.
290 * - ENOMEM : Memory allocation failure.
291 * - EINVAL : Invalid argument.
292 */
293bool qlisttbl_putstr(qlisttbl_t *tbl, const char *name, const char *str) {
294 size_t size = (str) ? (strlen(str) + 1) : 0;
295 return qlisttbl_put(tbl, name, (const void *)str, size);
296}
297
298/**
299 * qlisttbl->putstrf(): Put a formatted string into this table.
300 *
301 * @param tbl qlisttbl container pointer.
302 * @param name element name.
303 * @param format formatted value string.
304 *
305 * @return true on success, otherwise false.
306 * @retval errno will be set in error condition.
307 * - ENOMEM : Memory allocation failure.
308 * - EINVAL : Invalid argument.
309 */
310bool qlisttbl_putstrf(qlisttbl_t *tbl, const char *name, const char *format, ...) {
311 char *str;
312 DYNAMIC_VSPRINTF(str, format);
313 if (str == NULL) {
314 errno = ENOMEM;
315 return false;
316 }
317
318 bool ret = qlisttbl_putstr(tbl, name, str);
319 free(str);
320
321 return ret;
322}
323
324/**
325 * qlisttbl->putint(): Put an integer into this table as a string.
326 *
327 * @param tbl qlisttbl container pointer.
328 * @param name element name.
329 * @param num number data.
330 *
331 * @return true on success, otherwise false.
332 * @retval errno will be set in error condition.
333 * - ENOMEM : Memory allocation failure.
334 * - EINVAL : Invalid argument.
335 *
336 * @note
337 * The integer will be converted to a string object and stored as a string
338 * object.
339 */
340bool qlisttbl_putint(qlisttbl_t *tbl, const char *name, int64_t num) {
341 char str[20+1];
342 snprintf(str, sizeof(str), "%"PRId64, num);
343 return qlisttbl_putstr(tbl, name, str);
344}
345
346/**
347 * qlisttbl->get(): Finds an object with given name.
348 *
349 * If there are duplicate keys in the table, this will return the first matched
350 * one from the bottom (or the top if QLISTTBL_LOOKUPFORWARD option was given).
351 * So if there are duplicated keys, it'll return recently inserted one.
352 *
353 * @param tbl qlisttbl container pointer.
354 * @param name element name.
355 * @param size if size is not NULL, data size will be stored.
356 * @param newmem whether or not to allocate memory for the data.
357 *
358 * @return pointer to the data if the key is found on success, or NULL on failure.
359 * @retval errno will be set in error condition.
360 * - ENOENT : No such key found.
361 * - EINVAL : Invalid argument.
362 * - ENOMEM : Memory allocation failure.
363 *
364 * @code
365 * qlisttbl_t *tbl = qlisttbl();
366 * (...codes...)
367 *
368 * // with newmem flag unset
369 * size_t size;
370 * void *data = tbl->get(tbl, "key_name", &size, false);
371 *
372 * // with newmem flag set
373 * size_t size;
374 * void *data = tbl->get(tbl, "key_name", &size, true);
375 * (...does something with data...)
376 * if(data != NULL) free(data);
377 * @endcode
378 *
379 * @note
380 * If `newmem` is set, the returned data is newly allocated and must be freed
381 * by the caller. Otherwise, the returned pointer refers to the internal data
382 * buffer and must not be freed. In thread-safe mode, always set `newmem` to
383 * true to avoid race-condition issues.
384 */
385void *qlisttbl_get(qlisttbl_t *tbl, const char *name, size_t *size, bool newmem) {
386 if (name == NULL) {
387 errno = EINVAL;
388 return NULL;
389 }
390
391 qlisttbl_lock(tbl);
392 void *data = NULL;
393 qlisttbl_obj_t *obj = findobj(tbl, name, NULL);
394 if (obj != NULL) {
395 // get data
396 if (newmem == true) {
397 data = malloc(obj->size);
398 if (data == NULL) {
399 errno = ENOMEM;
400 qlisttbl_unlock(tbl);
401 return NULL;
402 }
403 memcpy(data, obj->data, obj->size);
404 } else {
405 data = obj->data;
406 }
407
408 // set size
409 if (size != NULL) *size = obj->size;
410 }
411 qlisttbl_unlock(tbl);
412
413 if (data == NULL) {
414 errno = ENOENT;
415 }
416
417 return data;
418}
419
420/**
421 * qlisttbl->getstr(): Finds an object with given name and returns as
422 * string type.
423 *
424 * @param tbl qlisttbl container pointer.
425 * @param name element name.
426 * @param newmem whether or not to allocate memory for the data.
427 *
428 * @return pointer to the data if the key is found on success, or NULL on failure.
429 * @retval errno will be set in error condition.
430 * - ENOENT : No such key found.
431 * - EINVAL : Invalid argument.
432 * - ENOMEM : Memory allocation failure.
433 */
434char *qlisttbl_getstr(qlisttbl_t *tbl, const char *name, bool newmem) {
435 return (char *)qlisttbl_get(tbl, name, NULL, newmem);
436}
437
438/**
439 * qlisttbl->getint(): Finds an object with given name and returns as
440 * integer type.
441 *
442 * @param tbl qlisttbl container pointer.
443 * @param name element name.
444 *
445 * @return an integer value of the integer object, otherwise returns 0.
446 * @retval errno will be set in error condition.
447 * - ENOENT : No such key found.
448 * - EINVAL : Invalid argument.
449 * - ENOMEM : Memory allocation failure.
450 */
451int64_t qlisttbl_getint(qlisttbl_t *tbl, const char *name) {
452 int64_t num = 0;
453 char *str = qlisttbl_getstr(tbl, name, true);
454 if (str != NULL) {
455 num = atoll(str);
456 free(str);
457 }
458 return num;
459}
460
461/**
462 * qlisttbl->getmulti(): Finds all objects with the given name and returns an
463 * array of objects.
464 *
465 * If there are duplicate keys in the table, this returns all matching ones.
466 * The order of returned objects depends on the setnextdir() setting. By
467 * default, the order is the same (forward) as listed in the table.
468 *
469 * @param tbl qlisttbl container pointer.
470 * @param name element name.
471 * @param newmem whether or not to allocate memory for the data.
472 * @param numobjs the number of returned objects will be stored here.
473 * (can be NULL)
474 *
475 * @return pointer to the data array if the key is found, or NULL on failure.
476 * @retval errno will be set in error condition.
477 * - ENOENT : No such key found.
478 * - EINVAL : Invalid argument.
479 * - ENOMEM : Memory allocation failure.
480 *
481 * @note
482 * The returned array of qlisttbl_data_t should be released by calling
483 * `freemulti()` after use. Even if `getmulti()` is called with `newmem`
484 * set to false, `freemulti()` should still be called so the object array
485 * itself can be released.
486 *
487 * @code
488 * size_t numobjs = 0;
489 * qlisttbl_data_t *objs = tbl->getmulti(tbl, "e2", true, &numobjs);
490 * for (i = 0; objs[i].data != NULL; i++) {
491 * printf("DATA=%s, SIZE=%zu\n", i, (char *)objs[i].data, objs[i].size);
492 * }
493 * tbl->freemulti(objs);
494 * @endcode
495 */
496qlisttbl_data_t *qlisttbl_getmulti(qlisttbl_t *tbl, const char *name, bool newmem,
497 size_t *numobjs) {
498 qlisttbl_data_t *objs = NULL; // objects container
499 size_t allocobjs = 0; // allocated number of objs
500 size_t numfound = 0; // number of keys found
501
502 qlisttbl_obj_t obj;
503 memset((void *)&obj, 0, sizeof(obj)); // must be cleared before call
504 qlisttbl_lock(tbl);
505 while (tbl->getnext(tbl, &obj, name, newmem) == true) {
506 numfound++;
507
508 // allocate object array.
509 if (numfound >= allocobjs) {
510 if (allocobjs == 0) allocobjs = 10; // start from 10
511 else allocobjs *= 2; // double size
512 objs = (qlisttbl_data_t *)realloc(objs, sizeof(qlisttbl_data_t) * allocobjs);
513 if (objs == NULL) {
514 DEBUG("qlisttbl->getmulti(): Memory reallocation failure.");
515 errno = ENOMEM;
516 break;
517 }
518 }
519
520 // copy reference
521 qlisttbl_data_t *newobj = &objs[numfound - 1];
522 newobj->data = obj.data;
523 newobj->size = obj.size;
524 newobj->type = (newmem == false) ? 1 : 2;
525
526 // release resource
527 if (newmem == true) {
528 if (obj.name != NULL) free(obj.name);
529 }
530
531 // clear next block
532 newobj = &objs[numfound];
533 memset((void *)newobj, '\0', sizeof(qlisttbl_data_t));
534 newobj->type = 0; // mark, end of objects
535 }
536 qlisttbl_unlock(tbl);
537
538 // return found counter
539 if (numobjs != NULL) {
540 *numobjs = numfound;
541 }
542
543 if (numfound == 0) {
544 errno = ENOENT;
545 }
546
547 return objs;
548}
549
550/**
551 * qlisttbl->freemulti(): Deallocate object array returned by getmulti().
552 *
553 * @param objs pointer of array of qlisttbl_data_t.
554 *
555 * @code
556 * qlisttbl_data_t *objs = tbl->getmulti(tbl, "newmem is true", true, &numobjs);
557 * tbl->freemulti(objs); // frees allocated objects and object array itself.
558 *
559 * qlisttbl_data_t *objs = tbl->getmulti(tbl, "even newmem is false", false, &numobjs);
560 * tbl->freemulti(objs); // frees object array itself.
561 *
562 * @endcode
563 */
564void qlisttbl_freemulti(qlisttbl_data_t *objs) {
565 if (objs == NULL) return;
566
567 qlisttbl_data_t *obj;
568 for (obj = &objs[0]; obj->type == 2; obj++) {
569 if (obj->data != NULL) free(obj->data);
570 }
571
572 free(objs);
573}
574
575/**
576 * qlisttbl->remove(): Remove matched objects with given name.
577 *
578 * @param tbl qlisttbl container pointer.
579 * @param name element name.
580 *
581 * @return a number of removed objects.
582 */
583size_t qlisttbl_remove(qlisttbl_t *tbl, const char *name) {
584 if (name == NULL) return false;
585
586 size_t numremoved = 0;
587
588 qlisttbl_obj_t obj;
589 memset((void*)&obj, 0, sizeof(obj)); // must be cleared before call
590 qlisttbl_lock(tbl);
591 while (qlisttbl_getnext(tbl, &obj, name, false) == true) {
592 qlisttbl_removeobj(tbl, &obj);
593 numremoved++;
594 }
595 qlisttbl_unlock(tbl);
596
597 return numremoved;
598}
599
600/**
601 * qlisttbl->removeobj(): Remove objects with given object pointer.
602 *
603 * This call is useful when you want to remove an element while traversing a
604 * table using getnext(). The address pointed to by obj may be different from
605 * the actual object in the table, but that is OK because we will recalculate
606 * the actual object address by following its links.
607 *
608 * @param tbl qlisttbl container pointer.
609 * @param obj object to remove.
610 *
611 * @return true if deletion succeeds, otherwise false.
612 * @retval errno will be set in error condition.
613 * - ENOENT : No such key found.
614 *
615 * @code
616 * qlisttbl_obj_t obj;
617 * memset((void*)&obj, 0, sizeof(obj)); // must be cleared before call
618 * tbl->lock(tbl);
619 * while(tbl->getnext(tbl, &obj, NULL, true) == true) {
620 * tbl->removeobj(tbl, &obj); // this will remove all entries in the table
621 * }
622 * tbl->unlock(tbl);
623 * @endcode
624 */
625bool qlisttbl_removeobj(qlisttbl_t *tbl, const qlisttbl_obj_t *obj) {
626 if (obj == NULL) return false;
627
628 qlisttbl_lock(tbl);
629
630 // copy chains
631 qlisttbl_obj_t *prev = obj->prev;
632 qlisttbl_obj_t *next = obj->next;
633
634 // find this object
635 qlisttbl_obj_t *this = NULL;
636 if (prev != NULL) this = prev->next;
637 else if (next != NULL) this = next->prev;
638 else this = tbl->first; // table has only one object.
639
640 // double check
641 if (this == NULL) {
642 qlisttbl_unlock(tbl);
643 DEBUG("qlisttbl->removeobj(): Can't verify object.");
644 errno = ENOENT;
645 return false;
646 }
647
648 // adjust chain links
649 if (prev == NULL) tbl->first = next; // if the object is first one
650 else prev->next = next; // not the first one
651
652 if (next == NULL) tbl->last = prev; // if the object is last one
653 else next->prev = prev; // not the first one
654
655 // adjust counter
656 tbl->num--;
657
658 qlisttbl_unlock(tbl);
659
660 // free object
661 free(this->name);
662 free(this->data);
663 free(this);
664
665 return true;
666}
667
668/**
669 * qlisttbl->getnext(): Get next element.
670 *
671 * Default searching direction is backward, from the bottom to top
672 * unless QLISTTBL_LOOKUPFORWARD option was specified.
673 *
674 * @param tbl qlisttbl container pointer.
675 * @param obj found data will be stored in this object
676 * @param name element name. If the key name is NULL, search all objects
677 * in the table.
678 * @param newmem whether or not to allocate memory for the data.
679 *
680 * @return true if found otherwise false
681 * @retval errno will be set in error condition.
682 * - ENOENT : No next element.
683 * - ENOMEM : Memory allocation failure.
684 *
685 * @note
686 * The obj should be initialized with 0 by using memset() before first call.
687 * If newmem flag is true, user should free obj.name and obj.data
688 * resources.
689 *
690 * @code
691 * qlisttbl_t *tbl = qlisttbl();
692 * (...add data into table...)
693 *
694 * // single-threaded usage
695 * qlisttbl_obj_t obj;
696 * memset((void*)&obj, 0, sizeof(obj)); // must be cleared before call
697 * while(tbl->getnext(tbl, &obj, NULL, false) == true) {
698 * printf("NAME=%s, DATA=%s, SIZE=%zu\n",
699 * obj.name, (char*)obj.data, obj.size);
700 * }
701 *
702 * // thread model with particular key search
703 * qlisttbl_obj_t obj;
704 * memset((void*)&obj, 0, sizeof(obj)); // must be cleared before call
705 * tbl->lock(tbl);
706 * while(tbl->getnext(tbl, &obj, "key_name", true) == true) {
707 * printf("NAME=%s, DATA=%s, SIZE=%zu\n",
708 * obj.name, (char*)obj.data, obj.size);
709 * free(obj.name);
710 * free(obj.data);
711 * }
712 * tbl->unlock(tbl);
713 * @endcode
714 */
715bool qlisttbl_getnext(qlisttbl_t *tbl, qlisttbl_obj_t *obj, const char *name,
716 bool newmem) {
717 if (obj == NULL) return NULL;
718
719 qlisttbl_lock(tbl);
720
721 qlisttbl_obj_t *cont = NULL;
722 if (obj->size == 0) { // first time call
723 if (name == NULL) { // full scan
724 cont = (tbl->lookupforward) ? tbl->first : tbl->last;
725 } else { // name search
726 cont = findobj(tbl, name, NULL);
727 }
728 } else { // next call
729 cont = (tbl->lookupforward) ? obj->next : obj->prev;
730 }
731
732 if (cont == NULL) {
733 errno = ENOENT;
734 qlisttbl_unlock(tbl);
735 return false;
736 }
737
738 uint32_t hash = (name != NULL) ? qhashmurmur3_32(name, strlen(name)) : 0;
739
740 bool ret = false;
741 while (cont != NULL) {
742 if (name == NULL || tbl->namematch(cont, name, hash) == true) {
743 if (newmem == true) {
744 obj->name = strdup(cont->name);
745 obj->data = malloc(cont->size);
746 if (obj->name == NULL || obj->data == NULL) {
747 if (obj->name != NULL) free(obj->name);
748 if (obj->data != NULL) free(obj->data);
749 obj->name = NULL;
750 obj->data = NULL;
751 errno = ENOMEM;
752 break;
753 }
754 memcpy(obj->data, cont->data, cont->size);
755 } else {
756 obj->name = cont->name;
757 obj->data = cont->data;
758 }
759 obj->hash = cont->hash;
760 obj->size = cont->size;
761 obj->prev = cont->prev;
762 obj->next = cont->next;
763
764 ret = true;
765 break;
766 }
767
768 cont = (tbl->lookupforward) ? cont->next : cont->prev;
769 }
770 qlisttbl_unlock(tbl);
771
772 if (ret == false) {
773 errno = ENOENT;
774 }
775
776 return ret;
777}
778
779/**
780 * qlisttbl->size(): Returns the number of elements in this table.
781 *
782 * @param tbl qlisttbl container pointer.
783 *
784 * @return the number of elements in this table.
785 */
786size_t qlisttbl_size(qlisttbl_t *tbl) {
787 return tbl->num;
788}
789
790/**
791 * qlisttbl->sort(): Sort keys in this table.
792 *
793 * @param tbl qlisttbl container pointer.
794 *
795 * @note
796 * It will sort the table in ascending manner, if you need descending order somehow,
797 * lookup-backword option will do the job without changing the order in the table.
798 *
799 * @code
800 * The appearence order of duplicated keys will be preserved in a sored table.
801 * See how duplicated key 'b' is ordered in below example
802 *
803 * [Unsorted] [sorted ASC] [sorted DES]
804 * d = 1 a = 2 d = 1
805 * a = 2 b = 3 c = 5
806 * b = 3 b = 4 b = 3
807 * b = 4 b = 6 b = 4
808 * c = 5 c = 5 b = 6
809 * b = 6 d = 1 a = 2
810 * @endcode
811 */
812void qlisttbl_sort(qlisttbl_t *tbl) {
813 // run bubble sort
814 qlisttbl_lock(tbl);
815 qlisttbl_obj_t *obj1, *obj2;
816 qlisttbl_obj_t tmpobj;
817 int n, n2, i;
818 for (n = tbl->num; n > 0;) {
819 n2 = 0;
820 for (i = 0, obj1 = tbl->first; i < (n - 1); i++, obj1 = obj1->next) {
821 obj2 = obj1->next; // this can't be null.
822 if (tbl->namecmp(obj1->name, obj2->name) > 0) {
823 // swapping contents is faster than adjusting links.
824 tmpobj = *obj1;
825 obj1->hash = obj2->hash;
826 obj1->name = obj2->name;
827 obj1->data = obj2->data;
828 obj1->size = obj2->size;
829 obj2->hash = tmpobj.hash;
830 obj2->name = tmpobj.name;
831 obj2->data = tmpobj.data;
832 obj2->size = tmpobj.size;
833
834 n2 = i + 1;
835 }
836 }
837 n = n2; // skip sorted tailing elements
838 }
839 qlisttbl_unlock(tbl);
840}
841
842/**
843 * qlisttbl->clear(): Removes all of the elements from this table.
844 *
845 * @param tbl qlisttbl container pointer.
846 */
847void qlisttbl_clear(qlisttbl_t *tbl) {
848 qlisttbl_lock(tbl);
849 qlisttbl_obj_t *obj;
850 for (obj = tbl->first; obj != NULL;) {
851 qlisttbl_obj_t *next = obj->next;
852 free(obj->name);
853 free(obj->data);
854 free(obj);
855 obj = next;
856 }
857
858 tbl->num = 0;
859 tbl->first = NULL;
860 tbl->last = NULL;
861 qlisttbl_unlock(tbl);
862}
863
864/**
865 * qlisttbl->save(): Save qlisttbl as plain text format
866 * Dumping direction is always forward(from the top to the bottom) to preserve
867 * the order when we load the file again.
868 *
869 * @param tbl qlisttbl container pointer.
870 * @param filepath save file path
871 * @param sepchar separator character between name and value. normally '=' is
872 * used.
873 * @param encode flag for encoding data . false can be used if the all data
874 * are string or integer type and has no new line. otherwise
875 * true must be set.
876 *
877 * @return true on success, otherwise false.
878 */
879bool qlisttbl_save(qlisttbl_t *tbl, const char *filepath, char sepchar,
880 bool encode) {
881 if (filepath == NULL) {
882 errno = EINVAL;
883 return false;
884 }
885
886 int fd;
887 if ((fd = open(filepath, O_CREAT|O_WRONLY|O_TRUNC, (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))) < 0) {
888 DEBUG("qlisttbl->save(): Can't open file %s", filepath);
889 return false;
890 }
891
892 char *gmtstr = qtime_gmt_str(0);
893 qio_printf(fd, -1, "# %s %s\n", filepath, gmtstr);
894 free(gmtstr);
895
896 qlisttbl_lock(tbl);
897 qlisttbl_obj_t *obj;
898 for (obj = tbl->first; obj; obj = obj->next) {
899 char *encval;
900 if (encode == true) encval = qurl_encode(obj->data, obj->size);
901 else encval = obj->data;
902 qio_printf(fd, -1, "%s%c%s\n", obj->name, sepchar, encval);
903 if (encode == true) free(encval);
904 }
905 qlisttbl_unlock(tbl);
906
907 close(fd);
908 return true;
909}
910
911/**
912 * qlisttbl->load(): Load and append entries from given filepath
913 * Loading direction is always appending at the bottom of the table to preserve
914 * the order as it was.
915 *
916 * @param tbl qlisttbl container pointer.
917 * @param filepath save file path
918 * @param sepchar separator character between name and value. normally '=' is
919 * used
920 * @param decode flag for decoding data
921 *
922 * @return the number of loaded entries, otherwise returns -1.
923 */
924ssize_t qlisttbl_load(qlisttbl_t *tbl, const char *filepath, char sepchar,
925 bool decode) {
926 // load file
927 char *str = qfile_load(filepath, NULL);
928 if (str == NULL) return -1;
929
930 // parse
931 qlisttbl_lock(tbl);
932 char *offset, *buf;
933 int cnt = 0;
934 for (offset = str; *offset != '\0'; ) {
935 // get one line into buf
936 for (buf = offset; *offset != '\n' && *offset != '\0'; offset++);
937 if (*offset != '\0') {
938 *offset = '\0';
939 offset++;
940 }
941 qstrtrim(buf);
942
943 // skip blank or comment line
944 if ((buf[0] == '#') || (buf[0] == '\0')) continue;
945
946 // parse
947 char *data = strdup(buf);
948 char *name = _q_makeword(data, sepchar);
949 qstrtrim(data);
950 qstrtrim(name);
951 if (decode == true) qurl_decode(data);
952
953 // add to the table.
954 qlisttbl_put(tbl, name, data, strlen(data) + 1);
955
956 free(name);
957 free(data);
958 }
959 qlisttbl_unlock(tbl);
960 free(str);
961
962 return cnt;
963}
964
965/**
966 * qlisttbl->debug(): Prints stored elements for debugging purposes.
967 *
968 * @param tbl qlisttbl container pointer.
969 * @param out output stream such as stdout or stderr.
970 *
971 * @return true on success, otherwise false.
972 * @retval errno will be set in error condition.
973 * - EIO : Invalid output stream.
974 */
975bool qlisttbl_debug(qlisttbl_t *tbl, FILE *out) {
976 if (out == NULL) {
977 errno = EIO;
978 return false;
979 }
980
981 qlisttbl_lock(tbl);
982 qlisttbl_obj_t *obj;
983 for (obj = tbl->first; obj; obj = obj->next) {
984 fprintf(out, "%s=" , obj->name);
985 _q_textout(out, obj->data, obj->size, MAX_HUMANOUT);
986 fprintf(out, " (%zu, %08x)\n" , obj->size, obj->hash);
987 }
988 qlisttbl_unlock(tbl);
989
990 return true;
991}
992
993/**
994 * qlisttbl->lock(): Enter critical section.
995 *
996 * @param tbl qlisttbl container pointer.
997 *
998 * @note
999 * Normally explicit locking is only needed when traverse all the
1000 * elements with qlisttbl->getnext().
1001 */
1002void qlisttbl_lock(qlisttbl_t *tbl) {
1003 Q_MUTEX_ENTER(tbl->qmutex);
1004}
1005
1006/**
1007 * qlisttbl->unlock(): Leave critical section.
1008 *
1009 * @param tbl qlisttbl container pointer.
1010 */
1011void qlisttbl_unlock(qlisttbl_t *tbl) {
1012 Q_MUTEX_LEAVE(tbl->qmutex);
1013}
1014
1015/**
1016 * qlisttbl->free(): Free qlisttbl_t
1017 *
1018 * @param tbl qlisttbl container pointer.
1019 */
1020void qlisttbl_free(qlisttbl_t *tbl) {
1021 qlisttbl_clear(tbl);
1022 Q_MUTEX_DESTROY(tbl->qmutex);
1023 free(tbl);
1024}
1025
1026#ifndef _DOXYGEN_SKIP
1027
1028// lock must be obtained from caller
1029static qlisttbl_obj_t *newobj(const char *name, const void *data, size_t size) {
1030 if (name == NULL || data == NULL || size <= 0) {
1031 errno = EINVAL;
1032 return NULL;
1033 }
1034
1035 // make a new object
1036 char *dup_name = strdup(name);
1037 void *dup_data = malloc(size);
1038 qlisttbl_obj_t *obj = (qlisttbl_obj_t *)malloc(sizeof(qlisttbl_obj_t));
1039 if (dup_name == NULL || dup_data == NULL || obj == NULL) {
1040 if (dup_name != NULL) free(dup_name);
1041 if (dup_data != NULL) free(dup_data);
1042 if (obj != NULL) free(obj);
1043 errno = ENOMEM;
1044 return NULL;
1045 }
1046 memcpy(dup_data, data, size);
1047 memset((void *)obj, '\0', sizeof(qlisttbl_obj_t));
1048
1049 // obj->hash = qhashmurmur3_32(dup_name);
1050 obj->name = dup_name;
1051 obj->data = dup_data;
1052 obj->size = size;
1053
1054 return obj;
1055}
1056
1057// lock must be obtained from caller
1058static bool insertobj(qlisttbl_t *tbl, qlisttbl_obj_t *obj) {
1059 // update hash
1060 obj->hash = qhashmurmur3_32(obj->name, strlen(obj->name));
1061
1062 qlisttbl_obj_t *prev = obj->prev;
1063 qlisttbl_obj_t *next = obj->next;
1064
1065 if (prev == NULL) tbl->first = obj;
1066 else prev->next = obj;
1067
1068 if (next == NULL) tbl->last = obj;
1069 else next->prev = obj;
1070
1071 // increase counter
1072 tbl->num++;
1073
1074 return true;
1075}
1076
1077// lock must be obtained from caller
1078static qlisttbl_obj_t *findobj(qlisttbl_t *tbl, const char *name, qlisttbl_obj_t *retobj) {
1079 if (retobj != NULL) {
1080 memset((void *)retobj, '\0', sizeof(qlisttbl_obj_t));
1081 }
1082
1083 if (name == NULL || tbl->num == 0) {
1084 errno = ENOENT;
1085 return NULL;
1086 }
1087
1088 uint32_t hash = qhashmurmur3_32(name, strlen(name));
1089 qlisttbl_obj_t *obj = (tbl->lookupforward) ? tbl->first : tbl->last;
1090 while (obj != NULL) {
1091 // name string will be compared only if the hash matches.
1092 if (tbl->namematch(obj, name, hash) == true) {
1093 if (retobj != NULL) {
1094 *retobj = *obj;
1095 }
1096 return obj;
1097 }
1098 obj = (tbl->lookupforward)? obj->next : obj->prev;
1099 }
1100
1101 // not found, set prev and next chain.
1102 if (retobj != NULL) {
1103 if (tbl->inserttop) {
1104 retobj->prev = NULL;
1105 retobj->next = tbl->first;
1106 } else {
1107 retobj->prev = tbl->last;
1108 retobj->next = NULL;
1109 }
1110 }
1111 errno = ENOENT;
1112
1113 return NULL;
1114}
1115
1116// key comp
1117static bool namematch(qlisttbl_obj_t *obj, const char *name, uint32_t hash) {
1118 if ((obj->hash == hash) && !strcmp(obj->name, name)) {
1119 return true;
1120 }
1121 return false;
1122}
1123
1124static bool namecasematch(qlisttbl_obj_t *obj, const char *name, uint32_t hash) {
1125 if (!strcasecmp(obj->name, name)) {
1126 return true;
1127 }
1128 return false;
1129}
1130
1131#endif /* _DOXYGEN_SKIP */
char * qurl_encode(const void *bin, size_t size)
Encode data using URL encoding (percent encoding).
Definition qencode.c:123
size_t qurl_decode(char *str)
Decode a URL-encoded string.
Definition qencode.c:188
void * qfile_load(const char *filepath, size_t *nbytes)
Load a file into memory.
Definition qfile.c:159
uint32_t qhashmurmur3_32(const void *data, size_t nbytes)
Get 32-bit Murmur3 hash.
Definition qhash.c:265
ssize_t qio_printf(int fd, int timeoutms, const char *format,...)
Write formatted output to a file descriptor.
Definition qio.c:323
qlisttbl_data_t * qlisttbl_getmulti(qlisttbl_t *tbl, const char *name, bool newmem, size_t *numobjs)
qlisttbl->getmulti(): Finds all objects with the given name and returns an array of objects.
Definition qlisttbl.c:496
bool qlisttbl_put(qlisttbl_t *tbl, const char *name, const void *data, size_t size)
qlisttbl->put(): Put an element to this table.
Definition qlisttbl.c:247
qlisttbl_t * qlisttbl(int options)
Create a new Q_LIST linked-list container.
Definition qlisttbl.c:149
bool qlisttbl_putstrf(qlisttbl_t *tbl, const char *name, const char *format,...)
qlisttbl->putstrf(): Put a formatted string into this table.
Definition qlisttbl.c:310
void qlisttbl_sort(qlisttbl_t *tbl)
qlisttbl->sort(): Sort keys in this table.
Definition qlisttbl.c:812
bool qlisttbl_putint(qlisttbl_t *tbl, const char *name, int64_t num)
qlisttbl->putint(): Put an integer into this table as a string.
Definition qlisttbl.c:340
void qlisttbl_freemulti(qlisttbl_data_t *objs)
qlisttbl->freemulti(): Deallocate object array returned by getmulti().
Definition qlisttbl.c:564
bool qlisttbl_removeobj(qlisttbl_t *tbl, const qlisttbl_obj_t *obj)
qlisttbl->removeobj(): Remove objects with given object pointer.
Definition qlisttbl.c:625
size_t qlisttbl_size(qlisttbl_t *tbl)
qlisttbl->size(): Returns the number of elements in this table.
Definition qlisttbl.c:786
void qlisttbl_free(qlisttbl_t *tbl)
qlisttbl->free(): Free qlisttbl_t
Definition qlisttbl.c:1020
void qlisttbl_lock(qlisttbl_t *tbl)
qlisttbl->lock(): Enter critical section.
Definition qlisttbl.c:1002
void qlisttbl_unlock(qlisttbl_t *tbl)
qlisttbl->unlock(): Leave critical section.
Definition qlisttbl.c:1011
char * qlisttbl_getstr(qlisttbl_t *tbl, const char *name, bool newmem)
qlisttbl->getstr(): Finds an object with given name and returns as string type.
Definition qlisttbl.c:434
bool qlisttbl_putstr(qlisttbl_t *tbl, const char *name, const char *str)
qlisttbl->putstr(): Put a string into this table.
Definition qlisttbl.c:293
void qlisttbl_clear(qlisttbl_t *tbl)
qlisttbl->clear(): Removes all of the elements from this table.
Definition qlisttbl.c:847
bool qlisttbl_save(qlisttbl_t *tbl, const char *filepath, char sepchar, bool encode)
qlisttbl->save(): Save qlisttbl as plain text format Dumping direction is always forward(from the top...
Definition qlisttbl.c:879
bool qlisttbl_debug(qlisttbl_t *tbl, FILE *out)
qlisttbl->debug(): Prints stored elements for debugging purposes.
Definition qlisttbl.c:975
bool qlisttbl_getnext(qlisttbl_t *tbl, qlisttbl_obj_t *obj, const char *name, bool newmem)
qlisttbl->getnext(): Get next element.
Definition qlisttbl.c:715
size_t qlisttbl_remove(qlisttbl_t *tbl, const char *name)
qlisttbl->remove(): Remove matched objects with given name.
Definition qlisttbl.c:583
void * qlisttbl_get(qlisttbl_t *tbl, const char *name, size_t *size, bool newmem)
qlisttbl->get(): Finds an object with given name.
Definition qlisttbl.c:385
ssize_t qlisttbl_load(qlisttbl_t *tbl, const char *filepath, char sepchar, bool decode)
qlisttbl->load(): Load and append entries from given filepath Loading direction is always appending a...
Definition qlisttbl.c:924
int64_t qlisttbl_getint(qlisttbl_t *tbl, const char *name)
qlisttbl->getint(): Finds an object with given name and returns as integer type.
Definition qlisttbl.c:451
char * qstrtrim(char *str)
Remove whitespace, including CR and LF, from both ends of a string.
Definition qstring.c:55
char * qtime_gmt_str(time_t utctime)
Get GMT time string formatted like 'Wed, 11-Nov-2007 23:19:25 GMT'.
Definition qtime.c:174