8ed7cfd44893aba8387e99d73fc6a3813676de5a
[platform/upstream/libzypp.git] / zypp / target / TargetImpl.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/target/TargetImpl.cc
10  *
11 */
12 #include <iostream>
13 #include <fstream>
14 #include <sstream>
15 #include <string>
16 #include <list>
17 #include <set>
18
19 #include <sys/types.h>
20 #include <dirent.h>
21
22 #include "zypp/base/LogTools.h"
23 #include "zypp/base/Exception.h"
24 #include "zypp/base/Iterator.h"
25 #include "zypp/base/Gettext.h"
26 #include "zypp/base/IOStream.h"
27 #include "zypp/base/Functional.h"
28 #include "zypp/base/UserRequestException.h"
29
30 #include "zypp/ZConfig.h"
31 #include "zypp/ZYppFactory.h"
32
33 #include "zypp/PoolItem.h"
34 #include "zypp/ResObjects.h"
35 #include "zypp/Url.h"
36 #include "zypp/TmpPath.h"
37 #include "zypp/RepoStatus.h"
38 #include "zypp/ExternalProgram.h"
39 #include "zypp/Repository.h"
40
41 #include "zypp/ResFilters.h"
42 #include "zypp/HistoryLog.h"
43 #include "zypp/target/TargetImpl.h"
44 #include "zypp/target/TargetCallbackReceiver.h"
45 #include "zypp/target/rpm/librpmDb.h"
46 #include "zypp/target/CommitPackageCache.h"
47
48 #include "zypp/parser/ProductFileReader.h"
49
50 #include "zypp/solver/detail/Testcase.h"
51
52 #include "zypp/repo/DeltaCandidates.h"
53 #include "zypp/repo/PackageProvider.h"
54 #include "zypp/repo/SrcPackageProvider.h"
55
56 #include "zypp/sat/Pool.h"
57 #include "zypp/sat/Transaction.h"
58
59 #include "zypp/PluginScript.h"
60
61 using namespace std;
62
63 ///////////////////////////////////////////////////////////////////
64 namespace zypp
65 { /////////////////////////////////////////////////////////////////
66   ///////////////////////////////////////////////////////////////////
67   namespace target
68   { /////////////////////////////////////////////////////////////////
69
70     /** Helper for commit plugin execution.
71      * \ingroup g_RAII
72      */
73     class CommitPlugins : private base::NonCopyable
74     {
75       public:
76
77       public:
78         /** Default ctor: Empty plugin list */
79         CommitPlugins()
80         {}
81
82         /** Dtor: Send PLUGINEND message and close plugins. */
83         ~CommitPlugins()
84         {
85           for_( it, _scripts.begin(), _scripts.end() )
86           {
87             MIL << "Unload plugin: " << *it << endl;
88             try {
89               it->send( PluginFrame( "PLUGINEND" ) );
90               PluginFrame ret( it->receive() );
91               if ( ! ret.isAckCommand() )
92               {
93                 WAR << "Failed to unload plugin: Bad plugin response." << endl;
94               }
95               it->close();
96             }
97             catch( const zypp::Exception &  )
98             {
99               WAR << "Failed to unload plugin." << endl;
100             }
101           }
102           // _scripts dtor will disconnect all remaining plugins!
103         }
104
105         /** Find and launch plugins sending PLUGINSTART message.
106          *
107          * If \a path_r is a directory all executable files whithin are
108          * expected to be plugins. Otherwise \a path_r must point to an
109          * executable plugin.
110          */
111         void load( const Pathname & path_r )
112         {
113           PathInfo pi( path_r );
114           if ( pi.isDir() )
115           {
116             std::list<Pathname> entries;
117             if ( filesystem::readdir( entries, pi.path(), false ) != 0 )
118             {
119               WAR << "Plugin dir is not readable: " << pi << endl;
120               return;
121             }
122             for_( it, entries.begin(), entries.end() )
123             {
124               PathInfo pii( *it );
125               if ( pii.isFile() && pii.userMayRX() )
126                 doLoad( pii );
127             }
128           }
129           else if ( pi.isFile() )
130           {
131             if ( pi.userMayRX() )
132               doLoad( pi );
133             else
134               WAR << "Plugin file is not executable: " << pi << endl;
135           }
136           else
137           {
138             WAR << "Plugin path is neither dir nor file: " << pi << endl;
139           }
140         }
141
142       private:
143         void doLoad( const PathInfo & pi_r )
144         {
145           MIL << "Load plugin: " << pi_r << endl;
146           try {
147             PluginFrame frame( "PLUGINBEGIN" );
148             if ( ZConfig::instance().hasUserData() )
149               frame.setHeader( "userdata", ZConfig::instance().userData() );
150
151             PluginScript plugin( pi_r.path() );
152             plugin.open();
153             plugin.send( frame );
154             PluginFrame ret( plugin.receive() );
155             if ( ret.isAckCommand() )
156             {
157               _scripts.push_back( plugin );
158             }
159             else
160             {
161               WAR << "Failed to load plugin: Bad plugin response." << endl;
162             }
163           }
164           catch( const zypp::Exception &  )
165           {
166              WAR << "Failed to load plugin." << endl;
167           }
168         }
169
170       private:
171         std::list<PluginScript> _scripts;
172     };
173
174     void testCommitPlugins( const Pathname & path_r ) // for testing only
175     {
176       USR << "+++++" << endl;
177       {
178         CommitPlugins pl;
179         pl.load( path_r );
180         USR << "=====" << endl;
181       }
182       USR << "-----" << endl;
183     }
184
185     ///////////////////////////////////////////////////////////////////
186
187     /** \internal Manage writing a new testcase when doing an upgrade. */
188     void writeUpgradeTestcase()
189     {
190       unsigned toKeep( ZConfig::instance().solver_upgradeTestcasesToKeep() );
191       MIL << "Testcases to keep: " << toKeep << endl;
192       if ( !toKeep )
193         return;
194       Target_Ptr target( getZYpp()->getTarget() );
195       if ( ! target )
196       {
197         WAR << "No Target no Testcase!" << endl;
198         return;
199       }
200
201       std::string stem( "updateTestcase" );
202       Pathname dir( target->assertRootPrefix("/var/log/") );
203       Pathname next( dir / Date::now().form( stem+"-%Y-%m-%d-%H-%M-%S" ) );
204
205       {
206         std::list<std::string> content;
207         filesystem::readdir( content, dir, /*dots*/false );
208         std::set<std::string> cases;
209         for_( c, content.begin(), content.end() )
210         {
211           if ( str::startsWith( *c, stem ) )
212             cases.insert( *c );
213         }
214         if ( cases.size() >= toKeep )
215         {
216           unsigned toDel = cases.size() - toKeep + 1; // +1 for the new one
217           for_( c, cases.begin(), cases.end() )
218           {
219             filesystem::recursive_rmdir( dir/(*c) );
220             if ( ! --toDel )
221               break;
222           }
223         }
224       }
225
226       MIL << "Write new testcase " << next << endl;
227       getZYpp()->resolver()->createSolverTestcase( next.asString(), false/*no solving*/ );
228     }
229
230     ///////////////////////////////////////////////////////////////////
231     namespace
232     { /////////////////////////////////////////////////////////////////
233
234       /** Execute script and report against report_r.
235        * Return \c std::pair<bool,PatchScriptReport::Action> to indicate if
236        * execution was successfull (<tt>first = true</tt>), or the desired
237        * \c PatchScriptReport::Action in case execution failed
238        * (<tt>first = false</tt>).
239        *
240        * \note The packager is responsible for setting the correct permissions
241        * of the script. If the script is not executable it is reported as an
242        * error. We must not modify the permessions.
243        */
244       std::pair<bool,PatchScriptReport::Action> doExecuteScript( const Pathname & root_r,
245                                                                  const Pathname & script_r,
246                                                                  callback::SendReport<PatchScriptReport> & report_r )
247       {
248         MIL << "Execute script " << PathInfo(Pathname::assertprefix( root_r,script_r)) << endl;
249
250         HistoryLog historylog;
251         historylog.comment(script_r.asString() + _(" executed"), /*timestamp*/true);
252         ExternalProgram prog( script_r.asString(), ExternalProgram::Stderr_To_Stdout, false, -1, true, root_r );
253
254         for ( std::string output = prog.receiveLine(); output.length(); output = prog.receiveLine() )
255         {
256           historylog.comment(output);
257           if ( ! report_r->progress( PatchScriptReport::OUTPUT, output ) )
258           {
259             WAR << "User request to abort script " << script_r << endl;
260             prog.kill();
261             // the rest is handled by exit code evaluation
262             // in case the script has meanwhile finished.
263           }
264         }
265
266         std::pair<bool,PatchScriptReport::Action> ret( std::make_pair( false, PatchScriptReport::ABORT ) );
267
268         if ( prog.close() != 0 )
269         {
270           ret.second = report_r->problem( prog.execError() );
271           WAR << "ACTION" << ret.second << "(" << prog.execError() << ")" << endl;
272           std::ostringstream sstr;
273           sstr << script_r << _(" execution failed") << " (" << prog.execError() << ")" << endl;
274           historylog.comment(sstr.str(), /*timestamp*/true);
275           return ret;
276         }
277
278         report_r->finish();
279         ret.first = true;
280         return ret;
281       }
282
283       /** Execute script and report against report_r.
284        * Return \c false if user requested \c ABORT.
285        */
286       bool executeScript( const Pathname & root_r,
287                           const Pathname & script_r,
288                           callback::SendReport<PatchScriptReport> & report_r )
289       {
290         std::pair<bool,PatchScriptReport::Action> action( std::make_pair( false, PatchScriptReport::ABORT ) );
291
292         do {
293           action = doExecuteScript( root_r, script_r, report_r );
294           if ( action.first )
295             return true; // success
296
297           switch ( action.second )
298           {
299             case PatchScriptReport::ABORT:
300               WAR << "User request to abort at script " << script_r << endl;
301               return false; // requested abort.
302               break;
303
304             case PatchScriptReport::IGNORE:
305               WAR << "User request to skip script " << script_r << endl;
306               return true; // requested skip.
307               break;
308
309             case PatchScriptReport::RETRY:
310               break; // again
311           }
312         } while ( action.second == PatchScriptReport::RETRY );
313
314         // THIS is not intended to be reached:
315         INT << "Abort on unknown ACTION request " << action.second << " returned" << endl;
316         return false; // abort.
317       }
318
319       /** Look for update scripts named 'name-version-release-*' and
320        *  execute them. Return \c false if \c ABORT was requested.
321        *
322        * \see http://en.opensuse.org/Software_Management/Code11/Scripts_and_Messages
323        */
324       bool RunUpdateScripts( const Pathname & root_r,
325                              const Pathname & scriptsPath_r,
326                              const std::vector<sat::Solvable> & checkPackages_r,
327                              bool aborting_r )
328       {
329         if ( checkPackages_r.empty() )
330           return true; // no installed packages to check
331
332         MIL << "Looking for new update scripts in (" <<  root_r << ")" << scriptsPath_r << endl;
333         Pathname scriptsDir( Pathname::assertprefix( root_r, scriptsPath_r ) );
334         if ( ! PathInfo( scriptsDir ).isDir() )
335           return true; // no script dir
336
337         std::list<std::string> scripts;
338         filesystem::readdir( scripts, scriptsDir, /*dots*/false );
339         if ( scripts.empty() )
340           return true; // no scripts in script dir
341
342         // Now collect and execute all matching scripts.
343         // On ABORT: at least log all outstanding scripts.
344         // - "name-version-release"
345         // - "name-version-release-*"
346         bool abort = false;
347         std::map<std::string, Pathname> unify; // scripts <md5,path>
348         for_( it, checkPackages_r.begin(), checkPackages_r.end() )
349         {
350           std::string prefix( str::form( "%s-%s", it->name().c_str(), it->edition().c_str() ) );
351           for_( sit, scripts.begin(), scripts.end() )
352           {
353             if ( ! str::hasPrefix( *sit, prefix ) )
354               continue;
355
356             if ( (*sit)[prefix.size()] != '\0' && (*sit)[prefix.size()] != '-' )
357               continue; // if not exact match it had to continue with '-'
358
359             PathInfo script( scriptsDir / *sit );
360             Pathname localPath( scriptsPath_r/(*sit) ); // without root prefix
361             std::string unifytag;                       // must not stay empty
362
363             if ( script.isFile() )
364             {
365               // Assert it's set executable, unify by md5sum.
366               filesystem::addmod( script.path(), 0500 );
367               unifytag = filesystem::md5sum( script.path() );
368             }
369             else if ( ! script.isExist() )
370             {
371               // Might be a dangling symlink, might be ok if we are in
372               // instsys (absolute symlink within the system below /mnt).
373               // readlink will tell....
374               unifytag = filesystem::readlink( script.path() ).asString();
375             }
376
377             if ( unifytag.empty() )
378               continue;
379
380             // Unify scripts
381             if ( unify[unifytag].empty() )
382             {
383               unify[unifytag] = localPath;
384             }
385             else
386             {
387               // translators: We may find the same script content in files with different names.
388               // Only the first occurence is executed, subsequent ones are skipped. It's a one-line
389               // message for a log file. Preferably start translation with "%s"
390               std::string msg( str::form(_("%s already executed as %s)"), localPath.asString().c_str(), unify[unifytag].c_str() ) );
391               MIL << "Skip update script: " << msg << endl;
392               HistoryLog().comment( msg, /*timestamp*/true );
393               continue;
394             }
395
396             if ( abort || aborting_r )
397             {
398               WAR << "Aborting: Skip update script " << *sit << endl;
399               HistoryLog().comment(
400                   localPath.asString() + _(" execution skipped while aborting"),
401                   /*timestamp*/true);
402             }
403             else
404             {
405               MIL << "Found update script " << *sit << endl;
406               callback::SendReport<PatchScriptReport> report;
407               report->start( make<Package>( *it ), script.path() );
408
409               if ( ! executeScript( root_r, localPath, report ) ) // script path without root prefix!
410                 abort = true; // requested abort.
411             }
412           }
413         }
414         return !abort;
415       }
416
417       ///////////////////////////////////////////////////////////////////
418       //
419       ///////////////////////////////////////////////////////////////////
420
421       inline void copyTo( std::ostream & out_r, const Pathname & file_r )
422       {
423         std::ifstream infile( file_r.c_str() );
424         for( iostr::EachLine in( infile ); in; in.next() )
425         {
426           out_r << *in << endl;
427         }
428       }
429
430       inline std::string notificationCmdSubst( const std::string & cmd_r, const UpdateNotificationFile & notification_r )
431       {
432         std::string ret( cmd_r );
433 #define SUBST_IF(PAT,VAL) if ( ret.find( PAT ) != std::string::npos ) ret = str::gsub( ret, PAT, VAL )
434         SUBST_IF( "%p", notification_r.solvable().asString() );
435         SUBST_IF( "%P", notification_r.file().asString() );
436 #undef SUBST_IF
437         return ret;
438       }
439
440       void sendNotification( const Pathname & root_r,
441                              const UpdateNotifications & notifications_r )
442       {
443         if ( notifications_r.empty() )
444           return;
445
446         std::string cmdspec( ZConfig::instance().updateMessagesNotify() );
447         MIL << "Notification command is '" << cmdspec << "'" << endl;
448         if ( cmdspec.empty() )
449           return;
450
451         std::string::size_type pos( cmdspec.find( '|' ) );
452         if ( pos == std::string::npos )
453         {
454           ERR << "Can't send Notification: Missing 'format |' in command spec." << endl;
455           HistoryLog().comment( str::Str() << _("Error sending update message notification."), /*timestamp*/true );
456           return;
457         }
458
459         std::string formatStr( str::toLower( str::trim( cmdspec.substr( 0, pos ) ) ) );
460         std::string commandStr( str::trim( cmdspec.substr( pos + 1 ) ) );
461
462         enum Format { UNKNOWN, NONE, SINGLE, DIGEST, BULK };
463         Format format = UNKNOWN;
464         if ( formatStr == "none" )
465           format = NONE;
466         else if ( formatStr == "single" )
467           format = SINGLE;
468         else if ( formatStr == "digest" )
469           format = DIGEST;
470         else if ( formatStr == "bulk" )
471           format = BULK;
472         else
473         {
474           ERR << "Can't send Notification: Unknown format '" << formatStr << " |' in command spec." << endl;
475           HistoryLog().comment( str::Str() << _("Error sending update message notification."), /*timestamp*/true );
476          return;
477         }
478
479         // Take care: commands are ececuted chroot(root_r). The message file
480         // pathnames in notifications_r are local to root_r. For physical access
481         // to the file they need to be prefixed.
482
483         if ( format == NONE || format == SINGLE )
484         {
485           for_( it, notifications_r.begin(), notifications_r.end() )
486           {
487             std::vector<std::string> command;
488             if ( format == SINGLE )
489               command.push_back( "<"+Pathname::assertprefix( root_r, it->file() ).asString() );
490             str::splitEscaped( notificationCmdSubst( commandStr, *it ), std::back_inserter( command ) );
491
492             ExternalProgram prog( command, ExternalProgram::Stderr_To_Stdout, false, -1, true, root_r );
493             if ( true ) // Wait for feedback
494             {
495               for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
496               {
497                 DBG << line;
498               }
499               int ret = prog.close();
500               if ( ret != 0 )
501               {
502                 ERR << "Notification command returned with error (" << ret << ")." << endl;
503                 HistoryLog().comment( str::Str() << _("Error sending update message notification."), /*timestamp*/true );
504                 return;
505               }
506             }
507           }
508         }
509         else if ( format == DIGEST || format == BULK )
510         {
511           filesystem::TmpFile tmpfile;
512           ofstream out( tmpfile.path().c_str() );
513           for_( it, notifications_r.begin(), notifications_r.end() )
514           {
515             if ( format == DIGEST )
516             {
517               out << it->file() << endl;
518             }
519             else if ( format == BULK )
520             {
521               copyTo( out << '\f', Pathname::assertprefix( root_r, it->file() ) );
522             }
523           }
524
525           std::vector<std::string> command;
526           command.push_back( "<"+tmpfile.path().asString() ); // redirect input
527           str::splitEscaped( notificationCmdSubst( commandStr, *notifications_r.begin() ), std::back_inserter( command ) );
528
529           ExternalProgram prog( command, ExternalProgram::Stderr_To_Stdout, false, -1, true, root_r );
530           if ( true ) // Wait for feedback otherwise the TmpFile goes out of scope.
531           {
532             for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
533             {
534               DBG << line;
535             }
536             int ret = prog.close();
537             if ( ret != 0 )
538             {
539               ERR << "Notification command returned with error (" << ret << ")." << endl;
540               HistoryLog().comment( str::Str() << _("Error sending update message notification."), /*timestamp*/true );
541               return;
542             }
543           }
544         }
545         else
546         {
547           INT << "Can't send Notification: Missing handler for 'format |' in command spec." << endl;
548           HistoryLog().comment( str::Str() << _("Error sending update message notification."), /*timestamp*/true );
549           return;
550         }
551       }
552
553
554       /** Look for update messages named 'name-version-release-*' and
555        *  send notification according to \ref ZConfig::updateMessagesNotify.
556        *
557        * \see http://en.opensuse.org/Software_Management/Code11/Scripts_and_Messages
558        */
559       void RunUpdateMessages( const Pathname & root_r,
560                               const Pathname & messagesPath_r,
561                               const std::vector<sat::Solvable> & checkPackages_r,
562                               ZYppCommitResult & result_r )
563       {
564         if ( checkPackages_r.empty() )
565           return; // no installed packages to check
566
567         MIL << "Looking for new update messages in (" <<  root_r << ")" << messagesPath_r << endl;
568         Pathname messagesDir( Pathname::assertprefix( root_r, messagesPath_r ) );
569         if ( ! PathInfo( messagesDir ).isDir() )
570           return; // no messages dir
571
572         std::list<std::string> messages;
573         filesystem::readdir( messages, messagesDir, /*dots*/false );
574         if ( messages.empty() )
575           return; // no messages in message dir
576
577         // Now collect all matching messages in result and send them
578         // - "name-version-release"
579         // - "name-version-release-*"
580         HistoryLog historylog;
581         for_( it, checkPackages_r.begin(), checkPackages_r.end() )
582         {
583           std::string prefix( str::form( "%s-%s", it->name().c_str(), it->edition().c_str() ) );
584           for_( sit, messages.begin(), messages.end() )
585           {
586             if ( ! str::hasPrefix( *sit, prefix ) )
587               continue;
588
589             if ( (*sit)[prefix.size()] != '\0' && (*sit)[prefix.size()] != '-' )
590               continue; // if not exact match it had to continue with '-'
591
592             PathInfo message( messagesDir / *sit );
593             if ( ! message.isFile() || message.size() == 0 )
594               continue;
595
596             MIL << "Found update message " << *sit << endl;
597             Pathname localPath( messagesPath_r/(*sit) ); // without root prefix
598             result_r.rUpdateMessages().push_back( UpdateNotificationFile( *it, localPath ) );
599             historylog.comment( str::Str() << _("New update message") << " " << localPath, /*timestamp*/true );
600           }
601         }
602         sendNotification( root_r, result_r.updateMessages() );
603       }
604
605       /////////////////////////////////////////////////////////////////
606     } // namespace
607     ///////////////////////////////////////////////////////////////////
608
609     void XRunUpdateMessages( const Pathname & root_r,
610                              const Pathname & messagesPath_r,
611                              const std::vector<sat::Solvable> & checkPackages_r,
612                              ZYppCommitResult & result_r )
613     { RunUpdateMessages( root_r, messagesPath_r, checkPackages_r, result_r ); }
614
615     /** Helper for PackageProvider queries during commit. */
616     struct QueryInstalledEditionHelper
617     {
618       bool operator()( const std::string & name_r,
619                        const Edition &     ed_r,
620                        const Arch &        arch_r ) const
621       {
622         rpm::librpmDb::db_const_iterator it;
623         for ( it.findByName( name_r ); *it; ++it )
624           {
625             if ( arch_r == it->tag_arch()
626                  && ( ed_r == Edition::noedition || ed_r == it->tag_edition() ) )
627               {
628                 return true;
629               }
630           }
631         return false;
632       }
633     };
634
635     /**
636      * \short Let the Source provide the package.
637      * \p pool_r \ref ResPool used to get candidates
638      * \p pi item to be commited
639     */
640     struct RepoProvidePackage
641     {
642       ResPool _pool;
643       repo::RepoMediaAccess &_access;
644
645       RepoProvidePackage( repo::RepoMediaAccess &access, ResPool pool_r )
646         : _pool(pool_r), _access(access)
647       {}
648
649       ManagedFile operator()( const PoolItem & pi )
650       {
651         // Redirect PackageProvider queries for installed editions
652         // (in case of patch/delta rpm processing) to rpmDb.
653         repo::PackageProviderPolicy packageProviderPolicy;
654         packageProviderPolicy.queryInstalledCB( QueryInstalledEditionHelper() );
655
656         Package::constPtr p = asKind<Package>(pi.resolvable());
657
658         // Build a repository list for repos
659         // contributing to the pool
660         std::list<Repository> repos( _pool.knownRepositoriesBegin(), _pool.knownRepositoriesEnd() );
661         repo::DeltaCandidates deltas(repos, p->name());
662         repo::PackageProvider pkgProvider( _access, p, deltas, packageProviderPolicy );
663
664         ManagedFile ret( pkgProvider.providePackage() );
665         return ret;
666       }
667     };
668     ///////////////////////////////////////////////////////////////////
669
670     IMPL_PTR_TYPE(TargetImpl);
671
672     TargetImpl_Ptr TargetImpl::_nullimpl;
673
674     /** Null implementation */
675     TargetImpl_Ptr TargetImpl::nullimpl()
676     {
677       if (_nullimpl == 0)
678         _nullimpl = new TargetImpl;
679       return _nullimpl;
680     }
681
682     ///////////////////////////////////////////////////////////////////
683     //
684     //  METHOD NAME : TargetImpl::TargetImpl
685     //  METHOD TYPE : Ctor
686     //
687     TargetImpl::TargetImpl( const Pathname & root_r, bool doRebuild_r )
688     : _root( root_r )
689     , _requestedLocalesFile( home() / "RequestedLocales" )
690     , _softLocksFile( home() / "SoftLocks" )
691     , _hardLocksFile( Pathname::assertprefix( _root, ZConfig::instance().locksFile() ) )
692     {
693       _rpm.initDatabase( root_r, Pathname(), doRebuild_r );
694
695       HistoryLog::setRoot(_root);
696
697       createAnonymousId();
698
699       MIL << "Initialized target on " << _root << endl;
700     }
701
702     /**
703      * generates a random id using uuidgen
704      */
705     static std::string generateRandomId()
706     {
707       std::ifstream uuidprovider( "/proc/sys/kernel/random/uuid" );
708       return iostr::getline( uuidprovider );
709     }
710
711     /**
712      * updates the content of \p filename
713      * if \p condition is true, setting the content
714      * the the value returned by \p value
715      */
716     void updateFileContent( const Pathname &filename,
717                             boost::function<bool ()> condition,
718                             boost::function<string ()> value )
719     {
720         string val = value();
721         // if the value is empty, then just dont
722         // do anything, regardless of the condition
723         if ( val.empty() )
724             return;
725
726         if ( condition() )
727         {
728             MIL << "updating '" << filename << "' content." << endl;
729
730             // if the file does not exist we need to generate the uuid file
731
732             std::ofstream filestr;
733             // make sure the path exists
734             filesystem::assert_dir( filename.dirname() );
735             filestr.open( filename.c_str() );
736
737             if ( filestr.good() )
738             {
739                 filestr << val;
740                 filestr.close();
741             }
742             else
743             {
744                 // FIXME, should we ignore the error?
745                 ZYPP_THROW(Exception("Can't openfile '" + filename.asString() + "' for writing"));
746             }
747         }
748     }
749
750     /** helper functor */
751     static bool fileMissing( const Pathname &pathname )
752     {
753         return ! PathInfo(pathname).isExist();
754     }
755
756     void TargetImpl::createAnonymousId() const
757     {
758
759       // create the anonymous unique id
760       // this value is used for statistics
761       Pathname idpath( home() / "AnonymousUniqueId");
762
763       try
764       {
765         updateFileContent( idpath,
766                            boost::bind(fileMissing, idpath),
767                            generateRandomId );
768       }
769       catch ( const Exception &e )
770       {
771         WAR << "Can't create anonymous id file" << endl;
772       }
773
774     }
775
776     void TargetImpl::createLastDistributionFlavorCache() const
777     {
778       // create the anonymous unique id
779       // this value is used for statistics
780       Pathname flavorpath( home() / "LastDistributionFlavor");
781
782       // is there a product
783       Product::constPtr p = baseProduct();
784       if ( ! p )
785       {
786           WAR << "No base product, I won't create flavor cache" << endl;
787           return;
788       }
789
790       string flavor = p->flavor();
791
792       try
793       {
794
795         updateFileContent( flavorpath,
796                            // only if flavor is not empty
797                            functor::Constant<bool>( ! flavor.empty() ),
798                            functor::Constant<string>(flavor) );
799       }
800       catch ( const Exception &e )
801       {
802         WAR << "Can't create flavor cache" << endl;
803         return;
804       }
805     }
806
807     ///////////////////////////////////////////////////////////////////
808     //
809     //  METHOD NAME : TargetImpl::~TargetImpl
810     //  METHOD TYPE : Dtor
811     //
812     TargetImpl::~TargetImpl()
813     {
814       _rpm.closeDatabase();
815       MIL << "Targets closed" << endl;
816     }
817
818     ///////////////////////////////////////////////////////////////////
819     //
820     // solv file handling
821     //
822     ///////////////////////////////////////////////////////////////////
823
824     Pathname TargetImpl::defaultSolvfilesPath() const
825     {
826       return Pathname::assertprefix( _root, ZConfig::instance().repoSolvfilesPath() / sat::Pool::instance().systemRepoAlias() );
827     }
828
829     void TargetImpl::clearCache()
830     {
831       Pathname base = solvfilesPath();
832       filesystem::recursive_rmdir( base );
833     }
834
835     bool TargetImpl::buildCache()
836     {
837       Pathname base = solvfilesPath();
838       Pathname rpmsolv       = base/"solv";
839       Pathname rpmsolvcookie = base/"cookie";
840
841       bool build_rpm_solv = true;
842       // lets see if the rpm solv cache exists
843
844       RepoStatus rpmstatus( RepoStatus( _root/"/var/lib/rpm/Name" )
845                             && (_root/"/etc/products.d") );
846
847       bool solvexisted = PathInfo(rpmsolv).isExist();
848       if ( solvexisted )
849       {
850         // see the status of the cache
851         PathInfo cookie( rpmsolvcookie );
852         MIL << "Read cookie: " << cookie << endl;
853         if ( cookie.isExist() )
854         {
855           RepoStatus status = RepoStatus::fromCookieFile(rpmsolvcookie);
856           // now compare it with the rpm database
857           if ( status.checksum() == rpmstatus.checksum() )
858             build_rpm_solv = false;
859           MIL << "Read cookie: " << rpmsolvcookie << " says: "
860               << (build_rpm_solv ? "outdated" : "uptodate") << endl;
861         }
862       }
863
864       if ( build_rpm_solv )
865       {
866         // if the solvfile dir does not exist yet, we better create it
867         filesystem::assert_dir( base );
868
869         Pathname oldSolvFile( solvexisted ? rpmsolv : Pathname() ); // to speedup rpmdb2solv
870
871         filesystem::TmpFile tmpsolv( filesystem::TmpFile::makeSibling( rpmsolv ) );
872         if ( !tmpsolv )
873         {
874           // Can't create temporary solv file, usually due to insufficient permission
875           // (user query while @System solv needs refresh). If so, try switching
876           // to a location within zypps temp. space (will be cleaned at application end).
877
878           bool switchingToTmpSolvfile = false;
879           Exception ex("Failed to cache rpm database.");
880           ex.remember(str::form("Cannot create temporary file under %s.", base.c_str()));
881
882           if ( ! solvfilesPathIsTemp() )
883           {
884             base = getZYpp()->tmpPath() / sat::Pool::instance().systemRepoAlias();
885             rpmsolv       = base/"solv";
886             rpmsolvcookie = base/"cookie";
887
888             filesystem::assert_dir( base );
889             tmpsolv = filesystem::TmpFile::makeSibling( rpmsolv );
890
891             if ( tmpsolv )
892             {
893               WAR << "Using a temporary solv file at " << base << endl;
894               switchingToTmpSolvfile = true;
895               _tmpSolvfilesPath = base;
896             }
897             else
898             {
899               ex.remember(str::form("Cannot create temporary file under %s.", base.c_str()));
900             }
901           }
902
903           if ( ! switchingToTmpSolvfile )
904           {
905             ZYPP_THROW(ex);
906           }
907         }
908
909         // Take care we unlink the solvfile on exception
910         ManagedFile guard( base, filesystem::recursive_rmdir );
911
912         std::ostringstream cmd;
913         cmd << "rpmdb2solv";
914         if ( ! _root.empty() )
915           cmd << " -r '" << _root << "'";
916
917         cmd << " -p '" << Pathname::assertprefix( _root, "/etc/products.d" ) << "'";
918
919         if ( ! oldSolvFile.empty() )
920           cmd << " '" << oldSolvFile << "'";
921
922         cmd << "  > '" << tmpsolv.path() << "'";
923
924         MIL << "Executing: " << cmd << endl;
925         ExternalProgram prog( cmd.str(), ExternalProgram::Stderr_To_Stdout );
926
927         cmd << endl;
928         for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
929           WAR << "  " << output;
930           cmd << "     " << output;
931         }
932
933         int ret = prog.close();
934         if ( ret != 0 )
935         {
936           Exception ex(str::form("Failed to cache rpm database (%d).", ret));
937           ex.remember( cmd.str() );
938           ZYPP_THROW(ex);
939         }
940
941         ret = filesystem::rename( tmpsolv, rpmsolv );
942         if ( ret != 0 )
943           ZYPP_THROW(Exception("Failed to move cache to final destination"));
944         // if this fails, don't bother throwing exceptions
945         filesystem::chmod( rpmsolv, 0644 );
946
947         rpmstatus.saveToCookieFile(rpmsolvcookie);
948
949         // We keep it.
950         guard.resetDispose();
951
952         // Finally send notification to plugins
953         // NOTE: quick hack looking for spacewalk plugin only
954         {
955           Pathname script( Pathname::assertprefix( _root, ZConfig::instance().pluginsPath()/"system/spacewalk" ) );
956           if ( PathInfo( script ).isX() )
957             try {
958               PluginScript spacewalk( script );
959               spacewalk.open();
960
961               PluginFrame notify( "PACKAGESETCHANGED" );
962               spacewalk.send( notify );
963
964               PluginFrame ret( spacewalk.receive() );
965               MIL << ret << endl;
966               if ( ret.command() == "ERROR" )
967                 ret.writeTo( WAR ) << endl;
968             }
969             catch ( const Exception & excpt )
970             {
971               WAR << excpt.asUserHistory() << endl;
972             }
973         }
974       }
975       return build_rpm_solv;
976     }
977
978     void TargetImpl::reload()
979     {
980         load( false );
981     }
982
983     void TargetImpl::unload()
984     {
985       Repository system( sat::Pool::instance().findSystemRepo() );
986       if ( system )
987         system.eraseFromPool();
988     }
989
990     void TargetImpl::load( bool force )
991     {
992       bool newCache = buildCache();
993       MIL << "New cache built: " << (newCache?"true":"false") <<
994         ", force loading: " << (force?"true":"false") << endl;
995
996       // now add the repos to the pool
997       sat::Pool satpool( sat::Pool::instance() );
998       Pathname rpmsolv( solvfilesPath() / "solv" );
999       MIL << "adding " << rpmsolv << " to pool(" << satpool.systemRepoAlias() << ")" << endl;
1000
1001       // Providing an empty system repo, unload any old content
1002       Repository system( sat::Pool::instance().findSystemRepo() );
1003
1004       if ( system && ! system.solvablesEmpty() )
1005       {
1006         if ( newCache || force )
1007         {
1008           system.eraseFromPool(); // invalidates system
1009         }
1010         else
1011         {
1012           return;     // nothing to do
1013         }
1014       }
1015
1016       if ( ! system )
1017       {
1018         system = satpool.systemRepo();
1019       }
1020
1021       try
1022       {
1023         MIL << "adding " << rpmsolv << " to system" << endl;
1024         system.addSolv( rpmsolv );
1025       }
1026       catch ( const Exception & exp )
1027       {
1028         ZYPP_CAUGHT( exp );
1029         MIL << "Try to handle exception by rebuilding the solv-file" << endl;
1030         clearCache();
1031         buildCache();
1032
1033         system.addSolv( rpmsolv );
1034       }
1035
1036       // (Re)Load the requested locales et al.
1037       // If the requested locales are empty, we leave the pool untouched
1038       // to avoid undoing changes the application applied. We expect this
1039       // to happen on a bare metal installation only. An already existing
1040       // target should be loaded before its settings are changed.
1041       {
1042         const LocaleSet & requestedLocales( _requestedLocalesFile.locales() );
1043         if ( ! requestedLocales.empty() )
1044         {
1045           satpool.setRequestedLocales( requestedLocales );
1046         }
1047       }
1048       {
1049         SoftLocksFile::Data softLocks( _softLocksFile.data() );
1050         if ( ! softLocks.empty() )
1051         {
1052           // Don't soft lock any installed item.
1053           for_( it, system.solvablesBegin(), system.solvablesEnd() )
1054           {
1055             softLocks.erase( it->ident() );
1056           }
1057           ResPool::instance().setAutoSoftLocks( softLocks );
1058         }
1059       }
1060       if ( ZConfig::instance().apply_locks_file() )
1061       {
1062         const HardLocksFile::Data & hardLocks( _hardLocksFile.data() );
1063         if ( ! hardLocks.empty() )
1064         {
1065           ResPool::instance().setHardLockQueries( hardLocks );
1066         }
1067       }
1068
1069       // now that the target is loaded, we can cache the flavor
1070       createLastDistributionFlavorCache();
1071
1072       MIL << "Target loaded: " << system.solvablesSize() << " resolvables" << endl;
1073     }
1074
1075     ///////////////////////////////////////////////////////////////////
1076     //
1077     // COMMIT
1078     //
1079     ///////////////////////////////////////////////////////////////////
1080     ZYppCommitResult TargetImpl::commit( ResPool pool_r, const ZYppCommitPolicy & policy_rX )
1081     {
1082       // ----------------------------------------------------------------- //
1083       ZYppCommitPolicy policy_r( policy_rX );
1084
1085       // Fake outstanding YCP fix: Honour restriction to media 1
1086       // at installation, but install all remaining packages if post-boot.
1087       if ( policy_r.restrictToMedia() > 1 )
1088         policy_r.allMedia();
1089
1090       if ( policy_r.downloadMode() == DownloadDefault ) {
1091         if ( root() == "/" )
1092           policy_r.downloadMode(DownloadInHeaps);
1093         else
1094           policy_r.downloadMode(DownloadAsNeeded);
1095       }
1096       // DownloadOnly implies dry-run.
1097       else if ( policy_r.downloadMode() == DownloadOnly )
1098         policy_r.dryRun( true );
1099       // ----------------------------------------------------------------- //
1100
1101       MIL << "TargetImpl::commit(<pool>, " << policy_r << ")" << endl;
1102
1103       ///////////////////////////////////////////////////////////////////
1104       // Prepare execution of commit plugins:
1105       ///////////////////////////////////////////////////////////////////
1106       CommitPlugins commitPlugins;
1107       if ( root() == "/" && ! policy_r.dryRun() )
1108       {
1109         Pathname plugindir( Pathname::assertprefix( _root, ZConfig::instance().pluginsPath()/"commit" ) );
1110         commitPlugins.load( plugindir );
1111       }
1112
1113       ///////////////////////////////////////////////////////////////////
1114       // Write out a testcase if we're in dist upgrade mode.
1115       ///////////////////////////////////////////////////////////////////
1116       if ( getZYpp()->resolver()->upgradeMode() )
1117       {
1118         if ( ! policy_r.dryRun() )
1119         {
1120           writeUpgradeTestcase();
1121         }
1122         else
1123         {
1124           DBG << "dryRun: Not writing upgrade testcase." << endl;
1125         }
1126       }
1127
1128       ///////////////////////////////////////////////////////////////////
1129       // Store non-package data:
1130       ///////////////////////////////////////////////////////////////////
1131       if ( ! policy_r.dryRun() )
1132       {
1133         filesystem::assert_dir( home() );
1134         // requested locales
1135         _requestedLocalesFile.setLocales( pool_r.getRequestedLocales() );
1136         // weak locks
1137         {
1138           SoftLocksFile::Data newdata;
1139           pool_r.getActiveSoftLocks( newdata );
1140           _softLocksFile.setData( newdata );
1141         }
1142         // hard locks
1143         if ( ZConfig::instance().apply_locks_file() )
1144         {
1145           HardLocksFile::Data newdata;
1146           pool_r.getHardLockQueries( newdata );
1147           _hardLocksFile.setData( newdata );
1148         }
1149       }
1150       else
1151       {
1152         DBG << "dryRun: Not stroring non-package data." << endl;
1153       }
1154
1155       ///////////////////////////////////////////////////////////////////
1156       // Compute transaction:
1157       ///////////////////////////////////////////////////////////////////
1158       ZYppCommitResult result( root() );
1159       result.rTransaction() = pool_r.resolver().getTransaction();
1160       result.rTransaction().order();
1161       // steps: this is our todo-list
1162       ZYppCommitResult::TransactionStepList & steps( result.rTransactionStepList() );
1163       if ( policy_r.restrictToMedia() )
1164       {
1165         // Collect until the 1st package from an unwanted media occurs.
1166         // Further collection could violate install order.
1167         MIL << "Restrict to media number " << policy_r.restrictToMedia() << endl;
1168         for_( it, result.transaction().begin(), result.transaction().end() )
1169         {
1170           if ( makeResObject( *it )->mediaNr() > 1 )
1171             break;
1172           steps.push_back( *it );
1173         }
1174       }
1175       else
1176       {
1177         result.rTransactionStepList().insert( steps.end(), result.transaction().begin(), result.transaction().end() );
1178       }
1179       MIL << "Todo: " << result << endl;
1180
1181       ///////////////////////////////////////////////////////////////////
1182       // First collect and display all messages
1183       // associated with patches to be installed.
1184       ///////////////////////////////////////////////////////////////////
1185       if ( ! policy_r.dryRun() )
1186       {
1187         for_( it, steps.begin(), steps.end() )
1188         {
1189           if ( ! it->satSolvable().isKind<Patch>() )
1190             continue;
1191
1192           PoolItem pi( *it );
1193           if ( ! pi.status().isToBeInstalled() )
1194             continue;
1195
1196           Patch::constPtr patch( asKind<Patch>(pi.resolvable()) );
1197           if ( ! patch ||patch->message().empty()  )
1198             continue;
1199
1200           MIL << "Show message for " << patch << endl;
1201           callback::SendReport<target::PatchMessageReport> report;
1202           if ( ! report->show( patch ) )
1203           {
1204             WAR << "commit aborted by the user" << endl;
1205             ZYPP_THROW( TargetAbortedException( N_("Installation has been aborted as directed.") ) );
1206           }
1207         }
1208       }
1209       else
1210       {
1211         DBG << "dryRun: Not checking patch messages." << endl;
1212       }
1213
1214       ///////////////////////////////////////////////////////////////////
1215       // Remove/install packages.
1216       ///////////////////////////////////////////////////////////////////
1217       DBG << "commit log file is set to: " << HistoryLog::fname() << endl;
1218       if ( ! policy_r.dryRun() || policy_r.downloadMode() == DownloadOnly )
1219       {
1220         // Prepare the package cache. Pass all items requiring download.
1221         repo::RepoMediaAccess access;
1222         RepoProvidePackage repoProvidePackage( access, pool_r );
1223         CommitPackageCache packageCache( root() / "tmp", repoProvidePackage );
1224         packageCache.setCommitList( steps.begin(), steps.end() );
1225
1226         bool miss = false;
1227         if ( policy_r.downloadMode() != DownloadAsNeeded )
1228         {
1229           // Preload the cache. Until now this means pre-loading all packages.
1230           // Once DownloadInHeaps is fully implemented, this will change and
1231           // we may actually have more than one heap.
1232           for_( it, steps.begin(), steps.end() )
1233           {
1234             switch ( it->stepType() )
1235             {
1236               case sat::Transaction::TRANSACTION_INSTALL:
1237               case sat::Transaction::TRANSACTION_MULTIINSTALL:
1238                 // proceed: only install actionas may require download.
1239                 break;
1240
1241               default:
1242                 // next: no download for or non-packages and delete actions.
1243                 continue;
1244                 break;
1245             }
1246
1247             PoolItem pi( *it );
1248             if ( pi->isKind<Package>() || pi->isKind<SrcPackage>() )
1249             {
1250               ManagedFile localfile;
1251               try
1252               {
1253                 // TODO: unify packageCache.get for Package and SrcPackage
1254                 if ( pi->isKind<Package>() )
1255                 {
1256                   localfile = packageCache.get( pi );
1257                 }
1258                 else if ( pi->isKind<SrcPackage>() )
1259                 {
1260                   repo::RepoMediaAccess access;
1261                   repo::SrcPackageProvider prov( access );
1262                   localfile = prov.provideSrcPackage( pi->asKind<SrcPackage>() );
1263                 }
1264                 else
1265                 {
1266                   INT << "Don't know howto cache: Neither Package nor SrcPackage: " << pi << endl;
1267                   continue;
1268                 }
1269                 localfile.resetDispose(); // keep the package file in the cache
1270               }
1271               catch ( const AbortRequestException & exp )
1272               {
1273                 it->stepStage( sat::Transaction::STEP_ERROR );
1274                 miss = true;
1275                 WAR << "commit cache preload aborted by the user" << endl;
1276                 ZYPP_THROW( TargetAbortedException( N_("Installation has been aborted as directed.") ) );
1277                 break;
1278               }
1279               catch ( const SkipRequestException & exp )
1280               {
1281                 ZYPP_CAUGHT( exp );
1282                 it->stepStage( sat::Transaction::STEP_ERROR );
1283                 miss = true;
1284                 WAR << "Skipping cache preload package " << pi->asKind<Package>() << " in commit" << endl;
1285                 continue;
1286               }
1287               catch ( const Exception & exp )
1288               {
1289                 // bnc #395704: missing catch causes abort.
1290                 // TODO see if packageCache fails to handle errors correctly.
1291                 ZYPP_CAUGHT( exp );
1292                 it->stepStage( sat::Transaction::STEP_ERROR );
1293                 miss = true;
1294                 INT << "Unexpected Error: Skipping cache preload package " << pi->asKind<Package>() << " in commit" << endl;
1295                 continue;
1296               }
1297             }
1298           }
1299         }
1300
1301         if ( miss )
1302         {
1303           ERR << "Some packages could not be provided. Aborting commit."<< endl;
1304         }
1305         else if ( ! policy_r.dryRun() )
1306         {
1307           commit( policy_r, packageCache, result );
1308         }
1309         else
1310         {
1311           DBG << "dryRun: Not installing/deleting anything." << endl;
1312         }
1313       }
1314       else
1315       {
1316         DBG << "dryRun: Not downloading/installing/deleting anything." << endl;
1317       }
1318
1319       ///////////////////////////////////////////////////////////////////
1320       // Try to rebuild solv file while rpm database is still in cache
1321       ///////////////////////////////////////////////////////////////////
1322       if ( ! policy_r.dryRun() )
1323       {
1324         buildCache();
1325       }
1326
1327       MIL << "TargetImpl::commit(<pool>, " << policy_r << ") returns: " << result << endl;
1328       return result;
1329     }
1330
1331     ///////////////////////////////////////////////////////////////////
1332     //
1333     // COMMIT internal
1334     //
1335     ///////////////////////////////////////////////////////////////////
1336     void TargetImpl::commit( const ZYppCommitPolicy & policy_r,
1337                              CommitPackageCache & packageCache_r,
1338                              ZYppCommitResult & result_r )
1339     {
1340       // steps: this is our todo-list
1341       ZYppCommitResult::TransactionStepList & steps( result_r.rTransactionStepList() );
1342       MIL << "TargetImpl::commit(<list>" << policy_r << ")" << steps.size() << endl;
1343
1344       bool abort = false;
1345       std::vector<sat::Solvable> successfullyInstalledPackages;
1346       TargetImpl::PoolItemList remaining;
1347
1348       for_( step, steps.begin(), steps.end() )
1349       {
1350         PoolItem citem( *step );
1351         if ( step->stepType() == sat::Transaction::TRANSACTION_IGNORE )
1352         {
1353           if ( citem->isKind<Package>() )
1354           {
1355             // for packages this means being obsoleted (by rpm)
1356             // thius no additional action is needed.
1357             step->stepStage( sat::Transaction::STEP_DONE );
1358             continue;
1359           }
1360         }
1361
1362         if ( citem->isKind<Package>() )
1363         {
1364           Package::constPtr p = citem->asKind<Package>();
1365           if ( citem.status().isToBeInstalled() )
1366           {
1367             ManagedFile localfile;
1368             try
1369             {
1370               localfile = packageCache_r.get( citem );
1371             }
1372             catch ( const AbortRequestException &e )
1373             {
1374               WAR << "commit aborted by the user" << endl;
1375               abort = true;
1376               step->stepStage( sat::Transaction::STEP_ERROR );
1377               break;
1378             }
1379             catch ( const SkipRequestException &e )
1380             {
1381               ZYPP_CAUGHT( e );
1382               WAR << "Skipping package " << p << " in commit" << endl;
1383               step->stepStage( sat::Transaction::STEP_ERROR );
1384               continue;
1385             }
1386             catch ( const Exception &e )
1387             {
1388               // bnc #395704: missing catch causes abort.
1389               // TODO see if packageCache fails to handle errors correctly.
1390               ZYPP_CAUGHT( e );
1391               INT << "Unexpected Error: Skipping package " << p << " in commit" << endl;
1392               step->stepStage( sat::Transaction::STEP_ERROR );
1393               continue;
1394             }
1395
1396 #warning Exception handling
1397             // create a installation progress report proxy
1398             RpmInstallPackageReceiver progress( citem.resolvable() );
1399             progress.connect(); // disconnected on destruction.
1400
1401             bool success = false;
1402             rpm::RpmInstFlags flags( policy_r.rpmInstFlags() & rpm::RPMINST_JUSTDB );
1403             // Why force and nodeps?
1404             //
1405             // Because zypp builds the transaction and the resolver asserts that
1406             // everything is fine.
1407             // We use rpm just to unpack and register the package in the database.
1408             // We do this step by step, so rpm is not aware of the bigger context.
1409             // So we turn off rpms internal checks, because we do it inside zypp.
1410             flags |= rpm::RPMINST_NODEPS;
1411             flags |= rpm::RPMINST_FORCE;
1412             //
1413             if (p->multiversionInstall())  flags |= rpm::RPMINST_NOUPGRADE;
1414             if (policy_r.dryRun())         flags |= rpm::RPMINST_TEST;
1415             if (policy_r.rpmExcludeDocs()) flags |= rpm::RPMINST_EXCLUDEDOCS;
1416             if (policy_r.rpmNoSignature()) flags |= rpm::RPMINST_NOSIGNATURE;
1417
1418             try
1419             {
1420               progress.tryLevel( target::rpm::InstallResolvableReport::RPM_NODEPS_FORCE );
1421               rpm().installPackage( localfile, flags );
1422               HistoryLog().install(citem);
1423
1424               if ( progress.aborted() )
1425               {
1426                 WAR << "commit aborted by the user" << endl;
1427                 localfile.resetDispose(); // keep the package file in the cache
1428                 abort = true;
1429                 step->stepStage( sat::Transaction::STEP_ERROR );
1430                 break;
1431               }
1432               else
1433               {
1434                 success = true;
1435                 step->stepStage( sat::Transaction::STEP_DONE );
1436               }
1437             }
1438             catch ( Exception & excpt_r )
1439             {
1440               ZYPP_CAUGHT(excpt_r);
1441               localfile.resetDispose(); // keep the package file in the cache
1442
1443               if ( policy_r.dryRun() )
1444               {
1445                 WAR << "dry run failed" << endl;
1446                 step->stepStage( sat::Transaction::STEP_ERROR );
1447                 break;
1448               }
1449               // else
1450               if ( progress.aborted() )
1451               {
1452                 WAR << "commit aborted by the user" << endl;
1453                 abort = true;
1454               }
1455               else
1456               {
1457                 WAR << "Install failed" << endl;
1458               }
1459               step->stepStage( sat::Transaction::STEP_ERROR );
1460               break; // stop
1461             }
1462
1463             if ( success && !policy_r.dryRun() )
1464             {
1465               citem.status().resetTransact( ResStatus::USER );
1466               successfullyInstalledPackages.push_back( citem.satSolvable() );
1467               step->stepStage( sat::Transaction::STEP_DONE );
1468             }
1469           }
1470           else
1471           {
1472             RpmRemovePackageReceiver progress( citem.resolvable() );
1473             progress.connect(); // disconnected on destruction.
1474
1475             bool success = false;
1476             rpm::RpmInstFlags flags( policy_r.rpmInstFlags() & rpm::RPMINST_JUSTDB );
1477             flags |= rpm::RPMINST_NODEPS;
1478             if (policy_r.dryRun()) flags |= rpm::RPMINST_TEST;
1479             try
1480             {
1481               rpm().removePackage( p, flags );
1482               HistoryLog().remove(citem);
1483
1484               if ( progress.aborted() )
1485               {
1486                 WAR << "commit aborted by the user" << endl;
1487                 abort = true;
1488                 step->stepStage( sat::Transaction::STEP_ERROR );
1489                 break;
1490               }
1491               else
1492               {
1493                 success = true;
1494                 step->stepStage( sat::Transaction::STEP_DONE );
1495               }
1496             }
1497             catch (Exception & excpt_r)
1498             {
1499               ZYPP_CAUGHT( excpt_r );
1500               if ( progress.aborted() )
1501               {
1502                 WAR << "commit aborted by the user" << endl;
1503                 abort = true;
1504                 step->stepStage( sat::Transaction::STEP_ERROR );
1505                 break;
1506               }
1507               // else
1508               WAR << "removal of " << p << " failed";
1509               step->stepStage( sat::Transaction::STEP_ERROR );
1510             }
1511             if ( success && !policy_r.dryRun() )
1512             {
1513               citem.status().resetTransact( ResStatus::USER );
1514               step->stepStage( sat::Transaction::STEP_DONE );
1515             }
1516           }
1517         }
1518         else if ( ! policy_r.dryRun() ) // other resolvables (non-Package)
1519         {
1520           // Status is changed as the buddy package buddy
1521           // gets installed/deleted. Handle non-buddies only.
1522           if ( ! citem.buddy() )
1523           {
1524             if ( citem->isKind<Product>() )
1525             {
1526               Product::constPtr p = citem->asKind<Product>();
1527               if ( citem.status().isToBeInstalled() )
1528               {
1529                 ERR << "Can't install orphan product without release-package! " << citem << endl;
1530               }
1531               else
1532               {
1533                 // Deleting the corresponding product entry is all we con do.
1534                 // So the product will no longer be visible as installed.
1535                 std::string referenceFilename( p->referenceFilename() );
1536                 if ( referenceFilename.empty() )
1537                 {
1538                   ERR << "Can't remove orphan product without 'referenceFilename'! " << citem << endl;
1539                 }
1540                 else
1541                 {
1542                   PathInfo referenceFile( Pathname::assertprefix( _root, Pathname( "/etc/products.d" ) ) / referenceFilename );
1543                   if ( ! referenceFile.isFile() || filesystem::unlink( referenceFile.path() ) != 0 )
1544                   {
1545                     ERR << "Delete orphan product failed: " << referenceFile << endl;
1546                   }
1547                 }
1548               }
1549             }
1550             else if ( citem->isKind<SrcPackage>() && citem.status().isToBeInstalled() )
1551             {
1552               // SrcPackage is install-only
1553               SrcPackage::constPtr p = citem->asKind<SrcPackage>();
1554               installSrcPackage( p );
1555             }
1556
1557             citem.status().resetTransact( ResStatus::USER );
1558             step->stepStage( sat::Transaction::STEP_DONE );
1559           }
1560
1561         }  // other resolvables
1562
1563       } // for
1564
1565       // Check presence of update scripts/messages. If aborting,
1566       // at least log omitted scripts.
1567       if ( ! successfullyInstalledPackages.empty() )
1568       {
1569         if ( ! RunUpdateScripts( _root, ZConfig::instance().update_scriptsPath(),
1570                                  successfullyInstalledPackages, abort ) )
1571         {
1572           WAR << "Commit aborted by the user" << endl;
1573           abort = true;
1574         }
1575         // send messages after scripts in case some script generates output,
1576         // that should be kept in t %ghost message file.
1577         RunUpdateMessages( _root, ZConfig::instance().update_messagesPath(),
1578                            successfullyInstalledPackages,
1579                            result_r );
1580       }
1581
1582       if ( abort )
1583       {
1584         ZYPP_THROW( TargetAbortedException( N_("Installation has been aborted as directed.") ) );
1585       }
1586     }
1587
1588     ///////////////////////////////////////////////////////////////////
1589
1590     rpm::RpmDb & TargetImpl::rpm()
1591     {
1592       return _rpm;
1593     }
1594
1595     bool TargetImpl::providesFile (const std::string & path_str, const std::string & name_str) const
1596     {
1597       return _rpm.hasFile(path_str, name_str);
1598     }
1599
1600
1601     Date TargetImpl::timestamp() const
1602     {
1603       return _rpm.timestamp();
1604     }
1605
1606     ///////////////////////////////////////////////////////////////////
1607     namespace
1608     {
1609       parser::ProductFileData baseproductdata( const Pathname & root_r )
1610       {
1611         PathInfo baseproduct( Pathname::assertprefix( root_r, "/etc/products.d/baseproduct" ) );
1612         if ( baseproduct.isFile() )
1613         {
1614           try
1615           {
1616             return parser::ProductFileReader::scanFile( baseproduct.path() );
1617           }
1618           catch ( const Exception & excpt )
1619           {
1620             ZYPP_CAUGHT( excpt );
1621           }
1622         }
1623         return parser::ProductFileData();
1624       }
1625
1626       inline Pathname staticGuessRoot( const Pathname & root_r )
1627       {
1628         if ( root_r.empty() )
1629         {
1630           // empty root: use existing Target or assume "/"
1631           Pathname ret ( ZConfig::instance().systemRoot() );
1632           if ( ret.empty() )
1633             return Pathname("/");
1634           return ret;
1635         }
1636         return root_r;
1637       }
1638
1639       inline std::string firstNonEmptyLineIn( const Pathname & file_r )
1640       {
1641         std::ifstream idfile( file_r.c_str() );
1642         for( iostr::EachLine in( idfile ); in; in.next() )
1643         {
1644           std::string line( str::trim( *in ) );
1645           if ( ! line.empty() )
1646             return line;
1647         }
1648         return std::string();
1649       }
1650     } // namescpace
1651     ///////////////////////////////////////////////////////////////////
1652
1653     Product::constPtr TargetImpl::baseProduct() const
1654     {
1655       ResPool pool(ResPool::instance());
1656       for_( it, pool.byKindBegin<Product>(), pool.byKindEnd<Product>() )
1657       {
1658         Product::constPtr p = (*it)->asKind<Product>();
1659         if ( p->isTargetDistribution() )
1660           return p;
1661       }
1662       return nullptr;
1663     }
1664
1665     LocaleSet TargetImpl::requestedLocales( const Pathname & root_r )
1666     {
1667       const Pathname needroot( staticGuessRoot(root_r) );
1668       const Target_constPtr target( getZYpp()->getTarget() );
1669       if ( target && target->root() == needroot )
1670         return target->requestedLocales();
1671       return RequestedLocalesFile( home(needroot) / "RequestedLocales" ).locales();
1672     }
1673
1674     std::string TargetImpl::targetDistribution() const
1675     { return baseproductdata( _root ).registerTarget(); }
1676     // static version:
1677     std::string TargetImpl::targetDistribution( const Pathname & root_r )
1678     { return baseproductdata( staticGuessRoot(root_r) ).registerTarget(); }
1679
1680     std::string TargetImpl::targetDistributionRelease() const
1681     { return baseproductdata( _root ).registerRelease(); }
1682     // static version:
1683     std::string TargetImpl::targetDistributionRelease( const Pathname & root_r )
1684     { return baseproductdata( staticGuessRoot(root_r) ).registerRelease();}
1685
1686     Target::DistributionLabel TargetImpl::distributionLabel() const
1687     {
1688       Target::DistributionLabel ret;
1689       parser::ProductFileData pdata( baseproductdata( _root ) );
1690       ret.shortName = pdata.shortName();
1691       ret.summary = pdata.summary();
1692       return ret;
1693     }
1694     // static version:
1695     Target::DistributionLabel TargetImpl::distributionLabel( const Pathname & root_r )
1696     {
1697       Target::DistributionLabel ret;
1698       parser::ProductFileData pdata( baseproductdata( staticGuessRoot(root_r) ) );
1699       ret.shortName = pdata.shortName();
1700       ret.summary = pdata.summary();
1701       return ret;
1702     }
1703
1704     std::string TargetImpl::distributionVersion() const
1705     {
1706       if ( _distributionVersion.empty() )
1707       {
1708         _distributionVersion = TargetImpl::distributionVersion(root());
1709         if ( !_distributionVersion.empty() )
1710           MIL << "Remember distributionVersion = '" << _distributionVersion << "'" << endl;
1711       }
1712       return _distributionVersion;
1713     }
1714     // static version
1715     std::string TargetImpl::distributionVersion( const Pathname & root_r )
1716     {
1717       std::string distributionVersion = baseproductdata( staticGuessRoot(root_r) ).edition().version();
1718       if ( distributionVersion.empty() )
1719       {
1720         // ...But the baseproduct method is not expected to work on RedHat derivatives.
1721         // On RHEL, Fedora and others the "product version" is determined by the first package
1722         // providing 'redhat-release'. This value is not hardcoded in YUM and can be configured
1723         // with the $distroverpkg variable.
1724         scoped_ptr<rpm::RpmDb> tmprpmdb;
1725         if ( ZConfig::instance().systemRoot() == Pathname() )
1726         {
1727           try
1728           {
1729               tmprpmdb.reset( new rpm::RpmDb );
1730               tmprpmdb->initDatabase( /*default ctor uses / but no additional keyring exports */ );
1731           }
1732           catch( ... )
1733           {
1734             return "";
1735           }
1736         }
1737         rpm::librpmDb::db_const_iterator it;
1738         if ( it.findByProvides( ZConfig::instance().distroverpkg() ) )
1739           distributionVersion = it->tag_version();
1740       }
1741       return distributionVersion;
1742     }
1743
1744
1745     std::string TargetImpl::distributionFlavor() const
1746     {
1747       return firstNonEmptyLineIn( home() / "LastDistributionFlavor" );
1748     }
1749     // static version:
1750     std::string TargetImpl::distributionFlavor( const Pathname & root_r )
1751     {
1752       return firstNonEmptyLineIn( staticGuessRoot(root_r) / "/var/lib/zypp/LastDistributionFlavor" );
1753     }
1754
1755     ///////////////////////////////////////////////////////////////////
1756
1757     std::string TargetImpl::anonymousUniqueId() const
1758     {
1759       return firstNonEmptyLineIn( home() / "AnonymousUniqueId" );
1760     }
1761     // static version:
1762     std::string TargetImpl::anonymousUniqueId( const Pathname & root_r )
1763     {
1764       return firstNonEmptyLineIn( staticGuessRoot(root_r) / "/var/lib/zypp/AnonymousUniqueId" );
1765     }
1766
1767     ///////////////////////////////////////////////////////////////////
1768
1769     void TargetImpl::installSrcPackage( const SrcPackage_constPtr & srcPackage_r )
1770     {
1771       // provide on local disk
1772       ManagedFile localfile = provideSrcPackage(srcPackage_r);
1773       // install it
1774       rpm().installPackage ( localfile );
1775     }
1776
1777     ManagedFile TargetImpl::provideSrcPackage( const SrcPackage_constPtr & srcPackage_r )
1778     {
1779       // provide on local disk
1780       repo::RepoMediaAccess access_r;
1781       repo::SrcPackageProvider prov( access_r );
1782       return prov.provideSrcPackage( srcPackage_r );
1783     }
1784     ////////////////////////////////////////////////////////////////
1785   } // namespace target
1786   ///////////////////////////////////////////////////////////////////
1787   /////////////////////////////////////////////////////////////////
1788 } // namespace zypp
1789 ///////////////////////////////////////////////////////////////////