/* tex-make.c: run external programs to make TeX-related files.

  Copyright 1993, 1994, 1995, 1996, 1997, 2008, 2009, 2010 Karl Berry.
  Copyright 1997, 1998, 2001-05 Olaf Weber.

  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, see <http://www.gnu.org/licenses/>.  */

#include <kpathsea/config.h>

#include <kpathsea/c-fopen.h>
#include <kpathsea/c-pathch.h>
#include <kpathsea/db.h>
#include <kpathsea/fn.h>
#include <kpathsea/magstep.h>
#include <kpathsea/readable.h>
#include <kpathsea/tex-make.h>
#include <kpathsea/variable.h>
#if defined(WIN32) && !defined(__MINGW32__)
#include <kpathsea/win32lib.h>
#endif

#if !defined (AMIGA) && !(defined (MSDOS) && !defined(__DJGPP__)) && !defined (WIN32)
#include <sys/wait.h>
#endif


/* We set the envvar MAKETEX_MAG, which is part of the default spec for
  MakeTeXPK above, based on KPATHSEA_DPI and MAKETEX_BASE_DPI.  */

static void
set_maketex_mag (kpathsea kpse)
{
 char q[MAX_INT_LENGTH * 3 + 3];
 int m;
 string dpi_str = getenv ("KPATHSEA_DPI");
 string bdpi_str = getenv ("MAKETEX_BASE_DPI");
 unsigned dpi = dpi_str ? atoi (dpi_str) : 0;
 unsigned bdpi = bdpi_str ? atoi (bdpi_str) : 0;

 /* If the environment variables aren't set, it's a bug.  */
 assert (dpi != 0 && bdpi != 0);

 /* Fix up for roundoff error.  Hopefully the driver has already fixed
    up DPI, but may as well be safe, and also get the magstep number.  */
 (void) kpathsea_magstep_fix (kpse, dpi, bdpi, &m);

 if (m == 0) {
     if (bdpi <= 4000) {
         sprintf(q, "%u+%u/%u", dpi / bdpi, dpi % bdpi, bdpi);
     } else {
         unsigned f = bdpi/4000;
         unsigned r = bdpi%4000;

         if (f > 1) {
             if (r > 0) {
                 sprintf(q, "%u+%u/(%u*%u+%u)",
                         dpi/bdpi, dpi%bdpi, f, (bdpi - r)/f, r);
             } else {
                 sprintf(q, "%u+%u/(%u*%u)", dpi/bdpi, dpi%bdpi, f, bdpi/f);
             }
         } else {
             sprintf(q, "%u+%u/(4000+%u)", dpi/bdpi, dpi%bdpi, r);
         }
     }
 } else {
     /* m is encoded with LSB being a ``half'' bit (see magstep.h).  Are
        we making an assumption here about two's complement?  Probably.
        In any case, if m is negative, we have to put in the sign
        explicitly, since m/2==0 if m==-1.  */
     const_string sign = "";
     if (m < 0) {
         m *= -1;
         sign = "-";
     }
     sprintf(q, "magstep\\(%s%d.%d\\)", sign, m / 2, (m & 1) * 5);
 }
 kpathsea_xputenv (kpse, "MAKETEX_MAG", q);
}

/* This mktex... program was disabled, or the script failed.  If this
  was a font creation (according to FORMAT), append CMD
  to a file missfont.log in the current directory.  */

static void
misstex (kpathsea kpse, kpse_file_format_type format,  string *args)
{
 string *s;

 /* If we weren't trying to make a font, do nothing.  Maybe should
    allow people to specify what they want recorded?  */
 if (format != kpse_gf_format
     && format != kpse_pk_format
     && format != kpse_any_glyph_format
     && format != kpse_tfm_format
     && format != kpse_vf_format)
   return;

 /* If this is the first time, have to open the log file.  But don't
    bother logging anything if they were discarding errors.  */
 if (!kpse->missfont && !kpse->make_tex_discard_errors) {
   const_string missfont_name = kpathsea_var_value (kpse, "MISSFONT_LOG");
   if (!missfont_name || *missfont_name == '1') {
     missfont_name = "missfont.log"; /* take default name */
   } else if (missfont_name
              && (*missfont_name == 0 || *missfont_name == '0')) {
     missfont_name = NULL; /* user requested no missfont.log */
   } /* else use user's name */

   kpse->missfont
     = missfont_name ? fopen (missfont_name, FOPEN_A_MODE) : NULL;
   if (!kpse->missfont && kpathsea_var_value (kpse, "TEXMFOUTPUT")) {
     missfont_name = concat3 (kpathsea_var_value (kpse, "TEXMFOUTPUT"),
                              DIR_SEP_STRING, missfont_name);
     kpse->missfont = fopen (missfont_name, FOPEN_A_MODE);
   }

   if (kpse->missfont)
     fprintf (stderr, "kpathsea: Appending font creation commands to %s.\n",
              missfont_name);
 }

 /* Write the command if we have a log file.  */
 if (kpse->missfont) {
   fputs (args[0], kpse->missfont);
   for (s = &args[1]; *s != NULL; s++) {
     putc(' ', kpse->missfont);
     fputs (*s, kpse->missfont);
   }
   putc ('\n', kpse->missfont);
 }
}


