/* General purpose program for Checker
   Copyright 1994, 1995 Tristan Gingold
		  Written July 1994 by Tristan Gingold

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 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
General Public License for more details.

You should have received a copy of the GNU General Public License 
along with this program; see the file COPYING.  If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

The author may be reached by US/French mail:
		Tristan Gingold 
		8 rue Parmentier
		F-91120 PALAISEAU
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdarg.h>
#include <dirent.h>
#include "config.h"
#include "help.h"

/* Possible macros:
 *  HAVE_BFD: bfd facilities available.  bfd is used to interpret the output,
              to read the symbol table.
 *  GCC_COMPILE: Checker on linux-i386, your program must be compiled with a
              special as.
 *  GCCCHECKER: GCC-Checker, which use -fcheck-memory-usage
 		      and -fprefix-function-name
 *  SIMCHECKER: Checker on Solaris2-sparc.  */

/* If we can use BFD, use it!  */ 
#ifdef HAVE_BFD_H
#include <bfd.h>
#include <getopt.h>
#endif

/* If checker can run GCC, define NEED_GCC.  */
#if defined (GCC_COMPILE) || defined (GCCCHECKER)
#define NEED_GCC
#endif

/* Defined by version.c.  */
extern char *version;
extern char *target;

/* The maximum line length.  */
#define MAXLEN 2048

#ifdef NEED_GCC
/* Where gcc can be found.  */
#ifndef GCC_PATH
#define GCC_PATH (char *)0
#endif

/* Where g++ can be found.  */
#ifndef GXX_PATH
#define GXX_PATH (char *)0
#endif
#endif

/* Where the libraries of Checker are.  */
#ifndef CHECKER_LIB_PATH
#define CHECKER_LIB_PATH "/usr/local/lib/checker"
#endif

/* The basename of argv[0].  */
char *program_name;


/* Give some tips and exit.  */
void give_tips (void) __attribute__ ((noreturn));
void
give_tips (void)
{
  fprintf (stderr, "Try `%s --help' for more informations.\n", program_name);
  exit (1);
}

/* A clean way to disp error messages.  */
void fatal (const char *format, ...) __attribute__ ((noreturn));
void
fatal (const char *format, ...)
{
  va_list pvar;

  va_start (pvar, format);
  fprintf (stderr, "%s: ", program_name);
  vfprintf (stderr, format, pvar);
  va_end (pvar);
  exit (EXIT_FAILURE);
}

/* Allocate memory or failed.  */
void *
xmalloc (size_t size)
{
  void *res;
  res = malloc (size);
  if (!res)
    fatal ("virtual memory exhausted\n");
  return res;
}

/* Allocate memory or failed.  */
void *
xrealloc (void *ptr, size_t size)
{
  void *res;
  res = realloc (ptr, size);
  if (!res)
    fatal ("virtual memory exhausted\n");
  return res;
}

/* Display the current version.  */
void
disp_version (int argc, char *argv[])
{
  printf ("Checker %s (%s)\n", version, target);
}

/* Replace all occurences of `@' with the real directory.  */
char *
expand_filename (char *file)
{
  char *pos;
  static char *lib_path;
  static size_t lib_len;

  if (!lib_path)
    {
      /* Name of the real directory.  */
      lib_path = getenv ("CHECKER_LIB_DIR");
      if (!lib_path)
	lib_path = CHECKER_LIB_PATH;
      lib_len = strlen (lib_path);
    }
  
  /* Only one `@' can appear in a string.  */
  pos = strchr (file, '@');
  if (pos)
    {
      size_t offset;
      char *n;

      offset = pos - file;
      n = xmalloc (strlen (file) + lib_len);
      memcpy (n, file, offset);
      memcpy (n + offset, lib_path, lib_len);
      strcpy (n + offset + lib_len, pos + 1);
      return n;
    }
  else
    return file;
}

/* Display the known pathes.  */
void
disp_path (int argc, char *argv[])
{
  printf ("Paths are:\n");
  if (GXX_PATH)
    printf ("g++: %s\n", GXX_PATH);
  else
    printf ("g++ is the first g++ found in your path\n");
  if (GCC_PATH)
    printf ("gcc: %s\n", GCC_PATH);
  else
    printf ("gcc is the first gcc found in your path\n");
  printf ("default checkerlib: %s\n", CHECKER_LIB_PATH);
  printf ("current checkerlib: %s\n", expand_filename ("@"));
}


