Blob Blame History Raw
/* annocheck - A tool for checking security features of binares.
   Copyright (c) 2018 - 2020 Red Hat.
   Created by Nick Clifton.

  This 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, or (at your
  option) any later version.

  It 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 "annobin-global.h"
#include "annocheck.h"
#include "config.h"
#include <rpm/rpmlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <elfutils/libdwelf.h>
#include <elfutils/libdwfl.h>
#if HAVE_LIBDEBUGINFOD
#include <elfutils/debuginfod.h>
#endif

/* Maximum number of input files.  FIXME: Use a linked list instead.  */
#define MAX_NUM_FILES 256

/* Prefix used to isolate annobin symbols from program symbols.  */
#define ANNOBIN_SYMBOL_PREFIX ".annobin_"

/* -1: silent, 0: normal, 1: verbose, 2: very verbose.  */
ulong         verbosity = 0;

static ulong         	num_files = 0;
static const char *     files[MAX_NUM_FILES];
static const char *     full_progname;
static const char *     progname;
static bool             ignore_unknown = true;
static char *           saved_args = NULL;
static char *           prefix = "";
static const char *     debug_rpm = NULL;
static const char *     debug_rpm_dir = NULL;
static const char *     debug_path = NULL;
static const char *     debug_file = NULL;
static uint             level = 0;
static const char *     tmpdir = NULL;

static checker *        first_checker = NULL;
static checker *        first_sec_checker = NULL;
static checker *        first_seg_checker = NULL;

typedef struct checker_internal
{
  /* True if this checker should be skipped for this file.  */
  bool             skip;

  /* Pointer to the next section checker.  */
  struct checker * next_sec;

  /* Pointer to the next segment checker.  */
  struct checker * next_seg;

  /* Pointer to the next checker.  */
  struct checker * next;

  /* Name of the datafile used to share data with other iterations.  */
  const char *     datafile;

} checker_internal;
  
/* -------------------------------------------------------------------- */

#define COMPONENT_NAME_DEPTH 4
static const char * component_names[COMPONENT_NAME_DEPTH] = {[0] = "annocheck"};
static unsigned int component_name_index = 0;
#define CURRENT_COMPONENT_NAME component_names[component_name_index]

static void
push_component (checker * tool)
{
  ++ component_name_index;
  if (component_name_index >= COMPONENT_NAME_DEPTH)
    {
      --component_name_index;
      einfo (WARN, "Out of component name stack");
    }
  else
    component_names[component_name_index] = tool->name;
}

static void
pop_component (void)
{
  if (component_name_index > 0)
    -- component_name_index;
  else
    einfo (WARN, "Empty component name stack");
}

/* -------------------------------------------------------------------- */
/* Print a message on stdout or stderr.  Returns FALSE (for error
   messages) so that it can be used as a terminator in boolean functions.  */

bool
einfo (einfo_type type, const char * format, ...)
{
  FILE *        file;
  const char *  pref = NULL;
  va_list       args;
  bool          res = false;

  switch (type)
    {
    case WARN:
    case SYS_WARN:
      pref = "Warning";
      file = stderr;
      break;
    case ERROR:
    case SYS_ERROR:
      pref = "Error";
      file = stderr;
      break;
    case FAIL:
      pref = "Internal Failure";
      file = stderr;
      break;
    case VERBOSE2:
    case VERBOSE:
      file = stdout;
      res  = true;
      break;
    case INFO:
      file = stdout;
      res  = true;
      break;
    case PARTIAL:
      file = stdout;
      res  = true;
      break;
    default:
      fprintf (stderr, "ICE: Unknown einfo type %x\n", type);
      exit (-1);
    }
  
  if (verbosity == -1UL
      || (type == VERBOSE && verbosity < 1)
      || (type == VERBOSE2 && verbosity < 2))
    return res;

  fflush (stderr);
  fflush (stdout);

  if (type != PARTIAL)
    fprintf (file, "%s: ", CURRENT_COMPONENT_NAME);

  const char *  do_newline;
  char          c;
  size_t        len = strlen (format);
  if (len < 1)
    {
      fprintf (stderr, "ICE: einfo called without a valid format string\n");
      exit (-1);
    }
  c = format[len - 1];
  if (c == '\n' || c == ' ')
    do_newline = "";
  else if (c == '.' || c == ':')
    do_newline = "\n";
  else
    do_newline = ".\n";

  if (pref)
    fprintf (file, "%s: ", pref);

  if (!PARTIAL && prefix[0])
    fprintf (file, "%s ", prefix);

  va_start (args, format);
  vfprintf (file, format, args);
  va_end (args);

  if (type == SYS_WARN || type == SYS_ERROR)
    fprintf (file, ": system error: %s", strerror (errno));

  if (type != PARTIAL)
    fprintf (file, "%s", do_newline);
  return res;
}

/* -------------------------------------------------------------------- */

static void
add_file (const char * filename)
{
  if (num_files == MAX_NUM_FILES)
    return;

  files[num_files ++] = filename;
}

static void
print_version (void)
{
  einfo (INFO, "Version %d.%02d", ANNOBIN_VERSION / 100, ANNOBIN_VERSION % 100);

  checker * tool;
  for (tool = first_checker; tool != NULL; tool = ((checker_internal *)(tool->internal))->next)
    if (tool->version)
      {
	push_component (tool);
	einfo (PARTIAL, " ");
	tool->version ();
	pop_component ();
      }
}

static void
usage (void)
{
  einfo (INFO, "Runs various scans on the given files");
  einfo (INFO, "Useage: %s [options] <file(s)>", CURRENT_COMPONENT_NAME);
  einfo (INFO, " Options are:");
  einfo (INFO, "   --debug-rpm=<RPM>  [Find separate dwarf debug information in <RPM>]");
  einfo (INFO, "   --debug-file=<FILE>[Find separate dwarf debug information in <FILE>]");
  einfo (INFO, "   --debug-dir=<DIR>  [Look in <DIR> for separate dwarf debug information files]");
  einfo (INFO, "   --help             [Display this message & exit]");
  einfo (INFO, "   --ignore-unknown   [Do not complain about unknown file types][default]");
  einfo (INFO, "   --report-unknown   [Do complain about unknown file types]");
  einfo (INFO, "   --quiet            [Do not print anything, just return an exit status]");
  einfo (INFO, "   --verbose          [Produce informational messages whilst working.  Repeat for more information]");
  einfo (INFO, "   --version          [Report the verion of the tool & exit]");

  einfo (INFO, "The following options are internal to the scanner and not expected to be supplied by the user:");
  einfo (INFO, "   --prefix=<TEXT>    [Include <TEXT> in the output description]");
  einfo (INFO, "   --tmpdir=<NAME>    [Absolute pathname of a temporary directory used to pass data between iterations]");
  einfo (INFO, "   --level=<N>        [Recursion level of the scanner]");

  einfo (INFO, "The following scanning tools are available:");

  checker * tool;
  for (tool = first_checker; tool != NULL; tool = ((checker_internal *)(tool->internal))->next)
    {
      push_component (tool);
      einfo (PARTIAL, "\n");
      if (tool->usage)
	tool->usage ();
      else
	einfo (INFO, "Does not have any specific options");
      pop_component ();
    }
}