/* Assume the script outputs the filename it creates (and nothing
  else) on standard output; hence, we run the script with `popen'.  */

static string
maketex (kpathsea kpse, kpse_file_format_type format, string* args)
{
 /* New implementation, use fork/exec pair instead of popen, since
  * the latter is virtually impossible to make safe.
  */
 unsigned len;
 string *s;
 string ret = NULL;
 string fn;
#if defined(WIN32)
 char   fullbin[256], *wrp;

 wrp = kpathsea_var_value(kpse, "SELFAUTOLOC");
 if(wrp == NULL) {
    fprintf(stderr, "I cannot get SELFAUTOLOC\n");
    exit(100);
 }

 strcpy(fullbin, wrp);
 free(wrp);
 for(wrp=fullbin; *wrp; wrp++) {
    if(*wrp == '/') *wrp = '\\';
 }
 strcat(fullbin, "\\");
 strcat(fullbin, args[0]);
#endif
 if (!kpse->make_tex_discard_errors) {
   fprintf (stderr, "\nkpathsea: Running");
   for (s = &args[0]; *s != NULL; s++)
     fprintf (stderr, " %s", *s);
   fputc('\n', stderr);
 }

#if defined (AMIGA)
 /* Amiga has a different interface. */
 {
   string cmd;
   string newcmd;
   cmd = xstrdup(args[0]);
   for (s = &args[1];  *s != NULL; s++) {
     newcmd = concat(cmd, *s);
     free (cmd);
     cmd = newcmd;
   }
   ret = system(cmd) == 0 ? getenv ("LAST_FONT_CREATED"): NULL;
   free (cmd);
 }
#elif defined (MSDOS) && !defined(__DJGPP__)
#error Implement new MSDOS mktex call interface here
#else /* WIN32 or Unix */
 {
#if defined (WIN32)
   /* spawnvp(_P_NOWAIT, ...) and pipe --ak 2002/12/15 */

   unsigned long nexitcode = STILL_ACTIVE;
   HANDLE hchild;
   int hstdout, childpipe[2];
   int hstderr = -1;
   FILE *Hnul = NULL;

   fn = NULL;

   if(_pipe(childpipe, 1024, O_TEXT | _O_NOINHERIT) == -1) {
     perror("kpathsea: pipe()");
     goto labeldone;
   }

   hstdout = _dup(fileno(stdout));
   if(_dup2(childpipe[1], fileno(stdout)) != 0) {
     close(hstdout);
     close(childpipe[0]);
     close(childpipe[1]);
     goto labeldone;
   }

   close(childpipe[1]);

   if(kpse->make_tex_discard_errors) {
     Hnul = fopen("nul", "w");
     if(!Hnul) {
       perror("kpathsea: fopen(\"nul\")");
     }
     else {
       hstderr = _dup(fileno(stderr));
       _dup2(fileno(Hnul), fileno(stderr));
     }
   }
   fprintf(stderr, "\nThe command name is %s\n", fullbin);
   hchild = (HANDLE)spawnvp(_P_NOWAIT, fullbin, (const char * const *) args);

   _dup2(hstdout, fileno(stdout));
   close(hstdout);

   if((int)hchild == -1) {
     close(childpipe[0]);
     goto labeldone;
   }

   if(hchild) {
     char buf[1024+1];
     int num;

     fn = xstrdup("");
     while(nexitcode == STILL_ACTIVE) {
       num = read(childpipe[0], buf, sizeof(buf)-1);
       if(num) {
         string newfn;
         buf[num] = '\0';
         newfn = concat(fn, buf);
         free(fn);
         fn = newfn;
       }
       if(!GetExitCodeProcess(hchild, &nexitcode)) {
         fn = NULL;
         close(childpipe[0]);
         goto labeldone;
       }
     }
     close(childpipe[0]);
   }

labeldone:
   if(kpse->make_tex_discard_errors && Hnul) {
      _dup2(hstderr, fileno(stderr));
      close(hstderr);
      fclose(Hnul);
   }
#else /* !WIN32 */
   /* Standard input for the child.  Set to /dev/null */
   int childin;
   /* Standard output for the child, what we're interested in. */
   int childout[2];
   /* Standard error for the child, same as parent or /dev/null */
   int childerr;
   /* Child pid. */
   pid_t childpid;

   /* Open the channels that the child will use. */
   /* A fairly horrible uses of gotos for here for the error case. */
   if ((childin = open("/dev/null", O_RDONLY)) < 0) {
     perror("kpathsea: open(\"/dev/null\", O_RDONLY)");
     goto error_childin;
   }
   if (pipe(childout) < 0) {
     perror("kpathsea: pipe()");
     goto error_childout;
   }
   if ((childerr = open("/dev/null", O_WRONLY)) < 0) {
     perror("kpathsea: open(\"/dev/null\", O_WRONLY)");
     goto error_childerr;
   }
   if ((childpid = fork()) < 0) {
     perror("kpathsea: fork()");
     close(childerr);
    error_childerr:
     close(childout[0]);
     close(childout[1]);
    error_childout:
     close(childin);
    error_childin:
     fn = NULL;
   } else if (childpid == 0) {
     /* Child
      *
      * We can use vfork, provided we're careful about what we
      * do here: do not return from this function, do not modify
      * variables, call _exit if there is a problem.
      *
      * Complete setting up the file descriptors.
      * We use dup(2) so the order in which we do this matters.
      */
     close(childout[0]);
     /* stdin -- the child will not receive input from this */
     if (childin != 0) {
       close(0);
       dup(childin);
       close(childin);
     }
     /* stdout -- the output of the child's action */
     if (childout[1] != 1) {
       close(1);
       dup(childout[1]);
       close(childout[1]);
     }
     /* stderr -- use /dev/null if we discard errors */
     if (childerr != 2) {
       if (kpse->make_tex_discard_errors) {
         close(2);
         dup(childerr);
       }
       close(childerr);
     }
     /* FIXME: We could/should close all other file descriptors as well. */
     /* exec -- on failure a call of _exit(2) it is the only option */
     if (execvp(args[0], args))
       perror(args[0]);
     _exit(1);
   } else {
     /* Parent */
     char buf[1024+1];
     int num;

     /* Clean up child file descriptors that we won't use anyway. */
     close(childin);
     close(childout[1]);
     close(childerr);
     /* Get stdout of child from the pipe. */
     fn = xstrdup("");
     while ((num = read(childout[0],buf,sizeof(buf)-1)) != 0) {
       if (num == -1) {
         if (errno != EINTR) {
           perror("kpathsea: read()");
           break;
         }
       } else {
         string newfn;
         buf[num] = '\0';
         newfn = concat(fn, buf);
         free(fn);
         fn = newfn;
       }
     }
     /* End of file on pipe, child should have exited at this point. */
     close(childout[0]);
     /* We don't really care about the exit status at this point. */
     wait(NULL);
   }
#endif /* !WIN32 */

   if (fn) {
     len = strlen(fn);

     /* Remove trailing newlines and returns.  */
     while (len && (fn[len - 1] == '\n' || fn[len - 1] == '\r')) {
       fn[len - 1] = '\0';
       len--;
     }

     ret = len == 0 ? NULL : kpathsea_readable_file (kpse, fn);
     if (!ret && len > 1) {
       WARNING2("kpathsea: %s output `%s' instead of a filename",
                args[0], fn);
     }

     /* Free the name if we're not returning it.  */
     if (fn != ret)
       free (fn);
   }
 }
#endif /* WIN32 or Unix */

 if (ret == NULL)
     misstex (kpse, format, args);
 else
     kpathsea_db_insert (kpse, ret);

 return ret;
}


