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