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