/* automatic.{cc,hh} -- code for automatic mode and interfacing with kpathsea
 *
 * Copyright (c) 2003-2014 Eddie Kohler
 *
 * This program 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. This program 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.
 */

#include <config.h>
#include "automatic.hh"
#include "kpseinterface.h"
#include "util.hh"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <sys/stat.h>
#include <sys/types.h>
#if HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#if HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#include <lcdf/error.hh>
#include <lcdf/straccum.hh>
#if HAVE_FCNTL_H
# include <fcntl.h>
#endif
#include <algorithm>

#ifdef WIN32
#ifdef _MSC_VER
# include <io.h>
# include <direct.h>
#endif
# define mkdir(dir, access) mkdir(dir)
# define COPY_CMD "copy"
# define CMD_SEP "&"
#else
# define COPY_CMD "cp"
# define CMD_SEP ";"
#endif

/* kpathsea may already have defined this */
#ifndef DEV_NULL
# ifdef WIN32
#  define DEV_NULL "NUL"
# else
#  define DEV_NULL "/dev/null"
# endif
#endif

#if HAVE_AUTO_T1DOTLESSJ
enum { T1DOTLESSJ_EXIT_J_NODOT = 2 };
#endif

static String odir[NUMODIR];
static String typeface;
static String vendor;
static String map_file;
#define DEFAULT_VENDOR "lcdftools"
#define DEFAULT_TYPEFACE "unknown"

static const struct {
    const char *name;
    const char *envvar;
    const char *texdir;
} odir_info[] = {
    { "encoding", "ENCODINGDESTDIR", "#fonts/enc/dvips/@#dvips/@" },
    { "TFM", "TFMDESTDIR", "fonts/tfm/%" },
    { "PL", "PLDESTDIR", "fonts/pl/%" },
    { "VF", "VFDESTDIR", "fonts/vf/%" },
    { "VPL", "VPLDESTDIR", "fonts/vpl/%" },
    { "Type 1", "T1DESTDIR", "fonts/type1/%" },
    { "DVIPS map", "DVIPS directory", "#fonts/map/dvips/@#dvips/@" },
    { "DVIPS updmap", "DVIPS directory", "dvips" },
    { "TrueType", "TTFDESTDIR", "fonts/truetype/%" },
    { "OpenType", "OPENTYPEDESTDIR", "fonts/opentype/%" },
    { "Type 42", "T42DESTDIR", "fonts/type42/%" }
};

#if HAVE_KPATHSEA
static String odir_kpathsea[NUMODIR];

static bool writable_texdir_tried = false;
static String writable_texdir;	// always ends with directory separator
static int tds_1_1 = -1;

static bool mktexupd_tried = false;
static String mktexupd;

static String
kpsei_string(char* x)
{
    String s(x);
    free((void*)x);
    return s;
}

static void
look_for_writable_texdir(const char *path_variable, bool create)
{
    String path = kpsei_string(kpsei_path_expand(path_variable));
    while (path && !writable_texdir) {
	const char* colon = std::find(path.begin(), path.end(), kpsei_env_sep_char);
	String texdir = path.substring(path.begin(), colon);
	path = path.substring(colon + 1, path.end());
	if (access(texdir.c_str(), W_OK) >= 0)
	    writable_texdir = texdir;
	else if (create && errno != EACCES && mkdir(texdir.c_str(), 0777) >= 0)
	    // create file if it doesn't exist already
	    writable_texdir = texdir;
    }
    if (writable_texdir && writable_texdir.back() != '/')
	writable_texdir += "/";
}

static void
find_writable_texdir(ErrorHandler *errh, const char *)
{
#if defined(W32TEX)
// W32TeX does not have TEXMFVAR
    char *p = kpsei_var_value("TEXMFVAR");
    if (p == NULL) // W32TeX
        look_for_writable_texdir("$TEXMFLOCAL", true);
    else { // TeXLive
        free (p);
        look_for_writable_texdir("$TEXMFVAR", true);
    }
#else
    look_for_writable_texdir("$TEXMFVAR", true);
#endif
    if (!writable_texdir)
	look_for_writable_texdir("$VARTEXMF", false);
    if (!writable_texdir)
	look_for_writable_texdir("$TEXMF", false);
    if (!writable_texdir) {
	errh->warning("no writable directory found in $TEXMFVAR or $TEXMF");
	errh->message("(You probably need to set your TEXMF environment variable; see\n\
the manual for more information. The current TEXMF path is\n\
%<%s%>.)", kpsei_string(kpsei_path_expand("$TEXMF")).c_str());
    }
    writable_texdir_tried = true;
}

