// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- Mode: C++ -*-
//
// Copyright (C) 2013-2025 Red Hat, Inc.

///@file

// In case we have a bad fts we include this before config.h because
// it can't handle _FILE_OFFSET_BITS.  Everything we need here is fine
// if its declarations just come first.  Also, include sys/types.h
// before fts. On some systems fts.h is not self contained.
#ifdef BAD_FTS
  #include <sys/types.h>
  #include <fts.h>
#endif

// For package configuration macros.
#include "config.h"

// In case we don't have a bad fts then we need to include fts.h after
// config.h.
#ifndef BAD_FTS
  #include <sys/types.h>
  #include <fts.h>
#endif

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <dirent.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <libgen.h>
#include <libxml/parser.h>
#include <libxml/xmlversion.h>
#include <lzma.h>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <iterator>
#include <memory>
#include <sstream>
#include <regex>

#include "abg-dwarf-reader.h"
#ifdef WITH_CTF
#include "abg-ctf-reader.h"
#endif
#ifdef WITH_BTF
#include "abg-btf-reader.h"
#endif
#include "abg-internal.h"
#include "abg-regex.h"
#include "abg-libxml-utils.h"

// <headers defining libabigail's API go under here>
ABG_BEGIN_EXPORT_DECLARATIONS

#include <abg-ir.h>
#include "abg-config.h"
#include "abg-tools-utils.h"

ABG_END_EXPORT_DECLARATIONS
// </headers defining libabigail's API>

using std::string;

namespace abigail
{

using namespace abigail::suppr;
using namespace abigail::ini;

/// @brief Namespace for a set of utility function used by tools based
/// on libabigail.
namespace tools_utils
{

/// This function needs to be called before any libabigail function.
///
/// Users of libabigail must call it prior to using any of the
/// functions of the library.
///
/// It intends to initialize the underlying libraries that might need
/// initialization, especially, libxml2, in multi-threaded environments.
void
initialize()
{
  xml::initialize();
}

/// Get the value of $libdir variable of the autotools build
/// system.  This is where shared libraries are usually installed.
///
/// @return a constant string (doesn't have to be free-ed by the
/// caller) that represent the value of the $libdir variable in the
/// autotools build system, or NULL if it's not set.
const char*
get_system_libdir()
{
#ifndef ABIGAIL_ROOT_SYSTEM_LIBDIR
#error the macro ABIGAIL_ROOT_SYSTEM_LIBDIR must be set at compile time
#endif

  static __thread const char* system_libdir(ABIGAIL_ROOT_SYSTEM_LIBDIR);
  return system_libdir;
}

/// The bitwise 'OR' operator for abidiff_status bit masks.
///
/// @param l the left hand side operand of the OR operator.
///
/// @param r the right hand side operand of the OR operator.
///
/// @return the result of the OR expression.
abidiff_status
operator|(abidiff_status l, abidiff_status r)
{return static_cast<abidiff_status>(static_cast<unsigned>(l)
				     | static_cast<unsigned>(r));}

/// The bitwise 'AND' operator for abidiff_status bit masks.
///
/// @param l the left hand side operand of the AND operator.
///
/// @param r the right hand side operand of the AND operator.
///
/// @return the result of the AND expression.
abidiff_status
operator&(abidiff_status l, abidiff_status r)
{return static_cast<abidiff_status>(static_cast<unsigned>(l)
				     & static_cast<unsigned>(r));}

/// The |= operator.
///
/// @param l the left hand side operand of the operator.
///
/// @param r the right hand side operand of the operator.
///
/// @param the resulting bit mask.
abidiff_status&
operator|=(abidiff_status&l, abidiff_status r)
{
  l = static_cast<abidiff_status>(static_cast<unsigned>(l)
				  | static_cast<unsigned>(r));
  return l;
}

/// Test if an instance of @param abidiff_status bits mask represents
/// an error.
///
/// This functions tests if the @ref ABIDIFF_ERROR bit is set in the
/// given bits mask.
///
/// @param s the bit mask to consider.
///
/// @return true iff @p s has its ABIDIFF_ERROR bit set.
bool
abidiff_status_has_error(abidiff_status s)
{return s & (ABIDIFF_ERROR | ABIDIFF_USAGE_ERROR);}

/// Test if an instance of @param abidiff_status bits mask represents
/// an abi change.
///
/// This functions tests if the @ref ABIDIFF_ABI_CHANGE bit is set in the
/// given bits mask.
///
/// @param s the bit mask to consider.
///
/// @return true iff @p s has its @ref ABIDIFF_ABI_CHANGE bit set.
bool
abidiff_status_has_abi_change(abidiff_status s)
{return s & ABIDIFF_ABI_CHANGE;}

/// Test if an instance of @param abidiff_status bits mask represents
/// an incompatible abi change.
///
/// This functions tests if the @ref ABIDIFF_INCOMPATIBLE_ABI_CHANGE
/// bit is set in the given bits mask.  Note that the this bit is set
/// then the bit @ref ABIDIFF_ABI_CHANGE must be set as well.
///
/// @param s the bit mask to consider.
///
/// @return true iff @p s has its @ref ABIDIFF_INCOMPATIBLE ABI_CHANGE
/// set.
bool
abidiff_status_has_incompatible_abi_change(abidiff_status s)
{return s & ABIDIFF_ABI_INCOMPATIBLE_CHANGE;}

#define DECLARE_STAT(st) \
  struct stat st; \
  memset(&st, 0, sizeof(st))

// <class timer stuff>

/// The private data type of the @ref timer class.
struct timer::priv
{
  timer::kind timer_kind;
  struct timeval begin_timeval;
  struct timeval end_timeval;

