/* Copyright 1989-2024 Free Software Foundation, Inc.
     Written by James Clark (jjc@jclark.com)

This file is part of groff, the GNU roff typesetting system.

groff 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 3 of the License, or
(at your option) any later version.

groff 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.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>. */

// A front end for GNU troff.

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <assert.h>
#include <errno.h>
#include <stdio.h> // EOF, FILE, fflush(), setbuf(), stderr, stdout
#include <stdlib.h> // exit(), EXIT_SUCCESS, free(), getenv(), putenv()
#include <string.h> // strerror(), strsignal()

#include <getopt.h> // getopt_long()
//
// TODO: operating system services proper (cf. the standard C/C++
// runtime and libraries) should be abstracted through posix.h/
// nonposix.h (if gnulib doesn't handle it for us).
#include <sys/types.h> // pid_t
#include <signal.h> // kill()

// needed for close(), dup(), execvp(), _exit(), fork(), pipe(), wait()
#include "posix.h"
#include "nonposix.h"

#include "lib.h"

#include "errarg.h"
#include "error.h"
#include "stringclass.h"
#include "cset.h"
#include "font.h"
#include "device.h"
#include "pipeline.h"
#include "relocate.h"
#include "defs.h"

#define GXDITVIEW "gxditview"

// troff will be passed an argument of -rXREG=1 if the -X option is
// specified
#define XREG ".X"

// The number of commands must be in sync with MAX_COMMANDS in
// pipeline.h.

// grap, chem, and ideal must come before pic;
// tbl must come before eqn
const int PRECONV_INDEX = 0;
const int SOELIM_INDEX = PRECONV_INDEX + 1;
const int REFER_INDEX = SOELIM_INDEX + 1;
const int GRAP_INDEX = REFER_INDEX + 1;
const int CHEM_INDEX = GRAP_INDEX + 1;
const int IDEAL_INDEX = CHEM_INDEX + 1;
const int PIC_INDEX = IDEAL_INDEX + 1;
const int TBL_INDEX = PIC_INDEX + 1;
const int GRN_INDEX = TBL_INDEX + 1;
const int EQN_INDEX = GRN_INDEX + 1;
const int TROFF_INDEX = EQN_INDEX + 1;
const int POST_INDEX = TROFF_INDEX + 1;
const int SPOOL_INDEX = POST_INDEX + 1;

const int NCOMMANDS = SPOOL_INDEX + 1;

class possible_command {
  char *name;
  string args;
  char **argv;

  void build_argv();
public:
  possible_command();
  ~possible_command();
  void clear_name();
  void set_name(const char *);
  void set_name(const char *, const char *);
  const char *get_name();
  void append_arg(const char *, const char * = 0 /* nullptr */);
  void insert_arg(const char *);
  void insert_args(string s);
  void clear_args();
  char **get_argv();
  void print(int is_last, FILE *fp);
};

extern "C" const char *Version_string;

int lflag = 0;
char *spooler = 0 /* nullptr */;
char *postdriver = 0 /* nullptr */;
char *predriver = 0 /* nullptr */;
bool need_postdriver = true;
char *saved_path = 0 /* nullptr */;
char *groff_bin_path = 0 /* nullptr */;
char *groff_font_path = 0 /* nullptr */;

possible_command commands[NCOMMANDS];

int run_commands(bool no_pipe);
void print_commands(FILE *);
void append_arg_to_string(const char *arg, string &str);
void handle_unknown_desc_command(const char *command, const char *arg,
				 const char *filename, int lineno);
const char *xbasename(const char *);

void usage(FILE *stream);

static char *xstrdup(const char *s) {
  if (0 /* nullptr */ == s)
    return const_cast<char *>(s);
  char *str = strdup(s);
  if (0 /* nullptr */ == str)
    fatal("unable to copy string: %1", strerror(errno));
  return str;
}