static String
get_vendor()
{
    return (vendor ? vendor : String(DEFAULT_VENDOR));
}

static String
get_typeface()
{
    return (typeface ? typeface : String(DEFAULT_TYPEFACE));
}
#endif

bool
set_vendor(const String &s)
{
    bool had = (bool) vendor;
    vendor = s;
    return !had;
}

bool
set_typeface(const String &s, bool override)
{
    bool had = (bool) typeface;
    if (!had || override)
	typeface = s;
    return !had;
}

String
getodir(int o, ErrorHandler *errh)
{
    assert(o >= 0 && o < NUMODIR);

    if (!odir[o] && automatic && odir_info[o].envvar)
	odir[o] = getenv(odir_info[o].envvar);

#if HAVE_KPATHSEA
    if (!odir[o] && automatic && !writable_texdir_tried)
	find_writable_texdir(errh, odir_info[o].name);

    if (!odir[o] && automatic && writable_texdir) {
	String suffix = odir_info[o].texdir;

	// May need to behave differently on TDS 1.1 rather than TDS 1.0.
	if (suffix[0] == '#') {
	    // check type of TDS
	    if (tds_1_1 < 0) {
		// using a procedure suggested by Olaf Weber
		String encfonts = kpsei_string(kpsei_path_expand("$TEXMFMAIN/fonts/enc"));
		if (!encfonts)
		    encfonts = kpsei_string(kpsei_path_expand("$TEXMFDIST/fonts/enc"));
		tds_1_1 = (encfonts != String());
	    }
	    if (tds_1_1 == 0)
		suffix = suffix.substring(std::find(suffix.begin() + 1, suffix.end(), '#') + 1, suffix.end());
	    else
		suffix = suffix.substring(suffix.begin() + 1, std::find(suffix.begin() + 1, suffix.end(), '#'));
	}

	String dir = writable_texdir + suffix;

	if (dir.back() == '%')
	    dir = dir.substring(0, -1) + get_vendor() + "/" + get_typeface();
	else if (dir.back() == '@')
	    dir = dir.substring(0, -1) + get_vendor();

	// create parent directories as appropriate
	int slash = writable_texdir.length() - 1;
	while (access(dir.c_str(), F_OK) < 0 && slash < dir.length()) {
	    if ((slash = dir.find_left('/', slash + 1)) < 0)
		slash = dir.length();
	    String subdir = dir.substring(0, slash);
	    if (access(subdir.c_str(), F_OK) < 0
		&& !no_create
		&& mkdir(subdir.c_str(), 0777) < 0)
		goto kpathsea_done;
	}

	// that's our answer
	odir[o] = dir;
	odir_kpathsea[o] = dir;
    }
  kpathsea_done:
#endif

    if (!odir[o]) {
	if (automatic) {
	    errh->warning("%s not specified, placing %s files in %<.%>", odir_info[o].envvar, odir_info[o].name);
#if !HAVE_KPATHSEA
	    static int kpathsea_warning = 0;
	    if (++kpathsea_warning == 1)
		errh->message("(This version of otftotfm lacks $TEXMF directory support.)");
#endif
	}
	odir[o] = ".";
    }

    while (odir[o].length() && odir[o].back() == '/')
	odir[o] = odir[o].substring(0, -1);

    if (verbose)
	errh->message("placing %s files in %<%s%>", odir_info[o].name, odir[o].c_str());
    return odir[o];
}

bool
setodir(int o, const String &value)
{
    assert(o >= 0 && o < NUMODIR);
    bool had = (bool) odir[o];
    odir[o] = value;
    return !had;
}

