/* main.c -
   Main program for sgmls.

     Written by James Clark (jjc@jclark.com).
*/

#include "config.h"
#include "std.h"
#include "getopt.h"
#include "entity.h"           /* Templates for entity control blocks. */
#include "adl.h"              /* Definitions for attribute list processing. */
#include "sgmlmain.h"         /* Main interface to SGML services. */
#include "appl.h"

#define READCNT 512

/* Before using argv[0] in error messages, strip off everything up to and
including the last character in prog that occurs in PROG_PREFIX. */

#ifndef PROG_PREFIX
#define PROG_PREFIX "/"
#endif /* not PROG_PREFIX */

/* Message catalogue name. */
#define CAT_NAME "sgmls"
/* Message set to use for application error messages. */
#define APP_SET 4

#ifdef HAVE_EXTENDED_PRINTF
#define xvfprintf vfprintf
#else
extern int xvfprintf P((FILE *, char *, va_list));
#endif

static VOID usage P((void));
static VOID fatal VP((int, ...));
static VOID do_error P((int, va_list));
static VOID swinit P((struct switches *));
static VOID write_caps P((char *, struct sgmlcap *));

static UNIV make_docent P((int, char **));
static char *munge_program_name P((char *, char *));
static VOID die P((void));
#ifdef SUPPORT_SUBDOC
static VOID build_subargv P((struct switches *));
static VOID cleanup P((void));
static char *create_subcap_file P((void));
#endif /* SUPPORT_SUBDOC */

static char *errlist[] = {
     0,
     "Out of memory",
     "Cannot open SGML document entity",
     "Cannot exec `%s': %s",
     "Cannot fork: %s",
     "Error waiting for process: %s",
     "Program %s got fatal signal %d",
     "Cannot open `%s': %s",
     "Subdocument capacity botch",
     "Non-existent subdocument entity `%s' not processed",
};

int suppsw = 0;			/* Non-zero means suppress output. */
int locsw = 0;			/* Non-zero means generate location info. */
static char *prog;		/* Program name (for error messages). */
static nl_catd catd;		/* Message catalogue descriptor. */
static char *capfile = 0;	/* File for capacity report. */
extern char *version_string;

char options[] = {
     'c', ':', 'd', 'e', 'g', 'i', ':', 'l', 'o', ':', 'p', 'r', 's', 'u', 'v',
#ifdef CANT_REDIRECT_STDERR
     'f', ':',
#endif /* CANT_REDIRECT_STDERR */
#ifdef TRACE
     'x', ':', 'y', ':',
#endif /* TRACE */
     '\0'
};

#ifdef SUPPORT_SUBDOC
int suberr = 0;			/* Error in subdocument. */
static char *subargv[sizeof(options)];
static int subargc = 0;
static char nopenbuf[sizeof(long)*3 + 1];
static char sgmldecl_file[L_tmpnam];
static char subcap_file[L_tmpnam];
#endif

