d84be1a15e7640155dd55520d803e8b6276c544d
[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     qva.qva_flags = (VERIFY_DIGEST|VERIFY_SIGNATURE);
1563
1564     RpmlogCapture vresult;
1565     LocaleGuard guard( LC_ALL, "C" );   // bsc#1076415: rpm log output is localized, but we need to parse it :(
1566     int res = ::rpmVerifySignatures( &qva, ts, fd, path_r.basename().c_str() );
1567     guard.restore();
1568
1569     ts = rpmtsFree(ts);
1570     ::Fclose( fd );
1571
1572     // results per line...
1573     //     Header V3 RSA/SHA256 Signature, key ID 3dbdc284: OK
1574     //     Header SHA1 digest: OK (a60386347863affefef484ff1f26c889373eb094)
1575     //     V3 RSA/SHA256 Signature, key ID 3dbdc284: OK
1576     //     MD5 digest: OK (fd5259fe677a406951dcb2e9d08c4dcc)
1577     //
1578     // TODO: try to get SIG info from the header rather than parsing the output
1579     std::vector<std::string> lines;
1580     str::split( vresult, std::back_inserter(lines), "\n" );
1581     unsigned count[7] = { 0, 0, 0, 0, 0, 0, 0 };
1582
1583     for ( unsigned i = 1; i < lines.size(); ++i )
1584     {
1585       std::string & line( lines[i] );
1586       RpmDb::CheckPackageResult lineres = RpmDb::CHK_ERROR;
1587       if ( line.find( ": OK" ) != std::string::npos )
1588       {
1589         lineres = RpmDb::CHK_OK;
1590         if ( line.find( "Signature, key ID" ) == std::string::npos )
1591           ++count[RpmDb::CHK_NOSIG];    // Valid but no gpg signature -> CHK_NOSIG
1592       }
1593       else if ( line.find( ": NOKEY" ) != std::string::npos )
1594       { lineres = RpmDb::CHK_NOKEY; }
1595       else if ( line.find( ": BAD" ) != std::string::npos )
1596       { lineres = RpmDb::CHK_FAIL; }
1597       else if ( line.find( ": UNKNOWN" ) != std::string::npos )
1598       { lineres = RpmDb::CHK_NOTFOUND; }
1599       else if ( line.find( ": NOTRUSTED" ) != std::string::npos )
1600       { lineres = RpmDb::CHK_NOTTRUSTED; }
1601
1602       ++count[lineres];
1603       detail_r.push_back( RpmDb::CheckPackageDetail::value_type( lineres, std::move(line) ) );
1604     }
1605
1606     RpmDb::CheckPackageResult ret = ( res ? RpmDb::CHK_ERROR : RpmDb::CHK_OK );
1607
1608     if ( count[RpmDb::CHK_FAIL] )
1609       ret = RpmDb::CHK_FAIL;
1610
1611     else if ( count[RpmDb::CHK_NOTFOUND] )
1612       ret = RpmDb::CHK_NOTFOUND;
1613
1614     else if ( count[RpmDb::CHK_NOKEY] )
1615       ret = RpmDb::CHK_NOKEY;
1616
1617     else if ( count[RpmDb::CHK_NOTTRUSTED] )
1618       ret = RpmDb::CHK_NOTTRUSTED;
1619
1620     else if ( ret == RpmDb::CHK_OK )
1621     {
1622       if ( count[RpmDb::CHK_OK] == count[RpmDb::CHK_NOSIG]  )
1623       {
1624         detail_r.push_back( RpmDb::CheckPackageDetail::value_type( RpmDb::CHK_NOSIG, std::string("    ")+_("Package is not signed!") ) );
1625         if ( requireGPGSig_r )
1626           ret = RpmDb::CHK_NOSIG;
1627       }
1628     }
1629
1630     if ( ret != RpmDb::CHK_OK )
1631     {
1632       WAR << path_r << " (" << requireGPGSig_r << " -> " << ret << ")" << endl;
1633       WAR << vresult;
1634     }
1635     return ret;
1636   }
1637
1638 } // namespace
1639 ///////////////////////////////////////////////////////////////////
1640 //
1641 //      METHOD NAME : RpmDb::checkPackage
1642 //      METHOD TYPE : RpmDb::CheckPackageResult
1643 //
1644 RpmDb::CheckPackageResult RpmDb::checkPackage( const Pathname & path_r, CheckPackageDetail & detail_r )
1645 { return doCheckPackageSig( path_r, root(), false/*requireGPGSig_r*/, detail_r ); }
1646
1647 RpmDb::CheckPackageResult RpmDb::checkPackage( const Pathname & path_r )
1648 { CheckPackageDetail dummy; return checkPackage( path_r, dummy ); }
1649
1650 RpmDb::CheckPackageResult RpmDb::checkPackageSignature( const Pathname & path_r, RpmDb::CheckPackageDetail & detail_r )
1651 { return doCheckPackageSig( path_r, root(), true/*requireGPGSig_r*/, detail_r ); }
1652
1653
1654 // determine changed files of installed package
1655 bool
1656 RpmDb::queryChangedFiles(FileList & fileList, const std::string& packageName)
1657 {
1658   bool ok = true;
1659
1660   fileList.clear();
1661
1662   if ( ! initialized() ) return false;
1663
1664   RpmArgVec opts;
1665
1666   opts.push_back ("-V");
1667   opts.push_back ("--nodeps");
1668   opts.push_back ("--noscripts");
1669   opts.push_back ("--nomd5");
1670   opts.push_back ("--");
1671   opts.push_back (packageName.c_str());
1672
1673   run_rpm (opts, ExternalProgram::Discard_Stderr);
1674
1675   if ( process == NULL )
1676     return false;
1677
1678   /* from rpm manpage
1679    5      MD5 sum
1680    S      File size
1681    L      Symlink
1682    T      Mtime
1683    D      Device
1684    U      User
1685    G      Group
1686    M      Mode (includes permissions and file type)
1687   */
1688
1689   std::string line;
1690   while (systemReadLine(line))
1691   {
1692     if (line.length() > 12 &&
1693         (line[0] == 'S' || line[0] == 's' ||
1694          (line[0] == '.' && line[7] == 'T')))
1695     {
1696       // file has been changed
1697       std::string filename;
1698
1699       filename.assign(line, 11, line.length() - 11);
1700       fileList.insert(filename);
1701     }
1702   }
1703
1704   systemStatus();
1705   // exit code ignored, rpm returns 1 no matter if package is installed or
1706   // not
1707
1708   return ok;
1709 }
1710
1711
1712
1713 /****************************************************************/
1714 /* private member-functions                                     */
1715 /****************************************************************/
1716
1717 /*--------------------------------------------------------------*/
1718 /* Run rpm with the specified arguments, handling stderr        */
1719 /* as specified  by disp                                        */
1720 /*--------------------------------------------------------------*/
1721 void
1722 RpmDb::run_rpm (const RpmArgVec& opts,
1723                 ExternalProgram::Stderr_Disposition disp)
1724 {
1725   if ( process )
1726   {
1727     delete process;
1728     process = NULL;
1729   }
1730   exit_code = -1;
1731
1732   if ( ! initialized() )
1733   {
1734     ZYPP_THROW(RpmDbNotOpenException());
1735   }
1736
1737   RpmArgVec args;
1738
1739   // always set root and dbpath
1740 #if defined(WORKAROUNDRPMPWDBUG)
1741   args.push_back("#/");         // chdir to / to workaround bnc#819354
1742 #endif
1743   args.push_back("rpm");
1744   args.push_back("--root");
1745   args.push_back(_root.asString().c_str());
1746   args.push_back("--dbpath");
1747   args.push_back(_dbPath.asString().c_str());
1748
1749   const char* argv[args.size() + opts.size() + 1];
1750
1751   const char** p = argv;
1752   p = copy (args.begin (), args.end (), p);
1753   p = copy (opts.begin (), opts.end (), p);
1754   *p = 0;
1755
1756   // Invalidate all outstanding database handles in case
1757   // the database gets modified.
1758   librpmDb::dbRelease( true );
1759
1760   // Launch the program with default locale
1761   process = new ExternalProgram(argv, disp, false, -1, true);
1762   return;
1763 }
1764
1765 /*--------------------------------------------------------------*/
1766 /* Read a line from the rpm process                             */
1767 /*--------------------------------------------------------------*/
1768 bool RpmDb::systemReadLine( std::string & line )
1769 {
1770   line.erase();
1771
1772   if ( process == NULL )
1773     return false;
1774
1775   if ( process->inputFile() )
1776   {
1777     process->setBlocking( false );
1778     FILE * inputfile = process->inputFile();
1779     int    inputfileFd = ::fileno( inputfile );
1780     do
1781     {
1782       /* Watch inputFile to see when it has input. */
1783       fd_set rfds;
1784       FD_ZERO( &rfds );
1785       FD_SET( inputfileFd, &rfds );
1786
1787       /* Wait up to 5 seconds. */
1788       struct timeval tv;
1789       tv.tv_sec = 5;
1790       tv.tv_usec = 0;
1791
1792       int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
1793
1794       if ( retval == -1 )
1795       {
1796         ERR << "select error: " << strerror(errno) << endl;
1797         if ( errno != EINTR )
1798           return false;
1799       }
1800       else if ( retval )
1801       {
1802         // Data is available now.
1803         static size_t linebuffer_size = 0;      // static because getline allocs
1804         static char * linebuffer = 0;           // and reallocs if buffer is too small
1805         ssize_t nread = getline( &linebuffer, &linebuffer_size, inputfile );
1806         if ( nread == -1 )
1807         {
1808           if ( ::feof( inputfile ) )
1809             return line.size(); // in case of pending output
1810         }
1811         else
1812         {
1813           if ( nread > 0 )
1814           {
1815             if ( linebuffer[nread-1] == '\n' )
1816               --nread;
1817             line += std::string( linebuffer, nread );
1818           }
1819
1820           if ( ! ::ferror( inputfile ) || ::feof( inputfile ) )
1821             return true; // complete line
1822         }
1823         clearerr( inputfile );
1824       }
1825       else
1826       {
1827         // No data within time.
1828         if ( ! process->running() )
1829           return false;
1830       }
1831     } while ( true );
1832   }
1833
1834   return false;
1835 }
1836
1837 /*--------------------------------------------------------------*/
1838 /* Return the exit status of the rpm process, closing the       */
1839 /* connection if not already done                               */
1840 /*--------------------------------------------------------------*/
1841 int
1842 RpmDb::systemStatus()
1843 {
1844   if ( process == NULL )
1845     return -1;
1846
1847   exit_code = process->close();
1848   if (exit_code == 0)
1849     error_message = "";
1850   else
1851     error_message = process->execError();
1852   process->kill();
1853   delete process;
1854   process = 0;
1855
1856   //   DBG << "exit code " << exit_code << endl;
1857
1858   return exit_code;
1859 }
1860
1861 /*--------------------------------------------------------------*/
1862 /* Forcably kill the rpm process                                */
1863 /*--------------------------------------------------------------*/
1864 void
1865 RpmDb::systemKill()
1866 {
1867   if (process) process->kill();
1868 }
1869
1870
1871 // generate diff mails for config files
1872 void RpmDb::processConfigFiles(const std::string& line, const std::string& name, const char* typemsg, const char* difffailmsg, const char* diffgenmsg)
1873 {
1874   std::string msg = line.substr(9);
1875   std::string::size_type pos1 = std::string::npos;
1876   std::string::size_type pos2 = std::string::npos;
1877   std::string file1s, file2s;
1878   Pathname file1;
1879   Pathname file2;
1880
1881   pos1 = msg.find (typemsg);
1882   for (;;)
1883   {
1884     if ( pos1 == std::string::npos )
1885       break;
1886
1887     pos2 = pos1 + strlen (typemsg);
1888
1889     if (pos2 >= msg.length() )
1890       break;
1891
1892     file1 = msg.substr (0, pos1);
1893     file2 = msg.substr (pos2);
1894
1895     file1s = file1.asString();
1896     file2s = file2.asString();
1897
1898     if (!_root.empty() && _root != "/")
1899     {
1900       file1 = _root + file1;
1901       file2 = _root + file2;
1902     }
1903
1904     std::string out;
1905     int ret = diffFiles (file1.asString(), file2.asString(), out, 25);
1906     if (ret)
1907     {
1908       Pathname file = _root + WARNINGMAILPATH;
1909       if (filesystem::assert_dir(file) != 0)
1910       {
1911         ERR << "Could not create " << file.asString() << endl;
1912         break;
1913       }
1914       file += Date(Date::now()).form("config_diff_%Y_%m_%d.log");
1915       std::ofstream notify(file.asString().c_str(), std::ios::out|std::ios::app);
1916       if (!notify)
1917       {
1918         ERR << "Could not open " <<  file << endl;
1919         break;
1920       }
1921
1922       // Translator: %s = name of an rpm package. A list of diffs follows
1923       // this message.
1924       notify << str::form(_("Changed configuration files for %s:"), name.c_str()) << endl;
1925       if (ret>1)
1926       {
1927         ERR << "diff failed" << endl;
1928         notify << str::form(difffailmsg,
1929                             file1s.c_str(), file2s.c_str()) << endl;
1930       }
1931       else
1932       {
1933         notify << str::form(diffgenmsg,
1934                             file1s.c_str(), file2s.c_str()) << endl;
1935
1936         // remove root for the viewer's pleasure (#38240)
1937         if (!_root.empty() && _root != "/")
1938         {
1939           if (out.substr(0,4) == "--- ")
1940           {
1941             out.replace(4, file1.asString().length(), file1s);
1942           }
1943           std::string::size_type pos = out.find("\n+++ ");
1944           if (pos != std::string::npos)
1945           {
1946             out.replace(pos+5, file2.asString().length(), file2s);
1947           }
1948         }
1949         notify << out << endl;
1950       }
1951       notify.close();
1952       notify.open("/var/lib/update-messages/yast2-packagemanager.rpmdb.configfiles");
1953       notify.close();
1954     }
1955     else
1956     {
1957       WAR << "rpm created " << file2 << " but it is not different from " << file2 << endl;
1958     }
1959     break;
1960   }
1961 }
1962
1963 ///////////////////////////////////////////////////////////////////
1964 //
1965 //
1966 //      METHOD NAME : RpmDb::installPackage
1967 //      METHOD TYPE : PMError
1968 //
1969 void RpmDb::installPackage( const Pathname & filename, RpmInstFlags flags )
1970 {
1971   callback::SendReport<RpmInstallReport> report;
1972
1973   report->start(filename);
1974
1975   do
1976     try
1977     {
1978       doInstallPackage(filename, flags, report);
1979       report->finish();
1980       break;
1981     }
1982     catch (RpmException & excpt_r)
1983     {
1984       RpmInstallReport::Action user = report->problem( excpt_r );
1985
1986       if ( user == RpmInstallReport::ABORT )
1987       {
1988         report->finish( excpt_r );
1989         ZYPP_RETHROW(excpt_r);
1990       }
1991       else if ( user == RpmInstallReport::IGNORE )
1992       {
1993         break;
1994       }
1995     }
1996   while (true);
1997 }
1998
1999 void RpmDb::doInstallPackage( const Pathname & filename, RpmInstFlags flags, callback::SendReport<RpmInstallReport> & report )
2000 {
2001   FAILIFNOTINITIALIZED;
2002   HistoryLog historylog;
2003
2004   MIL << "RpmDb::installPackage(" << filename << "," << flags << ")" << endl;
2005
2006
2007   // backup
2008   if ( _packagebackups )
2009   {
2010     // FIXME      report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
2011     if ( ! backupPackage( filename ) )
2012     {
2013       ERR << "backup of " << filename.asString() << " failed" << endl;
2014     }
2015     // FIXME status handling
2016     report->progress( 0 ); // allow 1% for backup creation.
2017   }
2018
2019   // run rpm
2020   RpmArgVec opts;
2021   if (flags & RPMINST_NOUPGRADE)
2022     opts.push_back("-i");
2023   else
2024     opts.push_back("-U");
2025
2026   opts.push_back("--percent");
2027   opts.push_back("--noglob");
2028
2029   // ZConfig defines cross-arch installation
2030   if ( ! ZConfig::instance().systemArchitecture().compatibleWith( ZConfig::instance().defaultSystemArchitecture() ) )
2031     opts.push_back("--ignorearch");
2032
2033   if (flags & RPMINST_NODIGEST)
2034     opts.push_back("--nodigest");
2035   if (flags & RPMINST_NOSIGNATURE)
2036     opts.push_back("--nosignature");
2037   if (flags & RPMINST_EXCLUDEDOCS)
2038     opts.push_back ("--excludedocs");
2039   if (flags & RPMINST_NOSCRIPTS)
2040     opts.push_back ("--noscripts");
2041   if (flags & RPMINST_FORCE)
2042     opts.push_back ("--force");
2043   if (flags & RPMINST_NODEPS)
2044     opts.push_back ("--nodeps");
2045   if (flags & RPMINST_IGNORESIZE)
2046     opts.push_back ("--ignoresize");
2047   if (flags & RPMINST_JUSTDB)
2048     opts.push_back ("--justdb");
2049   if (flags & RPMINST_TEST)
2050     opts.push_back ("--test");
2051   if (flags & RPMINST_NOPOSTTRANS)
2052     opts.push_back ("--noposttrans");
2053
2054   opts.push_back("--");
2055
2056   // rpm requires additional quoting of special chars:
2057   std::string quotedFilename( rpmQuoteFilename( workaroundRpmPwdBug( filename ) ) );
2058   opts.push_back ( quotedFilename.c_str() );
2059
2060   modifyDatabase(); // BEFORE run_rpm
2061   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
2062
2063   std::string line;
2064   std::string rpmmsg;                           // TODO: immediately forward lines via Callback::report rather than collecting
2065   std::vector<std::string> configwarnings;      // TODO: immediately process lines rather than collecting
2066
2067   unsigned linecnt = 0;
2068   while ( systemReadLine( line ) )
2069   {
2070     if ( str::startsWith( line, "%%" ) )
2071     {
2072       int percent;
2073       sscanf( line.c_str() + 2, "%d", &percent );
2074       report->progress( percent );
2075       continue;
2076     }
2077
2078     if ( linecnt < MAXRPMMESSAGELINES )
2079       ++linecnt;
2080     else if ( line.find( " scriptlet failed, " ) == std::string::npos ) // always log %script errors
2081       continue;
2082
2083     rpmmsg += line+'\n';
2084
2085     if ( str::startsWith( line, "warning:" ) )
2086       configwarnings.push_back(line);
2087   }
2088   if ( linecnt >= MAXRPMMESSAGELINES )
2089     rpmmsg += "[truncated]\n";
2090
2091   int rpm_status = systemStatus();
2092
2093   // evaluate result
2094   for (std::vector<std::string>::iterator it = configwarnings.begin();
2095        it != configwarnings.end(); ++it)
2096   {
2097     processConfigFiles(*it, Pathname::basename(filename), " saved as ",
2098                        // %s = filenames
2099                        _("rpm saved %s as %s, but it was impossible to determine the difference"),
2100                        // %s = filenames
2101                        _("rpm saved %s as %s.\nHere are the first 25 lines of difference:\n"));
2102     processConfigFiles(*it, Pathname::basename(filename), " created as ",
2103                        // %s = filenames
2104                        _("rpm created %s as %s, but it was impossible to determine the difference"),
2105                        // %s = filenames
2106                        _("rpm created %s as %s.\nHere are the first 25 lines of difference:\n"));
2107   }
2108
2109   if ( rpm_status != 0 )
2110   {
2111     historylog.comment(
2112         str::form("%s install failed", Pathname::basename(filename).c_str()),
2113         true /*timestamp*/);
2114     std::ostringstream sstr;
2115     sstr << "rpm output:" << endl << rpmmsg << endl;
2116     historylog.comment(sstr.str());
2117     // TranslatorExplanation the colon is followed by an error message
2118     ZYPP_THROW(RpmSubprocessException(_("RPM failed: ") + (rpmmsg.empty() ? error_message : rpmmsg) ));
2119   }
2120   else if ( ! rpmmsg.empty() )
2121   {
2122     historylog.comment(
2123         str::form("%s installed ok", Pathname::basename(filename).c_str()),
2124         true /*timestamp*/);
2125     std::ostringstream sstr;
2126     sstr << "Additional rpm output:" << endl << rpmmsg << endl;
2127     historylog.comment(sstr.str());
2128
2129     // report additional rpm output in finish
2130     // TranslatorExplanation Text is followed by a ':'  and the actual output.
2131     report->finishInfo(str::form( "%s:\n%s\n", _("Additional rpm output"),  rpmmsg.c_str() ));
2132   }
2133 }
2134
2135 ///////////////////////////////////////////////////////////////////
2136 //
2137 //
2138 //      METHOD NAME : RpmDb::removePackage
2139 //      METHOD TYPE : PMError
2140 //
2141 void RpmDb::removePackage( Package::constPtr package, RpmInstFlags flags )
2142 {
2143   // 'rpm -e' does not like epochs
2144   return removePackage( package->name()
2145                         + "-" + package->edition().version()
2146                         + "-" + package->edition().release()
2147                         + "." + package->arch().asString(), flags );
2148 }
2149
2150 ///////////////////////////////////////////////////////////////////
2151 //
2152 //
2153 //      METHOD NAME : RpmDb::removePackage
2154 //      METHOD TYPE : PMError
2155 //
2156 void RpmDb::removePackage( const std::string & name_r, RpmInstFlags flags )
2157 {
2158   callback::SendReport<RpmRemoveReport> report;
2159
2160   report->start( name_r );
2161
2162   do
2163     try
2164     {
2165       doRemovePackage(name_r, flags, report);
2166       report->finish();
2167       break;
2168     }
2169     catch (RpmException & excpt_r)
2170     {
2171       RpmRemoveReport::Action user = report->problem( excpt_r );
2172
2173       if ( user == RpmRemoveReport::ABORT )
2174       {
2175         report->finish( excpt_r );
2176         ZYPP_RETHROW(excpt_r);
2177       }
2178       else if ( user == RpmRemoveReport::IGNORE )
2179       {
2180         break;
2181       }
2182     }
2183   while (true);
2184 }
2185
2186
2187 void RpmDb::doRemovePackage( const std::string & name_r, RpmInstFlags flags, callback::SendReport<RpmRemoveReport> & report )
2188 {
2189   FAILIFNOTINITIALIZED;
2190   HistoryLog historylog;
2191
2192   MIL << "RpmDb::doRemovePackage(" << name_r << "," << flags << ")" << endl;
2193
2194   // backup
2195   if ( _packagebackups )
2196   {
2197     // FIXME solve this status report somehow
2198     //      report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
2199     if ( ! backupPackage( name_r ) )
2200     {
2201       ERR << "backup of " << name_r << " failed" << endl;
2202     }
2203     report->progress( 0 );
2204   }
2205   else
2206   {
2207     report->progress( 100 );
2208   }
2209
2210   // run rpm
2211   RpmArgVec opts;
2212   opts.push_back("-e");
2213   opts.push_back("--allmatches");
2214
2215   if (flags & RPMINST_NOSCRIPTS)
2216     opts.push_back("--noscripts");
2217   if (flags & RPMINST_NODEPS)
2218     opts.push_back("--nodeps");
2219   if (flags & RPMINST_JUSTDB)
2220     opts.push_back("--justdb");
2221   if (flags & RPMINST_TEST)
2222     opts.push_back ("--test");
2223   if (flags & RPMINST_FORCE)
2224   {
2225     WAR << "IGNORE OPTION: 'rpm -e' does not support '--force'" << endl;
2226   }
2227
2228   opts.push_back("--");
2229   opts.push_back(name_r.c_str());
2230
2231   modifyDatabase(); // BEFORE run_rpm
2232   run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
2233
2234   std::string line;
2235   std::string rpmmsg;           // TODO: immediately forward lines via Callback::report rather than collecting
2236
2237   // got no progress from command, so we fake it:
2238   // 5  - command started
2239   // 50 - command completed
2240   // 100 if no error
2241   report->progress( 5 );
2242   unsigned linecnt = 0;
2243   while (systemReadLine(line))
2244   {
2245     if ( linecnt < MAXRPMMESSAGELINES )
2246       ++linecnt;
2247     else if ( line.find( " scriptlet failed, " ) == std::string::npos ) // always log %script errors
2248       continue;
2249     rpmmsg += line+'\n';
2250   }
2251   if ( linecnt >= MAXRPMMESSAGELINES )
2252     rpmmsg += "[truncated]\n";
2253   report->progress( 50 );
2254   int rpm_status = systemStatus();
2255
2256   if ( rpm_status != 0 )
2257   {
2258     historylog.comment(
2259         str::form("%s remove failed", name_r.c_str()), true /*timestamp*/);
2260     std::ostringstream sstr;
2261     sstr << "rpm output:" << endl << rpmmsg << endl;
2262     historylog.comment(sstr.str());
2263     // TranslatorExplanation the colon is followed by an error message
2264     ZYPP_THROW(RpmSubprocessException(_("RPM failed: ") + (rpmmsg.empty() ? error_message: rpmmsg) ));
2265   }
2266   else if ( ! rpmmsg.empty() )
2267   {
2268     historylog.comment(
2269         str::form("%s removed ok", name_r.c_str()), true /*timestamp*/);
2270
2271     std::ostringstream sstr;
2272     sstr << "Additional rpm output:" << endl << rpmmsg << endl;
2273     historylog.comment(sstr.str());
2274
2275     // report additional rpm output in finish
2276     // TranslatorExplanation Text is followed by a ':'  and the actual output.
2277     report->finishInfo(str::form( "%s:\n%s\n", _("Additional rpm output"),  rpmmsg.c_str() ));
2278   }
2279 }
2280
2281 ///////////////////////////////////////////////////////////////////
2282 //
2283 //
2284 //      METHOD NAME : RpmDb::backupPackage
2285 //      METHOD TYPE : bool
2286 //
2287 bool RpmDb::backupPackage( const Pathname & filename )
2288 {
2289   RpmHeader::constPtr h( RpmHeader::readPackage( filename, RpmHeader::NOSIGNATURE ) );
2290   if ( ! h )
2291     return false;
2292
2293   return backupPackage( h->tag_name() );
2294 }
2295
2296 ///////////////////////////////////////////////////////////////////
2297 //
2298 //
2299 //      METHOD NAME : RpmDb::backupPackage
2300 //      METHOD TYPE : bool
2301 //
2302 bool RpmDb::backupPackage(const std::string& packageName)
2303 {
2304   HistoryLog progresslog;
2305   bool ret = true;
2306   Pathname backupFilename;
2307   Pathname filestobackupfile = _root+_backuppath+FILEFORBACKUPFILES;
2308
2309   if (_backuppath.empty())
2310   {
2311     INT << "_backuppath empty" << endl;
2312     return false;
2313   }
2314
2315   FileList fileList;
2316
2317   if (!queryChangedFiles(fileList, packageName))
2318   {
2319     ERR << "Error while getting changed files for package " <<
2320     packageName << endl;
2321     return false;
2322   }
2323
2324   if (fileList.size() <= 0)
2325   {
2326     DBG <<  "package " <<  packageName << " not changed -> no backup" << endl;
2327     return true;
2328   }
2329
2330   if (filesystem::assert_dir(_root + _backuppath) != 0)
2331   {
2332     return false;
2333   }
2334
2335   {
2336     // build up archive name
2337     time_t currentTime = time(0);
2338     struct tm *currentLocalTime = localtime(&currentTime);
2339
2340     int date = (currentLocalTime->tm_year + 1900) * 10000
2341                + (currentLocalTime->tm_mon + 1) * 100
2342                + currentLocalTime->tm_mday;
2343
2344     int num = 0;
2345     do
2346     {
2347       backupFilename = _root + _backuppath
2348                        + str::form("%s-%d-%d.tar.gz",packageName.c_str(), date, num);
2349
2350     }
2351     while ( PathInfo(backupFilename).isExist() && num++ < 1000);
2352
2353     PathInfo pi(filestobackupfile);
2354     if (pi.isExist() && !pi.isFile())
2355     {
2356       ERR << filestobackupfile.asString() << " already exists and is no file" << endl;
2357       return false;
2358     }
2359
2360     std::ofstream fp ( filestobackupfile.asString().c_str(), std::ios::out|std::ios::trunc );
2361
2362     if (!fp)
2363     {
2364       ERR << "could not open " << filestobackupfile.asString() << endl;
2365       return false;
2366     }
2367
2368     for (FileList::const_iterator cit = fileList.begin();
2369          cit != fileList.end(); ++cit)
2370     {
2371       std::string name = *cit;
2372       if ( name[0] == '/' )
2373       {
2374         // remove slash, file must be relative to -C parameter of tar
2375         name = name.substr( 1 );
2376       }
2377       DBG << "saving file "<< name << endl;
2378       fp << name << endl;
2379     }
2380     fp.close();
2381
2382     const char* const argv[] =
2383       {
2384         "tar",
2385         "-czhP",
2386         "-C",
2387         _root.asString().c_str(),
2388         "--ignore-failed-read",
2389         "-f",
2390         backupFilename.asString().c_str(),
2391         "-T",
2392         filestobackupfile.asString().c_str(),
2393         NULL
2394       };
2395
2396     // execute tar in inst-sys (we dont know if there is a tar below _root !)
2397     ExternalProgram tar(argv, ExternalProgram::Stderr_To_Stdout, false, -1, true);
2398
2399     std::string tarmsg;
2400
2401     // TODO: its probably possible to start tar with -v and watch it adding
2402     // files to report progress
2403     for (std::string output = tar.receiveLine(); output.length() ;output = tar.receiveLine())
2404     {
2405       tarmsg+=output;
2406     }
2407
2408     int ret = tar.close();
2409
2410     if ( ret != 0)
2411     {
2412       ERR << "tar failed: " << tarmsg << endl;
2413       ret = false;
2414     }
2415     else
2416     {
2417       MIL << "tar backup ok" << endl;
2418       progresslog.comment(
2419           str::form(_("created backup %s"), backupFilename.asString().c_str())
2420           , /*timestamp*/true);
2421     }
2422
2423     filesystem::unlink(filestobackupfile);
2424   }
2425
2426   return ret;
2427 }
2428
2429 void RpmDb::setBackupPath(const Pathname& path)
2430 {
2431   _backuppath = path;
2432 }
2433
2434 std::ostream & operator<<( std::ostream & str, RpmDb::CheckPackageResult obj )
2435 {
2436   switch ( obj )
2437   {
2438 #define OUTS(E,S) case RpmDb::E: return str << "["<< (unsigned)obj << "-"<< S << "]"; break
2439     // translators: possible rpm package signature check result [brief]
2440     OUTS( CHK_OK,               _("Signature is OK") );
2441     // translators: possible rpm package signature check result [brief]
2442     OUTS( CHK_NOTFOUND,         _("Unknown type of signature") );
2443     // translators: possible rpm package signature check result [brief]
2444     OUTS( CHK_FAIL,             _("Signature does not verify") );
2445     // translators: possible rpm package signature check result [brief]
2446     OUTS( CHK_NOTTRUSTED,       _("Signature is OK, but key is not trusted") );
2447     // translators: possible rpm package signature check result [brief]
2448     OUTS( CHK_NOKEY,            _("Signatures public key is not available") );
2449     // translators: possible rpm package signature check result [brief]
2450     OUTS( CHK_ERROR,            _("File does not exist or signature can't be checked") );
2451     // translators: possible rpm package signature check result [brief]
2452     OUTS( CHK_NOSIG,            _("File is unsigned") );
2453 #undef OUTS
2454   }
2455   return str << "UnknowSignatureCheckError("+str::numstring(obj)+")";
2456 }
2457
2458 std::ostream & operator<<( std::ostream & str, const RpmDb::CheckPackageDetail & obj )
2459 {
2460   for ( const auto & el : obj )
2461     str << el.second << endl;
2462   return str;
2463 }
2464
2465 } // namespace rpm
2466 } // namespace target
2467 } // namespace zypp