fe8889b64e3b663126c7e598adc7803be805e1a5
[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   std::string pubkeypath( pubkey_r.path().asString() );
1112   opts.push_back ( pubkeypath.c_str() );
1113
1114   // don't call modifyDatabase because it would remove the old
1115   // rpm3 database, if the current database is a temporary one.
1116   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
1117
1118   string line;
1119   while ( systemReadLine( line ) )
1120   {
1121     ( str::startsWith( line, "error:" ) ? WAR : DBG ) << line << endl;
1122   }
1123
1124   if ( systemStatus() != 0 )
1125   {
1126     //TranslatorExplanation first %s is file name, second is error message
1127     ZYPP_THROW(RpmSubprocessException(boost::str(boost::format(
1128         _("Failed to import public key from file %s: %s"))
1129         % pubkey_r.asString() % error_message)));
1130   }
1131   else
1132   {
1133     MIL << "Key " << pubkey_r << " imported in rpm trusted keyring." << endl;
1134   }
1135 }
1136
1137 ///////////////////////////////////////////////////////////////////
1138 //
1139 //
1140 //      METHOD NAME : RpmDb::removePubkey
1141 //      METHOD TYPE : PMError
1142 //
1143 void RpmDb::removePubkey( const PublicKey & pubkey_r )
1144 {
1145   FAILIFNOTINITIALIZED;
1146
1147   // check if the key is in the rpm database and just
1148   // return if it does not.
1149   set<Edition> rpm_keys = pubkeyEditions();
1150   set<Edition>::const_iterator found_edition = rpm_keys.end();
1151   std::string pubkeyVersion( pubkey_r.gpgPubkeyVersion() );
1152
1153   for_( it, rpm_keys.begin(), rpm_keys.end() )
1154   {
1155     if ( (*it).version() == pubkeyVersion )
1156     {
1157         found_edition = it;
1158         break;
1159     }
1160   }
1161
1162   // the key does not exist, cannot be removed
1163   if (found_edition == rpm_keys.end())
1164   {
1165       WAR << "Key " << pubkey_r.id() << " is not in rpm db" << endl;
1166       return;
1167   }
1168
1169   string rpm_name("gpg-pubkey-" + found_edition->asString());
1170
1171   RpmArgVec opts;
1172   opts.push_back ( "-e" );
1173   opts.push_back ( "--" );
1174   opts.push_back ( rpm_name.c_str() );
1175
1176   // don't call modifyDatabase because it would remove the old
1177   // rpm3 database, if the current database is a temporary one.
1178   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
1179
1180   string line;
1181   while ( systemReadLine( line ) )
1182   {
1183     if ( line.substr( 0, 6 ) == "error:" )
1184     {
1185       WAR << line << endl;
1186     }
1187     else
1188     {
1189       DBG << line << endl;
1190     }
1191   }
1192
1193   int rpm_status = systemStatus();
1194
1195   if ( rpm_status != 0 )
1196   {
1197     //TranslatorExplanation first %s is key name, second is error message
1198     ZYPP_THROW(RpmSubprocessException(boost::str(boost::format(
1199         _("Failed to remove public key %s: %s")) % pubkey_r.asString()
1200         % error_message)));
1201   }
1202   else
1203   {
1204     MIL << "Key " << pubkey_r << " has been removed from RPM trusted keyring" << endl;
1205   }
1206 }
1207
1208 ///////////////////////////////////////////////////////////////////
1209 //
1210 //
1211 //      METHOD NAME : RpmDb::pubkeys
1212 //      METHOD TYPE : set<Edition>
1213 //
1214 list<PublicKey> RpmDb::pubkeys() const
1215 {
1216   list<PublicKey> ret;
1217
1218   librpmDb::db_const_iterator it;
1219   for ( it.findByName( string( "gpg-pubkey" ) ); *it; ++it )
1220   {
1221     Edition edition = it->tag_edition();
1222     if (edition != Edition::noedition)
1223     {
1224       // we export the rpm key into a file
1225       RpmHeader::constPtr result;
1226       getData( string("gpg-pubkey"), edition, result );
1227       TmpFile file(getZYpp()->tmpPath());
1228       ofstream os;
1229       try
1230       {
1231         os.open(file.path().asString().c_str());
1232         // dump rpm key into the tmp file
1233         os << result->tag_description();
1234         //MIL << "-----------------------------------------------" << endl;
1235         //MIL << result->tag_description() <<endl;
1236         //MIL << "-----------------------------------------------" << endl;
1237         os.close();
1238         // read the public key from the dumped file
1239         PublicKey key(file);
1240         ret.push_back(key);
1241       }
1242       catch (exception &e)
1243       {
1244         ERR << "Could not dump key " << edition.asString() << " in tmp file " << file.path() << endl;
1245         // just ignore the key
1246       }
1247     }
1248   }
1249   return ret;
1250 }
1251
1252 set<Edition> RpmDb::pubkeyEditions() const
1253   {
1254     set<Edition> ret;
1255
1256     librpmDb::db_const_iterator it;
1257     for ( it.findByName( string( "gpg-pubkey" ) ); *it; ++it )
1258     {
1259       Edition edition = it->tag_edition();
1260       if (edition != Edition::noedition)
1261         ret.insert( edition );
1262     }
1263     return ret;
1264   }
1265
1266
1267 ///////////////////////////////////////////////////////////////////
1268 //
1269 //
1270 //      METHOD NAME : RpmDb::fileList
1271 //      METHOD TYPE : bool
1272 //
1273 //      DESCRIPTION :
1274 //
1275 list<FileInfo>
1276 RpmDb::fileList( const string & name_r, const Edition & edition_r ) const
1277 {
1278   list<FileInfo> result;
1279
1280   librpmDb::db_const_iterator it;
1281   bool found;
1282   if (edition_r == Edition::noedition)
1283   {
1284     found = it.findPackage( name_r );
1285   }
1286   else
1287   {
1288     found = it.findPackage( name_r, edition_r );
1289   }
1290   if (!found)
1291     return result;
1292
1293   return result;
1294 }
1295
1296
1297 ///////////////////////////////////////////////////////////////////
1298 //
1299 //
1300 //      METHOD NAME : RpmDb::hasFile
1301 //      METHOD TYPE : bool
1302 //
1303 //      DESCRIPTION :
1304 //
1305 bool RpmDb::hasFile( const string & file_r, const string & name_r ) const
1306 {
1307   librpmDb::db_const_iterator it;
1308   bool res;
1309   do
1310   {
1311     res = it.findByFile( file_r );
1312     if (!res) break;
1313     if (!name_r.empty())
1314     {
1315       res = (it->tag_name() == name_r);
1316     }
1317     ++it;
1318   }
1319   while (res && *it);
1320   return res;
1321 }
1322
1323 ///////////////////////////////////////////////////////////////////
1324 //
1325 //
1326 //      METHOD NAME : RpmDb::whoOwnsFile
1327 //      METHOD TYPE : string
1328 //
1329 //      DESCRIPTION :
1330 //
1331 string RpmDb::whoOwnsFile( const string & file_r) const
1332 {
1333   librpmDb::db_const_iterator it;
1334   if (it.findByFile( file_r ))
1335   {
1336     return it->tag_name();
1337   }
1338   return "";
1339 }
1340
1341 ///////////////////////////////////////////////////////////////////
1342 //
1343 //
1344 //      METHOD NAME : RpmDb::hasProvides
1345 //      METHOD TYPE : bool
1346 //
1347 //      DESCRIPTION :
1348 //
1349 bool RpmDb::hasProvides( const string & tag_r ) const
1350 {
1351   librpmDb::db_const_iterator it;
1352   return it.findByProvides( tag_r );
1353 }
1354
1355 ///////////////////////////////////////////////////////////////////
1356 //
1357 //
1358 //      METHOD NAME : RpmDb::hasRequiredBy
1359 //      METHOD TYPE : bool
1360 //
1361 //      DESCRIPTION :
1362 //
1363 bool RpmDb::hasRequiredBy( const string & tag_r ) const
1364 {
1365   librpmDb::db_const_iterator it;
1366   return it.findByRequiredBy( tag_r );
1367 }
1368
1369 ///////////////////////////////////////////////////////////////////
1370 //
1371 //
1372 //      METHOD NAME : RpmDb::hasConflicts
1373 //      METHOD TYPE : bool
1374 //
1375 //      DESCRIPTION :
1376 //
1377 bool RpmDb::hasConflicts( const string & tag_r ) const
1378 {
1379   librpmDb::db_const_iterator it;
1380   return it.findByConflicts( tag_r );
1381 }
1382
1383 ///////////////////////////////////////////////////////////////////
1384 //
1385 //
1386 //      METHOD NAME : RpmDb::hasPackage
1387 //      METHOD TYPE : bool
1388 //
1389 //      DESCRIPTION :
1390 //
1391 bool RpmDb::hasPackage( const string & name_r ) const
1392 {
1393   librpmDb::db_const_iterator it;
1394   return it.findPackage( name_r );
1395 }
1396
1397 ///////////////////////////////////////////////////////////////////
1398 //
1399 //
1400 //      METHOD NAME : RpmDb::hasPackage
1401 //      METHOD TYPE : bool
1402 //
1403 //      DESCRIPTION :
1404 //
1405 bool RpmDb::hasPackage( const string & name_r, const Edition & ed_r ) const
1406 {
1407   librpmDb::db_const_iterator it;
1408   return it.findPackage( name_r, ed_r );
1409 }
1410
1411 ///////////////////////////////////////////////////////////////////
1412 //
1413 //
1414 //      METHOD NAME : RpmDb::getData
1415 //      METHOD TYPE : PMError
1416 //
1417 //      DESCRIPTION :
1418 //
1419 void RpmDb::getData( const string & name_r,
1420                      RpmHeader::constPtr & result_r ) const
1421 {
1422   librpmDb::db_const_iterator it;
1423   it.findPackage( name_r );
1424   result_r = *it;
1425   if (it.dbError())
1426     ZYPP_THROW(*(it.dbError()));
1427 }
1428
1429 ///////////////////////////////////////////////////////////////////
1430 //
1431 //
1432 //      METHOD NAME : RpmDb::getData
1433 //      METHOD TYPE : void
1434 //
1435 //      DESCRIPTION :
1436 //
1437 void RpmDb::getData( const string & name_r, const Edition & ed_r,
1438                      RpmHeader::constPtr & result_r ) const
1439 {
1440   librpmDb::db_const_iterator it;
1441   it.findPackage( name_r, ed_r  );
1442   result_r = *it;
1443   if (it.dbError())
1444     ZYPP_THROW(*(it.dbError()));
1445 }
1446
1447 ///////////////////////////////////////////////////////////////////
1448 namespace
1449 {
1450   struct RpmlogCapture : public std::string
1451   {
1452     RpmlogCapture()
1453     { rpmlog()._cap = this; }
1454
1455     ~RpmlogCapture()
1456     { rpmlog()._cap = nullptr; }
1457
1458   private:
1459     struct Rpmlog
1460     {
1461       Rpmlog()
1462       : _cap( nullptr )
1463       {
1464         rpmlogSetCallback( rpmLogCB, this );
1465         rpmSetVerbosity( RPMLOG_INFO );
1466         _f = ::fopen( "/dev/null","w");
1467         rpmlogSetFile( _f );
1468       }
1469
1470       ~Rpmlog()
1471       { if ( _f ) ::fclose( _f ); }
1472
1473       static int rpmLogCB( rpmlogRec rec_r, rpmlogCallbackData data_r )
1474       { return reinterpret_cast<Rpmlog*>(data_r)->rpmLog( rec_r ); }
1475
1476       int rpmLog( rpmlogRec rec_r )
1477       {
1478         if ( _cap ) (*_cap) = rpmlogRecMessage( rec_r );
1479         return RPMLOG_DEFAULT;
1480       }
1481
1482       FILE * _f;
1483       std::string * _cap;
1484     };
1485
1486     static Rpmlog & rpmlog()
1487     { static Rpmlog _rpmlog; return _rpmlog; }
1488   };
1489
1490
1491 } // namespace
1492 ///////////////////////////////////////////////////////////////////
1493 //
1494 //      METHOD NAME : RpmDb::checkPackage
1495 //      METHOD TYPE : RpmDb::CheckPackageResult
1496 //
1497 RpmDb::CheckPackageResult RpmDb::checkPackage( const Pathname & path_r, CheckPackageDetail & detail_r )
1498 {
1499   PathInfo file( path_r );
1500   if ( ! file.isFile() )
1501   {
1502     ERR << "Not a file: " << file << endl;
1503     return CHK_ERROR;
1504   }
1505
1506   FD_t fd = ::Fopen( file.asString().c_str(), "r.ufdio" );
1507   if ( fd == 0 || ::Ferror(fd) )
1508   {
1509     ERR << "Can't open file for reading: " << file << " (" << ::Fstrerror(fd) << ")" << endl;
1510     if ( fd )
1511       ::Fclose( fd );
1512     return CHK_ERROR;
1513   }
1514   rpmts ts = ::rpmtsCreate();
1515   ::rpmtsSetRootDir( ts, root().asString().c_str() );
1516   ::rpmtsSetVSFlags( ts, RPMVSF_DEFAULT );
1517
1518   rpmQVKArguments_s qva;
1519   memset( &qva, 0, sizeof(rpmQVKArguments_s) );
1520   qva.qva_flags = (VERIFY_DIGEST|VERIFY_SIGNATURE);
1521
1522   RpmlogCapture vresult;
1523   int res = ::rpmVerifySignatures( &qva, ts, fd, path_r.basename().c_str() );
1524
1525   ts = rpmtsFree(ts);
1526   ::Fclose( fd );
1527
1528
1529   if ( res == 0 )
1530   {
1531     detail_r.push_back( CheckPackageDetail::value_type( CHK_OK, std::move(vresult) ) );
1532     return CHK_OK;
1533   }
1534
1535   // results per line...
1536   WAR << vresult;
1537   std::vector<std::string> lines;
1538   str::split( vresult, std::back_inserter(lines), "\n" );
1539   unsigned count[6] = { 0, 0, 0, 0, 0, 0 };
1540
1541   for ( unsigned i = 1; i < lines.size(); ++i )
1542   {
1543     std::string & line( lines[i] );
1544     CheckPackageResult lineres = CHK_ERROR;
1545     if ( line.find( ": OK" ) != std::string::npos )
1546     { lineres = CHK_OK; }
1547     else if ( line.find( ": NOKEY" ) != std::string::npos )
1548     { lineres = CHK_NOKEY; }
1549     else if ( line.find( ": BAD" ) != std::string::npos )
1550     { lineres = CHK_FAIL; }
1551     else if ( line.find( ": UNKNOWN" ) != std::string::npos )
1552     { lineres = CHK_NOTFOUND; }
1553     else if ( line.find( ": NOTRUSTED" ) != std::string::npos )
1554     { lineres = CHK_NOTTRUSTED; }
1555
1556     ++count[lineres];
1557     detail_r.push_back( CheckPackageDetail::value_type( lineres, std::move(line) ) );
1558   }
1559
1560   CheckPackageResult ret = CHK_ERROR;
1561   if ( count[CHK_FAIL] )
1562     ret = CHK_FAIL;
1563
1564   else if ( count[CHK_NOTFOUND] )
1565     ret = CHK_NOTFOUND;
1566
1567   else if ( count[CHK_NOKEY] )
1568     ret = CHK_NOKEY;
1569
1570   else if ( count[CHK_NOTTRUSTED] )
1571     ret = CHK_NOTTRUSTED;
1572
1573   return ret;
1574 }
1575
1576 RpmDb::CheckPackageResult RpmDb::checkPackage( const Pathname & path_r )
1577 { CheckPackageDetail dummy; return checkPackage( path_r, dummy ); }
1578
1579
1580 // determine changed files of installed package
1581 bool
1582 RpmDb::queryChangedFiles(FileList & fileList, const string& packageName)
1583 {
1584   bool ok = true;
1585
1586   fileList.clear();
1587
1588   if ( ! initialized() ) return false;
1589
1590   RpmArgVec opts;
1591
1592   opts.push_back ("-V");
1593   opts.push_back ("--nodeps");
1594   opts.push_back ("--noscripts");
1595   opts.push_back ("--nomd5");
1596   opts.push_back ("--");
1597   opts.push_back (packageName.c_str());
1598
1599   run_rpm (opts, ExternalProgram::Discard_Stderr);
1600
1601   if ( process == NULL )
1602     return false;
1603
1604   /* from rpm manpage
1605    5      MD5 sum
1606    S      File size
1607    L      Symlink
1608    T      Mtime
1609    D      Device
1610    U      User
1611    G      Group
1612    M      Mode (includes permissions and file type)
1613   */
1614
1615   string line;
1616   while (systemReadLine(line))
1617   {
1618     if (line.length() > 12 &&
1619         (line[0] == 'S' || line[0] == 's' ||
1620          (line[0] == '.' && line[7] == 'T')))
1621     {
1622       // file has been changed
1623       string filename;
1624
1625       filename.assign(line, 11, line.length() - 11);
1626       fileList.insert(filename);
1627     }
1628   }
1629
1630   systemStatus();
1631   // exit code ignored, rpm returns 1 no matter if package is installed or
1632   // not
1633
1634   return ok;
1635 }
1636
1637
1638
1639 /****************************************************************/
1640 /* private member-functions                                     */
1641 /****************************************************************/
1642
1643 /*--------------------------------------------------------------*/
1644 /* Run rpm with the specified arguments, handling stderr        */
1645 /* as specified  by disp                                        */
1646 /*--------------------------------------------------------------*/
1647 void
1648 RpmDb::run_rpm (const RpmArgVec& opts,
1649                 ExternalProgram::Stderr_Disposition disp)
1650 {
1651   if ( process )
1652   {
1653     delete process;
1654     process = NULL;
1655   }
1656   exit_code = -1;
1657
1658   if ( ! initialized() )
1659   {
1660     ZYPP_THROW(RpmDbNotOpenException());
1661   }
1662
1663   RpmArgVec args;
1664
1665   // always set root and dbpath
1666 #if defined(WORKAROUNDRPMPWDBUG)
1667   args.push_back("#/");         // chdir to / to workaround bnc#819354
1668 #endif
1669   args.push_back("rpm");
1670   args.push_back("--root");
1671   args.push_back(_root.asString().c_str());
1672   args.push_back("--dbpath");
1673   args.push_back(_dbPath.asString().c_str());
1674
1675   const char* argv[args.size() + opts.size() + 1];
1676
1677   const char** p = argv;
1678   p = copy (args.begin (), args.end (), p);
1679   p = copy (opts.begin (), opts.end (), p);
1680   *p = 0;
1681
1682   // Invalidate all outstanding database handles in case
1683   // the database gets modified.
1684   librpmDb::dbRelease( true );
1685
1686   // Launch the program with default locale
1687   process = new ExternalProgram(argv, disp, false, -1, true);
1688   return;
1689 }
1690
1691 /*--------------------------------------------------------------*/
1692 /* Read a line from the rpm process                             */
1693 /*--------------------------------------------------------------*/
1694 bool RpmDb::systemReadLine( string & line )
1695 {
1696   line.erase();
1697
1698   if ( process == NULL )
1699     return false;
1700
1701   if ( process->inputFile() )
1702   {
1703     process->setBlocking( false );
1704     FILE * inputfile = process->inputFile();
1705     int    inputfileFd = ::fileno( inputfile );
1706     do
1707     {
1708       /* Watch inputFile to see when it has input. */
1709       fd_set rfds;
1710       FD_ZERO( &rfds );
1711       FD_SET( inputfileFd, &rfds );
1712
1713       /* Wait up to 5 seconds. */
1714       struct timeval tv;
1715       tv.tv_sec = 5;
1716       tv.tv_usec = 0;
1717
1718       int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
1719
1720       if ( retval == -1 )
1721       {
1722         ERR << "select error: " << strerror(errno) << endl;
1723         if ( errno != EINTR )
1724           return false;
1725       }
1726       else if ( retval )
1727       {
1728         // Data is available now.
1729         static size_t linebuffer_size = 0;      // static because getline allocs
1730         static char * linebuffer = 0;           // and reallocs if buffer is too small
1731         ssize_t nread = getline( &linebuffer, &linebuffer_size, inputfile );
1732         if ( nread == -1 )
1733         {
1734           if ( ::feof( inputfile ) )
1735             return line.size(); // in case of pending output
1736         }
1737         else
1738         {
1739           if ( nread > 0 )
1740           {
1741             if ( linebuffer[nread-1] == '\n' )
1742               --nread;
1743             line += string( linebuffer, nread );
1744           }
1745
1746           if ( ! ::ferror( inputfile ) || ::feof( inputfile ) )
1747             return true; // complete line
1748         }
1749         clearerr( inputfile );
1750       }
1751       else
1752       {
1753         // No data within time.
1754         if ( ! process->running() )
1755           return false;
1756       }
1757     } while ( true );
1758   }
1759
1760   return false;
1761 }
1762
1763 /*--------------------------------------------------------------*/
1764 /* Return the exit status of the rpm process, closing the       */
1765 /* connection if not already done                               */
1766 /*--------------------------------------------------------------*/
1767 int
1768 RpmDb::systemStatus()
1769 {
1770   if ( process == NULL )
1771     return -1;
1772
1773   exit_code = process->close();
1774   if (exit_code == 0)
1775     error_message = "";
1776   else
1777     error_message = process->execError();
1778   process->kill();
1779   delete process;
1780   process = 0;
1781
1782   //   DBG << "exit code " << exit_code << endl;
1783
1784   return exit_code;
1785 }
1786
1787 /*--------------------------------------------------------------*/
1788 /* Forcably kill the rpm process                                */
1789 /*--------------------------------------------------------------*/
1790 void
1791 RpmDb::systemKill()
1792 {
1793   if (process) process->kill();
1794 }
1795
1796
1797 // generate diff mails for config files
1798 void RpmDb::processConfigFiles(const string& line, const string& name, const char* typemsg, const char* difffailmsg, const char* diffgenmsg)
1799 {
1800   string msg = line.substr(9);
1801   string::size_type pos1 = string::npos;
1802   string::size_type pos2 = string::npos;
1803   string file1s, file2s;
1804   Pathname file1;
1805   Pathname file2;
1806
1807   pos1 = msg.find (typemsg);
1808   for (;;)
1809   {
1810     if ( pos1 == string::npos )
1811       break;
1812
1813     pos2 = pos1 + strlen (typemsg);
1814
1815     if (pos2 >= msg.length() )
1816       break;
1817
1818     file1 = msg.substr (0, pos1);
1819     file2 = msg.substr (pos2);
1820
1821     file1s = file1.asString();
1822     file2s = file2.asString();
1823
1824     if (!_root.empty() && _root != "/")
1825     {
1826       file1 = _root + file1;
1827       file2 = _root + file2;
1828     }
1829
1830     string out;
1831     int ret = diffFiles (file1.asString(), file2.asString(), out, 25);
1832     if (ret)
1833     {
1834       Pathname file = _root + WARNINGMAILPATH;
1835       if (filesystem::assert_dir(file) != 0)
1836       {
1837         ERR << "Could not create " << file.asString() << endl;
1838         break;
1839       }
1840       file += Date(Date::now()).form("config_diff_%Y_%m_%d.log");
1841       ofstream notify(file.asString().c_str(), ios::out|ios::app);
1842       if (!notify)
1843       {
1844         ERR << "Could not open " <<  file << endl;
1845         break;
1846       }
1847
1848       // Translator: %s = name of an rpm package. A list of diffs follows
1849       // this message.
1850       notify << str::form(_("Changed configuration files for %s:"), name.c_str()) << endl;
1851       if (ret>1)
1852       {
1853         ERR << "diff failed" << endl;
1854         notify << str::form(difffailmsg,
1855                             file1s.c_str(), file2s.c_str()) << endl;
1856       }
1857       else
1858       {
1859         notify << str::form(diffgenmsg,
1860                             file1s.c_str(), file2s.c_str()) << endl;
1861
1862         // remove root for the viewer's pleasure (#38240)
1863         if (!_root.empty() && _root != "/")
1864         {
1865           if (out.substr(0,4) == "--- ")
1866           {
1867             out.replace(4, file1.asString().length(), file1s);
1868           }
1869           string::size_type pos = out.find("\n+++ ");
1870           if (pos != string::npos)
1871           {
1872             out.replace(pos+5, file2.asString().length(), file2s);
1873           }
1874         }
1875         notify << out << endl;
1876       }
1877       notify.close();
1878       notify.open("/var/lib/update-messages/yast2-packagemanager.rpmdb.configfiles");
1879       notify.close();
1880     }
1881     else
1882     {
1883       WAR << "rpm created " << file2 << " but it is not different from " << file2 << endl;
1884     }
1885     break;
1886   }
1887 }
1888
1889 ///////////////////////////////////////////////////////////////////
1890 //
1891 //
1892 //      METHOD NAME : RpmDb::installPackage
1893 //      METHOD TYPE : PMError
1894 //
1895 void RpmDb::installPackage( const Pathname & filename, RpmInstFlags flags )
1896 {
1897   callback::SendReport<RpmInstallReport> report;
1898
1899   report->start(filename);
1900
1901   do
1902     try
1903     {
1904       doInstallPackage(filename, flags, report);
1905       report->finish();
1906       break;
1907     }
1908     catch (RpmException & excpt_r)
1909     {
1910       RpmInstallReport::Action user = report->problem( excpt_r );
1911
1912       if ( user == RpmInstallReport::ABORT )
1913       {
1914         report->finish( excpt_r );
1915         ZYPP_RETHROW(excpt_r);
1916       }
1917       else if ( user == RpmInstallReport::IGNORE )
1918       {
1919         break;
1920       }
1921     }
1922   while (true);
1923 }
1924
1925 void RpmDb::doInstallPackage( const Pathname & filename, RpmInstFlags flags, callback::SendReport<RpmInstallReport> & report )
1926 {
1927   FAILIFNOTINITIALIZED;
1928   HistoryLog historylog;
1929
1930   MIL << "RpmDb::installPackage(" << filename << "," << flags << ")" << endl;
1931
1932
1933   // backup
1934   if ( _packagebackups )
1935   {
1936     // FIXME      report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
1937     if ( ! backupPackage( filename ) )
1938     {
1939       ERR << "backup of " << filename.asString() << " failed" << endl;
1940     }
1941     // FIXME status handling
1942     report->progress( 0 ); // allow 1% for backup creation.
1943   }
1944
1945   // run rpm
1946   RpmArgVec opts;
1947   if (flags & RPMINST_NOUPGRADE)
1948     opts.push_back("-i");
1949   else
1950     opts.push_back("-U");
1951
1952   opts.push_back("--percent");
1953   opts.push_back("--noglob");
1954
1955   // ZConfig defines cross-arch installation
1956   if ( ! ZConfig::instance().systemArchitecture().compatibleWith( ZConfig::instance().defaultSystemArchitecture() ) )
1957     opts.push_back("--ignorearch");
1958
1959   if (flags & RPMINST_NODIGEST)
1960     opts.push_back("--nodigest");
1961   if (flags & RPMINST_NOSIGNATURE)
1962     opts.push_back("--nosignature");
1963   if (flags & RPMINST_EXCLUDEDOCS)
1964     opts.push_back ("--excludedocs");
1965   if (flags & RPMINST_NOSCRIPTS)
1966     opts.push_back ("--noscripts");
1967   if (flags & RPMINST_FORCE)
1968     opts.push_back ("--force");
1969   if (flags & RPMINST_NODEPS)
1970     opts.push_back ("--nodeps");
1971   if (flags & RPMINST_IGNORESIZE)
1972     opts.push_back ("--ignoresize");
1973   if (flags & RPMINST_JUSTDB)
1974     opts.push_back ("--justdb");
1975   if (flags & RPMINST_TEST)
1976     opts.push_back ("--test");
1977   if (flags & RPMINST_NOPOSTTRANS)
1978     opts.push_back ("--noposttrans");
1979
1980   opts.push_back("--");
1981
1982   // rpm requires additional quoting of special chars:
1983   string quotedFilename( rpmQuoteFilename( workaroundRpmPwdBug( filename ) ) );
1984   opts.push_back ( quotedFilename.c_str() );
1985
1986   modifyDatabase(); // BEFORE run_rpm
1987   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
1988
1989   string line;
1990   string rpmmsg;
1991   vector<string> configwarnings;
1992
1993   unsigned linecnt = 0;
1994   while (systemReadLine(line))
1995   {
1996     if ( linecnt < MAXRPMMESSAGELINES )
1997       ++linecnt;
1998     else
1999       continue;
2000
2001     if (line.substr(0,2)=="%%")
2002     {
2003       int percent;
2004       sscanf (line.c_str () + 2, "%d", &percent);
2005       report->progress( percent );
2006     }
2007     else
2008       rpmmsg += line+'\n';
2009
2010     if ( line.substr(0,8) == "warning:" )
2011     {
2012       configwarnings.push_back(line);
2013     }
2014   }
2015   if ( linecnt > MAXRPMMESSAGELINES )
2016     rpmmsg += "[truncated]\n";
2017
2018   int rpm_status = systemStatus();
2019
2020   // evaluate result
2021   for (vector<string>::iterator it = configwarnings.begin();
2022        it != configwarnings.end(); ++it)
2023   {
2024     processConfigFiles(*it, Pathname::basename(filename), " saved as ",
2025                        // %s = filenames
2026                        _("rpm saved %s as %s, but it was impossible to determine the difference"),
2027                        // %s = filenames
2028                        _("rpm saved %s as %s.\nHere are the first 25 lines of difference:\n"));
2029     processConfigFiles(*it, Pathname::basename(filename), " created as ",
2030                        // %s = filenames
2031                        _("rpm created %s as %s, but it was impossible to determine the difference"),
2032                        // %s = filenames
2033                        _("rpm created %s as %s.\nHere are the first 25 lines of difference:\n"));
2034   }
2035
2036   if ( rpm_status != 0 )
2037   {
2038     historylog.comment(
2039         str::form("%s install failed", Pathname::basename(filename).c_str()),
2040         true /*timestamp*/);
2041     ostringstream sstr;
2042     sstr << "rpm output:" << endl << rpmmsg << endl;
2043     historylog.comment(sstr.str());
2044     // TranslatorExplanation the colon is followed by an error message
2045     ZYPP_THROW(RpmSubprocessException(string(_("RPM failed: ")) +
2046                (rpmmsg.empty() ? error_message : rpmmsg)));
2047   }
2048   else if ( ! rpmmsg.empty() )
2049   {
2050     historylog.comment(
2051         str::form("%s installed ok", Pathname::basename(filename).c_str()),
2052         true /*timestamp*/);
2053     ostringstream sstr;
2054     sstr << "Additional rpm output:" << endl << rpmmsg << endl;
2055     historylog.comment(sstr.str());
2056
2057     // report additional rpm output in finish
2058     // TranslatorExplanation Text is followed by a ':'  and the actual output.
2059     report->finishInfo(str::form( "%s:\n%s\n", _("Additional rpm output"),  rpmmsg.c_str() ));
2060   }
2061 }
2062
2063 ///////////////////////////////////////////////////////////////////
2064 //
2065 //
2066 //      METHOD NAME : RpmDb::removePackage
2067 //      METHOD TYPE : PMError
2068 //
2069 void RpmDb::removePackage( Package::constPtr package, RpmInstFlags flags )
2070 {
2071   // 'rpm -e' does not like epochs
2072   return removePackage( package->name()
2073                         + "-" + package->edition().version()
2074                         + "-" + package->edition().release()
2075                         + "." + package->arch().asString(), flags );
2076 }
2077
2078 ///////////////////////////////////////////////////////////////////
2079 //
2080 //
2081 //      METHOD NAME : RpmDb::removePackage
2082 //      METHOD TYPE : PMError
2083 //
2084 void RpmDb::removePackage( const string & name_r, RpmInstFlags flags )
2085 {
2086   callback::SendReport<RpmRemoveReport> report;
2087
2088   report->start( name_r );
2089
2090   do
2091     try
2092     {
2093       doRemovePackage(name_r, flags, report);
2094       report->finish();
2095       break;
2096     }
2097     catch (RpmException & excpt_r)
2098     {
2099       RpmRemoveReport::Action user = report->problem( excpt_r );
2100
2101       if ( user == RpmRemoveReport::ABORT )
2102       {
2103         report->finish( excpt_r );
2104         ZYPP_RETHROW(excpt_r);
2105       }
2106       else if ( user == RpmRemoveReport::IGNORE )
2107       {
2108         break;
2109       }
2110     }
2111   while (true);
2112 }
2113
2114
2115 void RpmDb::doRemovePackage( const string & name_r, RpmInstFlags flags, callback::SendReport<RpmRemoveReport> & report )
2116 {
2117   FAILIFNOTINITIALIZED;
2118   HistoryLog historylog;
2119
2120   MIL << "RpmDb::doRemovePackage(" << name_r << "," << flags << ")" << endl;
2121
2122   // backup
2123   if ( _packagebackups )
2124   {
2125     // FIXME solve this status report somehow
2126     //      report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
2127     if ( ! backupPackage( name_r ) )
2128     {
2129       ERR << "backup of " << name_r << " failed" << endl;
2130     }
2131     report->progress( 0 );
2132   }
2133   else
2134   {
2135     report->progress( 100 );
2136   }
2137
2138   // run rpm
2139   RpmArgVec opts;
2140   opts.push_back("-e");
2141   opts.push_back("--allmatches");
2142
2143   if (flags & RPMINST_NOSCRIPTS)
2144     opts.push_back("--noscripts");
2145   if (flags & RPMINST_NODEPS)
2146     opts.push_back("--nodeps");
2147   if (flags & RPMINST_JUSTDB)
2148     opts.push_back("--justdb");
2149   if (flags & RPMINST_TEST)
2150     opts.push_back ("--test");
2151   if (flags & RPMINST_FORCE)
2152   {
2153     WAR << "IGNORE OPTION: 'rpm -e' does not support '--force'" << endl;
2154   }
2155
2156   opts.push_back("--");
2157   opts.push_back(name_r.c_str());
2158
2159   modifyDatabase(); // BEFORE run_rpm
2160   run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
2161
2162   string line;
2163   string rpmmsg;
2164
2165   // got no progress from command, so we fake it:
2166   // 5  - command started
2167   // 50 - command completed
2168   // 100 if no error
2169   report->progress( 5 );
2170   unsigned linecnt = 0;
2171   while (systemReadLine(line))
2172   {
2173     if ( linecnt < MAXRPMMESSAGELINES )
2174       ++linecnt;
2175     else
2176       continue;
2177     rpmmsg += line+'\n';
2178   }
2179   if ( linecnt > MAXRPMMESSAGELINES )
2180     rpmmsg += "[truncated]\n";
2181   report->progress( 50 );
2182   int rpm_status = systemStatus();
2183
2184   if ( rpm_status != 0 )
2185   {
2186     historylog.comment(
2187         str::form("%s remove failed", name_r.c_str()), true /*timestamp*/);
2188     ostringstream sstr;
2189     sstr << "rpm output:" << endl << rpmmsg << endl;
2190     historylog.comment(sstr.str());
2191     // TranslatorExplanation the colon is followed by an error message
2192     ZYPP_THROW(RpmSubprocessException(string(_("RPM failed: ")) +
2193                (rpmmsg.empty() ? error_message: rpmmsg)));
2194   }
2195   else if ( ! rpmmsg.empty() )
2196   {
2197     historylog.comment(
2198         str::form("%s removed ok", name_r.c_str()), true /*timestamp*/);
2199
2200     ostringstream sstr;
2201     sstr << "Additional rpm output:" << endl << rpmmsg << endl;
2202     historylog.comment(sstr.str());
2203
2204     // report additional rpm output in finish
2205     // TranslatorExplanation Text is followed by a ':'  and the actual output.
2206     report->finishInfo(str::form( "%s:\n%s\n", _("Additional rpm output"),  rpmmsg.c_str() ));
2207   }
2208 }
2209
2210 ///////////////////////////////////////////////////////////////////
2211 //
2212 //
2213 //      METHOD NAME : RpmDb::backupPackage
2214 //      METHOD TYPE : bool
2215 //
2216 bool RpmDb::backupPackage( const Pathname & filename )
2217 {
2218   RpmHeader::constPtr h( RpmHeader::readPackage( filename, RpmHeader::NOSIGNATURE ) );
2219   if ( ! h )
2220     return false;
2221
2222   return backupPackage( h->tag_name() );
2223 }
2224
2225 ///////////////////////////////////////////////////////////////////
2226 //
2227 //
2228 //      METHOD NAME : RpmDb::backupPackage
2229 //      METHOD TYPE : bool
2230 //
2231 bool RpmDb::backupPackage(const string& packageName)
2232 {
2233   HistoryLog progresslog;
2234   bool ret = true;
2235   Pathname backupFilename;
2236   Pathname filestobackupfile = _root+_backuppath+FILEFORBACKUPFILES;
2237
2238   if (_backuppath.empty())
2239   {
2240     INT << "_backuppath empty" << endl;
2241     return false;
2242   }
2243
2244   FileList fileList;
2245
2246   if (!queryChangedFiles(fileList, packageName))
2247   {
2248     ERR << "Error while getting changed files for package " <<
2249     packageName << endl;
2250     return false;
2251   }
2252
2253   if (fileList.size() <= 0)
2254   {
2255     DBG <<  "package " <<  packageName << " not changed -> no backup" << endl;
2256     return true;
2257   }
2258
2259   if (filesystem::assert_dir(_root + _backuppath) != 0)
2260   {
2261     return false;
2262   }
2263
2264   {
2265     // build up archive name
2266     time_t currentTime = time(0);
2267     struct tm *currentLocalTime = localtime(&currentTime);
2268
2269     int date = (currentLocalTime->tm_year + 1900) * 10000
2270                + (currentLocalTime->tm_mon + 1) * 100
2271                + currentLocalTime->tm_mday;
2272
2273     int num = 0;
2274     do
2275     {
2276       backupFilename = _root + _backuppath
2277                        + str::form("%s-%d-%d.tar.gz",packageName.c_str(), date, num);
2278
2279     }
2280     while ( PathInfo(backupFilename).isExist() && num++ < 1000);
2281
2282     PathInfo pi(filestobackupfile);
2283     if (pi.isExist() && !pi.isFile())
2284     {
2285       ERR << filestobackupfile.asString() << " already exists and is no file" << endl;
2286       return false;
2287     }
2288
2289     ofstream fp ( filestobackupfile.asString().c_str(), ios::out|ios::trunc );
2290
2291     if (!fp)
2292     {
2293       ERR << "could not open " << filestobackupfile.asString() << endl;
2294       return false;
2295     }
2296
2297     for (FileList::const_iterator cit = fileList.begin();
2298          cit != fileList.end(); ++cit)
2299     {
2300       string name = *cit;
2301       if ( name[0] == '/' )
2302       {
2303         // remove slash, file must be relative to -C parameter of tar
2304         name = name.substr( 1 );
2305       }
2306       DBG << "saving file "<< name << endl;
2307       fp << name << endl;
2308     }
2309     fp.close();
2310
2311     const char* const argv[] =
2312       {
2313         "tar",
2314         "-czhP",
2315         "-C",
2316         _root.asString().c_str(),
2317         "--ignore-failed-read",
2318         "-f",
2319         backupFilename.asString().c_str(),
2320         "-T",
2321         filestobackupfile.asString().c_str(),
2322         NULL
2323       };
2324
2325     // execute tar in inst-sys (we dont know if there is a tar below _root !)
2326     ExternalProgram tar(argv, ExternalProgram::Stderr_To_Stdout, false, -1, true);
2327
2328     string tarmsg;
2329
2330     // TODO: its probably possible to start tar with -v and watch it adding
2331     // files to report progress
2332     for (string output = tar.receiveLine(); output.length() ;output = tar.receiveLine())
2333     {
2334       tarmsg+=output;
2335     }
2336
2337     int ret = tar.close();
2338
2339     if ( ret != 0)
2340     {
2341       ERR << "tar failed: " << tarmsg << endl;
2342       ret = false;
2343     }
2344     else
2345     {
2346       MIL << "tar backup ok" << endl;
2347       progresslog.comment(
2348           str::form(_("created backup %s"), backupFilename.asString().c_str())
2349           , /*timestamp*/true);
2350     }
2351
2352     filesystem::unlink(filestobackupfile);
2353   }
2354
2355   return ret;
2356 }
2357
2358 void RpmDb::setBackupPath(const Pathname& path)
2359 {
2360   _backuppath = path;
2361 }
2362
2363 std::ostream & operator<<( std::ostream & str, RpmDb::CheckPackageResult obj )
2364 {
2365   switch ( obj )
2366   {
2367 #define OUTS(E,S) case RpmDb::E: return str << "["<< (unsigned)obj << "-"<< S << "]"; break
2368     // translators: possible rpm package signature check result [brief]
2369     OUTS( CHK_OK,               _("Signature is OK") );
2370     // translators: possible rpm package signature check result [brief]
2371     OUTS( CHK_NOTFOUND,         _("Unknown type of signature") );
2372     // translators: possible rpm package signature check result [brief]
2373     OUTS( CHK_FAIL,             _("Signature does not verify") );
2374     // translators: possible rpm package signature check result [brief]
2375     OUTS( CHK_NOTTRUSTED,       _("Signature is OK, but key is not trusted") );
2376     // translators: possible rpm package signature check result [brief]
2377     OUTS( CHK_NOKEY,            _("Signatures public key is not available") );
2378     // translators: possible rpm package signature check result [brief]
2379     OUTS( CHK_ERROR,            _("File does not exist or signature can't be checked") );
2380 #undef OUTS
2381   }
2382   return str << "UnknowSignatureCheckError("+str::numstring(obj)+")";
2383 }
2384
2385 std::ostream & operator<<( std::ostream & str, const RpmDb::CheckPackageDetail & obj )
2386 {
2387   for ( const auto & el : obj )
2388     str << el.second << endl;
2389   return str;
2390 }
2391
2392 } // namespace rpm
2393 } // namespace target
2394 } // namespace zypp