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