/* Display the help.  */
void
help (int argc, char *argv[])
{
  printf ("Usage: %s COMMAND [OPTIONS] [ARGUMENTS]\n", program_name);
  puts ("Options and arguments can be set according to the commands:\n");
  puts ("checker -v");
  puts ("checker --version");
  puts ("  output version information and exit. No options.\n");
  puts ("checker -h");
  puts ("checker --help");
  puts ("  display help and exit. No options.\n");
  puts ("checker -p");
  puts ("checker --path");
  puts ("  display built-in paths.\n");
#ifdef NEED_GCC
  puts ("checker gcc [GCC_ARGUMENTS]...");
  puts ("checker -gcc [GCC_ARGUMENTS]...");
  puts ("  compile with GNU CC for Checker.\n");
  puts ("checker g++ [G++_ARGUMENTS]...");
  puts ("checker -g++ [G++_ARGUMENTS]...");
  puts ("  compile with GNU G++ for Checker.\n");
  puts ("checker -l");
  puts ("checker --libs");
  puts ("  disp known libraries.\n");
#endif
#ifdef HAVE_BFD_H 
  puts ("checker -i [OPTION]... [program [file]]");
  puts ("checker --interpret [OPTION]... [program [file]]");
  puts ("  interpret the FILE (or stdin) produced by PROGRAM (or a.out).");
  puts ("  -o, --output OUTFILE  Set the output file.");
  puts ("  -r, --realpath        Give the real path.");
  puts ("  -b, --basename        Give the basename.");
#endif
#ifdef SIMCHECKER
  puts ("checker your_program [args]");
  puts ("  run your program through Checker.");
#endif
  puts (help_message);
}

#ifdef HAVE_BFD_H
/* Interpret a file:
   replace each line   "$       pc=0xAAAAAAAA" 
   by		       "        pc=0xAAAAAAAA in function() at file:line"
   The informations are taken from the binary file.  */
