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