/* For various obscure reasons, doing this inside the device driver is
 * unnecessarily difficult; so we do it here.  Don't ask... */

#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <setjmp.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/nameser.h>
#define __const const
#include <resolv.h>
#include <unistd.h>
#include <support.h>
#include <osbind.h>
#include <mintbind.h>
#include "gs_descr.h"

#define MAX_RETRIES	10	/* number of times to retry for TRY_AGAIN */

static int main_fd;


/* in_tfork() -- used in "put-self-in-background" trick.  See below. */
static jmp_buf  tforkj;
static int in_tfork(long arg)
{
  /* Wait for parent to die, then longjmp back */
  while (getppid() > 1)
    sleep(1);
  longjmp(tforkj, 1);
  /*NOTREACHED*/
  return 0;
}


static void *KRmalloc_stub(int32 size)
{
  union GS_cmd cmd;

  cmd.KRmalloc_cmd.cmd = KRMALLOC_CMD;
  cmd.KRmalloc_cmd.size = size;
  return (void *)Fcntl(main_fd, (long)&cmd, GLUESTIK_CMD);
}

char *do_get_err_text(int16 err)
{
  return (char *)Fcntl(main_fd, (long)err, GETERRTXT_CMD);
}

static void log_entry_stub(int pid, int lev, const char *line)
{
  union GS_cmd cmd;

  cmd.log_entry_cmd.cmd = LOGENTRY_CMD;
  cmd.log_entry_cmd.pid = Pgetpid();
  cmd.log_entry_cmd.lev = lev;
  cmd.log_entry_cmd.line = line;
  (void)Fcntl(main_fd, (long)&cmd, GLUESTIK_CMD);
}


static void log_entry_(int pid, int lev, const char *fmt, ...)
{
  va_list ap;
  char line[512];

  va_start(ap, fmt);
  log_entry_fmt(line, fmt, ap);
  va_end(ap);

  log_entry_stub(pid, lev, line);
}


static int resolve_stub(int pid, struct GS_resolve_cmd *args)
{
  register struct hostent *H = 0;
  register char **raddr;
  register int i;
  register int retries = 0;
  int retval;

  log_entry_(pid, DBG_SYSCALL, "In resolve(\"%s\", %p, %p, %d)",
	     args->dn, (void *)args->rdn, (void *)args->alist, args->lsize);

  /* Experimental:  retry in the event of TRY_AGAIN */
  for (;;) {
    H = gethostbyname(args->dn);
    if (H)
      break;
    if (h_errno == TRY_AGAIN) {
      if (++retries < MAX_RETRIES) {
	log_entry_(pid, DBG_SYSCALL,
		   "In resolve(\"%s\", %p, %p, %d):  "
		   "gethostbyname() returns %H; retrying...",
		   args->dn, (void *)args->rdn, (void *)args->alist,
		   args->lsize, h_errno);
	sleep(1);
	continue;
      } else {
	log_entry_(pid, DBG_SYSCALL,
		   "In resolve(\"%s\", %p, %p, %d):  %d retries failed",
		   args->dn, (void *)args->rdn, (void *)args->alist,
		   args->lsize, MAX_RETRIES);
      }
    }

    log_entry_(pid, DBG_ERROR,
	       "In resolve(\"%s\", %p, %p, %d):  gethostbyname() returns %H",
	       args->dn, (void *)args->rdn, (void *)args->alist,
	       args->lsize, h_errno);
    switch (h_errno) {
      case HOST_NOT_FOUND:
	retval = E_NOHOSTNAME; break;
      case NO_DATA:
	retval = E_DNSNOADDR; break;
      case TRY_AGAIN:
      case NO_RECOVERY:
      default:
	retval = E_CANTRESOLVE; break;
    }
    log_entry_(pid, DBG_ERROR, "resolve(\"%s\", %p, %p, %d) returns %E",
	       args->dn, (void *)args->rdn, (void *)args->alist,
	       args->lsize, retval);
    return retval;
  }

  log_entry_(pid, DBG_TRACE,
	     "In resolve(\"%s\", %p, %p, %d):  gethostbyname() returns %p",
	     args->dn, (void *)args->rdn, (void *)args->alist, args->lsize,
	     (void *)H);

  if (args->rdn) {
    log_entry_(pid, DBG_TRACE, "Copying name \"%s\"", H->h_name);
    *(args->rdn) = KRmalloc_stub(strlen(H->h_name) + 1);
    strcpy(*(args->rdn), H->h_name);
  }
  /* BUG:  assumes addresses returned have type struct in_addr */
  for (i = 0, raddr = H->h_addr_list;
       *raddr && i < args->lsize;
       i++, raddr++) {
    log_entry_(pid, DBG_TRACE, "Copying address %A to array element %d",
	       ((struct in_addr *)(*raddr))->s_addr, i);
    args->alist[i] = ((struct in_addr *)(*raddr))->s_addr;
  }

  log_entry_(pid, DBG_SYSCALL, "resolve(\"%s\", %p, %p, %d) returns %d",
	     args->dn, (void *)args->rdn, (void *)args->alist, args->lsize, i);
  return i;
}


static void mainloop()
{
  PMSG pmsg;
  int n;
  struct GS_resolve_cmd *args;

  for (;;) {
    n = Pmsg(0, RESOLV_MBX, &pmsg);
    if (n < 0) {
      log_entry_(pmsg.pid, DBG_FATAL, "In resolve(): Pmsg(0) returned %e", n);
      return;
    }
    args = (struct GS_resolve_cmd *)pmsg.userlong1;
    pmsg.userlong2 = resolve_stub(pmsg.pid, args);
    n = Pmsg(1, 0xFFFF0000L | pmsg.pid, &pmsg);
    log_entry_(pmsg.pid, DBG_TRACE, "In resolve(): Pmsg(1) returned %e", n);
  }
}

int main()
{
  Cconws("GlueSTiK\277 STiK emulator for MiNTnet\r\n"
	 "DNS resolver module\r\n"
	 "Version " GS_VERSION "\r\n"
	 "\275 1996-98 Scott Bigham\r\n\r\n");

  /* "put-self-in-background" trick lifted from syslogd */
#if 0	/* when MiNT gets a non-blocking fork(), this will work */
  if (fork())
    exit(0);
#else	/* until then, we use this */
  if (!setjmp(tforkj) && tfork(in_tfork, 0) >= 0)
    _exit(0);
#endif

  main_fd = Fopen(GSDEV_NAME, 2);
  if (main_fd < 0) {
    static char buf[20];
    _ltoa(main_fd, buf, 10);
    Cconws("Unable to open GlueSTiK socket device (error code ");
    Cconws(buf);
    Cconws(")\r\n");
    return 1;
  }

  log_entry_(Pgetpid(), DBG_TRACE, "DNS lookup module active");
  mainloop();

  Fclose(main_fd);
  return 0;
}
