/* sml3_fehler.c: Fehler setzen und ausgeben */

/* Copyright 2012-2017 Kurt Nienhaus
 *
 * This file is part of libsammel3.
 * libsammel3 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 2 of the License, or
 * (at your option) any later version.
 * libsammel3 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 libsammel3.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include "config.h"
#ifdef SML3_HAVE_PTHREAD
# include <pthread.h>
#endif
#include "sml3_fehler.h"

struct fehler_st {
  struct fehler_st *next;
#define FEHLER_SIZE 4096
  char msg[FEHLER_SIZE];
  int errnum;
  int stop;
#ifdef SML3_HAVE_PTHREAD
  pthread_t pth;
#endif
};

static union {
  char c[sizeof(struct fehler_st)];
  struct fehler_st s;
} f_union = {};
static struct fehler_st *f_var = &f_union.s;

#ifdef SML3_HAVE_PTHREAD
static pthread_mutex_t f_mtx = PTHREAD_MUTEX_INITIALIZER;
#endif

void SML3_fehlerattach(void);
void SML3_fehlerdetach(void);
void _SML3_fehlerput(const char *, const char *, int, int, int, ...);
int SML3_fehlererrno(void);
void SML3_fehlermoderrno(int);
const char * SML3_fehlermsg(void);
const char * SML3_fehlermsgtext(char *, size_t);
void SML3_fehlerstart(void);
void SML3_fehlerstop(void);
char * SML3_strerror(int);

static struct fehler_st * get_fstruct(void);


/* get_fstruct [thread-sicher]:
 * gibt fuer aktuellen Thread Fehler-struct zurueck
 */
static struct fehler_st *
get_fstruct(void)
{
#ifdef SML3_HAVE_PTHREAD
  struct fehler_st *f_ptr;
  pthread_t ptself = pthread_self();

  pthread_mutex_lock(&f_mtx);
  for (f_ptr = f_var->next; f_ptr != NULL; f_ptr = f_ptr->next) {
    if (pthread_equal(ptself, f_ptr->pth)) { break; }
  }
  pthread_mutex_unlock(&f_mtx);
  if (f_ptr == NULL) { f_ptr = f_var; }
  return f_ptr;
#else
  return f_var;
#endif
} /* Ende get_fstruct */


/* SML3_fehlerattach [thread-sicher]:
 * reserviert eigene Fehlervariable fuer aufrufenden Thread,
 * muss zu Beginn eines neuen Threads aufgerufen werden
 */
void
SML3_fehlerattach(void)
{
#ifdef SML3_HAVE_PTHREAD
  struct fehler_st **f_pptr;
  pthread_t ptself = pthread_self();

  pthread_mutex_lock(&f_mtx);
  for (f_pptr = &f_var->next; *f_pptr != NULL; f_pptr = &(*f_pptr)->next) {
    if (pthread_equal(ptself, (*f_pptr)->pth)) { break; }
  }
  if (*f_pptr == NULL) {
    *f_pptr = calloc(1, sizeof(struct fehler_st));
    if (*f_pptr == NULL) { pthread_mutex_unlock(&f_mtx); fprintf(stderr, "SML3_fehlerattach: calloc: no memory"); exit(1); }
    (*f_pptr)->next = NULL;
    (*f_pptr)->pth = ptself;
  }
  *(*f_pptr)->msg = '\0';
  (*f_pptr)->errnum = 0;
  (*f_pptr)->stop = 0;
  pthread_mutex_unlock(&f_mtx);
#else
  ;
#endif
} /* Ende SML3_fehlerattach */


/* SML3_fehlerdetach [thread-sicher]:
 * gibt eigene Fehlervariable fuer aufrufenden Thread frei,
 * muss vor Beendigung eines Threads aufgerufen werden
 */
void
SML3_fehlerdetach(void)
{
#ifdef SML3_HAVE_PTHREAD
  struct fehler_st **f_pptr;
  pthread_t ptself = pthread_self();

  pthread_mutex_lock(&f_mtx);
  for (f_pptr = &f_var->next; *f_pptr != NULL; f_pptr = &(*f_pptr)->next) {
    if (pthread_equal(ptself, (*f_pptr)->pth)) { break; }
  }
  if (*f_pptr != NULL) {
    struct fehler_st *f_mrk = *f_pptr;
    *f_pptr = (*f_pptr)->next;
    free(f_mrk);
  }
  pthread_mutex_unlock(&f_mtx);
#else
  ;
#endif
} /* Ende SML3_fehlerdetach */


