// systemtap debuginfo rpm finder
// Copyright (C) 2009-2014 Red Hat Inc.
//
// This file is part of systemtap, and is free software.  You can
// redistribute it and/or modify it under the terms of the GNU General
// Public License (GPL); either version 2, or (at your option) any
// later version.

#include "config.h"
#include "session.h"
#include "rpm_finder.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <cerrno>
#include <cstdlib>

using namespace std;

#ifdef HAVE_LIBRPM

extern "C" {

#include <string.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmts.h>
#include <rpm/rpmdb.h>
#include <rpm/header.h>

#ifndef xfree
#define xfree free
#endif

}

#if ! HAVE_LIBRPMIO && HAVE_NSS
extern "C" {
#include <nss.h>
}
#include "nsscommon.h"
#endif

/* Returns the count of newly added rpms.  */
/* based on the code in F11 gdb-6.8.50.20090302 source rpm */
/* Added in the rpm_type parameter to specify what rpm to look for */

static int
missing_rpm_enlist (systemtap_session& sess, const char *filename, const char *rpm_type)
{
  static int rpm_init_done = 0;
  rpmts ts;
  rpmdbMatchIterator mi;
  int count = 0;

  if (filename == NULL)
    return 0;

  if (!rpm_init_done)
    {
      static int init_tried;

      /* Already failed the initialization before?  */
      if (init_tried)
      	return 0;
      init_tried = 1;

      if (rpmReadConfigFiles(NULL, NULL) != 0)
	{
	  cerr << _("Error reading the rpm configuration files") << endl;
	  return 0;
	}

      rpm_init_done = 1;
    }

  /* If we've seen this combo before, don't check again... */
  if (!sess.rpms_checked.insert(string(filename) + rpm_type).second)
    return 0;

  ts = rpmtsCreate(); 

  mi = rpmtsInitIterator(ts, RPMTAG_BASENAMES, filename, 0);
  if (mi != NULL)
    {
      for (;;)
	{
	  Header h;
	  char *rpminfo, *s, *s2;
	  char header[31] = {};
	  const char* arch = ".%{arch}";
          sprintf(header, "%%{sourcerpm}%s%s", rpm_type, arch);
	  errmsg_t err;
	  size_t rpminfolen = strlen(rpm_type);
	  size_t srcrpmlen = sizeof (".src.rpm") - 1;
	  rpmdbMatchIterator mi_rpminfo;
	  h = rpmdbNextIterator(mi);
	  if (h == NULL)
	    break;
	  /* Verify the kernel file is not already installed.  */

	  rpminfo = headerFormat(h, header, &err);

	  if (!rpminfo)
	    {
	      cerr << _("Error querying the rpm file `") << filename << "': "
		   << err << endl;
	      continue;
	    }
	  /* s = `.src.rpm-debuginfo.%{arch}' */
	  s = strrchr (rpminfo, '-') - srcrpmlen;
	  s2 = NULL;
	  if (s > rpminfo && memcmp (s, ".src.rpm", srcrpmlen) == 0)
	    {
	      /* s2 = `-%{release}.src.rpm-debuginfo.%{arch}' */
	      s2 = (char *) memrchr (rpminfo, '-', s - rpminfo);
	    }
	  if (s2)
	    {
	      /* s2 = `-%{version}-%{release}.src.rpm-debuginfo.%{arch}' */
	      s2 = (char *) memrchr (rpminfo, '-', s2 - rpminfo);
	    }
	  if (!s2)
	    {
	      cerr << _("Error querying the rpm file `") << filename 
		   << "': " << rpminfo << endl;
	      xfree (rpminfo);
	      continue;
	    }
	  /* s = `.src.rpm-debuginfo.%{arch}' */
	  /* s2 = `-%{version}-%{release}.src.rpm-debuginfo.%{arch}' */
	  memmove (s2 + rpminfolen, s2, s - s2);
	  memcpy (s2, rpm_type, rpminfolen);
	  /* s = `XXXX.%{arch}' */
	  /* strlen ("XXXX") == srcrpmlen + debuginfolen */
	  /* s2 = `-debuginfo-%{version}-%{release}XX.%{arch}' */
	  /* strlen ("XX") == srcrpmlen */
	  memmove (s + rpminfolen, s + srcrpmlen + rpminfolen,
		   strlen (s + srcrpmlen + rpminfolen) + 1);
	  /* s = `-debuginfo-%{version}-%{release}.%{arch}' */

	  /* RPMDBI_PACKAGES requires keylen == sizeof (int).  */
	  /* RPMDBI_LABEL is an interface for NVR-based dbiFindByLabel().  */
	  mi_rpminfo = rpmtsInitIterator(ts, (rpmTag)  RPMDBI_LABEL,
					      rpminfo, 0);
	  if (mi_rpminfo)
	    {
	      rpmdbFreeIterator(mi_rpminfo);
	      count = 0;
	      break;
	    }
	  /* The allocated memory gets utilized below for MISSING_RPM_HASH.  */
          if(strcmp(rpm_type,"-debuginfo")==0){
	    xfree(rpminfo);
	    rpminfo = headerFormat(h, "%{name}-%{version}-%{release}.%{arch}",
				   &err);
	  }
	  if (!rpminfo)
	    {
	      cerr << _("Error querying the rpm file `") << filename 
		   << "': " << err << endl;
	      continue;
	    }

	  /* Base package name for `debuginfo-install'.  We do not use the
	  `yum' command directly as the line
	  yum --enablerepo='*-debuginfo' install NAME-debuginfo.ARCH
	  would be more complicated than just:
	  debuginfo-install NAME-VERSION-RELEASE.ARCH
	  Do not supply the rpm base name (derived from .src.rpm name) as
	  debuginfo-install is unable to install the debuginfo package if
	  the base name PKG binary rpm is not installed while for example
	  PKG-libs would be installed (RH Bug 467901).
	  FUTURE: After multiple debuginfo versions simultaneously installed
	  get supported the support for the VERSION-RELEASE tags handling
	  may need an update.  */
	  sess.rpms_to_install.insert(rpminfo);
 	}
      count++;
      rpmdbFreeIterator(mi);
    }

  rpmtsFree(ts);

#if HAVE_NSS
  // librpm uses NSS cryptography but doesn't shut down NSS when it is done.
  // If NSS is available, it will be used by the compile server client on
  // specific certificate databases and thus, it must be shut down first.
  // Get librpm to do it if we can. Otherwise do it ourselves.
#if HAVE_LIBRPMIO
  rpmFreeCrypto (); // Shuts down NSS within librpm
#else
  nssCleanup (NULL); // Shut down NSS ourselves
#endif
#endif

  return count;
}
#endif	/* HAVE_LIBRPM */