static void xputenv(const char *s) {
  if (putenv(const_cast<char *>(s)) != 0)
    fatal("cannot update process environment: %1", strerror(errno));
  return;
}

static void xexit(int status) {
  free(spooler);
  free(predriver);
  free(postdriver);
  free(saved_path);
  free(groff_bin_path);
  free(groff_font_path);
  exit(status);
}

int main(int argc, char **argv)
{
  program_name = argv[0];
  static char stderr_buf[BUFSIZ];
  setbuf(stderr, stderr_buf);
  assert(NCOMMANDS <= MAX_COMMANDS);
  string Pargs, Largs, Fargs;
  int Kflag = 0;
  bool want_version_info = false;
  int Vflag = 0;
  int zflag = 0;
  int iflag = 0;
  int Xflag = 0;
  int oflag = 0;
  bool is_safer_mode_locked = false; // made true if `-S` explicit
  bool want_unsafe_mode = false;
  int is_xhtml = 0;
  int eflag = 0;
  int need_pic = 0;
  int opt;
  const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
  const char *encoding = getenv("GROFF_ENCODING");
  if (!command_prefix)
    command_prefix = PROG_PREFIX;
  commands[TROFF_INDEX].set_name(command_prefix, "troff");
  static const struct option long_options[] = {
    { "help", no_argument, 0 /* nullptr */, 'h' },
    { "version", no_argument, 0 /* nullptr */, 'v' },
    { 0 /* nullptr */, 0, 0 /* nullptr */, 0 }
  };
  while ((opt = getopt_long(argc, argv,
			    ":abcCd:D:eEf:F:gGhiI:jJkK:lL:m:M:"
			    "n:No:pP:r:RsStT:UvVw:W:XzZ",
			    long_options, 0 /* nullptr */))
	 != EOF) {
    char buf[3];
    buf[0] = '-';
    buf[1] = opt;
    buf[2] = '\0';
    switch (opt) {
    case 'i':
      iflag = 1;
      break;
    case 'I':
      commands[GRN_INDEX].set_name(command_prefix, "grn");
      commands[GRN_INDEX].append_arg("-M", optarg);
      commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
      commands[SOELIM_INDEX].append_arg(buf, optarg);
      // .psbb may need to search for files
      commands[TROFF_INDEX].append_arg(buf, optarg);
      // \X'ps:import' may need to search for files
      Pargs += buf;
      Pargs += optarg;
      Pargs += '\0';
      break;
    case 'D':
      commands[PRECONV_INDEX].set_name("preconv");
      commands[PRECONV_INDEX].append_arg("-D", optarg);
      break;
    case 'K':
      commands[PRECONV_INDEX].append_arg("-e", optarg);
      Kflag = 1;
      // fall through
    case 'k':
      commands[PRECONV_INDEX].set_name("preconv");
      break;
    case 't':
      commands[TBL_INDEX].set_name(command_prefix, "tbl");
      break;
    case 'J':
      // commands[IDEAL_INDEX].set_name(command_prefix, "gideal");
      // need_pic = 1;
      break;
    case 'j':
      commands[CHEM_INDEX].set_name(command_prefix, "chem");
      need_pic = 1;
      break;
    case 'p':
      commands[PIC_INDEX].set_name(command_prefix, "pic");
      break;
    case 'g':
      commands[GRN_INDEX].set_name(command_prefix, "grn");
      break;
    case 'G':
      commands[GRAP_INDEX].set_name(command_prefix, "grap");
      need_pic = 1;
      break;
    case 'e':
      eflag = 1;
      commands[EQN_INDEX].set_name(command_prefix, "eqn");
      break;
    case 's':
      commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
      break;
    case 'R':
      commands[REFER_INDEX].set_name(command_prefix, "refer");
      break;
    case 'z':
    case 'a':
      commands[TROFF_INDEX].append_arg(buf);
      // fall through
    case 'Z':
      zflag++;
      need_postdriver = false;
      break;
    case 'l':
      lflag++;
      break;
    case 'V':
      Vflag++;
      break;
    case 'v':
      want_version_info = true;
      printf("GNU groff version %s\n", Version_string);
      puts(
"Copyright 1989-2026 Free Software Foundation, Inc. and others\n"
"\n"
"This is free software, distributed under the terms of the GNU General"
" Public\n"
"License, version 3, or any later version, at your option.  There is NO"
" warranty;\n"
"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
"\n"
"See the \"COPYING\", \"FDL\", and \"LICENSES\" files in the groff"
" distribution or\n"
"package for additional notices and permissions."
      );
      puts("\nprograms in constructed pipeline:\n");
      fflush(stdout);
      // Pass -v to all possible subprograms
      commands[PRECONV_INDEX].append_arg(buf);
      commands[CHEM_INDEX].append_arg(buf);
      commands[IDEAL_INDEX].append_arg(buf);
      commands[POST_INDEX].append_arg(buf);
      // fall through
    case 'C':
      commands[SOELIM_INDEX].append_arg(buf);
      commands[REFER_INDEX].append_arg(buf);
      commands[PIC_INDEX].append_arg(buf);
      commands[GRAP_INDEX].append_arg(buf);
      commands[TBL_INDEX].append_arg(buf);
      commands[GRN_INDEX].append_arg(buf);
      commands[EQN_INDEX].append_arg(buf);
      commands[TROFF_INDEX].append_arg(buf);
      break;
    case 'N':
      commands[EQN_INDEX].append_arg(buf);
      break;
    case 'h':
      usage(stdout);
      exit(EXIT_SUCCESS);
      break;
    case 'E':
    case 'b':
      commands[TROFF_INDEX].append_arg(buf);
      break;
    case 'c':
      commands[TROFF_INDEX].append_arg(buf);
      break;
    case 'S':
      is_safer_mode_locked = true;
      want_unsafe_mode = false;
      break;
    case 'U':
      if (is_safer_mode_locked)
	warning("ignoring '-U' option; '-S' already specified");
      else
	want_unsafe_mode = true;
      break;
    case 'T':
      if (strcmp(optarg, "xhtml") == 0) {
	// force soelim to aid the html preprocessor
	commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
	Pargs += "-x";
	Pargs += '\0';
	Pargs += 'x';
	Pargs += '\0';
	is_xhtml = 1;
	device = "html";
	break;
      }
      if (strcmp(optarg, "html") == 0)
	// force soelim to aid the html preprocessor
	commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
      if (strcmp(optarg, "Xps") == 0) {
	warning("-TXps option is obsolete: use -X -Tps instead");
	device = "ps";
	Xflag++;
      }
      else
	device = optarg;
      break;
    case 'F':
      font::command_line_font_dir(optarg);
      if (Fargs.length() > 0) {
	Fargs += PATH_SEP_CHAR;
	Fargs += optarg;
      }
      else
	Fargs = optarg;
      break;
    case 'o':
      oflag = 1;
      // fall through
    case 'f':
    case 'm':
    case 'r':
    case 'd':
    case 'n':
    case 'w':
    case 'W':
      commands[TROFF_INDEX].append_arg(buf, optarg);
      break;
    case 'M':
      commands[EQN_INDEX].append_arg(buf, optarg);
      commands[GRAP_INDEX].append_arg(buf, optarg);
      commands[GRN_INDEX].append_arg(buf, optarg);
      commands[TROFF_INDEX].append_arg(buf, optarg);
      break;
    case 'P':
      Pargs += optarg;
      Pargs += '\0';
      break;
    case 'L':
      append_arg_to_string(optarg, Largs);
      break;
    case 'X':
      Xflag++;
      need_postdriver = false;
      break;
    case '?':
      if (optopt != 0)
	error("unrecognized command-line option '%1'", char(optopt));
      else
	error("unrecognized command-line option '%1'",
	      argv[(optind - 1)]);
      usage(stderr);
      xexit(2);
      break;
    case ':':
      error("command-line option '%1' requires an argument",
	    char(optopt));
      usage(stderr);
      xexit(2);
      break;
    default:
      assert(0 == "no case to handle option character");
      break;
    }
  }
  if (need_pic)
    commands[PIC_INDEX].set_name(command_prefix, "pic");
  if (encoding) {
    commands[PRECONV_INDEX].set_name("preconv");
    if (!Kflag && *encoding)
      commands[PRECONV_INDEX].append_arg("-e", encoding);
  }
  if (is_safer_mode_locked) {
    commands[TROFF_INDEX].insert_arg("-S");
    commands[PIC_INDEX].append_arg("-S");
  }
  else if (want_unsafe_mode) {
    commands[TROFF_INDEX].insert_arg("-U");
    commands[PIC_INDEX].append_arg("-U");
  }
  font::set_unknown_desc_command_handler(handle_unknown_desc_command);
  const char *desc = font::load_desc();
  if (0 /* nullptr */ == desc)
    fatal("cannot load 'DESC' description file for device '%1'",
	  device);
  if (need_postdriver && (0 /* nullptr */ == postdriver))
    fatal_with_file_and_line(desc, 0, "device description file missing"
			     " 'postpro' directive");
  if (predriver && !zflag) {
    commands[TROFF_INDEX].insert_arg(commands[TROFF_INDEX].get_name());
    commands[TROFF_INDEX].insert_arg("--");
    commands[TROFF_INDEX].set_name(predriver);
    // pass the device arguments to the predrivers as well
    commands[TROFF_INDEX].insert_args(Pargs);
    if (eflag && is_xhtml)
      commands[TROFF_INDEX].insert_arg("-e");
    if (want_version_info)
      commands[TROFF_INDEX].insert_arg("-v");
  }
  const char *real_driver = 0 /* nullptr */;
  if (Xflag) {
    real_driver = postdriver;
    postdriver = xstrdup(GXDITVIEW); // so we can free() it in xexit()
    commands[TROFF_INDEX].append_arg("-r" XREG "=", "1");
  }
  if (postdriver)
    commands[POST_INDEX].set_name(postdriver);
  int gxditview_flag = postdriver
		       && strcmp(xbasename(postdriver), GXDITVIEW) == 0;
  if (gxditview_flag && argc - optind == 1) {
    commands[POST_INDEX].append_arg("-title");
    commands[POST_INDEX].append_arg(argv[optind]);
    commands[POST_INDEX].append_arg("-xrm");
    commands[POST_INDEX].append_arg("*iconName:", argv[optind]);
    string filename_string("|");
    append_arg_to_string(argv[0], filename_string);
    append_arg_to_string("-Z", filename_string);
    for (int i = 1; i < argc; i++)
      append_arg_to_string(argv[i], filename_string);
    filename_string += '\0';
    commands[POST_INDEX].append_arg("-filename");
    commands[POST_INDEX].append_arg(filename_string.contents());
  }
  if (gxditview_flag && Xflag) {
    string print_string(real_driver);
    if (spooler) {
      print_string += " | ";
      print_string += spooler;
      print_string += Largs;
    }
    print_string += '\0';
    commands[POST_INDEX].append_arg("-printCommand");
    commands[POST_INDEX].append_arg(print_string.contents());
  }
  const char *p = Pargs.contents();
  const char *end = p + Pargs.length();
  while (p < end) {
    commands[POST_INDEX].append_arg(p);
    p = strchr(p, '\0') + 1;
  }
  if (gxditview_flag)
    commands[POST_INDEX].append_arg("-");
  if (lflag && !want_version_info && !Xflag && spooler) {
    commands[SPOOL_INDEX].set_name(BSHELL);
    commands[SPOOL_INDEX].append_arg(BSHELL_DASH_C);
    Largs += '\0';
    Largs = spooler + Largs;
    commands[SPOOL_INDEX].append_arg(Largs.contents());
  }
  if (zflag) {
    commands[POST_INDEX].set_name(0 /* nullptr */);
    commands[SPOOL_INDEX].set_name(0 /* nullptr */);
  }
  commands[TROFF_INDEX].append_arg("-T", device);
  if (strcmp(device, "html") == 0) {
    if (is_xhtml) {
      if (oflag)
	fatal("'-o' option is invalid with device 'xhtml'");
      if (zflag)
	commands[EQN_INDEX].append_arg("-Tmathml:xhtml");
      else if (eflag)
	commands[EQN_INDEX].clear_name();
    }
    else {
      if (oflag)
	fatal("'-o' option is invalid with device 'html'");
      // html renders equations as images via ps
      commands[EQN_INDEX].append_arg("-Tps:html");
    }
  }
  else
    commands[EQN_INDEX].append_arg("-T", device);

  commands[GRN_INDEX].append_arg("-T", device);

  int first_index;
  for (first_index = 0; first_index < TROFF_INDEX; first_index++)
    if (commands[first_index].get_name() != 0 /* nullptr */)
      break;
  if (optind < argc) {
    if (argv[optind][0] == '-' && argv[optind][1] != '\0')
      commands[first_index].append_arg("--");
    for (int i = optind; i < argc; i++)
      commands[first_index].append_arg(argv[i]);
    if (iflag)
      commands[first_index].append_arg("-");
  }
  if (Fargs.length() > 0) {
    string e = "GROFF_FONT_PATH";
    e += '=';
    e += Fargs;
    char *fontpath = getenv("GROFF_FONT_PATH");
    if (fontpath && *fontpath) {
      e += PATH_SEP_CHAR;
      e += fontpath;
    }
    e += '\0';
    groff_font_path = xstrdup(e.contents());
    xputenv(groff_font_path);
  }
  {
    // we save the original path in GROFF_PATH__ and put it into the
    // environment -- troff will pick it up later.
    char *path = getenv("PATH");
    string g = "GROFF_PATH__";
    g += '=';
    if (path && *path)
      g += path;
    g += '\0';
    saved_path = xstrdup(g.contents());
    xputenv(saved_path);
    char *binpath = getenv("GROFF_BIN_PATH");
    string f = "PATH";
    f += '=';
    if (binpath && *binpath)
      f += binpath;
    else {
      binpath = relocatep(BINPATH);
      f += binpath;
    }
    if (path && *path) {
      f += PATH_SEP_CHAR;
      f += path;
    }
    f += '\0';
    groff_bin_path = xstrdup(f.contents());
    xputenv(groff_bin_path);
  }
  if (Vflag)
    print_commands(Vflag == 1 ? stdout : stderr);
  if (Vflag == 1)
    xexit(EXIT_SUCCESS);
  // We need the lower two bits of the exit status for ourselves.
  int status = run_commands(want_version_info) << 2;
  assert(status < 65 || 0 == "run_commands() returned too many bits");
  xexit(status);
}