/* Create BASE in FORMAT and return the generated filename, or
  return NULL.  */

string
kpathsea_make_tex (kpathsea kpse, kpse_file_format_type format,
                  const_string base)
{
 kpse_format_info_type spec; /* some compilers lack struct initialization */
 string ret = NULL;

 spec = kpse->format_info[format];
 if (!spec.type) { /* Not initialized yet? */
   kpathsea_init_format (kpse, format);
   spec = kpse->format_info[format];
 }

 if (spec.program && spec.program_enabled_p) {
   /* See the documentation for the envvars we're dealing with here.  */
   /* Number of arguments is spec.argc + 1, plus the trailing NULL. */
   string *args = XTALLOC (spec.argc + 2, string);
   /* Helpers */
   int argnum;
   int i;

   /* FIXME
    * Check whether the name we were given is likely to be a problem.
    * Right now we err on the side of strictness:
    * - may not start with a hyphen (fixable in the scripts).
    * - allowed are: alphanumeric, underscore, hyphen, period, plus
    * ? also allowed DIRSEP, as we can be fed that when creating pk fonts
    * No doubt some possibilities were overlooked.
    */
   if (base[0] == '-' /* || IS_DIR_SEP(base[0])  */) {
     fprintf(stderr, "kpathsea: Invalid fontname `%s', starts with '%c'\n",
             base, base[0]);
     return NULL;
   }
   for (i = 0; base[i]; i++) {
     if (!ISALNUM(base[i])
         && base[i] != '-'
         && base[i] != '+'
         && base[i] != '_'
         && base[i] != '.'
         && !IS_DIR_SEP(base[i]))
     {
       fprintf(stderr, "kpathsea: Invalid fontname `%s', contains '%c'\n",
               base, base[i]);
       return NULL;
     }
   }

   if (format == kpse_gf_format
       || format == kpse_pk_format
       || format == kpse_any_glyph_format)
     set_maketex_mag (kpse);

   /* Here's an awful kludge: if the mode is `/', mktexpk recognizes
      it as a special case.  `kpse_prog_init' sets it to this in the
      first place when no mode is otherwise specified; this is so
      when the user defines a resolution, they don't also have to
      specify a mode; instead, mktexpk's guesses will take over.
      They use / for the value because then when it is expanded as
      part of the PKFONTS et al. path values, we'll wind up searching
      all the pk directories.  We put $MAKETEX_MODE in the path
      values in the first place so that sites with two different
      devices with the same resolution can find the right fonts; but
      such sites are uncommon, so they shouldn't make things harder
      for everyone else.  */
   for (argnum = 0; argnum < spec.argc; argnum++) {
       args[argnum] = kpathsea_var_expand (kpse, spec.argv[argnum]);
   }
   args[argnum++] = xstrdup(base);
   args[argnum] = NULL;

   ret = maketex (kpse, format, args);

   for (argnum = 0; args[argnum] != NULL; argnum++)
     free (args[argnum]);
   free (args);
 }

 return ret;
}