void
interpret (int argc, char *argv[])
{
  FILE *in = stdin;
  FILE *out = stdout;
  bfd *abfd;
  asection *textsection;
  unsigned int storage_needed;
  asymbol **symbol_table;
  unsigned int number_of_symbols;
  char *filename;
  char *target = NULL;
  char buffer[MAXLEN];
  bfd_vma offset;
  int i;
  char *srcname;
  char *srcname1;
  char *functionname;
  unsigned int line;
  
  static int path_flag = 0;
  static struct option interpret_opts[]={
    { "output", 1, 0, 'o'},
    { "realpath", 0, &path_flag, 1},
    { "basename", 0, &path_flag, 2},
    { 0, 0, 0, 0}};
  
  /* Getopt will emit a message in case of error. */ 
  opterr = 1;
 
  /* Read the arguments. */
  while (1)
    {
      i = getopt_long (argc, argv, "o:rb", interpret_opts, &i);
      if (i == -1)
	break;
      switch (i)
	{
	case 0:	/* 'realpath' & 'basename' */
	  break;
	case 'o':	/* -o: set the output file. */
	  if (strcmp ("-", optarg) == 0)
	    out = stdout;
	  else
	    {
	      out = fopen (optarg, "w");
	      if (out == NULL)
		{
		  fprintf (stderr, "%s: can't open %s", program_name, optarg);
		  perror ("");
		  exit (2);
		}
	    }
	  break;
	case 'r':	/* -r: give the realpath */
	  path_flag = 1;
	  break;
	case 'b':	/* -b: give the basename. */
	  path_flag = 2;
	  break;
	default:	/* error */
	  exit(2);
	}
   }
   
  /* PROGRAM can follow. */ 
  if (optind < argc)
    filename = argv[optind++];
  else
    filename = "a.out";	/* by default */
   
  /* FILE can follow. */
  if (optind < argc)
    {
      if (strcmp ("-", argv[optind]) == 0)
	in = stdin;
      else
	{
	  in = fopen (argv[optind], "r");
	  if (in == NULL)
	    {
	      fprintf (stderr,
		       "%s: can't open file %s", program_name, argv[optind]);
	      perror ("");
	      exit (2);
	    }
	}
      optind++;
    }
 
  if (optind != argc)
    {
      fprintf (stderr, "Too many arguments\n");
      give_tips ();
    }
  
  /* Open the PROGRAM file.  */    
  abfd = bfd_openr (filename, target);
  if (abfd == NULL)
    {
      fprintf (stderr, "%s: ", program_name);
      bfd_perror (filename);
      exit (2);
    }
  /* PROGRAM must be an executable.  */
  if (bfd_check_format (abfd, bfd_object) == false)
    {
      fprintf (stderr, "%s: %s is not an object file\n",
	       program_name, filename);
      exit (2);
    }
  
  /* Use the ".text" section.  */
  textsection = bfd_get_section_by_name (abfd, ".text");
  
  /* Read the symbol table.  */
  storage_needed = bfd_get_symtab_upper_bound (abfd);
  if (storage_needed == 0)
    fatal ("no symbols\n");
  symbol_table = (asymbol**) malloc (storage_needed);
  if (symbol_table == (asymbol**)0)
    fatal ("virtual memory exhausted\n");
  number_of_symbols = bfd_canonicalize_symtab (abfd, symbol_table);
  
  /* Read lines of the file.  */
  while (fgets (buffer, MAXLEN -2, in))
    {
      if (buffer[0] == '$')
	{
	  /* The format is: "$\tpc=0x%08x\n" */
	  /* Read the pc */
	  offset = 0;
	  for (i = 7; buffer[i] != '\n'; i++) 
	    {
	      offset <<= 4;	/*  *16  */
	      if (buffer[i] >= '0' && buffer[i] <= '9')
		offset += buffer[i] - '0';
	      else if (buffer[i] >= 'A' && buffer[i] <= 'F')
		offset += buffer[i] - 'A' + 10;
	      else if (buffer[i] >= 'a' && buffer[i] <= 'f')
		offset += buffer[i] - 'a' + 10;
	      else break;
	    }
	  
	  /* Find the symbols for offset.  */
	  if (bfd_find_nearest_line (abfd, textsection, symbol_table, offset,
				     &srcname, &functionname, &line) == false)
	    {
	      fprintf (stderr, "%s: ", program_name);
	      bfd_perror (filename);
	      exit (2);
	    }
	  /* The srcname can be processed.  */
	  switch (path_flag)
	    {
	    case 0:
	      srcname1 = srcname;
	      break;
	    case 1:
	      /* SRCNAME is processed only if it begins with a '/', since
		 it might not belong to the current directory.  */
	      if (srcname[0] == '/')
		{
		  /* Replace by the real path: follow the links, simplify.  */
		  realpath (srcname, buffer);
		  srcname1 = buffer;
		}
	      else
		srcname1 = srcname;
	      break;
	    case 2:
	      /* Replace by the basename.  */
	      srcname1 = srcname + strlen (srcname);
	      while (srcname1 != srcname && srcname1[-1] != '/')
		srcname1--;
	      break;
	    default:
	      srcname1 = srcname;
	    }
	  fprintf (out, "\tpc=0x%08x in %s() at %s:%d\n",
		   (unsigned int)offset, functionname, srcname1, line);
	}
      else
	{
	  /* Nothin to do. */
	  fputs (buffer, out);
	}
    }   
  
  /* Close the files.  */
  bfd_close (abfd);
  if (in != stdin)
    fclose (in);
  if (out != stdout)
    fclose (out);
  return;
}
#endif /* HAVE_BFD_H */


#ifdef NEED_GCC
/* When called with gcc, checker is a wrapper like g++.  It inserts args at
   the begining of the line command and libraries at the end.  */
#ifdef GCC_COMPILE
/* Note: the `@' is replaced with CHECKER_LIB_PATH or the value of the
   environment variable CHECKER_LIB_DIR.  This is mainly for testing
   purposes.  */
/* Args to insert, for gcc and g++.  */
char *added_args_for_gcc[] = { "-B@/", 0};
char *added_args_for_gpp[] = { "-B@/", 0};
char *added_libs_for_gcc[] = { 0};
char *added_libs_for_gxx[] = { 0};
#else
#ifdef GCCCHECKER
char *added_args_for_gcc[] = { 
	"-fcheck-memory-usage", "-fprefix-function-name",
#ifdef NEED_CHECKER_SPECS_FOR_GCC
	"-specs=@/specs",
	"-checker",
#endif
	"-g",
	"-I@/include",
	"-D__CHECKER__",
	"-DMALLOC_0_RETURNS_NULL",
	"-L@", 0};