  priv(timer::kind k)
    : timer_kind(k),
      begin_timeval(),
      end_timeval()
  {}
}; // end struct timer::priv

/// Constructor of the @ref timer type.
///
/// @param k the kind of timer to instantiate.
timer::timer(timer::kind k)
  : priv_(new timer::priv(k))
{
  if (priv_->timer_kind == START_ON_INSTANTIATION_TIMER_KIND)
    start();
}

/// Start the timer.
///
/// To stop the timer (and record the time elapsed since the timer was
/// started), call the timer::stop member function.
///
/// @return true upon successful completion.
bool
timer::start()
{
  if (gettimeofday(&priv_->begin_timeval, 0))
    return false;
  return true;
}

/// Stop the timer.
///
/// This records the time elapsed since the timer was started using
/// the timer::start member function.
///
/// @return true upon successful completion.
bool
timer::stop()
{
  if (gettimeofday(&priv_->end_timeval, 0))
    return false;
  return true;
}

/// Get the elapsed time in seconds.
///
/// @return the time elapsed between the invocation of the methods
/// timer::start() and timer::stop, in seconds.
time_t
timer::value_in_seconds() const
{return priv_->end_timeval.tv_sec - priv_->begin_timeval.tv_sec;}

/// Get the elapsed time in hour:minutes:seconds:milliseconds.
///
/// @param hours out parameter. This is set to the number of hours elapsed.
///
/// @param minutes out parameter.  This is set to the number of minutes
/// (passed the number of hours) elapsed.
///
/// @param seconds out parameter.  This is se to the number of
/// seconds (passed the number of hours and minutes) elapsed.
///
/// @param milliseconds.  This is set ot the number of milliseconds
/// (passed the number of hours, minutes and seconds) elapsed.
///
/// @return true upon successful completion.
bool
timer::value(time_t&		hours,
	     time_t&		minutes,
	     time_t&		seconds,
	     time_t&	milliseconds) const
{
  time_t elapsed_seconds =
    priv_->end_timeval.tv_sec - priv_->begin_timeval.tv_sec;
  suseconds_t elapsed_usecs =
    ((priv_->end_timeval.tv_sec * 1000000) + priv_->end_timeval.tv_usec)
    - ((priv_->begin_timeval.tv_sec * 1000000) + priv_->begin_timeval.tv_usec);

  milliseconds = 0;

  hours = elapsed_seconds / 3600;
  minutes = (elapsed_seconds % 3600) / 60;
  seconds = (elapsed_seconds % 3600) % 60;
  if (elapsed_seconds == 0)
    milliseconds = elapsed_usecs / 1000;

  return true;
}

/// Get the elapsed time as a human-readable string.
///
/// @return the elapsed time as a human-readable string.
string
timer::value_as_string() const
{
  time_t hours = 0, minutes = 0, seconds = 0;
  time_t msecs = 0;

  value(hours, minutes, seconds, msecs);

  std::ostringstream o;

  if (hours)
    o << hours << "h";

  if (minutes)
    o << minutes << "m";

  o << seconds << "s";

  if (msecs)
    o <<msecs <<"ms";

  return o.str();
}

/// Destructor of the @ref timer type.
timer::~timer()
{
}

/// Streaming operator for the @ref timer type.
///
/// Emit a string representing the elapsed time (in a human-readable
/// manner) to an output stream.
///
/// @param o the output stream to emit the elapsed time string to.
///
/// @param t the timer to consider.
///
/// @return the output stream considered.
ostream&
operator<<(ostream& o, const timer& t)
{
  o << t.value_as_string();
  return o;
}

/// Get the stat struct (as returned by the lstat() function of the C
/// library) of a file.  Note that the function uses lstat, so that
/// callers can detect symbolic links.
///
/// @param path the path to the function to stat.
///
/// @param s the resulting stat struct.
///
/// @return true iff the stat function completed successfully.
static bool
get_stat(const string& path,
	 struct stat* s)
{return (lstat(path.c_str(), s) == 0);}

/// Tests whether a path exists;
///
/// @param path the path to test for.
///
/// @return true iff the path at @p path exist.
bool
file_exists(const string& path)
{
  DECLARE_STAT(st);

  return get_stat(path, &st);
}

/// Test that a given directory exists.
///
/// @param path the path of the directory to consider.
///
/// @return true iff a directory exists with the name @p path
bool
dir_exists(const string &path)
{return file_exists(path) && is_dir(path);}

/// Test if a given directory exists and is empty.
///
/// @param path the path of the directory to consider
bool
dir_is_empty(const string &path)
{
  if (!dir_exists(path))
    return false;

  DIR* dir = opendir(path.c_str());
  if (!dir)
    return false;

  errno = 0;
  dirent *result = readdir(dir);
  if (result == NULL && errno != 0)
    return false;

  closedir(dir);

  return result == NULL;
}

/// Test if path is a path to a regular file or a symbolic link to a
/// regular file.
///
/// @param path the path to consider.
///
/// @return true iff path is a regular path.
bool
is_regular_file(const string& path)
{
  DECLARE_STAT(st);

  if (!get_stat(path, &st))
    return false;

  if (S_ISREG(st.st_mode))
    return true;

  if (S_ISLNK(st.st_mode))
    {
      string symlink_target_path;
      if (maybe_get_symlink_target_file_path(path, symlink_target_path))
	return is_regular_file(symlink_target_path);
    }

  return false;
}

/// Test if a directory contains a CTF archive.
///
/// @param directory the directory to consider.
///
/// @param archive_prefix the prefix of the archive file.
///
/// @return true iff @p directory contains a CTF archive file.
bool
dir_contains_ctf_archive(const string& directory,
			 const string& archive_prefix)
{
  string ctf_archive = directory + "/" + archive_prefix + ".ctfa";
  if (file_exists(ctf_archive))
    return true;
  return false;
}

/// Test if an ELF file has DWARF debug info.
///
/// This function supports split debug info files as well.
///
/// @param elf_file_path the path to the ELF file to consider.
///
/// @param debug_info_root a vector of pointer to directory to look
/// for debug info, in case the file is associated to split debug
/// info.  If there is no split debug info then this vector can be
/// empty.  Note that convert_char_stars_to_char_star_stars() can be
/// used to ease the construction of this vector.
///
/// @return true iff the ELF file at @elf_file_path is an ELF file
/// that contains debug info.
bool
file_has_dwarf_debug_info(const string& elf_file_path,
			  const vector<string>& debug_info_root_paths)
{
  if (guess_file_type(elf_file_path) != FILE_TYPE_ELF)
    return false;

  environment env;
  elf::reader r(elf_file_path,
		debug_info_root_paths,
		env);

  if (r.dwarf_debug_info())
    return true;

  return false;
}

/// Test if an ELF file has CTF debug info.
///
/// This function supports split debug info files as well.
/// Linux Kernel with CTF debug information generates a CTF archive:
/// a special file containing debug information for vmlinux and its
/// modules (*.ko) files it is located by default in the Kernel build
/// directory as "vmlinux.ctfa".
///
/// @param elf_file_path the path to the ELF file to consider.
///
/// @param debug_info_root a vector of pointer to directory to look
/// for debug info, in case the file is associated to split debug
/// info.  If there is no split debug info then this vector can be
/// empty.  Note that convert_char_stars_to_char_star_stars() can be
/// used to ease the construction of this vector.
///
/// @return true iff the ELF file at @elf_file_path is an ELF file
/// that contains debug info.
bool
file_has_ctf_debug_info(const string& elf_file_path,
			const vector<string>& debug_info_root_paths)
{
  if (guess_file_type(elf_file_path) != FILE_TYPE_ELF)
    return false;

  environment env;
  elf::reader r(elf_file_path,
		debug_info_root_paths,
		env);

  if (r.find_ctf_section())
    return true;

  string vmlinux;
  if (base_name(elf_file_path, vmlinux))
    {
      string dirname;
      if (dir_name(elf_file_path, dirname)
	    && dir_contains_ctf_archive(dirname, vmlinux))
	return true;
    }

  // vmlinux.ctfa could be provided with --debug-info-dir
  for (const auto& path : debug_info_root_paths)
    if (!path.empty() && find_file_under_dir(path, "vmlinux.ctfa", vmlinux))
      return true;

  return false;
}

/// Test if an ELF file has BTFG debug info.
///
/// @param elf_file_path the path to the ELF file to consider.
///
/// @param debug_info_root a vector of pointer to directory to look
/// for debug info, in case the file is associated to split debug
/// info.  If there is no split debug info then this vector can be
/// empty.  Note that convert_char_stars_to_char_star_stars() can be
/// used to ease the construction of this vector.
///
/// @return true iff the ELF file at @elf_file_path is an ELF file
/// that contains debug info.
bool
file_has_btf_debug_info(const string& elf_file_path,
			const vector<string>& debug_info_root_paths)
{
    if (guess_file_type(elf_file_path) != FILE_TYPE_ELF)
    return false;

  environment env;
  elf::reader r(elf_file_path, debug_info_root_paths, env);

  if (r.find_btf_section())
    return true;

  return false;
}

/// Tests if a given path is a directory or a symbolic link to a
/// directory.
///
/// @param path the path to test for.
///
/// @return true iff @p path is a directory.
bool
is_dir(const string& path)
{
  DECLARE_STAT(st);

  if (!get_stat(path, &st))
    return false;

  if (S_ISDIR(st.st_mode))
    return true;

  if (S_ISLNK(st.st_mode))
    {
      string symlink_target_path;
      if (maybe_get_symlink_target_file_path(path, symlink_target_path))
	{
	  if (!get_stat(path, &st))
	    return false;

	  if (S_ISDIR(st.st_mode))
	    return true;
	}
    }
  return false;
}

static const char* ANONYMOUS_STRUCT_INTERNAL_NAME =   "__anonymous_struct__";
static const char* ANONYMOUS_UNION_INTERNAL_NAME =    "__anonymous_union__";
static const char* ANONYMOUS_ENUM_INTERNAL_NAME =     "__anonymous_enum__";
static const char* ANONYMOUS_SUBRANGE_INTERNAL_NAME = "__anonymous_range__";

static int ANONYMOUS_STRUCT_INTERNAL_NAME_LEN =
  strlen(ANONYMOUS_STRUCT_INTERNAL_NAME);

static int ANONYMOUS_UNION_INTERNAL_NAME_LEN =
  strlen(ANONYMOUS_UNION_INTERNAL_NAME);

static int ANONYMOUS_ENUM_INTERNAL_NAME_LEN =
  strlen(ANONYMOUS_ENUM_INTERNAL_NAME);

/// Getter of the prefix for the name of anonymous structs.
///
/// @reaturn the prefix for the name of anonymous structs.
const char*
get_anonymous_struct_internal_name_prefix()
{return ANONYMOUS_STRUCT_INTERNAL_NAME;}

/// Getter of the prefix for the name of anonymous unions.
///
/// @reaturn the prefix for the name of anonymous unions.
const char*
get_anonymous_union_internal_name_prefix()
{return ANONYMOUS_UNION_INTERNAL_NAME;}

/// Getter of the prefix for the name of anonymous enums.
///
/// @reaturn the prefix for the name of anonymous enums.
const char*
get_anonymous_enum_internal_name_prefix()
{return ANONYMOUS_ENUM_INTERNAL_NAME;}

/// Getter of the prefix for the name of anonymous range.
///
/// @reaturn the prefix for the name of anonymous range.
const char*
get_anonymous_subrange_internal_name_prefix()
{return ANONYMOUS_SUBRANGE_INTERNAL_NAME;}

/// Compare two fully qualified decl names by taking into account that
/// they might have compontents that are anonymous types/namespace names.
///
/// For instance:
///
/// __anonymous_struct__1::foo and __anonymous_struct__2::foo are
/// considered being equivalent qualified names because both are data
/// members that belong to anonymous structs.  The anonymous structs
/// are numbered so that we can tell them appart (and look them up)
/// where there are several of them in the same scope.  But during
/// comparison, for various purposes, we want to consider them as
/// equivalent.
///
/// Similarly, __anonymous_struct__1::foo::__anonymous_struct__2::bar
/// and __anonymous_struct__10::foo::__anonymous_struct__11::bar are
/// equivalent.
///
/// But __anonymous_struct__1::foo::__anonymous_struct__2::bar and
/// __anonymous_struct__10::foo::__anonymous_union__11::bar are not
/// equivalent because the former designates a member of an anonymous
/// struct and the latter designates a member of an anonymous union.
///
/// So this function handles those cases.
///
/// @param l the name of the first (left hand side) decl to consider.
///
/// @param r the name of the second (right hand side) decl to consider.
///
/// @return true iff @p l is equivalent to @p r when taking into
/// account the anonymous scopes that both might have and if they
/// might be anonymous themselves.
bool
decl_names_equal(const string& l, const string& r)
{
  string::size_type l_pos1 = 0, r_pos1 = 0;
  const string::size_type l_length = l.length(), r_length = r.length();

  while (l_pos1 < l_length && r_pos1 < r_length)
    {
      string::size_type l_pos2 = l.find("::", l_pos1);
      string::size_type r_pos2 = r.find("::", r_pos1);
      if (l_pos2 == string::npos)
	l_pos2 = l_length;
      if (r_pos2 == string::npos)
	r_pos2 = r_length;

      if (l.compare(l_pos1, l_pos2 - l_pos1, r,
		    r_pos1, r_pos2 - r_pos1)
	  && (l.compare(l_pos1,
			ANONYMOUS_STRUCT_INTERNAL_NAME_LEN,
			ANONYMOUS_STRUCT_INTERNAL_NAME)
	      || r.compare(r_pos1,
			   ANONYMOUS_STRUCT_INTERNAL_NAME_LEN,
			   ANONYMOUS_STRUCT_INTERNAL_NAME))
	  && (l.compare(l_pos1,
			ANONYMOUS_UNION_INTERNAL_NAME_LEN,
			ANONYMOUS_UNION_INTERNAL_NAME)
	      || r.compare(r_pos1,
			   ANONYMOUS_UNION_INTERNAL_NAME_LEN,
			   ANONYMOUS_UNION_INTERNAL_NAME))
	  && (l.compare(l_pos1,
			ANONYMOUS_ENUM_INTERNAL_NAME_LEN,
			ANONYMOUS_ENUM_INTERNAL_NAME)
	      || r.compare(r_pos1,
			   ANONYMOUS_ENUM_INTERNAL_NAME_LEN,
			   ANONYMOUS_ENUM_INTERNAL_NAME)))
	return false;

      l_pos1 = l_pos2 == l_length ? l_pos2 : l_pos2 + 2;
      r_pos1 = r_pos2 == r_length ? r_pos2 : r_pos2 + 2;
    }

  return (l_pos1 == l_length) == (r_pos1 == r_length);
}

/// If a given file is a symbolic link, get the canonicalized absolute
/// path to the target file.
///
/// @param file_path the path to the file to consider.
///
/// @param target_path this parameter is set by the function to the
/// canonicalized path to the target file, if @p file_path is a
/// symbolic link.  In that case, the function returns true.
///
/// @return true iff @p file_path is a symbolic link.  In that case,
/// the function sets @p target_path to the canonicalized absolute
/// path of the target file.
bool
maybe_get_symlink_target_file_path(const string& file_path,
				   string& target_path)
{
  DECLARE_STAT(st);

  if (!get_stat(file_path, &st))
    return false;

  char *link_target_path = realpath(file_path.c_str(), NULL);
  if (!link_target_path)
    return false;

  target_path = link_target_path;
  free(link_target_path);
  return true;
}

/// Return the directory part of a file path.
///
/// @param path the file path to consider
///
/// @param dirnam the resulting directory part, or "." if the couldn't
/// figure out anything better (for now; maybe we should do something
/// better than this later ...).
///
/// @param keep_separator_at_end if true, then keep the separator at
/// the end of the resulting dir name.
///
/// @return true upon successful completion, false otherwise (okay,
/// for now it always return true, but that might change in the future).
bool
dir_name(string const& path,
	 string& dir_name,
	 bool keep_separator_at_end)
{
  if (path.empty())
    {
      dir_name = ".";
      return true;
    }

  char *p = strdup(path.c_str());
  char *r = ::dirname(p);
  dir_name = r;
  free(p);
  if (keep_separator_at_end
      && dir_name.length() < path.length())
    dir_name += "/";
  return true;
}

/// Return the file name part of a file part.
///
/// @param path the file path to consider.
///
/// @param file_name the name part of the file to consider.
///
///@return true upon successful completion, false otherwise (okay it
///always return true for now, but that might change in the future).
bool
base_name(string const &path,
	 string& file_name)
{
  if (path.empty())
    {
      file_name = ".";
      return true;
    }

  char *p = strdup(path.c_str());
  char *f = ::basename(p);
  file_name = f;
  free(p);
  return true;
}

/// Return the real path of a given path.
///
/// The real path of path 'foo_path' is the same path as foo_path, but
/// with symlinks and relative paths resolved.
///
/// @param path the path to consider.
///
/// @param result the computed real_path;
void
real_path(const string&path, string& result)
{
  if (path.empty())
    {
      result.clear();
      return;
    }

  char *realp = realpath(path.c_str(), NULL);
  if (realp)
    {
      result = realp;
      free(realp);
    }
}

/// Ensures #dir_path is a directory and is created.  If #dir_path is
/// not created, this function creates it.
///
/// \return true if #dir_path is a directory that is already present,
/// of if the function has successfuly created it.
bool
ensure_dir_path_created(const string& dir_path)
{
  struct stat st;
  memset(&st, 0, sizeof (st));

  int stat_result = 0;

  stat_result = stat(dir_path.c_str(), &st);
  if (stat_result == 0)
    {
      // A file or directory already exists with the same name.
      if (!S_ISDIR (st.st_mode))
	return false;
      return true;
    }

  string cmd;
  cmd = "mkdir -p " + dir_path;

  if (system(cmd.c_str()))
    return false;

  return true;
}

/// Ensures that the parent directory of #path is created.
///
/// \return true if the parent directory of #path is already present,
/// or if this function has successfuly created it.
bool
ensure_parent_dir_created(const string& path)
{
  bool is_ok = false;

  if (path.empty())
    return is_ok;

  string parent;
  if (dir_name(path, parent))
    is_ok = ensure_dir_path_created(parent);

  return is_ok;
}

/// Emit a prefix made of the name of the program which is emitting a
/// message to an output stream.
///
/// The prefix is a string which looks like:
///
///   "<program-name> : "
///
/// @param prog_name the name of the program to use in the prefix.
/// @param out the output stream where to emit the prefix.
///
/// @return the output stream where the prefix was emitted.
ostream&
emit_prefix(const string& prog_name, ostream& out)
{
  if (!prog_name.empty())
    out << prog_name << ": ";
  return out;
}

/// Check if a given path exists and is readable.
///
/// @param path the path to consider.
///
/// @param out the out stream to report errors to.
///
/// @return true iff path exists and is readable.
bool
check_file(const string& path, ostream& out, const string& prog_name)
{
  if (!file_exists(path))
    {
      emit_prefix(prog_name, out) << "file " << path << " does not exist\n";
      return false;
    }

  if (!is_regular_file(path))
    {
      emit_prefix(prog_name, out) << path << " is not a regular file\n";
      return false;
    }

  return true;
}

/// Check if a given path exists, is readable and is a directory.
///
/// @param path the path to consider.
///
/// @param out the out stream to report errors to.
///
/// @param prog_name the program name on behalf of which to report the
/// error, if any.
///
/// @return true iff @p path exists and is for a directory.
bool
check_dir(const string& path, ostream& out, const string& prog_name)
{
  if (!file_exists(path))
    {
      emit_prefix(prog_name, out) << "path " << path << " does not exist\n";
      return false;
    }

  if (!is_dir(path))
    {
      emit_prefix(prog_name, out) << path << " is not a directory\n";
      return false;
    }

  return true;
}

/// Test if a given string ends with a particular suffix.
///
/// @param str the string to consider.
///
/// @param suffix the suffix to test for.
///
/// @return true iff string @p str ends with suffix @p suffix.
bool
string_ends_with(const string& str, const string& suffix)
{
  string::size_type str_len = str.length(), suffix_len = suffix.length();

  if (str_len < suffix_len)
    return false;
  return str.compare(str_len - suffix_len, suffix_len, suffix) == 0;
}

/// Test if a given string begins with a particular prefix.
///
/// @param str the string consider.
///
/// @param prefix the prefix to look for.
///
/// @return true iff string @p str begins with prefix @p prefix.
bool
string_begins_with(const string& str, const string& prefix)
{
  if (str.empty())
    return false;

  if (prefix.empty())
    return true;

  string::size_type prefix_len = prefix.length();
  if (prefix_len > str.length())
    return false;

  return str.compare(0, prefix.length(), prefix) == 0;
}

/// Test if a string is made of ascii characters.
///
/// @param str the string to consider.
///
/// @return true iff @p str is made of ascii characters.
bool
string_is_ascii(const string& str)
{
  for (string::const_iterator i = str.begin(); i != str.end(); ++i)
    if (!isascii(*i))
      return false;

  return true;
}

/// Test if a string is made of ascii characters which are identifiers
/// acceptable in C or C++ programs.
///
///
/// In the C++ spec, [lex.charset]/2, we can read:
///
/// "if the hexadecimal value for a universal-character-name [...]  or
///  string literal corresponds to a control character (in either of
///  the ranges 0x00-0x1F or 0x7F-0x9F, both inclusive) [...] the
///  program is ill-formed."
///
/// @param str the string to consider.
///
/// @return true iff @p str is made of ascii characters, and is an
/// identifier.
bool
string_is_ascii_identifier(const string& str)
{
  for (string::const_iterator i = str.begin(); i != str.end(); ++i)
    {
      unsigned char c = *i;
    if (!isascii(c)
	|| (c <= 0x1F) // Rule out control characters
	|| (c >= 0x7F && c <= 0x9F)) // Rule out special extended
				     // ascii characters.
      return false;
    }

  return true;
}

/// Split a given string into substrings, given some delimiters.
///
/// @param input_string the input string to split.
///
/// @param delims a string containing the delimiters to consider.
///
/// @param result a vector of strings containing the splitted result.
///
/// @return true iff the function found delimiters in the input string
/// and did split it as a result.  Note that if no delimiter was found
/// in the input string, then the input string is added at the end of
/// the output vector of strings.
bool
split_string(const string& input_string,
	     const string& delims,
	     vector<string>& result)
{
  size_t current = 0, next;
  bool did_split = false;

  do
    {
      // Trim leading white spaces
      while (current < input_string.size() && isspace(input_string[current]))
	++current;

      if (current >= input_string.size())
	break;

      next = input_string.find_first_of(delims, current);
      if (next == string::npos)
	{
	  string s = input_string.substr(current);
	  if (!s.empty())
	    result.push_back(input_string.substr(current));
	  did_split = (current != 0);
	  break;
	}
      string s = input_string.substr(current, next - current);
      if (!s.empty())
	{
	  result.push_back(input_string.substr(current, next - current));
	  did_split = true;
	}
      current = next + 1;
    }
  while (next != string::npos);

  return did_split;
}

/// Get the suffix of a string, given a prefix to consider.
///
/// @param input_string the input string to consider.
///
/// @param prefix the prefix of the input string to consider.
///
/// @param suffix output parameter. This is set by the function to the
/// the computed suffix iff a suffix was found for prefix @p prefix.
///
/// @return true iff the function could find a prefix for the suffix
/// @p suffix in the input string @p input_string.
bool
string_suffix(const string&	input_string,
	      const string&	prefix,
	      string&		suffix)
{
  // Some basic sanity check before we start hostilities.
  if (prefix.length() >= input_string.length())
    return false;

  if (input_string.compare(0, prefix.length(), prefix) != 0)
    // The input string does not start with the string contained in
    // the prefix parameter.
    return false;

  suffix = input_string.substr(prefix.length());
  return true;
}

/// Return the prefix that is common to two strings.
///
/// @param s1 the first input string to consider.
///
/// @param s2 the second input string to consider.
///
/// @param result output parameter.  The resulting common prefix found
/// between @p s1 and @p s2.  This is set iff the function returns
/// true.
///
/// @return true iff @p result was set by this function with the
/// common prefix of @p s1 and @p s2.
static bool
common_prefix(const string& s1, const string& s2, string &result)
{
  if (s1.length() == 0 || s2.length() == 0)
    return false;

  result.clear();
  for (size_t i = 0; i < s1.length() && i< s2.length(); ++i)
    if (s1[i] == s2[i])
      result += s1[i];
    else
      break;

  return !result.empty();
}

/// Find the prefix common to a *SORTED* vector of strings.
///
/// @param input_strings a lexycographically sorted vector of
/// strings.  Please note that this vector absolutely needs to be
/// sorted for the function to work correctly.  Otherwise the results
/// are going to be wrong.
///
/// @param prefix output parameter.  This is set by this function with
/// the prefix common to the strings found in @p input_strings, iff
/// the function returns true.
///
/// @return true iff the function could find a common prefix to the
/// strings in @p input_strings.
bool
sorted_strings_common_prefix(vector<string>& input_strings, string& prefix)
{
  string prefix_candidate;
  bool found_prefix = false;

  if (input_strings.size() == 1)
    {
      if (dir_name(input_strings.front(), prefix,
		   /*keep_separator_at_end=*/true))
	return true;
      return false;
    }

  string cur_str;
  for (vector<string>::const_iterator i = input_strings.begin();
       i != input_strings.end();
       ++i)
    {
      dir_name(*i, cur_str, /*keep_separator_at_end=*/true);
      if (prefix_candidate.empty())
	{
	  prefix_candidate = cur_str;
	  continue;
	}

      string s;
      if (common_prefix(prefix_candidate, cur_str, s))
	{
	  ABG_ASSERT(!s.empty());
	  prefix_candidate = s;
	  found_prefix = true;
	}
    }

  if (found_prefix)
    {
      prefix = prefix_candidate;
      return true;
    }

  return false;
}

/// Return the version string of the library.
///
/// @return the version string of the library.
string
get_library_version_string()
{
  string major, minor, revision, version_string, suffix;
  abigail::abigail_get_library_version(major, minor, revision, suffix);
  version_string = major + "." + minor + "." + revision + suffix;
  return version_string;
}

/// Return the version string for the ABIXML format.
///
/// @return the version string of the ABIXML format.
string
get_abixml_version_string()
{
  string major, minor, version_string;
  abigail::abigail_get_abixml_version(major, minor);
  version_string = major + "." + minor;
  return version_string;
}

/// Execute a shell command and returns its output.
///
/// @param cmd the shell command to execute.
///
/// @param lines output parameter.  This is set with the lines that
/// constitute the output of the process that executed the command @p
/// cmd.
///
/// @return true iff the command was executed properly and no error
/// was encountered.
bool
execute_command_and_get_output(const string& cmd, vector<string>& lines)
{
  if (cmd.empty())
    return false;

  FILE *stream=
    popen(cmd.c_str(),
	  /*open 'stream' in
	    read-only mode: type=*/"r");

  if (stream == NULL)
    return false;

  string result;

#define TMP_BUF_LEN 1024 + 1
  char tmp_buf[TMP_BUF_LEN];
  memset(tmp_buf, 0, TMP_BUF_LEN);

  while (fgets(tmp_buf, TMP_BUF_LEN, stream))
    {
      lines.push_back(tmp_buf);
      memset(tmp_buf, 0, TMP_BUF_LEN);
    }

  if (pclose(stream) == -1)
    return false;

  return true;
}

/// Get a vector of arguments from a string containing a
/// comma-separated list of those arguments.
///
/// @param input_str the input string containing the comma-separated
/// list of arguments The input string has the form
/// "option=arg1,arg2,arg3,arg4".
///
/// @param option if the content of the input string @p input_str is
/// "option=arg1,arg2,arg3", then this parameter should be "option".
///
/// @param arguments this is set by the fonction the the arguments
/// that were a comma-separated list of arguments on the right hand
/// side of the '=' sign in the string @p input_str.
void
get_comma_separated_args_of_option(const string& input_str,
				   const string& option,
				   vector<string>& arguments)
{
  string s = input_str;

  string_suffix(s, option, s);
  if (string_begins_with(s, "\""))
    s = s.substr(1);
  if (string_ends_with(s, "\""))
    s = s.substr(0, s.size() - 1);
  split_string(s, ",", arguments);
}

/// Get the SONAMEs of the DSOs advertised as being "provided" by a
/// given RPM.  That set can be considered as being the set of
/// "public" DSOs of the RPM.
///
/// This runs the command "rpm -qp --provides <rpm> | grep .so" and
/// filters its result.
///
/// @param rpm_path the path to the RPM to consider.
///
/// @param provided_dsos output parameter.  This is set to the set of
/// SONAMEs of the DSOs advertised as being provided by the RPM
/// designated by @p rpm_path.
///
/// @return true iff we could successfully query the RPM to see what
/// DSOs it provides.
bool
get_dsos_provided_by_rpm(const string& rpm_path, set<string>& provided_dsos)
{
  vector<string> query_output;
  // We don't check the return value of this command because on some
  // system, the command can issue errors but still emit a valid
  // output.  We'll rather rely on the fact that the command emits a
  // valid output or not.
  execute_command_and_get_output("rpm -qp --provides "
				 + rpm_path + " 2> /dev/null | grep .so",
				 query_output);

  for (vector<string>::const_iterator line = query_output.begin();
       line != query_output.end();
       ++line)
    {
      string dso = line->substr(0, line->find('('));
      dso = trim_white_space(dso);
      if (!dso.empty())
	provided_dsos.insert(dso);
    }
  return true;
}

/// Remove spaces at the beginning and at the end of a given string.
///
/// @param str the input string to consider.
///
/// @return the @p str string with leading and trailing white spaces removed.
string
trim_white_space(const string& str)
{
  if (str.empty())
    return "";

  string result;
  string::size_type start, end;
  for (start = 0; start < str.length(); ++start)
    if (!isspace(str[start]))
      break;

  for (end = str.length() - 1; end > 0; --end)
    if (!isspace(str[end]))
      break;

  result = str.substr(start, end - start + 1);
  return result;
}

/// Remove white spaces from a string.
///
/// @param str the string to remove the white spaces from.
///
/// @return true iff any white space was removed from @p str.
bool
remove_white_spaces(string& str)
{
  if (str.erase(std::remove_if(str.begin(), str.end(), isspace),
		str.end()) == str.end())
    return false;
  return true;
}

/// Getter of a global instance of std::regex that matches numerical
/// litterals.
///
/// The regular expression is the following: "([0-9]+)([uUlL])+"
///
/// The purpose of this is to compile the regular expression only
/// once, the first time this function is invoked.  Subsquent
/// invocations return the already compiled regular expression.
///
/// @return a reference to a global instance of std::regex
static std::regex&
get_litteral_regex()
{
  static std::regex re("([0-9]+)([uUlL])+");
  return re;
}

/// Normalize the numerical litteral in a string.
///
/// Basically, if a litteral is present as 10u or 100UL, change it
/// into 10 or 100.
///
/// @param str the string to normalize.
///
/// @return true iff @p str was normalized.
bool
normalize_litterals(string& str)
{
  bool begin_pattern = false, middle_pattern = false, found_litteral = false;
  for (string::iterator i = str.begin(); i < str.end(); ++i)
    {
      if (isdigit(*i))
	begin_pattern = true;
      else
	{
	  if (begin_pattern
	      && (*i == 'u' || *i == 'U' || *i == 'l' || *i == 'L'))
	    middle_pattern = true;
	  else
	    {
	      if (middle_pattern)
		{
		  found_litteral = true;
		  break;
		}
	    }
	}
    }

  if (found_litteral)
    {
      std::regex& re = get_litteral_regex();
      str = std::regex_replace(str, re, "$1");
      return true;
    }

  return false;
}

/// Remove a string of pattern in front of a given string.
///
/// For instance, consider this string:
///    "../../../foo"
///
/// The pattern "../" is repeated three times in front of the
/// sub-string "foo".  Thus, the call:
///    trim_leading_string("../../../foo", "../")
/// will return the string "foo".
///
/// @param from the string to trim the leading repetition of pattern from.
///
/// @param to_trim the pattern to consider (and to trim).
///
/// @return the resulting string where the leading patter @p to_trim
/// has been removed from.
string
trim_leading_string(const string& from, const string& to_trim)
{
  string str = from;

  while (string_begins_with(str, to_trim))
    string_suffix(str, to_trim, str);
  return str;
}

/// Convert a vector<char*> into a vector<char**>.
///
/// @param char_stars the input vector.
///
/// @param char_star_stars the output vector.
void
convert_char_stars_to_char_star_stars(const vector<char*> &char_stars,
				      vector<char**>& char_star_stars)
{
  for (vector<char*>::const_iterator i = char_stars.begin();
       i != char_stars.end();
       ++i)
    char_star_stars.push_back(const_cast<char**>(&*i));
}

/// The private data of the @ref temp_file type.
struct temp_file::priv
{
  char*			   path_template_;
  int			   fd_;
  shared_ptr<std::fstream> fstream_;