/* SML3_fehlernew [thread-sicher]:
 * beginnt neue Fehlermeldungspur
 * 1.Arg: errno-Wert (oder 0) => wird gemerkt und setzt errno
 * 2.Arg: Format (oder NULL = Fehlervariable loeschen)
 * weitere Arg: Formatvariablen (falls 2.Arg != NULL)
 * #define SML3_fehlernew(errnum, ...)  _SML3_fehlerput(__FILE__, SML3_funcname, __LINE__, 0, errnum, __VA_ARGS__)
 *
 * SML3_fehleradd [thread-sicher]:
 * fuegt Fehlermeldung an
 * 1.Arg: Format (oder NULL = keine Zusatzinfos ausser Zeileninfo)
 * weitere Arg: Formatvariablen (falls 1.Arg != NULL)
 * #define SML3_fehleradd(...)  _SML3_fehlerput(__FILE__, SML3_funcname, __LINE__, 1, 0, __VA_ARGS__)
 *
 * SML3_fehlerexit [thread-sicher]:
 * beendet mit Fehlermeldung nach stderr
 * 1.Arg: Format (oder NULL = schon gesetzte Fehlermeldung verwenden)
 * weitere Arg: Formatvariablen (falls 1.Arg != NULL)
 * #define SML3_fehlerexit(...)  _SML3_fehlerput(__FILE__, SML3_funcname, __LINE__, 2, 0, __VA_ARGS__)
 *
 * _SML3_fehlerput [thread-sicher]:
 * setzt Fehlermeldung
 * 1.Arg: Datei
 * 2.Arg: Funktionsname
 * 3.Arg: Zeile
 * 4.Arg: 0 = SML3_fehlernew()
 *        1 = SML3_fehleradd()
 * 5.Arg: errno-Wert
 * weitere Arg: Format + Formatvariablen (oder NULL = geloescht bzw. keine weitere Info)
 */
void
_SML3_fehlerput(const char *datei, const char *funktion, int zeile, int flag, int errnum, ...)
{
  int pos0, pos, len;
  struct fehler_st *f_ptr = get_fstruct();
  va_list ap;
  char *fmt;

  if (flag == 1) { errnum = errno; }  /* SML3_fehleradd() */
  if (flag != 2 && f_ptr->stop != 0) { errno = errnum; return; }

  if (flag == 0) {  /* SML3_fehlernew() */
    f_ptr->errnum = errnum;
    *f_ptr->msg = '\0';
  }

  if ((pos0 = pos = strlen(f_ptr->msg)) >= FEHLER_SIZE - 1) { goto _putend; }
  if (funktion != NULL && *funktion != '\0') {
    len = snprintf(f_ptr->msg + pos, FEHLER_SIZE - pos, "%s[at %s:%s():%d]", flag == 1 ? "\n" : "",
          (datei == NULL ? "(null)" : datei), funktion, zeile);
  } else {
    len = snprintf(f_ptr->msg + pos, FEHLER_SIZE - pos, "%s[at %s:%d]", flag == 1 ? "\n" : "",
          (datei == NULL ? "(null)" : datei), zeile);
  }
  if (len < 0 || len >= FEHLER_SIZE - pos) { f_ptr->msg[pos0] = '\0'; goto _putend; }
  pos += len;
  if (pos >= FEHLER_SIZE - 1) { f_ptr->msg[pos0] = '\0'; goto _putend; }

  va_start(ap, errnum);
  fmt = va_arg(ap, char *);
  if (fmt == NULL && flag == 0) { f_ptr->errnum = errnum = 0; *f_ptr->msg = '\0'; }  /* SML3_fehlernew() */
  if (fmt != NULL && *fmt != '\0') {
    len = snprintf(f_ptr->msg + pos, FEHLER_SIZE - pos, ": ");
    if (len < 0 || len >= FEHLER_SIZE - pos) { va_end(ap); f_ptr->msg[pos] = '\0'; goto _putend; }
    pos += len;
    if (pos >= FEHLER_SIZE - 1) { va_end(ap); f_ptr->msg[pos0] = '\0'; goto _putend; }
    len = vsnprintf(f_ptr->msg + pos, FEHLER_SIZE - pos, fmt, ap);
    if (len < 0) { va_end(ap); f_ptr->msg[pos] = '\0'; goto _putend; }
    if (len >= FEHLER_SIZE - pos) { f_ptr->msg[FEHLER_SIZE - 1] = '\0'; }
  }
  va_end(ap);
  if (flag == 2 && *f_ptr->msg != '\0') { fprintf(stderr, "%s\n", f_ptr->msg); }
_putend:
  errno = errnum;
  if (flag == 2) { exit(1); }
} /* Ende _SML3_fehlerput */


/* SML3_fehlererrno [thread-sicher]:
 * gibt errno-Wert zurueck, der beim Aufruf von SML3_fehlernew() gemerkt wurde
 * Rueckgabe: SML3-errno-Wert
 */