static void
save_arg (const char * arg)
{
  if (saved_args)
    {
      char * new_saved_args = concat (saved_args, " ", arg, NULL);
      free (saved_args);
      saved_args = new_saved_args;
    }
  else
    saved_args = concat (arg, NULL);
}

/* Handle command line options.  Returns to caller if there is
   something to do.  */

static bool
process_command_line (uint argc, const char * argv[])
{
  uint a = 1;

  progname = component_names[0];

  if (argc > 0 && argv == NULL)
    return false;

  while (a < argc)
    {
      const char *  arg = argv[a];
      bool          used = false;
      checker *     tool;

      ++ a;

      for (tool = first_checker; tool != NULL; tool = ((checker_internal *)(tool->internal))->next)
	if (tool->process_arg != NULL)
	  {
	    push_component (tool);
	    if (tool->process_arg (arg, argv, argc, & a))
	      used = true;
	    pop_component ();
	  }

      if (used)
	{
	  save_arg (arg);
	  continue;
	}

      if (arg[0] == '-')
        {
	  const char *  parameter;
	  const char * orig_arg = arg;

	  arg += (arg[1] == '-' ? 2 : 1);
	  switch (*arg)
	    {
	    case 'h': /* --help */
	      usage ();
	      exit (EXIT_SUCCESS);

	    case 'i': /* --ignore-unknown  */
	      ignore_unknown = true;
	      break;

	    case 'r': /* --report-unknown  */
	      ignore_unknown = false;
	      break;

	    case 'q': /* --quiet */
	      save_arg (orig_arg);
	      verbosity = -1UL;
	      break;

	    case 'd': /* --debug-rpm, --debug-file or --debug-dir.  */
	      parameter = strchr (arg, '=');
	      if (parameter == NULL)
		parameter = argv[a++];
	      else
		parameter ++;

	      if (const_strneq (arg, "dwarf-dir")) /* Old name for --debug-dir.  */
		debug_path = parameter;
	      else if (const_strneq (arg, "debug-dir") || const_strneq (arg, "debugdir"))
		debug_path = parameter;
	      else if (const_strneq (arg, "debug-rpm") || const_strneq (arg, "debugrpm"))
		debug_rpm = parameter;
	      else if (const_strneq (arg, "debug-file") || const_strneq (arg, "debugfile"))
		debug_file = parameter;
	      else
		goto unknown_arg;

	      if (parameter == NULL)
		goto arg_missing_argument;

	      if (parameter[0] != '/')
		{
		  const char * tmp;
		  /* Convert a relative path to an absolute one so that if/when
		     we recurse into a directory, the path will remain valid.  */
		  if (parameter == argv[a-1])
		    tmp = concat (orig_arg, " ", getcwd (NULL, 0), "/", parameter, NULL);
		  else if (debug_rpm == parameter)
		    tmp = concat ("--debug-rpm=", getcwd (NULL, 0), "/", parameter, NULL);
		  else if (debug_path == parameter)
		    tmp = concat ("--debug-dir=", getcwd (NULL, 0), "/", parameter, NULL);
		  else /* debug_file == parameter  */
		    tmp = concat ("--debug-file=", getcwd (NULL, 0), "/", parameter, NULL);
		  save_arg (tmp);
		  free ((void *) tmp);
		}
	      else
		{
		  save_arg (orig_arg);
		  if (parameter == argv[a-1])
		    save_arg (parameter);
		}

	      if (debug_path != NULL && debug_rpm != NULL)
		{
		  static bool warned = false;
		  if (! warned)
		    einfo (WARN, "Behaviour is undefined when both --debug-rpm and --debug-dir are specified");
		  warned = true;
		}
	      break;

	    case 'p': /* --prefix  */
	      save_arg (orig_arg);
	      parameter = strchr (arg, '=');
	      if (parameter == NULL)
		parameter = argv[a++];
	      else
		parameter ++;

	      if (parameter == NULL)
		goto arg_missing_argument;

	      /* Prefix arguments accumulate.  */
	      prefix = concat (prefix, parameter, NULL);
	      break;

	    case 'l': /* --level */
	      parameter = strchr (arg, '=');
	      if (parameter == NULL)
		parameter = argv[a++];
	      else
		parameter ++;	      

	      if (parameter == NULL)
		goto arg_missing_argument;

	      level = strtoul (parameter, NULL, 0);
	      if (level < 1)
		{
		  einfo (WARN, "improper --level option: %s", parameter);
		  level = 1;
		}
	      break;

	    case 't': /* --tmpdir */
	      if (const_strneq (arg, "tmpdir"))
		{
		  parameter = strchr (arg, '=');
		  if (parameter == NULL)
		    parameter = argv[a++];
		  else
		    parameter ++;	      

		  if (parameter == NULL)
		    goto arg_missing_argument;

		  tmpdir = parameter;
		  assert (tmpdir[0] == '/');
		}
	      else
		goto unknown_arg;
	      break;
	      
	    case 'v': /* --verbose or --version */
	      if (const_strneq (arg, "version"))
		{
		  print_version ();
		  exit (EXIT_SUCCESS);
		}
	      else if (const_strneq (arg, "verbose")
		       /* Allow -v as an alias for --verbose.  */
		       || arg[1] == 0)
		{
		  save_arg (orig_arg);
		  verbosity ++;
		}
	      else
		goto unknown_arg;
	      break;

	    default:
	    unknown_arg:
	      einfo (WARN, "Unrecognised command line option: %s", orig_arg);
	      usage ();
	      return false;
	    arg_missing_argument:
	      einfo (ERROR, "Command line option '%s' needs an argument", orig_arg);
	      return false;
	    }
	}
      else
	add_file (arg);
    }

  if (num_files == 0)
    {
      einfo (WARN, "No input files specified");
      usage ();
      return false;
    }

  return true;
}

/* -------------------------------------------------------------------- */

/* Utility function to walk over a note section calling FUNC
   on each note.  PTR is passed to FUNC along with a pointer to the note.
   If FUNC returns false the walk is terminated.
   Returns FALSE if the walk could not be executed.  */

bool
annocheck_walk_notes (annocheck_data * data, annocheck_section * sec, note_walker func, void * ptr)
{
  assert (data != NULL && sec != NULL && func != NULL);

  if (sec->shdr.sh_type != SHT_NOTE
      || sec->data == NULL
      || sec->data->d_size == 0)
    return false;

  size_t offset = 0;

  GElf_Nhdr  note;
  size_t     name_offset;
  size_t     data_offset;
  
  while ((offset = gelf_getnote (sec->data, offset, & note, & name_offset, & data_offset)) != 0)
    if (! func (data, sec, & note, name_offset, data_offset, ptr))
      break;

  return true;
}

