/*
 * internal.c: internal data structures and helpers
 *
 * Copyright (C) 2007-2016 David Lutterkort
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * Author: David Lutterkort <dlutter@redhat.com>
 */

#include <config.h>

#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include <locale.h>

#include "internal.h"
#include "memory.h"
#include "fa.h"

#ifndef MIN
# define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

/* Cap file reads somwhat arbitrarily at 32 MB */
#define MAX_READ_LEN (32*1024*1024)

int pathjoin(char **path, int nseg, ...) {
    va_list ap;

    va_start(ap, nseg);
    for (int i=0; i < nseg; i++) {
        const char *seg = va_arg(ap, const char *);
        if (seg == NULL)
            seg = "()";
        int len = strlen(seg) + 1;

        if (*path != NULL) {
            len += strlen(*path) + 1;
            if (REALLOC_N(*path, len) == -1) {
                FREE(*path);
                va_end(ap);
                return -1;
            }
            if (strlen(*path) == 0 || (*path)[strlen(*path)-1] != SEP)
                strcat(*path, "/");
            if (seg[0] == SEP)
                seg += 1;
            strcat(*path, seg);
        } else {
            if ((*path = malloc(len)) == NULL) {
                va_end(ap);
                return -1;
            }
            strcpy(*path, seg);
        }
    }
    va_end(ap);
    return 0;
}

/* Like gnulib's fread_file, but read no more than the specified maximum
   number of bytes.  If the length of the input is <= max_len, and
   upon error while reading that data, it works just like fread_file.

   Taken verbatim from libvirt's util.c
*/

static char *
fread_file_lim (FILE *stream, size_t max_len, size_t *length)
{
    char *buf = NULL;
    size_t alloc = 0;
    size_t size = 0;
    int save_errno;

    for (;;) {
        size_t count;
        size_t requested;

        if (size + BUFSIZ + 1 > alloc) {
            char *new_buf;

            alloc += alloc / 2;
            if (alloc < size + BUFSIZ + 1)
                alloc = size + BUFSIZ + 1;

            new_buf = realloc (buf, alloc);
            if (!new_buf) {
                save_errno = errno;
                break;
            }

            buf = new_buf;
        }

        /* Ensure that (size + requested <= max_len); */
        requested = MIN (size < max_len ? max_len - size : 0,
                         alloc - size - 1);
        count = fread (buf + size, 1, requested, stream);
        size += count;

        if (count != requested || requested == 0) {
            save_errno = errno;
            if (ferror (stream))
                break;
            buf[size] = '\0';
            *length = size;
            return buf;
        }
    }

    free (buf);
    errno = save_errno;
    return NULL;
}

char* xfread_file(FILE *fp) {
    char *result;
    size_t len;

    if (!fp)
        return NULL;

    result = fread_file_lim(fp, MAX_READ_LEN, &len);

    if (result != NULL
        && len <= MAX_READ_LEN
        && (int) len == len)
        return result;

    free(result);
    return NULL;
}

char* xread_file(const char *path) {
    FILE *fp;
    char *result;

    fp = fopen(path, "r");
    if (!fp)
        return NULL;

    result = xfread_file(fp);
    fclose(fp);

    return result;
}

/*
 * Escape/unescape of string literals
 */
static const char *const escape_chars = "\a\b\t\n\v\f\r";
static const char *const escape_names = "abtnvfr";

char *unescape(const char *s, int len, const char *extra) {
    size_t size;
    const char *n;
    char *result, *t;
    int i;

    if (len < 0 || len > strlen(s))
        len = strlen(s);

    size = 0;
    for (i=0; i < len; i++, size++) {
        if (s[i] == '\\' && strchr(escape_names, s[i+1])) {
            i += 1;
        } else if (s[i] == '\\' && extra && strchr(extra, s[i+1])) {
            i += 1;
        }
    }

    if (ALLOC_N(result, size + 1) < 0)
        return NULL;

    for (i = 0, t = result; i < len; i++, size++) {
        if (s[i] == '\\' && (n = strchr(escape_names, s[i+1])) != NULL) {
            *t++ = escape_chars[n - escape_names];
            i += 1;
        } else if (s[i] == '\\' && extra && strchr(extra, s[i+1]) != NULL) {
            *t++ = s[i+1];
            i += 1;
        } else {
            *t++ = s[i];
        }
    }
    return result;
}