const char *
odirname(int o)
{
    assert(o >= 0 && o < NUMODIR);
    return odir_info[o].name;
}

#if HAVE_KPATHSEA
static bool
file_in_kpathsea_odir(int o, const String &file)
{
    return odir_kpathsea[o]
	&& file.length() > odir[o].length()
	&& memcmp(file.data(), odir[o].data(), odir[o].length()) == 0
	&& file[odir[o].length()] == '/';
}
#endif

void
update_odir(int o, String file, ErrorHandler *errh)
{
    assert(o >= 0 && o < NUMODIR);
#if HAVE_KPATHSEA
    if (file.find_left('/') < 0)
	file = odir[o] + "/" + file;

    // exit if this directory was not found via kpathsea, or the file is not
    // in the kpathsea directory
    if (!file_in_kpathsea_odir(o, file))
	return;

    assert(writable_texdir && writable_texdir.length() <= odir[o].length()
	   && memcmp(file.data(), writable_texdir.data(), writable_texdir.length()) == 0);

    // divide the filename into portions
    // file == writable_texdir + directory + file
    file = file.substring(writable_texdir.length());
    while (file && file[0] == '/')
	file = file.substring(1);
    int last_slash = file.find_right('/');
    String directory = (last_slash >= 0 ? file.substring(0, last_slash) : String());
    file = file.substring(last_slash >= 0 ? last_slash + 1 : 0);
    if (!file)			// no filename to update
	return;

    // return if nocreate
    if (no_create) {
	errh->message("would update %sls-R for %s/%s", writable_texdir.c_str(), directory.c_str(), file.c_str());
	return;
    } else if (verbose)
	errh->message("updating %sls-R for %s/%s", writable_texdir.c_str(), directory.c_str(), file.c_str());

    // try to update ls-R ourselves, rather than running mktexupd --
    // mktexupd's runtime is painful: a half second to update a file
    String ls_r = writable_texdir + "ls-R";
    bool success = false;
    if (access(ls_r.c_str(), R_OK) >= 0) // make sure it already exists
	if (FILE *f = fopen(ls_r.c_str(), "ab")) {
	    fprintf(f, "./%s:\n%s\n", directory.c_str(), file.c_str());
	    success = true;
	    fclose(f);
	}

    // otherwise, run mktexupd
    if (!success && writable_texdir.find_left('\'') < 0 && directory.find_left('\'') < 0 && file.find_left('\'') < 0) {
	// look for mktexupd script
	if (!mktexupd_tried) {
#ifdef WIN32
	    mktexupd = "mktexupd";
#else
	    mktexupd = kpsei_string(kpsei_find_file("mktexupd", KPSEI_FMT_WEB2C));
#endif
	    mktexupd_tried = true;
	}

	// run script
	if (mktexupd) {
	    String command = mktexupd + " " + shell_quote(writable_texdir + directory) + " " + shell_quote(file);
	    int retval = system(command.c_str());
	    if (retval == 127)
		errh->error("could not run %<%s%>", command.c_str());
	    else if (retval < 0)
		errh->error("could not run %<%s%>: %s", command.c_str(), strerror(errno));
	    else if (retval != 0)
		errh->error("%<%s%> failed", command.c_str());
	}
    }
#else
    (void) file, (void) errh;
#endif
}

bool
set_map_file(const String &s)
{
    bool had = (bool) map_file;
    map_file = s;
    return !had;
}