/* Read in the section header for SECTION.  */

static void
read_section_header (annocheck_data * data, Elf_Scn * section, Elf64_Shdr * s64hdr)
{
  if (data->is_32bit)
    {
      Elf32_Shdr * shdr = elf32_getshdr (section);

      s64hdr->sh_name = shdr->sh_name;
      s64hdr->sh_type = shdr->sh_type;
      s64hdr->sh_flags = shdr->sh_flags;
      s64hdr->sh_addr = shdr->sh_addr;
      s64hdr->sh_offset = shdr->sh_offset;
      s64hdr->sh_size = shdr->sh_size;
      s64hdr->sh_link = shdr->sh_link;
      s64hdr->sh_info = shdr->sh_info;
      s64hdr->sh_addralign = shdr->sh_addralign;
      s64hdr->sh_entsize = shdr->sh_entsize;
    }
  else
    memcpy (s64hdr, elf64_getshdr (section), sizeof * s64hdr);
}
  
/* -------------------------------------------------------------------- */

static bool
run_checkers (const char * filename, int fd, Elf * elf)
{
  annocheck_data data;

  memset (& data, 0, sizeof data);
  data.full_filename = filename;
  data.filename = BE_VERBOSE ? filename : lbasename (filename);
  data.fd = fd;
  data.dwarf_fd = -1;
  data.elf = elf;
  data.is_32bit = gelf_getclass (elf) == ELFCLASS32;

  checker * tool;

  /* Call the checker start functions.  */
  for (tool = first_checker; tool != NULL; tool = ((checker_internal *)(tool->internal))->next)
    {
      if (tool->start_file)
	{
	  push_component (tool);
	  ((checker_internal *)(tool->internal))->skip = ! tool->start_file (& data);
	  pop_component ();
	}
      else
	((checker_internal *)(tool->internal))->skip = false;
    }

  bool ret = true;  

  if (first_sec_checker != NULL)
    {
      size_t shstrndx;

      if (elf_getshdrstrndx (elf, & shstrndx) < 0)
	return einfo (ERROR, "%s: Unable to locate string section", filename);
	      
      Elf_Scn * scn = NULL;

      while ((scn = elf_nextscn (elf, scn)) != NULL)
	{
	  annocheck_section  sec;

	  memset (& sec, 0, sizeof sec);

	  sec.scn = scn;
	  read_section_header (& data, scn, & sec.shdr);
	  sec.secname = elf_strptr (elf, shstrndx, sec.shdr.sh_name);	  

	  /* Note - do not skip empty sections, they may still be interesting to some tools.
	     If a tool is not interested in an empty section, it can always determine this
	     in its interesting_sec() function.  */
	  einfo (VERBOSE2, "%s: Examining section %s", filename, sec.secname);

	  /* Walk the checkers, asking each in turn if they are interested in this section.  */
	  for (tool = first_sec_checker; tool != NULL; tool = ((checker_internal *)(tool->internal))->next_sec)
	    {
	      if (((checker_internal *)(tool->internal))->skip || tool->interesting_sec == NULL)
		continue;

	      push_component (tool);

	      if (tool->interesting_sec (& data, & sec))
		{
		  /* Delay loading the section contents until a checker expresses interest.  */
		  if (sec.data == NULL)
		    {
		      sec.data = elf_getdata (scn, NULL);
		      if (sec.data == NULL)
			ret = einfo (ERROR, "Failed to read in section %s", sec.secname);
		    }

		  if (sec.data != NULL)
		    {
		      einfo (VERBOSE2, "is interested in section %s", sec.secname);

		      assert (tool->check_sec != NULL);
		      ret &= tool->check_sec (& data, & sec);
		    }
		}
	      else
		einfo (VERBOSE2, "is not interested in %s", sec.secname);

	      pop_component ();
	    }
	}
    }

  if (first_seg_checker != NULL)
    {
      size_t phnum, cnt;

      elf_getphdrnum (elf, & phnum);

      for (cnt = 0; cnt < phnum; ++cnt)
	{
	  GElf_Phdr   mem;
	  annocheck_segment seg;

	  memset (& seg, 0, sizeof seg);

	  seg.phdr = gelf_getphdr (elf, cnt, & mem);
	  seg.number = cnt;

	  einfo (VERBOSE2, "%s: considering segment %lu", filename, (unsigned long) cnt);

	  for (tool = first_seg_checker; tool != NULL; tool = ((checker_internal *)(tool->internal))->next_seg)
	    {
	      if (((checker_internal *)(tool->internal))->skip || tool->interesting_seg == NULL)
		continue;

	      push_component (tool);

	      if (tool->interesting_seg (& data, & seg))
		{
		  /* Delay loading the contents of the segment until they are actually needed.  */
		  if (seg.data == NULL)
		    seg.data = elf_getdata_rawchunk (elf, seg.phdr->p_offset,
						     seg.phdr->p_filesz, ELF_T_BYTE);

		  assert (tool->check_seg != NULL);
		  ret &= tool->check_seg (& data, & seg);
		}
	      else
		einfo (VERBOSE2, "is not interested in segment %lu", (unsigned long) cnt);

	      pop_component ();
	    }
	}
    }

  for (tool = first_checker; tool != NULL; tool = ((checker_internal *)(tool->internal))->next)
    if (! ((checker_internal *)(tool->internal))->skip && tool->end_file)
      {
	push_component (tool);
	ret &= tool->end_file (& data);
	pop_component ();
      }

  return ret;
}

/* Like process_rpm_file, except that the rpm is just
   extracted and then left untouched.  Returns the name
   of the directory holding the rpm contents.  */

static const char *
extract_rpm_file (const char * filename)
{
  static char dirname[32];

  if (debug_rpm_dir != NULL)
    return debug_rpm_dir;

  dirname[0] = 0;
  strcpy (dirname, "annocheck.debuginfo.XXXXXX");
  if (mkdtemp (dirname) == NULL)
    {
      einfo (ERROR, "Failed to create temporary directory for debuginfo extraction: %s", filename);
      return NULL;
    }

  einfo (VERBOSE2, "Created temporary directory for debuginfo extraction: %s", dirname);

  char * fname;
  char * command;
  char * cwd = getcwd (NULL, 0);

  /* If filename is a relative path, convert it to an absolute one
     so that it can be found once we change into the temporary directory.  */
  if (filename[0] != '/')
    fname = concat (cwd, "/", filename, NULL);
  else
    /* This is just so that we can safely call free(fname) at the end.  */
    fname = concat (filename, NULL);

  if (access (fname, F_OK) == -1) 
    {
      einfo (SYS_ERROR, "Error reading rpm file file %s", fname);
      free (fname);
      return NULL;
    }

  command = concat (/* Change into the temporary directory.  */
		    "cd ", dirname,
		    /* Convert the rpm to cpio format.  */
		    " && rpm2cpio \"", fname, "\"",
		    /* Pipe the output into cpio in order to extract the files.  */
		    " | cpio -dium --quiet",
		    /* Then move out of the directory.  */
		    " && cd ..",
		    NULL);

  einfo (VERBOSE2, "Running rpm extractor command sequence: %s", command);
  fflush (stdin);
  
  if (system (command))
    {
      einfo (WARN, "Failed to extract rpm file: %s", filename);
      return NULL;
    }

  free (command);
  free (cwd);
  free (fname);

  einfo (VERBOSE2, "Extraction successful");
  return debug_rpm_dir = dirname;
}

