Imported Upstream version 17.20.0
[platform/upstream/libzypp.git] / zypp / target / rpm / RpmDb.cc
index 7203d79..00240a4 100644 (file)
@@ -33,11 +33,13 @@ extern "C"
 #include "zypp/base/String.h"
 #include "zypp/base/Gettext.h"
 #include "zypp/base/LocaleGuard.h"
+#include "zypp/base/DtorReset.h"
 
 #include "zypp/Date.h"
 #include "zypp/Pathname.h"
 #include "zypp/PathInfo.h"
 #include "zypp/PublicKey.h"
+#include "zypp/ProgressData.h"
 
 #include "zypp/target/rpm/RpmDb.h"
 #include "zypp/target/rpm/RpmCallbacks.h"
@@ -183,37 +185,6 @@ inline std::string stringPath( const Pathname & root_r, const Pathname & sub_r )
   return librpmDb::stringPath( root_r, sub_r );
 }
 
-/******************************************************************
- **
- **
- **    FUNCTION NAME : operator<<
- **    FUNCTION TYPE : std::ostream &
-*/
-std::ostream & operator<<( std::ostream & str, const RpmDb::DbStateInfoBits & obj )
-{
-  if ( obj == RpmDb::DbSI_NO_INIT )
-  {
-    str << "NO_INIT";
-  }
-  else
-  {
-#define ENUM_OUT(B,C) str << ( obj & RpmDb::B ? C : '-' )
-    str << "V4(";
-    ENUM_OUT( DbSI_HAVE_V4,    'X' );
-    ENUM_OUT( DbSI_MADE_V4,    'c' );
-    ENUM_OUT( DbSI_MODIFIED_V4,        'm' );
-    str << ")V3(";
-    ENUM_OUT( DbSI_HAVE_V3,    'X' );
-    ENUM_OUT( DbSI_HAVE_V3TOV4,        'B' );
-    ENUM_OUT( DbSI_MADE_V3TOV4,        'c' );
-    str << ")";
-#undef ENUM_OUT
-  }
-  return str;
-}
-
-
-
 ///////////////////////////////////////////////////////////////////
 //
 //     CLASS NAME : RpmDb
