libzypp  11.13.5
RpmDb.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 \---------------------------------------------------------------------*/
12 #include "librpm.h"
13 
14 #include <cstdlib>
15 #include <cstdio>
16 #include <ctime>
17 
18 #include <iostream>
19 #include <fstream>
20 #include <sstream>
21 #include <list>
22 #include <map>
23 #include <set>
24 #include <string>
25 #include <vector>
26 #include <algorithm>
27 
28 #include <boost/format.hpp>
29 
30 #include "zypp/base/Logger.h"
31 #include "zypp/base/String.h"
32 #include "zypp/base/Gettext.h"
33 
34 #include "zypp/Date.h"
35 #include "zypp/Pathname.h"
36 #include "zypp/PathInfo.h"
37 #include "zypp/PublicKey.h"
38 
39 #include "zypp/target/rpm/RpmDb.h"
41 
42 #include "zypp/HistoryLog.h"
45 #include "zypp/TmpPath.h"
46 #include "zypp/KeyRing.h"
47 #include "zypp/ZYppFactory.h"
48 #include "zypp/ZConfig.h"
49 
50 using namespace std;
51 using namespace zypp::filesystem;
52 
53 #define WARNINGMAILPATH "/var/log/YaST2/"
54 #define FILEFORBACKUPFILES "YaSTBackupModifiedFiles"
55 #define MAXRPMMESSAGELINES 10000
56 
57 namespace zypp
58 {
59  namespace zypp_readonly_hack
60  {
61  bool IGotIt(); // in readonly-mode
62  }
63 namespace target
64 {
65 namespace rpm
66 {
67 namespace
68 {
69 #if 1 // No more need to escape whitespace since rpm-4.4.2.3
70 const char* quoteInFilename_m = "\'\"";
71 #else
72 const char* quoteInFilename_m = " \t\'\"";
73 #endif
74 inline string rpmQuoteFilename( const Pathname & path_r )
75 {
76  string path( path_r.asString() );
77  for ( string::size_type pos = path.find_first_of( quoteInFilename_m );
78  pos != string::npos;
79  pos = path.find_first_of( quoteInFilename_m, pos ) )
80  {
81  path.insert( pos, "\\" );
82  pos += 2; // skip '\\' and the quoted char.
83  }
84  return path;
85 }
86 }
87 
89 {
90  KeyRingSignalReceiver(RpmDb &rpmdb) : _rpmdb(rpmdb)
91  {
92  connect();
93  }
94 
96  {
97  disconnect();
98  }
99 
100  virtual void trustedKeyAdded( const PublicKey &key )
101  {
102  MIL << "trusted key added to zypp Keyring. Importing" << endl;
103  // now import the key in rpm
104  try
105  {
106  _rpmdb.importPubkey( key );
107  }
108  catch (RpmException &e)
109  {
110  ERR << "Could not import key " << key.id() << " (" << key.name() << " from " << key.path() << " in rpm database" << endl;
111  }
112  }
113 
114  virtual void trustedKeyRemoved( const PublicKey &key )
115  {
116  MIL << "Trusted key removed from zypp Keyring. Removing..." << endl;
117 
118  // remove the key from rpm
119  try
120  {
121  _rpmdb.removePubkey( key );
122  }
123  catch (RpmException &e)
124  {
125  ERR << "Could not remove key " << key.id() << " (" << key.name() << ") from rpm database" << endl;
126  }
127  }
128 
130 };
131 
132 static shared_ptr<KeyRingSignalReceiver> sKeyRingReceiver;
133 
134 unsigned diffFiles(const string file1, const string file2, string& out, int maxlines)
135 {
136  const char* argv[] =
137  {
138  "diff",
139  "-u",
140  file1.c_str(),
141  file2.c_str(),
142  NULL
143  };
144  ExternalProgram prog(argv,ExternalProgram::Discard_Stderr, false, -1, true);
145 
146  //if(!prog)
147  //return 2;
148 
149  string line;
150  int count = 0;
151  for (line = prog.receiveLine(), count=0;
152  !line.empty();
153  line = prog.receiveLine(), count++ )
154  {
155  if (maxlines<0?true:count<maxlines)
156  out+=line;
157  }
158 
159  return prog.close();
160 }
161 
162 
163 
164 /******************************************************************
165  **
166  **
167  ** FUNCTION NAME : stringPath
168  ** FUNCTION TYPE : inline string
169 */
170 inline string stringPath( const Pathname & root_r, const Pathname & sub_r )
171 {
172  return librpmDb::stringPath( root_r, sub_r );
173 }
174 
175 /******************************************************************
176  **
177  **
178  ** FUNCTION NAME : operator<<
179  ** FUNCTION TYPE : ostream &
180 */
181 ostream & operator<<( ostream & str, const RpmDb::DbStateInfoBits & obj )
182 {
183  if ( obj == RpmDb::DbSI_NO_INIT )
184  {
185  str << "NO_INIT";
186  }
187  else
188  {
189 #define ENUM_OUT(B,C) str << ( obj & RpmDb::B ? C : '-' )
190  str << "V4(";
191  ENUM_OUT( DbSI_HAVE_V4, 'X' );
192  ENUM_OUT( DbSI_MADE_V4, 'c' );
193  ENUM_OUT( DbSI_MODIFIED_V4, 'm' );
194  str << ")V3(";
195  ENUM_OUT( DbSI_HAVE_V3, 'X' );
196  ENUM_OUT( DbSI_HAVE_V3TOV4, 'B' );
197  ENUM_OUT( DbSI_MADE_V3TOV4, 'c' );
198  str << ")";
199 #undef ENUM_OUT
200  }
201  return str;
202 }
203 
204 
205 
207 //
208 // CLASS NAME : RpmDb
209 //
211 
212 #define FAILIFNOTINITIALIZED if( ! initialized() ) { ZYPP_THROW(RpmDbNotOpenException()); }
213 
215 
217 //
218 //
219 // METHOD NAME : RpmDb::RpmDb
220 // METHOD TYPE : Constructor
221 //
222 RpmDb::RpmDb()
223  : _dbStateInfo( DbSI_NO_INIT )
224 #warning Check for obsolete memebers
225  , _backuppath ("/var/adm/backup")
226  , _packagebackups(false)
227  , _warndirexists(false)
228 {
229  process = 0;
230  exit_code = -1;
232  // Some rpm versions are patched not to abort installation if
233  // symlink creation failed.
234  setenv( "RPM_IgnoreFailedSymlinks", "1", 1 );
235  sKeyRingReceiver.reset(new KeyRingSignalReceiver(*this));
236 }
237 
239 //
240 //
241 // METHOD NAME : RpmDb::~RpmDb
242 // METHOD TYPE : Destructor
243 //
245 {
246  MIL << "~RpmDb()" << endl;
247  closeDatabase();
248  delete process;
249  MIL << "~RpmDb() end" << endl;
250  sKeyRingReceiver.reset();
251 }
252 
254 {
255  Date ts_rpm;
256 
257  Pathname db_path;
258  if ( dbPath().empty() )
259  db_path = "/var/lib/rpm";
260  else
261  db_path = dbPath();
262 
263  PathInfo rpmdb_info(root() + db_path + "/Packages");
264 
265  if ( rpmdb_info.isExist() )
266  return rpmdb_info.mtime();
267  else
268  return Date::now();
269 }
271 //
272 //
273 // METHOD NAME : RpmDb::dumpOn
274 // METHOD TYPE : ostream &
275 //
276 ostream & RpmDb::dumpOn( ostream & str ) const
277 {
278  str << "RpmDb[";
279 
280  if ( _dbStateInfo == DbSI_NO_INIT )
281  {
282  str << "NO_INIT";
283  }
284  else
285  {
286 #define ENUM_OUT(B,C) str << ( _dbStateInfo & B ? C : '-' )
287  str << "V4(";
288  ENUM_OUT( DbSI_HAVE_V4, 'X' );
289  ENUM_OUT( DbSI_MADE_V4, 'c' );
290  ENUM_OUT( DbSI_MODIFIED_V4, 'm' );
291  str << ")V3(";
292  ENUM_OUT( DbSI_HAVE_V3, 'X' );
293  ENUM_OUT( DbSI_HAVE_V3TOV4, 'B' );
294  ENUM_OUT( DbSI_MADE_V3TOV4, 'c' );
295  str << "): " << stringPath( _root, _dbPath );
296 #undef ENUM_OUT
297  }
298  return str << "]";
299 }
300 
302 //
303 //
304 // METHOD NAME : RpmDb::initDatabase
305 // METHOD TYPE : PMError
306 //
307 void RpmDb::initDatabase( Pathname root_r, Pathname dbPath_r, bool doRebuild_r )
308 {
310  // Check arguments
312  bool quickinit( root_r.empty() );
313 
314  if ( root_r.empty() )
315  root_r = "/";
316 
317  if ( dbPath_r.empty() )
318  dbPath_r = "/var/lib/rpm";
319 
320  if ( ! (root_r.absolute() && dbPath_r.absolute()) )
321  {
322  ERR << "Illegal root or dbPath: " << stringPath( root_r, dbPath_r ) << endl;
323  ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
324  }
325 
326  MIL << "Calling initDatabase: " << stringPath( root_r, dbPath_r )
327  << ( doRebuild_r ? " (rebuilddb)" : "" )
328  << ( quickinit ? " (quickinit)" : "" ) << endl;
329 
331  // Check whether already initialized
333  if ( initialized() )
334  {
335  if ( root_r == _root && dbPath_r == _dbPath )
336  {
337  return;
338  }
339  else
340  {
341  ZYPP_THROW(RpmDbAlreadyOpenException(_root, _dbPath, root_r, dbPath_r));
342  }
343  }
344 
346  // init database
349 
350  if ( quickinit )
351  {
352  MIL << "QUICK initDatabase (no systemRoot set)" << endl;
353  return;
354  }
355 
357  try
358  {
359  internal_initDatabase( root_r, dbPath_r, info );
360  }
361  catch (const RpmException & excpt_r)
362  {
363  ZYPP_CAUGHT(excpt_r);
365  ERR << "Cleanup on error: state " << info << endl;
366 
367  if ( dbsi_has( info, DbSI_MADE_V4 ) )
368  {
369  // remove the newly created rpm4 database and
370  // any backup created on conversion.
371  removeV4( root_r + dbPath_r, dbsi_has( info, DbSI_MADE_V3TOV4 ) );
372  }
373  ZYPP_RETHROW(excpt_r);
374  }
375  if ( dbsi_has( info, DbSI_HAVE_V3 ) )
376  {
377  if ( root_r == "/" || dbsi_has( info, DbSI_MODIFIED_V4 ) )
378  {
379  // Move obsolete rpm3 database beside.
380  MIL << "Cleanup: state " << info << endl;
381  removeV3( root_r + dbPath_r, dbsi_has( info, DbSI_MADE_V3TOV4 ) );
382  dbsi_clr( info, DbSI_HAVE_V3 );
383  }
384  else
385  {
386  // Performing an update: Keep the original rpm3 database
387  // and wait if the rpm4 database gets modified by installing
388  // or removing packages. Cleanup in modifyDatabase or closeDatabase.
389  MIL << "Update mode: Cleanup delayed until closeOldDatabase." << endl;
390  }
391  }
392 #warning CHECK: notify root about conversion backup.
393 
394  _root = root_r;
395  _dbPath = dbPath_r;
396  _dbStateInfo = info;
397 
398  if ( doRebuild_r )
399  {
400  if ( dbsi_has( info, DbSI_HAVE_V4 )
401  && ! dbsi_has( info, DbSI_MADE_V4 ) )
402  {
403  rebuildDatabase();
404  }
405  }
406 
407  MIL << "Syncronizing keys with zypp keyring" << endl;
408  syncTrustedKeys();
409 
410  // Close the database in case any write acces (create/convert)
411  // happened during init. This should drop any lock acquired
412  // by librpm. On demand it will be reopened readonly and should
413  // not hold any lock.
414  librpmDb::dbRelease( true );
415 
416  MIL << "InitDatabase: " << *this << endl;
417 }
418 
420 //
421 //
422 // METHOD NAME : RpmDb::internal_initDatabase
423 // METHOD TYPE : PMError
424 //
425 void RpmDb::internal_initDatabase( const Pathname & root_r, const Pathname & dbPath_r,
426  DbStateInfoBits & info_r )
427 {
428  info_r = DbSI_NO_INIT;
429 
431  // Get info about the desired database dir
433  librpmDb::DbDirInfo dbInfo( root_r, dbPath_r );
434 
435  if ( dbInfo.illegalArgs() )
436  {
437  // should not happen (checked in initDatabase)
438  ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
439  }
440  if ( ! dbInfo.usableArgs() )
441  {
442  ERR << "Bad database directory: " << dbInfo.dbDir() << endl;
443  ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
444  }
445 
446  if ( dbInfo.hasDbV4() )
447  {
448  dbsi_set( info_r, DbSI_HAVE_V4 );
449  MIL << "Found rpm4 database in " << dbInfo.dbDir() << endl;
450  }
451  else
452  {
453  MIL << "Creating new rpm4 database in " << dbInfo.dbDir() << endl;
454  }
455 
456  if ( dbInfo.hasDbV3() )
457  {
458  dbsi_set( info_r, DbSI_HAVE_V3 );
459  }
460  if ( dbInfo.hasDbV3ToV4() )
461  {
462  dbsi_set( info_r, DbSI_HAVE_V3TOV4 );
463  }
464 
465  DBG << "Initial state: " << info_r << ": " << stringPath( root_r, dbPath_r );
466  librpmDb::dumpState( DBG ) << endl;
467 
469  // Access database, create if needed
471 
472  // creates dbdir and empty rpm4 database if not present
473  librpmDb::dbAccess( root_r, dbPath_r );
474 
475  if ( ! dbInfo.hasDbV4() )
476  {
477  dbInfo.restat();
478  if ( dbInfo.hasDbV4() )
479  {
480  dbsi_set( info_r, DbSI_HAVE_V4 | DbSI_MADE_V4 );
481  }
482  }
483 
484  DBG << "Access state: " << info_r << ": " << stringPath( root_r, dbPath_r );
485  librpmDb::dumpState( DBG ) << endl;
486 
488  // Check whether to convert something. Create backup but do
489  // not remove anything here
491  librpmDb::constPtr dbptr;
492  librpmDb::dbAccess( dbptr );
493  bool dbEmpty = dbptr->empty();
494  if ( dbEmpty )
495  {
496  MIL << "Empty rpm4 database " << dbInfo.dbV4() << endl;
497  }
498 
499  if ( dbInfo.hasDbV3() )
500  {
501  MIL << "Found rpm3 database " << dbInfo.dbV3() << endl;
502 
503  if ( dbEmpty )
504  {
505  extern void convertV3toV4( const Pathname & v3db_r, const librpmDb::constPtr & v4db_r );
506  convertV3toV4( dbInfo.dbV3().path(), dbptr );
507 
508  // create a backup copy
509  int res = filesystem::copy( dbInfo.dbV3().path(), dbInfo.dbV3ToV4().path() );
510  if ( res )
511  {
512  WAR << "Backup converted rpm3 database failed: error(" << res << ")" << endl;
513  }
514  else
515  {
516  dbInfo.restat();
517  if ( dbInfo.hasDbV3ToV4() )
518  {
519  MIL << "Backup converted rpm3 database: " << dbInfo.dbV3ToV4() << endl;
521  }
522  }
523 
524  }
525  else
526  {
527 
528  WAR << "Non empty rpm3 and rpm4 database found: using rpm4" << endl;
529  // set DbSI_MODIFIED_V4 as it's not a temporary which can be removed.
530  dbsi_set( info_r, DbSI_MODIFIED_V4 );
531 
532  }
533 
534  DBG << "Convert state: " << info_r << ": " << stringPath( root_r, dbPath_r );
535  librpmDb::dumpState( DBG ) << endl;
536  }
537 
538  if ( dbInfo.hasDbV3ToV4() )
539  {
540  MIL << "Rpm3 database backup: " << dbInfo.dbV3ToV4() << endl;
541  }
542 }
543 
545 //
546 //
547 // METHOD NAME : RpmDb::removeV4
548 // METHOD TYPE : void
549 //
550 void RpmDb::removeV4( const Pathname & dbdir_r, bool v3backup_r )
551 {
552  const char * v3backup = "packages.rpm3";
553  const char * master = "Packages";
554  const char * index[] =
555  {
556  "Basenames",
557  "Conflictname",
558  "Depends",
559  "Dirnames",
560  "Filemd5s",
561  "Group",
562  "Installtid",
563  "Name",
564  "Providename",
565  "Provideversion",
566  "Pubkeys",
567  "Requirename",
568  "Requireversion",
569  "Sha1header",
570  "Sigmd5",
571  "Triggername",
572  // last entry!
573  NULL
574  };
575 
576  PathInfo pi( dbdir_r );
577  if ( ! pi.isDir() )
578  {
579  ERR << "Can't remove rpm4 database in non directory: " << dbdir_r << endl;
580  return;
581  }
582 
583  for ( const char ** f = index; *f; ++f )
584  {
585  pi( dbdir_r + *f );
586  if ( pi.isFile() )
587  {
588  filesystem::unlink( pi.path() );
589  }
590  }
591 
592  pi( dbdir_r + master );
593  if ( pi.isFile() )
594  {
595  MIL << "Removing rpm4 database " << pi << endl;
596  filesystem::unlink( pi.path() );
597  }
598 
599  if ( v3backup_r )
600  {
601  pi( dbdir_r + v3backup );
602  if ( pi.isFile() )
603  {
604  MIL << "Removing converted rpm3 database backup " << pi << endl;
605  filesystem::unlink( pi.path() );
606  }
607  }
608 }
609 
611 //
612 //
613 // METHOD NAME : RpmDb::removeV3
614 // METHOD TYPE : void
615 //
616 void RpmDb::removeV3( const Pathname & dbdir_r, bool v3backup_r )
617 {
618  const char * master = "packages.rpm";
619  const char * index[] =
620  {
621  "conflictsindex.rpm",
622  "fileindex.rpm",
623  "groupindex.rpm",
624  "nameindex.rpm",
625  "providesindex.rpm",
626  "requiredby.rpm",
627  "triggerindex.rpm",
628  // last entry!
629  NULL
630  };
631 
632  PathInfo pi( dbdir_r );
633  if ( ! pi.isDir() )
634  {
635  ERR << "Can't remove rpm3 database in non directory: " << dbdir_r << endl;
636  return;
637  }
638 
639  for ( const char ** f = index; *f; ++f )
640  {
641  pi( dbdir_r + *f );
642  if ( pi.isFile() )
643  {
644  filesystem::unlink( pi.path() );
645  }
646  }
647 
648 #warning CHECK: compare vs existing v3 backup. notify root
649  pi( dbdir_r + master );
650  if ( pi.isFile() )
651  {
652  Pathname m( pi.path() );
653  if ( v3backup_r )
654  {
655  // backup was already created
656  filesystem::unlink( m );
657  Pathname b( m.extend( "3" ) );
658  pi( b ); // stat backup
659  }
660  else
661  {
662  Pathname b( m.extend( ".deleted" ) );
663  pi( b );
664  if ( pi.isFile() )
665  {
666  // rempve existing backup
667  filesystem::unlink( b );
668  }
669  filesystem::rename( m, b );
670  pi( b ); // stat backup
671  }
672  MIL << "(Re)moved rpm3 database to " << pi << endl;
673  }
674 }
675 
677 //
678 //
679 // METHOD NAME : RpmDb::modifyDatabase
680 // METHOD TYPE : void
681 //
683 {
684  if ( ! initialized() )
685  return;
686 
687  // tag database as modified
689 
690  // Move outdated rpm3 database beside.
692  {
693  MIL << "Update mode: Delayed cleanup: state " << _dbStateInfo << endl;
696  }
697 }
698 
700 //
701 //
702 // METHOD NAME : RpmDb::closeDatabase
703 // METHOD TYPE : PMError
704 //
706 {
707  if ( ! initialized() )
708  {
709  return;
710  }
711 
712  MIL << "Calling closeDatabase: " << *this << endl;
713 
715  // Block further database access
718 
720  // Check fate if old version database still present
723  {
724  MIL << "Update mode: Delayed cleanup: state " << _dbStateInfo << endl;
726  {
727  // Move outdated rpm3 database beside.
729  }
730  else
731  {
732  // Remove unmodified rpm4 database
734  }
735  }
736 
738  // Uninit
740  _root = _dbPath = Pathname();
742 
743  MIL << "closeDatabase: " << *this << endl;
744 }
745 
747 //
748 //
749 // METHOD NAME : RpmDb::rebuildDatabase
750 // METHOD TYPE : PMError
751 //
753 {
755 
756  report->start( root() + dbPath() );
757 
758  try
759  {
760  doRebuildDatabase(report);
761  }
762  catch (RpmException & excpt_r)
763  {
764  report->finish(root() + dbPath(), RebuildDBReport::FAILED, excpt_r.asUserHistory());
765  ZYPP_RETHROW(excpt_r);
766  }
767  report->finish(root() + dbPath(), RebuildDBReport::NO_ERROR, "");
768 }
769 
771 {
773 
774  MIL << "RpmDb::rebuildDatabase" << *this << endl;
775  // FIXME Timecount _t( "RpmDb::rebuildDatabase" );
776 
777  PathInfo dbMaster( root() + dbPath() + "Packages" );
778  PathInfo dbMasterBackup( dbMaster.path().extend( ".y2backup" ) );
779 
780  // run rpm
781  RpmArgVec opts;
782  opts.push_back("--rebuilddb");
783  opts.push_back("-vv");
784 
785  // don't call modifyDatabase because it would remove the old
786  // rpm3 database, if the current database is a temporary one.
788 
789  // progress report: watch this file growing
790  PathInfo newMaster( root()
791  + dbPath().extend( str::form( "rebuilddb.%d",
792  process?process->getpid():0) )
793  + "Packages" );
794 
795  string line;
796  string errmsg;
797 
798  while ( systemReadLine( line ) )
799  {
800  if ( newMaster() )
801  { // file is removed at the end of rebuild.
802  // current size should be upper limit for new db
803  if ( ! report->progress( (100 * newMaster.size()) / dbMaster.size(), root() + dbPath()) )
804  {
805  WAR << "User requested abort." << endl;
806  systemKill();
807  filesystem::recursive_rmdir( newMaster.path().dirname() );
808  }
809  }
810 
811  if ( line.compare( 0, 2, "D:" ) )
812  {
813  errmsg += line + '\n';
814  // report.notify( line );
815  WAR << line << endl;
816  }
817  }
818 
819  int rpm_status = systemStatus();
820 
821  if ( rpm_status != 0 )
822  {
823  //TranslatorExplanation after semicolon is error message
824  ZYPP_THROW(RpmSubprocessException(string(_("RPM failed: ") +
825  (errmsg.empty() ? error_message: errmsg))));
826  }
827  else
828  {
829  report->progress( 100, root() + dbPath() ); // 100%
830  }
831 }
832 
834 namespace
835 {
840  void computeKeyRingSync( std::set<Edition> & rpmKeys_r, std::list<PublicKeyData> & zyppKeys_r )
841  {
843  // Remember latest release and where it ocurred
844  struct Key
845  {
846  Key()
847  : _inRpmKeys( nullptr )
848  , _inZyppKeys( nullptr )
849  {}
850 
851  void updateIf( const Edition & rpmKey_r )
852  {
853  std::string keyRelease( rpmKey_r.release() );
854  int comp = _release.compare( keyRelease );
855  if ( comp < 0 )
856  {
857  // update to newer release
858  _release.swap( keyRelease );
859  _inRpmKeys = &rpmKey_r;
860  _inZyppKeys = nullptr;
861  if ( !keyRelease.empty() )
862  DBG << "Old key in R: gpg-pubkey-" << rpmKey_r.version() << "-" << keyRelease << endl;
863  }
864  else if ( comp == 0 )
865  {
866  // stay with this release
867  if ( ! _inRpmKeys )
868  _inRpmKeys = &rpmKey_r;
869  }
870  // else: this is an old release
871  else
872  DBG << "Old key in R: gpg-pubkey-" << rpmKey_r.version() << "-" << keyRelease << endl;
873  }
874 
875  void updateIf( const PublicKeyData & zyppKey_r )
876  {
877  std::string keyRelease( zyppKey_r.gpgPubkeyRelease() );
878  int comp = _release.compare( keyRelease );
879  if ( comp < 0 )
880  {
881  // update to newer release
882  _release.swap( keyRelease );
883  _inRpmKeys = nullptr;
884  _inZyppKeys = &zyppKey_r;
885  if ( !keyRelease.empty() )
886  DBG << "Old key in Z: gpg-pubkey-" << zyppKey_r.gpgPubkeyVersion() << "-" << keyRelease << endl;
887  }
888  else if ( comp == 0 )
889  {
890  // stay with this release
891  if ( ! _inZyppKeys )
892  _inZyppKeys = &zyppKey_r;
893  }
894  // else: this is an old release
895  else
896  DBG << "Old key in Z: gpg-pubkey-" << zyppKey_r.gpgPubkeyVersion() << "-" << keyRelease << endl;
897  }
898 
899  std::string _release;
900  const Edition * _inRpmKeys;
901  const PublicKeyData * _inZyppKeys;
902  };
904 
905  // collect keys by ID(version) and latest creation(release)
906  std::map<std::string,Key> _keymap;
907 
908  for_( it, rpmKeys_r.begin(), rpmKeys_r.end() )
909  {
910  _keymap[(*it).version()].updateIf( *it );
911  }
912 
913  for_( it, zyppKeys_r.begin(), zyppKeys_r.end() )
914  {
915  _keymap[(*it).gpgPubkeyVersion()].updateIf( *it );
916  }
917 
918  // compute missing keys
919  std::set<Edition> rpmKeys;
920  std::list<PublicKeyData> zyppKeys;
921  for_( it, _keymap.begin(), _keymap.end() )
922  {
923  DBG << "gpg-pubkey-" << (*it).first << "-" << (*it).second._release << " "
924  << ( (*it).second._inRpmKeys ? "R" : "_" )
925  << ( (*it).second._inZyppKeys ? "Z" : "_" ) << endl;
926  if ( ! (*it).second._inRpmKeys )
927  {
928  zyppKeys.push_back( *(*it).second._inZyppKeys );
929  }
930  if ( ! (*it).second._inZyppKeys )
931  {
932  rpmKeys.insert( *(*it).second._inRpmKeys );
933  }
934  }
935  rpmKeys_r.swap( rpmKeys );
936  zyppKeys_r.swap( zyppKeys );
937  }
938 } // namespace
940 
942 {
943  MIL << "Going to sync trusted keys..." << endl;
944  std::set<Edition> rpmKeys( pubkeyEditions() );
945  std::list<PublicKeyData> zyppKeys( getZYpp()->keyRing()->trustedPublicKeyData() );
946  computeKeyRingSync( rpmKeys, zyppKeys );
947  MIL << (mode_r & SYNC_TO_KEYRING ? "" : "(skip) ") << "Rpm keys to export into zypp trusted keyring: " << rpmKeys.size() << endl;
948  MIL << (mode_r & SYNC_FROM_KEYRING ? "" : "(skip) ") << "Zypp trusted keys to import into rpm database: " << zyppKeys.size() << endl;
949 
951  if ( (mode_r & SYNC_TO_KEYRING) && ! rpmKeys.empty() )
952  {
953  // export to zypp keyring
954  MIL << "Exporting rpm keyring into zypp trusted keyring" <<endl;
955  // Temporarily disconnect to prevent the attemt to re-import the exported keys.
957  librpmDb::db_const_iterator keepDbOpen; // just to keep a ref.
958 
959  TmpFile tmpfile( getZYpp()->tmpPath() );
960  {
961  ofstream tmpos( tmpfile.path().c_str() );
962  for_( it, rpmKeys.begin(), rpmKeys.end() )
963  {
964  // we export the rpm key into a file
965  RpmHeader::constPtr result;
966  getData( string("gpg-pubkey"), *it, result );
967  tmpos << result->tag_description() << endl;
968  }
969  }
970  try
971  {
972  getZYpp()->keyRing()->multiKeyImport( tmpfile.path(), true /*trusted*/);
973  }
974  catch (Exception &e)
975  {
976  ERR << "Could not import keys into in zypp keyring" << endl;
977  }
978  }
979 
981  if ( (mode_r & SYNC_FROM_KEYRING) && ! zyppKeys.empty() )
982  {
983  // import from zypp keyring
984  MIL << "Importing zypp trusted keyring" << std::endl;
985  for_( it, zyppKeys.begin(), zyppKeys.end() )
986  {
987  try
988  {
989  importPubkey( getZYpp()->keyRing()->exportTrustedPublicKey( *it ) );
990  }
991  catch ( const RpmException & exp )
992  {
993  ZYPP_CAUGHT( exp );
994  }
995  }
996  }
997  MIL << "Trusted keys synced." << endl;
998 }
999 
1002 
1005 
1007 //
1008 //
1009 // METHOD NAME : RpmDb::importPubkey
1010 // METHOD TYPE : PMError
1011 //
1012 void RpmDb::importPubkey( const PublicKey & pubkey_r )
1013 {
1015 
1016  // bnc#828672: On the fly key import in READONLY
1018  {
1019  WAR << "Key " << pubkey_r << " can not be imported. (READONLY MODE)" << endl;
1020  return;
1021  }
1022 
1023  // check if the key is already in the rpm database
1024  Edition keyEd( pubkey_r.gpgPubkeyVersion(), pubkey_r.gpgPubkeyRelease() );
1025  set<Edition> rpmKeys = pubkeyEditions();
1026  bool hasOldkeys = false;
1027 
1028  for_( it, rpmKeys.begin(), rpmKeys.end() )
1029  {
1030  if ( keyEd == *it ) // quick test (Edition is IdStringType!)
1031  {
1032  MIL << "Key " << pubkey_r << " is already in the rpm trusted keyring. (skip import)" << endl;
1033  return;
1034  }
1035 
1036  if ( keyEd.version() != (*it).version() )
1037  continue; // different key ID (version)
1038 
1039  if ( keyEd.release() < (*it).release() )
1040  {
1041  MIL << "Key " << pubkey_r << " is older than one in the rpm trusted keyring. (skip import)" << endl;
1042  return;
1043  }
1044  else
1045  {
1046  hasOldkeys = true;
1047  }
1048  }
1049  MIL << "Key " << pubkey_r << " will be imported into the rpm trusted keyring." << (hasOldkeys?"(update)":"(new)") << endl;
1050 
1051  if ( hasOldkeys )
1052  {
1053  // We must explicitly delete old key IDs first (all releases,
1054  // that's why we don't call removePubkey here).
1055  std::string keyName( "gpg-pubkey-" + keyEd.version() );
1056  RpmArgVec opts;
1057  opts.push_back ( "-e" );
1058  opts.push_back ( "--allmatches" );
1059  opts.push_back ( "--" );
1060  opts.push_back ( keyName.c_str() );
1061  // don't call modifyDatabase because it would remove the old
1062  // rpm3 database, if the current database is a temporary one.
1064 
1065  string line;
1066  while ( systemReadLine( line ) )
1067  {
1068  ( str::startsWith( line, "error:" ) ? WAR : DBG ) << line << endl;
1069  }
1070 
1071  if ( systemStatus() != 0 )
1072  {
1073  ERR << "Failed to remove key " << pubkey_r << " from RPM trusted keyring (ignored)" << endl;
1074  }
1075  else
1076  {
1077  MIL << "Key " << pubkey_r << " has been removed from RPM trusted keyring" << endl;
1078  }
1079  }
1080 
1081  // import the new key
1082  RpmArgVec opts;
1083  opts.push_back ( "--import" );
1084  opts.push_back ( "--" );
1085  opts.push_back ( pubkey_r.path().asString().c_str() );
1086 
1087  // don't call modifyDatabase because it would remove the old
1088  // rpm3 database, if the current database is a temporary one.
1090 
1091  string line;
1092  while ( systemReadLine( line ) )
1093  {
1094  ( str::startsWith( line, "error:" ) ? WAR : DBG ) << line << endl;
1095  }
1096 
1097  if ( systemStatus() != 0 )
1098  {
1099  //TranslatorExplanation first %s is file name, second is error message
1100  ZYPP_THROW(RpmSubprocessException(boost::str(boost::format(
1101  _("Failed to import public key from file %s: %s"))
1102  % pubkey_r.asString() % error_message)));
1103  }
1104  else
1105  {
1106  MIL << "Key " << pubkey_r << " imported in rpm trusted keyring." << endl;
1107  }
1108 }
1109 
1111 //
1112 //
1113 // METHOD NAME : RpmDb::removePubkey
1114 // METHOD TYPE : PMError
1115 //
1116 void RpmDb::removePubkey( const PublicKey & pubkey_r )
1117 {
1119 
1120  // check if the key is in the rpm database and just
1121  // return if it does not.
1122  set<Edition> rpm_keys = pubkeyEditions();
1123  set<Edition>::const_iterator found_edition = rpm_keys.end();
1124  std::string pubkeyVersion( pubkey_r.gpgPubkeyVersion() );
1125 
1126  for_( it, rpm_keys.begin(), rpm_keys.end() )
1127  {
1128  if ( (*it).version() == pubkeyVersion )
1129  {
1130  found_edition = it;
1131  break;
1132  }
1133  }
1134 
1135  // the key does not exist, cannot be removed
1136  if (found_edition == rpm_keys.end())
1137  {
1138  WAR << "Key " << pubkey_r.id() << " is not in rpm db" << endl;
1139  return;
1140  }
1141 
1142  string rpm_name("gpg-pubkey-" + found_edition->asString());
1143 
1144  RpmArgVec opts;
1145  opts.push_back ( "-e" );
1146  opts.push_back ( "--" );
1147  opts.push_back ( rpm_name.c_str() );
1148 
1149  // don't call modifyDatabase because it would remove the old
1150  // rpm3 database, if the current database is a temporary one.
1152 
1153  string line;
1154  while ( systemReadLine( line ) )
1155  {
1156  if ( line.substr( 0, 6 ) == "error:" )
1157  {
1158  WAR << line << endl;
1159  }
1160  else
1161  {
1162  DBG << line << endl;
1163  }
1164  }
1165 
1166  int rpm_status = systemStatus();
1167 
1168  if ( rpm_status != 0 )
1169  {
1170  //TranslatorExplanation first %s is key name, second is error message
1171  ZYPP_THROW(RpmSubprocessException(boost::str(boost::format(
1172  _("Failed to remove public key %s: %s")) % pubkey_r.asString()
1173  % error_message)));
1174  }
1175  else
1176  {
1177  MIL << "Key " << pubkey_r << " has been removed from RPM trusted keyring" << endl;
1178  }
1179 }
1180 
1182 //
1183 //
1184 // METHOD NAME : RpmDb::pubkeys
1185 // METHOD TYPE : set<Edition>
1186 //
1187 list<PublicKey> RpmDb::pubkeys() const
1188 {
1189  list<PublicKey> ret;
1190 
1192  for ( it.findByName( string( "gpg-pubkey" ) ); *it; ++it )
1193  {
1194  Edition edition = it->tag_edition();
1195  if (edition != Edition::noedition)
1196  {
1197  // we export the rpm key into a file
1198  RpmHeader::constPtr result;
1199  getData( string("gpg-pubkey"), edition, result );
1200  TmpFile file(getZYpp()->tmpPath());
1201  ofstream os;
1202  try
1203  {
1204  os.open(file.path().asString().c_str());
1205  // dump rpm key into the tmp file
1206  os << result->tag_description();
1207  //MIL << "-----------------------------------------------" << endl;
1208  //MIL << result->tag_description() <<endl;
1209  //MIL << "-----------------------------------------------" << endl;
1210  os.close();
1211  // read the public key from the dumped file
1212  PublicKey key(file);
1213  ret.push_back(key);
1214  }
1215  catch (exception &e)
1216  {
1217  ERR << "Could not dump key " << edition.asString() << " in tmp file " << file.path() << endl;
1218  // just ignore the key
1219  }
1220  }
1221  }
1222  return ret;
1223 }
1224 
1225 set<Edition> RpmDb::pubkeyEditions() const
1226  {
1227  set<Edition> ret;
1228 
1230  for ( it.findByName( string( "gpg-pubkey" ) ); *it; ++it )
1231  {
1232  Edition edition = it->tag_edition();
1233  if (edition != Edition::noedition)
1234  ret.insert( edition );
1235  }
1236  return ret;
1237  }
1238 
1239 
1241 //
1242 //
1243 // METHOD NAME : RpmDb::fileList
1244 // METHOD TYPE : bool
1245 //
1246 // DESCRIPTION :
1247 //
1248 list<FileInfo>
1249 RpmDb::fileList( const string & name_r, const Edition & edition_r ) const
1250 {
1251  list<FileInfo> result;
1252 
1254  bool found;
1255  if (edition_r == Edition::noedition)
1256  {
1257  found = it.findPackage( name_r );
1258  }
1259  else
1260  {
1261  found = it.findPackage( name_r, edition_r );
1262  }
1263  if (!found)
1264  return result;
1265 
1266  return result;
1267 }
1268 
1269 
1271 //
1272 //
1273 // METHOD NAME : RpmDb::hasFile
1274 // METHOD TYPE : bool
1275 //
1276 // DESCRIPTION :
1277 //
1278 bool RpmDb::hasFile( const string & file_r, const string & name_r ) const
1279 {
1281  bool res;
1282  do
1283  {
1284  res = it.findByFile( file_r );
1285  if (!res) break;
1286  if (!name_r.empty())
1287  {
1288  res = (it->tag_name() == name_r);
1289  }
1290  ++it;
1291  }
1292  while (res && *it);
1293  return res;
1294 }
1295 
1297 //
1298 //
1299 // METHOD NAME : RpmDb::whoOwnsFile
1300 // METHOD TYPE : string
1301 //
1302 // DESCRIPTION :
1303 //
1304 string RpmDb::whoOwnsFile( const string & file_r) const
1305 {
1307  if (it.findByFile( file_r ))
1308  {
1309  return it->tag_name();
1310  }
1311  return "";
1312 }
1313 
1315 //
1316 //
1317 // METHOD NAME : RpmDb::hasProvides
1318 // METHOD TYPE : bool
1319 //
1320 // DESCRIPTION :
1321 //
1322 bool RpmDb::hasProvides( const string & tag_r ) const
1323 {
1325  return it.findByProvides( tag_r );
1326 }
1327 
1329 //
1330 //
1331 // METHOD NAME : RpmDb::hasRequiredBy
1332 // METHOD TYPE : bool
1333 //
1334 // DESCRIPTION :
1335 //
1336 bool RpmDb::hasRequiredBy( const string & tag_r ) const
1337 {
1339  return it.findByRequiredBy( tag_r );
1340 }
1341 
1343 //
1344 //
1345 // METHOD NAME : RpmDb::hasConflicts
1346 // METHOD TYPE : bool
1347 //
1348 // DESCRIPTION :
1349 //
1350 bool RpmDb::hasConflicts( const string & tag_r ) const
1351 {
1353  return it.findByConflicts( tag_r );
1354 }
1355 
1357 //
1358 //
1359 // METHOD NAME : RpmDb::hasPackage
1360 // METHOD TYPE : bool
1361 //
1362 // DESCRIPTION :
1363 //
1364 bool RpmDb::hasPackage( const string & name_r ) const
1365 {
1367  return it.findPackage( name_r );
1368 }
1369 
1371 //
1372 //
1373 // METHOD NAME : RpmDb::hasPackage
1374 // METHOD TYPE : bool
1375 //
1376 // DESCRIPTION :
1377 //
1378 bool RpmDb::hasPackage( const string & name_r, const Edition & ed_r ) const
1379 {
1380  librpmDb::db_const_iterator it;
1381  return it.findPackage( name_r, ed_r );
1382 }
1383 
1385 //
1386 //
1387 // METHOD NAME : RpmDb::getData
1388 // METHOD TYPE : PMError
1389 //
1390 // DESCRIPTION :
1391 //
1392 void RpmDb::getData( const string & name_r,
1393  RpmHeader::constPtr & result_r ) const
1394 {
1395  librpmDb::db_const_iterator it;
1396  it.findPackage( name_r );
1397  result_r = *it;
1398  if (it.dbError())
1399  ZYPP_THROW(*(it.dbError()));
1400 }
1401 
1403 //
1404 //
1405 // METHOD NAME : RpmDb::getData
1406 // METHOD TYPE : void
1407 //
1408 // DESCRIPTION :
1409 //
1410 void RpmDb::getData( const string & name_r, const Edition & ed_r,
1411  RpmHeader::constPtr & result_r ) const
1412 {
1413  librpmDb::db_const_iterator it;
1414  it.findPackage( name_r, ed_r );
1415  result_r = *it;
1416  if (it.dbError())
1417  ZYPP_THROW(*(it.dbError()));
1418 }
1419 
1421 //
1422 // METHOD NAME : RpmDb::checkPackage
1423 // METHOD TYPE : RpmDb::checkPackageResult
1424 //
1426 {
1427  PathInfo file( path_r );
1428  if ( ! file.isFile() )
1429  {
1430  ERR << "Not a file: " << file << endl;
1431  return CHK_ERROR;
1432  }
1433 
1434  FD_t fd = ::Fopen( file.asString().c_str(), "r.ufdio" );
1435  if ( fd == 0 || ::Ferror(fd) )
1436  {
1437  ERR << "Can't open file for reading: " << file << " (" << ::Fstrerror(fd) << ")" << endl;
1438  if ( fd )
1439  ::Fclose( fd );
1440  return CHK_ERROR;
1441  }
1442 
1443  rpmts ts = ::rpmtsCreate();
1444  ::rpmtsSetRootDir( ts, root().asString().c_str() );
1445  ::rpmtsSetVSFlags( ts, RPMVSF_DEFAULT );
1446  int res = ::rpmReadPackageFile( ts, fd, path_r.asString().c_str(), NULL );
1447  ts = rpmtsFree(ts);
1448 
1449  ::Fclose( fd );
1450 
1451  switch ( res )
1452  {
1453  case RPMRC_OK:
1454  return CHK_OK;
1455  break;
1456  case RPMRC_NOTFOUND:
1457  WAR << "Signature is unknown type. " << file << endl;
1458  return CHK_NOTFOUND;
1459  break;
1460  case RPMRC_FAIL:
1461  WAR << "Signature does not verify. " << file << endl;
1462  return CHK_FAIL;
1463  break;
1464  case RPMRC_NOTTRUSTED:
1465  WAR << "Signature is OK, but key is not trusted. " << file << endl;
1466  return CHK_NOTTRUSTED;
1467  break;
1468  case RPMRC_NOKEY:
1469  WAR << "Public key is unavailable. " << file << endl;
1470  return CHK_NOKEY;
1471  break;
1472  }
1473  ERR << "Error reading header." << file << endl;
1474  return CHK_ERROR;
1475 }
1476 
1477 // determine changed files of installed package
1478 bool
1479 RpmDb::queryChangedFiles(FileList & fileList, const string& packageName)
1480 {
1481  bool ok = true;
1482 
1483  fileList.clear();
1484 
1485  if ( ! initialized() ) return false;
1486 
1487  RpmArgVec opts;
1488 
1489  opts.push_back ("-V");
1490  opts.push_back ("--nodeps");
1491  opts.push_back ("--noscripts");
1492  opts.push_back ("--nomd5");
1493  opts.push_back ("--");
1494  opts.push_back (packageName.c_str());
1495 
1497 
1498  if ( process == NULL )
1499  return false;
1500 
1501  /* from rpm manpage
1502  5 MD5 sum
1503  S File size
1504  L Symlink
1505  T Mtime
1506  D Device
1507  U User
1508  G Group
1509  M Mode (includes permissions and file type)
1510  */
1511 
1512  string line;
1513  while (systemReadLine(line))
1514  {
1515  if (line.length() > 12 &&
1516  (line[0] == 'S' || line[0] == 's' ||
1517  (line[0] == '.' && line[7] == 'T')))
1518  {
1519  // file has been changed
1520  string filename;
1521 
1522  filename.assign(line, 11, line.length() - 11);
1523  fileList.insert(filename);
1524  }
1525  }
1526 
1527  systemStatus();
1528  // exit code ignored, rpm returns 1 no matter if package is installed or
1529  // not
1530 
1531  return ok;
1532 }
1533 
1534 
1535 
1536 /****************************************************************/
1537 /* private member-functions */
1538 /****************************************************************/
1539 
1540 /*--------------------------------------------------------------*/
1541 /* Run rpm with the specified arguments, handling stderr */
1542 /* as specified by disp */
1543 /*--------------------------------------------------------------*/
1544 void
1547 {
1548  if ( process )
1549  {
1550  delete process;
1551  process = NULL;
1552  }
1553  exit_code = -1;
1554 
1555  if ( ! initialized() )
1556  {
1558  }
1559 
1560  RpmArgVec args;
1561 
1562  // always set root and dbpath
1563  args.push_back("rpm");
1564  args.push_back("--root");
1565  args.push_back(_root.asString().c_str());
1566  args.push_back("--dbpath");
1567  args.push_back(_dbPath.asString().c_str());
1568 
1569  const char* argv[args.size() + opts.size() + 1];
1570 
1571  const char** p = argv;
1572  p = copy (args.begin (), args.end (), p);
1573  p = copy (opts.begin (), opts.end (), p);
1574  *p = 0;
1575 
1576  // Invalidate all outstanding database handles in case
1577  // the database gets modified.
1578  librpmDb::dbRelease( true );
1579 
1580  // Launch the program with default locale
1581  process = new ExternalProgram(argv, disp, false, -1, true);
1582  return;
1583 }
1584 
1585 /*--------------------------------------------------------------*/
1586 /* Read a line from the rpm process */
1587 /*--------------------------------------------------------------*/
1588 bool RpmDb::systemReadLine( string & line )
1589 {
1590  line.erase();
1591 
1592  if ( process == NULL )
1593  return false;
1594 
1595  if ( process->inputFile() )
1596  {
1597  process->setBlocking( false );
1598  FILE * inputfile = process->inputFile();
1599  int inputfileFd = ::fileno( inputfile );
1600  do
1601  {
1602  /* Watch inputFile to see when it has input. */
1603  fd_set rfds;
1604  FD_ZERO( &rfds );
1605  FD_SET( inputfileFd, &rfds );
1606 
1607  /* Wait up to 5 seconds. */
1608  struct timeval tv;
1609  tv.tv_sec = 5;
1610  tv.tv_usec = 0;
1611 
1612  int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
1613 
1614  if ( retval == -1 )
1615  {
1616  ERR << "select error: " << strerror(errno) << endl;
1617  if ( errno != EINTR )
1618  return false;
1619  }
1620  else if ( retval )
1621  {
1622  // Data is available now.
1623  static size_t linebuffer_size = 0; // static because getline allocs
1624  static char * linebuffer = 0; // and reallocs if buffer is too small
1625  ssize_t nread = getline( &linebuffer, &linebuffer_size, inputfile );
1626  if ( nread == -1 )
1627  {
1628  if ( ::feof( inputfile ) )
1629  return line.size(); // in case of pending output
1630  }
1631  else
1632  {
1633  if ( nread > 0 )
1634  {
1635  if ( linebuffer[nread-1] == '\n' )
1636  --nread;
1637  line += string( linebuffer, nread );
1638  }
1639 
1640  if ( ! ::ferror( inputfile ) || ::feof( inputfile ) )
1641  return true; // complete line
1642  }
1643  clearerr( inputfile );
1644  }
1645  else
1646  {
1647  // No data within time.
1648  if ( ! process->running() )
1649  return false;
1650  }
1651  } while ( true );
1652  }
1653 
1654  return false;
1655 }
1656 
1657 /*--------------------------------------------------------------*/
1658 /* Return the exit status of the rpm process, closing the */
1659 /* connection if not already done */
1660 /*--------------------------------------------------------------*/
1661 int
1663 {
1664  if ( process == NULL )
1665  return -1;
1666 
1667  exit_code = process->close();
1668  if (exit_code == 0)
1669  error_message = "";
1670  else
1672  process->kill();
1673  delete process;
1674  process = 0;
1675 
1676  // DBG << "exit code " << exit_code << endl;
1677 
1678  return exit_code;
1679 }
1680 
1681 /*--------------------------------------------------------------*/
1682 /* Forcably kill the rpm process */
1683 /*--------------------------------------------------------------*/
1684 void
1686 {
1687  if (process) process->kill();
1688 }
1689 
1690 
1691 // generate diff mails for config files
1692 void RpmDb::processConfigFiles(const string& line, const string& name, const char* typemsg, const char* difffailmsg, const char* diffgenmsg)
1693 {
1694  string msg = line.substr(9);
1695  string::size_type pos1 = string::npos;
1696  string::size_type pos2 = string::npos;
1697  string file1s, file2s;
1698  Pathname file1;
1699  Pathname file2;
1700 
1701  pos1 = msg.find (typemsg);
1702  for (;;)
1703  {
1704  if ( pos1 == string::npos )
1705  break;
1706 
1707  pos2 = pos1 + strlen (typemsg);
1708 
1709  if (pos2 >= msg.length() )
1710  break;
1711 
1712  file1 = msg.substr (0, pos1);
1713  file2 = msg.substr (pos2);
1714 
1715  file1s = file1.asString();
1716  file2s = file2.asString();
1717 
1718  if (!_root.empty() && _root != "/")
1719  {
1720  file1 = _root + file1;
1721  file2 = _root + file2;
1722  }
1723 
1724  string out;
1725  int ret = diffFiles (file1.asString(), file2.asString(), out, 25);
1726  if (ret)
1727  {
1728  Pathname file = _root + WARNINGMAILPATH;
1729  if (filesystem::assert_dir(file) != 0)
1730  {
1731  ERR << "Could not create " << file.asString() << endl;
1732  break;
1733  }
1734  file += Date(Date::now()).form("config_diff_%Y_%m_%d.log");
1735  ofstream notify(file.asString().c_str(), ios::out|ios::app);
1736  if (!notify)
1737  {
1738  ERR << "Could not open " << file << endl;
1739  break;
1740  }
1741 
1742  // Translator: %s = name of an rpm package. A list of diffs follows
1743  // this message.
1744  notify << str::form(_("Changed configuration files for %s:"), name.c_str()) << endl;
1745  if (ret>1)
1746  {
1747  ERR << "diff failed" << endl;
1748  notify << str::form(difffailmsg,
1749  file1s.c_str(), file2s.c_str()) << endl;
1750  }
1751  else
1752  {
1753  notify << str::form(diffgenmsg,
1754  file1s.c_str(), file2s.c_str()) << endl;
1755 
1756  // remove root for the viewer's pleasure (#38240)
1757  if (!_root.empty() && _root != "/")
1758  {
1759  if (out.substr(0,4) == "--- ")
1760  {
1761  out.replace(4, file1.asString().length(), file1s);
1762  }
1763  string::size_type pos = out.find("\n+++ ");
1764  if (pos != string::npos)
1765  {
1766  out.replace(pos+5, file2.asString().length(), file2s);
1767  }
1768  }
1769  notify << out << endl;
1770  }
1771  notify.close();
1772  notify.open("/var/lib/update-messages/yast2-packagemanager.rpmdb.configfiles");
1773  notify.close();
1774  }
1775  else
1776  {
1777  WAR << "rpm created " << file2 << " but it is not different from " << file2 << endl;
1778  }
1779  break;
1780  }
1781 }
1782 
1784 //
1785 //
1786 // METHOD NAME : RpmDb::installPackage
1787 // METHOD TYPE : PMError
1788 //
1789 void RpmDb::installPackage( const Pathname & filename, RpmInstFlags flags )
1790 {
1792 
1793  report->start(filename);
1794 
1795  do
1796  try
1797  {
1798  doInstallPackage(filename, flags, report);
1799  report->finish();
1800  break;
1801  }
1802  catch (RpmException & excpt_r)
1803  {
1804  RpmInstallReport::Action user = report->problem( excpt_r );
1805 
1806  if ( user == RpmInstallReport::ABORT )
1807  {
1808  report->finish( excpt_r );
1809  ZYPP_RETHROW(excpt_r);
1810  }
1811  else if ( user == RpmInstallReport::IGNORE )
1812  {
1813  break;
1814  }
1815  }
1816  while (true);
1817 }
1818 
1819 void RpmDb::doInstallPackage( const Pathname & filename, RpmInstFlags flags, callback::SendReport<RpmInstallReport> & report )
1820 {
1822  HistoryLog historylog;
1823 
1824  MIL << "RpmDb::installPackage(" << filename << "," << flags << ")" << endl;
1825 
1826 
1827  // backup
1828  if ( _packagebackups )
1829  {
1830  // FIXME report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
1831  if ( ! backupPackage( filename ) )
1832  {
1833  ERR << "backup of " << filename.asString() << " failed" << endl;
1834  }
1835  // FIXME status handling
1836  report->progress( 0 ); // allow 1% for backup creation.
1837  }
1838 
1839  // run rpm
1840  RpmArgVec opts;
1841  if (flags & RPMINST_NOUPGRADE)
1842  opts.push_back("-i");
1843  else
1844  opts.push_back("-U");
1845 
1846  opts.push_back("--percent");
1847 
1848  // ZConfig defines cross-arch installation
1849  if ( ! ZConfig::instance().systemArchitecture().compatibleWith( ZConfig::instance().defaultSystemArchitecture() ) )
1850  opts.push_back("--ignorearch");
1851 
1852  if (flags & RPMINST_NODIGEST)
1853  opts.push_back("--nodigest");
1854  if (flags & RPMINST_NOSIGNATURE)
1855  opts.push_back("--nosignature");
1856  if (flags & RPMINST_EXCLUDEDOCS)
1857  opts.push_back ("--excludedocs");
1858  if (flags & RPMINST_NOSCRIPTS)
1859  opts.push_back ("--noscripts");
1860  if (flags & RPMINST_FORCE)
1861  opts.push_back ("--force");
1862  if (flags & RPMINST_NODEPS)
1863  opts.push_back ("--nodeps");
1864  if (flags & RPMINST_IGNORESIZE)
1865  opts.push_back ("--ignoresize");
1866  if (flags & RPMINST_JUSTDB)
1867  opts.push_back ("--justdb");
1868  if (flags & RPMINST_TEST)
1869  opts.push_back ("--test");
1870 
1871  opts.push_back("--");
1872 
1873  // rpm requires additional quoting of special chars:
1874  string quotedFilename( rpmQuoteFilename( filename ) );
1875  opts.push_back ( quotedFilename.c_str() );
1876 
1877  modifyDatabase(); // BEFORE run_rpm
1879 
1880  string line;
1881  string rpmmsg;
1882  vector<string> configwarnings;
1883 
1884  unsigned linecnt = 0;
1885  while (systemReadLine(line))
1886  {
1887  if ( linecnt < MAXRPMMESSAGELINES )
1888  ++linecnt;
1889  else
1890  continue;
1891 
1892  if (line.substr(0,2)=="%%")
1893  {
1894  int percent;
1895  sscanf (line.c_str () + 2, "%d", &percent);
1896  report->progress( percent );
1897  }
1898  else
1899  rpmmsg += line+'\n';
1900 
1901  if ( line.substr(0,8) == "warning:" )
1902  {
1903  configwarnings.push_back(line);
1904  }
1905  }
1906  if ( linecnt > MAXRPMMESSAGELINES )
1907  rpmmsg += "[truncated]\n";
1908 
1909  int rpm_status = systemStatus();
1910 
1911  // evaluate result
1912  for (vector<string>::iterator it = configwarnings.begin();
1913  it != configwarnings.end(); ++it)
1914  {
1915  processConfigFiles(*it, Pathname::basename(filename), " saved as ",
1916  // %s = filenames
1917  _("rpm saved %s as %s, but it was impossible to determine the difference"),
1918  // %s = filenames
1919  _("rpm saved %s as %s.\nHere are the first 25 lines of difference:\n"));
1920  processConfigFiles(*it, Pathname::basename(filename), " created as ",
1921  // %s = filenames
1922  _("rpm created %s as %s, but it was impossible to determine the difference"),
1923  // %s = filenames
1924  _("rpm created %s as %s.\nHere are the first 25 lines of difference:\n"));
1925  }
1926 
1927  if ( rpm_status != 0 )
1928  {
1929  historylog.comment(
1930  str::form("%s install failed", Pathname::basename(filename).c_str()),
1931  true /*timestamp*/);
1932  ostringstream sstr;
1933  sstr << "rpm output:" << endl << rpmmsg << endl;
1934  historylog.comment(sstr.str());
1935  // TranslatorExplanation the colon is followed by an error message
1936  ZYPP_THROW(RpmSubprocessException(string(_("RPM failed: ")) +
1937  (rpmmsg.empty() ? error_message : rpmmsg)));
1938  }
1939  else if ( ! rpmmsg.empty() )
1940  {
1941  historylog.comment(
1942  str::form("%s installed ok", Pathname::basename(filename).c_str()),
1943  true /*timestamp*/);
1944  ostringstream sstr;
1945  sstr << "Additional rpm output:" << endl << rpmmsg << endl;
1946  historylog.comment(sstr.str());
1947 
1948  // report additional rpm output in finish
1949  // TranslatorExplanation Text is followed by a ':' and the actual output.
1950  report->finishInfo(str::form( "%s:\n%s\n", _("Additional rpm output"), rpmmsg.c_str() ));
1951  }
1952 }
1953 
1955 //
1956 //
1957 // METHOD NAME : RpmDb::removePackage
1958 // METHOD TYPE : PMError
1959 //
1960 void RpmDb::removePackage( Package::constPtr package, RpmInstFlags flags )
1961 {
1962  // 'rpm -e' does not like epochs
1963  return removePackage( package->name()
1964  + "-" + package->edition().version()
1965  + "-" + package->edition().release()
1966  + "." + package->arch().asString(), flags );
1967 }
1968 
1970 //
1971 //
1972 // METHOD NAME : RpmDb::removePackage
1973 // METHOD TYPE : PMError
1974 //
1975 void RpmDb::removePackage( const string & name_r, RpmInstFlags flags )
1976 {
1978 
1979  report->start( name_r );
1980 
1981  do
1982  try
1983  {
1984  doRemovePackage(name_r, flags, report);
1985  report->finish();
1986  break;
1987  }
1988  catch (RpmException & excpt_r)
1989  {
1990  RpmRemoveReport::Action user = report->problem( excpt_r );
1991 
1992  if ( user == RpmRemoveReport::ABORT )
1993  {
1994  report->finish( excpt_r );
1995  ZYPP_RETHROW(excpt_r);
1996  }
1997  else if ( user == RpmRemoveReport::IGNORE )
1998  {
1999  break;
2000  }
2001  }
2002  while (true);
2003 }
2004 
2005 
2006 void RpmDb::doRemovePackage( const string & name_r, RpmInstFlags flags, callback::SendReport<RpmRemoveReport> & report )
2007 {
2009  HistoryLog historylog;
2010 
2011  MIL << "RpmDb::doRemovePackage(" << name_r << "," << flags << ")" << endl;
2012 
2013  // backup
2014  if ( _packagebackups )
2015  {
2016  // FIXME solve this status report somehow
2017  // report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
2018  if ( ! backupPackage( name_r ) )
2019  {
2020  ERR << "backup of " << name_r << " failed" << endl;
2021  }
2022  report->progress( 0 );
2023  }
2024  else
2025  {
2026  report->progress( 100 );
2027  }
2028 
2029  // run rpm
2030  RpmArgVec opts;
2031  opts.push_back("-e");
2032  opts.push_back("--allmatches");
2033 
2034  if (flags & RPMINST_NOSCRIPTS)
2035  opts.push_back("--noscripts");
2036  if (flags & RPMINST_NODEPS)
2037  opts.push_back("--nodeps");
2038  if (flags & RPMINST_JUSTDB)
2039  opts.push_back("--justdb");
2040  if (flags & RPMINST_TEST)
2041  opts.push_back ("--test");
2042  if (flags & RPMINST_FORCE)
2043  {
2044  WAR << "IGNORE OPTION: 'rpm -e' does not support '--force'" << endl;
2045  }
2046 
2047  opts.push_back("--");
2048  opts.push_back(name_r.c_str());
2049 
2050  modifyDatabase(); // BEFORE run_rpm
2052 
2053  string line;
2054  string rpmmsg;
2055 
2056  // got no progress from command, so we fake it:
2057  // 5 - command started
2058  // 50 - command completed
2059  // 100 if no error
2060  report->progress( 5 );
2061  unsigned linecnt = 0;
2062  while (systemReadLine(line))
2063  {
2064  if ( linecnt < MAXRPMMESSAGELINES )
2065  ++linecnt;
2066  else
2067  continue;
2068  rpmmsg += line+'\n';
2069  }
2070  if ( linecnt > MAXRPMMESSAGELINES )
2071  rpmmsg += "[truncated]\n";
2072  report->progress( 50 );
2073  int rpm_status = systemStatus();
2074 
2075  if ( rpm_status != 0 )
2076  {
2077  historylog.comment(
2078  str::form("%s remove failed", name_r.c_str()), true /*timestamp*/);
2079  ostringstream sstr;
2080  sstr << "rpm output:" << endl << rpmmsg << endl;
2081  historylog.comment(sstr.str());
2082  // TranslatorExplanation the colon is followed by an error message
2083  ZYPP_THROW(RpmSubprocessException(string(_("RPM failed: ")) +
2084  (rpmmsg.empty() ? error_message: rpmmsg)));
2085  }
2086  else if ( ! rpmmsg.empty() )
2087  {
2088  historylog.comment(
2089  str::form("%s removed ok", name_r.c_str()), true /*timestamp*/);
2090 
2091  ostringstream sstr;
2092  sstr << "Additional rpm output:" << endl << rpmmsg << endl;
2093  historylog.comment(sstr.str());
2094 
2095  // report additional rpm output in finish
2096  // TranslatorExplanation Text is followed by a ':' and the actual output.
2097  report->finishInfo(str::form( "%s:\n%s\n", _("Additional rpm output"), rpmmsg.c_str() ));
2098  }
2099 }
2100 
2102 //
2103 //
2104 // METHOD NAME : RpmDb::backupPackage
2105 // METHOD TYPE : bool
2106 //
2107 bool RpmDb::backupPackage( const Pathname & filename )
2108 {
2110  if ( ! h )
2111  return false;
2112 
2113  return backupPackage( h->tag_name() );
2114 }
2115 
2117 //
2118 //
2119 // METHOD NAME : RpmDb::backupPackage
2120 // METHOD TYPE : bool
2121 //
2122 bool RpmDb::backupPackage(const string& packageName)
2123 {
2124  HistoryLog progresslog;
2125  bool ret = true;
2126  Pathname backupFilename;
2127  Pathname filestobackupfile = _root+_backuppath+FILEFORBACKUPFILES;
2128 
2129  if (_backuppath.empty())
2130  {
2131  INT << "_backuppath empty" << endl;
2132  return false;
2133  }
2134 
2136 
2137  if (!queryChangedFiles(fileList, packageName))
2138  {
2139  ERR << "Error while getting changed files for package " <<
2140  packageName << endl;
2141  return false;
2142  }
2143 
2144  if (fileList.size() <= 0)
2145  {
2146  DBG << "package " << packageName << " not changed -> no backup" << endl;
2147  return true;
2148  }
2149 
2151  {
2152  return false;
2153  }
2154 
2155  {
2156  // build up archive name
2157  time_t currentTime = time(0);
2158  struct tm *currentLocalTime = localtime(&currentTime);
2159 
2160  int date = (currentLocalTime->tm_year + 1900) * 10000
2161  + (currentLocalTime->tm_mon + 1) * 100
2162  + currentLocalTime->tm_mday;
2163 
2164  int num = 0;
2165  do
2166  {
2167  backupFilename = _root + _backuppath
2168  + str::form("%s-%d-%d.tar.gz",packageName.c_str(), date, num);
2169 
2170  }
2171  while ( PathInfo(backupFilename).isExist() && num++ < 1000);
2172 
2173  PathInfo pi(filestobackupfile);
2174  if (pi.isExist() && !pi.isFile())
2175  {
2176  ERR << filestobackupfile.asString() << " already exists and is no file" << endl;
2177  return false;
2178  }
2179 
2180  ofstream fp ( filestobackupfile.asString().c_str(), ios::out|ios::trunc );
2181 
2182  if (!fp)
2183  {
2184  ERR << "could not open " << filestobackupfile.asString() << endl;
2185  return false;
2186  }
2187 
2188  for (FileList::const_iterator cit = fileList.begin();
2189  cit != fileList.end(); ++cit)
2190  {
2191  string name = *cit;
2192  if ( name[0] == '/' )
2193  {
2194  // remove slash, file must be relative to -C parameter of tar
2195  name = name.substr( 1 );
2196  }
2197  DBG << "saving file "<< name << endl;
2198  fp << name << endl;
2199  }
2200  fp.close();
2201 
2202  const char* const argv[] =
2203  {
2204  "tar",
2205  "-czhP",
2206  "-C",
2207  _root.asString().c_str(),
2208  "--ignore-failed-read",
2209  "-f",
2210  backupFilename.asString().c_str(),
2211  "-T",
2212  filestobackupfile.asString().c_str(),
2213  NULL
2214  };
2215 
2216  // execute tar in inst-sys (we dont know if there is a tar below _root !)
2217  ExternalProgram tar(argv, ExternalProgram::Stderr_To_Stdout, false, -1, true);
2218 
2219  string tarmsg;
2220 
2221  // TODO: its probably possible to start tar with -v and watch it adding
2222  // files to report progress
2223  for (string output = tar.receiveLine(); output.length() ;output = tar.receiveLine())
2224  {
2225  tarmsg+=output;
2226  }
2227 
2228  int ret = tar.close();
2229 
2230  if ( ret != 0)
2231  {
2232  ERR << "tar failed: " << tarmsg << endl;
2233  ret = false;
2234  }
2235  else
2236  {
2237  MIL << "tar backup ok" << endl;
2238  progresslog.comment(
2239  str::form(_("created backup %s"), backupFilename.asString().c_str())
2240  , /*timestamp*/true);
2241  }
2242 
2243  filesystem::unlink(filestobackupfile);
2244  }
2245 
2246  return ret;
2247 }
2248 
2249 void RpmDb::setBackupPath(const Pathname& path)
2250 {
2251  _backuppath = path;
2252 }
2253 
2254 } // namespace rpm
2255 } // namespace target
2256 } // namespace zypp