#define TRY_DEBUG(format,args...)					\
  do									\
    {									\
      sprintf (debugfile, format, args);				\
      einfo (VERBOSE2, "%s:  try: %s", data->filename, debugfile);	\
      if ((fd = open (debugfile, O_RDONLY)) != -1)			\
	goto found;							\
    }									\
  while (0)

static bool
follow_debuglink (annocheck_data * data)
{
  char *  canon_dir = NULL;
  char *  debugfile = NULL;
  int     fd;

  /* Initialise the dwarf specific fields of the data structure.  */
  data->dwarf = NULL;
  data->dwarf_fd = -1;
  data->dwarf_filename = NULL;
  
  /* First try the build-id method.  */
  ssize_t       build_id_len;
  const void *  build_id_ptr;

  einfo (VERBOSE2, "%s: Attempting to locate separate debuginfo file", data->filename);

  if (debug_file)
    {
      debugfile = (char *) xmalloc (strlen (debug_file) + 2);
      TRY_DEBUG ("%s", debug_file);
      free (debugfile);
    }

  build_id_len = dwelf_elf_gnu_build_id (data->elf, & build_id_ptr);
  if (build_id_len > 0)
    {
      /* Compute the path to the debuginfo from the build id.
	 Since we know that we are running on a Fedora/RHEL
	 system we can just check the standard Fedora location:
	 
	  /usr/lib/debug/.build-id/NN/NN+NN.debug
	  
	where NNNN+NN is the build-id value as a hexadecimal
	string.  */

      const char *     path = NULL;
      const char *     leadin = "/usr/lib/debug/.build-id";
      unsigned char *  d = (unsigned char *) build_id_ptr;
      ssize_t          len = build_id_len;
      char             build_id_dir[3];
      char *           build_id_name;
      char *           n;

      einfo (VERBOSE2, "%s: Testing possibilities based upon the build-id", data->filename);

      if (debug_rpm)
	/* If the user has told us an rpm file that contains
	   debug information then extract it and use it.  */
	path = extract_rpm_file (debug_rpm);
      else if (debug_path)
	path = debug_path;

      if (path == NULL)
	path = "";
      
      debugfile = xmalloc (strlen (leadin)
			   + strlen (path)
			   + len * 2
			   + strlen (".debug") + 6);
      
      sprintf (build_id_dir, "%02x", * d++);
      len --;
      build_id_name = n = xmalloc (len * 2 + 1);
      while (len --)
	n += sprintf (n, "%02x", *d++);      
      
      if (* path)
	{
	  /* If the user has supplied a directory to search then this might be
	     an unpacked debuginfo rpm.  So try the following possibilities:

	     <path>/NNNNN.debug
	     <path>/NN/NNNNN.debug
	     <path>/.build-id/NN/NNNN.debug
	     <path>/usr/lib/debug/.build-id/NN/NNNNN.debug */

          TRY_DEBUG ("%s/%s.debug", path, build_id_name);
          TRY_DEBUG ("%s/%s/%s.debug", path, build_id_dir, build_id_name);
          TRY_DEBUG ("%s/.build-id/%s/%s.debug", path, build_id_dir, build_id_name);
          TRY_DEBUG ("%s/%s/%s/%s.debug", path, leadin + 1, build_id_dir, build_id_name);
	}

      TRY_DEBUG ("%s/%s/%s.debug", leadin, build_id_dir, build_id_name);

      free (debugfile);
      einfo (VERBOSE2, "%s: Could not find separate debug based on build-id", data->filename);
    }

  /* Now try using a .gnu.debuglink section.  */
  GElf_Word     crc;
  const char *  link;

  if ((link = dwelf_elf_gnu_debuglink (data->elf, & crc)) == NULL)
    {
      einfo (VERBOSE2, "%s: Could not find separate debug file", data->filename);
      return NULL;
    }

  einfo (VERBOSE2, "%s: Testing possibilities based upon the debuglink", data->filename);

  size_t canon_dirlen;

  /* Attempt to locate the separate file.  */
  canon_dir = lrealpath (data->filename);
  
  for (canon_dirlen = strlen (canon_dir); canon_dirlen > 0; canon_dirlen--)
    if (canon_dir[canon_dirlen - 1] == '/')
      break;
  canon_dir[canon_dirlen] = '\0';

#define DEBUGDIR_1 "/lib/debug"
#define DEBUGDIR_2 "/usr/lib/debug"
#define DEBUGDIR_3 "/usr/lib/debug/usr"
#define DEBUGDIR_4 "/usr/lib/debug/usr/lib64"

  debugfile = (char *) xmalloc (strlen (DEBUGDIR_1) + 1
				+ strlen (DEBUGDIR_2)
				+ strlen (DEBUGDIR_3)
				+ strlen (DEBUGDIR_4)
				+ canon_dirlen
				+ strlen (".debug/")
				+ strlen (link)
				+ 1);

  /* If we have been provided with a debug directory, try that first.  */
  if (debug_path)
    TRY_DEBUG ("%s/%s", debug_path, link);

  /* If we have been pointed at an debuginfo rpm then try that next.  */
  if (debug_rpm)
    {
      const char * dir = extract_rpm_file (debug_rpm);
      TRY_DEBUG ("./%s/%s", dir, link);
      TRY_DEBUG ("./%s%s/%s", dir, DEBUGDIR_1, link);
      TRY_DEBUG ("./%s%s/%s", dir, DEBUGDIR_2, link);
      TRY_DEBUG ("./%s%s/%s", dir, DEBUGDIR_3, link);
      TRY_DEBUG ("./%s%s/%s", dir, DEBUGDIR_4, link);
    }
  
  /* next try in the current directory.  */
  TRY_DEBUG ("./%s", link);

  /* Then try in a subdirectory called .debug.  */
  TRY_DEBUG ("./.debug/%s", link);

  /* Then try in the same directory as the original file.  */
  TRY_DEBUG ("%s%s", canon_dir, link);

  /* And the .debug subdirectory of that directory.  */
  TRY_DEBUG ("%s.debug/%s", canon_dir, link);

  /* Try the first extra debug file root.  */
  TRY_DEBUG ("%s/%s", DEBUGDIR_2, link);

  /* Try the first extra debug file root, with directory extensions.  */
  TRY_DEBUG ("%s%s%s", DEBUGDIR_2, canon_dir, link);

  /* Try the second extra debug file root.  */
  TRY_DEBUG ("%s/%s", DEBUGDIR_3, link);

  /* Try the fourth extra debug file root.  */
  TRY_DEBUG ("%s/%s", DEBUGDIR_4, link);

  /* Then try in the global debugfile directory.  */
  TRY_DEBUG ("%s/%s", DEBUGDIR_1, link);

  /* Then try in the global debugfile directory, with directory extensions.  */
  TRY_DEBUG ("%s%s%s", DEBUGDIR_1, canon_dir, link);

  /* Try the first extra debug file root, with directory extensions.  */
  TRY_DEBUG ("%s%s%s", DEBUGDIR_2, canon_dir, link);

  /* FIMXE: This is a workaround for a bug in the Fedora packaging
     system.  It is possible for the debuginfo files to be out of
     sync with their corresponding binary files.  Eg:
          ld-2.29.1-23.fc28
       vs ld-2.29.1-22.fc28.debug_info.
     So check for earlier versions of the debuginfo file in the directory
     where it is known that Fedora stores its debug files...  */
  char * dash = strrchr (link, '-');
  if (dash)
    {
      char * end;
      unsigned long revision = strtoul (dash + 1, & end, 10);

      while (revision > 1)
	{
	  --revision;

	  TRY_DEBUG ("%s%s%.*s%lu%s", DEBUGDIR_2, canon_dir, (int) (dash - link) + 1, link, revision, end);
	}
    }

#if HAVE_LIBDEBUGINFOD
  if (build_id_len > 0)
    {
      debuginfod_client *client = debuginfod_begin ();

      if (client != NULL)
        {
          free (debugfile);
          debugfile = NULL;

	  TRY_DEBUG ("DEBUGINFOD_URLS=%s", getenv (DEBUGINFOD_URLS_ENV_VAR) ?: "" );
          /* If the debug file is successfully downloaded, debugfile will be
             set to the path of the local copy.  */
          fd = debuginfod_find_debuginfo (client, build_id_ptr, build_id_len, & debugfile);

          debuginfod_end (client);

          if (fd >= 0)
            {
              /* Ensure file is read-only.  */
              close (fd);
              if ((fd = open (debugfile, O_RDONLY)) != -1)
                goto found;
            }
        }
    }
#endif /* HAVE_LIBDEBUGINFOD */

  /* Failed to find the file.  */
  einfo (VERBOSE, "%s: warn: Could not find separate debug file: %s", data->filename, link);
  
  free (canon_dir);
  free (debugfile);
  return false;

 found:
  /* FIXME: We should verify the CRC value.  */

  free (canon_dir);

  /* Now open the file...  */
  Dwarf * separate_debug_file = dwarf_begin (fd, DWARF_C_READ);
  if (separate_debug_file == NULL)
    {
      int err = dwarf_errno ();

      if (err)
	einfo (VERBOSE, "%s: warn: Failed to parse separate debug file '%s', (%s)",
	       data->filename, debugfile, dwarf_errmsg (err));
      else
	einfo (VERBOSE, "%s: warn: Failed to parse separate debug file '%s', (no error message available)",
	       data->filename, debugfile);	
      
      free (debugfile);
      return false;
    }

  einfo (VERBOSE2, "%s: Opened separate debug file: %s", data->filename, debugfile);
  data->dwarf_fd = fd;
  data->dwarf_filename = debugfile;
  data->dwarf_searched = false;
  data->dwarf = separate_debug_file;
  return true;
}

