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