/*
 *      dnsutl - utilities to make DNS easier to configure
 *      Copyright (C) 1991-1993, 1995, 1999, 2000, 2006, 2007 Peter Miller
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 3 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program. If not, see
 *      <http://www.gnu.org/licenses/>.
 */

#include <ac/ctype.h>
#include <ac/errno.h>
#include <ac/stdarg.h>
#include <ac/stddef.h>
#include <ac/stdio.h>
#include <ac/stdlib.h>
#include <ac/string.h>

#include <error.h>
#include <arglex.h>


static char     *huge_buffer;


static void
make_huge_buffer(void)
{
    static int      entry_count;

    if (huge_buffer)
        return;
    entry_count++;
    if (entry_count != 1)
    {
        static char     small_buffer[100];

        /* just enuf for "out of memory" message */
        huge_buffer = small_buffer;
    }
    else
    {
        huge_buffer = malloc(100000L);
        if (!huge_buffer)
            fatal("can't alloc buffer for error messages");
    }
    entry_count--;
}


/*
 * NAME
 *      wrap - wrap s string over lines
 *
 * SYNOPSIS
 *      void wrap(char *);
 *
 * DESCRIPTION
 *      The wrap function is used to print error messages onto stderr
 *      wrapping ling lines.
 *
 * CAVEATS
 *      Line length is assumed to be 80 characters.
 */

#define PAGE_WIDTH 79

static void
wrap(const char *s)
{
    static char     escapes[] = "\rr\nn\ff\bb\tt";
    char            tmp[PAGE_WIDTH + 2];
    int             first_line;
    char            *tp;
    int             bomb_later;

    /*
     * Flush stdout so that errors are in sync with the output.
     * If you get an error doing this, whinge about it _after_ reporting
     * the originating error.  Also, clear the error on stdout to
     * avoid getting caught in an infinite loop.
     */
    if (fflush(stdout) || ferror(stdout))
    {
        bomb_later = errno;
        clearerr(stdout);
    }
    else
        bomb_later = 0;

    first_line = 1;
    while (*s)
    {
        const char      *ep;
        int             ocol;

        /*
         * Work out how many characters fit on the line.
         */
        if (first_line)
            ocol = strlen(progname) + 2;
        else
            ocol = 8;
        for (ep = s; *ep; ++ep)
        {
            int             cw;
            int             c;

            c = (unsigned char)*ep;
            if (isprint(c))
                cw = 1 + (c == '\\');
            else
                cw = (strchr(escapes, c) ? 2 : 4);
            if (ocol + cw > PAGE_WIDTH)
                break;
            ocol += cw;
        }

        /*
         * see if there is a better place to break the line
         */
        if (*ep && *ep != ' ')
        {
            const char      *mp;

            for (mp = ep; mp > s; --mp)
            {
                if (strchr(" /", mp[-1]))
                {
                    ep = mp;
                    break;
                }
            }
        }

        /*
         * ignore trailing blanks
         */
        while (ep > s && ep[-1] == ' ')
            ep--;

        /*
         * print the line
         */
        if (first_line)
            sprintf(tmp, "%s: ", progname);
        else
            strcpy(tmp, "        ");
        tp = tmp + strlen(tmp);
        while (s < ep)
        {
            int             c;

            c = (unsigned char)*s++;
            if (isprint(c))
            {
                if (c == '\\')
                    *tp++ = '\\';
                *tp++ = c;
            }
            else
            {
                char            *esc;

                esc = strchr(escapes, c);
                if (esc)
                {
                    *tp++ = '\\';
                    *tp++ = esc[1];
                }
                else
                {
                    sprintf(tp, "\\%3.3o", c);
                    tp += strlen(tp);
                }
            }
        }
        *tp++ = '\n';
        *tp = 0;
        fputs(tmp, stderr);
        if (ferror(stderr))
            break;

        /*
         * skip leading spaces for subsequent lines
         */
        while (*s == ' ')
            s++;
        first_line = 0;
    }
    if (fflush(stderr) || ferror(stderr))
    {
        /* don't print why, there is no point! */
        exit(1);
    }
    if (bomb_later)
    {
        errno = bomb_later;
        nfatal("(stdout)");
    }
}


