/*
 *  innduct
 *  tailing reliable realtime streaming feeder for inn
 *  help.c - logging and utility functions
 *
 *  Copyright Ian Jackson <ijackson@chiark.greenend.org.uk>
 *  and contributors; see LICENCE.txt.
 *  SPDX-License-Identifier: GPL-3.0-or-later
 */

#include "innduct.h"


/* for logging, simulation, debugging, etc. */
int simulate_flush= -1;
int logv_use_syslog;
const char *logv_prefix="";

/*========== logging ==========*/

static void logcore(int sysloglevel, const char *fmt, ...) PRINTF(2,3);
static void logcore(int sysloglevel, const char *fmt, ...) {
  VA;
  if (logv_use_syslog) {
    vsyslog(sysloglevel,fmt,al);
  } else {
    if (self_pid) fprintf(stderr,"[%lu] ",(unsigned long)self_pid);
    vfprintf(stderr,fmt,al);
    putc('\n',stderr);
  }
  va_end(al);
}

void logv(int sysloglevel, const char *pfx, int errnoval,
	  const char *fmt, va_list al) {
  char msgbuf[1024]; /* NB do not call mvasprintf here or you'll recurse */
  vsnprintf(msgbuf,sizeof(msgbuf), fmt,al);
  msgbuf[sizeof(msgbuf)-1]= 0;

  if (sysloglevel >= LOG_ERR && (errnoval==EACCES || errnoval==EPERM))
    sysloglevel= LOG_ERR; /* run by wrong user, probably */

  logcore(sysloglevel, "%s%s: %s%s%s",
	  logv_prefix, pfx, msgbuf,
	  errnoval>=0 ? ": " : "",
	  errnoval>=0 ? strerror(errnoval) : "");
}

#define DEFFATAL(fn, pfx, sysloglevel, err, estatus)	\
  void fn(const char *fmt, ...) {			\
    preterminate();					\
    VA;							\
    logv(sysloglevel, pfx, err, fmt, al);		\
    exit(estatus);					\
  }

#define DEFLOG(fn, pfx, sysloglevel, err)	\
  void fn(const char *fmt, ...) {		\
    VA;						\
    logv(sysloglevel, pfx, err, fmt, al);	\
    va_end(al);					\
  }

#define INNLOGSET_DEFINE(fn, pfx, sysloglevel)				\
  void duct_log_##fn(int l, const char *fmt, va_list al, int errval) {	\
    logv(sysloglevel, pfx, errval ? errval : -1, fmt, al);		\
  }


/* We want to extend the set of logging functions from inn, and we
 * want to prepend the site name to all our messages. */

DEFFATAL(syscrash,    "critical", LOG_CRIT,    errno, 16);
DEFFATAL(crash,       "critical", LOG_CRIT,    -1,    16);

INNLOGSETS(INNLOGSET_DEFINE)

DEFLOG(info,          "info",     LOG_INFO,    -1)
DEFLOG(dbg,           "debug",    LOG_DEBUG,   -1)


/*========== utility functions etc. ==========*/

char *mvasprintf(const char *fmt, va_list al) {
  char *str;
  int rc= vasprintf(&str,fmt,al);
  if (rc<0) sysdie("vasprintf(\"%s\",...) failed", fmt);
  return str;
}

char *masprintf(const char *fmt, ...) {
  VA;
  char *str= mvasprintf(fmt,al);
  va_end(al);
  return str;
}

int close_perhaps(int *fd) {
  if (*fd <= 0) return 0;
  int r= close(*fd);
  *fd=0;
  return r;
}
void xclose(int fd, const char *what, const char *what2) {
  int r= close(fd);
  if (r) syscrash("close %s%s",what,what2?what2:"");
}
void xclose_perhaps(int *fd, const char *what, const char *what2) {
  if (*fd <= 0) return;
  xclose(*fd,what,what2);
  *fd=0;
}

pid_t xfork_bare(const char *what) {
  pid_t child= fork();
  if (child==-1) sysdie("cannot fork for %s",what);
  dbg("forked %s %ld", what, (unsigned long)child);
  return child;
}

pid_t xfork(const char *what) {
  pid_t child= xfork_bare(what);
  if (!child) postfork();
  return child;
}

void on_fd_read_except(int fd, oop_call_fd callback) {
  loop->on_fd(loop, fd, OOP_READ,      callback, 0);
  loop->on_fd(loop, fd, OOP_EXCEPTION, callback, 0);
}
void cancel_fd_read_except(int fd) {
  loop->cancel_fd(loop, fd, OOP_READ);
  loop->cancel_fd(loop, fd, OOP_EXCEPTION);
}

