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