libzypp 9.41.1

RpmDb.cc

Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00012 #include "librpm.h"
00013 
00014 #include <cstdlib>
00015 #include <cstdio>
00016 #include <ctime>
00017 
00018 #include <iostream>
00019 #include <fstream>
00020 #include <sstream>
00021 #include <list>
00022 #include <map>
00023 #include <set>
00024 #include <string>
00025 #include <vector>
00026 #include <algorithm>
00027 
00028 #include <boost/format.hpp>
00029 
00030 #include "zypp/base/Logger.h"
00031 #include "zypp/base/String.h"
00032 #include "zypp/base/Gettext.h"
00033 
00034 #include "zypp/Date.h"
00035 #include "zypp/Pathname.h"
00036 #include "zypp/PathInfo.h"
00037 #include "zypp/PublicKey.h"
00038 
00039 #include "zypp/target/rpm/RpmDb.h"
00040 #include "zypp/target/rpm/RpmCallbacks.h"
00041 
00042 #include "zypp/HistoryLog.h"
00043 #include "zypp/target/rpm/librpmDb.h"
00044 #include "zypp/target/rpm/RpmException.h"
00045 #include "zypp/TmpPath.h"
00046 #include "zypp/KeyRing.h"
00047 #include "zypp/ZYppFactory.h"
00048 #include "zypp/ZConfig.h"
00049 
00050 using namespace std;
00051 using namespace zypp::filesystem;
00052 
00053 #define WARNINGMAILPATH         "/var/log/YaST2/"
00054 #define FILEFORBACKUPFILES      "YaSTBackupModifiedFiles"
00055 #define MAXRPMMESSAGELINES      10000
00056 
00057 namespace zypp
00058 {
00059   namespace zypp_readonly_hack
00060   {
00061     bool IGotIt(); // in readonly-mode
00062   }
00063 namespace target
00064 {
00065 namespace rpm
00066 {
00067 namespace
00068 {
00069 #if 1 // No more need to escape whitespace since rpm-4.4.2.3
00070 const char* quoteInFilename_m = "\'\"";
00071 #else
00072 const char* quoteInFilename_m = " \t\'\"";
00073 #endif
00074 inline string rpmQuoteFilename( const Pathname & path_r )
00075 {
00076   string path( path_r.asString() );
00077   for ( string::size_type pos = path.find_first_of( quoteInFilename_m );
00078         pos != string::npos;
00079         pos = path.find_first_of( quoteInFilename_m, pos ) )
00080   {
00081     path.insert( pos, "\\" );
00082     pos += 2; // skip '\\' and the quoted char.
00083   }
00084   return path;
00085 }
00086 }
00087 
00088 struct KeyRingSignalReceiver : callback::ReceiveReport<KeyRingSignals>
00089 {
00090   KeyRingSignalReceiver(RpmDb &rpmdb) : _rpmdb(rpmdb)
00091   {
00092     connect();
00093   }
00094 
00095   ~KeyRingSignalReceiver()
00096   {
00097     disconnect();
00098   }
00099 
00100   virtual void trustedKeyAdded( const PublicKey &key )
00101   {
00102     MIL << "trusted key added to zypp Keyring. Importing" << endl;
00103     // now import the key in rpm
00104     try
00105     {
00106       _rpmdb.importPubkey( key );
00107     }
00108     catch (RpmException &e)
00109     {
00110       ERR << "Could not import key " << key.id() << " (" << key.name() << " from " << key.path() << " in rpm database" << endl;
00111     }
00112   }
00113 
00114   virtual void trustedKeyRemoved( const PublicKey &key  )
00115   {
00116     MIL << "Trusted key removed from zypp Keyring. Removing..." << endl;
00117 
00118     // remove the key from rpm
00119     try
00120     {
00121       _rpmdb.removePubkey( key );
00122     }
00123     catch (RpmException &e)
00124     {
00125       ERR << "Could not remove key " << key.id() << " (" << key.name() << ") from rpm database" << endl;
00126     }
00127   }
00128 
00129   RpmDb &_rpmdb;
00130 };
00131 
00132 static shared_ptr<KeyRingSignalReceiver> sKeyRingReceiver;
00133 
00134 unsigned diffFiles(const string file1, const string file2, string& out, int maxlines)
00135 {
00136   const char* argv[] =
00137     {
00138       "diff",
00139       "-u",
00140       file1.c_str(),
00141       file2.c_str(),
00142       NULL
00143     };
00144   ExternalProgram prog(argv,ExternalProgram::Discard_Stderr, false, -1, true);
00145 
00146   //if(!prog)
00147   //return 2;
00148 
00149   string line;
00150   int count = 0;
00151   for (line = prog.receiveLine(), count=0;
00152        !line.empty();
00153        line = prog.receiveLine(), count++ )
00154   {
00155     if (maxlines<0?true:count<maxlines)
00156       out+=line;
00157   }
00158 
00159   return prog.close();
00160 }
00161 
00162 
00163 
00164 /******************************************************************
00165  **
00166  **
00167  **     FUNCTION NAME : stringPath
00168  **     FUNCTION TYPE : inline string
00169 */
00170 inline string stringPath( const Pathname & root_r, const Pathname & sub_r )
00171 {
00172   return librpmDb::stringPath( root_r, sub_r );
00173 }
00174 
00175 /******************************************************************
00176  **
00177  **
00178  **     FUNCTION NAME : operator<<
00179  **     FUNCTION TYPE : ostream &
00180 */
00181 ostream & operator<<( ostream & str, const RpmDb::DbStateInfoBits & obj )
00182 {
00183   if ( obj == RpmDb::DbSI_NO_INIT )
00184   {
00185     str << "NO_INIT";
00186   }
00187   else
00188   {
00189 #define ENUM_OUT(B,C) str << ( obj & RpmDb::B ? C : '-' )
00190     str << "V4(";
00191     ENUM_OUT( DbSI_HAVE_V4,     'X' );
00192     ENUM_OUT( DbSI_MADE_V4,     'c' );
00193     ENUM_OUT( DbSI_MODIFIED_V4, 'm' );
00194     str << ")V3(";
00195     ENUM_OUT( DbSI_HAVE_V3,     'X' );
00196     ENUM_OUT( DbSI_HAVE_V3TOV4, 'B' );
00197     ENUM_OUT( DbSI_MADE_V3TOV4, 'c' );
00198     str << ")";
00199 #undef ENUM_OUT
00200   }
00201   return str;
00202 }
00203 
00204 
00205 
00207 //
00208 //      CLASS NAME : RpmDb
00209 //
00211 
00212 #define FAILIFNOTINITIALIZED if( ! initialized() ) { ZYPP_THROW(RpmDbNotOpenException()); }
00213 
00215 
00217 //
00218 //
00219 //      METHOD NAME : RpmDb::RpmDb
00220 //      METHOD TYPE : Constructor
00221 //
00222 RpmDb::RpmDb()
00223     : _dbStateInfo( DbSI_NO_INIT )
00224 #warning Check for obsolete memebers
00225     , _backuppath ("/var/adm/backup")
00226     , _packagebackups(false)
00227     , _warndirexists(false)
00228 {
00229   process = 0;
00230   exit_code = -1;
00231   librpmDb::globalInit();
00232   // Some rpm versions are patched not to abort installation if
00233   // symlink creation failed.
00234   setenv( "RPM_IgnoreFailedSymlinks", "1", 1 );
00235   sKeyRingReceiver.reset(new KeyRingSignalReceiver(*this));
00236 }
00237 
00239 //
00240 //
00241 //      METHOD NAME : RpmDb::~RpmDb
00242 //      METHOD TYPE : Destructor
00243 //
00244 RpmDb::~RpmDb()
00245 {
00246   MIL << "~RpmDb()" << endl;
00247   closeDatabase();
00248   delete process;
00249   MIL  << "~RpmDb() end" << endl;
00250   sKeyRingReceiver.reset();
00251 }
00252 
00253 Date RpmDb::timestamp() const
00254 {
00255   Date ts_rpm;
00256 
00257   Pathname db_path;
00258   if ( dbPath().empty() )
00259     db_path = "/var/lib/rpm";
00260   else
00261     db_path = dbPath();
00262 
00263   PathInfo rpmdb_info(root() + db_path + "/Packages");
00264 
00265   if ( rpmdb_info.isExist() )
00266     return rpmdb_info.mtime();
00267   else
00268     return Date::now();
00269 }
00271 //
00272 //
00273 //      METHOD NAME : RpmDb::dumpOn
00274 //      METHOD TYPE : ostream &
00275 //
00276 ostream & RpmDb::dumpOn( ostream & str ) const
00277 {
00278   str << "RpmDb[";
00279 
00280   if ( _dbStateInfo == DbSI_NO_INIT )
00281   {
00282     str << "NO_INIT";
00283   }
00284   else
00285   {
00286 #define ENUM_OUT(B,C) str << ( _dbStateInfo & B ? C : '-' )
00287     str << "V4(";
00288     ENUM_OUT( DbSI_HAVE_V4,     'X' );
00289     ENUM_OUT( DbSI_MADE_V4,     'c' );
00290     ENUM_OUT( DbSI_MODIFIED_V4, 'm' );
00291     str << ")V3(";
00292     ENUM_OUT( DbSI_HAVE_V3,     'X' );
00293     ENUM_OUT( DbSI_HAVE_V3TOV4, 'B' );
00294     ENUM_OUT( DbSI_MADE_V3TOV4, 'c' );
00295     str << "): " << stringPath( _root, _dbPath );
00296 #undef ENUM_OUT
00297   }
00298   return str << "]";
00299 }
00300 
00302 //
00303 //
00304 //      METHOD NAME : RpmDb::initDatabase
00305 //      METHOD TYPE : PMError
00306 //
00307 void RpmDb::initDatabase( Pathname root_r, Pathname dbPath_r, bool doRebuild_r )
00308 {
00310   // Check arguments
00312   bool quickinit( root_r.empty() );
00313 
00314   if ( root_r.empty() )
00315     root_r = "/";
00316 
00317   if ( dbPath_r.empty() )
00318     dbPath_r = "/var/lib/rpm";
00319 
00320   if ( ! (root_r.absolute() && dbPath_r.absolute()) )
00321   {
00322     ERR << "Illegal root or dbPath: " << stringPath( root_r, dbPath_r ) << endl;
00323     ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
00324   }
00325 
00326   MIL << "Calling initDatabase: " << stringPath( root_r, dbPath_r )
00327       << ( doRebuild_r ? " (rebuilddb)" : "" )
00328       << ( quickinit ? " (quickinit)" : "" ) << endl;
00329 
00331   // Check whether already initialized
00333   if ( initialized() )
00334   {
00335     if ( root_r == _root && dbPath_r == _dbPath )
00336     {
00337       return;
00338     }
00339     else
00340     {
00341       ZYPP_THROW(RpmDbAlreadyOpenException(_root, _dbPath, root_r, dbPath_r));
00342     }
00343   }
00344 
00346   // init database
00348   librpmDb::unblockAccess();
00349 
00350   if ( quickinit )
00351   {
00352     MIL << "QUICK initDatabase (no systemRoot set)" << endl;
00353     return;
00354   }
00355 
00356   DbStateInfoBits info = DbSI_NO_INIT;
00357   try
00358   {
00359     internal_initDatabase( root_r, dbPath_r, info );
00360   }
00361   catch (const RpmException & excpt_r)
00362   {
00363     ZYPP_CAUGHT(excpt_r);
00364     librpmDb::blockAccess();
00365     ERR << "Cleanup on error: state " << info << endl;
00366 
00367     if ( dbsi_has( info, DbSI_MADE_V4 ) )
00368     {
00369       // remove the newly created rpm4 database and
00370       // any backup created on conversion.
00371       removeV4( root_r + dbPath_r, dbsi_has( info, DbSI_MADE_V3TOV4 ) );
00372     }
00373     ZYPP_RETHROW(excpt_r);
00374   }
00375   if ( dbsi_has( info, DbSI_HAVE_V3 ) )
00376   {
00377     if ( root_r == "/" || dbsi_has( info, DbSI_MODIFIED_V4 ) )
00378     {
00379       // Move obsolete rpm3 database beside.
00380       MIL << "Cleanup: state " << info << endl;
00381       removeV3( root_r + dbPath_r, dbsi_has( info, DbSI_MADE_V3TOV4 ) );
00382       dbsi_clr( info, DbSI_HAVE_V3 );
00383     }
00384     else
00385     {
00386       // Performing an update: Keep the original rpm3 database
00387       // and wait if the rpm4 database gets modified by installing
00388       // or removing packages. Cleanup in modifyDatabase or closeDatabase.
00389       MIL << "Update mode: Cleanup delayed until closeOldDatabase." << endl;
00390     }
00391   }
00392 #warning CHECK: notify root about conversion backup.
00393 
00394   _root   = root_r;
00395   _dbPath = dbPath_r;
00396   _dbStateInfo = info;
00397 
00398   if ( doRebuild_r )
00399   {
00400     if (      dbsi_has( info, DbSI_HAVE_V4 )
00401          && ! dbsi_has( info, DbSI_MADE_V4 ) )
00402     {
00403       rebuildDatabase();
00404     }
00405   }
00406 
00407   MIL << "Syncronizing keys with zypp keyring" << endl;
00408   syncTrustedKeys();
00409 
00410   // Close the database in case any write acces (create/convert)
00411   // happened during init. This should drop any lock acquired
00412   // by librpm. On demand it will be reopened readonly and should
00413   // not hold any lock.
00414   librpmDb::dbRelease( true );
00415 
00416   MIL << "InitDatabase: " << *this << endl;
00417 }
00418 
00420 //
00421 //
00422 //      METHOD NAME : RpmDb::internal_initDatabase
00423 //      METHOD TYPE : PMError
00424 //
00425 void RpmDb::internal_initDatabase( const Pathname & root_r, const Pathname & dbPath_r,
00426                                    DbStateInfoBits & info_r )
00427 {
00428   info_r = DbSI_NO_INIT;
00429 
00431   // Get info about the desired database dir
00433   librpmDb::DbDirInfo dbInfo( root_r, dbPath_r );
00434 
00435   if ( dbInfo.illegalArgs() )
00436   {
00437     // should not happen (checked in initDatabase)
00438     ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
00439   }
00440   if ( ! dbInfo.usableArgs() )
00441   {
00442     ERR << "Bad database directory: " << dbInfo.dbDir() << endl;
00443     ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
00444   }
00445 
00446   if ( dbInfo.hasDbV4() )
00447   {
00448     dbsi_set( info_r, DbSI_HAVE_V4 );
00449     MIL << "Found rpm4 database in " << dbInfo.dbDir() << endl;
00450   }
00451   else
00452   {
00453     MIL << "Creating new rpm4 database in " << dbInfo.dbDir() << endl;
00454   }
00455 
00456   if ( dbInfo.hasDbV3() )
00457   {
00458     dbsi_set( info_r, DbSI_HAVE_V3 );
00459   }
00460   if ( dbInfo.hasDbV3ToV4() )
00461   {
00462     dbsi_set( info_r, DbSI_HAVE_V3TOV4 );
00463   }
00464 
00465   DBG << "Initial state: " << info_r << ": " << stringPath( root_r, dbPath_r );
00466   librpmDb::dumpState( DBG ) << endl;
00467 
00469   // Access database, create if needed
00471 
00472   // creates dbdir and empty rpm4 database if not present
00473   librpmDb::dbAccess( root_r, dbPath_r );
00474 
00475   if ( ! dbInfo.hasDbV4() )
00476   {
00477     dbInfo.restat();
00478     if ( dbInfo.hasDbV4() )
00479     {
00480       dbsi_set( info_r, DbSI_HAVE_V4 | DbSI_MADE_V4 );
00481     }
00482   }
00483 
00484   DBG << "Access state: " << info_r << ": " << stringPath( root_r, dbPath_r );
00485   librpmDb::dumpState( DBG ) << endl;
00486 
00488   // Check whether to convert something. Create backup but do
00489   // not remove anything here
00491   librpmDb::constPtr dbptr;
00492   librpmDb::dbAccess( dbptr );
00493   bool dbEmpty = dbptr->empty();
00494   if ( dbEmpty )
00495   {
00496     MIL << "Empty rpm4 database "  << dbInfo.dbV4() << endl;
00497   }
00498 
00499   if ( dbInfo.hasDbV3() )
00500   {
00501     MIL << "Found rpm3 database " << dbInfo.dbV3() << endl;
00502 
00503     if ( dbEmpty )
00504     {
00505       extern void convertV3toV4( const Pathname & v3db_r, const librpmDb::constPtr & v4db_r );
00506       convertV3toV4( dbInfo.dbV3().path(), dbptr );
00507 
00508       // create a backup copy
00509       int res = filesystem::copy( dbInfo.dbV3().path(), dbInfo.dbV3ToV4().path() );
00510       if ( res )
00511       {
00512         WAR << "Backup converted rpm3 database failed: error(" << res << ")" << endl;
00513       }
00514       else
00515       {
00516         dbInfo.restat();
00517         if ( dbInfo.hasDbV3ToV4() )
00518         {
00519           MIL << "Backup converted rpm3 database: " << dbInfo.dbV3ToV4() << endl;
00520           dbsi_set( info_r, DbSI_HAVE_V3TOV4 | DbSI_MADE_V3TOV4 );
00521         }
00522       }
00523 
00524     }
00525     else
00526     {
00527 
00528       WAR << "Non empty rpm3 and rpm4 database found: using rpm4" << endl;
00529       // set DbSI_MODIFIED_V4 as it's not a temporary which can be removed.
00530       dbsi_set( info_r, DbSI_MODIFIED_V4 );
00531 
00532     }
00533 
00534     DBG << "Convert state: " << info_r << ": " << stringPath( root_r, dbPath_r );
00535     librpmDb::dumpState( DBG ) << endl;
00536   }
00537 
00538   if ( dbInfo.hasDbV3ToV4() )
00539   {
00540     MIL << "Rpm3 database backup: " << dbInfo.dbV3ToV4() << endl;
00541   }
00542 }
00543 
00545 //
00546 //
00547 //      METHOD NAME : RpmDb::removeV4
00548 //      METHOD TYPE : void
00549 //
00550 void RpmDb::removeV4( const Pathname & dbdir_r, bool v3backup_r )
00551 {
00552   const char * v3backup = "packages.rpm3";
00553   const char * master = "Packages";
00554   const char * index[] =
00555     {
00556       "Basenames",
00557       "Conflictname",
00558       "Depends",
00559       "Dirnames",
00560       "Filemd5s",
00561       "Group",
00562       "Installtid",
00563       "Name",
00564       "Providename",
00565       "Provideversion",
00566       "Pubkeys",
00567       "Requirename",
00568       "Requireversion",
00569       "Sha1header",
00570       "Sigmd5",
00571       "Triggername",
00572       // last entry!
00573       NULL
00574     };
00575 
00576   PathInfo pi( dbdir_r );
00577   if ( ! pi.isDir() )
00578   {
00579     ERR << "Can't remove rpm4 database in non directory: " << dbdir_r << endl;
00580     return;
00581   }
00582 
00583   for ( const char ** f = index; *f; ++f )
00584   {
00585     pi( dbdir_r + *f );
00586     if ( pi.isFile() )
00587     {
00588       filesystem::unlink( pi.path() );
00589     }
00590   }
00591 
00592   pi( dbdir_r + master );
00593   if ( pi.isFile() )
00594   {
00595     MIL << "Removing rpm4 database " << pi << endl;
00596     filesystem::unlink( pi.path() );
00597   }
00598 
00599   if ( v3backup_r )
00600   {
00601     pi( dbdir_r + v3backup );
00602     if ( pi.isFile() )
00603     {
00604       MIL << "Removing converted rpm3 database backup " << pi << endl;
00605       filesystem::unlink( pi.path() );
00606     }
00607   }
00608 }
00609 
00611 //
00612 //
00613 //      METHOD NAME : RpmDb::removeV3
00614 //      METHOD TYPE : void
00615 //
00616 void RpmDb::removeV3( const Pathname & dbdir_r, bool v3backup_r )
00617 {
00618   const char * master = "packages.rpm";
00619   const char * index[] =
00620     {
00621       "conflictsindex.rpm",
00622       "fileindex.rpm",
00623       "groupindex.rpm",
00624       "nameindex.rpm",
00625       "providesindex.rpm",
00626       "requiredby.rpm",
00627       "triggerindex.rpm",
00628       // last entry!
00629       NULL
00630     };
00631 
00632   PathInfo pi( dbdir_r );
00633   if ( ! pi.isDir() )
00634   {
00635     ERR << "Can't remove rpm3 database in non directory: " << dbdir_r << endl;
00636     return;
00637   }
00638 
00639   for ( const char ** f = index; *f; ++f )
00640   {
00641     pi( dbdir_r + *f );
00642     if ( pi.isFile() )
00643     {
00644       filesystem::unlink( pi.path() );
00645     }
00646   }
00647 
00648 #warning CHECK: compare vs existing v3 backup. notify root
00649   pi( dbdir_r + master );
00650   if ( pi.isFile() )
00651   {
00652     Pathname m( pi.path() );
00653     if ( v3backup_r )
00654     {
00655       // backup was already created
00656       filesystem::unlink( m );
00657       Pathname b( m.extend( "3" ) );
00658       pi( b ); // stat backup
00659     }
00660     else
00661     {
00662       Pathname b( m.extend( ".deleted" ) );
00663       pi( b );
00664       if ( pi.isFile() )
00665       {
00666         // rempve existing backup
00667         filesystem::unlink( b );
00668       }
00669       filesystem::rename( m, b );
00670       pi( b ); // stat backup
00671     }
00672     MIL << "(Re)moved rpm3 database to " << pi << endl;
00673   }
00674 }
00675 
00677 //
00678 //
00679 //      METHOD NAME : RpmDb::modifyDatabase
00680 //      METHOD TYPE : void
00681 //
00682 void RpmDb::modifyDatabase()
00683 {
00684   if ( ! initialized() )
00685     return;
00686 
00687   // tag database as modified
00688   dbsi_set( _dbStateInfo, DbSI_MODIFIED_V4 );
00689 
00690   // Move outdated rpm3 database beside.
00691   if ( dbsi_has( _dbStateInfo, DbSI_HAVE_V3 ) )
00692   {
00693     MIL << "Update mode: Delayed cleanup: state " << _dbStateInfo << endl;
00694     removeV3( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 ) );
00695     dbsi_clr( _dbStateInfo, DbSI_HAVE_V3 );
00696   }
00697 }
00698 
00700 //
00701 //
00702 //      METHOD NAME : RpmDb::closeDatabase
00703 //      METHOD TYPE : PMError
00704 //
00705 void RpmDb::closeDatabase()
00706 {
00707   if ( ! initialized() )
00708   {
00709     return;
00710   }
00711 
00712   MIL << "Calling closeDatabase: " << *this << endl;
00713 
00715   // Block further database access
00717   librpmDb::blockAccess();
00718 
00720   // Check fate if old version database still present
00722   if ( dbsi_has( _dbStateInfo, DbSI_HAVE_V3 ) )
00723   {
00724     MIL << "Update mode: Delayed cleanup: state " << _dbStateInfo << endl;
00725     if ( dbsi_has( _dbStateInfo, DbSI_MODIFIED_V4 ) )
00726     {
00727       // Move outdated rpm3 database beside.
00728       removeV3( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 )  );
00729     }
00730     else
00731     {
00732       // Remove unmodified rpm4 database
00733       removeV4( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 ) );
00734     }
00735   }
00736 
00738   // Uninit
00740   _root = _dbPath = Pathname();
00741   _dbStateInfo = DbSI_NO_INIT;
00742 
00743   MIL << "closeDatabase: " << *this << endl;
00744 }
00745 
00747 //
00748 //
00749 //      METHOD NAME : RpmDb::rebuildDatabase
00750 //      METHOD TYPE : PMError
00751 //
00752 void RpmDb::rebuildDatabase()
00753 {
00754   callback::SendReport<RebuildDBReport> report;
00755 
00756   report->start( root() + dbPath() );
00757 
00758   try
00759   {
00760     doRebuildDatabase(report);
00761   }
00762   catch (RpmException & excpt_r)
00763   {
00764     report->finish(root() + dbPath(), RebuildDBReport::FAILED, excpt_r.asUserHistory());
00765     ZYPP_RETHROW(excpt_r);
00766   }
00767   report->finish(root() + dbPath(), RebuildDBReport::NO_ERROR, "");
00768 }
00769 
00770 void RpmDb::doRebuildDatabase(callback::SendReport<RebuildDBReport> & report)
00771 {
00772   FAILIFNOTINITIALIZED;
00773 
00774   MIL << "RpmDb::rebuildDatabase" << *this << endl;
00775   // FIXME  Timecount _t( "RpmDb::rebuildDatabase" );
00776 
00777   PathInfo dbMaster( root() + dbPath() + "Packages" );
00778   PathInfo dbMasterBackup( dbMaster.path().extend( ".y2backup" ) );
00779 
00780   // run rpm
00781   RpmArgVec opts;
00782   opts.push_back("--rebuilddb");
00783   opts.push_back("-vv");
00784 
00785   // don't call modifyDatabase because it would remove the old
00786   // rpm3 database, if the current database is a temporary one.
00787   run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
00788 
00789   // progress report: watch this file growing
00790   PathInfo newMaster( root()
00791                       + dbPath().extend( str::form( "rebuilddb.%d",
00792                                                     process?process->getpid():0) )
00793                       + "Packages" );
00794 
00795   string       line;
00796   string       errmsg;
00797 
00798   while ( systemReadLine( line ) )
00799   {
00800     if ( newMaster() )
00801     { // file is removed at the end of rebuild.
00802       // current size should be upper limit for new db
00803       if ( ! report->progress( (100 * newMaster.size()) / dbMaster.size(), root() + dbPath()) )
00804       {
00805         WAR << "User requested abort." << endl;
00806         systemKill();
00807         filesystem::recursive_rmdir( newMaster.path().dirname() );
00808       }
00809     }
00810 
00811     if ( line.compare( 0, 2, "D:" ) )
00812     {
00813       errmsg += line + '\n';
00814       //      report.notify( line );
00815       WAR << line << endl;
00816     }
00817   }
00818 
00819   int rpm_status = systemStatus();
00820 
00821   if ( rpm_status != 0 )
00822   {
00823     //TranslatorExplanation after semicolon is error message
00824     ZYPP_THROW(RpmSubprocessException(string(_("RPM failed: ") +
00825                (errmsg.empty() ? error_message: errmsg))));
00826   }
00827   else
00828   {
00829     report->progress( 100, root() + dbPath() ); // 100%
00830   }
00831 }
00832 
00834 namespace
00835 {
00837   // Remember latest release and where it ocurred
00838   struct Key
00839   {
00840     Key()
00841     : _inRpmKeys( nullptr )
00842     , _inZyppKeys( nullptr )
00843     {}
00844 
00845     void updateIf( const Edition & rpmKey_r )
00846     {
00847       std::string keyRelease( rpmKey_r.release() );
00848       int comp = _release.compare( keyRelease );
00849       if ( comp < 0 )
00850       {
00851         // update to newer release
00852         _release.swap( keyRelease );
00853         _inRpmKeys  = &rpmKey_r;
00854         _inZyppKeys = nullptr;
00855         if ( !keyRelease.empty() )
00856           DBG << "Old key in R: gpg-pubkey-" << rpmKey_r.version() << "-" <<  keyRelease << endl;
00857       }
00858       else if ( comp == 0 )
00859       {
00860         // stay with this release
00861         if ( ! _inRpmKeys )
00862           _inRpmKeys = &rpmKey_r;
00863       }
00864       // else: this is an old release
00865       else
00866         DBG << "Old key in R: gpg-pubkey-" << rpmKey_r.version() << "-" <<  keyRelease << endl;
00867     }
00868 
00869     void updateIf( const PublicKeyData & zyppKey_r )
00870     {
00871       std::string keyRelease( zyppKey_r.gpgPubkeyRelease() );
00872       int comp = _release.compare( keyRelease );
00873       if ( comp < 0 )
00874       {
00875         // update to newer release
00876         _release.swap( keyRelease );
00877         _inRpmKeys  = nullptr;
00878         _inZyppKeys = &zyppKey_r;
00879         if ( !keyRelease.empty() )
00880           DBG << "Old key in Z: gpg-pubkey-" << zyppKey_r.gpgPubkeyVersion() << "-" << keyRelease << endl;
00881       }
00882       else if ( comp == 0 )
00883       {
00884         // stay with this release
00885         if ( ! _inZyppKeys )
00886           _inZyppKeys = &zyppKey_r;
00887       }
00888       // else: this is an old release
00889       else
00890         DBG << "Old key in Z: gpg-pubkey-" << zyppKey_r.gpgPubkeyVersion() << "-" << keyRelease << endl;
00891     }
00892 
00893     std::string _release;
00894     const Edition * _inRpmKeys;
00895     const PublicKeyData * _inZyppKeys;
00896   };
00898 
00903   void computeKeyRingSync( std::set<Edition> & rpmKeys_r, std::list<PublicKeyData> & zyppKeys_r )
00904   {
00905 
00906     // collect keys by ID(version) and latest creation(release)
00907     std::map<std::string,Key> _keymap;
00908 
00909     for_( it, rpmKeys_r.begin(), rpmKeys_r.end() )
00910     {
00911       _keymap[(*it).version()].updateIf( *it );
00912     }
00913 
00914     for_( it, zyppKeys_r.begin(), zyppKeys_r.end() )
00915     {
00916       _keymap[(*it).gpgPubkeyVersion()].updateIf( *it );
00917     }
00918 
00919     // compute missing keys
00920     std::set<Edition> rpmKeys;
00921     std::list<PublicKeyData> zyppKeys;
00922     for_( it, _keymap.begin(), _keymap.end() )
00923     {
00924       DBG << "gpg-pubkey-" << (*it).first << "-" << (*it).second._release << " "
00925           << ( (*it).second._inRpmKeys  ? "R" : "_" )
00926           << ( (*it).second._inZyppKeys ? "Z" : "_" ) << endl;
00927       if ( ! (*it).second._inRpmKeys )
00928       {
00929         zyppKeys.push_back( *(*it).second._inZyppKeys );
00930       }
00931       if ( ! (*it).second._inZyppKeys )
00932       {
00933         rpmKeys.insert( *(*it).second._inRpmKeys );
00934       }
00935     }
00936     rpmKeys_r.swap( rpmKeys );
00937     zyppKeys_r.swap( zyppKeys );
00938   }
00939 } // namespace
00941 
00942 void RpmDb::syncTrustedKeys( SyncTrustedKeyBits mode_r )
00943 {
00944   MIL << "Going to sync trusted keys..." << endl;
00945   std::set<Edition> rpmKeys( pubkeyEditions() );
00946   std::list<PublicKeyData> zyppKeys( getZYpp()->keyRing()->trustedPublicKeyData() );
00947   computeKeyRingSync( rpmKeys, zyppKeys );
00948   MIL << (mode_r & SYNC_TO_KEYRING   ? "" : "(skip) ") << "Rpm keys to export into zypp trusted keyring: " << rpmKeys.size() << endl;
00949   MIL << (mode_r & SYNC_FROM_KEYRING ? "" : "(skip) ") << "Zypp trusted keys to import into rpm database: " << zyppKeys.size() << endl;
00950 
00952   if ( (mode_r & SYNC_TO_KEYRING) &&  ! rpmKeys.empty() )
00953   {
00954     // export to zypp keyring
00955     MIL << "Exporting rpm keyring into zypp trusted keyring" <<endl;
00956     // Temporarily disconnect to prevent the attemt to re-import the exported keys.
00957     callback::TempConnect<KeyRingSignals> tempDisconnect;
00958     librpmDb::db_const_iterator keepDbOpen; // just to keep a ref.
00959 
00960     TmpFile tmpfile( getZYpp()->tmpPath() );
00961     {
00962       ofstream tmpos( tmpfile.path().c_str() );
00963       for_( it, rpmKeys.begin(), rpmKeys.end() )
00964       {
00965         // we export the rpm key into a file
00966         RpmHeader::constPtr result;
00967         getData( string("gpg-pubkey"), *it, result );
00968         tmpos << result->tag_description() << endl;
00969       }
00970     }
00971     try
00972     {
00973       getZYpp()->keyRing()->multiKeyImport( tmpfile.path(), true /*trusted*/);
00974     }
00975     catch (Exception &e)
00976     {
00977       ERR << "Could not import keys into in zypp keyring" << endl;
00978     }
00979   }
00980 
00982   if ( (mode_r & SYNC_FROM_KEYRING) && ! zyppKeys.empty() )
00983   {
00984     // import from zypp keyring
00985     MIL << "Importing zypp trusted keyring" << std::endl;
00986     for_( it, zyppKeys.begin(), zyppKeys.end() )
00987     {
00988       try
00989       {
00990         importPubkey( getZYpp()->keyRing()->exportTrustedPublicKey( *it ) );
00991       }
00992       catch ( const RpmException & exp )
00993       {
00994         ZYPP_CAUGHT( exp );
00995       }
00996     }
00997   }
00998   MIL << "Trusted keys synced." << endl;
00999 }
01000 
01001 void RpmDb::importZyppKeyRingTrustedKeys()
01002 { syncTrustedKeys( SYNC_FROM_KEYRING ); }
01003 
01004 void RpmDb::exportTrustedKeysInZyppKeyRing()
01005 { syncTrustedKeys( SYNC_TO_KEYRING ); }
01006 
01008 //
01009 //
01010 //      METHOD NAME : RpmDb::importPubkey
01011 //      METHOD TYPE : PMError
01012 //
01013 void RpmDb::importPubkey( const PublicKey & pubkey_r )
01014 {
01015   FAILIFNOTINITIALIZED;
01016 
01017   // bnc#828672: On the fly key import in READONLY
01018   if ( zypp_readonly_hack::IGotIt() )
01019   {
01020     WAR << "Key " << pubkey_r << " can not be imported. (READONLY MODE)" << endl;
01021     return;
01022   }
01023 
01024   // check if the key is already in the rpm database
01025   Edition keyEd( pubkey_r.gpgPubkeyVersion(), pubkey_r.gpgPubkeyRelease() );
01026   set<Edition> rpmKeys = pubkeyEditions();
01027   bool hasOldkeys = false;
01028 
01029   for_( it, rpmKeys.begin(), rpmKeys.end() )
01030   {
01031     if ( keyEd == *it ) // quick test (Edition is IdStringType!)
01032     {
01033       MIL << "Key " << pubkey_r << " is already in the rpm trusted keyring. (skip import)" << endl;
01034       return;
01035     }
01036 
01037     if ( keyEd.version() != (*it).version() )
01038       continue; // different key ID (version)
01039 
01040     if ( keyEd.release() < (*it).release() )
01041     {
01042       MIL << "Key " << pubkey_r << " is older than one in the rpm trusted keyring. (skip import)" << endl;
01043       return;
01044     }
01045     else
01046     {
01047       hasOldkeys = true;
01048     }
01049   }
01050   MIL << "Key " << pubkey_r << " will be imported into the rpm trusted keyring." << (hasOldkeys?"(update)":"(new)") << endl;
01051 
01052   if ( hasOldkeys )
01053   {
01054     // We must explicitly delete old key IDs first (all releases,
01055     // that's why we don't call removePubkey here).
01056     std::string keyName( "gpg-pubkey-" + keyEd.version() );
01057     RpmArgVec opts;
01058     opts.push_back ( "-e" );
01059     opts.push_back ( "--allmatches" );
01060     opts.push_back ( "--" );
01061     opts.push_back ( keyName.c_str() );
01062     // don't call modifyDatabase because it would remove the old
01063     // rpm3 database, if the current database is a temporary one.
01064     run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
01065 
01066     string line;
01067     while ( systemReadLine( line ) )
01068     {
01069       ( str::startsWith( line, "error:" ) ? WAR : DBG ) << line << endl;
01070     }
01071 
01072     if ( systemStatus() != 0 )
01073     {
01074       ERR << "Failed to remove key " << pubkey_r << " from RPM trusted keyring (ignored)" << endl;
01075     }
01076     else
01077     {
01078       MIL << "Key " << pubkey_r << " has been removed from RPM trusted keyring" << endl;
01079     }
01080   }
01081 
01082   // import the new key
01083   RpmArgVec opts;
01084   opts.push_back ( "--import" );
01085   opts.push_back ( "--" );
01086   opts.push_back ( pubkey_r.path().asString().c_str() );
01087 
01088   // don't call modifyDatabase because it would remove the old
01089   // rpm3 database, if the current database is a temporary one.
01090   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
01091 
01092   string line;
01093   while ( systemReadLine( line ) )
01094   {
01095     ( str::startsWith( line, "error:" ) ? WAR : DBG ) << line << endl;
01096   }
01097 
01098   if ( systemStatus() != 0 )
01099   {
01100     //TranslatorExplanation first %s is file name, second is error message
01101     ZYPP_THROW(RpmSubprocessException(boost::str(boost::format(
01102         _("Failed to import public key from file %s: %s"))
01103         % pubkey_r.asString() % error_message)));
01104   }
01105   else
01106   {
01107     MIL << "Key " << pubkey_r << " imported in rpm trusted keyring." << endl;
01108   }
01109 }
01110 
01112 //
01113 //
01114 //      METHOD NAME : RpmDb::removePubkey
01115 //      METHOD TYPE : PMError
01116 //
01117 void RpmDb::removePubkey( const PublicKey & pubkey_r )
01118 {
01119   FAILIFNOTINITIALIZED;
01120 
01121   // check if the key is in the rpm database and just
01122   // return if it does not.
01123   set<Edition> rpm_keys = pubkeyEditions();
01124   set<Edition>::const_iterator found_edition = rpm_keys.end();
01125   std::string pubkeyVersion( pubkey_r.gpgPubkeyVersion() );
01126 
01127   for_( it, rpm_keys.begin(), rpm_keys.end() )
01128   {
01129     if ( (*it).version() == pubkeyVersion )
01130     {
01131         found_edition = it;
01132         break;
01133     }
01134   }
01135 
01136   // the key does not exist, cannot be removed
01137   if (found_edition == rpm_keys.end())
01138   {
01139       WAR << "Key " << pubkey_r.id() << " is not in rpm db" << endl;
01140       return;
01141   }
01142 
01143   string rpm_name("gpg-pubkey-" + found_edition->asString());
01144 
01145   RpmArgVec opts;
01146   opts.push_back ( "-e" );
01147   opts.push_back ( "--" );
01148   opts.push_back ( rpm_name.c_str() );
01149 
01150   // don't call modifyDatabase because it would remove the old
01151   // rpm3 database, if the current database is a temporary one.
01152   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
01153 
01154   string line;
01155   while ( systemReadLine( line ) )
01156   {
01157     if ( line.substr( 0, 6 ) == "error:" )
01158     {
01159       WAR << line << endl;
01160     }
01161     else
01162     {
01163       DBG << line << endl;
01164     }
01165   }
01166 
01167   int rpm_status = systemStatus();
01168 
01169   if ( rpm_status != 0 )
01170   {
01171     //TranslatorExplanation first %s is key name, second is error message
01172     ZYPP_THROW(RpmSubprocessException(boost::str(boost::format(
01173         _("Failed to remove public key %s: %s")) % pubkey_r.asString()
01174         % error_message)));
01175   }
01176   else
01177   {
01178     MIL << "Key " << pubkey_r << " has been removed from RPM trusted keyring" << endl;
01179   }
01180 }
01181 
01183 //
01184 //
01185 //      METHOD NAME : RpmDb::pubkeys
01186 //      METHOD TYPE : set<Edition>
01187 //
01188 list<PublicKey> RpmDb::pubkeys() const
01189 {
01190   list<PublicKey> ret;
01191 
01192   librpmDb::db_const_iterator it;
01193   for ( it.findByName( string( "gpg-pubkey" ) ); *it; ++it )
01194   {
01195     Edition edition = it->tag_edition();
01196     if (edition != Edition::noedition)
01197     {
01198       // we export the rpm key into a file
01199       RpmHeader::constPtr result;
01200       getData( string("gpg-pubkey"), edition, result );
01201       TmpFile file(getZYpp()->tmpPath());
01202       ofstream os;
01203       try
01204       {
01205         os.open(file.path().asString().c_str());
01206         // dump rpm key into the tmp file
01207         os << result->tag_description();
01208         //MIL << "-----------------------------------------------" << endl;
01209         //MIL << result->tag_description() <<endl;
01210         //MIL << "-----------------------------------------------" << endl;
01211         os.close();
01212         // read the public key from the dumped file
01213         PublicKey key(file);
01214         ret.push_back(key);
01215       }
01216       catch (exception &e)
01217       {
01218         ERR << "Could not dump key " << edition.asString() << " in tmp file " << file.path() << endl;
01219         // just ignore the key
01220       }
01221     }
01222   }
01223   return ret;
01224 }
01225 
01226 set<Edition> RpmDb::pubkeyEditions() const
01227   {
01228     set<Edition> ret;
01229 
01230     librpmDb::db_const_iterator it;
01231     for ( it.findByName( string( "gpg-pubkey" ) ); *it; ++it )
01232     {
01233       Edition edition = it->tag_edition();
01234       if (edition != Edition::noedition)
01235         ret.insert( edition );
01236     }
01237     return ret;
01238   }
01239 
01240 
01242 //
01243 //
01244 //      METHOD NAME : RpmDb::fileList
01245 //      METHOD TYPE : bool
01246 //
01247 //      DESCRIPTION :
01248 //
01249 list<FileInfo>
01250 RpmDb::fileList( const string & name_r, const Edition & edition_r ) const
01251 {
01252   list<FileInfo> result;
01253 
01254   librpmDb::db_const_iterator it;
01255   bool found;
01256   if (edition_r == Edition::noedition)
01257   {
01258     found = it.findPackage( name_r );
01259   }
01260   else
01261   {
01262     found = it.findPackage( name_r, edition_r );
01263   }
01264   if (!found)
01265     return result;
01266 
01267   return result;
01268 }
01269 
01270 
01272 //
01273 //
01274 //      METHOD NAME : RpmDb::hasFile
01275 //      METHOD TYPE : bool
01276 //
01277 //      DESCRIPTION :
01278 //
01279 bool RpmDb::hasFile( const string & file_r, const string & name_r ) const
01280 {
01281   librpmDb::db_const_iterator it;
01282   bool res;
01283   do
01284   {
01285     res = it.findByFile( file_r );
01286     if (!res) break;
01287     if (!name_r.empty())
01288     {
01289       res = (it->tag_name() == name_r);
01290     }
01291     ++it;
01292   }
01293   while (res && *it);
01294   return res;
01295 }
01296 
01298 //
01299 //
01300 //      METHOD NAME : RpmDb::whoOwnsFile
01301 //      METHOD TYPE : string
01302 //
01303 //      DESCRIPTION :
01304 //
01305 string RpmDb::whoOwnsFile( const string & file_r) const
01306 {
01307   librpmDb::db_const_iterator it;
01308   if (it.findByFile( file_r ))
01309   {
01310     return it->tag_name();
01311   }
01312   return "";
01313 }
01314 
01316 //
01317 //
01318 //      METHOD NAME : RpmDb::hasProvides
01319 //      METHOD TYPE : bool
01320 //
01321 //      DESCRIPTION :
01322 //
01323 bool RpmDb::hasProvides( const string & tag_r ) const
01324 {
01325   librpmDb::db_const_iterator it;
01326   return it.findByProvides( tag_r );
01327 }
01328 
01330 //
01331 //
01332 //      METHOD NAME : RpmDb::hasRequiredBy
01333 //      METHOD TYPE : bool
01334 //
01335 //      DESCRIPTION :
01336 //
01337 bool RpmDb::hasRequiredBy( const string & tag_r ) const
01338 {
01339   librpmDb::db_const_iterator it;
01340   return it.findByRequiredBy( tag_r );
01341 }
01342 
01344 //
01345 //
01346 //      METHOD NAME : RpmDb::hasConflicts
01347 //      METHOD TYPE : bool
01348 //
01349 //      DESCRIPTION :
01350 //
01351 bool RpmDb::hasConflicts( const string & tag_r ) const
01352 {
01353   librpmDb::db_const_iterator it;
01354   return it.findByConflicts( tag_r );
01355 }
01356 
01358 //
01359 //
01360 //      METHOD NAME : RpmDb::hasPackage
01361 //      METHOD TYPE : bool
01362 //
01363 //      DESCRIPTION :
01364 //
01365 bool RpmDb::hasPackage( const string & name_r ) const
01366 {
01367   librpmDb::db_const_iterator it;
01368   return it.findPackage( name_r );
01369 }
01370 
01372 //
01373 //
01374 //      METHOD NAME : RpmDb::hasPackage
01375 //      METHOD TYPE : bool
01376 //
01377 //      DESCRIPTION :
01378 //
01379 bool RpmDb::hasPackage( const string & name_r, const Edition & ed_r ) const
01380 {
01381   librpmDb::db_const_iterator it;
01382   return it.findPackage( name_r, ed_r );
01383 }
01384 
01386 //
01387 //
01388 //      METHOD NAME : RpmDb::getData
01389 //      METHOD TYPE : PMError
01390 //
01391 //      DESCRIPTION :
01392 //
01393 void RpmDb::getData( const string & name_r,
01394                      RpmHeader::constPtr & result_r ) const
01395 {
01396   librpmDb::db_const_iterator it;
01397   it.findPackage( name_r );
01398   result_r = *it;
01399   if (it.dbError())
01400     ZYPP_THROW(*(it.dbError()));
01401 }
01402 
01404 //
01405 //
01406 //      METHOD NAME : RpmDb::getData
01407 //      METHOD TYPE : void
01408 //
01409 //      DESCRIPTION :
01410 //
01411 void RpmDb::getData( const string & name_r, const Edition & ed_r,
01412                      RpmHeader::constPtr & result_r ) const
01413 {
01414   librpmDb::db_const_iterator it;
01415   it.findPackage( name_r, ed_r  );
01416   result_r = *it;
01417   if (it.dbError())
01418     ZYPP_THROW(*(it.dbError()));
01419 }
01420 
01422 //
01423 //      METHOD NAME : RpmDb::checkPackage
01424 //      METHOD TYPE : RpmDb::checkPackageResult
01425 //
01426 RpmDb::checkPackageResult RpmDb::checkPackage( const Pathname & path_r )
01427 {
01428   PathInfo file( path_r );
01429   if ( ! file.isFile() )
01430   {
01431     ERR << "Not a file: " << file << endl;
01432     return CHK_ERROR;
01433   }
01434 
01435   FD_t fd = ::Fopen( file.asString().c_str(), "r.ufdio" );
01436   if ( fd == 0 || ::Ferror(fd) )
01437   {
01438     ERR << "Can't open file for reading: " << file << " (" << ::Fstrerror(fd) << ")" << endl;
01439     if ( fd )
01440       ::Fclose( fd );
01441     return CHK_ERROR;
01442   }
01443 
01444   rpmts ts = ::rpmtsCreate();
01445   ::rpmtsSetRootDir( ts, root().asString().c_str() );
01446   ::rpmtsSetVSFlags( ts, RPMVSF_DEFAULT );
01447   int res = ::rpmReadPackageFile( ts, fd, path_r.asString().c_str(), NULL );
01448   ts = rpmtsFree(ts);
01449 
01450   ::Fclose( fd );
01451 
01452   switch ( res )
01453   {
01454   case RPMRC_OK:
01455     return CHK_OK;
01456     break;
01457   case RPMRC_NOTFOUND:
01458     WAR << "Signature is unknown type. " << file << endl;
01459     return CHK_NOTFOUND;
01460     break;
01461   case RPMRC_FAIL:
01462     WAR << "Signature does not verify. " << file << endl;
01463     return CHK_FAIL;
01464     break;
01465   case RPMRC_NOTTRUSTED:
01466     WAR << "Signature is OK, but key is not trusted. " << file << endl;
01467     return CHK_NOTTRUSTED;
01468     break;
01469   case RPMRC_NOKEY:
01470     WAR << "Public key is unavailable. " << file << endl;
01471     return CHK_NOKEY;
01472     break;
01473   }
01474   ERR << "Error reading header." << file << endl;
01475   return CHK_ERROR;
01476 }
01477 
01478 // determine changed files of installed package
01479 bool
01480 RpmDb::queryChangedFiles(FileList & fileList, const string& packageName)
01481 {
01482   bool ok = true;
01483 
01484   fileList.clear();
01485 
01486   if ( ! initialized() ) return false;
01487 
01488   RpmArgVec opts;
01489 
01490   opts.push_back ("-V");
01491   opts.push_back ("--nodeps");
01492   opts.push_back ("--noscripts");
01493   opts.push_back ("--nomd5");
01494   opts.push_back ("--");
01495   opts.push_back (packageName.c_str());
01496 
01497   run_rpm (opts, ExternalProgram::Discard_Stderr);
01498 
01499   if ( process == NULL )
01500     return false;
01501 
01502   /* from rpm manpage
01503    5      MD5 sum
01504    S      File size
01505    L      Symlink
01506    T      Mtime
01507    D      Device
01508    U      User
01509    G      Group
01510    M      Mode (includes permissions and file type)
01511   */
01512 
01513   string line;
01514   while (systemReadLine(line))
01515   {
01516     if (line.length() > 12 &&
01517         (line[0] == 'S' || line[0] == 's' ||
01518          (line[0] == '.' && line[7] == 'T')))
01519     {
01520       // file has been changed
01521       string filename;
01522 
01523       filename.assign(line, 11, line.length() - 11);
01524       fileList.insert(filename);
01525     }
01526   }
01527 
01528   systemStatus();
01529   // exit code ignored, rpm returns 1 no matter if package is installed or
01530   // not
01531 
01532   return ok;
01533 }
01534 
01535 
01536 
01537 /****************************************************************/
01538 /* private member-functions                                     */
01539 /****************************************************************/
01540 
01541 /*--------------------------------------------------------------*/
01542 /* Run rpm with the specified arguments, handling stderr        */
01543 /* as specified  by disp                                        */
01544 /*--------------------------------------------------------------*/
01545 void
01546 RpmDb::run_rpm (const RpmArgVec& opts,
01547                 ExternalProgram::Stderr_Disposition disp)
01548 {
01549   if ( process )
01550   {
01551     delete process;
01552     process = NULL;
01553   }
01554   exit_code = -1;
01555 
01556   if ( ! initialized() )
01557   {
01558     ZYPP_THROW(RpmDbNotOpenException());
01559   }
01560 
01561   RpmArgVec args;
01562 
01563   // always set root and dbpath
01564   args.push_back("rpm");
01565   args.push_back("--root");
01566   args.push_back(_root.asString().c_str());
01567   args.push_back("--dbpath");
01568   args.push_back(_dbPath.asString().c_str());
01569 
01570   const char* argv[args.size() + opts.size() + 1];
01571 
01572   const char** p = argv;
01573   p = copy (args.begin (), args.end (), p);
01574   p = copy (opts.begin (), opts.end (), p);
01575   *p = 0;
01576 
01577   // Invalidate all outstanding database handles in case
01578   // the database gets modified.
01579   librpmDb::dbRelease( true );
01580 
01581   // Launch the program with default locale
01582   process = new ExternalProgram(argv, disp, false, -1, true);
01583   return;
01584 }
01585 
01586 /*--------------------------------------------------------------*/
01587 /* Read a line from the rpm process                             */
01588 /*--------------------------------------------------------------*/
01589 bool RpmDb::systemReadLine( string & line )
01590 {
01591   line.erase();
01592 
01593   if ( process == NULL )
01594     return false;
01595 
01596   if ( process->inputFile() )
01597   {
01598     process->setBlocking( false );
01599     FILE * inputfile = process->inputFile();
01600     int    inputfileFd = ::fileno( inputfile );
01601     do
01602     {
01603       /* Watch inputFile to see when it has input. */
01604       fd_set rfds;
01605       FD_ZERO( &rfds );
01606       FD_SET( inputfileFd, &rfds );
01607 
01608       /* Wait up to 5 seconds. */
01609       struct timeval tv;
01610       tv.tv_sec = 5;
01611       tv.tv_usec = 0;
01612 
01613       int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
01614 
01615       if ( retval == -1 )
01616       {
01617         ERR << "select error: " << strerror(errno) << endl;
01618         if ( errno != EINTR )
01619           return false;
01620       }
01621       else if ( retval )
01622       {
01623         // Data is available now.
01624         static size_t linebuffer_size = 0;      // static because getline allocs
01625         static char * linebuffer = 0;           // and reallocs if buffer is too small
01626         ssize_t nread = getline( &linebuffer, &linebuffer_size, inputfile );
01627         if ( nread == -1 )
01628         {
01629           if ( ::feof( inputfile ) )
01630             return line.size(); // in case of pending output
01631         }
01632         else
01633         {
01634           if ( nread > 0 )
01635           {
01636             if ( linebuffer[nread-1] == '\n' )
01637               --nread;
01638             line += string( linebuffer, nread );
01639           }
01640 
01641           if ( ! ::ferror( inputfile ) || ::feof( inputfile ) )
01642             return true; // complete line
01643         }
01644         clearerr( inputfile );
01645       }
01646       else
01647       {
01648         // No data within time.
01649         if ( ! process->running() )
01650           return false;
01651       }
01652     } while ( true );
01653   }
01654 
01655   return false;
01656 }
01657 
01658 /*--------------------------------------------------------------*/
01659 /* Return the exit status of the rpm process, closing the       */
01660 /* connection if not already done                               */
01661 /*--------------------------------------------------------------*/
01662 int
01663 RpmDb::systemStatus()
01664 {
01665   if ( process == NULL )
01666     return -1;
01667 
01668   exit_code = process->close();
01669   if (exit_code == 0)
01670     error_message = "";
01671   else
01672     error_message = process->execError();
01673   process->kill();
01674   delete process;
01675   process = 0;
01676 
01677   //   DBG << "exit code " << exit_code << endl;
01678 
01679   return exit_code;
01680 }
01681 
01682 /*--------------------------------------------------------------*/
01683 /* Forcably kill the rpm process                                */
01684 /*--------------------------------------------------------------*/
01685 void
01686 RpmDb::systemKill()
01687 {
01688   if (process) process->kill();
01689 }
01690 
01691 
01692 // generate diff mails for config files
01693 void RpmDb::processConfigFiles(const string& line, const string& name, const char* typemsg, const char* difffailmsg, const char* diffgenmsg)
01694 {
01695   string msg = line.substr(9);
01696   string::size_type pos1 = string::npos;
01697   string::size_type pos2 = string::npos;
01698   string file1s, file2s;
01699   Pathname file1;
01700   Pathname file2;
01701 
01702   pos1 = msg.find (typemsg);
01703   for (;;)
01704   {
01705     if ( pos1 == string::npos )
01706       break;
01707 
01708     pos2 = pos1 + strlen (typemsg);
01709 
01710     if (pos2 >= msg.length() )
01711       break;
01712 
01713     file1 = msg.substr (0, pos1);
01714     file2 = msg.substr (pos2);
01715 
01716     file1s = file1.asString();
01717     file2s = file2.asString();
01718 
01719     if (!_root.empty() && _root != "/")
01720     {
01721       file1 = _root + file1;
01722       file2 = _root + file2;
01723     }
01724 
01725     string out;
01726     int ret = diffFiles (file1.asString(), file2.asString(), out, 25);
01727     if (ret)
01728     {
01729       Pathname file = _root + WARNINGMAILPATH;
01730       if (filesystem::assert_dir(file) != 0)
01731       {
01732         ERR << "Could not create " << file.asString() << endl;
01733         break;
01734       }
01735       file += Date(Date::now()).form("config_diff_%Y_%m_%d.log");
01736       ofstream notify(file.asString().c_str(), ios::out|ios::app);
01737       if (!notify)
01738       {
01739         ERR << "Could not open " <<  file << endl;
01740         break;
01741       }
01742 
01743       // Translator: %s = name of an rpm package. A list of diffs follows
01744       // this message.
01745       notify << str::form(_("Changed configuration files for %s:"), name.c_str()) << endl;
01746       if (ret>1)
01747       {
01748         ERR << "diff failed" << endl;
01749         notify << str::form(difffailmsg,
01750                             file1s.c_str(), file2s.c_str()) << endl;
01751       }
01752       else
01753       {
01754         notify << str::form(diffgenmsg,
01755                             file1s.c_str(), file2s.c_str()) << endl;
01756 
01757         // remove root for the viewer's pleasure (#38240)
01758         if (!_root.empty() && _root != "/")
01759         {
01760           if (out.substr(0,4) == "--- ")
01761           {
01762             out.replace(4, file1.asString().length(), file1s);
01763           }
01764           string::size_type pos = out.find("\n+++ ");
01765           if (pos != string::npos)
01766           {
01767             out.replace(pos+5, file2.asString().length(), file2s);
01768           }
01769         }
01770         notify << out << endl;
01771       }
01772       notify.close();
01773       notify.open("/var/lib/update-messages/yast2-packagemanager.rpmdb.configfiles");
01774       notify.close();
01775     }
01776     else
01777     {
01778       WAR << "rpm created " << file2 << " but it is not different from " << file2 << endl;
01779     }
01780     break;
01781   }
01782 }
01783 
01785 //
01786 //
01787 //      METHOD NAME : RpmDb::installPackage
01788 //      METHOD TYPE : PMError
01789 //
01790 void RpmDb::installPackage( const Pathname & filename, RpmInstFlags flags )
01791 {
01792   callback::SendReport<RpmInstallReport> report;
01793 
01794   report->start(filename);
01795 
01796   do
01797     try
01798     {
01799       doInstallPackage(filename, flags, report);
01800       report->finish();
01801       break;
01802     }
01803     catch (RpmException & excpt_r)
01804     {
01805       RpmInstallReport::Action user = report->problem( excpt_r );
01806 
01807       if ( user == RpmInstallReport::ABORT )
01808       {
01809         report->finish( excpt_r );
01810         ZYPP_RETHROW(excpt_r);
01811       }
01812       else if ( user == RpmInstallReport::IGNORE )
01813       {
01814         break;
01815       }
01816     }
01817   while (true);
01818 }
01819 
01820 void RpmDb::doInstallPackage( const Pathname & filename, RpmInstFlags flags, callback::SendReport<RpmInstallReport> & report )
01821 {
01822   FAILIFNOTINITIALIZED;
01823   HistoryLog historylog;
01824 
01825   MIL << "RpmDb::installPackage(" << filename << "," << flags << ")" << endl;
01826 
01827 
01828   // backup
01829   if ( _packagebackups )
01830   {
01831     // FIXME      report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
01832     if ( ! backupPackage( filename ) )
01833     {
01834       ERR << "backup of " << filename.asString() << " failed" << endl;
01835     }
01836     // FIXME status handling
01837     report->progress( 0 ); // allow 1% for backup creation.
01838   }
01839   else
01840   {
01841     report->progress( 100 );
01842   }
01843 
01844   // run rpm
01845   RpmArgVec opts;
01846   if (flags & RPMINST_NOUPGRADE)
01847     opts.push_back("-i");
01848   else
01849     opts.push_back("-U");
01850 
01851   opts.push_back("--percent");
01852   opts.push_back("--noglob");
01853 
01854   // ZConfig defines cross-arch installation
01855   if ( ! ZConfig::instance().systemArchitecture().compatibleWith( ZConfig::instance().defaultSystemArchitecture() ) )
01856     opts.push_back("--ignorearch");
01857 
01858   if (flags & RPMINST_NODIGEST)
01859     opts.push_back("--nodigest");
01860   if (flags & RPMINST_NOSIGNATURE)
01861     opts.push_back("--nosignature");
01862   if (flags & RPMINST_EXCLUDEDOCS)
01863     opts.push_back ("--excludedocs");
01864   if (flags & RPMINST_NOSCRIPTS)
01865     opts.push_back ("--noscripts");
01866   if (flags & RPMINST_FORCE)
01867     opts.push_back ("--force");
01868   if (flags & RPMINST_NODEPS)
01869     opts.push_back ("--nodeps");
01870   if (flags & RPMINST_IGNORESIZE)
01871     opts.push_back ("--ignoresize");
01872   if (flags & RPMINST_JUSTDB)
01873     opts.push_back ("--justdb");
01874   if (flags & RPMINST_TEST)
01875     opts.push_back ("--test");
01876 
01877   opts.push_back("--");
01878 
01879   // rpm requires additional quoting of special chars:
01880   string quotedFilename( rpmQuoteFilename( filename ) );
01881   opts.push_back ( quotedFilename.c_str() );
01882 
01883   modifyDatabase(); // BEFORE run_rpm
01884   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
01885 
01886   string line;
01887   string rpmmsg;
01888   vector<string> configwarnings;
01889 
01890   unsigned linecnt = 0;
01891   while (systemReadLine(line))
01892   {
01893     if ( linecnt < MAXRPMMESSAGELINES )
01894       ++linecnt;
01895     else
01896       continue;
01897 
01898     if (line.substr(0,2)=="%%")
01899     {
01900       int percent;
01901       sscanf (line.c_str () + 2, "%d", &percent);
01902       report->progress( percent );
01903     }
01904     else
01905       rpmmsg += line+'\n';
01906 
01907     if ( line.substr(0,8) == "warning:" )
01908     {
01909       configwarnings.push_back(line);
01910     }
01911   }
01912   if ( linecnt > MAXRPMMESSAGELINES )
01913     rpmmsg += "[truncated]\n";
01914 
01915   int rpm_status = systemStatus();
01916 
01917   // evaluate result
01918   for (vector<string>::iterator it = configwarnings.begin();
01919        it != configwarnings.end(); ++it)
01920   {
01921     processConfigFiles(*it, Pathname::basename(filename), " saved as ",
01922                        // %s = filenames
01923                        _("rpm saved %s as %s, but it was impossible to determine the difference"),
01924                        // %s = filenames
01925                        _("rpm saved %s as %s.\nHere are the first 25 lines of difference:\n"));
01926     processConfigFiles(*it, Pathname::basename(filename), " created as ",
01927                        // %s = filenames
01928                        _("rpm created %s as %s, but it was impossible to determine the difference"),
01929                        // %s = filenames
01930                        _("rpm created %s as %s.\nHere are the first 25 lines of difference:\n"));
01931   }
01932 
01933   if ( rpm_status != 0 )
01934   {
01935     historylog.comment(
01936         str::form("%s install failed", Pathname::basename(filename).c_str()),
01937         true /*timestamp*/);
01938     ostringstream sstr;
01939     sstr << "rpm output:" << endl << rpmmsg << endl;
01940     historylog.comment(sstr.str());
01941     // TranslatorExplanation the colon is followed by an error message
01942     ZYPP_THROW(RpmSubprocessException(string(_("RPM failed: ")) +
01943                (rpmmsg.empty() ? error_message : rpmmsg)));
01944   }
01945   else if ( ! rpmmsg.empty() )
01946   {
01947     historylog.comment(
01948         str::form("%s installed ok", Pathname::basename(filename).c_str()),
01949         true /*timestamp*/);
01950     ostringstream sstr;
01951     sstr << "Additional rpm output:" << endl << rpmmsg << endl;
01952     historylog.comment(sstr.str());
01953 
01954     // report additional rpm output in finish
01955     // TranslatorExplanation Text is followed by a ':'  and the actual output.
01956     report->finishInfo(str::form( "%s:\n%s\n", _("Additional rpm output"),  rpmmsg.c_str() ));
01957   }
01958 }
01959 
01961 //
01962 //
01963 //      METHOD NAME : RpmDb::removePackage
01964 //      METHOD TYPE : PMError
01965 //
01966 void RpmDb::removePackage( Package::constPtr package, RpmInstFlags flags )
01967 {
01968   // 'rpm -e' does not like epochs
01969   return removePackage( package->name()
01970                         + "-" + package->edition().version()
01971                         + "-" + package->edition().release()
01972                         + "." + package->arch().asString(), flags );
01973 }
01974 
01976 //
01977 //
01978 //      METHOD NAME : RpmDb::removePackage
01979 //      METHOD TYPE : PMError
01980 //
01981 void RpmDb::removePackage( const string & name_r, RpmInstFlags flags )
01982 {
01983   callback::SendReport<RpmRemoveReport> report;
01984 
01985   report->start( name_r );
01986 
01987   do
01988     try
01989     {
01990       doRemovePackage(name_r, flags, report);
01991       report->finish();
01992       break;
01993     }
01994     catch (RpmException & excpt_r)
01995     {
01996       RpmRemoveReport::Action user = report->problem( excpt_r );
01997 
01998       if ( user == RpmRemoveReport::ABORT )
01999       {
02000         report->finish( excpt_r );
02001         ZYPP_RETHROW(excpt_r);
02002       }
02003       else if ( user == RpmRemoveReport::IGNORE )
02004       {
02005         break;
02006       }
02007     }
02008   while (true);
02009 }
02010 
02011 
02012 void RpmDb::doRemovePackage( const string & name_r, RpmInstFlags flags, callback::SendReport<RpmRemoveReport> & report )
02013 {
02014   FAILIFNOTINITIALIZED;
02015   HistoryLog historylog;
02016 
02017   MIL << "RpmDb::doRemovePackage(" << name_r << "," << flags << ")" << endl;
02018 
02019   // backup
02020   if ( _packagebackups )
02021   {
02022     // FIXME solve this status report somehow
02023     //      report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
02024     if ( ! backupPackage( name_r ) )
02025     {
02026       ERR << "backup of " << name_r << " failed" << endl;
02027     }
02028     report->progress( 0 );
02029   }
02030   else
02031   {
02032     report->progress( 100 );
02033   }
02034 
02035   // run rpm
02036   RpmArgVec opts;
02037   opts.push_back("-e");
02038   opts.push_back("--allmatches");
02039 
02040   if (flags & RPMINST_NOSCRIPTS)
02041     opts.push_back("--noscripts");
02042   if (flags & RPMINST_NODEPS)
02043     opts.push_back("--nodeps");
02044   if (flags & RPMINST_JUSTDB)
02045     opts.push_back("--justdb");
02046   if (flags & RPMINST_TEST)
02047     opts.push_back ("--test");
02048   if (flags & RPMINST_FORCE)
02049   {
02050     WAR << "IGNORE OPTION: 'rpm -e' does not support '--force'" << endl;
02051   }
02052 
02053   opts.push_back("--");
02054   opts.push_back(name_r.c_str());
02055 
02056   modifyDatabase(); // BEFORE run_rpm
02057   run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
02058 
02059   string line;
02060   string rpmmsg;
02061 
02062   // got no progress from command, so we fake it:
02063   // 5  - command started
02064   // 50 - command completed
02065   // 100 if no error
02066   report->progress( 5 );
02067   unsigned linecnt = 0;
02068   while (systemReadLine(line))
02069   {
02070     if ( linecnt < MAXRPMMESSAGELINES )
02071       ++linecnt;
02072     else
02073       continue;
02074     rpmmsg += line+'\n';
02075   }
02076   if ( linecnt > MAXRPMMESSAGELINES )
02077     rpmmsg += "[truncated]\n";
02078   report->progress( 50 );
02079   int rpm_status = systemStatus();
02080 
02081   if ( rpm_status != 0 )
02082   {
02083     historylog.comment(
02084         str::form("%s remove failed", name_r.c_str()), true /*timestamp*/);
02085     ostringstream sstr;
02086     sstr << "rpm output:" << endl << rpmmsg << endl;
02087     historylog.comment(sstr.str());
02088     // TranslatorExplanation the colon is followed by an error message
02089     ZYPP_THROW(RpmSubprocessException(string(_("RPM failed: ")) +
02090                (rpmmsg.empty() ? error_message: rpmmsg)));
02091   }
02092   else if ( ! rpmmsg.empty() )
02093   {
02094     historylog.comment(
02095         str::form("%s removed ok", name_r.c_str()), true /*timestamp*/);
02096 
02097     ostringstream sstr;
02098     sstr << "Additional rpm output:" << endl << rpmmsg << endl;
02099     historylog.comment(sstr.str());
02100 
02101     // report additional rpm output in finish
02102     // TranslatorExplanation Text is followed by a ':'  and the actual output.
02103     report->finishInfo(str::form( "%s:\n%s\n", _("Additional rpm output"),  rpmmsg.c_str() ));
02104   }
02105 }
02106 
02108 //
02109 //
02110 //      METHOD NAME : RpmDb::backupPackage
02111 //      METHOD TYPE : bool
02112 //
02113 bool RpmDb::backupPackage( const Pathname & filename )
02114 {
02115   RpmHeader::constPtr h( RpmHeader::readPackage( filename, RpmHeader::NOSIGNATURE ) );
02116   if ( ! h )
02117     return false;
02118 
02119   return backupPackage( h->tag_name() );
02120 }
02121 
02123 //
02124 //
02125 //      METHOD NAME : RpmDb::backupPackage
02126 //      METHOD TYPE : bool
02127 //
02128 bool RpmDb::backupPackage(const string& packageName)
02129 {
02130   HistoryLog progresslog;
02131   bool ret = true;
02132   Pathname backupFilename;
02133   Pathname filestobackupfile = _root+_backuppath+FILEFORBACKUPFILES;
02134 
02135   if (_backuppath.empty())
02136   {
02137     INT << "_backuppath empty" << endl;
02138     return false;
02139   }
02140 
02141   FileList fileList;
02142 
02143   if (!queryChangedFiles(fileList, packageName))
02144   {
02145     ERR << "Error while getting changed files for package " <<
02146     packageName << endl;
02147     return false;
02148   }
02149 
02150   if (fileList.size() <= 0)
02151   {
02152     DBG <<  "package " <<  packageName << " not changed -> no backup" << endl;
02153     return true;
02154   }
02155 
02156   if (filesystem::assert_dir(_root + _backuppath) != 0)
02157   {
02158     return false;
02159   }
02160 
02161   {
02162     // build up archive name
02163     time_t currentTime = time(0);
02164     struct tm *currentLocalTime = localtime(&currentTime);
02165 
02166     int date = (currentLocalTime->tm_year + 1900) * 10000
02167                + (currentLocalTime->tm_mon + 1) * 100
02168                + currentLocalTime->tm_mday;
02169 
02170     int num = 0;
02171     do
02172     {
02173       backupFilename = _root + _backuppath
02174                        + str::form("%s-%d-%d.tar.gz",packageName.c_str(), date, num);
02175 
02176     }
02177     while ( PathInfo(backupFilename).isExist() && num++ < 1000);
02178 
02179     PathInfo pi(filestobackupfile);
02180     if (pi.isExist() && !pi.isFile())
02181     {
02182       ERR << filestobackupfile.asString() << " already exists and is no file" << endl;
02183       return false;
02184     }
02185 
02186     ofstream fp ( filestobackupfile.asString().c_str(), ios::out|ios::trunc );
02187 
02188     if (!fp)
02189     {
02190       ERR << "could not open " << filestobackupfile.asString() << endl;
02191       return false;
02192     }
02193 
02194     for (FileList::const_iterator cit = fileList.begin();
02195          cit != fileList.end(); ++cit)
02196     {
02197       string name = *cit;
02198       if ( name[0] == '/' )
02199       {
02200         // remove slash, file must be relative to -C parameter of tar
02201         name = name.substr( 1 );
02202       }
02203       DBG << "saving file "<< name << endl;
02204       fp << name << endl;
02205     }
02206     fp.close();
02207 
02208     const char* const argv[] =
02209       {
02210         "tar",
02211         "-czhP",
02212         "-C",
02213         _root.asString().c_str(),
02214         "--ignore-failed-read",
02215         "-f",
02216         backupFilename.asString().c_str(),
02217         "-T",
02218         filestobackupfile.asString().c_str(),
02219         NULL
02220       };
02221 
02222     // execute tar in inst-sys (we dont know if there is a tar below _root !)
02223     ExternalProgram tar(argv, ExternalProgram::Stderr_To_Stdout, false, -1, true);
02224 
02225     string tarmsg;
02226 
02227     // TODO: its probably possible to start tar with -v and watch it adding
02228     // files to report progress
02229     for (string output = tar.receiveLine(); output.length() ;output = tar.receiveLine())
02230     {
02231       tarmsg+=output;
02232     }
02233 
02234     int ret = tar.close();
02235 
02236     if ( ret != 0)
02237     {
02238       ERR << "tar failed: " << tarmsg << endl;
02239       ret = false;
02240     }
02241     else
02242     {
02243       MIL << "tar backup ok" << endl;
02244       progresslog.comment(
02245           str::form(_("created backup %s"), backupFilename.asString().c_str())
02246           , /*timestamp*/true);
02247     }
02248 
02249     filesystem::unlink(filestobackupfile);
02250   }
02251 
02252   return ret;
02253 }
02254 
02255 void RpmDb::setBackupPath(const Pathname& path)
02256 {
02257   _backuppath = path;
02258 }
02259 
02260 } // namespace rpm
02261 } // namespace target
02262 } // namespace zypp