  priv()
  {
    const char* templat = "/tmp/libabigail-tmp-file-XXXXXX";
    int s = strlen(templat);
    path_template_ = new char[s + 1];
    memset(path_template_, 0, s + 1);
    memcpy(path_template_, templat, s);

    fd_ = mkstemp(path_template_);
    if (fd_ == -1)
      return;

    fstream_.reset(new std::fstream(path_template_,
				    std::ios::trunc
				    | std::ios::in
				    | std::ios::out));
  }

  ~priv()
  {
    if (fd_ && fd_ != -1)
      {
	fstream_.reset();
	close(fd_);
	remove(path_template_);
      }
    delete [] path_template_;
  }
};

/// Default constructor of @ref temp_file.
///
/// It actually creates the temporary file.
temp_file::temp_file()
  : priv_(new priv)
{}

/// Test if the temporary file has been created and is usable.
///
/// @return true iff the temporary file has been created and is
/// useable.
bool
temp_file::is_good() const
{return priv_->fstream_->good();}

/// Return the path to the temporary file.
///
/// @return the path to the temporary file if it's usable, otherwise
/// return nil.
const char*
temp_file::get_path() const
{
  if (is_good())
    return priv_->path_template_;

  return 0;
}

/// Get the fstream to the temporary file.
///
/// Note that the current process is aborted if this member function
/// is invoked on an instance of @ref temp_file that is not usable.
/// So please test that the instance is usable by invoking the
/// temp_file::is_good() member function on it first.
///
/// @return the fstream to the temporary file.
std::fstream&
temp_file::get_stream()
{
  ABG_ASSERT(is_good());
  return *priv_->fstream_;
}

/// Create the temporary file and return it if it's usable.
///
/// @return the newly created temporary file if it's usable, nil
/// otherwise.
temp_file_sptr
temp_file::create()
{
  temp_file_sptr result(new temp_file);
  if (result->is_good())
    return result;

  return temp_file_sptr();
}

/// Get a pseudo random number.
///
/// @return a pseudo random number.
size_t
get_random_number()
{
  static __thread bool initialized = false;

  if (!initialized)
    {
      srand(time(NULL));
      initialized = true;
    }

  return rand();
}

/// Get a pseudo random number as string.
///
/// @return a pseudo random number as string.
string
get_random_number_as_string()
{
  std::ostringstream o;
  o << get_random_number();

  return o.str();
}

ostream&
operator<<(ostream& output,
	   file_type r)
{
  string repr;

  switch(r)
    {
    case FILE_TYPE_UNKNOWN:
      repr = "unknown file type";
      break;
    case FILE_TYPE_NATIVE_BI:
      repr = "native binary instrumentation file type";
      break;
    case FILE_TYPE_ELF:
      repr = "ELF file type";
      break;
    case FILE_TYPE_AR:
      repr = "archive file type";
      break;
    case FILE_TYPE_XML_CORPUS:
      repr = "native XML corpus file type";
      break;
    case FILE_TYPE_XML_CORPUS_GROUP:
      repr = "native XML corpus group file type";
      break;
    case FILE_TYPE_RPM:
      repr = "RPM file type";
      break;
    case FILE_TYPE_SRPM:
      repr = "SRPM file type";
      break;
    case FILE_TYPE_DEB:
      repr = "Debian binary file type";
      break;
    case FILE_TYPE_DIR:
      repr = "Directory type";
      break;
    case FILE_TYPE_TAR:
      repr = "GNU tar archive type";
      break;
    case FILE_TYPE_XZ:
      repr = "XZ compressed file";
    }

  output << repr;
  return output;
}

/// The kind of compression we want a de-compression std::streambuf
/// for.
///
/// This enum must be amended to add support for new compression
/// schemes, especially whenever a new enumerator is added to the enum
/// @ref file_type.
enum compression_kind
{
  COMPRESSION_KIND_UNKNOWN,
  /// The LZMA compression (used by the xz tool).
  COMPRESSION_KIND_XZ
}; //end enum compression_kind

/// Test if one of the enumerators of @ref file_type designates a
/// compression scheme.
///
/// This helper function needs to be updated whenever a new
/// compression-related enumerator is added to @ref file_type.
///
/// @return the kind of compression designated by @p t.
static compression_kind
is_compressed_file_type(file_type t)
{
  if (t == FILE_TYPE_XZ)
    return COMPRESSION_KIND_XZ;

  return COMPRESSION_KIND_UNKNOWN;
}

/// Guess the type of the content of an input stream.
///
/// @param in the input stream to guess the content type for.
///
/// @return the type of content guessed.
file_type
guess_file_type(istream& in)
{
  const unsigned BUF_LEN = 264;
  const unsigned NB_BYTES_TO_READ = 263;

  unsigned char buf[BUF_LEN];
  memset(buf, 0, BUF_LEN);

  std::streampos initial_pos = in.tellg();
  in.read(reinterpret_cast<char*>(buf), NB_BYTES_TO_READ);
  in.seekg(initial_pos);

  if (in.gcount() < 4 || in.bad())
    return FILE_TYPE_UNKNOWN;

  if (buf[0] == 0x7f
      && buf[1] == 'E'
      && buf[2] == 'L'
      && buf[3] == 'F')
    return FILE_TYPE_ELF;

  // XZ format.  Described at
  // https://tukaani.org/xz/xz-file-format.txt.
  if (in.gcount() >= 6
      && buf[0] == 0xFD
      && buf[1] == '7'
      && buf[2] == 'z'
      && buf[3] == 'X'
      && buf[4] == 'Z'
      && buf[5] == 0)
    return FILE_TYPE_XZ;

  if (buf[0] == '!'
      && buf[1] == '<'
      && buf[2] == 'a'
      && buf[3] == 'r'
      && buf[4] == 'c'
      && buf[5] == 'h'
      && buf[6] == '>')
    {
      if (strstr(reinterpret_cast<char*>(buf), "debian-binary"))
	return FILE_TYPE_DEB;
      else
	return FILE_TYPE_AR;
    }

  if (buf[0] == '<'
      && buf[1] == 'a'
      && buf[2] == 'b'
      && buf[3] == 'i'
      && buf[4] == '-'
      && buf[5] == 'i'
      && buf[6] == 'n'
      && buf[7] == 's'
      && buf[8] == 't'
      && buf[9] == 'r'
      && buf[10] == ' ')
    return FILE_TYPE_NATIVE_BI;

  if (buf[0]     == '<'
      && buf[1]  == 'a'
      && buf[2]  == 'b'
      && buf[3]  == 'i'
      && buf[4]  == '-'
      && buf[5]  == 'c'
      && buf[6]  == 'o'
      && buf[7]  == 'r'
      && buf[8]  == 'p'
      && buf[9]  == 'u'
      && buf[10] == 's'
      && buf[11] == '-'
      && buf[12] == 'g'
      && buf[13] == 'r'
      && buf[14] == 'o'
      && buf[15] == 'u'
      && buf[16] == 'p'
      && buf[17] == ' ')
    return FILE_TYPE_XML_CORPUS_GROUP;

  if (buf[0]     == '<'
      && buf[1]  == 'a'
      && buf[2]  == 'b'
      && buf[3]  == 'i'
      && buf[4]  == '-'
      && buf[5]  == 'c'
      && buf[6]  == 'o'
      && buf[7]  == 'r'
      && buf[8]  == 'p'
      && buf[9]  == 'u'
      && buf[10] == 's'
      && buf[11] == ' ')
    return FILE_TYPE_XML_CORPUS;

  // Detect RPM format.  Documented at
  // http://ftp.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html.
  if ((unsigned char) buf[0]    == 0xed
      && (unsigned char) buf[1] == 0xab
      && (unsigned char) buf[2] == 0xee
      && (unsigned char) buf[3] == 0xdb)
    {
        if (buf[7] == 0x00)
          return FILE_TYPE_RPM;
        else if (buf[7] == 0x01)
          return FILE_TYPE_SRPM;
        else
          return FILE_TYPE_UNKNOWN;
    }

  if (buf[257]    == 'u'
      && buf[258] == 's'
      && buf[259] == 't'
      && buf[260] == 'a'
      && buf[261] == 'r')
    return FILE_TYPE_TAR;

  return FILE_TYPE_UNKNOWN;
}

/// The factory of an std::streambuf aimed at decompressing data
/// coming from an input stream compressed with a particular
/// compression scheme.
///
/// This function must be amended to add support for new compression
/// schemes.
///
/// @param compressed_input the compressed input to create the
/// decompressor std::streambuf for.
///
/// @param compr the compression scheme kind.
///
/// @return a pointer to the std::streambuf to use for decompression.
/// If the compression scheme is not supported, the function returns
/// nil.
static shared_ptr<std::streambuf>
get_decompressed_streambuf(std::istream& compressed_input,
			   compression_kind compr)
{
  shared_ptr<std::streambuf> result;

  switch(compr)
    {
    case COMPRESSION_KIND_UNKNOWN:
      ABG_ASSERT_NOT_REACHED;
      break;

    case COMPRESSION_KIND_XZ:
    shared_ptr<std::streambuf> r(new xz_decompressor_type(compressed_input));
    result = r;
    break;
    };

  return result;
};// end struct compression_handler_type

/// Guess the type of the content of an file.
///
/// @param file_path the path to the file to consider.
///
/// @param look_through_compression if true, then decompress the file
/// and try to guess the type of the decompressed content.  Otherwise,
/// just return that it's a compressed type.
///
/// @return the type of content guessed.
file_type
guess_file_type(const string& file_path, bool look_through_compression)
{
  if (is_dir(file_path))
    return FILE_TYPE_DIR;

  if (string_ends_with(file_path, ".tar")
      || string_ends_with(file_path, ".tar.gz")
      || string_ends_with(file_path, ".tgz")
      || string_ends_with(file_path, ".tar.bz2")
      || string_ends_with(file_path, ".tbz2")
      || string_ends_with(file_path, ".tbz")
      || string_ends_with(file_path, ".tb2")
      || string_ends_with(file_path, ".tar.xz")
      || string_ends_with(file_path, ".txz")
      || string_ends_with(file_path, ".tar.lzma")
      || string_ends_with(file_path, ".tar.lz")
      || string_ends_with(file_path, ".tlz")
      || string_ends_with(file_path, ".tar.Z")
      || string_ends_with(file_path, ".taz")
      || string_ends_with(file_path, ".tz"))
    return FILE_TYPE_TAR;

  file_type r = FILE_TYPE_UNKNOWN;
  compression_kind compr_kind = COMPRESSION_KIND_UNKNOWN;
  shared_ptr<std::streambuf> decompressor_streambuf;

  if (string_ends_with(file_path, ".lzma")
      || string_ends_with(file_path, ".lz")
      || string_ends_with(file_path, ".xz"))
    {
      r = FILE_TYPE_XZ;
      compr_kind = COMPRESSION_KIND_XZ;
    }
  // else if there are other compression schemes supported, recognize
  // their file suffix here!

  if (is_compressed_file_type(r) && !look_through_compression)
    return r;

  do
    {
      shared_ptr<ifstream> input_fstream(new ifstream(file_path.c_str(),
						      ifstream::binary));
      shared_ptr<istream> input_stream = input_fstream;

      if (compr_kind != COMPRESSION_KIND_UNKNOWN)
	decompressor_streambuf = get_decompressed_streambuf(*input_stream,
							    compr_kind);

      if (decompressor_streambuf)
	input_stream.reset(new istream(decompressor_streambuf.get()));

      r = guess_file_type(*input_stream);

      input_fstream->close();

      if (!decompressor_streambuf)
	{
	  // So we haven't attempted to decompress the input stream.
	  //
	  // Have we found out that it was compressed nonetheless?
	  compr_kind = is_compressed_file_type(r);
	  if (compr_kind)
	    {
	      if (!look_through_compression)
		// The caller wants us to report that this file is
		// compressed.
		return r;

	      // We found out the input file is compressed, so we do
	      // have the means to decompress it.  However, we haven't
	      // yet gotten the de-compressor; that might be because
	      // we detected the compression just by looking at the
	      // file name suffix.  Let's go back to calling
	      // get_decompressed_streambuf again to get the
	      // decompressor.
	      ;
	    }
	  else
	    // No the file is not compressed let's get out of here.
	    break;
	}
    } while (!decompressor_streambuf && compr_kind);

  return r;
}

/// Get the package name of a .deb package.
///
/// @param str the string containing the .deb NVR.
///
/// @param name output parameter.  This is set with the package name
/// of the .deb package iff the function returns true.
///
/// @return true iff the function successfully finds the .deb package
/// name.
bool
get_deb_name(const string& str, string& name)
{
  if (str.empty() || str[0] == '_')
    return false;

  string::size_type str_len = str.length(), i = 0 ;

  for (; i < str_len; ++i)
    {
      if (str[i] == '_')
	break;
    }

  if (i == str_len)
    return false;

  name = str.substr(0, i);
  return true;
}

/// Get the package name of an rpm package.
///
/// @param str the string containing the NVR of the rpm.
///
/// @param name output parameter.  This is set with the package name
/// of the rpm package iff the function returns true.
///
/// @return true iff the function successfully finds the rpm package
/// name.
bool
get_rpm_name(const string& str, string& name)
{
  if (str.empty() || str[0] == '-')
    return false;

  string::size_type str_len = str.length(), i = 0;
  string::value_type c;

  for (; i < str_len; ++i)
    {
      c = str[i];
      string::size_type next_index = i + 1;
      if ((next_index < str_len) && c == '-' && isdigit(str[next_index]))
	break;
    }

  if (i == str_len)
    return false;

  name = str.substr(0, i);

  return true;
}

/// Get the architecture string from the NVR of an rpm.
///
/// @param str the NVR to consider.
///
/// @param arch output parameter.  Is set to the resulting
/// archirecture string iff the function returns true.
///
/// @return true iff the function could find the architecture string
/// from the NVR.
bool
get_rpm_arch(const string& str, string& arch)
{
  if (str.empty())
    return false;

  if (!string_ends_with(str, ".rpm"))
    return false;

  string::size_type str_len = str.length(), i = 0;
  string::value_type c;
  string::size_type last_dot_index = 0, dot_before_last_index = 0;

  for (i = str_len - 1; i > 0; --i)
    {
      c = str[i];
      if (c == '.')
	{
	  last_dot_index = i;
	  break;
	}
    }

  if (i == 0)
    return false;

  for(--i; i > 0; --i)
    {
      c = str[i];
      if (c == '.')
	{
	  dot_before_last_index = i;
	  break;
	}
    }

  if (i == 0)
    return false;

  arch = str.substr(dot_before_last_index + 1,
		    last_dot_index - dot_before_last_index - 1);

  return true;
}

/// Tests if a given file name designates a kernel package.
///
/// @param file_path the path to the file to consider.
///
/// @param file_type the type of the file @p file_name.
///
/// @return true iff @p file_name of kind @p file_type designates a
/// kernel package.
bool
file_is_kernel_package(const string& file_path, file_type file_type)
{
  bool result = false;

  if (file_type == FILE_TYPE_RPM)
    {
      if (rpm_contains_file(file_path, "vmlinuz"))
	result = true;
    }
  else if (file_type == FILE_TYPE_DEB)
    {
      string file_name;
      base_name(file_path, file_name);
      string package_name;
      if (get_deb_name(file_name, package_name))
	result = (string_begins_with(package_name, "linux-image"));
    }

  return result;
}

/// Test if an RPM package contains a given file.
///
/// @param rpm_path the path to the RPM package.
///
/// @param file_name the file name to test the presence for in the
/// rpm.
///
/// @return true iff the file named @file_name is present in the RPM.
bool
rpm_contains_file(const string& rpm_path, const string& file_name)
{
  vector<string> query_output;
  // We don't check the return value of this command because on some
  // system, the command can issue errors but still emit a valid
  // output.  We'll rather rely on the fact that the command emits a
  // valid output or not.
  execute_command_and_get_output("rpm -qlp "
				 + rpm_path + " 2> /dev/null",
				 query_output);

  for (auto& line : query_output)
    {
      line = trim_white_space(line);
      if (string_ends_with(line, file_name))
	return true;
    }

  return false;
}

/// Tests if a given file name designates a kernel debuginfo package.
///
/// @param file_name the file name to consider.
///
/// @param file_type the type of the file @p file_name.
///
/// @return true iff @p file_name of kind @p file_type designates a
/// kernel debuginfo package.
bool
file_is_kernel_debuginfo_package(const string& file_name, file_type file_type)
{
  bool result = false;
  string package_name;

  if (file_type == FILE_TYPE_RPM)
    {
      if (!get_rpm_name(file_name, package_name))
	return false;
      result = (package_name == "kernel-debuginfo");
    }
  else if (file_type == FILE_TYPE_DEB)
    {
      if (!get_deb_name(file_name, package_name))
	return false;
      result = (string_begins_with(package_name, "linux-image")
		&& (string_ends_with(package_name, "-dbg")
		    || string_ends_with(package_name, "-dbgsyms")));
    }

  return result;
}

/// The delete functor of a char buffer that has been created using
/// malloc.
struct malloced_char_star_deleter
{
  void
  operator()(char* ptr)
  {free(ptr);}
};

/// Return a copy of the path given in argument, turning it into an
/// absolute path by prefixing it with the concatenation of the result
/// of get_current_dir_name() and the '/' character.
///
/// The result being an shared_ptr to char*, it should manage its
/// memory by itself and the user shouldn't need to wory too much for
/// that.
///
/// @param p the path to turn into an absolute path.
///
/// @return a shared pointer to the resulting absolute path.
std::shared_ptr<char>
make_path_absolute(const char*p)
{
  using std::shared_ptr;

  shared_ptr<char> result;

  if (p && p[0] != '/')
    {
      shared_ptr<char> pwd(get_current_dir_name(),
			   malloced_char_star_deleter());
      string s = string(pwd.get()) + "/" + p;
      result.reset(strdup(s.c_str()), malloced_char_star_deleter());
    }
  else
    result.reset(strdup(p), malloced_char_star_deleter());

  return result;
}

/// Return a copy of the path given in argument, turning it into an
/// absolute path by prefixing it with the concatenation of the result
/// of get_current_dir_name() and the '/' character.
///
///
/// @param p the path to turn into an absolute path.
///
/// @return The resulting absolute path.
string
make_path_absolute(const string& p)
{
  string result;

  if (!p.empty() && p[0] != '/')
    {
      shared_ptr<char> pwd(get_current_dir_name(),
			   malloced_char_star_deleter());
      result = string(pwd.get()) + "/" + p;
    }
  else if (!p.empty())
    result = p;

  return result;
}

/// This is a sub-routine of gen_suppr_spec_from_headers and
/// handle_fts_entry.
///
/// It setups a type suppression which is meant to keep types defined
/// in a given file and suppress all other types.
///
/// @param file_path the path to the file that defines types that are
/// meant to be kept by the type suppression.  All other types defined
/// in other files are to be suppressed.  Note that this file path is
/// added to the vector returned by
/// type_suppression::get_source_locations_to_keep()
///
/// @param suppr the type suppression to setup.  If this smart pointer
/// is nil then a new instance @ref type_suppression is created and
/// this variable is made to point to it.
static void
handle_file_entry(const string& file_path,
		  type_suppression_sptr& suppr)
{
  if (!suppr)
    {
      suppr.reset(new type_suppression(get_opaque_types_suppr_spec_label(),
				       /*type_name_regexp=*/"",
				       /*type_name=*/""));

      // Types that are defined in system headers are usually
      // OK to be considered as public types.
      suppr->set_source_location_to_keep_regex_str("^/usr/include/");
      suppr->set_is_artificial(true);
    }

  // And types that are defined in header files that are under
  // the header directory file we are looking are to be
  // considered public types too.
  suppr->get_source_locations_to_keep().insert(file_path);
}

/// This is a sub-routine of gen_suppr_spec_from_headers.
///
/// @param entry if this file represents a regular (or symlink) file,
/// then its file name is going to be added to the vector returned by
/// type_suppression::get_source_locations_to_keep().
///
/// @param if @p entry represents a file, then its file name is going
/// to be added to the vector returned by the method
/// type_suppression::get_source_locations_to_keep of this instance.
/// If this smart pointer is nil then a new instance @ref
/// type_suppression is created and this variable is made to point to
/// it.
static void
handle_fts_entry(const FTSENT *entry,
		 type_suppression_sptr& suppr)
{
  if (entry == NULL
      || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
      || entry->fts_info == FTS_ERR
      || entry->fts_info == FTS_NS)
    return;

  string fname = entry->fts_name;
  if (!fname.empty())
    {
      if (string_ends_with(fname, ".h")
	  || string_ends_with(fname, ".hpp")
	  || string_ends_with(fname, ".hxx"))
	handle_file_entry (fname, suppr);
    }
}

/// Populate a type_supression from header files found in a given
/// directory tree.
///
/// The suppression suppresses types defined in source files that are
/// *NOT* found in the directory tree.
///
/// This is a subroutine for gen_suppr_spect_from_headers.
///
/// @param headers_root_dir the directory tree to consider for header
/// files.
///
/// @param result the type_supression to populate from the content of
/// @p headers_root_dir.
static void
gen_suppr_spec_from_headers_root_dir(const string& headers_root_dir,
				     type_suppression_sptr &result)
{
  if (!headers_root_dir.empty())
    {
      char* paths[] = {const_cast<char*>(headers_root_dir.c_str()), 0};

      if (FTS *file_hierarchy = fts_open(paths, FTS_LOGICAL|FTS_NOCHDIR, NULL))
	{
	  FTSENT *entry;
	  while ((entry = fts_read(file_hierarchy)))
	    handle_fts_entry(entry, result);
	  fts_close(file_hierarchy);
	}
    }
}

/// Generate a type suppression specification that suppresses ABI
/// changes for types defined in source files that are neither in a
/// given set of header root directories nor in a set of header
/// files.
///
/// @param headers_root_dirs ABI changes in types defined in files
/// *NOT* found in these directory trees are going be suppressed.
///
/// @param header_files a set of additional header files that define
/// types that are to be kept (not supressed) by the returned type
/// suppression.
///
/// @return the resulting type suppression generated, if any file was
/// found in the directory tree @p headers_root_dir.
type_suppression_sptr
gen_suppr_spec_from_headers(const vector<string>& headers_root_dirs,
			    const vector<string>& header_files)
{
  type_suppression_sptr result;

  for (vector<string>::const_iterator root_dir = headers_root_dirs.begin();
       root_dir != headers_root_dirs.end();
       ++root_dir)
    gen_suppr_spec_from_headers_root_dir(*root_dir, result);

  for (vector<string>::const_iterator file = header_files.begin();
       file != header_files.end();
       ++file)
    handle_file_entry(*file, result);

  return result;
}

/// Generate a type suppression specification that suppresses ABI
/// changes for types defined in source files that are neither in a
/// given header root dir, not in a set of header files.
///
/// @param headers_root_dir ABI changes in types defined in files
/// *NOT* found in this directory tree are going be suppressed.
///
/// @param header_files a set of additional header files that define
/// types that are to be kept (not supressed) by the returned type
/// suppression.
///
/// @return the resulting type suppression generated, if any file was
/// found in the directory tree @p headers_root_dir.
type_suppression_sptr
gen_suppr_spec_from_headers(const string& headers_root_dir,
			    const vector<string>& header_files)
{
  type_suppression_sptr result;
  vector<string> root_dirs;

  if (!headers_root_dir.empty())
    root_dirs.push_back(headers_root_dir);

  return gen_suppr_spec_from_headers(root_dirs, header_files);
}

/// Generate a type suppression specification that suppresses ABI
/// changes for types defined in source files that are not in a given
/// header root dir.
///
/// @param headers_root_dir ABI changes in types defined in files
/// *NOT* found in this directory tree are going be suppressed.
///
/// @return the resulting type suppression generated, if any file was
/// found in the directory tree @p headers_root_dir.
type_suppression_sptr
gen_suppr_spec_from_headers(const string& headers_root_dir)
{
  // We don't list individual files, just look under the headers_path.
  vector<string> header_files;
  return gen_suppr_spec_from_headers(headers_root_dir, header_files);
}

/// Generate a suppression specification from kernel abi whitelist
/// files.
///
/// A kernel ABI whitelist file is an INI file that usually has only
/// one section.  The name of the section is a string that ends up
/// with the sub-string "whitelist".  For instance
/// RHEL7_x86_64_whitelist.
///
/// Then the content of the section is a set of function or variable
/// names, one name per line.  Each function or variable name is the
/// name of a function or a variable whose changes are to be keept.
///
/// A whitelist file can have multiple sections (adhering to the naming
/// conventions and multiple files can be passed. The suppression that
/// is created takes all whitelist sections from all files into account.
/// Symbols (or expression of such) are deduplicated in the final
/// suppression expression.
///
/// This function reads the white lists and generates a
/// function_suppression_sptr and variable_suppression_sptr and returns
/// a vector containing those.
///
/// @param abi_whitelist_paths a vector of KMI whitelist paths
///
/// @return a vector or suppressions
suppressions_type
gen_suppr_spec_from_kernel_abi_whitelists
   (const std::vector<std::string>& abi_whitelist_paths)
{

  std::vector<std::string> whitelisted_names;
  for (std::vector<std::string>::const_iterator
	   path_iter = abi_whitelist_paths.begin(),
	   path_end = abi_whitelist_paths.end();
       path_iter != path_end;
       ++path_iter)
    {

      abigail::ini::config whitelist;
      if (!read_config(*path_iter, whitelist))
	continue;

      const ini::config::sections_type& whitelist_sections =
	  whitelist.get_sections();

      for (ini::config::sections_type::const_iterator
	       section_iter = whitelist_sections.begin(),
	       section_end = whitelist_sections.end();
	   section_iter != section_end;
	   ++section_iter)
	{
	  std::string section_name = (*section_iter)->get_name();
	  if (!string_ends_with(section_name, "whitelist")
	      && !string_ends_with(section_name, "stablelist"))
	    continue;
	  for (ini::config::properties_type::const_iterator
		   prop_iter = (*section_iter)->get_properties().begin(),
		   prop_end = (*section_iter)->get_properties().end();
	       prop_iter != prop_end;
	       ++prop_iter)
	    {
	      if (const simple_property_sptr& prop =
		      is_simple_property(*prop_iter))
		if (prop->has_empty_value())
		  {
		    const std::string& name = prop->get_name();
		    if (!name.empty())
		      whitelisted_names.push_back(name);
		  }
	    }
	}
    }

  suppressions_type result;
  if (!whitelisted_names.empty())
    {
      // Drop duplicates to simplify the regex we are generating
      std::sort(whitelisted_names.begin(), whitelisted_names.end());
      whitelisted_names.erase(std::unique(whitelisted_names.begin(),
					  whitelisted_names.end()),
			      whitelisted_names.end());

      // Build a regular expression representing the union of all
      // the function and variable names expressed in the white list.
      const std::string regex = regex::generate_from_strings(whitelisted_names);

      // Build a suppression specification which *keeps* functions
      // whose ELF symbols match the regular expression contained
      // in function_names_regexp.  This will also keep the ELF
      // symbols (not designated by any debug info) whose names
      // match this regexp.
      function_suppression_sptr fn_suppr(new function_suppression);
      fn_suppr->set_label("whitelist");
      fn_suppr->set_symbol_name_not_regex_str(regex);
      fn_suppr->set_drops_artifact_from_ir(true);
      result.push_back(fn_suppr);

      // Build a suppression specification which *keeps* variables
      // whose ELF symbols match the regular expression contained
      // in function_names_regexp.  This will also keep the ELF
      // symbols (not designated by any debug info) whose names
      // match this regexp.
      variable_suppression_sptr var_suppr(new variable_suppression);
      var_suppr->set_label("whitelist");
      var_suppr->set_symbol_name_not_regex_str(regex);
      var_suppr->set_drops_artifact_from_ir(true);
      result.push_back(var_suppr);
    }
  return result;
}

/// Get the path to the default system suppression file.
///
/// @return a copy of the default system suppression file.
string
get_default_system_suppression_file_path()
{
  string default_system_suppr_path;

  const char *s = getenv("LIBABIGAIL_DEFAULT_SYSTEM_SUPPRESSION_FILE");
  if (s)
    default_system_suppr_path = s;

  if (default_system_suppr_path.empty())
    default_system_suppr_path =
      get_system_libdir() + string("/libabigail/default.abignore");

  return default_system_suppr_path;
}

/// Get the path to the default user suppression file.
///
/// @return a copy of the default user suppression file.
string
get_default_user_suppression_file_path()
{
  string default_user_suppr_path;
  const char *s = getenv("LIBABIGAIL_DEFAULT_USER_SUPPRESSION_FILE");

  if (s == NULL)
    {
      s = getenv("HOME");
      if (s == NULL)
	return "";
      default_user_suppr_path  = s;
      if (default_user_suppr_path.empty())
	default_user_suppr_path = "~";
      default_user_suppr_path += "/.abignore";
    }
  else
    default_user_suppr_path = s;

  return default_user_suppr_path;
}

/// Load the default system suppression specification file and
/// populate a vector of @ref suppression_sptr with its content.
///
/// The default system suppression file is located at
/// $libdir/libabigail/default-libabigail.abignore.
///
/// @param supprs the vector to add the suppression specifications
/// read from the file to.
void
load_default_system_suppressions(suppr::suppressions_type& supprs)
{
  string default_system_suppr_path =
    get_default_system_suppression_file_path();

  read_suppressions(default_system_suppr_path, supprs);
}

/// Load the default user suppression specification file and populate
/// a vector of @ref suppression_sptr with its content.
///
/// The default user suppression file is located at $HOME~/.abignore.
///
/// @param supprs the vector to add the suppression specifications
/// read from the file to.
void
load_default_user_suppressions(suppr::suppressions_type& supprs)
{
  string default_user_suppr_path =
    get_default_user_suppression_file_path();

  read_suppressions(default_user_suppr_path, supprs);
}

/// Test if a given FTSENT* denotes a file with a given name.
///
/// @param entry the FTSENT* to consider.
///
/// @param fname the file name (or end of path) to consider.  The file
/// name can also be a path that is relative to the root directory the
/// current visit is started from.  The root directory is given by @p
/// root_dir.
///
/// @param root_dir the root dir from which the directory visit is
/// being performed.
///
/// @return true iff @p entry denotes a file which path ends with @p
/// fname.
static bool
entry_of_file_with_name(const FTSENT *entry,
			const string& fname,
			const string& root_dir)
{
  if (entry == NULL
      || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
      || entry->fts_info == FTS_ERR
      || entry->fts_info == FTS_NS)
    return false;

  string fpath = ::basename(entry->fts_path);
  if (fpath == fname)
    return true;

  fpath = trim_leading_string(entry->fts_path, root_dir);
  if (fpath == fname)
    return true;

  return false;
}

/// Find a given file under a root directory and return its absolute
/// path.
///
/// @param root_dir the root directory under which to look for.
///
/// @param file_path_to_look_for the file to look for under the
/// directory @p root_dir.
///
/// @param result the resulting path to @p file_path_to_look_for.
/// This is set iff the file has been found.
bool
find_file_under_dir(const string& root_dir,
		    const string& file_path_to_look_for,
		    string& result)
{
  char* paths[] = {const_cast<char*>(root_dir.c_str()), 0};

  FTS *file_hierarchy = fts_open(paths,
				 FTS_PHYSICAL|FTS_NOCHDIR|FTS_XDEV, 0);
  if (!file_hierarchy)
    return false;

  string r = root_dir;
  if (!string_ends_with(r, "/"))
    r += "/";

  FTSENT *entry;
  while ((entry = fts_read(file_hierarchy)))
    {
      if (entry_of_file_with_name(entry, file_path_to_look_for, r))
	{
	  result = entry->fts_path;
	  fts_close(file_hierarchy);
	  return true;
	}
      // Skip descendents of symbolic links.
      if (entry->fts_info == FTS_SL || entry->fts_info == FTS_SLNONE)
	{
	  fts_set(file_hierarchy, entry, FTS_SKIP);
	  continue;
	}
    }

  fts_close(file_hierarchy);
  return false;
}

/// Find a given file possibly under a set of directories and return
/// its absolute path.
///
/// @param root_dirs the vector of root directories under which to
/// look for.
///
/// @param file_path_to_look_for the file to look for under the
/// directory @p root_dir.
///
/// @param result the resulting path to @p file_path_to_look_for.
/// This is set iff the file has been found.
bool
find_file_under_dirs(const vector<string>& root_dirs,
		     const string& file_path_to_look_for,
		     string& result)
{
  if (root_dirs.empty())
    return find_file_under_dir(".", file_path_to_look_for, result);

  for (const auto& root_dir : root_dirs)
    if (find_file_under_dir(root_dir, file_path_to_look_for, result))
      return true;

  return false;
}

/// Get the dependencies of an ABI corpus, which are found in a set of
/// directories.  Note that the dependencies are listed as properties
/// of the ABI corpus.
///
/// If the corpus has a dependency that is not found under any of the
/// given directories, then the dependency is ignored and not
/// returned.
///
/// @param korpus the ABI corpus to consider.
///
/// @param deps_dirs the list of directories where to look for the
/// dependencies.
///
/// @param dependencies output parameter that is set the dependencies
/// of the corpus denoted by @p korpus which are found in the
/// directories @p deps_dirs.  This is set iff the function returns
/// true.
///
/// @return true iff some dependencies of the corpus @p korpus were
/// found in directories @p deps_dirs.
bool
get_dependencies(const corpus&		korpus,
		 const vector<string>&	deps_dirs,
		 set<string>&		dependencies)
{
  const vector<string>& set_of_needed = korpus.get_needed();
  if (set_of_needed.empty())
    return false;

  bool found_at_least_one_dependency =false;
  for (const auto& n :set_of_needed)
    {
      string dependency;
      if (dependencies.find(n) == dependencies.end()
	  && find_file_under_dirs(deps_dirs, n, dependency))
	{
	  dependencies.insert(dependency);
	  found_at_least_one_dependency = true;
	}
    }

  return found_at_least_one_dependency;
}

/// For each binary of a vector of binaries, if the binary is present
/// in at least one of the directories listed in a given vector,
/// construct a corpus and add it to a corpus group.
///
/// @param reader the reader used to read the binaries into an ABI corpus.
///
/// @param binaries the vector of binaries to read and add to a corpus
/// group.
///
/// @param deps_dirs the vector of directories where to look for the
/// binaries in @p binaries.
///
/// @param group the corpus group to add the corpus.
void
add_binaries_into_corpus_group(const fe_iface_sptr&	reader,
			       const vector<string>&	binaries,
			       const vector<string>&	deps_dirs,
			       corpus_group&		group)
{
  vector<string> bins;

  for (const auto& b : binaries)
    {
      string bin;
      if (find_file_under_dirs(deps_dirs, b, bin))
	bins.push_back(bin);
    }

  for (const auto& b : bins)
    {
      if (group.has_corpus(b))
	continue;

      reader->initialize(b);
      fe_iface::status stat = fe_iface::STATUS_UNKNOWN;
      corpus_sptr c = reader->read_corpus(stat);
      if (c && (stat & fe_iface::STATUS_OK))
	group.add_corpus(c);
    }
}

/// For each dependency of a given corpus, if it is present in at
/// least one of the directories listed in a given vector, construct a
/// corpus and add it to a corpus group.
///
/// @param reader the reader used to read the binaries into an ABI corpus.
///
/// @param korpus the corpus to consider.
///
/// @param deps_dirs the vector of directories where to look for the
/// dependencies of @p korpus.
///
/// @param group the corpus group to add the corpus.
void
add_dependencies_into_corpus_group(const fe_iface_sptr&	reader,
				   const corpus&		korpus,
				   const vector<string>&	deps_dirs,
				   corpus_group&		group)

{
  set<string> deps;
  if (!get_dependencies(korpus, deps_dirs, deps))
    return;

  for (const auto& dep: deps)
    {
      if (group.has_corpus(dep))
	continue;

      reader->initialize(dep);
      fe_iface::status stat = fe_iface::STATUS_UNKNOWN;
      corpus_sptr c = reader->read_corpus(stat);
      if (c && (stat & fe_iface::STATUS_OK))
	{
	  group.add_corpus(c);
	  add_dependencies_into_corpus_group(reader, *c, deps_dirs, group);
	}
    }
}

/// Create a corpus group made of a given korpus and a set of binaries
/// found in a set of directories.
///
/// @param reader the reader to use to read the binaries.
///
/// @param korpus the ABI corpus to add to the corpus group.
///
/// @param binaries the set of binaries to add to the corpus group, if
/// they are present one of the directories denoted by the vector @p
/// deps_dirs.
///
/// @param bins_dirs the directories where the binaries listed in @p
/// binaries are to be found.
///
/// @return a corpus group made of @p korpus and the binaries listed
/// in @p binaries and found in at least one of the directories found
/// in @p bins_dirs.
corpus_group_sptr
stick_corpus_and_binaries_into_corpus_group(const fe_iface_sptr&	reader,
					    const corpus_sptr&		korpus,
					    const vector<string>&	binaries,
					    const vector<string>&	bins_dirs)
{
    corpus_group_sptr result (new corpus_group(korpus->get_environment(),
					       korpus->get_path()));
    result->add_corpus(korpus);

    add_binaries_into_corpus_group(reader, binaries, bins_dirs, *result);

    return result;
}

/// Create a corpus group made of a given korpus and the subset of its
/// dependencies that can be found found in a set of directories.
///
/// @param reader the reader to use to read the binaries.
///
/// @param korpus the ABI corpus to add to the corpus group along with
/// its dependencies that can be found in a subset of directories.
///
/// @param deps_dirs the directories where the dependencies of the ABI
/// corpus denoted by @p korpus binaries are to be found.
///
/// @return a corpus group made of @p korpus and the subset of its
/// dependencies found in at least one of the directories denoted by
/// @p deps_dirs.
corpus_group_sptr
stick_corpus_and_dependencies_into_corpus_group(const fe_iface_sptr&	reader,
						const corpus_sptr&	korpus,
						const vector<string>&	deps_dirs)
{
  corpus_group_sptr result (new corpus_group(korpus->get_environment(),
					     korpus->get_path()));
  result->add_corpus(korpus);

  add_dependencies_into_corpus_group(reader, *korpus, deps_dirs, *result);

  return result;
}

/// If we were given suppression specification files or kabi whitelist
/// files, this function parses those, come up with suppression
/// specifications as a result, and set them to the read context.
///
/// @param read_ctxt the read context to consider.
///
/// @param suppr_paths paths to suppression specification files that
/// we were given.  If empty, it means we were not given any
/// suppression specification path.
///
/// @param kabi_whitelist_paths paths to kabi whitelist files that we
/// were given.  If empty, it means we were not given any kabi
/// whitelist.
///
/// @param supprs the suppressions specifications resulting from
/// parsing the suppression specification files at @p suppr_paths and
/// the kabi whitelist at @p kabi_whitelist_paths.
///
/// @param opts the options to consider.
static void
load_generate_apply_suppressions(elf_based_reader&	rdr,
				 vector<string>&	suppr_paths,
				 vector<string>&	kabi_whitelist_paths,
				 suppressions_type&	supprs)
{
  if (supprs.empty())
    {
      for (vector<string>::const_iterator i = suppr_paths.begin();
	   i != suppr_paths.end();
	   ++i)
	read_suppressions(*i, supprs);

      const suppressions_type& wl_suppr =
	  gen_suppr_spec_from_kernel_abi_whitelists(kabi_whitelist_paths);

      supprs.insert(supprs.end(), wl_suppr.begin(), wl_suppr.end());
    }

  rdr.add_suppressions(supprs);
}

/// Test if an FTSENT pointer (resulting from fts_read) represents the
/// vmlinux binary.
///
/// @param entry the FTSENT to consider.
///
/// @return true iff @p entry is for a vmlinux binary.
static bool
is_vmlinux(const FTSENT *entry)
{
  if (entry == NULL
      || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
      || entry->fts_info == FTS_ERR
      || entry->fts_info == FTS_NS)
    return false;

  string fname = entry->fts_name;

  if (fname == "vmlinux")
    {
      string dirname;
      dir_name(entry->fts_path, dirname);
      if (string_ends_with(dirname, "compressed"))
	return false;

      return true;
    }

  return false;
}

/// Test if an FTSENT pointer (resulting from fts_read) represents a a
/// linux kernel module binary.
///
/// @param entry the FTSENT to consider.
///
/// @return true iff @p entry is for a linux kernel module binary.
static bool
is_kernel_module(const FTSENT *entry)
{
    if (entry == NULL
      || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
      || entry->fts_info == FTS_ERR
      || entry->fts_info == FTS_NS)
    return false;

  string fname = entry->fts_name;
  if (string_ends_with(fname, ".ko")
      || string_ends_with(fname, ".ko.xz")
      || string_ends_with(fname, ".ko.gz"))
    return true;

  return false;
}

/// Test if a given file denoted by a FTSENT* is a regular file or a
/// symlink.
///
/// @param entry returned by the combo fts_open/fts_read.
///
/// @return true iff @p entry is for a regular file or a symlink.
static bool
is_file(const FTSENT *entry)
{
  return (entry
	  && (entry->fts_info == FTS_F
	      || entry->fts_info == FTS_SL));
}

/// Find a vmlinux and its kernel modules in a given directory tree.
///
/// @param from the directory tree to start looking from.
///
/// @param vmlinux_path output parameter.  This is set to the path
/// where the vmlinux binary is found.  This is set iff the returns
/// true and if this argument was empty to begin with.
///
/// @param module_paths output parameter.  This is set to the paths of
/// the linux kernel module binaries.
///
/// @return true iff at least the vmlinux binary was found.
static bool
find_vmlinux_and_module_paths(const string&	from,
			      string		&vmlinux_path,
			      vector<string>	&module_paths)
{
  char* path[] = {const_cast<char*>(from.c_str()), 0};

  FTS *file_hierarchy = fts_open(path, FTS_PHYSICAL|FTS_NOCHDIR|FTS_XDEV, 0);
  if (!file_hierarchy)
    return false;

  bool found_vmlinux = !vmlinux_path.empty();
  FTSENT *entry;
  while ((entry = fts_read(file_hierarchy)))
    {
      // Skip descendents of symbolic links.
      if (entry->fts_info == FTS_SL || entry->fts_info == FTS_SLNONE)
	{
	  fts_set(file_hierarchy, entry, FTS_SKIP);
	  continue;
	}

      if (!found_vmlinux && is_vmlinux(entry))
	{
	  vmlinux_path = entry->fts_path;
	  found_vmlinux = true;
	}
      else if (is_kernel_module(entry))
	module_paths.push_back(entry->fts_path);
    }

  fts_close(file_hierarchy);

  return found_vmlinux;
}

/// Find a vmlinux binary in a given directory tree.
///
/// @param from the directory tree to start looking from.
///
/// @param vmlinux_path output parameter
///
/// return true iff the vmlinux binary was found
static bool
find_vmlinux_path(const string&	from,
		  string		&vmlinux_path)
{
  char* path[] = {const_cast<char*>(from.c_str()), 0};

  FTS *file_hierarchy = fts_open(path, FTS_PHYSICAL|FTS_NOCHDIR|FTS_XDEV, 0);
  if (!file_hierarchy)
    return false;

  bool found_vmlinux = false;
  FTSENT *entry;
  while ((entry = fts_read(file_hierarchy)))
    {
      // Skip descendents of symbolic links.
      if (entry->fts_info == FTS_SL || entry->fts_info == FTS_SLNONE)
	{
	  fts_set(file_hierarchy, entry, FTS_SKIP);
	  continue;
	}

      if (!found_vmlinux && is_vmlinux(entry))
	{
	  vmlinux_path = entry->fts_path;
	  found_vmlinux = true;
	  break;
	}
    }

  fts_close(file_hierarchy);

  return found_vmlinux;
}

/// Get all the sub-directories (which contain a regular file) of a
/// given directory.
///
/// @param root_dir the root directory to consider.
///
/// @param dirs the sub-directories of @p root_dir which contain a
/// file.
bool
get_file_path_dirs_under_dir(const string& root_dir, vector<string>& dirs)
{
  char* paths[] = {const_cast<char*>(root_dir.c_str()), 0};
  FTS *file_hierarchy = fts_open(paths,
				 FTS_PHYSICAL|FTS_NOCHDIR|FTS_XDEV, 0);
  if (!file_hierarchy)
    return false;

  string r = root_dir;
  if (!string_ends_with(r, "/"))
    r += "/";

  bool found_file = false;
  FTSENT *entry;
  while ((entry = fts_read(file_hierarchy)))
    {
      // Skip descendents of symbolic links.
      if (entry->fts_info == FTS_SL || entry->fts_info == FTS_SLNONE)
	{
	  fts_set(file_hierarchy, entry, FTS_SKIP);
	  continue;
	}

      if (is_file(entry))
	found_file = true;

      string path = entry->fts_path;
      dir_name(path, path);
      dirs.push_back(path);
    }

  fts_close(file_hierarchy);

  return found_file;
}

/// Get the paths of the vmlinux and kernel module binaries under
/// given directory.
///
/// @param dist_root the directory under which to look for.
///
/// @param debug_info_root_path the path to the directory under which
/// debug info is going to be found for binaries under @p dist_root.
///
/// @param vmlinux_path output parameter.  The path of the vmlinux
/// binary that was found.
///
/// @param module_paths output parameter.  The paths of the kernel
/// module binaries that were found, sorted to impose a deterministic
/// ordering.
///
/// @return true if at least the path to the vmlinux binary was found.
bool
get_binary_paths_from_kernel_dist(const string&	dist_root,
				  const string&	debug_info_root_path,
				  string&		vmlinux_path,
				  vector<string>&	module_paths)
{
  if (!dir_exists(dist_root))
    return false;

  // For now, we assume either an Enterprise Linux or a Fedora kernel
  // distribution directory.
  //
  // We also take into account split debug info package for these.  In
  // this case, the content split debug info package is installed
  // under the 'debug_info_root_path' directory and its content is
  // accessible from <debug_info_root_path>/usr/lib/debug directory.

  string kernel_modules_root = dist_root;
  string debug_info_root;
  if (dir_exists(dist_root + "/lib/modules"))
    {
      kernel_modules_root = dist_root + "/lib/modules";
      debug_info_root = debug_info_root_path.empty()
	? dist_root + "/usr/lib/debug"
	: debug_info_root_path;
    }

  if (dir_is_empty(debug_info_root))
    debug_info_root.clear();

  bool found = false;
  // If vmlinux_path is empty, we want to look for it under
  // debug_info_root, because this is where Enterprise Linux packages
  // put it.  Modules however are to be looked for under
  // kernel_modules_root.
  if (// So, Let's look for modules under kernel_modules_root ...
      find_vmlinux_and_module_paths(kernel_modules_root,
				    vmlinux_path,
				    module_paths)
      // ... and if vmlinux_path is empty, look for vmlinux under the
      // debug info root.
      || find_vmlinux_and_module_paths(debug_info_root,
				       vmlinux_path,
				       module_paths))
    found = true;

  std::sort(module_paths.begin(), module_paths.end());

  return found;
}

/// Get the path of the vmlinux binary under the given directory, that
/// must have been generated either from extracting a package.
///
/// @param from the directory under which to look for.
///
/// @param vmlinux_path output parameter.  The path of the vmlinux
/// binary that was found.
///
/// @return true if the path to the vmlinux binary was found.
bool
get_vmlinux_path_from_kernel_dist(const string&	from,
				  string&		vmlinux_path)
{
    if (!dir_exists(from))
    return false;

  // For now, we assume the possibility of having either an Enterprise
  // Linux or a Fedora kernel distribution directory.  In those cases,
  // the vmlinux binary is located under the /lib/modules
  // sub-directory.  So we might as well save some time by picking it
  // from there directly.

    string dist_root = from;
  if (dir_exists(dist_root + "/lib/modules"))
    dist_root += "/lib/modules";

  bool found = false;
  if (find_vmlinux_path(dist_root, vmlinux_path))
    found = true;

  return found;
}

/// Get the paths of the vmlinux and kernel module binaries under
/// given directory.
///
/// @param dist_root the directory under which to look for.
///
/// @param vmlinux_path output parameter.  The path of the vmlinux
/// binary that was found.
///
/// @param module_paths output parameter.  The paths of the kernel
/// module binaries that were found.
///
/// @return true if at least the path to the vmlinux binary was found.
bool
get_binary_paths_from_kernel_dist(const string&	dist_root,
				  string&		vmlinux_path,
				  vector<string>&	module_paths)
{
  string debug_info_root_path;
  return get_binary_paths_from_kernel_dist(dist_root,
					   debug_info_root_path,
					   vmlinux_path,
					   module_paths);
}

/// It builds a @ref corpus_group made of vmlinux kernel file and
/// the kernel modules found under @p root directory and under its
/// sub-directories, recursively.
///
/// @param rdr the raeder that should be used to extract the debug
/// infomation from the linux kernel and its modules used to build
/// the corpora @p group.
///
/// @param the group @ref corpus_group to be built.
///
/// @param vmlinux the path to the vmlinux binary.
///
/// @param modules a vector with the paths to the linux kernel
/// modules.
///
/// @param root the path of the directory under which the kernel
/// kernel modules were found.
///
/// @param di_root the directory in aboslute path which debug
/// info is to be found for binaries under director @p root
///
/// @param suppr_paths the paths to the suppression specifications to
/// apply while loading the binaries.
///
/// @param kabi_wl_path the paths to the kabi whitelist files to take
/// into account while loading the binaries.
///
/// @param supprs the suppressions resulting from parsing the
/// suppression specifications at @p suppr_paths.  This is set by this
/// function.
///
/// @param verbose true if the function has to emit some verbose
/// messages.
///
/// @param t time to trace time spent in each step.
///
/// @param env the environment to create the corpus_group in.
static void
load_vmlinux_corpus(elf_based_reader_sptr rdr,
                    corpus_group_sptr&  group,
                    const string&       vmlinux,
                    vector<string>&     modules,
                    const string&       root,
                    vector<string>&     di_roots,
                    vector<string>&     suppr_paths,
                    vector<string>&     kabi_wl_paths,
                    suppressions_type&  supprs,
                    bool                verbose,
                    timer&              t,
                    environment&        env)
{
  abigail::fe_iface::status status = abigail::fe_iface::STATUS_OK;
  rdr->options().do_log = verbose;

  if (verbose)
    {
      std::cerr << "Loading stable lists:'";
      for (auto s : kabi_wl_paths)
	std::cerr << s << ",";
      std::cerr << "'...\n";
    }
  t.start();
  load_generate_apply_suppressions(*rdr, suppr_paths,
                                   kabi_wl_paths, supprs);
  t.stop();

  if (verbose)
    std::cerr << "loaded white list and generated suppr spec in: "
     << t
     << "\n";

  group.reset(new corpus_group(env, root));

  rdr->corpus_group(group);

  if (verbose)
    std::cerr << "reading kernel binary '"
     << vmlinux << "' ...\n" << std::flush;

  // Read the vmlinux corpus and add it to the group.
  t.start();
  rdr->read_and_add_corpus_to_group(*group, status);
  t.stop();

  if (verbose)
    std::cerr << vmlinux
     << " reading DONE in:"
     << t << "\n";

  if (group->is_empty())
    return;

  // Now add the corpora of the modules to the corpus group.
  int total_nb_modules = modules.size();
  int cur_module_index = 1;
  for (vector<string>::const_iterator m = modules.begin();
       m != modules.end();
       ++m, ++cur_module_index)
    {
      if (verbose)
        std::cerr << "reading module '"
         << *m << "' ("
         << cur_module_index
         << "/" << total_nb_modules
         << ") ...\n" << std::flush;

      rdr->initialize(*m, di_roots,
                      /*read_all_types=*/false,
                      /*linux_kernel_mode=*/true);

      load_generate_apply_suppressions(*rdr, suppr_paths,
                                       kabi_wl_paths, supprs);

      rdr->corpus_group(group);

      t.start();
      rdr->read_and_add_corpus_to_group(*group, status);
      t.stop();
      if (verbose)
	std::cerr << "Module reading DONE in: "
		  << t << " for '" << *m
		  << "' (" << cur_module_index << "/" << total_nb_modules << ")"
		  << "'\n";
    }

  if (verbose)
    {
      std::cerr << "Total number of functions: "
		<< group->get_functions().size() << "\n";
      std::cerr << "Total number of variables: "
		<< group->get_variables().size() << "\n";
    }
}

/// Walk a given directory and build an instance of @ref corpus_group
/// from the vmlinux kernel binary and the linux kernel modules found
/// under that directory and under its sub-directories, recursively.
///
/// The main corpus of the @ref corpus_group is made of the vmlinux
/// binary.  The other corpora are made of the linux kernel binaries.
///
/// @param root the path of the directory under which the kernel
/// kernel modules are to be found.  The vmlinux can also be found
/// somewhere under that directory, but if it's not in there, its path
/// can be set to the @p vmlinux_path parameter.
///
/// @param debug_info_root the directory under which debug info is to
/// be found for binaries under director @p root.
///
/// @param vmlinux_path the path to the vmlinux binary, if that binary
/// is not under the @p root directory.  If this is empty, then it
/// means the vmlinux binary is to be found under the @p root
/// directory.
///
/// @param suppr_paths the paths to the suppression specifications to
/// apply while loading the binaries.
///
/// @param kabi_wl_path the paths to the kabi whitelist files to take
/// into account while loading the binaries.
///
/// @param supprs the suppressions resulting from parsing the
/// suppression specifications at @p suppr_paths.  This is set by this
/// function.
///
/// @param verbose true if the function has to emit some verbose
/// messages.
///
/// @param env the environment to create the corpus_group in.
///
/// @param requested_fe_kind the kind of front-end requested by the
/// user.
corpus_group_sptr
build_corpus_group_from_kernel_dist_under(const string&	root,
					  const string		debug_info_root,
					  const string&	vmlinux_path,
					  vector<string>&	suppr_paths,
					  vector<string>&	kabi_wl_paths,
					  suppressions_type&	supprs,
					  bool			verbose,
					  environment&		env,
					  corpus::origin	requested_fe_kind)
{
  string vmlinux = vmlinux_path;
  corpus_group_sptr group;
  vector<string> modules;

  if (verbose)
    std::cerr << "Analysing kernel dist root '"
	      << root
	      << "' with vmlinux path: '"
	      << vmlinux_path
	      << "' ... \n" << std::flush;

  timer t;

  t.start();
  bool got_binary_paths =
    get_binary_paths_from_kernel_dist(root, debug_info_root, vmlinux, modules);
  t.stop();

  if (verbose)
    std::cerr << "Kernel tree binary paths analysis DONE in: " << t << "\n";

  if (got_binary_paths)
    {
      string di_root =
	make_path_absolute(debug_info_root);
      vector<string> di_roots;
      di_roots.push_back(di_root);

#ifdef WITH_CTF
      string di_root_ctf;
      if (requested_fe_kind & corpus::CTF_ORIGIN)
        {
          di_root_ctf = make_path_absolute(root);
          di_roots.push_back(di_root_ctf);
        }
#endif

      abigail::elf_based_reader_sptr reader =
        create_best_elf_based_reader(vmlinux,
                                     di_roots,
                                     env,
				     requested_fe_kind,
                                     /*read_all_types=*/false,
                                     /*linux_kernel_mode=*/true);
      ABG_ASSERT(reader);
      load_vmlinux_corpus(reader, group, vmlinux,
                          modules, root, di_roots,
                          suppr_paths, kabi_wl_paths,
                          supprs, verbose, t, env);
    }

  return group;
}

/// Create the best elf based reader (or front-end), given an ELF
/// file.
///
/// This function looks into the ELF file; depending on the kind of
/// debug info it contains and on the request of the user, the "best"
/// front-end is created.
///
/// If the user requested the use of the CTF front-end, then, if the
/// file contains CTF debug info, the CTF front-end is created,
/// assuming libabigail is built with CTF support.
///
/// If the binary ONLY has CTF debug info, then CTF front-end is
/// created, even if the user hasn't explicitly requested the creation
/// of the CTF front-end.
///
/// Otherwise, by default, the DWARF front-end is created.
///
/// @param elf_file_path a path to the ELF file to consider
///
/// @param debug_info_root_paths a vector of the paths where to look
/// for debug info, if applicable.
///
/// @param env the environment to use for the front-end.
///
/// @param requested_fe_kind the kind of front-end specifically
/// requested by the user. At the moment, only the CTF front-end can
/// be requested, using the "--ctf" command line option on some tools
/// using the library.
///
/// @param show_all_types option to be passed to elf based readers.
///
/// @param linux_kernel_mode option to bed passed to elf based readers,
///
/// @return the ELF based Reader that is better adapted for the binary
/// designated by @p elf_file_path.
elf_based_reader_sptr
create_best_elf_based_reader(const string& elf_file_path,
			     const vector<string>& debug_info_root_paths,
			     environment& env,
			     corpus::origin requested_fe_kind,
			     bool show_all_types,
			     bool linux_kernel_mode)
{
  elf_based_reader_sptr result;
  if (guess_file_type(elf_file_path) != FILE_TYPE_ELF)
    return result;

  if (requested_fe_kind & corpus::CTF_ORIGIN)
    {
#ifdef WITH_CTF
      if (file_has_ctf_debug_info(elf_file_path, debug_info_root_paths))
	result = ctf::create_reader(elf_file_path, debug_info_root_paths, env);
#endif
    }
  else if (requested_fe_kind & corpus::BTF_ORIGIN)
    {
#ifdef WITH_BTF
      if (file_has_btf_debug_info(elf_file_path, debug_info_root_paths))
	result = btf::create_reader(elf_file_path, debug_info_root_paths, env,
				    show_all_types, linux_kernel_mode);
#endif
    }
  else
    {
      // The user hasn't formally requested the use of the CTF front-end.
#ifdef WITH_CTF
      if (!file_has_dwarf_debug_info(elf_file_path, debug_info_root_paths)
	  && file_has_ctf_debug_info(elf_file_path, debug_info_root_paths))
	// The file has CTF debug info and no DWARF, let's use the CTF
	// front end even if it wasn't formally requested by the user.
	result = ctf::create_reader(elf_file_path, debug_info_root_paths, env);
#endif

#ifdef WITH_BTF
      if (!file_has_dwarf_debug_info(elf_file_path, debug_info_root_paths)
	  && file_has_btf_debug_info(elf_file_path, debug_info_root_paths))
	// The file has BTF debug info and no BTF, let's use the BTF
	// front-end even if it wasn't formally requested by the user.
	result = btf::create_reader(elf_file_path, debug_info_root_paths, env,
				    show_all_types, linux_kernel_mode);
#endif
    }

  if (!result)
    {
      // This is the default case.  At worst, the DWARF reader knows
      // how to handle just ELF data for the case where there is no
      // DWARF debug info present.
      result = dwarf::create_reader(elf_file_path,
				    debug_info_root_paths,
				    env,
				    show_all_types,
				    linux_kernel_mode);
    }

  return result;
}

/// ---------------------------------------------------
/// <xz_decompressor definition>
///----------------------------------------------------

/// The private data of the @ref xz_decompressor_type class.
struct xz_decompressor_type::priv
{
  std::istream& xz_istream;
  lzma_stream lzma;
  // A 100k bytes buffer for xz data coming from the xz'ed istream.
  // That buffer is going to be fed into the lzma decoding machinery.
  char inbuf[1024 * 100] = {};
  // A 100k bytes buffer for decompressed data coming out of the lzma
  // machinery
  char outbuf[1024 * 100] = {};

