fixed generating diff mails for config files
[platform/upstream/libzypp.git] / zypp / target / rpm / RpmDb.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/target/rpm/RpmDb.h
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 <list>
21 #include <map>
22 #include <set>
23 #include <string>
24 #include <vector>
25
26 #include "zypp/base/Logger.h"
27
28 #include "zypp/Date.h"
29 #include "zypp/Pathname.h"
30 #include "zypp/PathInfo.h"
31
32 #include "zypp/target/rpm/RpmDb.h"
33 #include "zypp/target/rpm/RpmCallbacks.h"
34
35 #include "zypp/target/rpm/librpmDb.h"
36 #include "zypp/target/rpm/RpmPackageImpl.h"
37 #include "zypp/target/rpm/RpmException.h"
38 #include "zypp/CapSet.h"
39 #include "zypp/CapFactory.h"
40
41 #ifndef _
42 #define _(X) X
43 #endif
44
45 using namespace std;
46
47 namespace zypp {
48   namespace target {
49     namespace rpm {
50
51 unsigned diffFiles(const std::string file1, const std::string file2, std::string& out, int maxlines)
52 {
53     const char* argv[] =
54     {
55         "diff",
56         "-u",
57         file1.c_str(),
58         file2.c_str(),
59         NULL
60     };
61     ExternalProgram* prog = new ExternalProgram(argv,ExternalProgram::Discard_Stderr, false, -1, true);
62
63     if(!prog)
64         return 2;
65
66     string line;
67     int count = 0;
68     for(line = prog->receiveLine(), count=0;
69         !line.empty();
70         line = prog->receiveLine(), count++ )
71     {
72         if(maxlines<0?true:count<maxlines)
73             out+=line;
74     }
75
76     return prog->close();
77 }
78
79
80
81 /******************************************************************
82 **
83 **
84 **      FUNCTION NAME : stringPath
85 **      FUNCTION TYPE : inline string
86 */
87 inline string stringPath( const Pathname & root_r, const Pathname & sub_r )
88 {
89   return librpmDb::stringPath( root_r, sub_r );
90 }
91
92 /******************************************************************
93 **
94 **
95 **      FUNCTION NAME : operator<<
96 **      FUNCTION TYPE : ostream &
97 */
98 ostream & operator<<( ostream & str, const RpmDb::DbStateInfoBits & obj )
99 {
100   if ( obj == RpmDb::DbSI_NO_INIT ) {
101     str << "NO_INIT";
102   } else {
103 #define ENUM_OUT(B,C) str << ( obj & RpmDb::B ? C : '-' )
104     str << "V4(";
105     ENUM_OUT( DbSI_HAVE_V4,     'X' );
106     ENUM_OUT( DbSI_MADE_V4,     'c' );
107     ENUM_OUT( DbSI_MODIFIED_V4, 'm' );
108     str << ")V3(";
109     ENUM_OUT( DbSI_HAVE_V3,     'X' );
110     ENUM_OUT( DbSI_HAVE_V3TOV4, 'B' );
111     ENUM_OUT( DbSI_MADE_V3TOV4, 'c' );
112     str << ")";
113 #undef ENUM_OUT
114   }
115   return str;
116 }
117
118 ///////////////////////////////////////////////////////////////////
119 //      CLASS NAME : RpmDbPtr
120 //      CLASS NAME : RpmDbconstPtr
121 ///////////////////////////////////////////////////////////////////
122
123 #define WARNINGMAILPATH "/var/log/YaST2/"
124 #define FILEFORBACKUPFILES "YaSTBackupModifiedFiles"
125
126 ///////////////////////////////////////////////////////////////////
127 //
128 //      CLASS NAME : RpmDb::Logfile
129 /**
130  * Simple wrapper for progress log. Refcnt, filename and corresponding
131  * ofstream are static members. Logfile constructor raises, destructor
132  * lowers refcounter. On refcounter changing from 0->1, file is opened.
133  * Changing from 1->0 the file is closed. Thus Logfile objects should be
134  * local to those functions, writing the log, and must not be stored
135  * permanently;
136  *
137  * Usage:
138  *  some methothd ()
139  *  {
140  *    Logfile progresslog;
141  *    ...
142  *    progresslog() << "some message" << endl;
143  *    ...
144  *  }
145  **/
146 class RpmDb::Logfile {
147   Logfile( const Logfile & );
148   Logfile & operator=( const Logfile & );
149   private:
150     static ofstream _log;
151     static unsigned _refcnt;
152     static Pathname _fname;
153     static void openLog() {
154       if ( !_fname.empty() ) {
155         _log.clear();
156         _log.open( _fname.asString().c_str(), std::ios::out|std::ios::app );
157         if( !_log )
158           ERR << "Could not open logfile '" << _fname << "'" << endl;
159       }
160     }
161     static void closeLog() {
162       _log.clear();
163       _log.close();
164     }
165     static void refUp() {
166       if ( !_refcnt )
167         openLog();
168       ++_refcnt;
169     }
170     static void refDown() {
171       --_refcnt;
172       if ( !_refcnt )
173         closeLog();
174     }
175   public:
176     Logfile() { refUp(); }
177     ~Logfile() { refDown(); }
178     ostream & operator()( bool timestamp = false ) {
179       if ( timestamp ) {
180         _log << Date(Date::now()).form( "%Y-%m-%d %H:%M:%S ");
181       }
182       return _log;
183     }
184     static void setFname( const Pathname & fname_r ) {
185       MIL << "installation log file " << fname_r << endl;
186       if ( _refcnt )
187         closeLog();
188       _fname = fname_r;
189       if ( _refcnt )
190         openLog();
191     }
192 };
193
194 ///////////////////////////////////////////////////////////////////
195
196 Pathname RpmDb::Logfile::_fname;
197 ofstream RpmDb::Logfile::_log;
198 unsigned RpmDb::Logfile::_refcnt = 0;
199
200 ///////////////////////////////////////////////////////////////////
201
202 ///////////////////////////////////////////////////////////////////
203 //
204 //
205 //      METHOD NAME : RpmDb::setInstallationLogfile
206 //      METHOD TYPE : bool
207 //
208 bool RpmDb::setInstallationLogfile( const Pathname & filename )
209 {
210   Logfile::setFname( filename );
211   return true;
212 }
213
214 ///////////////////////////////////////////////////////////////////
215 //
216 //      CLASS NAME : RpmDb::Packages
217 /**
218  * Helper class for RpmDb::getPackages() to build the
219  * list<Package::Ptr> returned. We have to assert, that there
220  * is a unique entry for every string.
221  *
222  * In the first step we build the _index map which helps to catch
223  * multiple occurances of a string in the rpmdb. That's not desired,
224  * but possible. Usg. the last package instance installed is strored
225  * in the _index map.
226  *
227  * At the end buildList() is called to build the list<Package::Ptr>
228  * from the _index map. _valid is set true to assign that the list
229  * is in sync with the rpmdb content. Operations changing the rpmdb
230  * content (install/remove package) should set _valid to false. The
231  * next call to RpmDb::getPackages() will then reread the the rpmdb.
232  *
233  * Note that outside RpmDb::getPackages() _list and _index are always
234  * in sync. So you may use lookup(PkgName) to retrieve a specific
235  * Package::Ptr.
236  **/
237 class RpmDb::Packages {
238   public:
239     list<Package::Ptr>        _list;
240     map<std::string,Package::Ptr> _index;
241     bool                      _valid;
242     Packages() : _valid( false ) {}
243     void clear() {
244       _list.clear();
245       _index.clear();
246       _valid = false;
247     }
248     Package::Ptr lookup( const string & name_r ) const {
249       map<string,Package::Ptr>::const_iterator got = _index.find( name_r );
250       if ( got != _index.end() )
251         return got->second;
252       return Package::Ptr();
253     }
254     void buildList() {
255       _list.clear();
256       for ( map<string,Package::Ptr>::iterator iter = _index.begin();
257             iter != _index.end(); ++iter ) {
258         if ( iter->second )
259           _list.push_back( iter->second );
260       }
261       _valid = true;
262     }
263 };
264
265 ///////////////////////////////////////////////////////////////////
266
267 ///////////////////////////////////////////////////////////////////
268 //
269 //      CLASS NAME : RpmDb
270 //
271 ///////////////////////////////////////////////////////////////////
272
273 #define FAILIFNOTINITIALIZED if( ! initialized() ) { ZYPP_THROW(RpmDbNotOpenException()); }
274
275 ///////////////////////////////////////////////////////////////////
276
277 ///////////////////////////////////////////////////////////////////
278 //
279 //
280 //      METHOD NAME : RpmDb::RpmDb
281 //      METHOD TYPE : Constructor
282 //
283 RpmDb::RpmDb()
284     : _dbStateInfo( DbSI_NO_INIT )
285     , _packages( * new Packages ) // delete in destructor
286 #warning Check for obsolete memebers
287     , _backuppath ("/var/adm/backup")
288     , _packagebackups(false)
289     , _warndirexists(false)
290 {
291    process = 0;
292    exit_code = -1;
293
294    // Some rpm versions are patched not to abort installation if
295    // symlink creation failed.
296    setenv( "RPM_IgnoreFailedSymlinks", "1", 1 );
297 }
298
299 ///////////////////////////////////////////////////////////////////
300 //
301 //
302 //      METHOD NAME : RpmDb::~RpmDb
303 //      METHOD TYPE : Destructor
304 //
305 RpmDb::~RpmDb()
306 {
307    MIL << "~RpmDb()" << endl;
308    closeDatabase();
309
310    delete process;
311    delete &_packages;
312    MIL  << "~RpmDb() end" << endl;
313 }
314
315 ///////////////////////////////////////////////////////////////////
316 //
317 //
318 //      METHOD NAME : RpmDb::dumpOn
319 //      METHOD TYPE : std::ostream &
320 //
321 std::ostream & RpmDb::dumpOn( std::ostream & str ) const
322 {
323   str << "RpmDb[";
324
325   if ( _dbStateInfo == DbSI_NO_INIT ) {
326     str << "NO_INIT";
327   } else {
328 #define ENUM_OUT(B,C) str << ( _dbStateInfo & B ? C : '-' )
329     str << "V4(";
330     ENUM_OUT( DbSI_HAVE_V4,     'X' );
331     ENUM_OUT( DbSI_MADE_V4,     'c' );
332     ENUM_OUT( DbSI_MODIFIED_V4, 'm' );
333     str << ")V3(";
334     ENUM_OUT( DbSI_HAVE_V3,     'X' );
335     ENUM_OUT( DbSI_HAVE_V3TOV4, 'B' );
336     ENUM_OUT( DbSI_MADE_V3TOV4, 'c' );
337     str << "): " << stringPath( _root, _dbPath );
338 #undef ENUM_OUT
339   }
340   return str << "]";
341 }
342
343 ///////////////////////////////////////////////////////////////////
344 //
345 //
346 //      METHOD NAME : RpmDb::initDatabase
347 //      METHOD TYPE : PMError
348 //
349 void RpmDb::initDatabase( Pathname root_r, Pathname dbPath_r )
350 {
351   ///////////////////////////////////////////////////////////////////
352   // Check arguments
353   ///////////////////////////////////////////////////////////////////
354   if ( root_r.empty() )
355     root_r = "/";
356
357   if ( dbPath_r.empty() )
358     dbPath_r = "/var/lib/rpm";
359
360   if ( ! (root_r.absolute() && dbPath_r.absolute()) ) {
361     ERR << "Illegal root or dbPath: " << stringPath( root_r, dbPath_r ) << endl;
362     ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
363   }
364
365   MIL << "Calling initDatabase: " << stringPath( root_r, dbPath_r ) << endl;
366
367   ///////////////////////////////////////////////////////////////////
368   // Check whether already initialized
369   ///////////////////////////////////////////////////////////////////
370   if ( initialized() ) {
371     if ( root_r == _root && dbPath_r == _dbPath ) {
372       return;
373     } else {
374       ZYPP_THROW(RpmDbAlreadyOpenException(_root, _dbPath, root_r, dbPath_r));
375     }
376   }
377
378   ///////////////////////////////////////////////////////////////////
379   // init database
380   ///////////////////////////////////////////////////////////////////
381   librpmDb::unblockAccess();
382   DbStateInfoBits info = DbSI_NO_INIT;
383   try {
384     internal_initDatabase( root_r, dbPath_r, info );
385   }
386   catch (const RpmException & excpt_r)
387   {
388     ZYPP_CAUGHT(excpt_r);
389     librpmDb::blockAccess();
390     ERR << "Cleanup on error: state " << info << endl;
391
392     if ( dbsi_has( info, DbSI_MADE_V4 ) ) {
393       // remove the newly created rpm4 database and
394       // any backup created on conversion.
395       removeV4( root_r + dbPath_r, dbsi_has( info, DbSI_MADE_V3TOV4 ) );
396     }
397     ZYPP_RETHROW(excpt_r);
398   }
399   if ( dbsi_has( info, DbSI_HAVE_V3 ) ) {
400     if ( root_r == "/" || dbsi_has( info, DbSI_MODIFIED_V4 ) ) {
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     } else {
406         // Performing an update: Keep the original rpm3 database
407         // and wait if the rpm4 database gets modified by installing
408         // or removing packages. Cleanup in modifyDatabase or closeDatabase.
409         MIL << "Update mode: Cleanup delayed until closeOldDatabase." << endl;
410     }
411   }
412 #warning CHECK: notify root about conversion backup.
413
414   _root   = root_r;
415   _dbPath = dbPath_r;
416   _dbStateInfo = info;
417
418 #warning Add rebuild database once have the info about context
419 #if 0
420   if ( ! ( Y2PM::runningFromSystem() ) ) {
421     if (      dbsi_has( info, DbSI_HAVE_V4 )
422         && ! dbsi_has( info, DbSI_MADE_V4 ) ) {
423       err = rebuildDatabase();
424     }
425   }
426 #endif
427
428   // Close the database in case any write acces (create/convert)
429   // happened during init. This should drop any lock acquired
430   // by librpm. On demand it will be reopened readonly and should
431   // not hold any lock.
432   librpmDb::dbRelease( true );
433   MIL << "InitDatabase: " << *this << endl;
434 }
435
436 ///////////////////////////////////////////////////////////////////
437 //
438 //
439 //      METHOD NAME : RpmDb::internal_initDatabase
440 //      METHOD TYPE : PMError
441 //
442 void RpmDb::internal_initDatabase( const Pathname & root_r, const Pathname & dbPath_r,
443                                       DbStateInfoBits & info_r )
444 {
445   info_r = DbSI_NO_INIT;
446
447   ///////////////////////////////////////////////////////////////////
448   // Get info about the desired database dir
449   ///////////////////////////////////////////////////////////////////
450   librpmDb::DbDirInfo dbInfo( root_r, dbPath_r );
451
452   if ( dbInfo.illegalArgs() ) {
453     // should not happen (checked in initDatabase)
454     ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
455   }
456   if ( ! dbInfo.usableArgs() ) {
457     ERR << "Bad database directory: " << dbInfo.dbDir() << endl;
458     ZYPP_THROW(RpmInvalidRootException(root_r, dbPath_r));
459   }
460
461   if ( dbInfo.hasDbV4() ) {
462     dbsi_set( info_r, DbSI_HAVE_V4 );
463     MIL << "Found rpm4 database in " << dbInfo.dbDir() << endl;
464   } else {
465     MIL << "Creating new rpm4 database in " << dbInfo.dbDir() << endl;
466   }
467
468   if ( dbInfo.hasDbV3() ) {
469     dbsi_set( info_r, DbSI_HAVE_V3 );
470   }
471   if ( dbInfo.hasDbV3ToV4() ) {
472     dbsi_set( info_r, DbSI_HAVE_V3TOV4 );
473   }
474
475   DBG << "Initial state: " << info_r << ": " << stringPath( root_r, dbPath_r );
476   librpmDb::dumpState( DBG ) << endl;
477
478   ///////////////////////////////////////////////////////////////////
479   // Access database, create if needed
480   ///////////////////////////////////////////////////////////////////
481
482   // creates dbdir and empty rpm4 database if not present
483   librpmDb::dbAccess( root_r, dbPath_r );
484
485   if ( ! dbInfo.hasDbV4() ) {
486     dbInfo.restat();
487     if ( dbInfo.hasDbV4() ) {
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     MIL << "Empty rpm4 database "  << dbInfo.dbV4() << endl;
504   }
505
506   if ( dbInfo.hasDbV3() ) {
507     MIL << "Found rpm3 database " << dbInfo.dbV3() << endl;
508
509     if ( dbEmpty ) {
510       extern void convertV3toV4( const Pathname & v3db_r, const librpmDb::constPtr & v4db_r );
511       convertV3toV4( dbInfo.dbV3().path(), dbptr );
512
513       // create a backup copy
514       int res = filesystem::copy( dbInfo.dbV3().path(), dbInfo.dbV3ToV4().path() );
515       if ( res ) {
516         WAR << "Backup converted rpm3 database failed: error(" << res << ")" << endl;
517       } else {
518         dbInfo.restat();
519         if ( dbInfo.hasDbV3ToV4() ) {
520           MIL << "Backup converted rpm3 database: " << dbInfo.dbV3ToV4() << endl;
521           dbsi_set( info_r, DbSI_HAVE_V3TOV4 | DbSI_MADE_V3TOV4 );
522         }
523       }
524
525     } else {
526
527       WAR << "Non empty rpm3 and rpm4 database found: using rpm4" << endl;
528 #warning EXCEPTION: nonempty rpm4 and rpm3 database found.
529       //ConvertDbReport::Send report( RpmDbCallbacks::convertDbReport );
530       //report->start( dbInfo.dbV3().path() );
531       //report->stop( some error );
532
533       // set DbSI_MODIFIED_V4 as it's not a temporary which can be removed.
534       dbsi_set( info_r, DbSI_MODIFIED_V4 );
535
536     }
537
538     DBG << "Convert state: " << info_r << ": " << stringPath( root_r, dbPath_r );
539     librpmDb::dumpState( DBG ) << endl;
540   }
541
542   if ( dbInfo.hasDbV3ToV4() ) {
543     MIL << "Rpm3 database backup: " << dbInfo.dbV3ToV4() << endl;
544   }
545 }
546
547 ///////////////////////////////////////////////////////////////////
548 //
549 //
550 //      METHOD NAME : RpmDb::removeV4
551 //      METHOD TYPE : void
552 //
553 void RpmDb::removeV4( const Pathname & dbdir_r, bool v3backup_r )
554 {
555   const char * v3backup = "packages.rpm3";
556   const char * master = "Packages";
557   const char * index[] = {
558     "Basenames",
559     "Conflictname",
560     "Depends",
561     "Dirnames",
562     "Filemd5s",
563     "Group",
564     "Installtid",
565     "Name",
566     "Providename",
567     "Provideversion",
568     "Pubkeys",
569     "Requirename",
570     "Requireversion",
571     "Sha1header",
572     "Sigmd5",
573     "Triggername",
574     // last entry!
575     NULL
576   };
577
578   PathInfo pi( dbdir_r );
579   if ( ! pi.isDir() ) {
580     ERR << "Can't remove rpm4 database in non directory: " << dbdir_r << endl;
581     return;
582   }
583
584   for ( const char ** f = index; *f; ++f ) {
585     pi( dbdir_r + *f );
586     if ( pi.isFile() ) {
587       filesystem::unlink( pi.path() );
588     }
589   }
590
591   pi( dbdir_r + master );
592   if ( pi.isFile() ) {
593     MIL << "Removing rpm4 database " << pi << endl;
594     filesystem::unlink( pi.path() );
595   }
596
597   if ( v3backup_r ) {
598     pi( dbdir_r + v3backup );
599     if ( pi.isFile() ) {
600       MIL << "Removing converted rpm3 database backup " << pi << endl;
601       filesystem::unlink( pi.path() );
602     }
603   }
604 }
605
606 ///////////////////////////////////////////////////////////////////
607 //
608 //
609 //      METHOD NAME : RpmDb::removeV3
610 //      METHOD TYPE : void
611 //
612 void RpmDb::removeV3( const Pathname & dbdir_r, bool v3backup_r )
613 {
614   const char * master = "packages.rpm";
615   const char * index[] = {
616     "conflictsindex.rpm",
617     "fileindex.rpm",
618     "groupindex.rpm",
619     "nameindex.rpm",
620     "providesindex.rpm",
621     "requiredby.rpm",
622     "triggerindex.rpm",
623     // last entry!
624     NULL
625   };
626
627   PathInfo pi( dbdir_r );
628   if ( ! pi.isDir() ) {
629     ERR << "Can't remove rpm3 database in non directory: " << dbdir_r << endl;
630     return;
631   }
632
633   for ( const char ** f = index; *f; ++f ) {
634     pi( dbdir_r + *f );
635     if ( pi.isFile() ) {
636       filesystem::unlink( pi.path() );
637     }
638   }
639
640 #warning CHECK: compare vs existing v3 backup. notify root
641   pi( dbdir_r + master );
642   if ( pi.isFile() ) {
643     Pathname m( pi.path() );
644     if ( v3backup_r ) {
645       // backup was already created
646       filesystem::unlink( m );
647       Pathname b( m.extend( "3" ) );
648       pi( b ); // stat backup
649     } else {
650       Pathname b( m.extend( ".deleted" ) );
651       pi( b );
652       if ( pi.isFile() ) {
653         // rempve existing backup
654         filesystem::unlink( b );
655       }
656       filesystem::rename( m, b );
657       pi( b ); // stat backup
658     }
659     MIL << "(Re)moved rpm3 database to " << pi << endl;
660   }
661 }
662
663 ///////////////////////////////////////////////////////////////////
664 //
665 //
666 //      METHOD NAME : RpmDb::modifyDatabase
667 //      METHOD TYPE : void
668 //
669 void RpmDb::modifyDatabase()
670 {
671   if ( ! initialized() )
672     return;
673
674   // tag database as modified
675   dbsi_set( _dbStateInfo, DbSI_MODIFIED_V4 );
676
677   // Move outdated rpm3 database beside.
678   if ( dbsi_has( _dbStateInfo, DbSI_HAVE_V3 ) ) {
679     MIL << "Update mode: Delayed cleanup: state " << _dbStateInfo << endl;
680     removeV3( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 ) );
681     dbsi_clr( _dbStateInfo, DbSI_HAVE_V3 );
682   }
683
684   // invalidate Packages list
685   _packages._valid = false;
686 }
687
688 ///////////////////////////////////////////////////////////////////
689 //
690 //
691 //      METHOD NAME : RpmDb::closeDatabase
692 //      METHOD TYPE : PMError
693 //
694 void RpmDb::closeDatabase()
695 {
696   if ( ! initialized() ) {
697     return;
698   }
699
700   MIL << "Calling closeDatabase: " << *this << endl;
701
702   ///////////////////////////////////////////////////////////////////
703   // Block further database access
704   ///////////////////////////////////////////////////////////////////
705   _packages.clear();
706   librpmDb::blockAccess();
707
708   ///////////////////////////////////////////////////////////////////
709   // Check fate if old version database still present
710   ///////////////////////////////////////////////////////////////////
711   if ( dbsi_has( _dbStateInfo, DbSI_HAVE_V3 ) ) {
712     MIL << "Update mode: Delayed cleanup: state " << _dbStateInfo << endl;
713     if ( dbsi_has( _dbStateInfo, DbSI_MODIFIED_V4 ) ) {
714       // Move outdated rpm3 database beside.
715       removeV3( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 )  );
716     } else {
717       // Remove unmodified rpm4 database
718       removeV4( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 ) );
719     }
720   }
721
722   ///////////////////////////////////////////////////////////////////
723   // Uninit
724   ///////////////////////////////////////////////////////////////////
725   _root = _dbPath = Pathname();
726   _dbStateInfo = DbSI_NO_INIT;
727
728   MIL << "closeDatabase: " << *this << endl;
729 }
730
731 ///////////////////////////////////////////////////////////////////
732 //
733 //
734 //      METHOD NAME : RpmDb::rebuildDatabase
735 //      METHOD TYPE : PMError
736 //
737 void RpmDb::rebuildDatabase()
738 {
739   RebuildDbReport report;
740   try {
741     doRebuildDatabase(report);
742   }
743   catch (RpmException & excpt_r)
744   {
745     report.end(excpt_r);
746     ZYPP_RETHROW(excpt_r);
747   }
748   report.end();
749 }
750
751 void RpmDb::doRebuildDatabase(RebuildDbReport & report)
752 {
753   FAILIFNOTINITIALIZED;
754
755   MIL << "RpmDb::rebuildDatabase" << *this << endl;
756 // FIXME  Timecount _t( "RpmDb::rebuildDatabase" );
757
758   PathInfo dbMaster( root() + dbPath() + "Packages" );
759   PathInfo dbMasterBackup( dbMaster.path().extend( ".y2backup" ) );
760
761   // run rpm
762   RpmArgVec opts;
763   opts.push_back("--rebuilddb");
764   opts.push_back("-vv");
765
766   // don't call modifyDatabase because it would remove the old
767   // rpm3 database, if the current database is a temporary one.
768   // But do invalidate packages list.
769   _packages._valid = false;
770   run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
771
772   // progress report: watch this file growing
773   PathInfo newMaster( root()
774                       + dbPath().extend( str::form( "rebuilddb.%d",
775                                                            process?process->getpid():0) )
776                       + "Packages" );
777
778   string       line;
779   string       errmsg;
780
781   while ( systemReadLine( line ) ) {
782     if ( newMaster() ) { // file is removed at the end of rebuild.
783       // current size should be upper limit for new db
784       report.progress( (100 * newMaster.size()) / dbMaster.size());
785     }
786
787     if ( line.compare( 0, 2, "D:" ) ) {
788       errmsg += line + '\n';
789 //      report.notify( line );
790       WAR << line << endl;
791     }
792   }
793
794   int rpm_status = systemStatus();
795
796   if ( rpm_status != 0 ) {
797     ZYPP_THROW(RpmSubprocessException(string("rpm failed with message: ") + errmsg));
798   } else {
799     report.progress( 100 ); // 100%
800   }
801 }
802
803 ///////////////////////////////////////////////////////////////////
804 //
805 //
806 //      METHOD NAME : RpmDb::importPubkey
807 //      METHOD TYPE : PMError
808 //
809 void RpmDb::importPubkey( const Pathname & pubkey_r )
810 {
811   FAILIFNOTINITIALIZED;
812
813   RpmArgVec opts;
814   opts.push_back ( "--import" );
815   opts.push_back ( "--" );
816   opts.push_back ( pubkey_r.asString().c_str() );
817
818   // don't call modifyDatabase because it would remove the old
819   // rpm3 database, if the current database is a temporary one.
820   // But do invalidate packages list.
821   _packages._valid = false;
822   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
823
824   string line;
825   while ( systemReadLine( line ) ) {
826     if ( line.substr( 0, 6 ) == "error:" ) {
827       WAR << line << endl;
828     } else {
829       DBG << line << endl;
830     }
831   }
832
833   int rpm_status = systemStatus();
834
835   if ( rpm_status != 0 ) {
836     ZYPP_THROW(RpmSubprocessException(string("Failed to import public key from file ") + pubkey_r.asString() + string(": rpm returned  ") + str::numstring(rpm_status)));
837   } else {
838     MIL << "Imported public key from file " << pubkey_r << endl;
839   }
840 }
841
842 ///////////////////////////////////////////////////////////////////
843 //
844 //
845 //      METHOD NAME : RpmDb::importPubkey
846 //      METHOD TYPE : PMError
847 //
848 void RpmDb::importPubkey( const Pathname & keyring_r, const string & keyname_r )
849 {
850   FAILIFNOTINITIALIZED;
851
852   // create tempfile
853   char tmpname[] = "/tmp/zypp.pubkey.XXXXXX";
854   int tmpfd = mkstemp( tmpname );
855   if ( tmpfd == -1 ) {
856     ZYPP_THROW(RpmSubprocessException("Unable to create a unique temporary file for pubkey"));
857   }
858
859   // export keyname from keyring
860   RpmArgVec args;
861   args.push_back( "gpg" );
862   args.push_back( "--armor" );
863   args.push_back( "--no-default-keyring" );
864   args.push_back( "--keyring" );
865   args.push_back( keyring_r.asString().c_str() );
866   args.push_back( "--export" );
867   args.push_back( keyname_r.c_str() );
868
869   const char * argv[args.size() + 1];
870   const char ** p = argv;
871   p = copy( args.begin(), args.end(), p );
872   *p = 0;
873
874   // launch gpg
875   ExternalProgram prg( argv, ExternalProgram::Discard_Stderr, false, -1, true );
876   int res = 0;
877
878   // read key
879
880   try {
881     for ( string line( prg.receiveLine() ); line.length(); line = prg.receiveLine() ) {
882       ssize_t written = write( tmpfd, line.c_str(), line.length() );
883       if ( written == -1 || unsigned(written) != line.length() ) {
884         ZYPP_THROW(RpmSubprocessException(string("Error writing pubkey to ") + tmpname));
885       }
886       res += written; // empty file indicates key not found
887     }
888   }
889   catch (RpmException & excpt_r)
890   {
891     ZYPP_CAUGHT(excpt_r);
892     close( tmpfd );
893     filesystem::unlink( tmpname );
894     ZYPP_RETHROW(excpt_r);
895   }
896   close( tmpfd );
897
898   if ( ! res ) {
899     ZYPP_THROW(RpmSubprocessException(string("gpg: no key '") + keyname_r + string("' found in  '") + keyring_r.asString() + string("'")));
900   }
901
902   // check gpg returncode
903   res = prg.close();
904   if ( res ) {
905     // remove tempfile
906     filesystem::unlink( tmpname );
907     ZYPP_THROW(RpmSubprocessException(string("gpg: export '") + keyname_r + string("' from '") + keyring_r.asString() + "' returned " + str::numstring(res)));
908   }
909
910   MIL << "Exported '" << keyname_r << "' from '" << keyring_r << "' to " << tmpname << endl;
911   try {
912     importPubkey( tmpname );
913   }
914   catch (RpmException & excpt_r)
915   {
916     ZYPP_CAUGHT(excpt_r);
917     filesystem::unlink( tmpname );
918     ZYPP_RETHROW(excpt_r);
919   }
920   filesystem::unlink( tmpname );
921 }
922
923 ///////////////////////////////////////////////////////////////////
924 //
925 //
926 //      METHOD NAME : RpmDb::pubkeys
927 //      METHOD TYPE : set<Edition>
928 //
929 set<Edition> RpmDb::pubkeys() const
930 {
931   set<Edition> ret;
932
933   librpmDb::db_const_iterator it;
934   for ( it.findByName( string( "gpg-pubkey" ) ); *it; ++it ) {
935     ret.insert( it->tag_edition() );
936   }
937
938   return ret;
939 }
940
941 ///////////////////////////////////////////////////////////////////
942 //
943 //
944 //      METHOD NAME : RpmDb::packagesValid
945 //      METHOD TYPE : bool
946 //
947 bool RpmDb::packagesValid() const
948 {
949   return( _packages._valid || ! initialized() );
950 }
951
952 ///////////////////////////////////////////////////////////////////
953 //
954 //
955 //      METHOD NAME : RpmDb::getPackages
956 //      METHOD TYPE : const std::list<Package::Ptr> &
957 //
958 //      DESCRIPTION :
959 //
960 const std::list<Package::Ptr> & RpmDb::getPackages()
961 {
962   ScanDbReport report;
963   try {
964     const std::list<Package::Ptr> & ret = doGetPackages(report);
965     report.end();
966     return ret;
967   }
968   catch (RpmException & excpt_r)
969   {
970     report.end(excpt_r);
971     ZYPP_RETHROW(excpt_r);
972   }
973 }
974
975
976 const std::list<Package::Ptr> & RpmDb::doGetPackages(ScanDbReport & report)
977 {
978   if ( packagesValid() ) {
979     return _packages._list;
980   }
981
982 // FIXME  Timecount _t( "RpmDb::getPackages" );
983
984 #warning how to detect corrupt db while reading.
985
986   _packages.clear();
987
988   ///////////////////////////////////////////////////////////////////
989   // Collect package data. A map is used to check whethere there are
990   // multiple entries for the same string. If so we consider the last
991   // one installed to be the one we're interesed in.
992   ///////////////////////////////////////////////////////////////////
993   unsigned expect = 0;
994   librpmDb::db_const_iterator iter; // findAll
995   {
996     // quick check
997     for ( ; *iter; ++iter ) {
998       ++expect;
999     }
1000     if ( iter.dbError() ) {
1001       ERR << "No database access: " << iter.dbError() << endl;
1002       ZYPP_THROW(*(iter.dbError()));
1003     }
1004   }
1005   unsigned current = 0;
1006
1007   CapFactory _f;
1008   for ( iter.findAll(); *iter; ++iter, ++current, report.progress( (100*current)/expect)) {
1009
1010     string name = iter->tag_name();
1011     if ( name == string( "gpg-pubkey" ) ) {
1012       // pseudo package filtered, as we can't handle multiple instances
1013       // of 'gpg-pubkey-VERS-REL'.
1014       continue;
1015     }
1016     Date installtime = iter->tag_installtime();
1017     Package::Ptr & nptr = _packages._index[name]; // be shure to get a reference!
1018
1019     if ( nptr ) {
1020       WAR << "Multiple entries for package '" << name << "' in rpmdb" << endl;
1021       if ( nptr->installtime() > installtime )
1022         continue;
1023       // else overwrite previous entry
1024     }
1025
1026     // create dataprovider
1027     shared_ptr<RPMPackageImpl> impl(new RPMPackageImpl(*iter));
1028
1029     // Collect basic Resolvable data
1030     NVRAD dataCollect( iter->tag_name(),
1031                        iter->tag_edition(),
1032                        iter->tag_arch() );
1033
1034     list<string> filenames = impl->filenames();
1035     dataCollect.provides = iter->tag_provides ( & _filerequires );
1036     for (list<string>::const_iterator filename = filenames.begin();
1037          filename != filenames.end();
1038          filename++)
1039     {
1040       if (filename->find("/bin/")
1041         || filename->find("/sbin/")
1042         || filename->find("/lib/")
1043         || filename->find("/lib64/")
1044         || filename->find("/etc/")
1045         || filename->find("/usr/games/")
1046         || filename->find("/usr/share/dict/words")
1047         || filename->find("/usr/share/magic.mime")
1048         || filename->find("/opt/gnome/games"))
1049       {
1050         try {
1051           dataCollect.provides.insert(_f.parse(ResTraits<Package>::kind, *filename));
1052         }
1053         catch (Exception & excpt_r)
1054         {
1055           ZYPP_CAUGHT(excpt_r);
1056           WAR << "Invalid capability: " << *filename << endl;
1057         }
1058       }
1059     }
1060
1061     dataCollect.requires    = iter->tag_requires ( &_filerequires );
1062     dataCollect.prerequires = iter->tag_prerequires ( &_filerequires );
1063     dataCollect.conflicts   = iter->tag_conflicts( &_filerequires );
1064     dataCollect.obsoletes   = iter->tag_obsoletes( &_filerequires );
1065
1066     // create package from dataprovider
1067     nptr = detail::makeResolvableFromImpl( dataCollect, impl );
1068
1069   }
1070
1071   ///////////////////////////////////////////////////////////////////
1072   // Evaluate filerequires collected so far
1073   ///////////////////////////////////////////////////////////////////
1074   for( set<string>::iterator it = _filerequires.begin(); it != _filerequires.end(); ++it ) {
1075
1076     for ( iter.findByFile( *it ); *iter; ++iter ) {
1077       Package::Ptr pptr = _packages.lookup( iter->tag_name() );
1078       if ( !pptr ) {
1079         WAR << "rpmdb.findByFile returned unknown package " << *iter << endl;
1080         continue;
1081       }
1082       pptr->injectProvides(_f.parse(ResTraits<Package>::kind, *it));
1083     }
1084
1085   }
1086
1087   ///////////////////////////////////////////////////////////////////
1088   // Build final packages list
1089   ///////////////////////////////////////////////////////////////////
1090   _packages.buildList();
1091   DBG << "Found installed packages: " << _packages._list.size() << endl;
1092   return _packages._list;
1093 }
1094
1095 #warning Uncomment this function if it is needed
1096 #if 0
1097 ///////////////////////////////////////////////////////////////////
1098 //
1099 //
1100 //      METHOD NAME : RpmDb::traceFileRel
1101 //      METHOD TYPE : void
1102 //
1103 //      DESCRIPTION :
1104 //
1105 void RpmDb::traceFileRel( const PkgRelation & rel_r )
1106 {
1107   if ( ! rel_r.isFileRel() )
1108     return;
1109
1110   if ( ! _filerequires.insert( rel_r.name() ).second )
1111     return; // already got it in _filerequires
1112
1113   if ( ! _packages._valid )
1114     return; // collect only. Evaluated in first call to getPackages()
1115
1116   //
1117   // packages already initialized. Must check and insert here
1118   //
1119   librpmDb::db_const_iterator iter;
1120   if ( iter.dbError() ) {
1121     ERR << "No database access: " << iter.dbError() << endl;
1122     return;
1123   }
1124
1125   for ( iter.findByFile( rel_r.name() ); *iter; ++iter ) {
1126     Package::Ptr pptr = _packages.lookup( iter->tag_name() );
1127     if ( !pptr ) {
1128       WAR << "rpmdb.findByFile returned unpknown package " << *iter << endl;
1129       continue;
1130     }
1131     pptr->addProvides( rel_r.name() );
1132   }
1133 }
1134 #endif
1135
1136 ///////////////////////////////////////////////////////////////////
1137 //
1138 //
1139 //      METHOD NAME : RpmDb::hasFile
1140 //      METHOD TYPE : bool
1141 //
1142 //      DESCRIPTION :
1143 //
1144 bool RpmDb::hasFile( const std::string & file_r ) const
1145 {
1146   librpmDb::db_const_iterator it;
1147   return it.findByFile( file_r );
1148 }
1149
1150 ///////////////////////////////////////////////////////////////////
1151 //
1152 //
1153 //      METHOD NAME : RpmDb::hasProvides
1154 //      METHOD TYPE : bool
1155 //
1156 //      DESCRIPTION :
1157 //
1158 bool RpmDb::hasProvides( const std::string & tag_r ) const
1159 {
1160   librpmDb::db_const_iterator it;
1161   return it.findByProvides( tag_r );
1162 }
1163
1164 ///////////////////////////////////////////////////////////////////
1165 //
1166 //
1167 //      METHOD NAME : RpmDb::hasRequiredBy
1168 //      METHOD TYPE : bool
1169 //
1170 //      DESCRIPTION :
1171 //
1172 bool RpmDb::hasRequiredBy( const std::string & tag_r ) const
1173 {
1174   librpmDb::db_const_iterator it;
1175   return it.findByRequiredBy( tag_r );
1176 }
1177
1178 ///////////////////////////////////////////////////////////////////
1179 //
1180 //
1181 //      METHOD NAME : RpmDb::hasConflicts
1182 //      METHOD TYPE : bool
1183 //
1184 //      DESCRIPTION :
1185 //
1186 bool RpmDb::hasConflicts( const std::string & tag_r ) const
1187 {
1188   librpmDb::db_const_iterator it;
1189   return it.findByConflicts( tag_r );
1190 }
1191
1192 ///////////////////////////////////////////////////////////////////
1193 //
1194 //
1195 //      METHOD NAME : RpmDb::hasPackage
1196 //      METHOD TYPE : bool
1197 //
1198 //      DESCRIPTION :
1199 //
1200 bool RpmDb::hasPackage( const string & name_r ) const
1201 {
1202   librpmDb::db_const_iterator it;
1203   return it.findPackage( name_r );
1204 }
1205
1206 ///////////////////////////////////////////////////////////////////
1207 //
1208 //
1209 //      METHOD NAME : RpmDb::getData
1210 //      METHOD TYPE : PMError
1211 //
1212 //      DESCRIPTION :
1213 //
1214 void RpmDb::getData( const string & name_r,
1215                         RpmHeader::constPtr & result_r ) const
1216 {
1217   librpmDb::db_const_iterator it;
1218   it.findPackage( name_r );
1219   result_r = *it;
1220   if (it.dbError())
1221     ZYPP_THROW(*(it.dbError()));
1222 }
1223
1224 ///////////////////////////////////////////////////////////////////
1225 //
1226 //
1227 //      METHOD NAME : RpmDb::getData
1228 //      METHOD TYPE : PMError
1229 //
1230 //      DESCRIPTION :
1231 //
1232 void RpmDb::getData( const std::string & name_r, const Edition & ed_r,
1233                         RpmHeader::constPtr & result_r ) const
1234 {
1235   librpmDb::db_const_iterator it;
1236   it.findPackage( name_r, ed_r  );
1237   result_r = *it;
1238   if (it.dbError())
1239     ZYPP_THROW(*(it.dbError()));
1240 }
1241
1242 /*--------------------------------------------------------------*/
1243 /* Checking the source rpm <rpmpath> with rpm --chcksig and     */
1244 /* the version number.                                          */
1245 /*--------------------------------------------------------------*/
1246 unsigned
1247 RpmDb::checkPackage (const Pathname & packagePath, string version, string md5 )
1248 {
1249     unsigned result = 0;
1250
1251     if ( ! version.empty() ) {
1252       RpmHeader::constPtr h( RpmHeader::readPackage( packagePath, RpmHeader::NOSIGNATURE ) );
1253       if ( ! h || Edition( version ) != h->tag_edition() ) {
1254         result |= CHK_INCORRECT_VERSION;
1255       }
1256     }
1257
1258     if(!md5.empty())
1259     {
1260 #warning TBD MD5 check
1261         WAR << "md5sum check not yet implemented" << endl;
1262         return CHK_INCORRECT_FILEMD5;
1263     }
1264
1265     std::string path = packagePath.asString();
1266     // checking --checksig
1267     const char *const argv[] = {
1268         "rpm", "--checksig", "--", path.c_str(), 0
1269     };
1270
1271     exit_code = -1;
1272
1273     string output = "";
1274     unsigned int k;
1275     for ( k = 0; k < (sizeof(argv) / sizeof(*argv)) -1; k++ )
1276     {
1277         output = output + " " + argv[k];
1278     }
1279
1280     DBG << "rpm command: " << output << endl;
1281
1282     if ( process != NULL )
1283     {
1284         delete process;
1285         process = NULL;
1286     }
1287     // Launch the program
1288     process = new ExternalProgram( argv, ExternalProgram::Stderr_To_Stdout, false, -1, true);
1289
1290
1291     if ( process == NULL )
1292     {
1293         result |= CHK_OTHER_FAILURE;
1294         DBG << "create process failed" << endl;
1295     }
1296
1297     string value;
1298     output = process->receiveLine();
1299
1300     while ( output.length() > 0)
1301     {
1302         string::size_type         ret;
1303
1304         // extract \n
1305         ret = output.find_first_of ( "\n" );
1306         if ( ret != string::npos )
1307         {
1308             value.assign ( output, 0, ret );
1309         }
1310         else
1311         {
1312             value = output;
1313         }
1314
1315         DBG << "stdout: " << value << endl;
1316
1317         string::size_type pos;
1318         if((pos = value.find (path)) != string::npos)
1319         {
1320             string rest = value.substr (pos + path.length() + 1);
1321             if (rest.find("NOT OK") == string::npos)
1322             {
1323                 // see what checks are ok
1324                 if (rest.find("md5") == string::npos)
1325                 {
1326                     result |= CHK_MD5SUM_MISSING;
1327                 }
1328                 if (rest.find("gpg") == string::npos)
1329                 {
1330                     result |= CHK_GPGSIG_MISSING;
1331                 }
1332             }
1333             else
1334             {
1335                 // see what checks are not ok
1336                 if (rest.find("MD5") != string::npos)
1337                 {
1338                     result |= CHK_INCORRECT_PKGMD5;
1339                 }
1340                 else
1341                 {
1342                     result |= CHK_MD5SUM_MISSING;
1343                 }
1344
1345                 if (rest.find("GPG") != string::npos)
1346                 {
1347                     result |= CHK_INCORRECT_GPGSIG;
1348                 }
1349                 else
1350                 {
1351                     result |= CHK_GPGSIG_MISSING;
1352                 }
1353             }
1354         }
1355
1356         output = process->receiveLine();
1357     }
1358
1359     if ( result == 0 && systemStatus() != 0 )
1360     {
1361         // error
1362         result |= CHK_OTHER_FAILURE;
1363     }
1364
1365     return ( result );
1366 }
1367
1368 // determine changed files of installed package
1369 bool
1370 RpmDb::queryChangedFiles(FileList & fileList, const string& packageName)
1371 {
1372     bool ok = true;
1373
1374     fileList.clear();
1375
1376     if( ! initialized() ) return false;
1377
1378     RpmArgVec opts;
1379
1380     opts.push_back ("-V");
1381     opts.push_back ("--nodeps");
1382     opts.push_back ("--noscripts");
1383     opts.push_back ("--nomd5");
1384     opts.push_back ("--");
1385     opts.push_back (packageName.c_str());
1386
1387     run_rpm (opts, ExternalProgram::Discard_Stderr);
1388
1389     if ( process == NULL )
1390         return false;
1391
1392     /* from rpm manpage
1393        5      MD5 sum
1394        S      File size
1395        L      Symlink
1396        T      Mtime
1397        D      Device
1398        U      User
1399        G      Group
1400        M      Mode (includes permissions and file type)
1401     */
1402
1403     string line;
1404     while (systemReadLine(line))
1405     {
1406         if (line.length() > 12 &&
1407             (line[0] == 'S' || line[0] == 's' ||
1408              (line[0] == '.' && line[7] == 'T')))
1409         {
1410             // file has been changed
1411             string filename;
1412
1413             filename.assign(line, 11, line.length() - 11);
1414             fileList.insert(filename);
1415         }
1416     }
1417
1418     systemStatus();
1419     // exit code ignored, rpm returns 1 no matter if package is installed or
1420     // not
1421
1422     return ok;
1423 }
1424
1425
1426
1427 /****************************************************************/
1428 /* private member-functions                                     */
1429 /****************************************************************/
1430
1431 /*--------------------------------------------------------------*/
1432 /* Run rpm with the specified arguments, handling stderr        */
1433 /* as specified  by disp                                        */
1434 /*--------------------------------------------------------------*/
1435 void
1436 RpmDb::run_rpm (const RpmArgVec& opts,
1437                 ExternalProgram::Stderr_Disposition disp)
1438 {
1439     if ( process ) {
1440         delete process;
1441         process = NULL;
1442     }
1443     exit_code = -1;
1444
1445     if ( ! initialized() ) {
1446         ZYPP_THROW(RpmDbNotOpenException());
1447     }
1448
1449     RpmArgVec args;
1450
1451     // always set root and dbpath
1452     args.push_back("rpm");
1453     args.push_back("--root");
1454     args.push_back(_root.asString().c_str());
1455     args.push_back("--dbpath");
1456     args.push_back(_dbPath.asString().c_str());
1457
1458     const char* argv[args.size() + opts.size() + 1];
1459
1460     const char** p = argv;
1461     p = copy (args.begin (), args.end (), p);
1462     p = copy (opts.begin (), opts.end (), p);
1463     *p = 0;
1464
1465     // Invalidate all outstanding database handles in case
1466     // the database gets modified.
1467     librpmDb::dbRelease( true );
1468
1469     // Launch the program with default locale
1470     process = new ExternalProgram(argv, disp, false, -1, true);
1471     return;
1472 }
1473
1474 /*--------------------------------------------------------------*/
1475 /* Read a line from the rpm process                             */
1476 /*--------------------------------------------------------------*/
1477 bool
1478 RpmDb::systemReadLine(string &line)
1479 {
1480     line.erase();
1481
1482     if ( process == NULL )
1483         return false;
1484
1485     line = process->receiveLine();
1486
1487     if (line.length() == 0)
1488         return false;
1489
1490     if (line[line.length() - 1] == '\n')
1491         line.erase(line.length() - 1);
1492
1493     return true;
1494 }
1495
1496 /*--------------------------------------------------------------*/
1497 /* Return the exit status of the rpm process, closing the       */
1498 /* connection if not already done                               */
1499 /*--------------------------------------------------------------*/
1500 int
1501 RpmDb::systemStatus()
1502 {
1503    if ( process == NULL )
1504       return -1;
1505
1506    exit_code = process->close();
1507    process->kill();
1508    delete process;
1509    process = 0;
1510
1511 //   DBG << "exit code " << exit_code << endl;
1512
1513   return exit_code;
1514 }
1515
1516 /*--------------------------------------------------------------*/
1517 /* Forcably kill the rpm process                                */
1518 /*--------------------------------------------------------------*/
1519 void
1520 RpmDb::systemKill()
1521 {
1522   if (process) process->kill();
1523 }
1524
1525
1526 // generate diff mails for config files
1527 void RpmDb::processConfigFiles(const string& line, const string& name, const char* typemsg, const char* difffailmsg, const char* diffgenmsg)
1528 {
1529     string msg = line.substr(9);
1530     string::size_type pos1 = string::npos;
1531     string::size_type pos2 = string::npos;
1532     string file1s, file2s;
1533     Pathname file1;
1534     Pathname file2;
1535
1536     pos1 = msg.find (typemsg);
1537     for (;;)
1538     {
1539         if( pos1 == string::npos )
1540             break;
1541
1542         pos2 = pos1 + strlen (typemsg);
1543
1544         if (pos2 >= msg.length() )
1545             break;
1546
1547         file1 = msg.substr (0, pos1);
1548         file2 = msg.substr (pos2);
1549
1550         file1s = file1.asString();
1551         file2s = file2.asString();
1552
1553         if (!_root.empty() && _root != "/")
1554         {
1555             file1 = _root + file1;
1556             file2 = _root + file2;
1557         }
1558
1559         string out;
1560         int ret = diffFiles (file1.asString(), file2.asString(), out, 25);
1561         if (ret)
1562         {
1563             Pathname file = _root + WARNINGMAILPATH;
1564             if (filesystem::assert_dir(file) != 0)
1565             {
1566                 ERR << "Could not create " << file.asString() << endl;
1567                 break;
1568             }
1569             file += Date(Date::now()).form("config_diff_%Y_%m_%d.log");
1570             ofstream notify(file.asString().c_str(), std::ios::out|std::ios::app);
1571             if(!notify)
1572             {
1573                 ERR << "Could not open " <<  file << endl;
1574                 break;
1575             }
1576
1577             // Translator: %s = name of an rpm package. A list of diffs follows
1578             // this message.
1579             notify << str::form(_("Changed configuration files for %s:"), name.c_str()) << endl;
1580             if(ret>1)
1581             {
1582                 ERR << "diff failed" << endl;
1583                 notify << str::form(difffailmsg,
1584                     file1s.c_str(), file2s.c_str()) << endl;
1585             }
1586             else
1587             {
1588                 notify << str::form(diffgenmsg,
1589                     file1s.c_str(), file2s.c_str()) << endl;
1590
1591                 // remove root for the viewer's pleasure (#38240)
1592                 if (!_root.empty() && _root != "/")
1593                 {
1594                     if(out.substr(0,4) == "--- ")
1595                     {
1596                         out.replace(4, file1.asString().length(), file1s);
1597                     }
1598                     string::size_type pos = out.find("\n+++ ");
1599                     if(pos != string::npos)
1600                     {
1601                         out.replace(pos+5, file2.asString().length(), file2s);
1602                     }
1603                 }
1604                 notify << out << endl;
1605             }
1606             notify.close();
1607             notify.open("/var/lib/update-messages/yast2-packagemanager.rpmdb.configfiles");
1608             notify.close();
1609         }
1610         else
1611         {
1612             WAR << "rpm created " << file2 << " but it is not different from " << file2 << endl;
1613         }
1614         break;
1615     }
1616 }
1617
1618 ///////////////////////////////////////////////////////////////////
1619 //
1620 //
1621 //      METHOD NAME : RpmDb::installPackage
1622 //      METHOD TYPE : PMError
1623 //
1624 void RpmDb::installPackage( const Pathname & filename, unsigned flags )
1625 {
1626   RpmInstallReport report;
1627   try {
1628     doInstallPackage(filename, flags, report);
1629   }
1630   catch (RpmException & excpt_r)
1631   {
1632     report.end(excpt_r);
1633     ZYPP_RETHROW(excpt_r);
1634   }
1635   report.end();
1636
1637 }
1638 void RpmDb::doInstallPackage( const Pathname & filename, unsigned flags, RpmInstallReport & report )
1639 {
1640     FAILIFNOTINITIALIZED;
1641     Logfile progresslog;
1642
1643     MIL << "RpmDb::installPackage(" << filename << "," << flags << ")" << endl;
1644
1645     // backup
1646     if ( _packagebackups ) {
1647 // FIXME      report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
1648       if ( ! backupPackage( filename ) ) {
1649         ERR << "backup of " << filename.asString() << " failed" << endl;
1650       }
1651 // FIXME status handling
1652       report.progress( 0 ); // allow 1% for backup creation.
1653     } else {
1654       report.progress( 100 );
1655     }
1656
1657     // run rpm
1658     RpmArgVec opts;
1659     if (flags & RPMINST_NOUPGRADE)
1660       opts.push_back("-i");
1661     else
1662       opts.push_back("-U");
1663     opts.push_back("--percent");
1664
1665     if (flags & RPMINST_NODIGEST)
1666         opts.push_back("--nodigest");
1667     if (flags & RPMINST_NOSIGNATURE)
1668         opts.push_back("--nosignature");
1669     if (flags & RPMINST_NODOCS)
1670         opts.push_back ("--excludedocs");
1671     if (flags & RPMINST_NOSCRIPTS)
1672         opts.push_back ("--noscripts");
1673     if (flags & RPMINST_FORCE)
1674         opts.push_back ("--force");
1675     if (flags & RPMINST_NODEPS)
1676         opts.push_back ("--nodeps");
1677     if(flags & RPMINST_IGNORESIZE)
1678         opts.push_back ("--ignoresize");
1679     if(flags & RPMINST_JUSTDB)
1680         opts.push_back ("--justdb");
1681
1682     opts.push_back("--");
1683     opts.push_back (filename.asString().c_str());
1684
1685     modifyDatabase(); // BEFORE run_rpm
1686     run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
1687
1688     string line;
1689     string rpmmsg;
1690     vector<string> configwarnings;
1691     vector<string> errorlines;
1692
1693     while (systemReadLine(line))
1694     {
1695         if (line.substr(0,2)=="%%")
1696         {
1697             int percent;
1698             sscanf (line.c_str () + 2, "%d", &percent);
1699             report.progress( percent );
1700         }
1701         else
1702             rpmmsg += line+'\n';
1703
1704         if( line.substr(0,8) == "warning:" )
1705         {
1706             configwarnings.push_back(line);
1707         }
1708     }
1709     int rpm_status = systemStatus();
1710
1711     // evaluate result
1712     for(vector<string>::iterator it = configwarnings.begin();
1713         it != configwarnings.end(); ++it)
1714     {
1715             processConfigFiles(*it, Pathname::basename(filename), " saved as ",
1716                 // %s = filenames
1717                 _("rpm saved %s as %s but it was impossible to determine the difference"),
1718                 // %s = filenames
1719                 _("rpm saved %s as %s.\nHere are the first 25 lines of difference:\n"));
1720             processConfigFiles(*it, Pathname::basename(filename), " created as ",
1721                 // %s = filenames
1722                 _("rpm created %s as %s but it was impossible to determine the difference"),
1723                 // %s = filenames
1724                 _("rpm created %s as %s.\nHere are the first 25 lines of difference:\n"));
1725     }
1726
1727     if ( rpm_status != 0 )  {
1728       // %s = filename of rpm package
1729       progresslog(/*timestamp*/true) << str::form(_("%s install failed"), Pathname::basename(filename).c_str()) << endl;
1730       progresslog() << _("rpm output:") << endl << rpmmsg << endl;
1731       ZYPP_THROW(RpmSubprocessException(string("RPM failed: ") + rpmmsg));
1732     } else {
1733       // %s = filename of rpm package
1734       progresslog(/*timestamp*/true) << str::form(_("%s installed ok"), Pathname::basename(filename).c_str()) << endl;
1735       if( ! rpmmsg.empty() ) {
1736         progresslog() << _("Additional rpm output:") << endl << rpmmsg << endl;
1737       }
1738     }
1739 }
1740
1741 ///////////////////////////////////////////////////////////////////
1742 //
1743 //
1744 //      METHOD NAME : RpmDb::removePackage
1745 //      METHOD TYPE : PMError
1746 //
1747 void RpmDb::removePackage( Package::constPtr package, unsigned flags )
1748 {
1749   return removePackage( package->name(), flags );
1750 }
1751
1752 ///////////////////////////////////////////////////////////////////
1753 //
1754 //
1755 //      METHOD NAME : RpmDb::removePackage
1756 //      METHOD TYPE : PMError
1757 //
1758 void RpmDb::removePackage( const string & name_r, unsigned flags )
1759 {
1760   RpmRemoveReport report;
1761   try {
1762     doRemovePackage(name_r, flags, report);
1763   }
1764   catch (RpmException & excpt_r)
1765   {
1766     report.end(excpt_r);
1767     ZYPP_RETHROW(excpt_r);
1768   }
1769   report.end();
1770 }
1771
1772
1773 void RpmDb::doRemovePackage( const string & name_r, unsigned flags, RpmRemoveReport & report )
1774 {
1775     FAILIFNOTINITIALIZED;
1776     Logfile progresslog;
1777
1778     MIL << "RpmDb::removePackage(" << name_r << "," << flags << ")" << endl;
1779
1780     // backup
1781     if ( _packagebackups ) {
1782 // FIXME solve this status report somehow
1783 //      report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
1784       if ( ! backupPackage( name_r ) ) {
1785         ERR << "backup of " << name_r << " failed" << endl;
1786       }
1787       report.progress( 0 );
1788     } else {
1789       report.progress( 100 );
1790     }
1791
1792     // run rpm
1793     RpmArgVec opts;
1794     opts.push_back("-e");
1795     opts.push_back("--allmatches");
1796
1797     if (flags & RPMINST_NOSCRIPTS)
1798         opts.push_back("--noscripts");
1799     if (flags & RPMINST_NODEPS)
1800         opts.push_back("--nodeps");
1801     if (flags & RPMINST_JUSTDB)
1802         opts.push_back("--justdb");
1803     if (flags & RPMINST_FORCE) {
1804       WAR << "IGNORE OPTION: 'rpm -e' does not support '--force'" << endl;
1805     }
1806
1807     opts.push_back("--");
1808     opts.push_back(name_r.c_str());
1809
1810     modifyDatabase(); // BEFORE run_rpm
1811     run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
1812
1813     string line;
1814     string rpmmsg;
1815
1816     // got no progress from command, so we fake it:
1817     // 5  - command started
1818     // 50 - command completed
1819     // 100 if no error
1820     report.progress( 5 );
1821     while (systemReadLine(line))
1822     {
1823         rpmmsg += line+'\n';
1824     }
1825     report.progress( 50 );
1826     int rpm_status = systemStatus();
1827
1828     if ( rpm_status != 0 ) {
1829       // %s = name of rpm package
1830       progresslog(/*timestamp*/true) << str::form(_("%s remove failed"), name_r.c_str()) << endl;
1831       progresslog() << _("rpm output:") << endl << rpmmsg << endl;
1832       ZYPP_THROW(RpmSubprocessException(string("RPM failed: ") + rpmmsg));
1833     } else {
1834       progresslog(/*timestamp*/true) << str::form(_("%s remove ok"), name_r.c_str()) << endl;
1835       if( ! rpmmsg.empty() ) {
1836         progresslog() << _("Additional rpm output:") << endl << rpmmsg << endl;
1837       }
1838     }
1839 }
1840
1841 string
1842 RpmDb::checkPackageResult2string(unsigned code)
1843 {
1844     string msg;
1845     // begin of line characters
1846     string bol = " - ";
1847     // end of line characters
1848     string eol = "\n";
1849     if(code == 0)
1850         return string(_("Ok"))+eol;
1851
1852     //translator: these are different kinds of how an rpm package can be broken
1853     msg = _("Package is not OK for the following reasons:");
1854     msg += eol;
1855
1856     if(code&CHK_INCORRECT_VERSION)
1857     {
1858         msg += bol;
1859         msg+=_("Package contains different version than expected");
1860         msg += eol;
1861     }
1862     if(code&CHK_INCORRECT_FILEMD5)
1863     {
1864         msg += bol;
1865         msg+=_("Package file has incorrect MD5 sum");
1866         msg += eol;
1867     }
1868     if(code&CHK_GPGSIG_MISSING)
1869     {
1870         msg += bol;
1871         msg+=_("Package is not signed");
1872         msg += eol;
1873     }
1874     if(code&CHK_MD5SUM_MISSING)
1875     {
1876         msg += bol;
1877         msg+=_("Package has no MD5 sum");
1878         msg += eol;
1879     }
1880     if(code&CHK_INCORRECT_GPGSIG)
1881     {
1882         msg += bol;
1883         msg+=_("Package has incorrect signature");
1884         msg += eol;
1885     }
1886     if(code&CHK_INCORRECT_PKGMD5)
1887     {
1888         msg += bol;
1889         msg+=_("Package archive has incorrect MD5 sum");
1890         msg += eol;
1891     }
1892     if(code&CHK_OTHER_FAILURE)
1893     {
1894         msg += bol;
1895         msg+=_("rpm failed for unkown reason, see log file");
1896         msg += eol;
1897     }
1898
1899     return msg;
1900 }
1901
1902 ///////////////////////////////////////////////////////////////////
1903 //
1904 //
1905 //      METHOD NAME : RpmDb::backupPackage
1906 //      METHOD TYPE : bool
1907 //
1908 bool RpmDb::backupPackage( const Pathname & filename )
1909 {
1910   RpmHeader::constPtr h( RpmHeader::readPackage( filename, RpmHeader::NOSIGNATURE ) );
1911   if( ! h )
1912     return false;
1913
1914   return backupPackage( h->tag_name() );
1915 }
1916
1917 ///////////////////////////////////////////////////////////////////
1918 //
1919 //
1920 //      METHOD NAME : RpmDb::backupPackage
1921 //      METHOD TYPE : bool
1922 //
1923 bool RpmDb::backupPackage(const string& packageName)
1924 {
1925     Logfile progresslog;
1926     bool ret = true;
1927     Pathname backupFilename;
1928     Pathname filestobackupfile = _root+_backuppath+FILEFORBACKUPFILES;
1929
1930     if (_backuppath.empty())
1931     {
1932         INT << "_backuppath empty" << endl;
1933         return false;
1934     }
1935
1936     FileList fileList;
1937
1938     if (!queryChangedFiles(fileList, packageName))
1939     {
1940         ERR << "Error while getting changed files for package " <<
1941             packageName << endl;
1942         return false;
1943     }
1944
1945     if (fileList.size() <= 0)
1946     {
1947         DBG <<  "package " <<  packageName << " not changed -> no backup" << endl;
1948         return true;
1949     }
1950
1951     if (filesystem::assert_dir(_root + _backuppath) != 0)
1952     {
1953         return false;
1954     }
1955
1956     {
1957         // build up archive name
1958         time_t currentTime = time(0);
1959         struct tm *currentLocalTime = localtime(&currentTime);
1960
1961         int date = (currentLocalTime->tm_year + 1900) * 10000
1962             + (currentLocalTime->tm_mon + 1) * 100
1963             + currentLocalTime->tm_mday;
1964
1965         int num = 0;
1966         do
1967         {
1968             backupFilename = _root + _backuppath
1969                 + str::form("%s-%d-%d.tar.gz",packageName.c_str(), date, num);
1970
1971         }
1972         while ( PathInfo(backupFilename).isExist() && num++ < 1000);
1973
1974         PathInfo pi(filestobackupfile);
1975         if(pi.isExist() && !pi.isFile())
1976         {
1977             ERR << filestobackupfile.asString() << " already exists and is no file" << endl;
1978             return false;
1979         }
1980
1981         std::ofstream fp ( filestobackupfile.asString().c_str(), std::ios::out|std::ios::trunc );
1982
1983         if(!fp)
1984         {
1985             ERR << "could not open " << filestobackupfile.asString() << endl;
1986             return false;
1987         }
1988
1989         for (FileList::const_iterator cit = fileList.begin();
1990             cit != fileList.end(); ++cit)
1991         {
1992             string name = *cit;
1993             if ( name[0] == '/' )
1994             {
1995                 // remove slash, file must be relative to -C parameter of tar
1996                 name = name.substr( 1 );
1997             }
1998             DBG << "saving file "<< name << endl;
1999             fp << name << endl;
2000         }
2001         fp.close();
2002
2003         const char* const argv[] =
2004         {
2005             "tar",
2006             "-czhP",
2007             "-C",
2008             _root.asString().c_str(),
2009             "--ignore-failed-read",
2010             "-f",
2011             backupFilename.asString().c_str(),
2012             "-T",
2013             filestobackupfile.asString().c_str(),
2014             NULL
2015         };
2016
2017         // execute tar in inst-sys (we dont know if there is a tar below _root !)
2018         ExternalProgram tar(argv, ExternalProgram::Stderr_To_Stdout, false, -1, true);
2019
2020         string tarmsg;
2021
2022         // TODO: its probably possible to start tar with -v and watch it adding
2023         // files to report progress
2024         for (string output = tar.receiveLine(); output.length() ;output = tar.receiveLine())
2025         {
2026             tarmsg+=output;
2027         }
2028
2029         int ret = tar.close();
2030
2031         if ( ret != 0)
2032         {
2033             ERR << "tar failed: " << tarmsg << endl;
2034             ret = false;
2035         }
2036         else
2037         {
2038             MIL << "tar backup ok" << endl;
2039             progresslog(/*timestamp*/true) << str::form(_("created backup %s"), backupFilename.asString().c_str()) << endl;
2040         }
2041
2042         filesystem::unlink(filestobackupfile);
2043     }
2044
2045     return ret;
2046 }
2047
2048 void RpmDb::setBackupPath(const Pathname& path)
2049 {
2050     _backuppath = path;
2051 }
2052
2053     } // namespace rpm
2054   } // namespace target
2055 } // namespace zypp