const char *xbasename(const char *s)
{
  if (!s)
    return 0 /* nullptr */;
  // DIR_SEPS[] are possible directory separator characters; see
  // nonposix.h.  We want the rightmost separator of all possible ones.
  // Example: d:/foo\\bar.
  const char *p = strrchr(s, DIR_SEPS[0]), *p1;
  const char *sep = &DIR_SEPS[1];

  while (*sep)
    {
      p1 = strrchr(s, *sep);
      if (p1 && (!p || p1 > p))
	p = p1;
      sep++;
    }
  return p ? p + 1 : s;
}

void handle_unknown_desc_command(const char *command, const char *arg,
				 const char *filename, int lineno)
{
  if (strcmp(command, "print") == 0) {
    if (arg == 0 /* nullptr */)
      error_with_file_and_line(filename, lineno, "'print' directive"
			       " requires an argument");
    else
      spooler = xstrdup(arg);
    return;
  }
  if (strcmp(command, "prepro") == 0) {
    if (arg == 0 /* nullptr */)
      error("'prepro' directive requires an argument");
    else {
      for (const char *p = arg; *p; p++)
	if (csspace(*p)) {
	  error_with_file_and_line(filename, lineno, "invalid 'prepro'"
				   " directive argument '%1': program"
				   " name required", arg);
	}
      predriver = xstrdup(arg);
    }
    return;
  }
  if (strcmp(command, "postpro") == 0) {
    if (arg == 0 /* nullptr */)
      error_with_file_and_line(filename, lineno, "'postpro' directive"
			       " requires an argument");
    else {
      for (const char *p = arg; *p; p++)
	if (csspace(*p)) {
	  error_with_file_and_line(filename, lineno, "invalid 'postpro'"
				   " directive argument '%1': program"
				   " name required", arg);
	  return;
	}
      postdriver = xstrdup(arg);
    }
    return;
  }
}