#if defined (KPSE_COMPAT_API)
string
kpse_make_tex (kpse_file_format_type format,  const_string base)
{
 return kpathsea_make_tex (kpse_def, format, base);
}
#endif


#ifdef TEST

void
test_make_tex (kpathsea kpse, kpse_file_format_type fmt, const_string base)
{
 string answer;

 printf ("\nAttempting %s in format %d:\n", base, fmt);

 answer = kpathsea_make_tex (kpse, fmt, base);
 puts (answer ? answer : "(nil)");
}


int
main (int argc, char **argv)
{
 kpathsea kpse = xcalloc(1, sizeof(kpathsea_instance));
 kpathsea_set_program_name(kpse, argv[0], NULL);
 kpathsea_xputenv (kpse, "KPATHSEA_DPI", "781"); /* call mktexpk */
 kpathsea_xputenv (kpse,"MAKETEX_BASE_DPI", "300"); /* call mktexpk */
 kpathsea_set_program_enabled(kpse, kpse_pk_format, 1, kpse_src_env);
 test_make_tex (kpse, kpse_pk_format, "cmr10");

 /* Fail with mktextfm.  */
 kpathsea_set_program_enabled(kpse, kpse_tfm_format, 1, kpse_src_env);
 test_make_tex (kpse, kpse_tfm_format, "foozler99");

 /* Call something disabled.  */
 test_make_tex (kpse, kpse_bst_format, "no-way");

 return 0;
}

#endif /* TEST */


/*
Local variables:
standalone-compile-command: "gcc -g -I. -I.. -DTEST tex-make.c kpathsea.a"
End:
*/