@@ -231,11 +202,8 @@ std::ostream & operator<<( std::ostream & str, const RpmDb::DbStateInfoBits & ob
 //     METHOD TYPE : Constructor
 //
 RpmDb::RpmDb()
-    : _dbStateInfo( DbSI_NO_INIT )
-#warning Check for obsolete memebers
-    , _backuppath ("/var/adm/backup")
+    : _backuppath ("/var/adm/backup")
     , _packagebackups(false)
-    , _warndirexists(false)
 {
   process = 0;
   exit_code = -1;
@@ -261,23 +229,6 @@ RpmDb::~RpmDb()
   sKeyRingReceiver.reset();
 }
 
-Date RpmDb::timestamp() const
-{
-  Date ts_rpm;
-
-  Pathname db_path;
-  if ( dbPath().empty() )
-    db_path = "/var/lib/rpm";
-  else
-    db_path = dbPath();
-
-  PathInfo rpmdb_info(root() + db_path + "/Packages");
-
-  if ( rpmdb_info.isExist() )
-    return rpmdb_info.mtime();
-  else
-    return Date::now();
-}
 ///////////////////////////////////////////////////////////////////
 //
 //
@@ -286,27 +237,7 @@ Date RpmDb::timestamp() const
 //
 std::ostream & RpmDb::dumpOn( std::ostream & str ) const
 {
-  str << "RpmDb[";
-
-  if ( _dbStateInfo == DbSI_NO_INIT )
-  {
-    str << "NO_INIT";
-  }
-  else
-  {
-#define ENUM_OUT(B,C) str << ( _dbStateInfo & B ? C : '-' )
-    str << "V4(";
-    ENUM_OUT( DbSI_HAVE_V4,    'X' );
-    ENUM_OUT( DbSI_MADE_V4,    'c' );
-    ENUM_OUT( DbSI_MODIFIED_V4,        'm' );
-    str << ")V3(";
-    ENUM_OUT( DbSI_HAVE_V3,    'X' );
-    ENUM_OUT( DbSI_HAVE_V3TOV4,        'B' );
-    ENUM_OUT( DbSI_MADE_V3TOV4,        'c' );
-    str << "): " << stringPath( _root, _dbPath );
-#undef ENUM_OUT
-  }
-  return str << "]";
+  return str << "RpmDb[" << stringPath( _root, _dbPath ) << "]";
 }
 
 ///////////////////////////////////////////////////////////////////
@@ -315,7 +246,7 @@ std::ostream & RpmDb::dumpOn( std::ostream & str ) const
 //     METHOD NAME : RpmDb::initDatabase
 //     METHOD TYPE : PMError
 //
-void RpmDb::initDatabase( Pathname root_r, Pathname dbPath_r, bool doRebuild_r )
+void RpmDb::initDatabase( Pathname root_r, bool doRebuild_r )
 {
   ///////////////////////////////////////////////////////////////////
   // Check arguments
@@ -325,17 +256,19 @@ void RpmDb::initDatabase( Pathname root_r, Pathname dbPath_r, bool doRebuild_r )
   if ( root_r.empty() )
     root_r = "/";
 
-  if ( dbPath_r.empty() )
-    dbPath_r = "/var/lib/rpm";
+  // NOTE: Former argument, but now locked to "/var/lib/rpm".
+  // A custom dbPath is not actually needed and would only work
+  // reliably if libsolv also supports it. By now no further
+  // cleanup in the code.
+  const Pathname & dbPath_r { librpmDb::defaultDbPath() };
 
-  if ( ! (root_r.absolute() && dbPath_r.absolute()) )
+  if ( ! root_r.absolute() )
   {
     ERR << "Illegal root or dbPath: " << stringPath( root_r, dbPath_r ) << endl;
     ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
   }
 
-  if ( dbPath_r == "/var/lib/rpm"
-    && ! PathInfo( root_r/"/var/lib/rpm" ).isExist()
+  if ( ! PathInfo( root_r/"/var/lib/rpm" ).isExist()
     && PathInfo( root_r/"/usr/lib/sysimage/rpm" ).isDir() )
   {
     WAR << "Rpm package was deleted? Injecting missing rpmdb compat symlink." << endl;
@@ -372,56 +305,23 @@ void RpmDb::initDatabase( Pathname root_r, Pathname dbPath_r, bool doRebuild_r )
     return;
   }
 
-  DbStateInfoBits info = DbSI_NO_INIT;
   try
   {
-    internal_initDatabase( root_r, dbPath_r, info );
+    // creates dbdir and empty rpm database if not present
+    librpmDb::dbAccess( root_r );
   }
   catch (const RpmException & excpt_r)
   {
     ZYPP_CAUGHT(excpt_r);
     librpmDb::blockAccess();
-    ERR << "Cleanup on error: state " << info << endl;
-
-    if ( dbsi_has( info, DbSI_MADE_V4 ) )
-    {
-      // remove the newly created rpm4 database and
-      // any backup created on conversion.
-      removeV4( root_r + dbPath_r, dbsi_has( info, DbSI_MADE_V3TOV4 ) );
-    }
     ZYPP_RETHROW(excpt_r);
   }
-  if ( dbsi_has( info, DbSI_HAVE_V3 ) )
-  {
-    if ( root_r == "/" || dbsi_has( info, DbSI_MODIFIED_V4 ) )
-    {
-      // Move obsolete rpm3 database beside.
-      MIL << "Cleanup: state " << info << endl;
-      removeV3( root_r + dbPath_r, dbsi_has( info, DbSI_MADE_V3TOV4 ) );
-      dbsi_clr( info, DbSI_HAVE_V3 );
-    }
-    else
-    {
-      // Performing an update: Keep the original rpm3 database
-      // and wait if the rpm4 database gets modified by installing
-      // or removing packages. Cleanup in modifyDatabase or closeDatabase.
-      MIL << "Update mode: Cleanup delayed until closeOldDatabase." << endl;
-    }
-  }
-#warning CHECK: notify root about conversion backup.
 
   _root   = root_r;
   _dbPath = dbPath_r;
-  _dbStateInfo = info;
 
   if ( doRebuild_r )
-  {
-    if (      dbsi_has( info, DbSI_HAVE_V4 )
-         && ! dbsi_has( info, DbSI_MADE_V4 ) )
-    {
-      rebuildDatabase();
-    }
-  }
+    rebuildDatabase();
 
   MIL << "Synchronizing keys with zypp keyring" << endl;
   syncTrustedKeys();
@@ -438,286 +338,6 @@ void RpmDb::initDatabase( Pathname root_r, Pathname dbPath_r, bool doRebuild_r )
 ///////////////////////////////////////////////////////////////////
 //
 //
-//     METHOD NAME : RpmDb::internal_initDatabase
-//     METHOD TYPE : PMError
-//
-void RpmDb::internal_initDatabase( const Pathname & root_r, const Pathname & dbPath_r,
-                                   DbStateInfoBits & info_r )
-{
-  info_r = DbSI_NO_INIT;
-
-  ///////////////////////////////////////////////////////////////////
-  // Get info about the desired database dir
-  ///////////////////////////////////////////////////////////////////
-  librpmDb::DbDirInfo dbInfo( root_r, dbPath_r );
-
-  if ( dbInfo.illegalArgs() )
-  {
-    // should not happen (checked in initDatabase)
-    ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
-  }
-  if ( ! dbInfo.usableArgs() )
-  {
-    ERR << "Bad database directory: " << dbInfo.dbDir() << endl;
-    ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
-  }
-
-  if ( dbInfo.hasDbV4() )
-  {
-    dbsi_set( info_r, DbSI_HAVE_V4 );
-    MIL << "Found rpm4 database in " << dbInfo.dbDir() << endl;
-  }
-  else
-  {
-    MIL << "Creating new rpm4 database in " << dbInfo.dbDir() << endl;
-  }
-
-  if ( dbInfo.hasDbV3() )
-  {
-    dbsi_set( info_r, DbSI_HAVE_V3 );
-  }
-  if ( dbInfo.hasDbV3ToV4() )
-  {
-    dbsi_set( info_r, DbSI_HAVE_V3TOV4 );
-  }
-
-  DBG << "Initial state: " << info_r << ": " << stringPath( root_r, dbPath_r );
-  librpmDb::dumpState( DBG ) << endl;
-
-  ///////////////////////////////////////////////////////////////////
-  // Access database, create if needed
-  ///////////////////////////////////////////////////////////////////
-
-  // creates dbdir and empty rpm4 database if not present
-  librpmDb::dbAccess( root_r, dbPath_r );
-
-  if ( ! dbInfo.hasDbV4() )
-  {
-    dbInfo.restat();
-    if ( dbInfo.hasDbV4() )
-    {
-      dbsi_set( info_r, DbSI_HAVE_V4 | DbSI_MADE_V4 );
-    }
-  }
-
-  DBG << "Access state: " << info_r << ": " << stringPath( root_r, dbPath_r );
-  librpmDb::dumpState( DBG ) << endl;
-
-  ///////////////////////////////////////////////////////////////////
-  // Check whether to convert something. Create backup but do
-  // not remove anything here
-  ///////////////////////////////////////////////////////////////////
-  librpmDb::constPtr dbptr;
-  librpmDb::dbAccess( dbptr );
-  bool dbEmpty = dbptr->empty();
-  if ( dbEmpty )
-  {
-    MIL << "Empty rpm4 database "  << dbInfo.dbV4() << endl;
-  }
-
-  if ( dbInfo.hasDbV3() )
-  {
-    MIL << "Found rpm3 database " << dbInfo.dbV3() << endl;
-
-    if ( dbEmpty )
-    {
-      extern void convertV3toV4( const Pathname & v3db_r, const librpmDb::constPtr & v4db_r );
-      convertV3toV4( dbInfo.dbV3().path(), dbptr );
-
-      // create a backup copy
-      int res = filesystem::copy( dbInfo.dbV3().path(), dbInfo.dbV3ToV4().path() );
-      if ( res )
-      {
-        WAR << "Backup converted rpm3 database failed: error(" << res << ")" << endl;
-      }
-      else
-      {
-        dbInfo.restat();
-        if ( dbInfo.hasDbV3ToV4() )
-        {
-          MIL << "Backup converted rpm3 database: " << dbInfo.dbV3ToV4() << endl;
-          dbsi_set( info_r, DbSI_HAVE_V3TOV4 | DbSI_MADE_V3TOV4 );
-        }
-      }
-
-    }
-    else
-    {
-
-      WAR << "Non empty rpm3 and rpm4 database found: using rpm4" << endl;
-      // set DbSI_MODIFIED_V4 as it's not a temporary which can be removed.
-      dbsi_set( info_r, DbSI_MODIFIED_V4 );
-
-    }
-
-    DBG << "Convert state: " << info_r << ": " << stringPath( root_r, dbPath_r );
-    librpmDb::dumpState( DBG ) << endl;
-  }
-
-  if ( dbInfo.hasDbV3ToV4() )
-  {
-    MIL << "Rpm3 database backup: " << dbInfo.dbV3ToV4() << endl;
-  }
-}
-
-///////////////////////////////////////////////////////////////////
-//
-//
-//     METHOD NAME : RpmDb::removeV4
-//     METHOD TYPE : void
-//
-void RpmDb::removeV4( const Pathname & dbdir_r, bool v3backup_r )
-{
-  const char * v3backup = "packages.rpm3";
-  const char * master = "Packages";
-  const char * index[] =
-    {
-      "Basenames",
-      "Conflictname",
-      "Depends",
-      "Dirnames",
-      "Filemd5s",
-      "Group",
-      "Installtid",
-      "Name",
-      "Providename",
-      "Provideversion",
-      "Pubkeys",
-      "Requirename",
-      "Requireversion",
-      "Sha1header",
-      "Sigmd5",
-      "Triggername",
-      // last entry!
-      NULL
-    };
-
-  PathInfo pi( dbdir_r );
-  if ( ! pi.isDir() )
-  {
-    ERR << "Can't remove rpm4 database in non directory: " << dbdir_r << endl;
-    return;
-  }
-
-  for ( const char ** f = index; *f; ++f )
-  {
-    pi( dbdir_r + *f );
-    if ( pi.isFile() )
-    {
-      filesystem::unlink( pi.path() );
-    }
-  }
-
-  pi( dbdir_r + master );
-  if ( pi.isFile() )
-  {
-    MIL << "Removing rpm4 database " << pi << endl;
-    filesystem::unlink( pi.path() );
-  }
-
-  if ( v3backup_r )
-  {
-    pi( dbdir_r + v3backup );
-    if ( pi.isFile() )
-    {
-      MIL << "Removing converted rpm3 database backup " << pi << endl;
-      filesystem::unlink( pi.path() );
-    }
-  }
-}
-
-///////////////////////////////////////////////////////////////////
-//
-//
-//     METHOD NAME : RpmDb::removeV3
-//     METHOD TYPE : void
-//
-void RpmDb::removeV3( const Pathname & dbdir_r, bool v3backup_r )
-{
-  const char * master = "packages.rpm";
-  const char * index[] =
-    {
-      "conflictsindex.rpm",
-      "fileindex.rpm",
-      "groupindex.rpm",
-      "nameindex.rpm",
-      "providesindex.rpm",
-      "requiredby.rpm",
-      "triggerindex.rpm",
-      // last entry!
-      NULL
-    };
-
-  PathInfo pi( dbdir_r );
-  if ( ! pi.isDir() )
-  {
-    ERR << "Can't remove rpm3 database in non directory: " << dbdir_r << endl;
-    return;
-  }
-
-  for ( const char ** f = index; *f; ++f )
-  {
-    pi( dbdir_r + *f );
-    if ( pi.isFile() )
-    {
-      filesystem::unlink( pi.path() );
-    }
-  }
-
-#warning CHECK: compare vs existing v3 backup. notify root
-  pi( dbdir_r + master );
-  if ( pi.isFile() )
-  {
-    Pathname m( pi.path() );
-    if ( v3backup_r )
-    {
-      // backup was already created
-      filesystem::unlink( m );
-      Pathname b( m.extend( "3" ) );
-      pi( b ); // stat backup
-    }
-    else
-    {
-      Pathname b( m.extend( ".deleted" ) );
-      pi( b );
-      if ( pi.isFile() )
-      {
-        // rempve existing backup
-        filesystem::unlink( b );
-      }
-      filesystem::rename( m, b );
-      pi( b ); // stat backup
-    }
-    MIL << "(Re)moved rpm3 database to " << pi << endl;
-  }
-}
-
-///////////////////////////////////////////////////////////////////
-//
-//
-//     METHOD NAME : RpmDb::modifyDatabase
-//     METHOD TYPE : void
-//
-void RpmDb::modifyDatabase()
-{
-  if ( ! initialized() )
-    return;
-
-  // tag database as modified
-  dbsi_set( _dbStateInfo, DbSI_MODIFIED_V4 );
-
-  // Move outdated rpm3 database beside.
-  if ( dbsi_has( _dbStateInfo, DbSI_HAVE_V3 ) )
-  {
-    MIL << "Update mode: Delayed cleanup: state " << _dbStateInfo << endl;
-    removeV3( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 ) );
-    dbsi_clr( _dbStateInfo, DbSI_HAVE_V3 );
-  }
-}
-
-///////////////////////////////////////////////////////////////////
-//
-//
 //     METHOD NAME : RpmDb::closeDatabase
 //     METHOD TYPE : PMError
 //
@@ -736,28 +356,9 @@ void RpmDb::closeDatabase()
   librpmDb::blockAccess();
 
   ///////////////////////////////////////////////////////////////////
-  // Check fate if old version database still present
-  ///////////////////////////////////////////////////////////////////
-  if ( dbsi_has( _dbStateInfo, DbSI_HAVE_V3 ) )
-  {
-    MIL << "Update mode: Delayed cleanup: state " << _dbStateInfo << endl;
-    if ( dbsi_has( _dbStateInfo, DbSI_MODIFIED_V4 ) )
-    {
-      // Move outdated rpm3 database beside.
-      removeV3( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 )  );
-    }
-    else
-    {
-      // Remove unmodified rpm4 database
-      removeV4( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 ) );
-    }
-  }
-
-  ///////////////////////////////////////////////////////////////////
   // Uninit
   ///////////////////////////////////////////////////////////////////
   _root = _dbPath = Pathname();