int main(argc, argv)
int argc;
char **argv;
{
     static char stderr_buf[BUFSIZ];
     int opt;
#ifdef CANT_REDIRECT_STDERR
     char *errfile = 0;
#endif
     struct sgmlcap cap;
     struct switches sw;
     int nincludes = 0;	      /* number of -i options */
     setbuf(stderr, stderr_buf);

     /* Define MAIN_HOOK in config.h if some function needs to be called here. */
#ifdef MAIN_HOOK
     MAIN_HOOK(argc, argv);
#endif
#ifdef SUPPORT_SUBDOC
     subargv[subargc++] = argv[0];
#endif

     prog = argv[0] = munge_program_name(argv[0], "sgmls");

     catd = catopen(CAT_NAME, 0);
     swinit(&sw);

     while ((opt = getopt(argc, argv, options)) != EOF) {
	  switch (opt) {
	  case 'l':	      /* Generate location information. */
	       locsw = 1;
	       break;
	  case 'c':	      /* Print capacity usage. */
	       capfile = optarg;
	       break;
	  case 's':	      /* Suppress output. */
	       suppsw = 1;
	       break;
	  case 'd':           /* Report duplicate entity declarations. */
	       sw.swdupent = 1;
	       break;
	  case 'e':           /* Provide entity stack trace in error msg. */
	       sw.swenttr = 1;
	       break;
#ifdef CANT_REDIRECT_STDERR
	  case 'f':	      /* Redirect errors. */
	       errfile = optarg;
	       break;
#endif /* CANT_REDIRECT_STDERR */
	  case 'g':           /* Provide GI stack trace in error messages. */
	       sw.sweltr = 1;
	       break;
	  case 'p':	      /* Parse only the prolog. */
	       sw.onlypro = 1;
	       suppsw = 1;
	       break;
	  case 'r':           /* Give warning for defaulted references. */
	       sw.swrefmsg = 1;
	       break;
	  case 'u':
	       sw.swundef = 1;
	       break;
#ifdef TRACE
	  case 'x':	       /* Trace options for the document body. */
	       sw.trace = optarg;
	       break;
	  case 'y':	       /* Trace options for the prolog. */
	       sw.ptrace =  optarg;
	       break;
#endif /* TRACE */
	  case 'v':	       /* Print the version number. */
	       fprintf(stderr, "sgmls version %s\n", version_string);
	       fflush(stderr);
	       break;
	  case 'o':
	       sw.nopen = atol(optarg);
	       if (sw.nopen <= 0)
		    usage();
	       break;
	  case 'i':	      /* Define parameter entity as "INCLUDE". */
	       sw.includes = (char **)xrealloc((UNIV)sw.includes,
					       (nincludes + 2)*sizeof(char *));
	       sw.includes[nincludes++] = optarg;
	       sw.includes[nincludes] = 0;
	       break;
	  case '?':
	       usage();
	  default:
	       abort();
	  }
     }
     
#ifdef CANT_REDIRECT_STDERR
     if (errfile) {
	  FILE *fp;
	  errno = 0;
	  fp = fopen(errfile, "w");
	  if (!fp)
	       fatal(E_OPEN, errfile, strerror(errno));
	  fclose(fp);
	  errno = 0;
	  if (!freopen(errfile, "w", stderr)) {
	       /* Can't use fatal() since stderr is now closed */
	       printf("%s: ", prog);
	       printf(errlist[E_OPEN], errfile, strerror(errno));
	       putchar('\n');
	       exit(EXIT_FAILURE);
	  }
     }
#endif /* CANT_REDIRECT_STDERR */

     (void)sgmlset(&sw);

#ifdef SUPPORT_SUBDOC
     build_subargv(&sw);
#endif
     if (sgmlsdoc(make_docent(argc - optind, argv + optind)))
	  fatal(E_DOC);

     process_document(sw.nopen > 0);
     sgmlend(&cap);
     if (capfile)
	  write_caps(capfile, &cap);
#ifdef SUPPORT_SUBDOC
     cleanup();
     if (suberr)
	  exit(EXIT_FAILURE);
#endif /* SUPPORT_SUBDOC */
     if (sgmlgcnterr() > 0)
	  exit(EXIT_FAILURE);
     if (!sw.nopen)
	  output_conforming();
     exit(EXIT_SUCCESS);
}

static char *munge_program_name(arg, dflt)
char *arg, *dflt;
{
     char *p;
#ifdef PROG_STRIP_EXTENSION
     char *ext;
#endif
     if (!arg || !*arg)
	  return dflt;
     p = strchr(arg, '\0');
     for (;;) {
	  if (p == arg)
	       break;
	  --p;
	  if (strchr(PROG_PREFIX, *p)) {
	       p++;
	       break;
	  }
     }
     arg = p;
#ifdef PROG_STRIP_EXTENSION
     ext = strrchr(arg, '.');
     if (ext) {
	  p = (char *)xmalloc(ext - arg + 1);
	  memcpy(p, arg, ext - arg);
	  p[ext - arg] = '\0';
	  arg = p;
     }
#endif /* PROG_STRIP_EXTENSION */
#ifdef PROG_FOLD
#ifdef PROG_STRIP_EXTENSION
     if (!ext) {
#endif
	  p = xmalloc(strlen(arg) + 1);
	  strcpy(p, arg);
	  arg = p;
#ifdef PROG_STRIP_EXTENSION
     }
#endif
     for (p = arg; *p; p++)
	  if (ISASCII((unsigned char)*p) && isupper((unsigned char)*p))
	       *p = tolower((unsigned char)*p);
#endif /* PROG_FOLD */
     return arg;
}