void print_commands(FILE *fp)
{
  int last;
  for (last = SPOOL_INDEX; last >= 0; last--)
    if (commands[last].get_name() != 0 /* nullptr */)
      break;
  for (int i = 0; i <= last; i++)
    if (commands[i].get_name() != 0 /* nullptr */)
      commands[i].print(i == last, fp);
}

// Run the commands. Return the code with which to exit.

int run_commands(bool no_pipe)
{
  char **v[NCOMMANDS]; // vector of argv arrays to pipe together
  int ncommands = 0;
  for (int i = 0; i < NCOMMANDS; i++)
    if (commands[i].get_name() != 0 /* nullptr */)
      v[ncommands++] = commands[i].get_argv();
  return run_pipeline(ncommands, v, no_pipe);
}

possible_command::possible_command()
: name(0), argv(0)
{
}

possible_command::~possible_command()
{
  free(name);
  delete[] argv;
}

void possible_command::set_name(const char *s)
{
  free(name);
  name = xstrdup(s);
}

void possible_command::clear_name()
{
  delete[] name;
  delete[] argv;
  name = NULL;
  argv = NULL;
}

void possible_command::set_name(const char *s1, const char *s2)
{
  free(name);
  name = (char*)malloc(strlen(s1) + strlen(s2) + 1);
  strcpy(name, s1);
  strcat(name, s2);
}