#ifdef HAVE_LIBRPM
void
missing_rpm_list_print (systemtap_session &sess, const char* rpm_type)
{
  if (sess.rpms_to_install.size() > 0 && ! sess.suppress_warnings) {

    if(strcmp(rpm_type,"-devel")==0)
    {
	// We default to using 'yum' as the package manager, unless we
	// can find 'dnf'.
	string pkg_mgr = "yum";
	string dnf_path = find_executable("dnf");

	if (is_fully_resolved(dnf_path, "", sess.sysenv))
	    pkg_mgr = "dnf";
	cerr << sess.colorize(_F("Incorrect version or missing kernel-devel package, use: %s install ", pkg_mgr.c_str()),
                              "error");
    }
    else if(strcmp(rpm_type,"-debuginfo")==0)
      cerr << sess.colorize(_("Missing separate debuginfos, use: debuginfo-install "), "error");

    else{
        cerr << _("Incorrect parameter passed, please report this error.") << endl;
	_exit(1);
	}

    for (set<std::string>::iterator it=sess.rpms_to_install.begin();
	 it !=sess.rpms_to_install.end(); it++)
    {
      cerr << sess.colorize(*it,"error") << " ";
    }
    cerr << endl;
  }
}
#else
void
missing_rpm_list_print (systemtap_session &, const char *)
{
}
#endif

#ifdef HAVE_LIBRPM
int
find_debug_rpms (systemtap_session &sess, const char * filename)
{
  const char *rpm_type = "-debuginfo";
  return missing_rpm_enlist(sess, filename, rpm_type);
}
#else
int
find_debug_rpms (systemtap_session &, const char *)
{
  return 0;
}
#endif


#ifdef HAVE_LIBRPM
int find_devel_rpms(systemtap_session &sess, const char * filename)
{
  const char *rpm_type = "-devel";
  return missing_rpm_enlist(sess, filename, rpm_type);
}
#else
int find_devel_rpms(systemtap_session &, const char *)
{
  return 0;
}
#endif
