RpmDb.cc

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

Generated on Tue May 5 14:43:20 2015 for libzypp by  doxygen 1.5.6