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