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