int
SML3_fehlererrno(void)
{
  struct fehler_st *f_ptr = get_fstruct();
  return f_ptr->errnum;
} /* Ende SML3_fehlererrno */


/* SML3_fehlermoderrno [thread-sicher]:
 * veraendert mit SML3_fehlernew() gesetzten errno-Wert
 * 1.Arg: SML3-errno-Wert
 */
void
SML3_fehlermoderrno(int errnum)
{
  struct fehler_st *f_ptr = get_fstruct();
  f_ptr->errnum = errnum;
} /* Ende SML3_fehlermoderrno */


/* SML3_fehlermsg [thread-sicher]:
 * gibt Fehlermeldungspur zurueck
 * Rueckgabe: Fehlermeldungspur
 */
const char *
SML3_fehlermsg(void)
{
  struct fehler_st *f_ptr = get_fstruct();
  return f_ptr->msg;
} /* Ende SML3_fehlermsg */


/* SML3_fehlermsgtext [thread-sicher]:
 * gibt reinen Fehlermeldungstext im 2.Arg zurueck
 * 1.Arg: fuer Rueckgabe Fehlermeldungstext
 * 2.Arg: sizeof 1.Arg
 * Rueckgabe: Pointer auf 1.Arg (oder leer, aber nicht NULL)
 */
const char *
SML3_fehlermsgtext(char *fbuf, size_t fbufsize)
{
  struct fehler_st *f_ptr = get_fstruct();
  char *p1, *p2;
  size_t fpos;
  int r1;

  if (fbuf == NULL || fbufsize == 0) { return ""; }
  fpos = 0;
  for (p1 = strchr(f_ptr->msg, ']'); p1 != NULL; p1 = strchr(p1, ']')) {
    p2 = strchr(p1, '\n');
    if (p1[1] == ':') {
      p1++;
      p1 += 1 + strspn(p1 + 1, " ");
      r1 = snprintf(fbuf + fpos, fbufsize - fpos, "%s%.*s",
           fpos == 0 ? "": " - ", (int)((p2 == NULL ? p1 + strlen(p1) : p2) - p1), p1);
      if (r1 < 0 || r1 >= (int)(fbufsize - fpos)) { break; }
      fpos += r1;
    }
    if (p2 == NULL) { break; }
    p1 = p2 + 1;
  }
  return fbuf;
} /* Ende SML3_fehlermsgtext */


/* SML3_fehlerstart [thread-sicher]:
 * aktiviert Setzen von Fehlermeldungen.
 * Je Aufruf wird eine Stackvariable dekrementiert,
 * d.h. SML3_fehlerstart() muss ebenso oft aufgerufen werden,
 * wie SML3_fehlerstop() aufgerufen wurde, damit Fehler wieder gesetzt werden.
 */
void
SML3_fehlerstart(void)
{
  struct fehler_st *f_ptr = get_fstruct();
  if (f_ptr->stop != 0) { f_ptr->stop--; }
} /* Ende SML3_fehlerstart */


/* SML3_fehlerstop [thread-sicher]:
 * deaktiviert Setzen von Fehlermeldungen.
 * Je Aufruf wird eine Stackvariable inkrementiert,
 * d.h. es muss ebenso oft SML3_fehlerstart() aufgerufen werden,
 * damit Fehler wieder gesetzt werden.
 */
void
SML3_fehlerstop(void)
{
  struct fehler_st *f_ptr = get_fstruct();
  f_ptr->stop++;
} /* Ende SML3_fehlerstop */


/* SML3_strerror:
 * wie strerror(), aber threadsicher
 * 1.Arg: Fehlernummer
 * Rueckgabe: statischer String mit Fehlermeldung
 */
char *
SML3_strerror(int errnum)
{
#define MAX_NR 1024
#define MAX_SIZE 256
  static int neu = 1;
  static char errtxt[MAX_NR][MAX_SIZE];
#ifdef SML3_HAVE_PTHREAD
  static pthread_mutex_t e_mtx = PTHREAD_MUTEX_INITIALIZER;
  pthread_mutex_lock(&e_mtx);
#endif

  if (neu) { memset(errtxt, 0, sizeof(errtxt)); snprintf(errtxt[MAX_NR - 1], MAX_SIZE, "Unknown error"); neu = 0; }
  if (errnum >= MAX_NR - 1) { errnum = MAX_NR - 1; goto _fend; }
  if (*errtxt[errnum] == '\0') { snprintf(errtxt[errnum], MAX_SIZE, "%s", strerror(errnum)); }

_fend:
#ifdef SML3_HAVE_PTHREAD
  pthread_mutex_unlock(&e_mtx);
#endif
  return errtxt[errnum];
} /* Ende SML3_strerror */