/* -------------------------------------------------------------------- */

static bool
scan_dwarf (annocheck_data * data, Dwarf * dwarf, dwarf_walker func, void * ptr)
{
  Dwarf_Off  cuoffset;
  Dwarf_Off  ncuoffset = 0;
  size_t     hsize;

  while (dwarf_nextcu (dwarf, cuoffset = ncuoffset, & ncuoffset, & hsize, NULL, NULL, NULL) == 0)
    {
      Dwarf_Off cudieoff = cuoffset + hsize;
      Dwarf_Die cudie;

      if (dwarf_offdie (dwarf, cudieoff, & cudie) == NULL)
	{
	  einfo (ERROR, "%s: Empty CU", data->filename);
	  continue;
	}

      if (! func (data, dwarf, & cudie, ptr))
	return false;
    }

  return true;
}

/* Utility function to walk over the DWARF debug information in DATA calling FUNC
   on each DIE.  PTR is passed to FUNC along with a pointer to the DIE.
   If FUNC returns false the walk is terminated.
   Returns FALSE if the walk could not be executed.  */

bool
annocheck_walk_dwarf (annocheck_data * data, dwarf_walker func, void * ptr)
{
  Dwarf * dwarf;

  if (! data->dwarf_searched)
    {
      dwarf = dwarf_begin (data->fd, DWARF_C_READ);

      if (dwarf != NULL)
	{
	  data->dwarf = dwarf;
	  data->dwarf_fd = data->fd;
	  data->dwarf_filename = data->filename;
	  data->dwarf_searched = true;
	}
      else if (! follow_debuglink (data))
	return einfo (VERBOSE2, "%s: Does not contain or link to any DWARF information", data->filename);
    }

  if ((dwarf = data->dwarf) == NULL)
    return true;

  (void) scan_dwarf (data, dwarf, func, ptr);

  /* We used to call dwarf_getalt() if the scan failed to find anything.
     But that was a waste of time.  libdw will automatically load any
     alternate debug info files pointed to by sections in the binary.  */

  /* We do not close the dwarf handle as we will probably want to use it again.  */
  return true;
}

/* -------------------------------------------------------------------- */

static bool
ends_with (const char * string, const char * ending, const size_t end_len)
{
  size_t len;

  return (string != NULL
	  && ending != NULL
	  && end_len > 0
	  && (len = strlen (string)) > end_len
	  && streq (string + (len - end_len), ending));
}

static const char *
find_symbol_in (Elf * elf, Elf_Scn * sym_sec, ulong addr, Elf64_Shdr * sym_hdr, bool prefer_func)
{
  Elf_Data * sym_data;

  if ((sym_data = elf_getdata (sym_sec, NULL)) == NULL)
    {
      einfo (VERBOSE2, "No symbol section data");
      return NULL;
    }

  bool use_sym = false;
  bool use_saved = false;
  GElf_Sym saved_sym = {0};
  GElf_Sym sym;
  unsigned int symndx;

  for (symndx = 1; gelf_getsym (sym_data, symndx, & sym) != NULL; symndx++)
    {
      /* As of version 3 of the protocol, start symbols might be biased by 2.  */
      if (sym.st_value >= addr && sym.st_value <= addr + 2)
	{
	  if (prefer_func && GELF_ST_TYPE (sym.st_info) == STT_FUNC)
	    {
	      use_sym = true;
	      break;
	    }

	  const char * name = elf_strptr (elf, sym_hdr->sh_link, sym.st_name);
	  if (ends_with (name, "_end", strlen ("_end")))
	    continue;

	  if (ends_with (name, ".end", strlen (".end")))
	    continue;

	  if (! use_saved)
	    {
	      memcpy (& saved_sym, & sym, sizeof sym);
	      use_saved = true;
	    }
	  else
	    {
	      /* Save this symbol if it is a better fit than the currently
		 saved symbol.  */
	      if (GELF_ST_VISIBILITY (sym.st_other) != STV_HIDDEN
		  && GELF_ST_TYPE (sym.st_info) != STT_NOTYPE)
		memcpy (& saved_sym, & sym, sizeof sym);
	    }
	}
    }

  if (use_sym)
    return elf_strptr (elf, sym_hdr->sh_link, sym.st_name);

  if (use_saved)
    return elf_strptr (elf, sym_hdr->sh_link, saved_sym.st_name);

  return NULL;
}