const char *possible_command::get_name()
{
  return name;
}

void possible_command::clear_args()
{
  args.clear();
}

void possible_command::append_arg(const char *s, const char *t)
{
  args += s;
  if (t)
    args += t;
  args += '\0';
}

void possible_command::insert_arg(const char *s)
{
  string str(s);
  str += '\0';
  str += args;
  args = str;
}

void possible_command::insert_args(string s)
{
  const char *p = s.contents();
  const char *end = p + s.length();
  int l = 0;
  if (p >= end)
    return;
  // find the total number of arguments in our string
  do {
    l++;
    p = strchr(p, '\0') + 1;
  } while (p < end);
  // now insert each argument preserving the order
  for (int i = l - 1; i >= 0; i--) {
    p = s.contents();
    for (int j = 0; j < i; j++)
      p = strchr(p, '\0') + 1;
    insert_arg(p);
  }
}

void possible_command::build_argv()
{
  if (argv)
    return;
  // Count the number of arguments.
  int len = args.length();
  int argc = 1;
  char *p = 0 /* nullptr */;
  if (len > 0) {
    p = &args[0];
    for (int i = 0; i < len; i++)
      if (p[i] == '\0')
	argc++;
  }
  // Build an argument vector.
  argv = new char *[argc + 1];
  argv[0] = name;
  for (int i = 1; i < argc; i++) {
    argv[i] = p;
    p = strchr(p, '\0') + 1;
  }
  argv[argc] = 0 /* nullptr */;
}

