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