static UNIV make_docent(argc, argv)
int argc;
char **argv;
{
     UNS len = 1;
     int i;
     UNIV res;
     char *ptr;
     static char *stdinname = STDINNAME;

     if (argc == 0) {
	  argv = &stdinname;
	  argc = 1;
     }

     for (i = 0; i < argc; i++)
	  len += strlen(argv[i]) + 1;
     
     res = xmalloc(len);
     ptr = (char *)res;
     for (i = 0; i < argc; i++) {
	  strcpy(ptr, argv[i]);
	  ptr = strchr(ptr, '\0') + 1;
     }
     *ptr = '\0';
     return res;
}


static VOID usage()
{
     /* Don't mention -o since this are for internal use only. */
     fprintf(stderr, "Usage: %s [-deglprsuv]%s [-c file] [-i entity]%s [filename ...]\n",
	     prog,
#ifdef CANT_REDIRECT_STDERR
	     " [-f file]",
#else /* not CANT_REDIRECT_STDERR */
	     "",
#endif /* not CANT_REDIRECT_STDERR */
#ifdef TRACE
	     " [-x flags] [-y flags]"
#else /* not TRACE */
	     ""
#endif /* not TRACE */
	     );
     exit(EXIT_FAILURE);
}

static VOID die()
{
#ifdef SUPPORT_SUBDOC
     cleanup();
#endif /* SUPPORT_SUBDOC */
     exit(EXIT_FAILURE);
}

static VOID swinit(swp)
struct switches *swp;
{
     swp->swenttr = 0;
     swp->sweltr = 0;
     swp->swbufsz = READCNT+2;
     swp->prog = prog;
     swp->swdupent = 0;
     swp->swrefmsg = 0;
#ifdef TRACE
     swp->trace = 0;
     swp->ptrace = 0;
#endif /* TRACE */
     swp->catd = catd;
     swp->swambig = 1;	      /* Always check for ambiguity. */
     swp->swundef = 0;
     swp->nopen = 0;
     swp->onlypro = 0;
     swp->includes = 0;
     swp->die = die;
}

#ifdef SUPPORT_SUBDOC

static VOID build_subargv(swp)
struct switches *swp;
{
     if (suppsw)
	  subargv[subargc++] = "-s";
     if (locsw)
	  subargv[subargc++] = "-l";
     if (swp->swdupent)
	  subargv[subargc++] = "-d";
     if (swp->swenttr)
	  subargv[subargc++] = "-e";
     if (swp->sweltr)
	  subargv[subargc++] = "-g";
     if (swp->swrefmsg)
	  subargv[subargc++] = "-r";
#ifdef TRACE
     if (swp->trace) {
	  subargv[subargc++] = "-x";
	  subargv[subargc++] = swp->trace;
     }
     if (swp->ptrace) {
	  subargv[subargc++] = "-y";
	  subargv[subargc++] = swp->ptrace;
     }
#endif /* TRACE */
     subargv[subargc++] = "-o";
     sprintf(nopenbuf, "%ld", swp->nopen + 1);
     subargv[subargc++] = nopenbuf;
}


static
VOID handler(sig)
int sig;
{
     signal(sig, SIG_DFL);
     cleanup();
     raise(sig);
}

static
VOID cleanup()
{
     if (sgmldecl_file[0]) {
	  (void)remove(sgmldecl_file);
	  sgmldecl_file[0] = '\0';
     }
     if (subcap_file[0]) {
	  (void)remove(subcap_file);
	  subcap_file[0] = '\0';
     }
}

static
char *store_sgmldecl()
{
     if (!sgmldecl_file[0]) {
	  FILE *fp;
	  if (signal(SIGINT, SIG_IGN) != SIG_IGN)
	       signal(SIGINT, handler);
#ifdef SIGTERM
	  if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
	       signal(SIGTERM, handler);
#endif /* SIGTERM */
#ifdef SIGPIPE
	  if (signal(SIGPIPE, SIG_IGN) != SIG_IGN)
	       signal(SIGPIPE, handler);
#endif
#ifdef SIGHUP
	  if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
	       signal(SIGHUP, handler);
#endif
	  tmpnam(sgmldecl_file);
	  errno = 0;
	  fp = fopen(sgmldecl_file, "w");
	  if (!fp)
	       fatal(E_OPEN, sgmldecl_file, strerror(errno));
	  sgmlwrsd(fp);
	  fclose(fp);
     }
     return sgmldecl_file;
}