char *escape(const char *text, int cnt, const char *extra) {

    int len = 0;
    char *esc = NULL, *e;

    if (cnt < 0 || cnt > strlen(text))
        cnt = strlen(text);

    for (int i=0; i < cnt; i++) {
        if (text[i] && (strchr(escape_chars, text[i]) != NULL))
            len += 2;  /* Escaped as '\x' */
        else if (text[i] && extra && (strchr(extra, text[i]) != NULL))
            len += 2;  /* Escaped as '\x' */
        else if (! isprint(text[i]))
            len += 4;  /* Escaped as '\ooo' */
        else
            len += 1;
    }
    if (ALLOC_N(esc, len+1) < 0)
        return NULL;
    e = esc;
    for (int i=0; i < cnt; i++) {
        char *p;
        if (text[i] && ((p = strchr(escape_chars, text[i])) != NULL)) {
            *e++ = '\\';
            *e++ = escape_names[p - escape_chars];
        } else if (text[i] && extra && (strchr(extra, text[i]) != NULL)) {
            *e++ = '\\';
            *e++ = text[i];
        } else if (! isprint(text[i])) {
            sprintf(e, "\\%03o", (unsigned char) text[i]);
            e += 4;
        } else {
            *e++ = text[i];
        }
    }
    return esc;
}

int print_chars(FILE *out, const char *text, int cnt) {
    int total = 0;
    char *esc;

    if (text == NULL) {
        fprintf(out, "nil");
        return 3;
    }
    if (cnt < 0)
        cnt = strlen(text);

    esc = escape(text, cnt, "\"");
    total = strlen(esc);
    if (out != NULL)
        fprintf(out, "%s", esc);
    free(esc);

    return total;
}

char *format_pos(const char *text, int pos) {
    static const int window = 28;
    char *buf = NULL, *left = NULL, *right = NULL;
    int before = pos;
    int llen, rlen;
    int r;

    if (before > window)
        before = window;
    left = escape(text + pos - before, before, NULL);
    if (left == NULL)
        goto done;
    right = escape(text + pos, window, NULL);
    if (right == NULL)
        goto done;

    llen = strlen(left);
    rlen = strlen(right);
    if (llen < window && rlen < window) {
        r = asprintf(&buf, "%*s%s|=|%s%-*s\n", window - llen, "<", left,
                     right, window - rlen, ">");
    } else if (strlen(left) < window) {
        r = asprintf(&buf, "%*s%s|=|%s>\n", window - llen, "<", left, right);
    } else if (strlen(right) < window) {
        r = asprintf(&buf, "<%s|=|%s%-*s\n", left, right, window - rlen, ">");
    } else {
        r = asprintf(&buf, "<%s|=|%s>\n", left, right);
    }
    if (r < 0) {
        buf = NULL;
    }

 done:
    free(left);
    free(right);
    return buf;
}

void print_pos(FILE *out, const char *text, int pos) {
    char *format = format_pos(text, pos);

    if (format != NULL) {
        fputs(format, out);
        FREE(format);
    }
}

int __aug_init_memstream(struct memstream *ms) {
    MEMZERO(ms, 1);
#if HAVE_OPEN_MEMSTREAM
    ms->stream = open_memstream(&(ms->buf), &(ms->size));
    return ms->stream == NULL ? -1 : 0;
#else
    ms->stream = tmpfile();
    if (ms->stream == NULL) {
        return -1;
    }
    return 0;
#endif
}

int __aug_close_memstream(struct memstream *ms) {
#if !HAVE_OPEN_MEMSTREAM
    rewind(ms->stream);
    ms->buf = fread_file_lim(ms->stream, MAX_READ_LEN, &(ms->size));
#endif
    if (fclose(ms->stream) == EOF) {
        FREE(ms->buf);
        ms->size = 0;
        return -1;
    }
    return 0;
}

int tree_sibling_index(struct tree *tree) {
    struct tree *siblings = tree->parent->children;

    int cnt = 0, ind = 0;

    list_for_each(t, siblings) {
        if (streqv(t->label, tree->label)) {
            cnt += 1;
            if (t == tree)
                ind = cnt;
        }
    }

    if (cnt > 1) {
        return ind;
    } else {
        return 0;
    }
}

char *path_expand(struct tree *tree, const char *ppath) {
    char *path;
    const char *label;
    char *escaped = NULL;
    int r;

    int ind = tree_sibling_index(tree);

    if (ppath == NULL)
        ppath = "";

    if (tree->label == NULL)
        label = "(none)";
    else
        label = tree->label;

    r = pathx_escape_name(label, &escaped);
    if (r < 0)
        return NULL;

    if (escaped != NULL)
        label = escaped;

    if (ind > 0) {
        r = asprintf(&path, "%s/%s[%d]", ppath, label, ind);
    } else {
        r = asprintf(&path, "%s/%s", ppath, label);
    }

    free(escaped);

    if (r == -1)
        return NULL;
    return path;
}