char *added_args_for_gxx[] = { 
	"-fcheck-memory-usage", "-fprefix-function-name",
#ifdef NEED_CHECKER_SPECS_FOR_GCC
	"-specs=@/specs",
	"-checker",
#endif
	"-g",
	"-I@/include",
	"-D__CHECKER__",
	"-DMALLOC_0_RETURNS_NULL",
	"-L@", 0};
char *added_libs_for_gcc[] = { 0};
char *added_libs_for_gxx[] = { 0};
#endif
#endif


/* Replace all occurences of `@' with the real directory.  */
void
expand_path (char **tab)
{
  for (; *tab; tab++)
    *tab = expand_filename (*tab);
}

static int nbr_available_libs;
static int max_available_libs;
static char **available_libs;
/* The -v flag.  */
static int flag_verbose;

/* Read all the "libchkr_*.a" filename in the library directory.
   These filenames define stubs for system libraries, and must be added
   if such a system library is used.  */
void
read_available_stubs (void)
{
  int len;
  char *path;
  char *filename;
  DIR *dirp;
  struct dirent *direntp;

  path = expand_filename ("@/");
  
  /* Open the directory.  */
  dirp = opendir (path);
  if (!dirp)
    {
      fprintf (stderr, "can't open `%s': ", path);
      perror ("");
      fprintf (stderr, "\n");
      exit (1);
    }

  /* Read each entry.  */
  while ((direntp = readdir (dirp)) != NULL)
    {
      filename = direntp->d_name;
      if (strncmp (filename, "libchkr_", 8) != 0)
	continue;
      len = strlen (filename);
      if (filename[len - 1] != 'a' || filename[len - 2] != '.')
	continue;
      /* This is a stub library.  */
      /* Add it to the list, which can be expanded if too tiny.  */
      if (max_available_libs == 0)
	{
	  max_available_libs = 20;
	  available_libs =
	    (char **) xmalloc (max_available_libs * sizeof (char*));
	}
      else if (nbr_available_libs == max_available_libs)
	{
	  max_available_libs += 20;
	  available_libs = realloc (available_libs,
				    max_available_libs * sizeof (char *));
	}
      available_libs[nbr_available_libs] = xmalloc (len - 10);
      filename[len - 2] = 0;
      strcpy (available_libs[nbr_available_libs], filename + 8);
      nbr_available_libs++;
    }
  closedir (dirp);
}

void
disp_libs (int argc, char *argv[])
{
  int i;

  read_available_stubs ();
  printf ("known libraries are:");
  for (i = 0; i < nbr_available_libs; i++)
    printf (" %s", available_libs[i]);
  printf ("\n");
}

char **compiler_args;
int nbr_compiler_args;

void
set_compiler (const char *compiler_name)
{
  /* Compiler path.  */
  char *compiler;
  int max_compiler_args;
  char *tok;
  const char *ifs = " \t";

  compiler = strdup (compiler_name);
  tok = strtok (compiler, ifs);
  nbr_compiler_args = 0;
  max_compiler_args = 10;
  compiler_args = (char **) xmalloc (max_compiler_args * sizeof (char *));

  while (tok)
    {
      if (nbr_compiler_args == max_compiler_args)
	{
	  max_compiler_args *= 2;
	  compiler_args 
	    = (char **)xrealloc (compiler_args,
				 max_compiler_args * sizeof (char *));
	}
      compiler_args[nbr_compiler_args++] = strdup (tok);
      tok = strtok (NULL, ifs);
    }
  free (compiler);
}