static
char *create_subcap_file()
{
     if (subcap_file[0] == '\0') {
	  FILE *fp;
	  tmpnam(subcap_file);
	  fp = fopen(subcap_file, "w");
	  if (!fp)
	       fatal(E_OPEN, subcap_file, strerror(errno));
	  fclose(fp);
     }
     return subcap_file;
}

char **make_argv(id)
UNIV id;
{
     int nfiles;
     char *p;
     char **argv;
     int i;

     for (p = (char *)id, nfiles = 0; *p; p = strchr(p, '\0') + 1)
	  nfiles++;
     
     argv = (char **)xmalloc((subargc + 2 + 1 + nfiles + 1)*sizeof(char *));
     memcpy((UNIV)argv, (UNIV)subargv, subargc*sizeof(char *));
     
     i = subargc;

     argv[i++] = "-c";
     argv[i++] = create_subcap_file();

     argv[i++] = store_sgmldecl();

     for (p = (char *)id; *p; p = strchr(p, '\0') + 1)
	  argv[i++] = p;
     argv[i] = 0;
     return argv;
}

VOID get_subcaps()
{
     long cap[NCAPACITY];
     FILE *fp;
     int i;

     if (!subcap_file[0])
	  return;
     errno = 0;
     fp = fopen(subcap_file, "r");
     if (!fp)
	  fatal(E_OPEN, subcap_file, strerror(errno));
     for (i = 0; i < NCAPACITY; i++)
	  if (fscanf(fp, "%*s %ld", cap + i) != 1)
	       fatal(E_CAPBOTCH);
     fclose(fp);
     sgmlsubcap(cap);
}


#endif /* SUPPORT_SUBDOC */

/* Print capacity statistics.*/

static VOID write_caps(name, p)
char *name;
struct sgmlcap *p;
{
     FILE *fp;
     int i;
     fp = fopen(name, "w");
     if (!fp)
	  fatal(E_OPEN, name, strerror(errno));
     /* This is in RACT format. */
     for (i = 0; i < NCAPACITY; i++)
	  fprintf(fp, "%s %ld\n", p->name[i], p->number[i]*p->points[i]);
     fclose(fp);
}

UNIV xmalloc(n)
UNS n;
{
     UNIV p = malloc(n);
     if (!p)
	  fatal(E_NOMEM);
     return p;
}

UNIV xrealloc(s, n)
UNIV s;
UNS n;
{
     s = s ? realloc(s, n) : malloc(n);
     if (!s)
	  fatal(E_NOMEM);
     return s;
}

static
#ifdef VARARGS
VOID fatal(va_alist) va_dcl
#else
VOID fatal(int errnum,...)
#endif
{
#ifdef VARARGS
     int errnum;
#endif
     va_list ap;
     
#ifdef VARARGS
     va_start(ap);
     errnum = va_arg(ap, int);
#else
     va_start(ap, errnum);
#endif
     do_error(errnum, ap);
     va_end(ap);
     exit(EXIT_FAILURE);
}

#ifdef VARARGS
VOID appl_error(va_alist) va_dcl
#else
VOID appl_error(int errnum,...)
#endif
{
#ifdef VARARGS
     int errnum;
#endif
     va_list ap;
     
#ifdef VARARGS
     va_start(ap);
     errnum = va_arg(ap, int);
#else
     va_start(ap, errnum);
#endif
     do_error(errnum, ap);
     va_end(ap);
}

static
VOID do_error(errnum, ap)
int errnum;
va_list ap;
{
     char *text;
     fprintf(stderr, "%s: ", prog);
     assert(errnum > 0);
     assert(errnum < sizeof(errlist)/sizeof(errlist[0]));
     text = catgets(catd, APP_SET, errnum, errlist[errnum]);
     assert(text != 0);
     xvfprintf(stderr, text, ap);
     fputc('\n', stderr);
     fflush(stderr);
}

/*
Local Variables:
c-indent-level: 5
c-continued-statement-offset: 5
c-brace-offset: -5
c-argdecl-indent: 0
c-label-offset: -5
comment-column: 30
End:
*/