/*
 *  NAME
 *      error - place a message on the error stream
 *
 *  SYNOPSIS
 *      void error(char *s, ...);
 *
 *  DESCRIPTION
 *      Error places a message on the error output stream.
 *      The first argument is a printf-like format string,
 *      optionally followed by other arguments.
 *      The message will be prefixed by the program name and a colon,
 *      and will be terminated with a newline, automatically.
 *
 *  CAVEAT
 *      Things like "error(filename)" blow up if the filename
 *      contains a '%' character.
 */

void
error(const char *s, ...)
{
    va_list         ap;

    va_start(ap, s);
    make_huge_buffer();
    vsprintf(huge_buffer, s, ap);
    va_end(ap);
    wrap(huge_buffer);
}


/*
 *  NAME
 *      nerror - place a system fault message on the error stream
 *
 *  SYNOPSIS
 *      void nerror(char *s, ...);
 *
 *  DESCRIPTION
 *      Nerror places a message on the error output stream.
 *      The first argument is a printf-like format string,
 *      optionally followed by other arguments.
 *      The message will be prefixed by the program name and a colon,
 *      and will be terminated with a text description of the error
 *      indicated by the 'errno' global variable, automatically.
 *
 *  CAVEAT
 *      Things like "nerror(filename)" blow up if the filename
 *      contains a '%' character.
 */

void
nerror(const char *s, ...)
{
    va_list         ap;
    int             n;

    va_start(ap, s);
    n = errno;
    make_huge_buffer();
    vsprintf(huge_buffer, s, ap);
    va_end(ap);
    strcat(huge_buffer, ": ");
    strcat(huge_buffer, strerror(n));
    wrap(huge_buffer);
}


/*
 *  NAME
 *      nfatal - place a system fault message on the error stream and exit
 *
 *  SYNOPSIS
 *      void nfatal(char *s, ...);
 *
 *  DESCRIPTION
 *      Nfatal places a message on the error output stream and exits.
 *      The first argument is a printf-like format string,
 *      optionally followed by other arguments.
 *      The message will be prefixed by the program name and a colon,
 *      and will be terminated with a text description of the error
 *      indicated by the 'errno' global variable, automatically.
 *
 *  CAVEAT
 *      Things like "nfatal(filename)" blow up if the filename
 *      contains a '%' character.
 *
 *      This function does NOT return.
 */

void
nfatal(const char *s, ...)
{
    va_list         ap;
    int             n;

    va_start(ap, s);
    n = errno;
    make_huge_buffer();
    vsprintf(huge_buffer, s, ap);
    va_end(ap);
    strcat(huge_buffer, ": ");
    strcat(huge_buffer, strerror(n));
    wrap(huge_buffer);
    exit(1);
}


/*
 *  NAME
 *      fatal - place a message on the error stream and exit
 *
 *  SYNOPSIS
 *      void fatal(char *s, ...);
 *
 *  DESCRIPTION
 *      Fatal places a message on the error output stream and exits.
 *      The first argument is a printf-like format string,
 *      optionally followed by other arguments.
 *      The message will be prefixed by the program name and a colon,
 *      and will be terminated with a newline, automatically.
 *
 *  CAVEAT
 *      Things like "error(filename)" blow up if the filename
 *      contains a '%' character.
 *
 *      This function does NOT return.
 */

void
fatal(const char *s, ...)
{
    va_list         ap;

    va_start(ap, s);
    make_huge_buffer();
    vsprintf(huge_buffer, s, ap);
    va_end(ap);
    wrap(huge_buffer);
    exit(1);
}


/*
 *  NAME
 *      assert - make an assertion
 *
 *  SYNOPSIS
 *      void assert(int condition);
 *
 *  DESCRIPTION
 *      Assert is a handy tool for a programmer to guarantee the internal
 *      consistency of their program. If "-DDEBUG" is specified on
 *      the compiler's command line, then assert will generate code to verify
 *      the assertios made. If no DEBUG is defined, assertions will generate
 *      no code.
 *
 *  CAVEAT
 *      If the assertion fails, a fatal diagnostic is issued.
 *
 *      The #define's which control the compilation may be found in "error.h".
 *
 */

void
cook_assert(int c, const char *s, const char *file, int line)
{
    if (c)
        return;
    error("%s: %d: assertion \"%s\" failed (bug)", file, line, s);
    abort();
}


syntax highlighted by Code2HTML, v. 0.9.1