void report_child_status(const char *what, int status) {
  if (WIFEXITED(status)) {
    int es= WEXITSTATUS(status);
    if (es)
      warn("%s: child died with error exit status %d", what, es);
  } else if (WIFSIGNALED(status)) {
    int sig= WTERMSIG(status);
    const char *sigstr= strsignal(sig);
    const char *coredump= WCOREDUMP(status) ? " (core dumped)" : "";
    if (sigstr)
      warn("%s: child died due to fatal signal %s%s", what, sigstr, coredump);
    else
      warn("%s: child died due to unknown fatal signal %d%s",
	   what, sig, coredump);
  } else {
    warn("%s: child died with unknown wait status %d", what,status);
  }
}

int xwaitpid(pid_t *pid, const char *what) {
  int status;

  int r= kill(*pid, SIGKILL);
  if (r) syscrash("cannot kill %s child", what);

  pid_t got= waitpid(*pid, &status, 0);
  if (got==-1) syscrash("cannot reap %s child", what);
  if (got==0) crash("cannot reap %s child", what);

  *pid= 0;

  return status;
}

void *zxmalloc(size_t sz) {
  void *p= xmalloc(sz);
  memset(p,0,sz);
  return p;
}

void xunlink(const char *path, const char *what) {
  int r= unlink(path);
  if (r) syscrash("can't unlink %s %s", path, what);
}

time_t xtime(void) {
  time_t now= time(0);
  if (now==-1) syscrash("time(2) failed");
  return now;
}

void xsigaction(int signo, const struct sigaction *sa) {
  int r= sigaction(signo,sa,0);
  if (r) syscrash("sigaction failed for \"%s\"", strsignal(signo));
}
void xsigsetdefault(int signo) {
  struct sigaction sa;
  memset(&sa,0,sizeof(sa));
  sa.sa_handler= SIG_DFL;
  xsigaction(signo,&sa);
}
void raise_default(int signo) {
  xsigsetdefault(signo);
  raise(signo);
  abort();
}

void xgettimeofday(struct timeval *tv_r) {
  int r= gettimeofday(tv_r,0);
  if (r) syscrash("gettimeofday(2) failed");
}
void xsetnonblock(int fd, int nonb) {
  int errnoval= oop_fd_nonblock(fd, nonb);
  if (errnoval) { errno= errnoval; syscrash("setnonblocking"); }
}

void check_isreg(const struct stat *stab, const char *path,
		 const char *what) {
  if (!S_ISREG(stab->st_mode))
    crash("%s %s not a plain file (mode 0%lo)",
	  what, path, (unsigned long)stab->st_mode);
}

void xfstat(int fd, struct stat *stab_r, const char *what) {
  int r= fstat(fd, stab_r);
  if (r) syscrash("could not fstat %s", what);
}

void xfstat_isreg(int fd, struct stat *stab_r,
		  const char *path, const char *what) {
  xfstat(fd, stab_r, what);
  check_isreg(stab_r, path, what);
}

void xlstat_isreg(const char *path, struct stat *stab,
		  int *enoent_r /* 0 means ENOENT is fatal */,
		  const char *what) {
  int r= lstat(path, stab);
  if (r) {
    if (errno==ENOENT && enoent_r) { *enoent_r=1; return; }
    syscrash("could not lstat %s %s", what, path);
  }
  if (enoent_r) *enoent_r= 0;
  check_isreg(stab, path, what);
}

int samefile(const struct stat *a, const struct stat *b) {
  assert(S_ISREG(a->st_mode));
  assert(S_ISREG(b->st_mode));
  return (a->st_ino == b->st_ino &&
	  a->st_dev == b->st_dev);
}

char *sanitise(const char *input, int len) {
  static char sanibuf[100]; /* returns pointer to this buffer! */

  const char *p= input;
  const char *endp= len>=0 ? input+len : 0;
  char *q= sanibuf;
  *q++= '`';
  for (;;) {
    if (q > sanibuf+sizeof(sanibuf)-8) { strcpy(q,"'.."); break; }
    int c= (!endp || p<endp) ? *p++ : 0;
    if (!c) { *q++= '\''; *q=0; break; }
    if (c>=' ' && c<=126 && c!='\\') { *q++= c; continue; }
    sprintf(q,"\\x%02x",c);
    q += 4;
  }
  return sanibuf;
}
