Home
       refactor the code to avoid copying the whole ical file to memory - ics2txt - convert icalendar .ics file to plain text
  HTML git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ics2txt
   DIR Log
   DIR Files
   DIR Refs
   DIR Tags
   DIR README
       ---
   DIR commit 917f5b056d0b1241f0816bfd41276a36b5727fb1
   DIR parent b81a0df00fa8f12b03fa371762b6bd4ee3db8422
  HTML Author: Josuah Demangeon <me@josuah.net>
       Date:   Sat, 12 Jun 2021 22:50:29 +0200
       
       refactor the code to avoid copying the whole ical file to memory
       
       This is how Evil_Bob does it with xml.c, a sane and tiny XML parser.
       
       The document structure gets parsed, and a struct parser passed to the
       parser contains function pointers. As parsed objects get encountered,
       the matching function pointers gets called with the parsed text as
       parameter. Then the content can be processed as it is read, instead of
       parsed first and processed after.
       
       Diffstat:
         M Makefile                            |       4 ++--
         M ical.c                              |     351 ++++++++++---------------------
         M ical.h                              |      79 ++++++++-----------------------
         M ics2tree.c                          |      81 +++++++++++++------------------
         D log.c                               |      89 -------------------------------
         D log.h                               |      15 ---------------
         D map.c                               |     106 ------------------------------
         D map.h                               |      24 ------------------------
         M util.c                              |      88 +++++++++++++++++++++++++------
         M util.h                              |      21 +++++++++++++++------
       
       10 files changed, 253 insertions(+), 605 deletions(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       @@ -7,8 +7,8 @@ CFLAGS = $D $W -g
        PREFIX = /usr/local
        MANPREFIX = ${PREFIX}/man
        
       -SRC = ical.c map.c util.c log.c
       -HDR = ical.h map.h util.h log.h
       +SRC = ical.c util.c
       +HDR = ical.h util.h
        OBJ = ${SRC:.c=.o}
        BIN = ics2tree
        MAN1 = ics2txt.1
   DIR diff --git a/ical.c b/ical.c
       @@ -1,288 +1,157 @@
        #include "ical.h"
        
        #include <assert.h>
       +#include <ctype.h>
        #include <errno.h>
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
       -#include <strings.h> /* strcase* */
       +#include <strings.h>
        
        #include "util.h"
        
       -enum ical_err ical_errno;
       -
       -int
       -ical_getline(char **line, char **ln, size_t *sz, FILE *fp)
       +static int
       +ical_error(IcalParser *p, char const *msg)
        {
       -        int c;
       -        void *v;
       -
       -        if ((v = realloc(*line, 1)) == NULL)
       -                return -ICAL_ERR_SYSTEM;
       -        *line = v;
       -        (*line)[0] = '\0';
       -
       -        do { top:
       -                if (getline(ln, sz, fp) <= 0)
       -                        return ferror(fp) ? -ICAL_ERR_SYSTEM : 0;
       -                strchomp(*ln);
       -                if (**ln == '\0')
       -                        goto top;
       -                if (strappend(line, *ln) < 0)
       -                        return -ICAL_ERR_SYSTEM;
       -                if ((c = fgetc(fp)) == EOF)
       -                        return ferror(fp) ? -ICAL_ERR_SYSTEM : 1;
       -        } while (c == ' ');
       -
       -        ungetc(c, fp);
       -        assert(!ferror(fp));
       -        return 1;
       +        p->errmsg = msg;
       +        return -1;
        }
        
       -char *
       -ical_strerror(int i)
       -{
       -        enum ical_err err = (i > 0) ? i : -i;
       +#define CALL(p, fn, ...) ((p)->fn ? (p)->fn((p), __VA_ARGS__) : 0)
        
       -        switch (err) {
       -        case ICAL_ERR_OK:
       -                return "no error";
       -        case ICAL_ERR_SYSTEM:
       -                return "system error";
       -        case ICAL_ERR_END_MISMATCH:
       -                return "END: does not match its corresponding BEGIN:";
       -        case ICAL_ERR_MISSING_BEGIN:
       -                return "unexpected content line before any BEGIN:";
       -        case ICAL_ERR_MISSING_COLUMN:
       -                return "missing ':' character from line";
       -        case ICAL_ERR_MISSING_SEMICOLUMN:
       -                return "missing ';' character before ':'";
       -        case ICAL_ERR_MISSING_EQUAL:
       -                return "missing '=' character in parameter before ':'";
       -        case ICAL_ERR_MIN_NESTED:
       -                return "too many END: for the number of BEGIN:";
       -        case ICAL_ERR_MAX_NESTED:
       -                return "maximum nesting level reached";
       -        case ICAL_ERR_LENGTH:
       -                assert(!"used internally, should not happen");
       -        }
       -        assert(!"unknown error code");
       -        return "not a valid ical error code";
       -}
       -
       -struct ical_value *
       -ical_new_value(char const *line)
       +static int
       +ical_parse_value(IcalParser *p, char **sp, char *name)
        {
       -        struct ical_value *new;
       -        size_t len;
       +        int err;
       +        char *s, c, *val;
        
       -        len = strlen(line);
       -        if ((new = calloc(1, sizeof *new + len + 1)) == NULL)
       -                return NULL;
       -        memcpy(new->buf, line, len + 1);
       -        return new;
       -}
       +        s = *sp;
        
       -void
       -ical_free_value(struct ical_value *value)
       -{
       -        map_free(&value->param, NULL);
       -        free(value);
       -}
       -
       -int
       -ical_parse_value(struct ical_value *value)
       -{
       -        char *column, *equal, *param, *cp;
       -        int e = errno;
       -
       -        value->name = value->buf;
       -
       -        if ((column = strchr(value->buf, ':')) == NULL)
       -                return -ICAL_ERR_MISSING_COLUMN;
       -        *column = '\0';
       -        value->value = column + 1;
       -
       -        if ((cp = strchr(value->buf, ';')) != NULL)
       -                *cp++ = '\0';
       -        while ((param = strsep(&cp, ";")) != NULL) {
       -                if ((equal = strchr(param, '=')) == NULL)
       -                        return -ICAL_ERR_MISSING_EQUAL;
       -                *equal = '\0';
       -                if (map_set(&value->param, param, equal + 1) < 0)
       -                        return -ICAL_ERR_SYSTEM;
       +        if (*s == '"') {
       +                ++s;
       +                for (val = s; !iscntrl(*s) && !strchr(",;:\"", *s); s++);
       +                if (*s != '"')
       +                        return ical_error(p, "missing '\"'");
       +                *s++ = '\0';
       +        } else {
       +                for (val = s; !iscntrl(*s) && !strchr(",;:'\"", *s); s++);
                }
        
       -        assert(errno == e);
       -        return 0;
       -}
       -
       -struct ical_vnode *
       -ical_new_vnode(char const *name)
       -{
       -        struct ical_vnode *new;
       -        size_t sz;
       +        c = *s, *s = '\0';
       +        if ((err = CALL(p, fn_param_value, name, val)) != 0)
       +                return err;
       +        *s = c;
        
       -        if ((new = calloc(1, sizeof *new)) == NULL)
       -                return NULL;
       -        sz = sizeof new->name;
       -        if (strlcpy(new->name, name, sz) >= sz) {
       -                errno = EMSGSIZE;
       -                goto err;
       -        }
       -        return new;
       -err:
       -        ical_free_vnode(new);
       -        return NULL;
       +        *sp = s;
       +        return 0;
        }
        
       -static void
       -ical_free_value_void(void *v)
       +static int
       +ical_parse_param(IcalParser *p, char **sp)
        {
       -        ical_free_value(v);
       -}
       +        int err;
       +        char *s, *name;
        
       -static void
       -ical_free_vnode_void(void *v)
       -{
       -        ical_free_vnode(v);
       -}
       +        s = *sp;
        
       -void
       -ical_free_vnode(struct ical_vnode *node)
       -{
       -        if (node == NULL)
       -                return;
       -        map_free(&node->values, ical_free_value_void);
       -        map_free(&node->childs, ical_free_vnode_void);
       -        ical_free_vnode(node->next);
       -        free(node);
       -}
       +        do {
       +                for (name = s; isalnum(*s) || *s == '-'; s++);
       +                if (s == name || (*s != '='))
       +                        return ical_error(p, "invalid parameter name");
       +                *s++ = '\0';
       +                if ((err = CALL(p, fn_param_name, name)) != 0)
       +                        return err;
        
       -int
       -ical_push_nested(struct ical_vcalendar *vcal, struct ical_vnode *new)
       -{
       -        struct ical_vnode **node;
       +                do {
       +                        if ((err = ical_parse_value(p, &s, name)) != 0)
       +                                return err;
       +                } while (*s == ',' && s++);
       +        } while (*s == ';' && s++);
        
       -        node = vcal->nested;
       -        for (int i = 0; *node != NULL; node++, i++) {
       -                if (i >= ICAL_NESTED_MAX)
       -                        return -ICAL_ERR_MAX_NESTED;
       -        }
       -        node[0] = new;
       -        node[1] = NULL;
       +        *sp = s;
                return 0;
        }
        
       -struct ical_vnode *
       -ical_pop_nested(struct ical_vcalendar *vcal)
       +static int
       +ical_parse_contentline(IcalParser *p, char *line)
        {
       -        struct ical_vnode **node, **prev = vcal->nested, *old;
       +        int err;
       +        char *s, c, *name, *end;
        
       -        for (prev = node = vcal->nested; *node != NULL; node++) {
       -                vcal->current = *prev;
       -                prev = node;
       -                old = *node;
       -        }
       -        *prev = NULL;
       -        if (vcal->nested[0] == NULL)
       -                vcal->current = NULL;
       -        return old;
       -}
       +        s = line;
        
       -int
       -ical_begin_vnode(struct ical_vcalendar *vcal, char const *name)
       -{
       -        struct ical_vnode *new;
       -        int e;
       +        for (name = s; isalnum(*s) || *s == '-'; s++);
       +        if (s == name || (*s != ';' && *s != ':'))
       +                return ical_error(p, "invalid entry name");
       +        c = *s, *s = '\0';
       +        if (strcasecmp(name, "BEGIN") != 0 && strcasecmp(name, "END") != 0)
       +                if ((err = CALL(p, fn_entry_name, name)) != 0)
       +                        return err;
       +        *s = c;
       +        end = s;
        
       -        if ((new = ical_new_vnode(name)) == NULL)
       -                return -ICAL_ERR_SYSTEM;
       -        if ((e = ical_push_nested(vcal, new)) < 0)
       -                goto err;
       -        if (vcal->root == NULL) {
       -                vcal->root = new;
       -        } else {
       -                new->next = map_get(&vcal->current->childs, new->name);
       -                if (map_set(&vcal->current->childs, new->name, new) < 0) {
       -                        e = -ICAL_ERR_SYSTEM;
       -                        goto err;
       -                }
       +        while (*s == ';') {
       +                s++;
       +                if ((err = ical_parse_param(p, &s)) != 0)
       +                        return err;
                }
       -        vcal->current = new;
       -        return 0;
       -err:
       -        ical_free_vnode(new);
       -        return e;
       -}
        
       -int
       -ical_end_vnode(struct ical_vcalendar *vcal, char const *name)
       -{
       -        struct ical_vnode *old;
       -
       -        if ((old = ical_pop_nested(vcal)) == NULL)
       -                return -ICAL_ERR_MIN_NESTED;
       -        if (strcasecmp(name, old->name) != 0)
       -                return -ICAL_ERR_END_MISMATCH;
       -        return 0;
       -}
       -
       -int
       -ical_push_value(struct ical_vcalendar *vcal, struct ical_value *new)
       -{
       -        if (strcasecmp(new->name, "BEGIN") == 0) {
       -                int e = ical_begin_vnode(vcal, new->value);
       -                ical_free_value(new);
       -                return e;
       -        }
       -        if (strcasecmp(new->name, "END") == 0) {
       -                int e = ical_end_vnode(vcal, new->value);
       -                ical_free_value(new);
       -                return e;
       +        if (*s != ':')
       +                return ical_error(p, "expected ':' delimiter");
       +        s++;
       +
       +        *end = '\0';
       +        if (strcasecmp(name, "BEGIN") == 0) {
       +                if ((err = CALL(p, fn_block_begin, s)) != 0)
       +                        return err;
       +                p->level++;
       +        } else if (strcasecmp(name, "END") == 0) {
       +                if ((err = CALL(p, fn_block_end, s)) != 0)
       +                        return err;
       +                p->level--;
       +        } else {
       +                if ((err = CALL(p, fn_entry_value, name, s)) != 0)
       +                        return err;
                }
        
       -        if (vcal->current == NULL)
       -                return -ICAL_ERR_MISSING_BEGIN;
       -
       -        new->next = map_get(&vcal->current->values, new->name);
       -        if (map_set(&vcal->current->values, new->name, new) < 0)
       -                return -ICAL_ERR_SYSTEM;
       -
                return 0;
        }
        
        int
       -ical_read_vcalendar(struct ical_vcalendar *vcal, FILE *fp)
       +ical_parse(IcalParser *p, FILE *fp)
        {
       -        char *line = NULL, *ln = NULL;
       +        char *ln = NULL, *contentline = NULL;
                size_t sz = 0;
       -        ssize_t r;
       -        int e;
       -
       -        memset(vcal, 0, sizeof *vcal);
       -
       -        while ((r = ical_getline(&line, &ln, &sz, fp)) > 0) {
       -                struct ical_value *new;
       -
       -                if ((new = ical_new_value(line)) == NULL) {
       -                        e = -ICAL_ERR_SYSTEM;
       -                        goto err;
       -                }
       -                if ((e = ical_parse_value(new)) < 0)
       -                        goto err;
       -                if ((e = ical_push_value(vcal, new)) < 0)
       -                        goto err;
       +        int err, c;
       +
       +        while (!feof(fp)) {
       +                if ((contentline = realloc(contentline, 1)) == NULL)
       +                        return -1;
       +                *contentline = '\0';
       +
       +                do {
       +                        do {
       +                                p->line++;
       +                                if (getline(&ln, &sz, fp) <= 0)
       +                                        return -1;
       +                                strchomp(ln);
       +                        } while (*ln == '\0');
       +
       +                        if (strappend(&contentline, ln) < 0)
       +                                return -1;
       +                        if ((c = fgetc(fp)) == EOF) {
       +                                if (ferror(fp))
       +                                        return -1;
       +                                goto done;
       +                        }
       +                } while (c == ' ');
       +                ungetc(c, fp);
       +done:
       +                assert(!ferror(fp));
       +                if ((err = ical_parse_contentline(p, contentline)) != 0)
       +                        break;
                }
       -        e = (r == 0) ? 0 : -ICAL_ERR_SYSTEM;
       -err:
       -        free(line);
       +        free(contentline);
                free(ln);
       -        return e;
       -}
       -
       -void
       -ical_free_vcalendar(struct ical_vcalendar *vcal)
       -{
       -        ical_free_vnode(vcal->root);
       +        return err;
        }
   DIR diff --git a/ical.h b/ical.h
       @@ -4,65 +4,28 @@
        #include <stdio.h>
        #include <time.h>
        
       -#include "map.h"
       -
       -#define ICAL_NESTED_MAX 4
       -
       -enum ical_err {
       -        ICAL_ERR_OK,
       -        ICAL_ERR_SYSTEM,
       -        ICAL_ERR_END_MISMATCH,
       -        ICAL_ERR_MISSING_BEGIN,
       -        ICAL_ERR_MISSING_COLUMN,
       -        ICAL_ERR_MISSING_SEMICOLUMN,
       -        ICAL_ERR_MISSING_EQUAL,
       -        ICAL_ERR_MIN_NESTED,
       -        ICAL_ERR_MAX_NESTED,
       -
       -        ICAL_ERR_LENGTH,
       -};
       -
       -/* global propoerties for an iCalendar document as well as parsing state */
       -
       -struct ical_vcalendar {
       -        time_t tzid;
       -        struct ical_vnode *root;
       -        struct ical_vnode *nested[ICAL_NESTED_MAX + 1];
       -        struct ical_vnode *current;
       -};
       -
       -/* element part of an iCalendar document with eventual nested childs */
       -
       -struct ical_vnode {
       -        char name[32];
       -        struct map values; /*(struct ical_value *)*/
       -        struct map childs; /*(struct ical_vnode *)*/
       -        struct ical_vnode *next;
       -};
       -
       -/* one line whith the whole content unfolded */
       -
       -struct ical_value {
       -        char *name, *value;
       -        struct map param;
       -        struct ical_value *next;
       -        char buf[];
       +typedef struct IcalParser IcalParser;
       +struct IcalParser {
       +        /* function called on content */
       +        int (*fn_entry_name)(IcalParser *, char *);
       +        int (*fn_param_name)(IcalParser *, char *);
       +        int (*fn_param_value)(IcalParser *, char *, char *);
       +        int (*fn_entry_value)(IcalParser *, char *, char *);
       +        int (*fn_block_begin)(IcalParser *, char *);
       +        int (*fn_block_end)(IcalParser *, char *);
       +        /* if returning non-zero then halt the parser */
       +
       +        int         base64encoded;
       +        char const *errmsg;
       +        size_t         line;
       +
       +        /* stack of blocks names: "name1\0name2\0...nameN\0\0" */
       +        int         level;
       +        char         stack[1024];
        };
        
       -/** src/ical.c **/
       -int ical_getline(char **line, char **ln, size_t *sz, FILE *fp);
       -char * ical_strerror(int i);
       -struct ical_value * ical_new_value(char const *line);
       -void ical_free_value(struct ical_value *value);
       -int ical_parse_value(struct ical_value *value);
       -struct ical_vnode * ical_new_vnode(char const *name);
       -void ical_free_vnode(struct ical_vnode *node);
       -int ical_push_nested(struct ical_vcalendar *vcal, struct ical_vnode *new);
       -struct ical_vnode * ical_pop_nested(struct ical_vcalendar *vcal);
       -int ical_begin_vnode(struct ical_vcalendar *vcal, char const *name);
       -int ical_end_vnode(struct ical_vcalendar *vcal, char const *name);
       -int ical_push_value(struct ical_vcalendar *vcal, struct ical_value *new);
       -void ical_free_vcalendar(struct ical_vcalendar *vcal);
       -int ical_read_vcalendar(struct ical_vcalendar *vcal, FILE *fp);
       +int         ical_parse(IcalParser *, FILE *);
       +//TODO: char        *ical_get_time(char *);
       +//TODO: char        *ical_get_value(IcalCtx *, char *);
        
        #endif
   DIR diff --git a/ics2tree.c b/ics2tree.c
       @@ -3,76 +3,63 @@
        #include <string.h>
        
        #include "ical.h"
       -#include "log.h"
        #include "util.h"
        
       -void
       +static void
        print_ruler(int level)
        {
       -        for (int i = 0; i < level; i++)
       +        while (level-- > 0)
                        fprintf(stdout, ": ");
        }
        
       -void
       -print_ical_tree_param(struct map_entry *entry, int level)
       +static int
       +fn_entry_name(IcalParser *p, char *name)
        {
       -        if (entry == NULL)
       -                return;
       -        print_ruler(level);
       -        fprintf(stdout, "param %s=%s\n", entry->key, (char *)entry->value);
       +        print_ruler(p->level);
       +        printf("name %s\n", name);
       +        return 0;
        }
        
       -void
       -print_ical_tree_value(struct ical_value *value, int level)
       +static int
       +fn_block_begin(IcalParser *p, char *name)
        {
       -        if (value == NULL)
       -                return;
       -        print_ruler(level);
       -        fprintf(stdout, "value %s:%s\n", value->name, value->value);
       -        for (size_t i = 0; i < value->param.len; i++)
       -                print_ical_tree_param(value->param.entry + i, level + 1);
       -        print_ical_tree_value(value->next, level);
       +        print_ruler(p->level);
       +        printf("begin %s\n", name);
       +        return 0;
        }
        
       -void
       -print_ical_tree_vnode(struct ical_vnode *node, int level)
       +static int
       +fn_param_value(IcalParser *p, char *name, char *value)
        {
       -        if (node == NULL)
       -                return;
       -        print_ruler(level);
       -        fprintf(stdout, "node %s\n", node->name);
       -        for (size_t i = 0; i < node->values.len; i++)
       -                print_ical_tree_value(node->values.entry[i].value, level + 1);
       -        for (size_t i = 0; i < node->childs.len; i++)
       -                print_ical_tree_vnode(node->childs.entry[i].value, level + 1);
       -        print_ical_tree_vnode(node->next, level);
       +        print_ruler(p->level + 1);
       +        printf("param %s=%s\n", name, value);
       +        return 0;
        }
        
       -int
       -print_ical_tree(FILE *fp)
       +static int
       +fn_entry_value(IcalParser *p, char *name, char *value)
        {
       -        struct ical_vcalendar vcal;
       -        int e;
       +        (void)name;
        
       -        if ((e = ical_read_vcalendar(&vcal, fp)) < 0)
       -                die("reading ical file: %s", ical_strerror(e));
       -
       -        print_ical_tree_vnode(vcal.root, 0);
       -        fprintf(stdout, "end\n");
       -        fflush(stdout);
       -
       -        ical_free_vcalendar(&vcal);
       +        print_ruler(p->level + 1);
       +        printf("value %s\n", value);
                return 0;
        }
        
        int
        main(int argc, char **argv)
        {
       -        log_arg0 = *argv++;
       +        IcalParser p = {0};
       +        arg0 = *argv++;
       +
       +        p.fn_entry_name = fn_entry_name;
       +        p.fn_block_begin = fn_block_begin;
       +        p.fn_param_value = fn_param_value;
       +        p.fn_entry_value = fn_entry_value;
        
                if (*argv == NULL) {
       -                if (print_ical_tree(stdin) < 0)
       -                        die("converting stdin");
       +                if (ical_parse(&p, stdin) < 0)
       +                        err("parsing stdin:%d %s", p.line, p.errmsg);
                }
        
                for (; *argv != NULL; argv++, argc--) {
       @@ -80,9 +67,9 @@ main(int argc, char **argv)
        
                        debug("converting \"%s\"", *argv);
                        if ((fp = fopen(*argv, "r")) == NULL)
       -                        die("opening %s", *argv);
       -                if (print_ical_tree(fp) < 0)
       -                        die("converting %s", *argv);
       +                        err("opening %s", *argv);
       +                if (ical_parse(&p, fp) < 0)
       +                        err("parsing %s:%d: %s", *argv, p.line, p.errmsg);
                        fclose(fp);
                }
                return 0;
   DIR diff --git a/log.c b/log.c
       @@ -1,89 +0,0 @@
       -#include "log.h"
       -
       -#include <assert.h>
       -#include <string.h>
       -
       -/*
       - * log.c - log to standard error according to the log level
       - *
       - * Instead of logging to syslog, delegate logging to a separate
       - * tool, such as FreeBSD's daemon(8), POSIX's logger(1).
       - */
       -
       -#include <errno.h>
       -#include <stdio.h>
       -#include <stdlib.h>
       -
       -#define LOG_DEFAULT 3 /* info */
       -
       -int log_level = -1;
       -char *log_arg0 = NULL;
       -
       -void
       -vlogf(int level, char const *flag, char const *fmt, va_list va)
       -{
       -        char *env;
       -        int e = errno;
       -
       -        if (log_level < 0) {
       -                env = getenv("LOG");
       -                log_level = (env == NULL ? 0 : atoi(env));
       -                log_level = (log_level > 0 ? log_level : LOG_DEFAULT);
       -        }
       -
       -        if (log_level < level)
       -                return;
       -
       -        if (log_arg0 != NULL)
       -                fprintf(stderr, "%s: ", log_arg0);
       -
       -        fprintf(stderr, "%s: ", flag);
       -        vfprintf(stderr, fmt, va);
       -
       -        if (e != 0)
       -                fprintf(stderr, ": %s", strerror(e));
       -
       -        fprintf(stderr, "\n");
       -        fflush(stderr);
       -}
       -
       -void
       -die(char const *fmt, ...)
       -{
       -        va_list va;
       -
       -        va_start(va, fmt);
       -        vlogf(1, "error", fmt, va);
       -        va_end(va);
       -        exit(1);
       -}
       -
       -void
       -warn(char const *fmt, ...)
       -{
       -        va_list va;
       -
       -        va_start(va, fmt);
       -        vlogf(2, "warn", fmt, va);
       -        va_end(va);
       -}
       -
       -void
       -info(char const *fmt, ...)
       -{
       -        va_list va;
       -
       -        va_start(va, fmt);
       -        vlogf(3, "info", fmt, va);
       -        va_end(va);
       -}
       -
       -void
       -debug(char const *fmt, ...)
       -{
       -        va_list va;
       -
       -        va_start(va, fmt);
       -        vlogf(4, "debug", fmt, va);
       -        va_end(va);
       -}
   DIR diff --git a/log.h b/log.h
       @@ -1,15 +0,0 @@
       -#ifndef LOG_H
       -#define LOG_H
       -
       -#include <stdarg.h>
       -
       -/** src/log.c **/
       -extern int log_level;
       -extern char *log_arg0;
       -void vlogf(int level, char const *flag, char const *fmt, va_list va);
       -void die(char const *fmt, ...);
       -void warn(char const *fmt, ...);
       -void info(char const *fmt, ...);
       -void debug(char const *fmt, ...);
       -
       -#endif
   DIR diff --git a/map.c b/map.c
       @@ -1,106 +0,0 @@
       -#include "map.h"
       -
       -#include <stdlib.h>
       -#include <string.h>
       -
       -#include "util.h"
       -
       -static int
       -map_cmp(void const *v1, void const *v2)
       -{
       -        struct map_entry const *e1 = v1, *e2 = v2;
       -
       -        return strcmp(e1->key, e2->key);
       -}
       -
       -void *
       -map_get(struct map *map, char *key)
       -{
       -        struct map_entry *entry, k = { .key = key };
       -        size_t sz;
       -
       -        sz = sizeof(*map->entry);
       -        if ((entry = bsearch(&k, map->entry, map->len, sz, map_cmp)) == NULL)
       -                return NULL;
       -        return entry->value;
       -}
       -
       -int
       -map_set(struct map *map, char *key, void *value)
       -{
       -        struct map_entry *insert, *e;
       -        size_t i, sz;
       -        void *v;
       -
       -        for (i = 0; i < map->len; i++) {
       -                int cmp = strcmp(key, map->entry[i].key);
       -
       -                if (cmp == 0) {
       -                        map->entry[i].value = value;
       -                        return 0;
       -                }
       -                if (cmp < 0)
       -                        break;
       -        }
       -
       -        sz = sizeof(*map->entry);
       -        if ((v = reallocarray(map->entry, map->len + 1, sz)) == NULL)
       -                return -1;
       -        map->entry = v;
       -        map->len++;
       -
       -        insert = map->entry + i;
       -        for (e = map->entry + map->len - 2; e >= insert; e--)
       -                e[1] = e[0];
       -
       -        insert->key = key;
       -        insert->value = value;
       -
       -        return 0;
       -}
       -
       -int
       -map_del(struct map *map, char *key)
       -{
       -        size_t i;
       -
       -        for (i = 0; i < map->len; i++) {
       -                int cmp = strcmp(key, map->entry[i].key);
       -
       -                if (cmp == 0)
       -                        break;
       -                if (cmp < 0)
       -                        return -1;
       -        }
       -        if (i == map->len)
       -                return -1;
       -
       -        map->len--;
       -        for (; i < map->len; i++)
       -                map->entry[i] = map->entry[i + 1];
       -        return 0;
       -}
       -
       -void
       -map_init(struct map *map)
       -{
       -        memset(map, 0, sizeof(*map));
       -}
       -
       -void
       -map_free_keys(struct map *map)
       -{
       -        for (size_t i = 0; i < map->len; i++)
       -                free(map->entry[i].key);
       -}
       -
       -void
       -map_free(struct map *map, void (*fn)(void *))
       -{
       -        if (fn != NULL) {
       -                for (size_t i = 0; i < map->len; i++)
       -                        fn(map->entry[i].value);
       -        }
       -        free(map->entry);
       -        map->len = 0;
       -}
   DIR diff --git a/map.h b/map.h
       @@ -1,24 +0,0 @@
       -#ifndef MAP_H
       -#define MAP_H
       -
       -#include <stddef.h>
       -
       -struct map_entry {
       -        char *key;
       -        void *value;
       -};
       -
       -struct map {
       -        struct map_entry *entry;
       -        size_t len;
       -};
       -
       -/** src/map.c **/
       -void * map_get(struct map *map, char *key);
       -int map_set(struct map *map, char *key, void *value);
       -int map_del(struct map *map, char *key);
       -void map_init(struct map *map);
       -void map_free_keys(struct map *map);
       -void map_free(struct map *map, void (*fn)(void *));
       -
       -#endif
   DIR diff --git a/util.c b/util.c
       @@ -4,6 +4,59 @@
        #include <stdint.h>
        #include <stdlib.h>
        #include <string.h>
       +#include <stdio.h>
       +
       +char *arg0;
       +
       +/* logging */
       +
       +static void
       +_log(char const *tag, char const *fmt, va_list va)
       +{
       +        if (arg0 != NULL)
       +                fprintf(stderr, "%s: ", arg0);
       +        fprintf(stderr, "%s: ", tag);
       +        vfprintf(stderr, fmt, va);
       +        if (errno != 0)
       +                fprintf(stderr, ": %s", strerror(errno));
       +        fprintf(stderr, "\n");
       +        fflush(stderr);
       +}
       +
       +void
       +err(char const *fmt, ...)
       +{
       +        va_list va;
       +
       +        va_start(va, fmt);
       +        _log("error", fmt, va);
       +        exit(1);
       +}
       +
       +void
       +warn(char const *fmt, ...)
       +{
       +        va_list va;
       +
       +        va_start(va, fmt);
       +        _log("warning", fmt, va);
       +}
       +
       +void
       +debug(char const *fmt, ...)
       +{
       +        static int verbose = -1;
       +        va_list va;
       +
       +        if (verbose < 0)
       +                verbose = (getenv("DEBUG") == NULL);
       +        if (!verbose)
       +                return;
       +        va_start(va, fmt);
       +        _log("debug", fmt, va);
       +}
       +
       +/* strings */
        
        size_t
        strlcpy(char *buf, char const *str, size_t sz)
       @@ -18,21 +71,20 @@ strlcpy(char *buf, char const *str, size_t sz)
        }
        
        char *
       -strsep(char **str_p, char const *sep)
       +strsep(char **sp, char const *sep)
        {
                char *s, *prev;
        
       -        if (*str_p == NULL)
       +        if (*sp == NULL)
                        return NULL;
       -
       -        for (s = prev = *str_p; strchr(sep, *s) == NULL; s++)
       +        prev = *sp;
       +        for (s = *sp; strchr(sep, *s) == NULL; s++)
                        continue;
       -
                if (*s == '\0') {
       -                *str_p = NULL;
       +                *sp = NULL;
                } else {
       +                *sp = s + 1;
                        *s = '\0';
       -                *str_p = s + 1;
                }
                return prev;
        }
       @@ -44,28 +96,30 @@ strchomp(char *line)
        
                len = strlen(line);
                if (len > 0 && line[len - 1] == '\n')
       -                line[len-- - 1] = '\0';
       +                line[--len] = '\0';
                if (len > 0 && line[len - 1] == '\r')
       -                line[len-- - 1] = '\0';
       +                line[--len] = '\0';
        }
        
        int
       -strappend(char **base_p, char const *s)
       +strappend(char **dstp, char const *src)
        {
       -        size_t base_len, s_len;
       -        void *v;
       +        size_t dstlen, srclen;
       +        void *mem;
        
       -        base_len = (*base_p == NULL) ? (0) : (strlen(*base_p));
       -        s_len = strlen(s);
       +        dstlen = (*dstp == NULL) ? 0 : strlen(*dstp);
       +        srclen = strlen(src);
        
       -        if ((v = realloc(*base_p, base_len + s_len + 1)) == NULL)
       +        if ((mem = realloc(*dstp, dstlen + srclen + 1)) == NULL)
                        return -1;
       +        *dstp = mem;
        
       -        *base_p = v;
       -        memcpy(*base_p + base_len, s, s_len + 1);
       +        memcpy(*dstp + dstlen, src, srclen + 1);
                return 0;
        }
        
       +/* memory */
       +
        void *
        reallocarray(void *buf, size_t len, size_t sz)
        {
   DIR diff --git a/util.h b/util.h
       @@ -2,12 +2,21 @@
        #define UTIL_H
        
        #include <stddef.h>
       +#include <stdarg.h>
        
       -/** src/util.c **/
       -size_t strlcpy(char *buf, char const *str, size_t sz);
       -char * strsep(char **str_p, char const *sep);
       -void strchomp(char *line);
       -int strappend(char **base_p, char const *s);
       -void * reallocarray(void *buf, size_t len, size_t sz);
       +/* logging */
       +extern char *arg0;
       +void         err(char const *fmt, ...);
       +void         warn(char const *fmt, ...);
       +void         debug(char const *fmt, ...);
       +
       +/* strings */
       +size_t         strlcpy(char *buf, char const *str, size_t sz);
       +char        *strsep(char **str_p, char const *sep);
       +void         strchomp(char *line);
       +int         strappend(char **base_p, char const *s);
       +
       +/* memory */
       +void        *reallocarray(void *buf, size_t len, size_t sz);
        
        #endif