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