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