typedef struct walker_info
{
  ulong          start;
  ulong          end;
  const char **  name;
  bool           prefer_func;
} walker_info;

static bool
find_symbol_addr_using_dwarf (annocheck_data * data, Dwarf * dwarf, Dwarf_Die * die, void * ptr)
{
  assert (data != NULL && die != NULL && ptr != NULL);

  walker_info * info = (walker_info *) ptr;

  /* If we are examining a separate debuginfo file then
     it might have a symbol table that we can use.  */
  if (data->elf != dwarf_getelf (dwarf))
    {
      Elf_Scn *    sym_sec = NULL;
      Elf *        elf = dwarf_getelf (dwarf);

      while ((sym_sec = elf_nextscn (elf, sym_sec)) != NULL)
	{
	  Elf64_Shdr   sym_shdr;

	  read_section_header (data, sym_sec, & sym_shdr);

	  if ((sym_shdr.sh_type == SHT_SYMTAB) || (sym_shdr.sh_type == SHT_DYNSYM))
	    {
	      const char * name;

	      name = find_symbol_in (elf, sym_sec, info->start, & sym_shdr, info->prefer_func);
	      if (name)
		{
		  *(info->name) = name;
		  return false;
		}
	    }
	}
    }

  size_t         nlines;
  Dwarf_Lines *  lines;

  dwarf_getsrclines (die, & lines, & nlines);

  if (lines != NULL && nlines > 0)
    {
      Dwarf_Line * line;
      size_t       indx = 1;

      einfo (VERBOSE2, "Scanning %ld lines in the DWARF line table", (unsigned long) nlines);
      while ((line = dwarf_onesrcline (lines, indx)) != NULL)
	{
	  Dwarf_Addr addr;

	  dwarf_lineaddr (line, & addr);

	  if (addr >= info->start && addr <= info->end)
	    {
	      *(info->name) = dwarf_linesrc (line, NULL, NULL);
	      return false;
	    }

	  ++ indx;
	}
    }

  return true;
}

/* Return the name of a symbol most appropriate for address range START..END.
   Returns NULL if no symbol could be found.  */

const char *
annocheck_find_symbol_for_address_range (annocheck_data *     data,
					 annocheck_section *  sec,
					 ulong                start,
					 ulong                end,
					 bool                 prefer_func)
{
  static const char * previous_result;
  static ulong        previous_start;
  static ulong        previous_end;

  const char * name = NULL;
  Elf64_Shdr   sym_shdr;
  Elf_Scn *    sym_sec = NULL;

  if (start > end)
    return NULL;

  if (start == previous_start && end == previous_end)
    return previous_result;

  assert (data != NULL);

  previous_start = start;
  previous_end   = end;

  einfo (VERBOSE2, "Look for a symbol matching address %#lx..%#lx", start, end);

  /* If the provided section has a link then try this first.  */
  if (sec != NULL && sec->shdr.sh_link)
    {
      sym_sec = elf_getscn (data->elf, sec->shdr.sh_link);
      read_section_header (data, sym_sec, & sym_shdr);

      if (sym_shdr.sh_type == SHT_SYMTAB || sym_shdr.sh_type == SHT_DYNSYM)
	{
	  name = find_symbol_in (data->elf, sym_sec, start, & sym_shdr, prefer_func);
	  if (name != NULL)
	    goto found;

	}
    }

  /* Search for symbol sections.  */
  sym_sec = NULL;

  while ((sym_sec = elf_nextscn (data->elf, sym_sec)) != NULL)
    {
      read_section_header (data, sym_sec, & sym_shdr);

      if ((sym_shdr.sh_type == SHT_SYMTAB) || (sym_shdr.sh_type == SHT_DYNSYM))
	{
	  name = find_symbol_in (data->elf, sym_sec, start, & sym_shdr, prefer_func);
	  if (name)
	    goto found;
	}
    }

  /* Now check DWARF data for an address match.  */
  walker_info walker;
  walker.start = start;
  walker.end = end;
  walker.name = & name;
  walker.prefer_func = prefer_func;
  annocheck_walk_dwarf (data, find_symbol_addr_using_dwarf, & walker);

 found:
  /* If we have found an ".annobin_" prefixed symbol then skip the prefix.  */
  if (name && strncmp (name, ANNOBIN_SYMBOL_PREFIX, strlen (ANNOBIN_SYMBOL_PREFIX)) == 0)
    name += strlen (ANNOBIN_SYMBOL_PREFIX);

  return previous_result = name;
}

/* -------------------------------------------------------------------- */

static bool process_elf (const char *, int, Elf *);

static bool
process_ar (const char * filename, int fd, Elf * elf)
{
  Elf *    subelf;
  Elf_Cmd  cmd = ELF_C_READ_MMAP;
  bool     ret = true;

  while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
    {
      /* Get the header for this element.  */
      Elf_Arhdr * arhdr = elf_getarhdr (subelf);
      const char * fname = concat (filename, ":", arhdr->ar_name, NULL);

      /* Skip over the index entries.  */
      if (! streq (arhdr->ar_name, "/")
	  && ! streq (arhdr->ar_name, "//"))
	ret = process_elf (fname, fd, subelf);

      /* Get next archive element.  */
      cmd = elf_next (subelf);

      if (elf_end (subelf))
	return einfo (FAIL, "unable to close archive member %s", fname);

      free ((char *) fname);
    }

  return ret;
}

static bool
process_elf (const char * filename, int fd, Elf * elf)
{
  bool ret;

  switch (elf_kind (elf))
    {
    case ELF_K_AR:
      ret = process_ar (filename, fd, elf);
      break;
    case ELF_K_ELF:
      ret = run_checkers (filename, fd, elf);
      break;
    default:
      if (ignore_unknown)
	return true;
      return einfo (WARN, "%s: is not an ELF format file", filename);
    }

  return ret;
}

static const char *
itoa (uint level)
{
  switch (level)
    {
    case 0: return "0";
    case 1: return "1";
    case 2: return "2";
    case 3: return "3";
    default: return "4"; /* Can this ever be reached ?  */
    }
}

