backup commit
[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 constlibrpmDbPtr & v4db_r );
480 #warning FIXME exception handling for following function
481       convertV3toV4( dbInfo.dbV3().path(), dbptr );
482
483       // create a backup copy
484       int res = filesystem::copy( dbInfo.dbV3().path(), dbInfo.dbV3ToV4().path() );
485       if ( res ) {
486         WAR << "Backup converted rpm3 database failed: error(" << res << ")" << endl;
487       } else {
488         dbInfo.restat();
489         if ( dbInfo.hasDbV3ToV4() ) {
490           MIL << "Backup converted rpm3 database: " << dbInfo.dbV3ToV4() << endl;
491           dbsi_set( info_r, DbSI_HAVE_V3TOV4 | DbSI_MADE_V3TOV4 );
492         }
493       }
494
495     } else {
496
497       WAR << "Non empty rpm3 and rpm4 database found: using rpm4" << endl;
498 #warning EXCEPTION: nonempty rpm4 and rpm3 database found.
499       //ConvertDbReport::Send report( RpmDbCallbacks::convertDbReport );
500       //report->start( dbInfo.dbV3().path() );
501       //report->stop( some error );
502
503       // set DbSI_MODIFIED_V4 as it's not a temporary which can be removed.
504       dbsi_set( info_r, DbSI_MODIFIED_V4 );
505
506     }
507
508     DBG << "Convert state: " << info_r << ": " << stringPath( root_r, dbPath_r );
509     librpmDb::dumpState( DBG ) << endl;
510   }
511
512   if ( dbInfo.hasDbV3ToV4() ) {
513     MIL << "Rpm3 database backup: " << dbInfo.dbV3ToV4() << endl;
514   }
515 }
516
517 ///////////////////////////////////////////////////////////////////
518 //
519 //
520 //      METHOD NAME : RpmDb::removeV4
521 //      METHOD TYPE : void
522 //
523 void RpmDb::removeV4( const Pathname & dbdir_r, bool v3backup_r )
524 {
525   const char * v3backup = "packages.rpm3";
526   const char * master = "Packages";
527   const char * index[] = {
528     "Basenames",
529     "Conflictname",
530     "Depends",
531     "Dirnames",
532     "Filemd5s",
533     "Group",
534     "Installtid",
535     "Name",
536     "Providename",
537     "Provideversion",
538     "Pubkeys",
539     "Requirename",
540     "Requireversion",
541     "Sha1header",
542     "Sigmd5",
543     "Triggername",
544     // last entry!
545     NULL
546   };
547
548   PathInfo pi( dbdir_r );
549   if ( ! pi.isDir() ) {
550     ERR << "Can't remove rpm4 database in non directory: " << dbdir_r << endl;
551     return;
552   }
553
554   for ( const char ** f = index; *f; ++f ) {
555     pi( dbdir_r + *f );
556     if ( pi.isFile() ) {
557       filesystem::unlink( pi.path() );
558     }
559   }
560
561   pi( dbdir_r + master );
562   if ( pi.isFile() ) {
563     MIL << "Removing rpm4 database " << pi << endl;
564     filesystem::unlink( pi.path() );
565   }
566
567   if ( v3backup_r ) {
568     pi( dbdir_r + v3backup );
569     if ( pi.isFile() ) {
570       MIL << "Removing converted rpm3 database backup " << pi << endl;
571       filesystem::unlink( pi.path() );
572     }
573   }
574 }
575
576 ///////////////////////////////////////////////////////////////////
577 //
578 //
579 //      METHOD NAME : RpmDb::removeV3
580 //      METHOD TYPE : void
581 //
582 void RpmDb::removeV3( const Pathname & dbdir_r, bool v3backup_r )
583 {
584   const char * master = "packages.rpm";
585   const char * index[] = {
586     "conflictsindex.rpm",
587     "fileindex.rpm",
588     "groupindex.rpm",
589     "nameindex.rpm",
590     "providesindex.rpm",
591     "requiredby.rpm",
592     "triggerindex.rpm",
593     // last entry!
594     NULL
595   };
596
597   PathInfo pi( dbdir_r );
598   if ( ! pi.isDir() ) {
599     ERR << "Can't remove rpm3 database in non directory: " << dbdir_r << endl;
600     return;
601   }
602
603   for ( const char ** f = index; *f; ++f ) {
604     pi( dbdir_r + *f );
605     if ( pi.isFile() ) {
606       filesystem::unlink( pi.path() );
607     }
608   }
609
610 #warning CHECK: compare vs existing v3 backup. notify root
611   pi( dbdir_r + master );
612   if ( pi.isFile() ) {
613     Pathname m( pi.path() );
614     if ( v3backup_r ) {
615       // backup was already created
616       filesystem::unlink( m );
617       Pathname b( m.extend( "3" ) );
618       pi( b ); // stat backup
619     } else {
620       Pathname b( m.extend( ".deleted" ) );
621       pi( b );
622       if ( pi.isFile() ) {
623         // rempve existing backup
624         filesystem::unlink( b );
625       }
626       filesystem::rename( m, b );
627       pi( b ); // stat backup
628     }
629     MIL << "(Re)moved rpm3 database to " << pi << endl;
630   }
631 }
632
633 ///////////////////////////////////////////////////////////////////
634 //
635 //
636 //      METHOD NAME : RpmDb::modifyDatabase
637 //      METHOD TYPE : void
638 //
639 void RpmDb::modifyDatabase()
640 {
641   if ( ! initialized() )
642     return;
643
644   // tag database as modified
645   dbsi_set( _dbStateInfo, DbSI_MODIFIED_V4 );
646
647   // Move outdated rpm3 database beside.
648   if ( dbsi_has( _dbStateInfo, DbSI_HAVE_V3 ) ) {
649     MIL << "Update mode: Delayed cleanup: state " << _dbStateInfo << endl;
650     removeV3( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 ) );
651     dbsi_clr( _dbStateInfo, DbSI_HAVE_V3 );
652   }
653
654   // invalidate Packages list
655   _packages._valid = false;
656 }
657
658 ///////////////////////////////////////////////////////////////////
659 //
660 //
661 //      METHOD NAME : RpmDb::closeDatabase
662 //      METHOD TYPE : PMError
663 //
664 void RpmDb::closeDatabase()
665 {
666   if ( ! initialized() ) {
667     return;
668   }
669
670   MIL << "Calling closeDatabase: " << *this << endl;
671
672   ///////////////////////////////////////////////////////////////////
673   // Block further database access
674   ///////////////////////////////////////////////////////////////////
675   _packages.clear();
676   librpmDb::blockAccess();
677
678   ///////////////////////////////////////////////////////////////////
679   // Check fate if old version database still present
680   ///////////////////////////////////////////////////////////////////
681   if ( dbsi_has( _dbStateInfo, DbSI_HAVE_V3 ) ) {
682     MIL << "Update mode: Delayed cleanup: state " << _dbStateInfo << endl;
683     if ( dbsi_has( _dbStateInfo, DbSI_MODIFIED_V4 ) ) {
684       // Move outdated rpm3 database beside.
685       removeV3( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 )  );
686     } else {
687       // Remove unmodified rpm4 database
688       removeV4( _root + _dbPath, dbsi_has( _dbStateInfo, DbSI_MADE_V3TOV4 ) );
689     }
690   }
691
692   ///////////////////////////////////////////////////////////////////
693   // Uninit
694   ///////////////////////////////////////////////////////////////////
695   _root = _dbPath = Pathname();
696   _dbStateInfo = DbSI_NO_INIT;
697
698   MIL << "closeDatabase: " << *this << endl;
699 }
700
701 ///////////////////////////////////////////////////////////////////
702 //
703 //
704 //      METHOD NAME : RpmDb::rebuildDatabase
705 //      METHOD TYPE : PMError
706 //
707 void RpmDb::rebuildDatabase()
708 {
709   RebuildDbReport report;
710   try {
711     doRebuildDatabase(report);
712   }
713   catch (RpmException & excpt_r)
714   {
715     report.end(excpt_r);
716     ZYPP_RETHROW(excpt_r);
717   }
718   report.end();
719 }
720
721 void RpmDb::doRebuildDatabase(RebuildDbReport & report)
722 {
723   FAILIFNOTINITIALIZED;
724
725   MIL << "RpmDb::rebuildDatabase" << *this << endl;
726 // FIXME  Timecount _t( "RpmDb::rebuildDatabase" );
727
728   PathInfo dbMaster( root() + dbPath() + "Packages" );
729   PathInfo dbMasterBackup( dbMaster.path().extend( ".y2backup" ) );
730
731   // run rpm
732   RpmArgVec opts;
733   opts.push_back("--rebuilddb");
734   opts.push_back("-vv");
735
736   // don't call modifyDatabase because it would remove the old
737   // rpm3 database, if the current database is a temporary one.
738   // But do invalidate packages list.
739   _packages._valid = false;
740   run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
741
742   // progress report: watch this file growing
743   PathInfo newMaster( root()
744                       + dbPath().extend( str::form( "rebuilddb.%d",
745                                                            process?process->getpid():0) )
746                       + "Packages" );
747
748   string       line;
749   string       errmsg;
750
751   while ( systemReadLine( line ) ) {
752     if ( newMaster() ) { // file is removed at the end of rebuild.
753       // current size should be upper limit for new db
754       report.progress( (100 * newMaster.size()) / dbMaster.size());
755     }
756
757     if ( line.compare( 0, 2, "D:" ) ) {
758       errmsg += line + '\n';
759 //      report.notify( line );
760       WAR << line << endl;
761     }
762   }
763
764   int rpm_status = systemStatus();
765
766   if ( rpm_status != 0 ) {
767     ZYPP_THROW(RpmSubprocessException(string("rpm failed with message: ") + errmsg));
768   } else {
769     report.progress( 100 ); // 100%
770   }
771 }
772
773 ///////////////////////////////////////////////////////////////////
774 //
775 //
776 //      METHOD NAME : RpmDb::importPubkey
777 //      METHOD TYPE : PMError
778 //
779 void RpmDb::importPubkey( const Pathname & pubkey_r )
780 {
781   FAILIFNOTINITIALIZED;
782
783   RpmArgVec opts;
784   opts.push_back ( "--import" );
785   opts.push_back ( "--" );
786   opts.push_back ( pubkey_r.asString().c_str() );
787
788   // don't call modifyDatabase because it would remove the old
789   // rpm3 database, if the current database is a temporary one.
790   // But do invalidate packages list.
791   _packages._valid = false;
792   run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
793
794   string line;
795   while ( systemReadLine( line ) ) {
796     if ( line.substr( 0, 6 ) == "error:" ) {
797       WAR << line << endl;
798     } else {
799       DBG << line << endl;
800     }
801   }
802
803   int rpm_status = systemStatus();
804
805   if ( rpm_status != 0 ) {
806     ZYPP_THROW(RpmSubprocessException(string("Failed to import public key from file ") + pubkey_r.asString() + string(": rpm returned  ") + str::numstring(rpm_status)));
807   } else {
808     MIL << "Imported public key from file " << pubkey_r << endl;
809   }
810 }
811
812 ///////////////////////////////////////////////////////////////////
813 //
814 //
815 //      METHOD NAME : RpmDb::importPubkey
816 //      METHOD TYPE : PMError
817 //
818 void RpmDb::importPubkey( const Pathname & keyring_r, const string & keyname_r )
819 {
820   FAILIFNOTINITIALIZED;
821
822   // create tempfile
823   char tmpname[] = "/tmp/zypp.pubkey.XXXXXX";
824   int tmpfd = mkstemp( tmpname );
825   if ( tmpfd == -1 ) {
826     ZYPP_THROW(RpmSubprocessException("Unable to create a unique temporary file for pubkey"));
827   }
828
829   // export keyname from keyring
830   RpmArgVec args;
831   args.push_back( "gpg" );
832   args.push_back( "--armor" );
833   args.push_back( "--no-default-keyring" );
834   args.push_back( "--keyring" );
835   args.push_back( keyring_r.asString().c_str() );
836   args.push_back( "--export" );
837   args.push_back( keyname_r.c_str() );
838
839   const char * argv[args.size() + 1];
840   const char ** p = argv;
841   p = copy( args.begin(), args.end(), p );
842   *p = 0;
843
844   // launch gpg
845   ExternalProgram prg( argv, ExternalProgram::Discard_Stderr, false, -1, true );
846   int res = 0;
847
848   // read key
849
850   try {
851     for ( string line( prg.receiveLine() ); line.length(); line = prg.receiveLine() ) {
852       ssize_t written = write( tmpfd, line.c_str(), line.length() );
853       if ( written == -1 || unsigned(written) != line.length() ) {
854         ZYPP_THROW(RpmSubprocessException(string("Error writing pubkey to ") + tmpname));
855       }
856       res += written; // empty file indicates key not found
857     }
858   }
859   catch (RpmException & excpt_r)
860   {
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 #warning FIXME handle exception from line below
881   importPubkey( tmpname );
882   filesystem::unlink( tmpname );
883 }
884
885 ///////////////////////////////////////////////////////////////////
886 //
887 //
888 //      METHOD NAME : RpmDb::pubkeys
889 //      METHOD TYPE : set<Edition>
890 //
891 set<Edition> RpmDb::pubkeys() const
892 {
893   set<Edition> ret;
894
895   librpmDb::db_const_iterator it;
896   for ( it.findByName( string( "gpg-pubkey" ) ); *it; ++it ) {
897     ret.insert( it->tag_edition() );
898   }
899
900   return ret;
901 }
902
903 ///////////////////////////////////////////////////////////////////
904 //
905 //
906 //      METHOD NAME : RpmDb::packagesValid
907 //      METHOD TYPE : bool
908 //
909 bool RpmDb::packagesValid() const
910 {
911   return( _packages._valid || ! initialized() );
912 }
913
914 ///////////////////////////////////////////////////////////////////
915 //
916 //
917 //      METHOD NAME : RpmDb::getPackages
918 //      METHOD TYPE : const std::list<Package::Ptr> &
919 //
920 //      DESCRIPTION :
921 //
922 const std::list<Package::Ptr> & RpmDb::getPackages()
923 {
924   ScanDbReport report;
925   try {
926     const std::list<Package::Ptr> & ret = doGetPackages(report);
927     report.end();
928     return ret;
929   }
930   catch (RpmException & excpt_r)
931   {
932     report.end(excpt_r);
933     ZYPP_RETHROW(excpt_r);
934   }
935 }
936
937
938 const std::list<Package::Ptr> & RpmDb::doGetPackages(ScanDbReport & report)
939 {
940   if ( packagesValid() ) {
941     return _packages._list;
942   }
943
944 // FIXME  Timecount _t( "RpmDb::getPackages" );
945
946 #warning how to detect corrupt db while reading.
947
948   _packages.clear();
949
950   ///////////////////////////////////////////////////////////////////
951   // Collect package data. A map is used to check whethere there are
952   // multiple entries for the same string. If so we consider the last
953   // one installed to be the one we're interesed in.
954   ///////////////////////////////////////////////////////////////////
955   unsigned expect = 0;
956   librpmDb::db_const_iterator iter; // findAll
957   {
958     // quick check
959     for ( ; *iter; ++iter ) {
960       ++expect;
961     }
962     if ( iter.dbError() ) {
963       ERR << "No database access: " << iter.dbError() << endl;
964       ZYPP_THROW(*(iter.dbError()));
965     }
966   }
967   unsigned current = 0;
968
969   for ( iter.findAll(); *iter; ++iter, ++current, report.progress( (100*current)/expect)) {
970
971     string name = iter->tag_name();
972     if ( name == string( "gpg-pubkey" ) ) {
973       // pseudo package filtered, as we can't handle multiple instances
974       // of 'gpg-pubkey-VERS-REL'.
975       continue;
976     }
977     Date installtime = iter->tag_installtime();
978     Package::Ptr & nptr = _packages._index[name]; // be shure to get a reference!
979
980     if ( nptr ) {
981       WAR << "Multiple entries for package '" << name << "' in rpmdb" << endl;
982       if ( nptr->installtime() > installtime )
983         continue;
984       // else overwrite previous entry
985     }
986
987     // create dataprovider and package
988     shared_ptr<RPMPackageImpl> impl(new RPMPackageImpl(*iter));
989     nptr = detail::makeResolvableFromImpl(
990       iter->tag_name(),
991       iter->tag_edition(),
992       iter->tag_arch(),
993       impl);
994
995     Dependencies _deps;
996     _deps.setProvides(iter->tag_provides ( & _filerequires ) );
997     _deps.setRequires ( iter->tag_requires ( &_filerequires ) );
998     _deps.setPrerequires ( iter->tag_prerequires ( &_filerequires ) );
999     _deps.setConflicts( iter->tag_conflicts( &_filerequires ) );
1000     _deps.setObsoletes( iter->tag_obsoletes( &_filerequires ) );
1001     nptr->setDeps(_deps);
1002   }
1003
1004   ///////////////////////////////////////////////////////////////////
1005   // Evaluate filerequires collected so far
1006   ///////////////////////////////////////////////////////////////////
1007   for( set<string>::iterator it = _filerequires.begin(); it != _filerequires.end(); ++it ) {
1008
1009     for ( iter.findByFile( *it ); *iter; ++iter ) {
1010       Package::Ptr pptr = _packages.lookup( iter->tag_name() );
1011       if ( !pptr ) {
1012         WAR << "rpmdb.findByFile returned unknown package " << *iter << endl;
1013         continue;
1014       }
1015       Dependencies _deps = pptr->deps();
1016 #warning Add FileDeps
1017 //      pptr->addProvides( *it );
1018       pptr->setDeps(_deps);
1019     }
1020
1021   }
1022
1023   ///////////////////////////////////////////////////////////////////
1024   // Build final packages list
1025   ///////////////////////////////////////////////////////////////////
1026   _packages.buildList();
1027   DBG << "Found installed packages: " << _packages._list.size() << endl;
1028   return _packages._list;
1029 }
1030
1031 #warning Uncomment this function
1032 #if 0
1033 ///////////////////////////////////////////////////////////////////
1034 //
1035 //
1036 //      METHOD NAME : RpmDb::traceFileRel
1037 //      METHOD TYPE : void
1038 //
1039 //      DESCRIPTION :
1040 //
1041 void RpmDb::traceFileRel( const PkgRelation & rel_r )
1042 {
1043   if ( ! rel_r.isFileRel() )
1044     return;
1045
1046   if ( ! _filerequires.insert( rel_r.name() ).second )
1047     return; // already got it in _filerequires
1048
1049   if ( ! _packages._valid )
1050     return; // collect only. Evaluated in first call to getPackages()
1051
1052   //
1053   // packages already initialized. Must check and insert here
1054   //
1055   librpmDb::db_const_iterator iter;
1056   if ( iter.dbError() ) {
1057     ERR << "No database access: " << iter.dbError() << endl;
1058     return;
1059   }
1060
1061   for ( iter.findByFile( rel_r.name() ); *iter; ++iter ) {
1062     Package::Ptr pptr = _packages.lookup( iter->tag_name() );
1063     if ( !pptr ) {
1064       WAR << "rpmdb.findByFile returned unpknown package " << *iter << endl;
1065       continue;
1066     }
1067     pptr->addProvides( rel_r.name() );
1068   }
1069 }
1070 #endif
1071
1072 ///////////////////////////////////////////////////////////////////
1073 //
1074 //
1075 //      METHOD NAME : RpmDb::hasFile
1076 //      METHOD TYPE : bool
1077 //
1078 //      DESCRIPTION :
1079 //
1080 bool RpmDb::hasFile( const std::string & file_r ) const
1081 {
1082   librpmDb::db_const_iterator it;
1083   return it.findByFile( file_r );
1084 }
1085
1086 ///////////////////////////////////////////////////////////////////
1087 //
1088 //
1089 //      METHOD NAME : RpmDb::hasProvides
1090 //      METHOD TYPE : bool
1091 //
1092 //      DESCRIPTION :
1093 //
1094 bool RpmDb::hasProvides( const std::string & tag_r ) const
1095 {
1096   librpmDb::db_const_iterator it;
1097   return it.findByProvides( tag_r );
1098 }
1099
1100 ///////////////////////////////////////////////////////////////////
1101 //
1102 //
1103 //      METHOD NAME : RpmDb::hasRequiredBy
1104 //      METHOD TYPE : bool
1105 //
1106 //      DESCRIPTION :
1107 //
1108 bool RpmDb::hasRequiredBy( const std::string & tag_r ) const
1109 {
1110   librpmDb::db_const_iterator it;
1111   return it.findByRequiredBy( tag_r );
1112 }
1113
1114 ///////////////////////////////////////////////////////////////////
1115 //
1116 //
1117 //      METHOD NAME : RpmDb::hasConflicts
1118 //      METHOD TYPE : bool
1119 //
1120 //      DESCRIPTION :
1121 //
1122 bool RpmDb::hasConflicts( const std::string & tag_r ) const
1123 {
1124   librpmDb::db_const_iterator it;
1125   return it.findByConflicts( tag_r );
1126 }
1127
1128 ///////////////////////////////////////////////////////////////////
1129 //
1130 //
1131 //      METHOD NAME : RpmDb::hasPackage
1132 //      METHOD TYPE : bool
1133 //
1134 //      DESCRIPTION :
1135 //
1136 bool RpmDb::hasPackage( const string & name_r ) const
1137 {
1138   librpmDb::db_const_iterator it;
1139   return it.findPackage( name_r );
1140 }
1141
1142 ///////////////////////////////////////////////////////////////////
1143 //
1144 //
1145 //      METHOD NAME : RpmDb::getData
1146 //      METHOD TYPE : PMError
1147 //
1148 //      DESCRIPTION :
1149 //
1150 void RpmDb::getData( const string & name_r,
1151                         RpmHeader::constPtr & result_r ) const
1152 {
1153   librpmDb::db_const_iterator it;
1154   it.findPackage( name_r );
1155   result_r = *it;
1156   if (it.dbError())
1157     ZYPP_THROW(*(it.dbError()));
1158 }
1159
1160 ///////////////////////////////////////////////////////////////////
1161 //
1162 //
1163 //      METHOD NAME : RpmDb::getData
1164 //      METHOD TYPE : PMError
1165 //
1166 //      DESCRIPTION :
1167 //
1168 void RpmDb::getData( const std::string & name_r, const Edition & ed_r,
1169                         RpmHeader::constPtr & result_r ) const
1170 {
1171   librpmDb::db_const_iterator it;
1172   it.findPackage( name_r, ed_r  );
1173   result_r = *it;
1174   if (it.dbError())
1175     ZYPP_THROW(*(it.dbError()));
1176 }
1177
1178 /*--------------------------------------------------------------*/
1179 /* Checking the source rpm <rpmpath> with rpm --chcksig and     */
1180 /* the version number.                                          */
1181 /*--------------------------------------------------------------*/
1182 unsigned
1183 RpmDb::checkPackage (const Pathname & packagePath, string version, string md5 )
1184 {
1185     unsigned result = 0;
1186
1187     if ( ! version.empty() ) {
1188       RpmHeader::constPtr h( RpmHeader::readPackage( packagePath, RpmHeader::NOSIGNATURE ) );
1189       if ( ! h || Edition( version ) != h->tag_edition() ) {
1190         result |= CHK_INCORRECT_VERSION;
1191       }
1192     }
1193
1194     if(!md5.empty())
1195     {
1196 #warning TBD MD5 check
1197         WAR << "md5sum check not yet implemented" << endl;
1198         return CHK_INCORRECT_FILEMD5;
1199     }
1200
1201     std::string path = packagePath.asString();
1202     // checking --checksig
1203     const char *const argv[] = {
1204         "rpm", "--checksig", "--", path.c_str(), 0
1205     };
1206
1207     exit_code = -1;
1208
1209     string output = "";
1210     unsigned int k;
1211     for ( k = 0; k < (sizeof(argv) / sizeof(*argv)) -1; k++ )
1212     {
1213         output = output + " " + argv[k];
1214     }
1215
1216     DBG << "rpm command: " << output << endl;
1217
1218     if ( process != NULL )
1219     {
1220         delete process;
1221         process = NULL;
1222     }
1223     // Launch the program
1224     process = new ExternalProgram( argv, ExternalProgram::Stderr_To_Stdout, false, -1, true);
1225
1226
1227     if ( process == NULL )
1228     {
1229         result |= CHK_OTHER_FAILURE;
1230         DBG << "create process failed" << endl;
1231     }
1232
1233     string value;
1234     output = process->receiveLine();
1235
1236     while ( output.length() > 0)
1237     {
1238         string::size_type         ret;
1239
1240         // extract \n
1241         ret = output.find_first_of ( "\n" );
1242         if ( ret != string::npos )
1243         {
1244             value.assign ( output, 0, ret );
1245         }
1246         else
1247         {
1248             value = output;
1249         }
1250
1251         DBG << "stdout: " << value << endl;
1252
1253         string::size_type pos;
1254         if((pos = value.find (path)) != string::npos)
1255         {
1256             string rest = value.substr (pos + path.length() + 1);
1257             if (rest.find("NOT OK") == string::npos)
1258             {
1259                 // see what checks are ok
1260                 if (rest.find("md5") == string::npos)
1261                 {
1262                     result |= CHK_MD5SUM_MISSING;
1263                 }
1264                 if (rest.find("gpg") == string::npos)
1265                 {
1266                     result |= CHK_GPGSIG_MISSING;
1267                 }
1268             }
1269             else
1270             {
1271                 // see what checks are not ok
1272                 if (rest.find("MD5") != string::npos)
1273                 {
1274                     result |= CHK_INCORRECT_PKGMD5;
1275                 }
1276                 else
1277                 {
1278                     result |= CHK_MD5SUM_MISSING;
1279                 }
1280
1281                 if (rest.find("GPG") != string::npos)
1282                 {
1283                     result |= CHK_INCORRECT_GPGSIG;
1284                 }
1285                 else
1286                 {
1287                     result |= CHK_GPGSIG_MISSING;
1288                 }
1289             }
1290         }
1291
1292         output = process->receiveLine();
1293     }
1294
1295     if ( result == 0 && systemStatus() != 0 )
1296     {
1297         // error
1298         result |= CHK_OTHER_FAILURE;
1299     }
1300
1301     return ( result );
1302 }
1303
1304 // determine changed files of installed package
1305 bool
1306 RpmDb::queryChangedFiles(FileList & fileList, const string& packageName)
1307 {
1308     bool ok = true;
1309
1310     fileList.clear();
1311
1312     if( ! initialized() ) return false;
1313
1314     RpmArgVec opts;
1315
1316     opts.push_back ("-V");
1317     opts.push_back ("--nodeps");
1318     opts.push_back ("--noscripts");
1319     opts.push_back ("--nomd5");
1320     opts.push_back ("--");
1321     opts.push_back (packageName.c_str());
1322
1323     run_rpm (opts, ExternalProgram::Discard_Stderr);
1324
1325     if ( process == NULL )
1326         return false;
1327
1328     /* from rpm manpage
1329        5      MD5 sum
1330        S      File size
1331        L      Symlink
1332        T      Mtime
1333        D      Device
1334        U      User
1335        G      Group
1336        M      Mode (includes permissions and file type)
1337     */
1338
1339     string line;
1340     while (systemReadLine(line))
1341     {
1342         if (line.length() > 12 &&
1343             (line[0] == 'S' || line[0] == 's' ||
1344              (line[0] == '.' && line[7] == 'T')))
1345         {
1346             // file has been changed
1347             string filename;
1348
1349             filename.assign(line, 11, line.length() - 11);
1350             fileList.insert(filename);
1351         }
1352     }
1353
1354     systemStatus();
1355     // exit code ignored, rpm returns 1 no matter if package is installed or
1356     // not
1357
1358     return ok;
1359 }
1360
1361
1362
1363 /****************************************************************/
1364 /* private member-functions                                     */
1365 /****************************************************************/
1366
1367 /*--------------------------------------------------------------*/
1368 /* Run rpm with the specified arguments, handling stderr        */
1369 /* as specified  by disp                                        */
1370 /*--------------------------------------------------------------*/
1371 void
1372 RpmDb::run_rpm (const RpmArgVec& opts,
1373                 ExternalProgram::Stderr_Disposition disp)
1374 {
1375     if ( process ) {
1376         delete process;
1377         process = NULL;
1378     }
1379     exit_code = -1;
1380
1381     if ( ! initialized() ) {
1382         ZYPP_THROW(RpmDbNotOpenException());
1383     }
1384
1385     RpmArgVec args;
1386
1387     // always set root and dbpath
1388     args.push_back("rpm");
1389     args.push_back("--root");
1390     args.push_back(_root.asString().c_str());
1391     args.push_back("--dbpath");
1392     args.push_back(_dbPath.asString().c_str());
1393
1394     const char* argv[args.size() + opts.size() + 1];
1395
1396     const char** p = argv;
1397     p = copy (args.begin (), args.end (), p);
1398     p = copy (opts.begin (), opts.end (), p);
1399     *p = 0;
1400
1401     // Invalidate all outstanding database handles in case
1402     // the database gets modified.
1403     librpmDb::dbRelease( true );
1404
1405     // Launch the program with default locale
1406     process = new ExternalProgram(argv, disp, false, -1, true);
1407     return;
1408 }
1409
1410 /*--------------------------------------------------------------*/
1411 /* Read a line from the rpm process                             */
1412 /*--------------------------------------------------------------*/
1413 bool
1414 RpmDb::systemReadLine(string &line)
1415 {
1416     line.erase();
1417
1418     if ( process == NULL )
1419         return false;
1420
1421     line = process->receiveLine();
1422
1423     if (line.length() == 0)
1424         return false;
1425
1426     if (line[line.length() - 1] == '\n')
1427         line.erase(line.length() - 1);
1428
1429     return true;
1430 }
1431
1432 /*--------------------------------------------------------------*/
1433 /* Return the exit status of the rpm process, closing the       */
1434 /* connection if not already done                               */
1435 /*--------------------------------------------------------------*/
1436 int
1437 RpmDb::systemStatus()
1438 {
1439    if ( process == NULL )
1440       return -1;
1441
1442    exit_code = process->close();
1443    process->kill();
1444    delete process;
1445    process = 0;
1446
1447 //   DBG << "exit code " << exit_code << endl;
1448
1449   return exit_code;
1450 }
1451
1452 /*--------------------------------------------------------------*/
1453 /* Forcably kill the rpm process                                */
1454 /*--------------------------------------------------------------*/
1455 void
1456 RpmDb::systemKill()
1457 {
1458   if (process) process->kill();
1459 }
1460
1461
1462 // generate diff mails for config files
1463 void RpmDb::processConfigFiles(const string& line, const string& name, const char* typemsg, const char* difffailmsg, const char* diffgenmsg)
1464 {
1465     string msg = line.substr(9);
1466     string::size_type pos1 = string::npos;
1467     string::size_type pos2 = string::npos;
1468     string file1s, file2s;
1469     Pathname file1;
1470     Pathname file2;
1471
1472     pos1 = msg.find (typemsg);
1473     for (;;)
1474     {
1475         if( pos1 == string::npos )
1476             break;
1477
1478         pos2 = pos1 + strlen (typemsg);
1479
1480         if (pos2 >= msg.length() )
1481             break;
1482
1483         file1 = msg.substr (0, pos1);
1484         file2 = msg.substr (pos2);
1485
1486         file1s = file1.asString();
1487         file2s = file2.asString();
1488
1489         if (!_root.empty() && _root != "/")
1490         {
1491             file1 = _root + file1;
1492             file2 = _root + file2;
1493         }
1494
1495         string out;
1496 #warning FIXME the diffing functionality
1497 #if 0
1498         int ret = Diff::differ (file1.asString(), file2.asString(), out, 25);
1499         if (ret)
1500         {
1501             Pathname file = _root + WARNINGMAILPATH;
1502             if (filesystem::assert_dir(file) != 0)
1503             {
1504                 ERR << "Could not create " << file.asString() << endl;
1505                 break;
1506             }
1507             file += Date(Date::now()).form("config_diff_%Y_%m_%d.log");
1508             ofstream notify(file.asString().c_str(), std::ios::out|std::ios::app);
1509             if(!notify)
1510             {
1511                 ERR << "Could not open " <<  file << endl;
1512                 break;
1513             }
1514
1515             // Translator: %s = name of an rpm package. A list of diffs follows
1516             // this message.
1517             notify << str::form(_("Changed configuration files for %s:"), name.c_str()) << endl;
1518             if(ret>1)
1519             {
1520                 ERR << "diff failed" << endl;
1521                 notify << str::form(difffailmsg,
1522                     file1s.c_str(), file2s.c_str()) << endl;
1523             }
1524             else
1525             {
1526                 notify << str::form(diffgenmsg,
1527                     file1s.c_str(), file2s.c_str()) << endl;
1528
1529                 // remove root for the viewer's pleasure (#38240)
1530                 if (!_root.empty() && _root != "/")
1531                 {
1532                     if(out.substr(0,4) == "--- ")
1533                     {
1534                         out.replace(4, file1.asString().length(), file1s);
1535                     }
1536                     string::size_type pos = out.find("\n+++ ");
1537                     if(pos != string::npos)
1538                     {
1539                         out.replace(pos+5, file2.asString().length(), file2s);
1540                     }
1541                 }
1542                 notify << out << endl;
1543             }
1544             notify.close();
1545             notify.open("/var/lib/update-messages/yast2-packagemanager.rpmdb.configfiles");
1546             notify.close();
1547         }
1548         else
1549         {
1550             WAR << "rpm created " << file2 << " but it is not different from " << file2 << endl;
1551         }
1552 #endif
1553         break;
1554     }
1555 }
1556
1557 ///////////////////////////////////////////////////////////////////
1558 //
1559 //
1560 //      METHOD NAME : RpmDb::installPackage
1561 //      METHOD TYPE : PMError
1562 //
1563 void RpmDb::installPackage( const Pathname & filename, unsigned flags )
1564 {
1565   RpmInstallReport report;
1566   try {
1567     doInstallPackage(filename, flags, report);
1568   }
1569   catch (RpmException & excpt_r)
1570   {
1571     report.end(excpt_r);
1572     ZYPP_RETHROW(excpt_r);
1573   }
1574   report.end();
1575
1576 }
1577 void RpmDb::doInstallPackage( const Pathname & filename, unsigned flags, RpmInstallReport & report )
1578 {
1579     FAILIFNOTINITIALIZED;
1580     Logfile progresslog;
1581
1582     MIL << "RpmDb::installPackage(" << filename << "," << flags << ")" << endl;
1583
1584     // backup
1585     if ( _packagebackups ) {
1586 // FIXME      report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
1587       if ( ! backupPackage( filename ) ) {
1588         ERR << "backup of " << filename.asString() << " failed" << endl;
1589       }
1590 // FIXME status handling
1591       report.progress( 0 ); // allow 1% for backup creation.
1592     } else {
1593       report.progress( 100 );
1594     }
1595
1596     // run rpm
1597     RpmArgVec opts;
1598     if (flags & RPMINST_NOUPGRADE)
1599       opts.push_back("-i");
1600     else
1601       opts.push_back("-U");
1602     opts.push_back("--percent");
1603
1604     if (flags & RPMINST_NODIGEST)
1605         opts.push_back("--nodigest");
1606     if (flags & RPMINST_NOSIGNATURE)
1607         opts.push_back("--nosignature");
1608     if (flags & RPMINST_NODOCS)
1609         opts.push_back ("--excludedocs");
1610     if (flags & RPMINST_NOSCRIPTS)
1611         opts.push_back ("--noscripts");
1612     if (flags & RPMINST_FORCE)
1613         opts.push_back ("--force");
1614     if (flags & RPMINST_NODEPS)
1615         opts.push_back ("--nodeps");
1616     if(flags & RPMINST_IGNORESIZE)
1617         opts.push_back ("--ignoresize");
1618     if(flags & RPMINST_JUSTDB)
1619         opts.push_back ("--justdb");
1620
1621     opts.push_back("--");
1622     opts.push_back (filename.asString().c_str());
1623
1624     modifyDatabase(); // BEFORE run_rpm
1625     run_rpm( opts, ExternalProgram::Stderr_To_Stdout );
1626
1627     string line;
1628     string rpmmsg;
1629     vector<string> configwarnings;
1630     vector<string> errorlines;
1631
1632     while (systemReadLine(line))
1633     {
1634         if (line.substr(0,2)=="%%")
1635         {
1636             int percent;
1637             sscanf (line.c_str () + 2, "%d", &percent);
1638             report.progress( percent );
1639         }
1640         else
1641             rpmmsg += line+'\n';
1642
1643         if( line.substr(0,8) == "warning:" )
1644         {
1645             configwarnings.push_back(line);
1646         }
1647     }
1648     int rpm_status = systemStatus();
1649
1650     // evaluate result
1651     for(vector<string>::iterator it = configwarnings.begin();
1652         it != configwarnings.end(); ++it)
1653     {
1654             processConfigFiles(*it, Pathname::basename(filename), " saved as ",
1655                 // %s = filenames
1656                 _("rpm saved %s as %s but it was impossible to determine the difference"),
1657                 // %s = filenames
1658                 _("rpm saved %s as %s.\nHere are the first 25 lines of difference:\n"));
1659             processConfigFiles(*it, Pathname::basename(filename), " created as ",
1660                 // %s = filenames
1661                 _("rpm created %s as %s but it was impossible to determine the difference"),
1662                 // %s = filenames
1663                 _("rpm created %s as %s.\nHere are the first 25 lines of difference:\n"));
1664     }
1665
1666     if ( rpm_status != 0 )  {
1667       // %s = filename of rpm package
1668       progresslog(/*timestamp*/true) << str::form(_("%s install failed"), Pathname::basename(filename).c_str()) << endl;
1669       progresslog() << _("rpm output:") << endl << rpmmsg << endl;
1670       ZYPP_THROW(RpmSubprocessException(string("RPM failed: ") + rpmmsg));
1671     } else {
1672       // %s = filename of rpm package
1673       progresslog(/*timestamp*/true) << str::form(_("%s installed ok"), Pathname::basename(filename).c_str()) << endl;
1674       if( ! rpmmsg.empty() ) {
1675         progresslog() << _("Additional rpm output:") << endl << rpmmsg << endl;
1676       }
1677     }
1678 }
1679
1680 ///////////////////////////////////////////////////////////////////
1681 //
1682 //
1683 //      METHOD NAME : RpmDb::removePackage
1684 //      METHOD TYPE : PMError
1685 //
1686 void RpmDb::removePackage( Package::constPtr package, unsigned flags )
1687 {
1688   return removePackage( package->name(), flags );
1689 }
1690
1691 ///////////////////////////////////////////////////////////////////
1692 //
1693 //
1694 //      METHOD NAME : RpmDb::removePackage
1695 //      METHOD TYPE : PMError
1696 //
1697 void RpmDb::removePackage( const string & name_r, unsigned flags )
1698 {
1699   RpmRemoveReport report;
1700   try {
1701     doRemovePackage(name_r, flags, report);
1702   }
1703   catch (RpmException & excpt_r)
1704   {
1705     report.end(excpt_r);
1706     ZYPP_RETHROW(excpt_r);
1707   }
1708   report.end();
1709 }
1710
1711
1712 void RpmDb::doRemovePackage( const string & name_r, unsigned flags, RpmRemoveReport & report )
1713 {
1714     FAILIFNOTINITIALIZED;
1715     Logfile progresslog;
1716
1717     MIL << "RpmDb::removePackage(" << name_r << "," << flags << ")" << endl;
1718
1719     // backup
1720     if ( _packagebackups ) {
1721 // FIXME solve this status report somehow
1722 //      report->progress( pd.init( -2, 100 ) ); // allow 1% for backup creation.
1723       if ( ! backupPackage( name_r ) ) {
1724         ERR << "backup of " << name_r << " failed" << endl;
1725       }
1726       report.progress( 0 );
1727     } else {
1728       report.progress( 100 );
1729     }
1730
1731     // run rpm
1732     RpmArgVec opts;
1733     opts.push_back("-e");
1734     opts.push_back("--allmatches");
1735
1736     if (flags & RPMINST_NOSCRIPTS)
1737         opts.push_back("--noscripts");
1738     if (flags & RPMINST_NODEPS)
1739         opts.push_back("--nodeps");
1740     if (flags & RPMINST_JUSTDB)
1741         opts.push_back("--justdb");
1742     if (flags & RPMINST_FORCE) {
1743       WAR << "IGNORE OPTION: 'rpm -e' does not support '--force'" << endl;
1744     }
1745
1746     opts.push_back("--");
1747     opts.push_back(name_r.c_str());
1748
1749     modifyDatabase(); // BEFORE run_rpm
1750     run_rpm (opts, ExternalProgram::Stderr_To_Stdout);
1751
1752     string line;
1753     string rpmmsg;
1754
1755     // got no progress from command, so we fake it:
1756     // 5  - command started
1757     // 50 - command completed
1758     // 100 if no error
1759     report.progress( 5 );
1760     while (systemReadLine(line))
1761     {
1762         rpmmsg += line+'\n';
1763     }
1764     report.progress( 50 );
1765     int rpm_status = systemStatus();
1766
1767     if ( rpm_status != 0 ) {
1768       // %s = name of rpm package
1769       progresslog(/*timestamp*/true) << str::form(_("%s remove failed"), name_r.c_str()) << endl;
1770       progresslog() << _("rpm output:") << endl << rpmmsg << endl;
1771       ZYPP_THROW(RpmSubprocessException(string("RPM failed: ") + rpmmsg));
1772     } else {
1773       progresslog(/*timestamp*/true) << str::form(_("%s remove ok"), name_r.c_str()) << endl;
1774       if( ! rpmmsg.empty() ) {
1775         progresslog() << _("Additional rpm output:") << endl << rpmmsg << endl;
1776       }
1777     }
1778 }
1779
1780 string
1781 RpmDb::checkPackageResult2string(unsigned code)
1782 {
1783     string msg;
1784     // begin of line characters
1785     string bol = " - ";
1786     // end of line characters
1787     string eol = "\n";
1788     if(code == 0)
1789         return string(_("Ok"))+eol;
1790
1791     //translator: these are different kinds of how an rpm package can be broken
1792     msg = _("Package is not OK for the following reasons:");
1793     msg += eol;
1794
1795     if(code&CHK_INCORRECT_VERSION)
1796     {
1797         msg += bol;
1798         msg+=_("Package contains different version than expected");
1799         msg += eol;
1800     }
1801     if(code&CHK_INCORRECT_FILEMD5)
1802     {
1803         msg += bol;
1804         msg+=_("Package file has incorrect MD5 sum");
1805         msg += eol;
1806     }
1807     if(code&CHK_GPGSIG_MISSING)
1808     {
1809         msg += bol;
1810         msg+=_("Package is not signed");
1811         msg += eol;
1812     }
1813     if(code&CHK_MD5SUM_MISSING)
1814     {
1815         msg += bol;
1816         msg+=_("Package has no MD5 sum");
1817         msg += eol;
1818     }
1819     if(code&CHK_INCORRECT_GPGSIG)
1820     {
1821         msg += bol;
1822         msg+=_("Package has incorrect signature");
1823         msg += eol;
1824     }
1825     if(code&CHK_INCORRECT_PKGMD5)
1826     {
1827         msg += bol;
1828         msg+=_("Package archive has incorrect MD5 sum");
1829         msg += eol;
1830     }
1831     if(code&CHK_OTHER_FAILURE)
1832     {
1833         msg += bol;
1834         msg+=_("rpm failed for unkown reason, see log file");
1835         msg += eol;
1836     }
1837
1838     return msg;
1839 }
1840
1841 ///////////////////////////////////////////////////////////////////
1842 //
1843 //
1844 //      METHOD NAME : RpmDb::backupPackage
1845 //      METHOD TYPE : bool
1846 //
1847 bool RpmDb::backupPackage( const Pathname & filename )
1848 {
1849   RpmHeader::constPtr h( RpmHeader::readPackage( filename, RpmHeader::NOSIGNATURE ) );
1850   if( ! h )
1851     return false;
1852
1853   return backupPackage( h->tag_name() );
1854 }
1855
1856 ///////////////////////////////////////////////////////////////////
1857 //
1858 //
1859 //      METHOD NAME : RpmDb::backupPackage
1860 //      METHOD TYPE : bool
1861 //
1862 bool RpmDb::backupPackage(const string& packageName)
1863 {
1864     Logfile progresslog;
1865     bool ret = true;
1866     Pathname backupFilename;
1867     Pathname filestobackupfile = _root+_backuppath+FILEFORBACKUPFILES;
1868
1869     if (_backuppath.empty())
1870     {
1871         INT << "_backuppath empty" << endl;
1872         return false;
1873     }
1874
1875     FileList fileList;
1876
1877     if (!queryChangedFiles(fileList, packageName))
1878     {
1879         ERR << "Error while getting changed files for package " <<
1880             packageName << endl;
1881         return false;
1882     }
1883
1884     if (fileList.size() <= 0)
1885     {
1886         DBG <<  "package " <<  packageName << " not changed -> no backup" << endl;
1887         return true;
1888     }
1889
1890     if (filesystem::assert_dir(_root + _backuppath) != 0)
1891     {
1892         return false;
1893     }
1894
1895     {
1896         // build up archive name
1897         time_t currentTime = time(0);
1898         struct tm *currentLocalTime = localtime(&currentTime);
1899
1900         int date = (currentLocalTime->tm_year + 1900) * 10000
1901             + (currentLocalTime->tm_mon + 1) * 100
1902             + currentLocalTime->tm_mday;
1903
1904         int num = 0;
1905         do
1906         {
1907             backupFilename = _root + _backuppath
1908                 + str::form("%s-%d-%d.tar.gz",packageName.c_str(), date, num);
1909
1910         }
1911         while ( PathInfo(backupFilename).isExist() && num++ < 1000);
1912
1913         PathInfo pi(filestobackupfile);
1914         if(pi.isExist() && !pi.isFile())
1915         {
1916             ERR << filestobackupfile.asString() << " already exists and is no file" << endl;
1917             return false;
1918         }
1919
1920         std::ofstream fp ( filestobackupfile.asString().c_str(), std::ios::out|std::ios::trunc );
1921
1922         if(!fp)
1923         {
1924             ERR << "could not open " << filestobackupfile.asString() << endl;
1925             return false;
1926         }
1927
1928         for (FileList::const_iterator cit = fileList.begin();
1929             cit != fileList.end(); ++cit)
1930         {
1931             string name = *cit;
1932             if ( name[0] == '/' )
1933             {
1934                 // remove slash, file must be relative to -C parameter of tar
1935                 name = name.substr( 1 );
1936             }
1937             DBG << "saving file "<< name << endl;
1938             fp << name << endl;
1939         }
1940         fp.close();
1941
1942         const char* const argv[] =
1943         {
1944             "tar",
1945             "-czhP",
1946             "-C",
1947             _root.asString().c_str(),
1948             "--ignore-failed-read",
1949             "-f",
1950             backupFilename.asString().c_str(),
1951             "-T",
1952             filestobackupfile.asString().c_str(),
1953             NULL
1954         };
1955
1956         // execute tar in inst-sys (we dont know if there is a tar below _root !)
1957         ExternalProgram tar(argv, ExternalProgram::Stderr_To_Stdout, false, -1, true);
1958
1959         string tarmsg;
1960
1961         // TODO: its probably possible to start tar with -v and watch it adding
1962         // files to report progress
1963         for (string output = tar.receiveLine(); output.length() ;output = tar.receiveLine())
1964         {
1965             tarmsg+=output;
1966         }
1967
1968         int ret = tar.close();
1969
1970         if ( ret != 0)
1971         {
1972             ERR << "tar failed: " << tarmsg << endl;
1973             ret = false;
1974         }
1975         else
1976         {
1977             MIL << "tar backup ok" << endl;
1978             progresslog(/*timestamp*/true) << str::form(_("created backup %s"), backupFilename.asString().c_str()) << endl;
1979         }
1980
1981         filesystem::unlink(filestobackupfile);
1982     }
1983
1984     return ret;
1985 }
1986
1987 void RpmDb::setBackupPath(const Pathname& path)
1988 {
1989     _backuppath = path;
1990 }
1991
1992     } // namespace rpm
1993   } // namespace target
1994 } // namespace zypp