String
installed_type1(const String &otf_filename, const String &ps_fontname, bool allow_generate, ErrorHandler *errh)
{
    (void) otf_filename, (void) allow_generate, (void) errh;

    if (!ps_fontname)
	return String();

#if HAVE_KPATHSEA
# if HAVE_AUTO_CFFTOT1
    if (!(force && allow_generate && otf_filename && otf_filename != "-" && getodir(O_TYPE1, errh))) {
# endif
	// look for .pfb and .pfa
	String file, path;
	if ((file = ps_fontname + ".pfb", path = kpsei_string(kpsei_find_file(file.c_str(), KPSEI_FMT_TYPE1)))
	    || (file = ps_fontname + ".pfa", path = kpsei_string(kpsei_find_file(file.c_str(), KPSEI_FMT_TYPE1)))) {
	    if (path == "./" + file || path == file) {
		if (verbose)
		    errh->message("ignoring Type 1 file %s found with kpathsea in %<.%>", path.c_str());
	    } else {
		if (verbose)
		    errh->message("Type 1 file %s found with kpathsea at %s", file.c_str(), path.c_str());
		return path;
	    }
	}
# if HAVE_AUTO_CFFTOT1
    }
# endif
#endif

#if HAVE_AUTO_CFFTOT1
    // if not found, and can generate on the fly, run cfftot1
    if (allow_generate && otf_filename && otf_filename != "-" && getodir(O_TYPE1, errh)) {
	String pfb_filename = odir[O_TYPE1] + "/" + ps_fontname + ".pfb";
	if (pfb_filename.find_left('\'') >= 0 || otf_filename.find_left('\'') >= 0)
	    return String();
	String command = "cfftot1 " + shell_quote(otf_filename) + " -n " + shell_quote(ps_fontname) + " " + shell_quote(pfb_filename);
	int retval = mysystem(command.c_str(), errh);
	if (retval == 127)
	    errh->error("could not run %<%s%>", command.c_str());
	else if (retval < 0)
	    errh->error("could not run %<%s%>: %s", command.c_str(), strerror(errno));
	else if (retval != 0)
	    errh->error("%<%s%> failed", command.c_str());
	if (retval == 0) {
	    update_odir(O_TYPE1, pfb_filename, errh);
	    return pfb_filename;
	}
    }
#endif

    return String();
}

String
installed_type1_dotlessj(const String &otf_filename, const String &ps_fontname, bool allow_generate, ErrorHandler *errh)
{
    (void) otf_filename, (void) allow_generate, (void) errh;

    if (!ps_fontname)
	return String();
    if (verbose)
	errh->message("searching for dotless-j font for %s", ps_fontname.c_str());

    String j_ps_fontname = ps_fontname + "LCDFJ";

#if HAVE_KPATHSEA
# if HAVE_AUTO_T1DOTLESSJ
    if (!(force && allow_generate && getodir(O_TYPE1, errh))) {
# endif
	// look for existing .pfb or .pfa
	String file, path;
	if ((file = j_ps_fontname + ".pfb", path = kpsei_string(kpsei_find_file(file.c_str(), KPSEI_FMT_TYPE1)))
	    || (file = j_ps_fontname + ".pfa", path = kpsei_string(kpsei_find_file(file.c_str(), KPSEI_FMT_TYPE1)))) {
	    // ignore versions in the current directory
	    if (path == "./" + file || path == file) {
		if (verbose)
		    errh->message("ignoring Type 1 file %s found with kpathsea in %<.%>", path.c_str());
	    } else {
		if (verbose)
		    errh->message("Type 1 file %s found with kpathsea at %s", file.c_str(), path.c_str());
		return path;
	    }
	}
# if HAVE_AUTO_T1DOTLESSJ
    }
# endif
#endif

#if HAVE_AUTO_T1DOTLESSJ
    // if not found, and can generate on the fly, try running t1dotlessj
    if (allow_generate && getodir(O_TYPE1, errh)) {
	if (String base_filename = installed_type1(otf_filename, ps_fontname, allow_generate, errh)) {
	    String pfb_filename = odir[O_TYPE1] + "/" + j_ps_fontname + ".pfb";
	    if (pfb_filename.find_left('\'') >= 0 || base_filename.find_left('\'') >= 0)
		return String();
	    String command = "t1dotlessj " + shell_quote(base_filename) + " -n " + shell_quote(j_ps_fontname) + " " + shell_quote(pfb_filename);
	    int retval = mysystem(command.c_str(), errh);
	    if (retval == 127)
		errh->warning("could not run %<%s%>", command.c_str());
	    else if (retval < 0)
		errh->warning("could not run %<%s%>: %s", command.c_str(), strerror(errno));
	    else if (WEXITSTATUS(retval) == T1DOTLESSJ_EXIT_J_NODOT)
		return String("\0", 1);
	    else if (retval != 0)
		errh->warning("%<%s%> failed (%d)", command.c_str(), retval);
	    if (retval == 0) {
		update_odir(O_TYPE1, pfb_filename, errh);
		return pfb_filename;
	    } else
		errh->warning("output font will not contain a dotless-j character");
	}
    }
#endif

    return String();
}