char *path_of_tree(struct tree *tree) {
    int depth, i;
    struct tree *t, **anc;
    char *path = NULL;

    for (t = tree, depth = 1; ! ROOT_P(t); depth++, t = t->parent);
    if (ALLOC_N(anc, depth) < 0)
        return NULL;

    for (t = tree, i = depth - 1; i >= 0; i--, t = t->parent)
        anc[i] = t;

    for (i = 0; i < depth; i++) {
        char *p = path_expand(anc[i], path);
        free(path);
        path = p;
    }
    FREE(anc);
    return path;
}

/* User-facing path cleaning */
static char *cleanstr(char *path, const char sep) {
    if (path == NULL || strlen(path) == 0)
        return path;
    char *e = path + strlen(path) - 1;
    while (e >= path && (*e == sep || isspace(*e)))
        *e-- = '\0';
    return path;
}

char *cleanpath(char *path) {
    if (path == NULL || strlen(path) == 0)
        return path;
    if (STREQ(path, "/"))
        return path;
    return cleanstr(path, SEP);
}

const char *xstrerror(int errnum, char *buf, size_t len) {
#ifdef HAVE_STRERROR_R
# if defined(__USE_GNU) && defined(__GLIBC__)
    /* Annoying GNU specific API contract */
    return strerror_r(errnum, buf, len);
# else
    strerror_r(errnum, buf, len);
    return buf;
# endif
#else
    int n = snprintf(buf, len, "errno=%d", errnum);
    return (0 < n && n < len
            ? buf : "internal error: buffer too small in xstrerror");
#endif
}

int xasprintf(char **strp, const char *format, ...) {
  va_list args;
  int result;

  va_start (args, format);
  result = vasprintf (strp, format, args);
  va_end (args);
  if (result < 0)
      *strp = NULL;
  return result;
}

/* From libvirt's src/xen/block_stats.c */
int xstrtoint64(char const *s, int base, int64_t *result) {
    long long int lli;
    char *p;

    errno = 0;
    lli = strtoll(s, &p, base);
    if (errno || !(*p == 0 || *p == '\n') || p == s || (int64_t) lli != lli)
        return -1;
    *result = lli;
    return 0;
}

void calc_line_ofs(const char *text, size_t pos, size_t *line, size_t *ofs) {
    *line = 1;
    *ofs = 0;
    for (const char *t = text; t < text + pos; t++) {
        *ofs += 1;
        if (*t == '\n') {
            *ofs = 0;
            *line += 1;
        }
    }
}

#if HAVE_USELOCALE
int regexp_c_locale(ATTRIBUTE_UNUSED char **u, ATTRIBUTE_UNUSED size_t *len) {
    /* On systems with uselocale, we are ok, since we make sure that we
     * switch to the "C" locale any time we enter through the public API
     */
    return 0;
}
#else
int regexp_c_locale(char **u, size_t *len) {
    /* Without uselocale, we need to expand character ranges */
    int r;
    char *s = *u;
    size_t s_len, u_len;
    if (len == NULL) {
        len = &u_len;
        s_len = strlen(s);
    } else {
        s_len = *len;
    }
    r = fa_expand_char_ranges(s, s_len, u, len);
    if (r != 0) {
        *u = s;
        *len = s_len;
    }
    if (r < 0)
        return -1;
    /* Syntax errors will be caught when the result is compiled */
    if (r > 0)
        return 0;
    free(s);
    return 1;
}
#endif

#if ENABLE_DEBUG
bool debugging(const char *category) {
    const char *debug = getenv("AUGEAS_DEBUG");
    const char *s;

    if (debug == NULL)
        return false;

    for (s = debug; s != NULL; ) {
        if (STREQLEN(s, category, strlen(category)))
            return true;
        s = strchr(s, ':');
        if (s != NULL)
            s+=1;
    }
    return false;
}

FILE *debug_fopen(const char *format, ...) {
    va_list ap;
    FILE *result = NULL;
    const char *dir;
    char *name = NULL, *path = NULL;
    int r;

    dir = getenv("AUGEAS_DEBUG_DIR");
    if (dir == NULL)
        goto error;

    va_start(ap, format);
    r = vasprintf(&name, format, ap);
    va_end(ap);
    if (r < 0)
        goto error;

    r = xasprintf(&path, "%s/%s", dir, name);
    if (r < 0)
        goto error;

    result = fopen(path, "w");

 error:
    free(name);
    free(path);
    return result;
}
#endif
/*
 * Local variables:
 *  indent-tabs-mode: nil
 *  c-indent-level: 4
 *  c-basic-offset: 4
 *  tab-width: 4
 * End:
 */