  priv(std::istream& i)
    : xz_istream(i),
      lzma(LZMA_STREAM_INIT)
  {}
};// end xz_decompressor_type::priv

/// Constructor of the @ref xz_decompressor_type class.
///
/// @param xz_istream the input stream containing the xz-compressed
/// data to decompress.
xz_decompressor_type::xz_decompressor_type(std::istream& xz_istream)
  : priv_(new priv(xz_istream))
{
  // Initialize the native LZMA stream to decompress.
  lzma_ret status = lzma_stream_decoder(&priv_->lzma,
					UINT64_MAX,
					LZMA_CONCATENATED);
  ABG_ASSERT(status == LZMA_OK);
}

/// Destructor of the @ref xz_decompressor_type class.
xz_decompressor_type::~xz_decompressor_type()
{
  lzma_end(&priv_->lzma);
}

/// The implementation of the virtual protected
/// std:streambuf::underflow method.  This method is invoked by the
/// std::streambuf facility to re-fill its internals buffers with data
/// coming from the associated input stream and to update the gptr()
/// and egptr() pointers by using the std::streambuf::setg method.
///
/// This is where the decompression using the lzma library is
/// performed.
std::streambuf::int_type
xz_decompressor_type::underflow()
{
  if (gptr() < egptr())
    return *gptr();

  // Let's read 'nr' bytes of xz data into inbuf
  priv_->xz_istream.read(priv_->inbuf, sizeof(priv_->inbuf));
  size_t nr = priv_->xz_istream.gcount();

  if (nr != 0)
    {
      // So there is fresh compressed input to be decompressed.  Let's
      // prepare the lzma input stream machinery then.
      priv_->lzma.avail_in = nr;
      priv_->lzma.next_in = reinterpret_cast<uint8_t*>(priv_->inbuf);
    }

  if (priv_->lzma.avail_out || priv_->lzma.avail_in)
    {
      // There is still compressed data in the lzma context to
      // decompress, so let's tell lzma where to put the decompressed
      // data.
      priv_->lzma.avail_out = sizeof(priv_->outbuf);
      priv_->lzma.next_out = reinterpret_cast<uint8_t*>(priv_->outbuf);
    }

  // Let's now ask the lzma machinery to decompress the next_in buffer
  // and put the result into the next_out buffer.
  lzma_ret result = lzma_code(&priv_->lzma, LZMA_RUN);
  if (result != LZMA_OK && result != LZMA_STREAM_END)
    {
      // TODO: list the possible error codes and tell them explicitely
      // to the user, just like what is done in
      // https://github.com/tukaani-project/xz/blob/master/doc/examples/02_decompress.c.
      std::ostringstream o;
      o << "LZMA decompression failed;"
	<< " return code of lzma_code() is : "
	<< result;
      throw std::runtime_error(o.str());
    }

  // Let's get the number of bytes decompressed by the lzma
  // machinery.  I got this from the example in the xz code base at
  // https://github.com/tukaani-project/xz/blob/master/doc/examples/02_decompress.c.
  size_t nr_decompressed_bytes = sizeof(priv_->outbuf) - priv_->lzma.avail_out;

  // Now set the relevant index pointers of this streambuf.
  setg(priv_->outbuf, priv_->outbuf, priv_->outbuf + nr_decompressed_bytes);

  if (nr_decompressed_bytes > 0)
    return *gptr();

  // If we reached this point, then it means we there is no more
  // decompressed bytes in the decompressed stream.  Tell the lzma
  // machinery that we've reached the end of the data.
  result = lzma_code(&priv_->lzma, LZMA_FINISH);
  ABG_ASSERT(result == LZMA_OK || result == LZMA_STREAM_END);
  return traits_type::eof();
}

/// ---------------------------------------------------
/// </xz_decompressor definition>
///----------------------------------------------------

}//end namespace tools_utils

using abigail::ir::function_decl;

/// Dump (to the standard error stream) two sequences of strings where
/// each string represent one of the functions in the two sequences of
/// functions given in argument to this function.
///
/// @param a_begin the begin iterator for the first input sequence of
/// functions.
///
/// @parm a_end the end iterator for the first input sequence of
/// functions.
///
/// @param b_begin the begin iterator for the second input sequence of
/// functions.
///
/// @param b_end the end iterator for the second input sequence of functions.
void
dump_functions_as_string(std::vector<function_decl*>::const_iterator a_begin,
			 std::vector<function_decl*>::const_iterator a_end,
			 std::vector<function_decl*>::const_iterator b_begin,
			 std::vector<function_decl*>::const_iterator b_end)
{abigail::fns_to_str(a_begin, a_end, b_begin, b_end, std::cerr);}

/// Dump (to the standard error output stream) a pretty representation
/// of the signatures of two sequences of functions.
///
/// @param a_begin the start iterator of the first input sequence of functions.
///
/// @param a_end the end iterator of the first input sequence of functions.
///
/// @param b_begin the start iterator of the second input sequence of functions.
///
/// @param b_end the end iterator of the second input sequence of functions.
void
dump_function_names(std::vector<function_decl*>::const_iterator a_begin,
		    std::vector<function_decl*>::const_iterator a_end,
		    std::vector<function_decl*>::const_iterator b_begin,
		    std::vector<function_decl*>::const_iterator b_end)
{
  std::vector<function_decl*>::const_iterator i;
  std::ostream& o = std::cerr;
  for (i = a_begin; i != a_end; ++i)
    o << (*i)->get_pretty_representation() << "\n";

  o << "  ->|<-  \n";
  for (i = b_begin; i != b_end; ++i)
    o << (*i)->get_pretty_representation() << "\n";
  o << "\n";
}

/// Compare two functions that are in a vector of functions.
///
/// @param an iterator to the beginning of the the sequence of functions.
///
/// @param f1_index the index of the first function to compare.
///
/// @param f2_inde the index of the second function to compare
bool
compare_functions(vector<function_decl*>::const_iterator base,
		  unsigned f1_index, unsigned f2_index)
{
  function_decl* fn1 = base[f1_index];
  function_decl* fn2 = base[f2_index];

  return *fn1 == *fn2;
}

}//end namespace abigail