String
installed_truetype(const String &ttf_filename, bool allow_generate, ErrorHandler *errh)
{
    String file = pathname_filename(ttf_filename);

#if HAVE_KPATHSEA
    if (!(force && allow_generate && ttf_filename && ttf_filename != "-" && getodir(O_TRUETYPE, errh))) {
	if (String path = kpsei_string(kpsei_find_file(file.c_str(), KPSEI_FMT_TRUETYPE))) {
	    if (path == "./" + file || path == file) {
		if (verbose)
		    errh->message("ignoring TrueType file %s found with kpathsea in %<.%>", path.c_str());
	    } else {
		if (verbose)
		    errh->message("TrueType file %s found with kpathsea at %s", file.c_str(), path.c_str());
		return path;
	    }
	}
    }
#endif

    // perhaps generate type 42 in the future, for now just copy
    if (allow_generate && ttf_filename && ttf_filename != "-" && getodir(O_TRUETYPE, errh)) {
	String installed_ttf_filename = odir[O_TRUETYPE] + "/" + file;
	if (installed_ttf_filename.find_left('\'') >= 0 || installed_ttf_filename.find_left('\"') >= 0)
	    return String();

	int retval;
	if (!same_filename(ttf_filename, installed_ttf_filename)) {
	    String command = COPY_CMD " " + shell_quote(ttf_filename) + " " + shell_quote(installed_ttf_filename);
	    retval = mysystem(command.c_str(), errh);
	    if (retval == 127)
		errh->error("could not run %<%s%>", command.c_str());
	    else if (retval < 0)
		errh->error("could not run %<%s%>: %s", command.c_str(), strerror(errno));
	    else if (retval != 0)
		errh->error("%<%s%> failed", command.c_str());
	} else {
	    if (verbose)
		errh->message("TrueType file %s already located in output directory", installed_ttf_filename.c_str());
	    retval = 0;
	}

	if (retval == 0) {
	    update_odir(O_TRUETYPE, installed_ttf_filename, errh);
	    return installed_ttf_filename;
	}
    }

    return String();
}

String
installed_type42(const String &ttf_filename, const String &ps_fontname, bool allow_generate, ErrorHandler *errh)
{
    (void) allow_generate, (void) ttf_filename, (void) errh;

    if (!ps_fontname)
	return String();

#if HAVE_KPATHSEA
# if HAVE_AUTO_TTFTOTYPE42
    if (!(force && allow_generate && ttf_filename && ttf_filename != "-" && getodir(O_TYPE42, errh))) {
# endif
	// look for .pfb and .pfa
	String file, path;
	if ((file = ps_fontname + ".t42", path = kpsei_string(kpsei_find_file(file.c_str(), KPSEI_FMT_TYPE42)))) {
	    if (path == "./" + file || path == file) {
		if (verbose)
		    errh->message("ignoring Type 42 file %s found with kpathsea in %<.%>", path.c_str());
	    } else {
		if (verbose)
		    errh->message("Type 42 file %s found with kpathsea at %s", file.c_str(), path.c_str());
		return path;
	    }
	}
# if HAVE_AUTO_TTFTOTYPE42
    }
# endif
#endif

#if HAVE_AUTO_TTFTOTYPE42
    // if not found, and can generate on the fly, run ttftotype42
    if (allow_generate && ttf_filename && ttf_filename != "-" && getodir(O_TYPE42, errh)) {
	String t42_filename = odir[O_TYPE42] + "/" + ps_fontname + ".t42";
	if (t42_filename.find_left('\'') >= 0 || ttf_filename.find_left('\'') >= 0)
	    return String();
	String command = "ttftotype42 " + shell_quote(ttf_filename) + " " + shell_quote(t42_filename);
	int retval = mysystem(command.c_str(), errh);
	if (retval == 127)
	    errh->error("could not run %<%s%>", command.c_str());
	else if (retval < 0)
	    errh->error("could not run %<%s%>: %s", command.c_str(), strerror(errno));
	else if (retval != 0)
	    errh->error("%<%s%> failed", command.c_str());
	if (retval == 0) {
	    update_odir(O_TYPE42, t42_filename, errh);
	    return t42_filename;
	}
    }
#endif

    return String();
}