-  _dbStateInfo = DbSI_NO_INIT;
 
   MIL << "closeDatabase: " << *this << endl;
 }
@@ -789,62 +390,73 @@ void RpmDb::rebuildDatabase()
 void RpmDb::doRebuildDatabase(callback::SendReport<RebuildDBReport> & report)
 {
   FAILIFNOTINITIALIZED;
-
   MIL << "RpmDb::rebuildDatabase" << *this << endl;
-  // FIXME  Timecount _t( "RpmDb::rebuildDatabase" );
 
-  PathInfo dbMaster( root() + dbPath() + "Packages" );
-  PathInfo dbMasterBackup( dbMaster.path().extend( ".y2backup" ) );
-
-  // run rpm
-  RpmArgVec opts;
-  opts.push_back("--rebuilddb");
-  opts.push_back("-vv");
-
-  // don't call modifyDatabase because it would remove the old
-  // rpm3 database, if the current database is a temporary one.
-  run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
+  const Pathname mydbpath { root()/dbPath() }; // the configured path used in reports
+  {
+    // For --rebuilddb take care we're using the real db directory
+    // and not a symlink. Otherwise rpm will rename the symlink and
+    // replace it with a real directory containing the converted db.
+    DtorReset guardRoot  { _root };
+    DtorReset guardDbPath{ _dbPath };
+    _root = "/";
+    _dbPath = filesystem::expandlink( mydbpath );
 
-  // progress report: watch this file growing
-  PathInfo newMaster( root()
-                      + dbPath().extend( str::form( "rebuilddb.%d",
-                                                    process?process->getpid():0) )
-                      + "Packages" );
+    // run rpm
+    RpmArgVec opts;
+    opts.push_back("--rebuilddb");
+    opts.push_back("-vv");
+    run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
+  }
 
-  std::string       line;
-  std::string       errmsg;
+  // generate and report progress
+  ProgressData tics;
+  {
+    ProgressData::value_type hdrTotal = 0;
+    for ( librpmDb::db_const_iterator it; *it; ++it, ++hdrTotal )
+    {;}
+    tics.range( hdrTotal );
+  }
+  tics.sendTo( [&report,&mydbpath]( const ProgressData & tics_r ) -> bool {
+    return report->progress( tics_r.reportValue(), mydbpath );
+  } );
+  tics.toMin();
 
+  std::string line;
+  std::string errmsg;
   while ( systemReadLine( line ) )
   {
-    if ( newMaster() )
-    { // file is removed at the end of rebuild.
-      // current size should be upper limit for new db
-      if ( ! report->progress( (100 * newMaster.size()) / dbMaster.size(), root() + dbPath()) )
+    static const std::string debugPrefix    { "D:" };
+    static const std::string progressPrefix { "D:  read h#" };
+    static const std::string ignoreSuffix   { "digest: OK" };
+
+    if ( ! str::startsWith( line, debugPrefix ) )
+    {
+      if ( ! str::endsWith( line, ignoreSuffix ) )
       {
-        WAR << "User requested abort." << endl;
-        systemKill();
-        filesystem::recursive_rmdir( newMaster.path().dirname() );
+       errmsg += line;
+       errmsg += '\n';
+       WAR << line << endl;
       }
     }
-
-    if ( line.compare( 0, 2, "D:" ) )
+    else if ( str::startsWith( line, progressPrefix ) )
     {
-      errmsg += line + '\n';
-      //      report.notify( line );
-      WAR << line << endl;
+      if ( ! tics.incr() )
+      {
+       WAR << "User requested abort." << endl;
+       systemKill();
+      }
     }
   }
 
-  int rpm_status = systemStatus();
-
-  if ( rpm_status != 0 )
+  if ( systemStatus() != 0 )
   {
     //TranslatorExplanation after semicolon is error message
     ZYPP_THROW(RpmSubprocessException(std::string(_("RPM failed: ")) + (errmsg.empty() ? error_message: errmsg) ) );
   }
   else
   {
-    report->progress( 100, root() + dbPath() ); // 100%
+    tics.toMax();
   }
 }
 
@@ -1123,8 +735,6 @@ void RpmDb::importPubkey( const PublicKey & pubkey_r )
     opts.push_back ( "--allmatches" );
     opts.push_back ( "--" );
     opts.push_back ( keyName.c_str() );
-    // don't call modifyDatabase because it would remove the old
-    // rpm3 database, if the current database is a temporary one.
     run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
 
     std::string line;
@@ -1149,9 +759,6 @@ void RpmDb::importPubkey( const PublicKey & pubkey_r )
   opts.push_back ( "--" );
   std::string pubkeypath( pubkey_r.path().asString() );
   opts.push_back ( pubkeypath.c_str() );
-
-  // don't call modifyDatabase because it would remove the old
-  // rpm3 database, if the current database is a temporary one.
   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
 
   std::string line;
@@ -1219,9 +826,6 @@ void RpmDb::removePubkey( const PublicKey & pubkey_r )
   opts.push_back ( "-e" );
   opts.push_back ( "--" );
   opts.push_back ( rpm_name.c_str() );
-
-  // don't call modifyDatabase because it would remove the old
-  // rpm3 database, if the current database is a temporary one.
   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
 
   std::string line;
@@ -2061,8 +1665,6 @@ void RpmDb::doInstallPackage( const Pathname & filename, RpmInstFlags flags, cal
   // rpm requires additional quoting of special chars:
   std::string quotedFilename( rpmQuoteFilename( workaroundRpmPwdBug( filename ) ) );
   opts.push_back ( quotedFilename.c_str() );
-
-  modifyDatabase(); // BEFORE run_rpm
   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
 
   std::string line;
@@ -2232,8 +1834,6 @@ void RpmDb::doRemovePackage( const std::string & name_r, RpmInstFlags flags, cal
 
   opts.push_back("--");
   opts.push_back(name_r.c_str());
-
-  modifyDatabase(); // BEFORE run_rpm
   run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
 
   std::string line;