static bool
process_rpm_file (const char * filename)
{
  /* It turns out that the simplest/most portable way to handle an rpm is
     to use the rpm2cpio and cpio programs to unpack it for us...  */
  char dirname[32];

  strcpy (dirname, "annocheck.rpm.XXXXXX");
  if (mkdtemp (dirname) == NULL)
    return einfo (WARN, "Failed to create temporary directory for processing rpm: %s", filename);

  einfo (VERBOSE2, "Created temporary directory for rpm processing: %s", dirname);

  char * fname;
  char * pname;
  char * command;
  char * cwd = getcwd (NULL, 0);

  if (filename[0] != '/')
    fname = concat (cwd, "/", filename, NULL);
  else
    fname = concat (filename, NULL);

  if (full_progname[0] != '/' && strchr (full_progname, '/'))
    pname = concat (cwd, "/", full_progname, NULL);
  else
    pname = concat (full_progname, NULL);

  command = concat (/* Change into the temporary directory.  */
		    "cd ", dirname,
		    /* Convert the rpm to cpio format.  */
		    " && rpm2cpio \"", fname, "\"",
		    /* Pipe the output into cpio in order to extract the files.  */
		    " | cpio -dium --quiet",
		    /* Run annocheck on the files in the directory, skipping unknown file types,
		       and prefixing the output with the rpm name.  */
		    " && ", pname, " --ignore-unknown ",
		    "--prefix \"", lbasename (filename), "\"",
		    /* Increment the recursion level.  */
		    " --level ", itoa (level + 1),
		    /* Pass on the name of the temporary data directory, if created.  */
		    tmpdir == NULL ? "" : " --tmpdir ",
		    tmpdir == NULL ? "" : tmpdir,
		    /* Then all the other options that the user has supplied.  */
		    " ", saved_args ? saved_args : "",
		    " .",
		    NULL);

  einfo (VERBOSE2, "Running rpm extractor command sequence: %s", command);
  fflush (stdin);

  int result = system (command);
  if (result == -1 || result == 127)
    return einfo (WARN, "Failed to process rpm file: %s", filename);

  free (command);
  free (cwd);
  free (fname);
  free (pname);

  /* Delete the temporary directory.  */
  command = concat ("rm -fr ", dirname, NULL);
  if (system (command))
    einfo (WARN, "Failed to delete temporary directory: %s", dirname);
  free (command);

  einfo (VERBOSE2, "RPM processed successfully");
  return result == EXIT_SUCCESS;
}

static bool
process_file (const char * filename)
{
  size_t       len;
  struct stat  statbuf;

  /* Fast track ignoring of debuginfo files.
     FIXME: Maybe add other file extensions ?
     FIXME: Maybe check that the extension is at the end of the filename ?  */
  if (ignore_unknown && strstr (filename, ".debug"))
    return true;

  /* When ignoring unknown file types (which typically happens when processing the
     contents of an rpm), we do not follow symbolic links.  This allows us to detect
     and ignore these links.  */
  if ((ignore_unknown ? lstat (filename, & statbuf) : stat (filename, & statbuf)) < 0)
    {
      if (errno == ENOENT)
	return einfo (WARN, "'%s': No such file", filename);

      return einfo (SYS_WARN, "Could not locate '%s'", filename);
    }

  if (S_ISDIR (statbuf.st_mode))
    {
      DIR * dir = opendir (filename);

      if (dir == NULL)
	return einfo (SYS_WARN, "unable to read directory: %s", filename);

      struct dirent * entry;
      bool result = true;

      einfo (VERBOSE2, "Scanning directory: '%s'", filename);
      while ((entry = readdir (dir)) != NULL)
	{
	  if (streq (entry->d_name, ".") || streq (entry->d_name, ".."))
	    continue;

	  const char * file = concat (filename, "/", entry->d_name, NULL);
	  result &= process_file (file);
	  free ((char *) file);
	}

      closedir (dir);
      return result;
    }

  if (! S_ISREG (statbuf.st_mode))
    {
      if (ignore_unknown)
	return true;

      return einfo (WARN, "'%s' is not an ordinary file", filename);
    }

  if (statbuf.st_size < 0)
    return einfo (WARN, "'%s' has negative size, probably it is too large", filename);

  /* If the file is an RPM hand it off for separate processing.  */

  /* FIXME: the rpmReadPackageFile() function can generate a seg-fault
     when processing some rpms (eg: wireshark-cli-2.6.0-3.fc27.aarch64.rpm)
     so just check for a .rpm suffix first.  */
  if ((len = strlen (filename)) > 4 && streq (filename + len - 4, ".rpm"))
    return process_rpm_file (filename);
    
  FD_t rpm_fd;
  if ((rpm_fd = Fopen (filename, "r")) != NULL)
    {
      rpmts  ts = 0;
      Header hdr;
      bool   res = false;

      if (rpmReadPackageFile (ts, rpm_fd, filename, & hdr) == RPMRC_OK)
	res = process_rpm_file (filename);

      Fclose (rpm_fd);
      if (res)
	return true;
    }

  /* Otherwise open it and try to process it as an ELF file.  */
  int fd = open (filename, O_RDONLY);
  if (fd == -1)
    return einfo (SYS_WARN, "Could not open %s", filename);

  Elf * elf = elf_begin (fd, ELF_C_READ, NULL);
  if (elf == NULL)
    {
      close (fd);
      return einfo (WARN, "Unable to parse %s - maybe it is not an RPM or ELF file ?", filename);
    }

  bool ret = process_elf (filename, fd, elf);

  if (elf_end (elf))
    {
      close (fd);
      return einfo (WARN, "Failed to close ELF file: %s", filename);
    }

  if (close (fd))
    return einfo (SYS_WARN, "Unable to close: %s", filename);

  return ret;
}

/* Runs the given CHECKER over the sections and segments in FD.
   The filename associated with FD is assumed to be FILENAME.  */