int
update_autofont_map(const String &fontname, String mapline, ErrorHandler *errh)
{
#if HAVE_KPATHSEA
    if (automatic && !map_file && getodir(O_MAP, errh))
	map_file = odir[O_MAP] + "/" + get_vendor() + ".map";
#endif

    if (map_file == "" || map_file == "-")
	fputs(mapline.c_str(), stdout);
    else {
	// report no_create/verbose
	if (no_create) {
	    errh->message("would update %s for %s", map_file.c_str(), String(fontname).c_str());
	    return 0;
	} else if (verbose)
	    errh->message("updating %s for %s", map_file.c_str(), String(fontname).c_str());

	int fd = open(map_file.c_str(), O_RDWR | O_CREAT, 0666);
	if (fd < 0)
	    return errh->lerror(map_file, "%s", strerror(errno));
	FILE *f = fdopen(fd, "r+");
	// NB: also change encoding logic if you change this code

#if defined(F_SETLKW) && defined(HAVE_FTRUNCATE)
	{
	    struct flock lock;
	    lock.l_type = F_WRLCK;
	    lock.l_whence = SEEK_SET;
	    lock.l_start = 0;
	    lock.l_len = 0;
	    int result;
	    while ((result = fcntl(fd, F_SETLKW, &lock)) < 0 && errno == EINTR)
		/* try again */;
	    if (result < 0) {
		result = errno;
		fclose(f);
		return errh->error("locking %s: %s", map_file.c_str(), strerror(result));
	    }
	}
#endif

	// read old data from map file
	StringAccum sa;
	int amt;
	do {
	    if (char *x = sa.reserve(8192)) {
		amt = fread(x, 1, 8192, f);
		sa.adjust_length(amt);
	    } else
		amt = 0;
	} while (amt != 0);
	if (!feof(f))
	    return errh->error("%s: %s", map_file.c_str(), strerror(errno));
	String text = sa.take_string();

	// add comment if necessary
	bool created = (!text);
	if (created)
	    text = "% Automatically maintained by otftotfm or other programs. Do not edit.\n\n";
	if (text.back() != '\n')
	    text += "\n";

	// append old encodings
	int fl = 0;
	int nl = text.find_left('\n') + 1;
	bool changed = created;
	while (fl < text.length()) {
	    if (fl + fontname.length() + 1 < nl
		&& memcmp(text.data() + fl, fontname.data(), fontname.length()) == 0
		&& text[fl + fontname.length()] == ' ') {
		// found the old name
		if (text.substring(fl, nl - fl) == mapline) {
		    // duplicate of old name, don't change it
		    fclose(f);
		    if (verbose)
			errh->message("%s unchanged", map_file.c_str());
		    return 0;
		} else {
		    text = text.substring(0, fl) + text.substring(nl);
		    nl = fl;
		    changed = true;
		}
	    }
	    fl = nl;
	    nl = text.find_left('\n', fl) + 1;
	}

	if (!mapline && !changed) {
            // special case: empty mapline, unchanged file
	    if (verbose)
		errh->message("%s unchanged", map_file.c_str());
	} else {
            // add our text
            text += mapline;

            // rewind file
#if HAVE_FTRUNCATE
            rewind(f);
            if (ftruncate(fd, 0) < 0)
#endif
            {
                fclose(f);
                f = fopen(map_file.c_str(), "wb");
                fd = fileno(f);
            }

            // write data
            ignore_result(fwrite(text.data(), 1, text.length(), f));
        }

	fclose(f);

	// inform about the new file if necessary
	if (created)
	    update_odir(O_MAP, map_file, errh);

#if HAVE_KPATHSEA && !WIN32
	// run 'updmap' if present
	String updmap_dir, updmap_file;
	if (automatic && (output_flags & G_UPDMAP))
	    updmap_dir = getodir(O_MAP_PARENT, errh);
	if (updmap_dir && (updmap_file = updmap_dir + "/updmap")
	    && access(updmap_file.c_str(), X_OK) >= 0) {
	    // want to run 'updmap' from its directory, can't use system()
	    if (verbose)
		errh->message("running %s", updmap_file.c_str());

	    pid_t child = fork();
	    if (child < 0)
		errh->fatal("%s during fork", strerror(errno));
	    else if (child == 0) {
		// change to updmap directory, run it
		if (chdir(updmap_dir.c_str()) < 0)
		    errh->fatal("%s: %s during chdir", updmap_dir.c_str(), strerror(errno));
		if (execl("./updmap", updmap_file.c_str(), (const char*) 0) < 0)
		    errh->fatal("%s: %s during exec", updmap_file.c_str(), strerror(errno));
		exit(1);	// should never get here
	    }

# if HAVE_WAITPID
	    // wait for updmap to finish
	    int status;
	    while (1) {
		pid_t answer = waitpid(child, &status, 0);
		if (answer >= 0)
		    break;
		else if (errno != EINTR)
		    errh->fatal("%s during wait", strerror(errno));
	    }
	    if (!WIFEXITED(status))
		errh->warning("%s exited abnormally", updmap_file.c_str());
	    else if (WEXITSTATUS(status) != 0)
		errh->warning("%s exited with status %d", updmap_file.c_str(), WEXITSTATUS(status));
# else
#  error "need waitpid() support: report this bug to the maintainer"
# endif
	    goto ran_updmap;
	}

# if HAVE_AUTO_UPDMAP
	// run system updmap
	if (output_flags & G_UPDMAP) {
	    String filename = map_file;
	    int slash = filename.find_right('/');
	    if (slash >= 0)
		filename = filename.substring(slash + 1);
            String redirect = verbose ? " 1>&2" : " >" DEV_NULL " 2>&1";
#if defined(W32TEX)
// jtex_filetype is defined only in W32TeX
	    char *p = kpsei_var_value("jtex_filetype");
	    if (p != NULL) { // W32TeX
		free(p);
		String option = "--add ";
	    } else // TeXLive
#endif
	    String option = "--enable Map ";
	    String command = "updmap --nomkmap " + option  + shell_quote(filename) + redirect
                + CMD_SEP " updmap" + redirect;
	    int retval = mysystem(command.c_str(), errh);
	    if (retval == 127)
		errh->warning("could not run %<%s%>", command.c_str());
	    else if (retval < 0)
		errh->warning("could not run %<%s%>: %s", command.c_str(), strerror(errno));
	    else if (retval != 0)
		errh->warning("%<%s%> exited with status %d;\nrun it manually to check for errors", command.c_str(), WEXITSTATUS(retval));
	    goto ran_updmap;
	}
# endif

	if (verbose)
	    errh->message("not running updmap");

      ran_updmap: ;
#endif
    }

    return 0;
}

String
locate_encoding(String encfile, ErrorHandler *errh, bool literal)
{
    if (!encfile || encfile == "-")
	return encfile;

    if (!literal) {
	int slash = encfile.find_right('/');
	int dot = encfile.find_left('.', slash >= 0 ? slash : 0);
	if (dot < 0)
	    if (String file = locate_encoding(encfile + ".enc", errh, true))
		return file;
    }

#if HAVE_KPATHSEA
    if (String file = kpsei_string(kpsei_find_file(encfile.c_str(), KPSEI_FMT_ENCODING))) {
	if (verbose)
	    errh->message("encoding file %s found with kpathsea at %s", encfile.c_str(), file.c_str());
	return file;
    } else if (verbose)
	errh->message("encoding file %s not found with kpathsea", encfile.c_str());
#endif

    if (access(encfile.c_str(), R_OK) >= 0)
	return encfile;
    else
	return String();
}
