#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <ctype.h>

static ssize_t convert (const char *in, size_t in_len, char **out, size_t *out_size);
static void convert_error (const char *in, size_t in_len, int bad_ofs, const char *fmt, ...);
static void bail (const char *fmt, ...);

int
main (int argc, char *argv[])
{
        FILE * fp;
        char * in = NULL;
        size_t in_size = 0;
        ssize_t in_len;
        char * out = NULL;
        size_t out_size = 0;
        ssize_t out_len;
        char * p;
        size_t w;
        int rc;

        //fp = fopen(name, "r");
        //if (fp == NULL)
        //        bail("open %s failed", name);
        fp = stdin;

        while ((in_len = getline(&in, &in_size, fp)) != -1) {

                out_len = convert (in, in_len, &out, &out_size);

                p = out;
                w = out_len;
                do {
                        rc = write (1, p, w);

                        if (rc<=0) bail("failed to write %d bytes", w);
                        if (rc>w) bail("write of %d returned %d", w, rc);

                        p+=rc;
                        w-=rc;
                } while (w);
        }

        if (in)
                free(in);

        return EXIT_SUCCESS;
}

static ssize_t
convert (const char *in, size_t in_len, char **out, size_t *out_size)
{
        int expected;
        const char *i, *ie = in + in_len;
        char *o, *oe;
        uint reg;

        expected = in_len/2;
        if (!*out || expected > *out_size) {
                free (*out);
                *out = malloc (expected);
                if (!*out)
                        bail ("unable to allocate %d bytes", expected);
                *out_size = expected;
        }
        o = *out;
        oe = *out + *out_size;

        for (i=in; i < ie; i++) {

                if (! isxdigit(*i)) {

                        if (isalnum(*i))
                                convert_error (in, in_len, i-in,
                                        "unexpected character");

                        continue;
                }

                if (ie-i < 2)
                        convert_error (in, in_len, i-in,
                                        "insufficient number of hex digits");

                reg = isdigit(*i)
                        ? ( *i - '0' )
                        : ( tolower(*i) - 'a' );
                i++;

                if (! isxdigit(*i))
                        convert_error (in, in_len, i-in,
                                        "hexdigit followed by a non hexdigit");

                reg <<=4;
                reg += isdigit(*i)
                        ? ( *i - '0' )
                        : ( tolower(*i) - 'a' );

                *(o++) = reg;
        }

        return o - *out;
}

static void
convert_error (const char *in, size_t in_len, int bad_ofs,
        const char *fmt, ...)
{
        va_list ap;

        fprintf (stderr, "parsing error @ %d ...\n", bad_ofs);
        fputs (in, stderr);
        fprintf (stderr, "%*s\n", bad_ofs+1, "^");

        va_start(ap, fmt);
        vfprintf (stderr, fmt, ap);
        va_end(ap);

        fprintf (stderr, "\n");

        exit (EXIT_FAILURE);
}

static void
bail (const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        vfprintf (stderr, fmt, ap);
        va_end(ap);

        fprintf (stderr, "\n");

        exit (EXIT_FAILURE);
}