void possible_command::print(int is_last, FILE *fp)
{
  build_argv();
  if (IS_BSHELL(argv[0])
      && argv[1] != 0 /* nullptr */
      && strcmp(argv[1], BSHELL_DASH_C) == 0
      && argv[2] != 0 /* nullptr */ && argv[3] == 0 /* nullptr */)
    fputs(argv[2], fp);
  else {
    fputs(argv[0], fp);
    string str;
    for (int i = 1; argv[i] != 0 /* nullptr */; i++) {
      str.clear();
      append_arg_to_string(argv[i], str);
      put_string(str, fp);
    }
  }
  if (is_last)
    putc('\n', fp);
  else
    fputs(" | ", fp);
}

void append_arg_to_string(const char *arg, string &str)
{
  str += ' ';
  int needs_quoting = 0;
  // Native Windows programs don't support '..' style of quoting, so
  // always behave as if ARG included the single quote character.
#if defined(_WIN32) && !defined(__CYGWIN__)
  int contains_single_quote = 1;
#else
  int contains_single_quote = 0;
#endif
  const char*p;
  for (p = arg; *p != '\0'; p++)
    switch (*p) {
    case ';':
    case '&':
    case '(':
    case ')':
    case '|':
    case '^':
    case '<':
    case '>':
    case '\n':
    case ' ':
    case '\t':
    case '\\':
    case '"':
    case '$':
    case '?':
    case '*':
      needs_quoting = 1;
      break;
    case '\'':
      contains_single_quote = 1;
      break;
    }
  if (contains_single_quote || arg[0] == '\0') {
    str += '"';
    for (p = arg; *p != '\0'; p++)
      switch (*p) {
#if !(defined(_WIN32) && !defined(__CYGWIN__))
      case '"':
      case '\\':
      case '$':
	str += '\\';
#else
      case '"':
      case '\\':
	if (*p == '"' || (*p == '\\' && p[1] == '"'))
	  str += '\\';
#endif
	// fall through
      default:
	str += *p;
	break;
      }
    str += '"';
  }
  else if (needs_quoting) {
    str += '\'';
    str += arg;
    str += '\'';
  }
  else
    str += arg;
}

