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