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