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