/* Compile with gcc.  */
void
compile (char *added_args[], char *added_libs[], char *prog, char *prog1,
	 int argc, char *argv[])
{
  int i,j;

  /* The new argument array.  */
  char **newargv;

  /* The libraries with stubs.  */
  char **libs;
  int nlib;

  /* If true, libraries are inserted.  */
  int library = 1;

  /* Number of items in added_{args, libs}.  */
  int nbr_added_args;
  int nbr_added_libs;
  int nargc;

  if (prog)
    set_compiler (prog);
  else
    set_compiler (prog1);

  /* Compute the number of items.  */
  for (i = 0; added_args[i]; i++)
    ;
  nbr_added_args = i;

  for (i = 0; added_libs[i]; i++)
    ;
  nbr_added_libs = i;

  /* Alloc space for the array.  */
  newargv = (char **) alloca ((nbr_compiler_args + 2 * argc + nbr_added_args
			       + 2 * nbr_added_libs) * sizeof (char*));
  libs = (char **) alloca (argc * sizeof (char *));
  nlib = 0;

  /* Check if we have to insert the libraries.  */
  for (i = 0; i < argc; i++)
    if (argv[i][0] == '-')
      {
        /* The option -nostdlib, -c, -S, -E, -M and -MM prevent from linking.
           Note also that 'gcc -v' prevent also from linking.  */
        if (strcmp (argv[i], "nostdlib") == 0)
          library = 0;
        else if (argv[i][1] && argv[i][2] == 0)
          {
            if (strchr ("cSEM", argv[i][1]))
              library = 0;
            else if (argv[i][1] == 'v')
              {
                flag_verbose++;
                if (argc == 2)
                  library = 0;
              }
          }
        else if (strcmp (argv[i], "-MM") == 0)
          library = 0;
      }

  /* Don't had the library if gcc is invoked without any args.  */
  if (argc == 1)
    library = 0;

  if (library)
    {
      read_available_stubs ();
      
      /* Remove libraries for which stubs are available.  */
      for (i = 0; i < argc; i++)
	if (argv[i][0] == '-' && argv[i][1] == 'l')
	  {
	    for (j = 0; j < nbr_available_libs; j++)
	      if (strcmp (argv[i] + 2, available_libs[j]) == 0)
		{
		  libs[nlib] = argv[i];
		  nlib++;
		  memmove (argv + i, argv + i + 1,
			   (argc - i - 1) * sizeof (char *));
		  argc--;
		  break;
		}
	    if (j >= nbr_available_libs)
	      fprintf (stderr,
		       "%s:warning: no stubs for `%s'."
		       " This library should have been compiled with "
		       "checkergcc.\n",
		       program_name, argv[i]);
	  }
    }

  /* Set the compiler name.  */
  for (i = 0; i < nbr_compiler_args; i++)
    newargv[i] = compiler_args[i];
  nargc = nbr_compiler_args;
  
  /* Insert the (added) args.  */
  for (i = 0; i < nbr_added_args; i++)
    newargv[nargc++] = added_args[i];
  
  /* Copy the line args.  */
  for (i = 1; i < argc; i++)
    newargv[nargc++] = argv[i];

  /* Insert the library.  */
  if (library)
    {
      int len;
      char *p;

      /* The checker library.  */
      newargv[nargc++] = expand_filename ("@/gccchecker.o");
      newargv[nargc++] = expand_filename ("@/begin-stubs.o");
      
      /* The stubs for standard libraries.  */
      for (i = 0; i < nlib; i++)
	{
	  len = strlen (libs[i]);
	  p = xmalloc (len + 5 + 1);
	  strcpy (p, "-lchkr_");
	  strcpy (p + 7, libs[i] + 2);
	  newargv[nargc++] = p;
	}

      /* The stubs for libc.  */
#ifdef HAVE_GXX
      newargv[nargc++] = "-lchkr_gcc2";
#endif
      newargv[nargc++] = "-lchkr_c";
#ifdef HAVE_GXX
      newargv[nargc++] = "-lchkr_gcc2";
#endif

      /* A mark for end of stubs.  */
      newargv[nargc++] = expand_filename ("@/end-stubs.o");

      /* Then the standard libraries.  */
      for (i = 0; i < nlib; i++)
	newargv[nargc++] = libs[i];
      for (i = 0; i < nbr_added_libs; i++)
	newargv[nargc++] = added_libs[i];
    }

  /* The null (end of args).  */
  newargv[nargc++] = (char *) 0;  

  /* Verbose.  */
  if (flag_verbose)
    {
      for (i = 0; newargv[i]; i++)
	{
	  fputs (newargv[i], stdout);
	  putchar (' ');
	}
      putchar ('\n');
    }
   
  /* Compile now.  */
  execvp (newargv[0], newargv);
  perror ("gcc not found");
  exit (1);
}

/* Compile with gcc.  */
void
gcc (int argc, char *argv[])
{
  expand_path (added_args_for_gcc);
  expand_path (added_libs_for_gcc);
  compile (added_args_for_gcc, added_libs_for_gcc,
  	   GCC_PATH,
	   "gcc", argc, argv);
}

/* Compile with g++.  */
void
gplusplus (int argc, char *argv[])
{
  expand_path (added_args_for_gxx);
  expand_path (added_libs_for_gxx);
  compile (added_args_for_gxx, added_libs_for_gxx,
  	   GXX_PATH,
	   "g++", argc, argv);
}

