changeset 16:0cea9c0cfce7

Sync.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 02 Nov 2010 23:22:44 +0200
parents 4adf7093060c
children 77e34ec14f05
files th_config.c th_config.h th_util.c th_util.h
diffstat 4 files changed, 437 insertions(+), 91 deletions(-) [+]
line wrap: on
line diff
--- a/th_config.c	Sat Oct 30 17:48:40 2010 +0300
+++ b/th_config.c	Tue Nov 02 23:22:44 2010 +0200
@@ -1,7 +1,7 @@
 /*
  * Very simple configuration handling functions
  * Programmed and designed by Matti 'ccr' Hamalainen
- * (C) Copyright 2004-2008 Tecnic Software productions (TNSP)
+ * (C) Copyright 2004-2010 Tecnic Software productions (TNSP)
  *
  * Please read file 'COPYING' for information on license and distribution.
  */
@@ -19,15 +19,15 @@
 
 /* Free a given configuration (the values are not free'd)
  */
-void th_config_free(cfgitem_t *cfg)
+void th_cfg_free(cfgitem_t *cfg)
 {
     cfgitem_t *curr = cfg;
 
     while (curr != NULL) {
         cfgitem_t *next = curr->next;
         
-        if (curr->type == ITEM_BLOCK)
-            th_config_free((cfgitem_t *) curr->data);
+        if (curr->type == ITEM_SECTION)
+            th_cfg_free((cfgitem_t *) curr->data);
         
         th_free(curr->name);
         th_free(curr);
@@ -38,8 +38,7 @@
 
 /* Allocate and add new item to configuration
  */
-static cfgitem_t *th_config_add(cfgitem_t **cfg, char *name, int type,
-                 BOOL (*validate)(cfgitem_t *), void *data)
+static cfgitem_t *th_cfg_add(cfgitem_t **cfg, const char *name, const int type, void *data)
 {
     cfgitem_t *node;
 
@@ -54,7 +53,6 @@
     /* Set values */
     node->type = type;
     node->data = data;
-    node->validate = validate;
     node->name = th_strdup(name);
     
     /* Insert into linked list */
@@ -74,12 +72,12 @@
 
 /* Add integer type setting into give configuration
  */
-int th_config_add_int(cfgitem_t ** cfg, char * name, BOOL(*itemValidate) (cfgitem_t *),
+int th_cfg_add_int(cfgitem_t ** cfg, char * name,
               int *itemData, int itemDef)
 {
     cfgitem_t *node;
 
-    node = th_config_add(cfg, name, ITEM_INT, itemValidate, (void *) itemData);
+    node = th_cfg_add(cfg, name, ITEM_INT, (void *) itemData);
     if (node == NULL)
         return -1;
 
@@ -89,12 +87,12 @@
 }
 
 
-int th_config_add_hexvalue(cfgitem_t ** cfg, char * name, BOOL(*itemValidate) (cfgitem_t *),
+int th_cfg_add_hexvalue(cfgitem_t ** cfg, char * name,
               int *itemData, int itemDef)
 {
     cfgitem_t *node;
 
-    node = th_config_add(cfg, name, ITEM_HEX_TRIPLET, itemValidate, (void *) itemData);
+    node = th_cfg_add(cfg, name, ITEM_HEX_TRIPLET, (void *) itemData);
     if (node == NULL)
         return -1;
 
@@ -106,12 +104,12 @@
 
 /* Add unsigned integer type setting into give configuration
  */
-int th_config_add_uint(cfgitem_t ** cfg, char * name, BOOL(*itemValidate) (cfgitem_t *),
+int th_cfg_add_uint(cfgitem_t ** cfg, char * name,
                unsigned int * itemData, unsigned int itemDef)
 {
     cfgitem_t *node;
 
-    node = th_config_add(cfg, name, ITEM_UINT, itemValidate, (void *) itemData);
+    node = th_cfg_add(cfg, name, ITEM_UINT, (void *) itemData);
     if (node == NULL)
         return -1;
 
@@ -123,12 +121,12 @@
 
 /* Add strint type setting into given configuration
  */
-int th_config_add_string(cfgitem_t ** cfg, char * name, BOOL(*itemValidate) (cfgitem_t *),
+int th_cfg_add_string(cfgitem_t ** cfg, char * name,
               char ** itemData, char * itemDef)
 {
     cfgitem_t *node;
 
-    node = th_config_add(cfg, name, ITEM_STRING, itemValidate, (void *) itemData);
+    node = th_cfg_add(cfg, name, ITEM_STRING, (void *) itemData);
     if (node == NULL)
         return -1;
 
@@ -140,12 +138,12 @@
 
 /* Add boolean type setting into given configuration
  */
-int th_config_add_bool(cfgitem_t ** cfg, char * name, BOOL(*itemValidate) (cfgitem_t *),
+int th_cfg_add_bool(cfgitem_t ** cfg, char * name,
                BOOL * itemData, BOOL itemDef)
 {
     cfgitem_t *node;
 
-    node = th_config_add(cfg, name, ITEM_BOOL, itemValidate, (void *) itemData);
+    node = th_cfg_add(cfg, name, ITEM_BOOL, (void *) itemData);
     if (node == NULL)
         return -1;
 
@@ -157,11 +155,11 @@
 
 /* Add implicit comment
  */
-int th_config_add_comment(cfgitem_t ** cfg, char * comment)
+int th_cfg_add_comment(cfgitem_t ** cfg, char * comment)
 {
     cfgitem_t *node;
 
-    node = th_config_add(cfg, comment, ITEM_COMMENT, NULL, NULL);
+    node = th_cfg_add(cfg, comment, ITEM_COMMENT, NULL);
     if (node == NULL)
         return -1;
 
@@ -169,13 +167,28 @@
 }
 
 
-/* Add new block
+/* Add new section
  */
-int th_config_add_section(cfgitem_t ** cfg, char * name, cfgitem_t *data)
+int th_cfg_add_section(cfgitem_t ** cfg, char * name, cfgitem_t *data)
 {
     cfgitem_t *node;
     
-    node = th_config_add(cfg, name, ITEM_BLOCK, NULL, (void *) data);
+    node = th_cfg_add(cfg, name, ITEM_SECTION, (void *) data);
+    if (node == NULL)
+        return -1;
+
+    return 0;
+}
+
+
+int th_cfg_add_string_list(cfgitem_t ** cfg, char * name, qlist_t **data)
+{
+    cfgitem_t *node;
+    
+    if (data == NULL)
+        return -5;
+    
+    node = th_cfg_add(cfg, name, ITEM_STRING_LIST, (void *) data);
     if (node == NULL)
         return -1;
 
@@ -196,7 +209,8 @@
     PM_STRING,
     PM_INT,
     PM_BOOL,
-    PM_BLOCK
+    PM_SECTION,
+    PM_ARRAY
 };
 
 #define VADDCH(ch) if (strPos < SET_MAX_BUF) { tmpStr[strPos++] = ch; }
@@ -209,7 +223,7 @@
 } conffile_t;
 
 
-static void th_config_error(conffile_t *f, const char *fmt, ...)
+static void th_cfg_error(conffile_t *f, const char *fmt, ...)
 {
     va_list ap;
     va_start(ap, fmt);
@@ -219,7 +233,7 @@
 }
 
 
-static int th_config_read_sect(conffile_t *f, cfgitem_t * cfg, int nesting)
+static int th_cfg_read_sect(conffile_t *f, cfgitem_t * cfg, int nesting)
 {
     cfgitem_t *item = NULL;
     char tmpStr[SET_MAX_BUF + 1];
@@ -241,7 +255,7 @@
             switch (c = fgetc(f->file)) {
             case EOF:
                 if (parseMode != PM_NORMAL) {
-                    th_config_error(f,
+                    th_cfg_error(f,
                     "Unexpected end of file.\n");
                     parseMode = PM_ERROR;
                 } else
@@ -277,8 +291,7 @@
                     /* Check for validation errors */
                     return (validError) ? 1 : 0;
                 } else {
-                    th_config_error(f,
-                    "HMMM!\n");
+                    th_cfg_error(f, "Invalid nesting sequence encountered.\n");
                     parseMode = PM_ERROR;
                 }
             } else if (th_isalpha(c)) {
@@ -288,7 +301,7 @@
                 strPos = 0;
             } else {
                 /* Error! Invalid character found */
-                th_config_error(f,
+                th_cfg_error(f,
                     "Unexpected character '%c'.\n", c);
                 parseMode = PM_ERROR;
             }
@@ -312,7 +325,7 @@
                 else
                 {
                     /* Error! Key name string too long! */
-                    th_config_error(f,
+                    th_cfg_error(f,
                         "Config key name too long!");
                     parseMode = PM_ERROR;
                 }
@@ -320,7 +333,7 @@
             } else {
                 /* Error! Invalid character found */
                 tmpStr[strPos] = 0;
-                th_config_error(f,
+                th_cfg_error(f,
                     "Unexpected character '%c' in key name '%s'.\n", c, tmpStr);
                 parseMode = PM_ERROR;
             }
@@ -348,6 +361,10 @@
                         nextMode = PM_STRING;
                         break;
 
+                    case ITEM_STRING_LIST:
+                        nextMode = PM_ARRAY;
+                        break;
+
                     case ITEM_INT:
                     case ITEM_UINT:
                         nextMode = PM_INT;
@@ -357,8 +374,8 @@
                         nextMode = PM_BOOL;
                         break;
                     
-                    case ITEM_BLOCK:
-                        nextMode = PM_BLOCK;
+                    case ITEM_SECTION:
+                        nextMode = PM_SECTION;
                         break;
                     }
 
@@ -368,7 +385,7 @@
                     strPos = 0;
                 } else {
                     /* Error! No configuration key by this name found */
-                    th_config_error(f,
+                    th_cfg_error(f,
                         "No such configuration setting ('%s')\n", tmpStr);
                     parseMode = PM_ERROR;
                 }
@@ -376,7 +393,7 @@
                 c = -1;
             } else {
                 /* Error! '=' expected! */
-                th_config_error(f,
+                th_cfg_error(f,
                     "Unexpected character '%c', assignation '=' was expected.\n", c);
                 parseMode = PM_ERROR;
             }
@@ -398,15 +415,39 @@
             }
             break;
 
-        case PM_BLOCK:
-            /* Block parsing mode */
+        case PM_ARRAY:
+            if (isStart) {
+                switch (item->type) {
+                    case ITEM_STRING_LIST:
+                        prevMode = parseMode;
+                        parseMode = PM_STRING;
+                        break;
+                }
+            } else if (c == ',') {
+                switch (item->type) {
+                    case ITEM_STRING_LIST:
+                        c = -1;
+                        isStart = TRUE;
+                        prevMode = parseMode;
+                        parseMode = PM_NEXT;
+                        nextMode = PM_STRING;
+                        break;
+                }
+            } else {
+                prevMode = parseMode;
+                parseMode = PM_NORMAL;
+            }
+            break;
+
+        case PM_SECTION:
+            /* Section parsing mode */
             if (c != '{') {
-                /* Error! Block start '{' expected! */
-                th_config_error(f,
-                    "Unexpected character '%c', block start '{' was expected.\n", c);
+                /* Error! Section start '{' expected! */
+                th_cfg_error(f,
+                    "Unexpected character '%c', section start '{' was expected.\n", c);
                 parseMode = PM_ERROR;
             } else {
-                int res = th_config_read_sect(f, (cfgitem_t *) item->data, nesting + 1);
+                int res = th_cfg_read_sect(f, (cfgitem_t *) item->data, nesting + 1);
                 c = -1;
                 if (res > 0)
                     validError = TRUE;
@@ -430,23 +471,32 @@
                 /* End of string, set the value */
                 tmpStr[strPos] = 0;
                 
-                if (item->type == ITEM_HEX_TRIPLET) {
-                } else if (item->type == ITEM_STRING) {
-                    th_pstrcpy((char **) item->data, tmpStr);
+                switch (item->type) {
+                    case ITEM_HEX_TRIPLET:
+                        *(int *) item->data = th_get_hex_triplet(tmpStr);
+                        prevMode = parseMode;
+                        parseMode = PM_NORMAL;
+                        break;
+                    case ITEM_STRING:
+                        th_pstrcpy((char **) item->data, tmpStr);
+                        prevMode = parseMode;
+                        parseMode = PM_NORMAL;
+                        break;
+                    case ITEM_STRING_LIST:
+                        th_llist_append(item->list, th_strdup(tmpStr));
+                        prevMode = parseMode;
+                        parseMode = PM_NEXT;
+                        nextMode = PM_ARRAY;
+                        break;
                 }
                 
-                if (item->validate != NULL && !item->validate(item))
-                    validError = TRUE;
-
-                prevMode = parseMode;
-                parseMode = PM_NORMAL;
             } else {
                 /* Add character to string */
                 VADDCH(c)
                 else
                 {
                     /* Error! String too long! */
-                    th_config_error(f,
+                    th_cfg_error(f,
                         "String too long! Maximum is %d characters.",
                         SET_MAX_BUF);
                     parseMode = PM_ERROR;
@@ -460,7 +510,7 @@
             /* Integer parsing mode */
             if (isStart && item->type == ITEM_UINT && c == '-') {
                 /* Error! Negative values not allowed for unsigned ints */
-                th_config_error(f,
+                th_cfg_error(f,
                         "Negative value specified for %s, unsigned value expected.",
                         item->name);
                 parseMode = PM_ERROR;
@@ -484,14 +534,12 @@
                     *((unsigned int *) item->data) = atol(tmpStr);
                     break;
                 }
-                if (item->validate != NULL && !item->validate(item))
-                    validError = TRUE;
 
                 prevMode = parseMode;
                 parseMode = PM_NORMAL;
             } else {
                 /* Error! Unexpected character. */
-                th_config_error(f,
+                th_cfg_error(f,
                     "Unexpected character '%c' for integer setting '%s'.",
                     c, item->name);
                 parseMode = PM_ERROR;
@@ -499,7 +547,7 @@
 
             if (isError) {
                 /* Error! String too long! */
-                th_config_error(f, "String too long! Maximum is %d characters.",
+                th_cfg_error(f, "String too long! Maximum is %d characters.",
                         SET_MAX_BUF);
                 parseMode = PM_ERROR;
             }
@@ -535,14 +583,11 @@
                 }
                 
                 if (isError) {
-                    th_config_error(f, "Invalid boolean value for '%s'.\n", item->name);
+                    th_cfg_error(f, "Invalid boolean value for '%s'.\n", item->name);
                     parseMode = PM_ERROR;
                 } else {
                     *((BOOL *) item->data) = tmpBool;
 
-                    if (item->validate != NULL && !item->validate(item))
-                        validError = TRUE;
-
                     prevMode = parseMode;
                     parseMode = PM_NORMAL;
                 }
@@ -564,7 +609,7 @@
 }
 
 
-int th_config_read(FILE *inFile, char *filename, cfgitem_t * cfg)
+int th_cfg_read(FILE *inFile, char *filename, cfgitem_t * cfg)
 {
     conffile_t f;
     
@@ -572,7 +617,7 @@
     f.filename = filename;
     f.line = 1;
     
-    return th_config_read_sect(&f, cfg, 0);
+    return th_cfg_read_sect(&f, cfg, 0);
 }
 
 
@@ -586,7 +631,7 @@
 }
 
 
-static int th_config_write_sect(conffile_t *f, cfgitem_t *item, int nesting)
+static int th_cfg_write_sect(conffile_t *f, cfgitem_t *item, int nesting)
 {
     while (item != NULL) {
         if (item->type == ITEM_COMMENT) {
@@ -599,42 +644,67 @@
             
             switch (item->type) {
             case ITEM_STRING:
-                if (*((char **) item->data) == NULL) {
+                if (*(item->val_str) == NULL) {
                     if (fprintf(f->file, "#%s = \"\"\n", item->name) < 0)
                         return -3;
                 } else {
                     if (fprintf(f->file, "%s = \"%s\"\n",
-                        item->name, *((char **) item->data)) < 0)
+                        item->name, *(item->val_str)) < 0)
+                        return -3;
+                }
+                break;
+
+            case ITEM_STRING_LIST:
+                if (*(item->list) == NULL) {
+                    if (fprintf(f->file, "#%s = \"\", \"\"\n", item->name) < 0)
+                        return -3;
+                } else {
+                    qlist_t *node = *(item->list);
+                    size_t n = th_llist_length(node);
+                    if (fprintf(f->file, "%s = ", item->name) < 0)
+                        return -3;
+                    
+                    while (node != NULL) {
+                        if (node->data != NULL)
+                            fprintf(f->file, "\"%s\"", (char *) node->data);
+
+                        if (--n > 0)
+                            fprintf(f->file, ", ");
+                        
+                        node = node->next;
+                    }
+
+                    if (fprintf(f->file, "\n") < 0)
                         return -3;
                 }
                 break;
 
             case ITEM_INT:
                 if (fprintf(f->file, "%s = %i\n",
-                    item->name, *((int *) item->data)) < 0)
+                    item->name, *(item->val_int)) < 0)
                     return -4;
                 break;
 
             case ITEM_UINT:
                 if (fprintf(f->file, "%s = %d\n",
-                    item->name, *((unsigned int *) item->data)) < 0)
+                    item->name, *(item->val_uint)) < 0)
                     return -5;
                 break;
 
             case ITEM_BOOL:
                 if (fprintf(f->file, "%s = %s\n",
-                    item->name, *((BOOL *) item->data) ? "yes" : "no") < 0)
+                    item->name, *(item->val_bool) ? "yes" : "no") < 0)
                     return -6;
                 break;
             
-            case ITEM_BLOCK:
+            case ITEM_SECTION:
                 {
                 int res;
-                if (fprintf(f->file, "\n%s = {\n", item->name) < 0)
+                if (fprintf(f->file, "%s = {\n", item->name) < 0)
                     return -7;
-                res = th_config_write_sect(f, (cfgitem_t *) item->data, nesting + 1);
+                res = th_cfg_write_sect(f, (cfgitem_t *) item->data, nesting + 1);
                 if (res != 0) return res;
-                if (fprintf(f->file, "} # End of '%s'\n\n", item->name) < 0)
+                if (fprintf(f->file, "}\n\n") < 0)
                     return -8;
                 }
                 break;
@@ -653,7 +723,7 @@
 }
 
 
-int th_config_write(FILE *outFile, char *filename, cfgitem_t *cfg)
+int th_cfg_write(FILE *outFile, char *filename, cfgitem_t *cfg)
 {
     conffile_t f;
     
@@ -667,7 +737,7 @@
     fprintf(outFile, "# Configuration written by %s %s\n\n", 
         th_prog_fullname, th_prog_version);
     
-    return th_config_write_sect(&f, cfg, 0);
+    return th_cfg_write_sect(&f, cfg, 0);
 }
 
 
--- a/th_config.h	Sat Oct 30 17:48:40 2010 +0300
+++ b/th_config.h	Tue Nov 02 23:22:44 2010 +0200
@@ -19,14 +19,17 @@
 /* Definitions
  */
 enum ITEM_TYPE {
-    ITEM_BLOCK = 1,
+    ITEM_SECTION = 1,
     ITEM_COMMENT,
     ITEM_STRING,
     ITEM_INT,
     ITEM_UINT,
     ITEM_BOOL,
     ITEM_FLOAT,
-    ITEM_HEX_TRIPLET
+    ITEM_HEX_TRIPLET,
+
+    ITEM_STRING_LIST,
+    ITEM_HEX_TRIPLET_LIST
 };
 
 
@@ -34,14 +37,15 @@
     int  type;
     char *name;
     union {
-        void *data;
         int *val_int;
         unsigned int *val_uint;
-        char *val_str;
+        char **val_str;
         BOOL *val_bool;
+
+        void *data;
+        qlist_t **list;
         struct _cfgitem_t *section;
     };
-    BOOL (*validate)(struct _cfgitem_t *);
     
     struct _cfgitem_t *next, *prev;
 } cfgitem_t;
@@ -49,19 +53,20 @@
 
 /* Functions
  */
-int     th_config_read(FILE *, char *, cfgitem_t *);
-void    th_config_free(cfgitem_t *);
-int     th_config_write(FILE *, char *, cfgitem_t *);
+int     th_cfg_read(FILE *, char *, cfgitem_t *);
+void    th_cfg_free(cfgitem_t *);
+int     th_cfg_write(FILE *, char *, cfgitem_t *);
 
-int     th_config_add_section(cfgitem_t **cfg, char *name, cfgitem_t *data);
-int     th_config_add_comment(cfgitem_t **cfg, char *comment);
-int     th_config_add_int(cfgitem_t **cfg, char *name, BOOL (*validate)(cfgitem_t *), int *data, int itemDef);
-int     th_config_add_uint(cfgitem_t **cfg, char *name, BOOL (*validate)(cfgitem_t *), unsigned int *data, unsigned int itemDef);
-int     th_config_add_string(cfgitem_t **cfg, char *name, BOOL (*validate)(cfgitem_t *), char **data, char *itemDef);
-int     th_config_add_bool(cfgitem_t **cfg, char *name, BOOL (*validate)(cfgitem_t *), BOOL *data, BOOL itemDef);
-int     th_config_add_float(cfgitem_t **cfg, char *name, BOOL (*validate)(cfgitem_t *), float *data, float itemDef);
-int     th_config_add_hexvalue(cfgitem_t **cfg, char *name, BOOL (*validate)(cfgitem_t *), int *data, int itemDef);
+int     th_cfg_add_section(cfgitem_t **cfg, char *name, cfgitem_t *data);
+int     th_cfg_add_comment(cfgitem_t **cfg, char *comment);
+int     th_cfg_add_int(cfgitem_t **cfg, char *name, int *data, int itemDef);
+int     th_cfg_add_uint(cfgitem_t **cfg, char *name, unsigned int *data, unsigned int itemDef);
+int     th_cfg_add_string(cfgitem_t **cfg, char *name, char **data, char *itemDef);
+int     th_cfg_add_bool(cfgitem_t **cfg, char *name, BOOL *data, BOOL itemDef);
+int     th_cfg_add_float(cfgitem_t **cfg, char *name, float *data, float itemDef);
+int     th_cfg_add_hexvalue(cfgitem_t **cfg, char *name, int *data, int itemDef);
 
+int     th_cfg_add_string_list(cfgitem_t **cfg, char *name, qlist_t **list);
 
 #ifdef __cplusplus
 }
--- a/th_util.c	Sat Oct 30 17:48:40 2010 +0300
+++ b/th_util.c	Tue Nov 02 23:22:44 2010 +0200
@@ -1,7 +1,7 @@
 /*
  * Generic utility-functions, macros and defaults
  * Programmed and designed by Matti 'ccr' Hamalainen
- * (C) Copyright 2002-2008 Tecnic Software productions (TNSP)
+ * (C) Copyright 2002-2010 Tecnic Software productions (TNSP)
  *
  * Please read file 'COPYING' for information on license and distribution.
  */
@@ -151,3 +151,246 @@
     return p;
 }
 #endif
+
+
+/* Doubly linked list handling
+ *
+ * In this implementation first node's prev points to last node of the list,
+ * and last node's next is NULL. This way we can semi-efficiently traverse to
+ * beginning and end of the list, assuming user does not do weird things.
+ */
+qlist_t * th_llist_new(void *data)
+{
+    qlist_t *res = th_calloc(sizeof(qlist_t), 1);
+    res->data = data;
+    return res;
+}
+
+void th_llist_free_func(qlist_t *list, void (*freefunc)(void *data))
+{
+    qlist_t *curr = list;
+
+    while (curr != NULL) {
+        qlist_t *next = curr->next;
+        if (freefunc != NULL && curr->data != NULL)
+            freefunc(curr->data);
+        th_free(curr);
+        curr = next;
+    }
+}
+
+
+void th_llist_free(qlist_t *list)
+{
+    th_llist_free_func(list, NULL);
+}
+
+void th_llist_append_node(qlist_t **list, qlist_t *node)
+{
+    if (*list != NULL) {
+        node->prev = (*list)->prev;
+        (*list)->prev->next = node;
+        (*list)->prev = node;
+        (*list)->num++;
+    } else {
+        *list = node;
+        node->prev = *list;
+        (*list)->num = 1;
+    }
+
+    node->next = NULL;
+}
+
+
+qlist_t *th_llist_append(qlist_t **list, void *data)
+{
+    qlist_t *node = th_llist_new(data);
+
+    th_llist_append_node(list, node);
+
+    return node;
+}
+
+
+void th_llist_prepend_node(qlist_t **list, qlist_t *node)
+{
+    if (*list != NULL) {
+        node->prev = (*list)->prev;
+        node->next = *list;
+        (*list)->prev = node;
+        node->num = (*list)->num + 1;
+        *list = node;
+    } else {
+        *list = node->prev = node;
+        node->next = NULL;
+        (*list)->num = 1;
+    }
+
+}
+
+
+qlist_t *th_llist_prepend(qlist_t **list, void *data)
+{
+    qlist_t *node = th_llist_new(data);
+
+    th_llist_prepend_node(list, node);
+
+    return node;
+}
+
+/*
+1) Remove a middle node
+
+    node0->prev->next = node->next (node1)
+    node0->next->prev = node->prev (list)
+
+    node2 <- list <=> node0 <=> node1 <=> node2 -> NULL
+    node2 <- list <=> node1 <=> node2 -> NULL
+
+2) Remove first node when many items
+
+
+    node2 <- list <=> node0 <=> node1 <=> node2 -> NULL
+    node2 <- node0 <=> node1 <=> node2 -> NULL
+
+    *list = node0
+
+3) Remove last node in list
+
+    if (node->next == NULL) {
+        list->prev = node->prev;
+        node->prev->next = NULL;
+    }
+
+    node2 <- list <=> node0 <=> node1 <=> node2 -> NULL
+    node1 <- list <=> node0 <=> node1 -> NULL
+
+4) Remove last
+
+    list <- list -> NULL
+    
+    
+*/
+static void th_llist_delete_node_fast(qlist_t **list, qlist_t *node)
+{
+    if (node == *list) {
+        /* First node in list */
+        qlist_t *tmp = (*list)->next;
+        if (tmp != NULL) {
+            tmp->num = (*list)->num - 1;
+            tmp->prev = (*list)->prev;
+        }
+        *list = tmp;
+    } else {
+        /* Somewhere in middle or end */
+        if (node->prev != NULL)
+            node->prev->next = node->next;
+
+        if (node->next != NULL)
+            node->next->prev = node->prev;
+        else
+            (*list)->prev = node; /* Last node */
+
+        (*list)->num--;
+    }
+    
+    node->next = node->prev = NULL;
+}
+
+
+void th_llist_delete_node(qlist_t **list, qlist_t *node)
+{
+    qlist_t *curr = *list;
+
+    while (curr != NULL) {
+        qlist_t *next = curr->next;
+        if (curr == node) {
+            th_llist_delete_node_fast(list, curr);
+            th_free(node);
+            break;
+        }
+        curr = next;
+    }
+}
+
+
+void th_llist_delete(qlist_t **list, const void *data)
+{
+    qlist_t *curr = *list;
+
+    while (curr != NULL) {
+        qlist_t *next = curr->next;
+        if (curr->data == data) {
+            th_llist_delete_node_fast(list, curr);
+            th_free(curr);
+            break;
+        }
+        curr = next;
+    }
+}
+
+
+qlist_t * th_llist_get_nth(qlist_t *list, const size_t n)
+{
+    qlist_t *curr = list;
+    size_t i;
+
+    for (i = 0; curr != NULL && i < n; curr = curr->next, i++);
+
+    return curr;
+}
+
+
+size_t th_llist_length(const qlist_t *list)
+{
+    if (list == NULL)
+        return 0;
+    else
+        return list->num;
+}
+
+
+ssize_t th_llist_position(const qlist_t *list, const qlist_t *node)
+{
+    const qlist_t *curr = list;
+    ssize_t i = 0;
+
+    while (curr != NULL) {
+        if (curr == node)
+            return i;
+        else
+            i++;
+
+        curr = curr->next;
+    }
+    
+    return -1;
+}
+
+
+qlist_t * th_llist_find(qlist_t *list, const void *data)
+{
+    qlist_t *curr = list;
+
+    while (curr != NULL) {
+        if (curr->data == data)
+            return curr;
+        curr = curr->next;
+    }
+
+    return NULL;
+}
+
+
+qlist_t * th_llist_find_func(qlist_t *list, const void *userdata, int (compare)(const void *, const void *))
+{
+    qlist_t *curr = list;
+
+    while (curr != NULL) {
+        if (compare(curr->data, userdata) == 0)
+            return curr;
+        curr = curr->next;
+    }
+
+    return NULL;
+}
--- a/th_util.h	Sat Oct 30 17:48:40 2010 +0300
+++ b/th_util.h	Tue Nov 02 23:22:44 2010 +0200
@@ -1,7 +1,7 @@
 /*
  * Generic utility-functions, macros and defaults
  * Programmed and designed by Matti 'ccr' Hamalainen
- * (C) Copyright 2002-2009 Tecnic Software productions (TNSP)
+ * (C) Copyright 2002-2010 Tecnic Software productions (TNSP)
  *
  * Please read file 'COPYING' for information on license and distribution.
  */
@@ -15,6 +15,7 @@
 #include "th_types.h"
 #include <stdarg.h>
 #include <stdlib.h>
+#include <sys/types.h>
 #ifndef HAVE_NO_ASSERT
 #include <assert.h>
 #endif
@@ -83,6 +84,33 @@
 void    *th_memset(void *, int, size_t);
 #endif
 
+
+/* Doubly linked list handling
+ */
+typedef struct _qlist_t {
+    void *data;
+    size_t num;
+    struct _qlist_t *prev, *next;
+} qlist_t;
+
+
+qlist_t * th_llist_new(void *data);
+void      th_llist_free(qlist_t *list);
+void      th_llist_free_func(qlist_t *list, void (*freefunc)(void *data));
+
+qlist_t * th_llist_append(qlist_t **list, void *data);
+qlist_t * th_llist_prepend(qlist_t **list, void *data);
+void      th_llist_delete(qlist_t **list, const void *data);
+void      th_llist_delete_node(qlist_t **list, qlist_t *node);
+
+qlist_t * th_llist_get_nth(qlist_t *list, const size_t n);
+size_t    th_llist_length(const qlist_t *list);
+ssize_t   th_llist_position(const qlist_t *list, const qlist_t *node);
+
+qlist_t * th_llist_find(qlist_t *list, const void *data);
+qlist_t * th_llist_find_func(qlist_t *list, const void *userdata, int (compare)(const void *, const void *));
+
+
 #ifdef __cplusplus
 }
 #endif