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

doxygen