/* Compile with specified compiler.  */
void
gen_compile (int argc, char *argv[])
{
  expand_path (added_args_for_gcc);
  expand_path (added_libs_for_gcc);
  compile (added_args_for_gcc, added_libs_for_gcc, NULL,
  	   argv[0] + sizeof ("--compile=") - 1, argc, argv);
}

#endif /* NEED_GCC */

#ifdef SIMCHECKER
void
run_simchecker (char **argv)
{
  struct stat statbuf;
  char *preload = "LD_PRELOAD=" CHECKER_LIB_PATH "/checker.so.1";
  char *preloaded_file = preload + 11;
  
  if (getenv ("LD_PRELOAD"))
    fatal ("LD_PRELOAD variable is already set. "
	   "Unset it before running Checker.\n");
  if (stat (preloaded_file, &statbuf) == -1)
    {
      fprintf (stderr, "Can't stat the shared library `%s': ", preloaded_file);
      perror (NULL);
      exit (2);
    }
  if (stat (argv[1], &statbuf) == -1)
    {
      fprintf (stderr, "Can't stat your program `%s': ", argv[1]);
      perror (NULL);
      exit (2);
    }
  if (   ((statbuf.st_mode & S_ISUID) && geteuid () != statbuf.st_uid)
      || ((statbuf.st_mode & S_ISGID) && getegid () != statbuf.st_gid))
    fatal ("Checker can't work with the set[ug]id program `%s'\n"
	   "Please, do `su' and try again.\n", argv[1]);
  putenv (preload);
  execv (argv[1], argv + 1);
  perror ("exec failed");
  exit (2);
}
#endif /* SIMCHECKER */

/* FIXME: use GNU getopt.  */
/* Describe which function to call according to flags.  */
struct flags
{
  char has_opt;
  char short_flag;
  char *long_flag;
  void (*function)(int, char **);
};

struct flags commands[] = {
  { 0, 'h', "--help",	&help},
  { 0, 'v', "--version",	&disp_version},
  { 0, 'p', "--path",	&disp_path},
#ifdef HAVE_BFD_H
  { 0, 'i', "--interpret",	&interpret},
#endif
#ifdef NEED_GCC
  { 0, 0,  "gcc",	&gcc},
  { 0, 0,  "-gcc",	&gcc},
  { 0, 0,  "g++",	&gplusplus},
  { 0, 0,  "-g++",	&gplusplus},
  { 0, 'l', "--libs",	&disp_libs},
  { 1, 0,   "--compile",&gen_compile},
#endif
  { 0, 0,   (char*)0 }};

int
main (int argc, char *argv[])
{
  struct flags *aflag;
  char *basename;
  int len;

  /* Set the program name.  */
  program_name = argv[0];

  /* Extract the basename of argv[0].  */
  basename = argv[0] + strlen (argv[0]);
  while (basename != argv[0] && basename[-1] != '/')
    basename--;

#ifdef NEED_GCC
  if (strcmp ("checkergcc", basename) == 0)
    gcc (argc, argv);
 
  if (strcmp ("checkerg++", basename) == 0)
    gplusplus (argc, argv);
#endif /* NEED_GCC */
   
  if (argc < 2)
    {
      fprintf (stderr, "Usage: %s command [args]\n", program_name);
      give_tips ();
    }
 
  if (argv[1][0] == '-' && argv[1][1] != '\0' && argv[1][2] == '\0')
    {
      /* A short flag.  */
      for (aflag = commands; aflag->long_flag; aflag++)
	{
	  if (argv[1][1] == aflag->short_flag)
	    {
	      (*aflag->function)(argc - 1, &argv[1]);
	      exit (0);
	    }
	}
    }
  else
    {
      /* Something else.  */
      for (aflag = commands; aflag->long_flag; aflag++)
	{
	  len = strlen (aflag->long_flag);
	  if (strncmp (argv[1], aflag->long_flag, len) == 0
	      && (!aflag->has_opt || argv[1][len] == '='))
	    {
	      (*aflag->function)(argc - 1, &argv[1]);
	      exit (0);
	    }
	}
    }
  
#ifdef SIMCHECKER
  /* `checker prog [args]'.  PROG must not begin with a `-'.  */
  if (argv[1] && argv[1][1] != '-')
    run_simchecker (argv);
#endif /* SIMCHECKER */

  fprintf (stderr, "%s: unknown command %s\n", program_name, argv[1]);
  give_tips ();
  return 1;
}
