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