Initial revision
[egate.git] / res+log / gw-res.c
diff --git a/res+log/gw-res.c b/res+log/gw-res.c
new file mode 100644 (file)
index 0000000..3e43e77
--- /dev/null
@@ -0,0 +1,552 @@
+/*
+   gw-res.c: Implementation of resource management.
+
+   Europagate, 1994-1995.
+
+   $Log: gw-res.c,v $
+   Revision 1.1  1995/02/09 17:27:11  adam
+   Initial revision
+
+
+   Initial:       Dec  8, 94 (Adam Dickmeiss)
+   Last update:   Dec 19, 94 (Adam Dickmeiss)
+
+ */
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+
+#if HAVE_FLOCK
+#include <sys/file.h>
+#else
+#include <sys/types.h>
+#endif
+
+#include <gw-log.h>
+#include "gw-resp.h"
+
+/*
+   symtab_open: Create empty symbol table.
+     symbol table handle is returned.
+ */
+
+static struct res_symtab *symtab_open (void)
+{
+    struct res_symtab *symtab;
+
+    symtab = malloc (sizeof(*symtab));
+    if (!symtab)
+        return NULL;
+    symtab->next = NULL;
+    return symtab;
+}
+
+/*
+   symtab_close: Delete symbol table.
+ */
+static void symtab_close (struct res_symtab *symtab)
+{
+    struct res_sym_entry *entry, *entry1;
+
+    for (entry = symtab->next; entry; entry = entry1)
+    {
+        entry1 = entry->next;
+        free (entry);
+    }
+    free (symtab);
+}
+
+/*
+   symtab_override: Add symbol to table. 'rl' holds symbol
+     table entry info. If the symbol is already present it
+     will override old info.
+ */
+static int symtab_override (struct res_symtab *symtab,
+                            struct res_line_info *rl)
+{
+    struct res_sym_entry *entry;
+
+    for (entry = symtab->next; entry; entry=entry->next)
+        if (!strcmp (entry->info->name, rl->name))
+        {
+            entry->info = rl;
+            return 1;
+        }
+    entry = malloc (sizeof(*entry));
+    if (!entry)
+        return -1;
+    entry->next = symtab->next;
+    symtab->next = entry;
+    entry->info = rl;
+    return 0;
+}
+
+/*
+   symtab_lookup: Symbol table lookup. If successful info is returned; 
+     otherwise NULL is returned.
+ */
+static struct res_line_info *symtab_lookup (struct res_symtab *symtab, 
+                                            const char *name)
+{
+    struct res_sym_entry *entry;
+
+    for (entry = symtab->next; entry; entry=entry->next)
+        if (!strcmp (entry->info->name, name))
+            return entry->info;
+    return NULL;
+}
+
+/*
+   lock_file: File locking using fcntl.
+ */
+#if !HAVE_FLOCK
+static void lock_file (int fd, int type)
+{
+    struct flock area;
+    area.l_type = type;
+    area.l_whence = SEEK_SET;
+    area.l_start = 0L;
+    area.l_len = 0L;
+    fcntl (fd, F_SETLKW, &area);
+}
+#endif
+
+/*
+   gw_res_init: A resource handle is returned by this function
+     describing empty resources.
+ */
+GwRes gw_res_init (void)
+{
+    GwRes p;
+
+    if (!(p = malloc (sizeof(*p))))
+        return p;
+    p->files = NULL;
+    p->symtab = symtab_open ();
+    if (!p->symtab)
+    {
+        free (p);
+        return NULL;
+    }
+    return p;
+}
+
+/*
+   gw_res_close: The resources described by 'id' are freed.
+      No further references to 'id' are allowed.
+ */
+void gw_res_close (GwRes id)
+{
+    struct res_line_info *rl, *rl1;
+    struct res_file_info *rf, *rf1;
+    
+    assert (id);
+
+    symtab_close (id->symtab);
+    for (rf = id->files; rf; rf = rf1)
+    {
+        for (rl = rf->lines; rl; rl = rl1)
+        {
+            free (rl->name);
+            free (rl->value);
+            rl1 = rl->next;
+            free (rl);
+        }
+        free (rf->fname);
+        rf1 = rf->next;
+        free (rf);
+    }
+    free (id);
+}
+
+/*
+   add_name: add a node with line information.
+ */
+static struct res_line_info *add_name (enum res_kind kind, 
+                                       const char *name, const char *value)
+{
+    struct res_line_info *rl;
+
+    if (!(rl = malloc (sizeof(*rl))))
+        return NULL;
+    rl->next = NULL;
+    rl->kind = kind;
+    if (name)
+    {
+        rl->name = gw_strdup (name);
+        if (!rl->name)
+        {
+            free (rl);
+            return NULL;
+        }
+    }
+    else
+        rl->name = NULL;
+    if (value)
+    {
+        rl->value = gw_strdup (value);
+        if (!rl->value)
+        {
+            free (rl->name);
+            free (rl);
+            return NULL;
+        }
+    }
+    else
+        rl->value = NULL;
+    return rl;
+}
+
+/*
+   gw_res_merge: The resources described by 'id' are merged by the contents
+     of  'filename'. If a resource is duplicated (in both resources 'id'
+     and the file) the resource is set to the value specified in 'filename'.
+     This function returns 0 on success; -1 on failure ('filename'
+     could not be read)
+ */
+int gw_res_merge (GwRes id, const char *filename)
+{
+    FILE *inf;
+    char buffer[1024];
+    char value[2048];
+    struct res_file_info *ri;
+    struct res_line_info **rlp, *rl;
+    struct res_line_info *rl_last = NULL;
+    int err = 0;
+
+    assert (id);
+    
+    gw_log (GW_LOG_DEBUG, "res", "gw_res_merge");
+    gw_log (GW_LOG_DEBUG, "res", "checking %s", filename);
+    if (!(inf = fopen (filename, "r")))
+        return -1;
+#if HAVE_FLOCK
+    flock (fileno(inf), LOCK_SH);
+#else
+    lock_file (fileno (inf), F_RDLCK);
+#endif
+    if (!(ri = malloc (sizeof (*ri))))
+    {
+        fclose (inf);
+        return -1;
+    }
+    if (!(ri->fname = gw_strdup (filename)))
+    {
+        free (ri);
+        fclose (inf);
+        return -2;
+    }
+    gw_log (GW_LOG_DEBUG, "res", "reading %s", filename);
+    ri->next = id->files;
+    id->files = ri;
+    rlp = &ri->lines;
+    ri->lines = NULL;
+    while (fgets (buffer, sizeof(buffer)-1, inf))
+    {
+        char *cp;
+        while ((cp = strchr (buffer, '\n')))
+            *cp = '\0';
+        if (*buffer == '#')
+        {   /* comment line */
+            rl = add_name (comment, NULL, buffer);
+            if (!rl)
+            {
+                err = -2;
+                break;
+            }
+            *rlp = rl;
+            rlp = &rl->next;
+        }
+        else if (*buffer == '\0' || *buffer == ' ' || *buffer == '\t')
+        {
+            int i = 0;
+            while (buffer[i] == ' ' || buffer[i] == '\t')
+                i++;
+            if (buffer[i] == '\0')
+            {   /* empty line */
+                rl = add_name (blank, NULL, NULL);
+                if (!rl)
+                {
+                    err = -2;
+                    break;
+                }
+                *rlp = rl;
+                rlp = &rl->next;
+            }
+            else
+            {   /* continuation line */
+                int j = strlen (buffer)-1;
+                /* strip trailing blanks */
+                while (buffer[j] == '\t' || buffer[j] == ' ')
+                    --j;
+                buffer[j+1] = '\0';
+                if (rl_last)
+                {
+                    if (strlen(value)+strlen(buffer+i) >= sizeof(value)-2)
+                    {
+                        gw_log (GW_LOG_WARN, "res", "Resource `%s' is "
+                                " truncated", rl_last->name);
+                    }
+                    else
+                    {
+                        /* effectively add one blank, then buffer */
+                        strcat (value, " ");
+                        strcat (value, buffer+i);
+                    }
+                }
+                else
+                    gw_log (GW_LOG_WARN, "res", "Resource file has bad "
+                            "continuation line");
+            }
+        }
+        else 
+        {   /* resource line */
+            int i = 0;
+            if (rl_last)
+            {
+                rl_last->value = gw_strdup (value);
+                rl_last = NULL;
+            }
+            while (buffer[i] && buffer[i] != ':')
+                i++;
+            if (buffer[i] == ':')
+            {
+                int j = strlen(buffer)-1;
+                buffer[i++] = '\0';            /* terminate name */
+                while (buffer[i] == ' ' || buffer[i] == '\t')
+                    i++;                       /* skip blanks before */
+                while (buffer[j] == '\t' || buffer[j] == ' ')
+                    --j;                       /* skip blanks after */
+                buffer[j+1] = '\0';            /* terminate value */
+                strcpy (value, buffer+i);
+                rl_last = add_name (resource, buffer, NULL);
+                if (!rl_last)
+                    err = -2;
+                else
+                {
+                    *rlp = rl_last;
+                    rlp = &rl_last->next;
+                }
+            }
+        }
+    }
+    if (rl_last)
+        rl_last->value = gw_strdup (value);
+#if HAVE_FLOCK
+    flock (fileno (inf), LOCK_UN);
+#else
+    lock_file (fileno (inf), F_UNLCK);
+#endif
+    fclose (inf);
+    gw_log (GW_LOG_DEBUG, "res", "close of %s", filename);
+    for (rl = ri->lines; rl; rl = rl->next)
+    {
+        switch (rl->kind)
+        {
+        case comment:
+            gw_log (GW_LOG_DEBUG, "res", "%s", rl->value);
+            break;
+        case resource:
+            gw_log (GW_LOG_DEBUG, "res", "%s: %s", rl->name, rl->value);
+            if (symtab_override (id->symtab, rl) < 0)
+                err = -2;
+            break;
+        case blank:
+            gw_log (GW_LOG_DEBUG, "res", "");
+            break;
+        default:
+            assert (0);
+        }
+    }
+    gw_log (GW_LOG_DEBUG, "res", "gw_res_merge returned %d", err);
+    return err;
+}
+
+/*
+   gw_res_get: The resource with name 'name' is checked in the resources
+     represented by 'id'. If the resource is present a pointer to the
+     value (null-terminated string) is returned. If the value is not
+     present the value of 'def' is returned.
+ */
+const char *gw_res_get (GwRes id, const char *name, const char *def)
+{
+    struct res_line_info *rl;
+
+    assert (id);
+    rl = symtab_lookup (id->symtab, name);
+    if (!rl)
+        return def;
+    return rl->value;
+}
+
+/*
+   gw_res_put: Change a resource - modify if it exists - add if not
+     already there. The resource will have impact on the file name
+     'fname'. Use gw_res_commit (see below) to actually write to the
+     resource file.
+ */
+int gw_res_put (GwRes id, const char *name, const char *value, 
+                const char *fname)
+{
+    struct res_file_info *ri;
+    struct res_line_info **rlp;
+    assert (id);
+    assert (fname);
+
+    for (ri = id->files; ri; ri = ri->next)
+        if (!strcmp (ri->fname, fname))
+            break;
+    if (!ri)
+    {
+        if (!(ri = malloc (sizeof (*ri))))
+            return -1;
+        if (!(ri->fname = gw_strdup (fname)))
+        {
+            free (ri);
+            return -1;
+        }
+        ri->next = id->files;
+        id->files = ri;
+        ri->lines = NULL;
+    }
+    for (rlp = &ri->lines; *rlp; rlp = &(*rlp)->next)
+        if (!strcmp ((*rlp)->name, name))
+            break;
+    if (*rlp)
+    {
+        char *new_val = gw_strdup (value);
+        if (!new_val)
+            return -1;
+        free ((*rlp)->value);
+        (*rlp)->value = new_val;
+    }
+    else
+    {
+        *rlp = add_name (resource, name, value);
+        if (!*rlp)
+            return -1;
+        (*rlp)->next = NULL;
+        if (symtab_override (id->symtab, *rlp) < 0)
+            return -1;
+    }
+    return 0;
+}
+
+/*
+   gw_res_commit: Write the resource file 'fname'. If resources
+     are modified/added then these will be written now.
+ */
+int gw_res_commit (GwRes id, const char *fname)
+{
+    struct res_file_info *ri;
+    struct res_line_info *rl;
+    FILE *out;
+    int i, pos;
+
+    assert (id);
+    assert (fname);
+
+    for (ri = id->files; ri; ri = ri->next)
+        if (!strcmp (ri->fname, fname))
+            break;
+    if (!ri)
+        return -1;
+    if (!(out = fopen (fname, "w")))
+        return -1;
+#if HAVE_FLOCK
+    flock (fileno (out), LOCK_EX);
+#else
+    lock_file (fileno (out), F_WRLCK);
+#endif
+    for (rl = ri->lines; rl; rl = rl->next)
+        switch (rl->kind)
+        {
+        case comment:
+            fputs (rl->value, out);
+        case blank:
+            fputc ('\n', out);
+            break;
+        case resource:
+            fprintf (out, "%s: ", rl->name);
+            pos = strlen(rl->name)+2;
+            i = 0;
+            while (1)
+            {
+                int left = 78-pos;
+                if (strlen(rl->value+i) <= left)
+                    break;
+                while (left > 0)
+                {
+                    if (rl->value[i+left] == ' ')
+                        break;
+                    
+                    --left;
+                }
+                if (left > 0)
+                {
+                    int j;
+                    for (j = 0; j<left; j++)
+                        fputc (rl->value[i+j], out);
+                    i += left+1;
+                }
+                else
+                    break;
+                fprintf (out, "\n ");
+                pos = 2;
+            }
+            fprintf (out, "%s\n", rl->value+i);
+            break;
+        default:
+            assert (0);
+        }
+    fflush (out);
+#if HAVE_FLOCK
+    flock (fileno (out), LOCK_UN);
+#else
+    lock_file (fileno (out), F_UNLCK);
+#endif
+    fclose (out);
+    return 0;
+}
+
+/*
+   gw_res_trav: Traverse resources associated with file 'fname'. For
+     each resource the handler 'tf' is invoked with name and value.
+ */
+int gw_res_trav (GwRes id, const char *fname, void (*tf)(const char *name,
+                                                         const char *value))
+{
+    assert (id);
+    assert (tf);
+
+    if (fname)
+    {
+        struct res_file_info *ri;
+        struct res_line_info *rl;
+
+        for (ri = id->files; ri; ri = ri->next)
+            if (!strcmp (ri->fname, fname))
+                break;
+        if (!ri)
+            return -1;
+        for (rl = ri->lines; rl; rl = rl->next)
+            if (rl->kind == resource)
+                (*tf) (rl->name, rl->value);
+    }
+    else
+    {
+        struct res_sym_entry *entry;
+        
+        for (entry = id->symtab->next; entry; entry=entry->next)
+            (*tf) (entry->info->name, entry->info->value);
+    }
+    return 0;
+}
+
+
+