bool
annocheck_process_extra_file (checker *     checker,
			      const char *  extra_filename,
			      const char *  original_filename,
			      int           fd)
{
  Elf * elf = elf_begin (fd, ELF_C_READ, NULL);

  if (elf == NULL)
    return einfo (WARN, "Unable to parse extra file '%s'", extra_filename);

  bool ret = true;
  if (elf_kind (elf) != ELF_K_ELF)
    return einfo (WARN, "%s: is not an ELF executable file", extra_filename);

  annocheck_data data;

  memset (& data, 0, sizeof data);
  data.full_filename = extra_filename;
  data.filename = original_filename;
  data.fd = fd;
  data.dwarf_fd = -1;
  data.elf = elf;
  data.is_32bit = gelf_getclass (elf) == ELFCLASS32;

  /* Run the start_file callback, if defined.  */
  if (checker->start_file)
    {
      push_component (checker);
      checker->start_file (& data);
      pop_component ();
    }

  size_t shstrndx;

  if (elf_getshdrstrndx (elf, & shstrndx) < 0)
    return einfo (WARN, "%s: Unable to locate string section", extra_filename);
	      
  Elf_Scn * scn = NULL;

  while ((scn = elf_nextscn (elf, scn)) != NULL)
    {
      annocheck_section  sec;

      memset (& sec, 0, sizeof sec);

      sec.scn = scn;
      read_section_header (& data, scn, & sec.shdr);
      sec.secname = elf_strptr (elf, shstrndx, sec.shdr.sh_name);	  

      /* Note - do not skip empty sections, they may still be interesting to some tools.
	 If a tool is not interested in an empty section, it can always determine this
	 in its interesting_sec() function.  */
      einfo (VERBOSE2, "%s: Examining section %s", extra_filename, sec.secname);

      if (checker->interesting_sec == NULL)
	continue;

      push_component (checker);
      if (checker->interesting_sec (& data, & sec))
	{
	  /* Delay loading the section contents until a checker expresses interest.  */
	  if (sec.data == NULL)
	    {
	      sec.data = elf_getdata (scn, NULL);
	      if (sec.data == NULL)
		ret = einfo (ERROR, "%s: Failed to read in section %s", extra_filename, sec.secname);
	    }

	  if (sec.data != NULL)
	    {
	      einfo (VERBOSE2, "is interested in section %s", sec.secname);

	      assert (checker->check_sec != NULL);
	      ret &= checker->check_sec (& data, & sec);
	    }
	}
      else
	einfo (VERBOSE2, "is not interested in %s", sec.secname);

      pop_component ();
    }

  size_t phnum, cnt;

  elf_getphdrnum (elf, & phnum);

  for (cnt = 0; cnt < phnum; ++cnt)
    {
      GElf_Phdr   mem;
      annocheck_segment seg;

      memset (& seg, 0, sizeof seg);

      seg.phdr = gelf_getphdr (elf, cnt, & mem);
      seg.number = cnt;

      einfo (VERBOSE2, "%s: considering segment %lu", extra_filename, (unsigned long) cnt);

      if (checker->interesting_seg == NULL)
	continue;

      push_component (checker);

      if (checker->interesting_seg (& data, & seg))
	{
	  /* Delay loading the contents of the segment until they are actually needed.  */
	  if (seg.data == NULL)
	    seg.data = elf_getdata_rawchunk (elf, seg.phdr->p_offset,
					     seg.phdr->p_filesz, ELF_T_BYTE);

	  assert (checker->check_seg != NULL);
	  ret &= checker->check_seg (& data, & seg);
	}
      else
	einfo (VERBOSE2, "is not interested in segment %lu", (unsigned long) cnt);

      pop_component ();
    }

  /* Run the end_file callback, if defined.  */
  if (checker->end_file)
    {
      push_component (checker);
      checker->end_file (& data);
      pop_component ();
    }

  if (elf_end (elf))
    return einfo (WARN, "Failed to close extra file: %s", extra_filename);

  return ret;
}

static bool
process_files (void)
{
  bool result = true;

  while (num_files)
    result &= process_file (files [-- num_files]);

  return result;
}

static const char *
create_tmpdir (void)
{
  static char temp[32];

  if (tmpdir != NULL)
    return tmpdir;

  /* This assert can be triggered if a tool defines an END_SCAN function
     but no START_SCAN function.  */
  assert (level == 0);

  strcpy (temp, "annocheck.data.XXXXXX");
  tmpdir = mkdtemp (temp);
  if (tmpdir == NULL)
    {
      einfo (ERROR, "Unable to make temporary data directory");
      return NULL;
    }

  tmpdir = concat (getcwd (NULL, 0), "/", tmpdir, NULL);
  einfo (VERBOSE2, "Created temporary directory for data transfer: %s", tmpdir);

  return tmpdir;
}

/* -------------------------------------------------------------------- */

int
main (int argc, const char ** argv)
{
  checker *     tool;
  bool          self_made_tmpdir = false;


  if (argv != NULL && argv[0] != NULL)
    {
      full_progname = argv[0];
      component_names[0] = lbasename (argv[0]);
    }
  else
    component_names[0] = full_progname = "annocheck";

  if (elf_version (EV_CURRENT) == EV_NONE)
    {
      einfo (FAIL, "Could not initialise libelf");
      return EXIT_FAILURE;
    }

  if (rpmReadConfigFiles (NULL, NULL) != 0)
    {
      einfo (FAIL, "Could not initialise librpm");
      return EXIT_FAILURE;
    }

  if (! process_command_line (argc, argv))
    return EXIT_FAILURE;

  if (level == 0)
    einfo (INFO, "Version %d.%02d", ANNOBIN_VERSION / 100, ANNOBIN_VERSION % 100);
  
  for (tool = first_checker; tool != NULL; tool = ((checker_internal *)(tool->internal))->next)
    if (tool->start_scan != NULL)
      {
	checker_internal * internal = (checker_internal *)(tool->internal);

	if (internal->datafile == NULL)
	  {
	    if (tmpdir == NULL)
	      {
		assert (level == 0);
		tmpdir = create_tmpdir ();
		if (tmpdir == NULL)
		  return EXIT_FAILURE;
		self_made_tmpdir = true;
	      }

	    if (tmpdir[strlen (tmpdir) - 1] == '/')
	      internal->datafile = concat (tmpdir, tool->name, NULL);
	    else
	      internal->datafile = concat (tmpdir, "/", tool->name, NULL);
	  }

	push_component (tool);
	tool->start_scan (level, internal->datafile);
	pop_component ();
      }
  
  bool res = process_files ();

  for (tool = first_checker; tool != NULL; tool = ((checker_internal *)(tool->internal))->next)
    if (tool->end_scan != NULL)
      {
	checker_internal * internal = (checker_internal *)(tool->internal);

	if (internal->datafile == NULL)
	  {
	    einfo (ERROR, "data file should have already been created");
	    continue;
	  }
	push_component (tool);
	tool->end_scan (level, internal->datafile);
	pop_component ();

	free ((char *) internal->datafile);
      }
  
  if (debug_rpm_dir)
    rmdir (debug_rpm_dir);

  if (self_made_tmpdir)
    {
      assert (level == 0);
      assert (tmpdir != 0);
      rmdir (tmpdir);
    }

  return res ? EXIT_SUCCESS : EXIT_FAILURE;
}

/* -------------------------------------------------------------------- */

bool
annocheck_add_checker (struct checker * new_checker, uint major)
{
  if (major < (ANNOBIN_VERSION / 100))
    return false;

  checker_internal * internal = XCNEW (checker_internal);

  new_checker->internal = internal;
  if (new_checker->interesting_sec)
    {
      internal->next_sec = first_sec_checker;
      first_sec_checker = new_checker;
    }

  if (new_checker->interesting_seg)
    {
      internal->next_seg = first_seg_checker;
      first_seg_checker = new_checker;
    }

  internal->next = first_checker;
  first_checker = new_checker;

  return true;
}