char **possible_command::get_argv()
{
  build_argv();
  return argv;
}

void usage(FILE *stream)
{
  // Add `J` to the cluster if we ever get ideal(1) support.
  fprintf(stream,
"usage: %s [-abcCeEgGijklNpRsStUVXzZ] [-d ctext] [-d string=text]"
" [-D fallback-encoding] [-f font-family] [-F font-directory]"
" [-I inclusion-directory] [-K input-encoding] [-L spooler-argument]"
" [-m macro-package] [-M macro-directory] [-n page-number]"
" [-o page-list] [-P postprocessor-argument] [-r cnumeric-expression]"
" [-r register=numeric-expression] [-T output-device]"
" [-w warning-category] [-W warning-category]"
" [file ...]\n"
"usage: %s {-v | --version}\n"
"usage: %s {-h | --help}\n",
	  program_name, program_name, program_name);
  if (stdout == stream)
    fputs("\n"
"groff (GNU roff) is a typesetting system that reads plain text input\n"
"files that include formatting commands to produce output in\n"
"PostScript, PDF, HTML, or DVI formats or for display to a terminal.\n"
"See the groff(1) manual page.\n",
	  stream);
}

extern "C" {

void c_error(const char *format, const char *arg1, const char *arg2,
	     const char *arg3)
{
  error(format, arg1, arg2, arg3);
}

void c_fatal(const char *format, const char *arg1, const char *arg2,
	     const char *arg3)
{
  fatal(format, arg1, arg2, arg3);
}

}

// Local Variables:
// fill-column: 72
// mode: C++
// End:
// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
