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