libzypp 17.35.9
TargetImpl.cc
Go to the documentation of this file.
1/*---------------------------------------------------------------------\
2| ____ _ __ __ ___ |
3| |__ / \ / / . \ . \ |
4| / / \ V /| _/ _/ |
5| / /__ | | | | | | |
6| /_____||_| |_| |_| |
7| |
8\---------------------------------------------------------------------*/
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>
28#include <zypp-core/base/UserRequestException>
29#include <zypp/base/Json.h>
30
31#include <zypp/ZConfig.h>
32#include <zypp/ZYppFactory.h>
33#include <zypp/PathInfo.h>
34
35#include <zypp/PoolItem.h>
36#include <zypp/ResObjects.h>
37#include <zypp/Url.h>
38#include <zypp/TmpPath.h>
39#include <zypp/RepoStatus.h>
41#include <zypp/Repository.h>
43
44#include <zypp/ResFilters.h>
45#include <zypp/HistoryLog.h>
51
54
55#include <zypp/sat/Pool.h>
59
62#include <zypp-core/zyppng/base/EventLoop>
63#include <zypp-core/zyppng/base/UnixSignalSource>
64#include <zypp-core/zyppng/io/AsyncDataSource>
65#include <zypp-core/zyppng/io/Process>
69#include <zypp-core/zyppng/base/EventDispatcher>
70
71#include <shared/commit/CommitMessages.h>
72
74
75#include <zypp/PluginExecutor.h>
76
77// include the error codes from zypp-rpm
78#include "tools/zypp-rpm/errorcodes.h"
79#include <rpm/rpmlog.h>
80
81#include <optional>
82
83namespace zypp::env {
85 {
86 static bool val = [](){
87 const char * env = getenv("TRANSACTIONAL_UPDATE");
88 return( env && zypp::str::strToBool( env, true ) );
89 }();
90 return val;
91 }
92} // namespace zypp::env
93
94using std::endl;
95
97extern "C"
98{
99#include <solv/repo_rpmdb.h>
100#include <solv/chksum.h>
101}
102namespace zypp
103{
104 namespace target
105 {
106 inline std::string rpmDbStateHash( const Pathname & root_r )
107 {
108 std::string ret;
109 AutoDispose<void*> state { ::rpm_state_create( sat::Pool::instance().get(), root_r.c_str() ), ::rpm_state_free };
110 AutoDispose<Chksum*> chk { ::solv_chksum_create( REPOKEY_TYPE_SHA1 ), []( Chksum *chk ) -> void {
111 ::solv_chksum_free( chk, nullptr );
112 } };
113 if ( ::rpm_hash_database_state( state, chk ) == 0 )
114 {
115 int md5l;
116 const unsigned char * md5 = ::solv_chksum_get( chk, &md5l );
117 ret = ::pool_bin2hex( sat::Pool::instance().get(), md5, md5l );
118 }
119 else
120 WAR << "rpm_hash_database_state failed" << endl;
121 return ret;
122 }
123
125 { return RepoStatus( rpmDbStateHash( root_r ), Date() ); }
126
127 } // namespace target
128} // namespace
130
132namespace zypp
133{
135 namespace
136 {
137 // HACK for bnc#906096: let pool re-evaluate multiversion spec
138 // if target root changes. ZConfig returns data sensitive to
139 // current target root.
140 inline void sigMultiversionSpecChanged()
141 {
142 sat::detail::PoolMember::myPool().multiversionSpecChanged();
143 }
144 } //namespace
146
148 namespace json
149 {
150 // Lazy via template specialisation / should switch to overloading
151
152 template<>
154 {
155 using sat::Transaction;
157
158 for ( const Transaction::Step & step : steps_r )
159 // ignore implicit deletes due to obsoletes and non-package actions
160 if ( step.stepType() != Transaction::TRANSACTION_IGNORE )
161 ret.add( step );
162
163 return ret.asJSON();
164 }
165
167 template<>
168 inline std::string toJSON( const sat::Transaction::Step & step_r )
169 {
170 static const std::string strType( "type" );
171 static const std::string strStage( "stage" );
172 static const std::string strSolvable( "solvable" );
173
174 static const std::string strTypeDel( "-" );
175 static const std::string strTypeIns( "+" );
176 static const std::string strTypeMul( "M" );
177
178 static const std::string strStageDone( "ok" );
179 static const std::string strStageFailed( "err" );
180
181 static const std::string strSolvableN( "n" );
182 static const std::string strSolvableE( "e" );
183 static const std::string strSolvableV( "v" );
184 static const std::string strSolvableR( "r" );
185 static const std::string strSolvableA( "a" );
186
187 using sat::Transaction;
189
190 switch ( step_r.stepType() )
191 {
192 case Transaction::TRANSACTION_IGNORE: /*empty*/ break;
193 case Transaction::TRANSACTION_ERASE: ret.add( strType, strTypeDel ); break;
194 case Transaction::TRANSACTION_INSTALL: ret.add( strType, strTypeIns ); break;
195 case Transaction::TRANSACTION_MULTIINSTALL: ret.add( strType, strTypeMul ); break;
196 }
197
198 switch ( step_r.stepStage() )
199 {
200 case Transaction::STEP_TODO: /*empty*/ break;
201 case Transaction::STEP_DONE: ret.add( strStage, strStageDone ); break;
202 case Transaction::STEP_ERROR: ret.add( strStage, strStageFailed ); break;
203 }
204
205 {
206 IdString ident;
207 Edition ed;
208 Arch arch;
209 if ( sat::Solvable solv = step_r.satSolvable() )
210 {
211 ident = solv.ident();
212 ed = solv.edition();
213 arch = solv.arch();
214 }
215 else
216 {
217 // deleted package; post mortem data stored in Transaction::Step
218 ident = step_r.ident();
219 ed = step_r.edition();
220 arch = step_r.arch();
221 }
222
224 { strSolvableN, ident.asString() },
225 { strSolvableV, ed.version() },
226 { strSolvableR, ed.release() },
227 { strSolvableA, arch.asString() }
228 };
229 if ( Edition::epoch_t epoch = ed.epoch() )
230 s.add( strSolvableE, epoch );
231
232 ret.add( strSolvable, s );
233 }
234
235 return ret.asJSON();
236 }
237 } // namespace json
239
241 namespace target
242 {
244 namespace
245 {
246 class AssertMountedBase
247 {
248 NON_COPYABLE(AssertMountedBase);
249 NON_MOVABLE(AssertMountedBase);
250 protected:
251 AssertMountedBase()
252 {}
253
254 ~AssertMountedBase()
255 {
256 if ( ! _mountpoint.empty() ) {
257 // we mounted it so we unmount...
258 MIL << "We mounted " << _mountpoint << " so we unmount it" << endl;
259 execute({ "umount", "-R", "-l", _mountpoint.asString() });
260 }
261 }
262
263 protected:
264 int execute( ExternalProgram::Arguments && cmd_r ) const
265 {
266 ExternalProgram prog( cmd_r, ExternalProgram::Stderr_To_Stdout );
267 for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
268 { DBG << line; }
269 return prog.close();
270 }
271
272 protected:
273 Pathname _mountpoint;
274
275 };
276
279 class AssertProcMounted : private AssertMountedBase
280 {
281 public:
282 AssertProcMounted( Pathname root_r )
283 {
284 root_r /= "/proc";
285 if ( ! PathInfo(root_r/"self").isDir() ) {
286 MIL << "Try to make sure proc is mounted at" << root_r << endl;
287 if ( filesystem::assert_dir(root_r) == 0
288 && execute({ "mount", "-t", "proc", "/proc", root_r.asString() }) == 0 ) {
289 _mountpoint = std::move(root_r); // so we'll later unmount it
290 }
291 else {
292 WAR << "Mounting proc at " << root_r << " failed" << endl;
293 }
294 }
295 }
296 };
297
300 class AssertDevMounted : private AssertMountedBase
301 {
302 public:
303 AssertDevMounted( Pathname root_r )
304 {
305 root_r /= "/dev";
306 if ( ! PathInfo(root_r/"null").isChr() ) {
307 MIL << "Try to make sure dev is mounted at" << root_r << endl;
308 // https://unix.stackexchange.com/questions/263972/unmount-a-rbind-mount-without-affecting-the-original-mount
309 // Without --make-rslave unmounting <sandbox-root>/dev/pts
310 // may unmount /dev/pts and you're out of ptys.
311 if ( filesystem::assert_dir(root_r) == 0
312 && execute({ "mount", "--rbind", "--make-rslave", "/dev", root_r.asString() }) == 0 ) {
313 _mountpoint = std::move(root_r); // so we'll later unmount it
314 }
315 else {
316 WAR << "Mounting dev at " << root_r << " failed" << endl;
317 }
318 }
319 }
320 };
321
322 } // namespace
324
326 namespace
327 {
328 SolvIdentFile::Data getUserInstalledFromHistory( const Pathname & historyFile_r )
329 {
330 SolvIdentFile::Data onSystemByUserList;
331 // go and parse it: 'who' must constain an '@', then it was installed by user request.
332 // 2009-09-29 07:25:19|install|lirc-remotes|0.8.5-3.2|x86_64|root@opensuse|InstallationImage|a204211eb0...
333 std::ifstream infile( historyFile_r.c_str() );
334 for( iostr::EachLine in( infile ); in; in.next() )
335 {
336 const char * ch( (*in).c_str() );
337 // start with year
338 if ( *ch < '1' || '9' < *ch )
339 continue;
340 const char * sep1 = ::strchr( ch, '|' ); // | after date
341 if ( !sep1 )
342 continue;
343 ++sep1;
344 // if logs an install or delete
345 bool installs = true;
346 if ( ::strncmp( sep1, "install|", 8 ) )
347 {
348 if ( ::strncmp( sep1, "remove |", 8 ) )
349 continue; // no install and no remove
350 else
351 installs = false; // remove
352 }
353 sep1 += 8; // | after what
354 // get the package name
355 const char * sep2 = ::strchr( sep1, '|' ); // | after name
356 if ( !sep2 || sep1 == sep2 )
357 continue;
358 (*in)[sep2-ch] = '\0';
359 IdString pkg( sep1 );
360 // we're done, if a delete
361 if ( !installs )
362 {
363 onSystemByUserList.erase( pkg );
364 continue;
365 }
366 // now guess whether user installed or not (3rd next field contains 'user@host')
367 if ( (sep1 = ::strchr( sep2+1, '|' )) // | after version
368 && (sep1 = ::strchr( sep1+1, '|' )) // | after arch
369 && (sep2 = ::strchr( sep1+1, '|' )) ) // | after who
370 {
371 (*in)[sep2-ch] = '\0';
372 if ( ::strchr( sep1+1, '@' ) )
373 {
374 // by user
375 onSystemByUserList.insert( pkg );
376 continue;
377 }
378 }
379 }
380 MIL << "onSystemByUserList found: " << onSystemByUserList.size() << endl;
381 return onSystemByUserList;
382 }
383 } // namespace
385
387 namespace
388 {
389 inline PluginFrame transactionPluginFrame( const std::string & command_r, ZYppCommitResult::TransactionStepList & steps_r )
390 {
391 return PluginFrame( command_r, json::Object {
392 { "TransactionStepList", steps_r }
393 }.asJSON() );
394 }
395 } // namespace
397
400 {
401 unsigned toKeep( ZConfig::instance().solver_upgradeTestcasesToKeep() );
402 MIL << "Testcases to keep: " << toKeep << endl;
403 if ( !toKeep )
404 return;
405 Target_Ptr target( getZYpp()->getTarget() );
406 if ( ! target )
407 {
408 WAR << "No Target no Testcase!" << endl;
409 return;
410 }
411
412 std::string stem( "updateTestcase" );
413 Pathname dir( target->assertRootPrefix("/var/log/") );
414 Pathname next( dir / Date::now().form( stem+"-%Y-%m-%d-%H-%M-%S" ) );
415
416 {
417 std::list<std::string> content;
418 filesystem::readdir( content, dir, /*dots*/false );
419 std::set<std::string> cases;
420 for_( c, content.begin(), content.end() )
421 {
422 if ( str::startsWith( *c, stem ) )
423 cases.insert( *c );
424 }
425 if ( cases.size() >= toKeep )
426 {
427 unsigned toDel = cases.size() - toKeep + 1; // +1 for the new one
428 for_( c, cases.begin(), cases.end() )
429 {
431 if ( ! --toDel )
432 break;
433 }
434 }
435 }
436
437 MIL << "Write new testcase " << next << endl;
438 getZYpp()->resolver()->createSolverTestcase( next.asString(), false/*no solving*/ );
439 }
440
442 namespace
443 {
444
455 std::pair<bool,PatchScriptReport::Action> doExecuteScript( const Pathname & root_r,
456 const Pathname & script_r,
458 {
459 MIL << "Execute script " << PathInfo(Pathname::assertprefix( root_r,script_r)) << endl;
460
462 historylog.comment(script_r.asString() + _(" executed"), /*timestamp*/true);
464
465 for ( std::string output = prog.receiveLine(); output.length(); output = prog.receiveLine() )
466 {
467 historylog.comment(output);
468 if ( ! report_r->progress( PatchScriptReport::OUTPUT, output ) )
469 {
470 WAR << "User request to abort script " << script_r << endl;
471 prog.kill();
472 // the rest is handled by exit code evaluation
473 // in case the script has meanwhile finished.
474 }
475 }
476
477 std::pair<bool,PatchScriptReport::Action> ret( std::make_pair( false, PatchScriptReport::ABORT ) );
478
479 if ( prog.close() != 0 )
480 {
481 ret.second = report_r->problem( prog.execError() );
482 WAR << "ACTION" << ret.second << "(" << prog.execError() << ")" << endl;
483 std::ostringstream sstr;
484 sstr << script_r << _(" execution failed") << " (" << prog.execError() << ")" << endl;
485 historylog.comment(sstr.str(), /*timestamp*/true);
486 return ret;
487 }
488
489 report_r->finish();
490 ret.first = true;
491 return ret;
492 }
493
497 bool executeScript( const Pathname & root_r,
498 const Pathname & script_r,
499 callback::SendReport<PatchScriptReport> & report_r )
500 {
501 std::pair<bool,PatchScriptReport::Action> action( std::make_pair( false, PatchScriptReport::ABORT ) );
502
503 do {
504 action = doExecuteScript( root_r, script_r, report_r );
505 if ( action.first )
506 return true; // success
507
508 switch ( action.second )
509 {
511 WAR << "User request to abort at script " << script_r << endl;
512 return false; // requested abort.
513 break;
514
516 WAR << "User request to skip script " << script_r << endl;
517 return true; // requested skip.
518 break;
519
521 break; // again
522 }
523 } while ( action.second == PatchScriptReport::RETRY );
524
525 // THIS is not intended to be reached:
526 INT << "Abort on unknown ACTION request " << action.second << " returned" << endl;
527 return false; // abort.
528 }
529
535 bool RunUpdateScripts( const Pathname & root_r,
536 const Pathname & scriptsPath_r,
537 const std::vector<sat::Solvable> & checkPackages_r,
538 bool aborting_r )
539 {
540 if ( checkPackages_r.empty() )
541 return true; // no installed packages to check
542
543 MIL << "Looking for new update scripts in (" << root_r << ")" << scriptsPath_r << endl;
544 Pathname scriptsDir( Pathname::assertprefix( root_r, scriptsPath_r ) );
545 if ( ! PathInfo( scriptsDir ).isDir() )
546 return true; // no script dir
547
548 std::list<std::string> scripts;
549 filesystem::readdir( scripts, scriptsDir, /*dots*/false );
550 if ( scripts.empty() )
551 return true; // no scripts in script dir
552
553 // Now collect and execute all matching scripts.
554 // On ABORT: at least log all outstanding scripts.
555 // - "name-version-release"
556 // - "name-version-release-*"
557 bool abort = false;
558 std::map<std::string, Pathname> unify; // scripts <md5,path>
559 for_( it, checkPackages_r.begin(), checkPackages_r.end() )
560 {
561 std::string prefix( str::form( "%s-%s", it->name().c_str(), it->edition().c_str() ) );
562 for_( sit, scripts.begin(), scripts.end() )
563 {
564 if ( ! str::hasPrefix( *sit, prefix ) )
565 continue;
566
567 if ( (*sit)[prefix.size()] != '\0' && (*sit)[prefix.size()] != '-' )
568 continue; // if not exact match it had to continue with '-'
569
570 PathInfo script( scriptsDir / *sit );
571 Pathname localPath( scriptsPath_r/(*sit) ); // without root prefix
572 std::string unifytag; // must not stay empty
573
574 if ( script.isFile() )
575 {
576 // Assert it's set as executable, unify by md5sum.
577 filesystem::addmod( script.path(), 0500 );
578 unifytag = filesystem::md5sum( script.path() );
579 }
580 else if ( ! script.isExist() )
581 {
582 // Might be a dangling symlink, might be ok if we are in
583 // instsys (absolute symlink within the system below /mnt).
584 // readlink will tell....
585 unifytag = filesystem::readlink( script.path() ).asString();
586 }
587
588 if ( unifytag.empty() )
589 continue;
590
591 // Unify scripts
592 if ( unify[unifytag].empty() )
593 {
594 unify[unifytag] = localPath;
595 }
596 else
597 {
598 // translators: We may find the same script content in files with different names.
599 // Only the first occurence is executed, subsequent ones are skipped. It's a one-line
600 // message for a log file. Preferably start translation with "%s"
601 std::string msg( str::form(_("%s already executed as %s)"), localPath.asString().c_str(), unify[unifytag].c_str() ) );
602 MIL << "Skip update script: " << msg << endl;
603 HistoryLog().comment( msg, /*timestamp*/true );
604 continue;
605 }
606
607 if ( abort || aborting_r )
608 {
609 WAR << "Aborting: Skip update script " << *sit << endl;
610 HistoryLog().comment(
611 localPath.asString() + _(" execution skipped while aborting"),
612 /*timestamp*/true);
613 }
614 else
615 {
616 MIL << "Found update script " << *sit << endl;
617 callback::SendReport<PatchScriptReport> report;
618 report->start( make<Package>( *it ), script.path() );
619
620 if ( ! executeScript( root_r, localPath, report ) ) // script path without root prefix!
621 abort = true; // requested abort.
622 }
623 }
624 }
625 return !abort;
626 }
627
629 //
631
632 inline void copyTo( std::ostream & out_r, const Pathname & file_r )
633 {
634 std::ifstream infile( file_r.c_str() );
635 for( iostr::EachLine in( infile ); in; in.next() )
636 {
637 out_r << *in << endl;
638 }
639 }
640
641 inline std::string notificationCmdSubst( const std::string & cmd_r, const UpdateNotificationFile & notification_r )
642 {
643 std::string ret( cmd_r );
644#define SUBST_IF(PAT,VAL) if ( ret.find( PAT ) != std::string::npos ) ret = str::gsub( ret, PAT, VAL )
645 SUBST_IF( "%p", notification_r.solvable().asString() );
646 SUBST_IF( "%P", notification_r.file().asString() );
647#undef SUBST_IF
648 return ret;
649 }
650
651 void sendNotification( const Pathname & root_r,
652 const UpdateNotifications & notifications_r )
653 {
654 if ( notifications_r.empty() )
655 return;
656
657 std::string cmdspec( ZConfig::instance().updateMessagesNotify() );
658 MIL << "Notification command is '" << cmdspec << "'" << endl;
659 if ( cmdspec.empty() )
660 return;
661
662 std::string::size_type pos( cmdspec.find( '|' ) );
663 if ( pos == std::string::npos )
664 {
665 ERR << "Can't send Notification: Missing 'format |' in command spec." << endl;
666 HistoryLog().comment( str::Str() << _("Error sending update message notification."), /*timestamp*/true );
667 return;
668 }
669
670 std::string formatStr( str::toLower( str::trim( cmdspec.substr( 0, pos ) ) ) );
671 std::string commandStr( str::trim( cmdspec.substr( pos + 1 ) ) );
672
673 enum Format { UNKNOWN, NONE, SINGLE, DIGEST, BULK };
674 Format format = UNKNOWN;
675 if ( formatStr == "none" )
676 format = NONE;
677 else if ( formatStr == "single" )
678 format = SINGLE;
679 else if ( formatStr == "digest" )
680 format = DIGEST;
681 else if ( formatStr == "bulk" )
682 format = BULK;
683 else
684 {
685 ERR << "Can't send Notification: Unknown format '" << formatStr << " |' in command spec." << endl;
686 HistoryLog().comment( str::Str() << _("Error sending update message notification."), /*timestamp*/true );
687 return;
688 }
689
690 // Take care: commands are ececuted chroot(root_r). The message file
691 // pathnames in notifications_r are local to root_r. For physical access
692 // to the file they need to be prefixed.
693
694 if ( format == NONE || format == SINGLE )
695 {
696 for_( it, notifications_r.begin(), notifications_r.end() )
697 {
698 std::vector<std::string> command;
699 if ( format == SINGLE )
700 command.push_back( "<"+Pathname::assertprefix( root_r, it->file() ).asString() );
701 str::splitEscaped( notificationCmdSubst( commandStr, *it ), std::back_inserter( command ) );
702
703 ExternalProgram prog( command, ExternalProgram::Stderr_To_Stdout, false, -1, true, root_r );
704 if ( true ) // Wait for feedback
705 {
706 for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
707 {
708 DBG << line;
709 }
710 int ret = prog.close();
711 if ( ret != 0 )
712 {
713 ERR << "Notification command returned with error (" << ret << ")." << endl;
714 HistoryLog().comment( str::Str() << _("Error sending update message notification."), /*timestamp*/true );
715 return;
716 }
717 }
718 }
719 }
720 else if ( format == DIGEST || format == BULK )
721 {
722 filesystem::TmpFile tmpfile;
723 std::ofstream out( tmpfile.path().c_str() );
724 for_( it, notifications_r.begin(), notifications_r.end() )
725 {
726 if ( format == DIGEST )
727 {
728 out << it->file() << endl;
729 }
730 else if ( format == BULK )
731 {
732 copyTo( out << '\f', Pathname::assertprefix( root_r, it->file() ) );
733 }
734 }
735
736 std::vector<std::string> command;
737 command.push_back( "<"+tmpfile.path().asString() ); // redirect input
738 str::splitEscaped( notificationCmdSubst( commandStr, *notifications_r.begin() ), std::back_inserter( command ) );
739
740 ExternalProgram prog( command, ExternalProgram::Stderr_To_Stdout, false, -1, true, root_r );
741 if ( true ) // Wait for feedback otherwise the TmpFile goes out of scope.
742 {
743 for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
744 {
745 DBG << line;
746 }
747 int ret = prog.close();
748 if ( ret != 0 )
749 {
750 ERR << "Notification command returned with error (" << ret << ")." << endl;
751 HistoryLog().comment( str::Str() << _("Error sending update message notification."), /*timestamp*/true );
752 return;
753 }
754 }
755 }
756 else
757 {
758 INT << "Can't send Notification: Missing handler for 'format |' in command spec." << endl;
759 HistoryLog().comment( str::Str() << _("Error sending update message notification."), /*timestamp*/true );
760 return;
761 }
762 }
763
764
770 void RunUpdateMessages( const Pathname & root_r,
771 const Pathname & messagesPath_r,
772 const std::vector<sat::Solvable> & checkPackages_r,
773 ZYppCommitResult & result_r )
774 {
775 if ( checkPackages_r.empty() )
776 return; // no installed packages to check
777
778 MIL << "Looking for new update messages in (" << root_r << ")" << messagesPath_r << endl;
779 Pathname messagesDir( Pathname::assertprefix( root_r, messagesPath_r ) );
780 if ( ! PathInfo( messagesDir ).isDir() )
781 return; // no messages dir
782
783 std::list<std::string> messages;
784 filesystem::readdir( messages, messagesDir, /*dots*/false );
785 if ( messages.empty() )
786 return; // no messages in message dir
787
788 // Now collect all matching messages in result and send them
789 // - "name-version-release"
790 // - "name-version-release-*"
791 HistoryLog historylog;
792 for_( it, checkPackages_r.begin(), checkPackages_r.end() )
793 {
794 std::string prefix( str::form( "%s-%s", it->name().c_str(), it->edition().c_str() ) );
795 for_( sit, messages.begin(), messages.end() )
796 {
797 if ( ! str::hasPrefix( *sit, prefix ) )
798 continue;
799
800 if ( (*sit)[prefix.size()] != '\0' && (*sit)[prefix.size()] != '-' )
801 continue; // if not exact match it had to continue with '-'
802
803 PathInfo message( messagesDir / *sit );
804 if ( ! message.isFile() || message.size() == 0 )
805 continue;
806
807 MIL << "Found update message " << *sit << endl;
808 Pathname localPath( messagesPath_r/(*sit) ); // without root prefix
809 result_r.rUpdateMessages().push_back( UpdateNotificationFile( *it, localPath ) );
810 historylog.comment( str::Str() << _("New update message") << " " << localPath, /*timestamp*/true );
811 }
812 }
813 sendNotification( root_r, result_r.updateMessages() );
814 }
815
819 void logPatchStatusChanges( const sat::Transaction & transaction_r, TargetImpl & target_r )
820 {
821 ResPool::ChangedPseudoInstalled changedPseudoInstalled { ResPool::instance().changedPseudoInstalled() };
822 if ( changedPseudoInstalled.empty() )
823 return;
824
825 if ( ! transaction_r.actionEmpty( ~sat::Transaction::STEP_DONE ) )
826 {
827 // Need to recompute the patch list if commit is incomplete!
828 // We remember the initially established status, then reload the
829 // Target to get the current patch status. Then compare.
830 WAR << "Need to recompute the patch status changes as commit is incomplete!" << endl;
831 ResPool::EstablishedStates establishedStates{ ResPool::instance().establishedStates() };
832 target_r.load();
833 changedPseudoInstalled = establishedStates.changedPseudoInstalled();
834 }
835
836 HistoryLog historylog;
837 for ( const auto & el : changedPseudoInstalled )
838 historylog.patchStateChange( el.first, el.second );
839 }
840
842 } // namespace
844
846 const Pathname & messagesPath_r,
847 const std::vector<sat::Solvable> & checkPackages_r,
850
852
854
856 //
857 // METHOD NAME : TargetImpl::TargetImpl
858 // METHOD TYPE : Ctor
859 //
861 : _root( root_r )
862 , _requestedLocalesFile( home() / "RequestedLocales" )
863 , _autoInstalledFile( home() / "AutoInstalled" )
864 , _hardLocksFile( Pathname::assertprefix( _root, ZConfig::instance().locksFile() ) )
865 , _vendorAttr( Pathname::assertprefix( _root, ZConfig::instance().vendorPath() ) )
866 {
868
870
872 sigMultiversionSpecChanged(); // HACK: see sigMultiversionSpecChanged
873 MIL << "Initialized target on " << _root << endl;
874 }
875
879 static std::string generateRandomId()
880 {
881 std::ifstream uuidprovider( "/proc/sys/kernel/random/uuid" );
883 }
884
890 void updateFileContent( const Pathname &filename,
891 boost::function<bool ()> condition,
892 boost::function<std::string ()> value )
893 {
894 std::string val = value();
895 // if the value is empty, then just dont
896 // do anything, regardless of the condition
897 if ( val.empty() )
898 return;
899
900 if ( condition() )
901 {
902 MIL << "updating '" << filename << "' content." << endl;
903
904 // if the file does not exist we need to generate the uuid file
905
906 std::ofstream filestr;
907 // make sure the path exists
908 filesystem::assert_dir( filename.dirname() );
909 filestr.open( filename.c_str() );
910
911 if ( filestr.good() )
912 {
913 filestr << val;
914 filestr.close();
915 }
916 else
917 {
918 // FIXME, should we ignore the error?
919 ZYPP_THROW(Exception("Can't openfile '" + filename.asString() + "' for writing"));
920 }
921 }
922 }
923
925 static bool fileMissing( const Pathname &pathname )
926 {
927 return ! PathInfo(pathname).isExist();
928 }
929
931 {
932 // bsc#1024741: Omit creating a new uid for chrooted systems (if it already has one, fine)
933 if ( root() != "/" )
934 return;
935
936 // Create the anonymous unique id, used for download statistics
937 Pathname idpath( home() / "AnonymousUniqueId");
938
939 try
940 {
942 std::bind(fileMissing, idpath),
944 }
945 catch ( const Exception &e )
946 {
947 WAR << "Can't create anonymous id file" << endl;
948 }
949
950 }
951
953 {
954 // create the anonymous unique id
955 // this value is used for statistics
956 Pathname flavorpath( home() / "LastDistributionFlavor");
957
958 // is there a product
960 if ( ! p )
961 {
962 WAR << "No base product, I won't create flavor cache" << endl;
963 return;
964 }
965
966 std::string flavor = p->flavor();
967
968 try
969 {
970
972 // only if flavor is not empty
973 functor::Constant<bool>( ! flavor.empty() ),
975 }
976 catch ( const Exception &e )
977 {
978 WAR << "Can't create flavor cache" << endl;
979 return;
980 }
981 }
982
984 //
985 // METHOD NAME : TargetImpl::~TargetImpl
986 // METHOD TYPE : Dtor
987 //
989 {
991 sigMultiversionSpecChanged(); // HACK: see sigMultiversionSpecChanged
992 MIL << "Closed target on " << _root << endl;
993 }
994
996 //
997 // solv file handling
998 //
1000
1002 {
1003 return Pathname::assertprefix( _root, ZConfig::instance().repoSolvfilesPath() / sat::Pool::instance().systemRepoAlias() );
1004 }
1005
1007 {
1008 Pathname base = solvfilesPath();
1010 }
1011
1013 {
1014 Pathname base = solvfilesPath();
1015 Pathname rpmsolv = base/"solv";
1016 Pathname rpmsolvcookie = base/"cookie";
1017
1018 bool build_rpm_solv = true;
1019 // lets see if the rpm solv cache exists
1020
1021 RepoStatus rpmstatus( rpmDbRepoStatus(_root) && RepoStatus(_root/"etc/products.d") );
1022
1024 if ( solvexisted )
1025 {
1026 // see the status of the cache
1028 MIL << "Read cookie: " << cookie << endl;
1029 if ( cookie.isExist() )
1030 {
1032 // now compare it with the rpm database
1033 if ( status == rpmstatus )
1034 build_rpm_solv = false;
1035 MIL << "Read cookie: " << rpmsolvcookie << " says: "
1036 << (build_rpm_solv ? "outdated" : "uptodate") << endl;
1037 }
1038 }
1039
1040 if ( build_rpm_solv )
1041 {
1042 // if the solvfile dir does not exist yet, we better create it
1043 filesystem::assert_dir( base );
1044
1045 Pathname oldSolvFile( solvexisted ? rpmsolv : Pathname() ); // to speedup rpmdb2solv
1046
1048 if ( !tmpsolv )
1049 {
1050 // Can't create temporary solv file, usually due to insufficient permission
1051 // (user query while @System solv needs refresh). If so, try switching
1052 // to a location within zypps temp. space (will be cleaned at application end).
1053
1054 bool switchingToTmpSolvfile = false;
1055 Exception ex("Failed to cache rpm database.");
1056 ex.remember(str::form("Cannot create temporary file under %s.", base.c_str()));
1057
1058 if ( ! solvfilesPathIsTemp() )
1059 {
1060 base = getZYpp()->tmpPath() / sat::Pool::instance().systemRepoAlias();
1061 rpmsolv = base/"solv";
1062 rpmsolvcookie = base/"cookie";
1063
1064 filesystem::assert_dir( base );
1066
1067 if ( tmpsolv )
1068 {
1069 WAR << "Using a temporary solv file at " << base << endl;
1071 _tmpSolvfilesPath = base;
1072 }
1073 else
1074 {
1075 ex.remember(str::form("Cannot create temporary file under %s.", base.c_str()));
1076 }
1077 }
1078
1079 if ( ! switchingToTmpSolvfile )
1080 {
1081 ZYPP_THROW(ex);
1082 }
1083 }
1084
1085 // Take care we unlink the solvfile on exception
1087
1089#ifdef ZYPP_RPMDB2SOLV_PATH
1090 cmd.push_back( ZYPP_RPMDB2SOLV_PATH );
1091#else
1092 cmd.push_back( "rpmdb2solv" );
1093#endif
1094 if ( ! _root.empty() ) {
1095 cmd.push_back( "-r" );
1096 cmd.push_back( _root.asString() );
1097 }
1098 cmd.push_back( "-D" );
1099 cmd.push_back( rpm().dbPath().asString() );
1100 cmd.push_back( "-X" ); // autogenerate pattern/product/... from -package
1101 // bsc#1104415: no more application support // cmd.push_back( "-A" ); // autogenerate application pseudo packages
1102 cmd.push_back( "-p" );
1103 cmd.push_back( Pathname::assertprefix( _root, "/etc/products.d" ).asString() );
1104
1105 if ( ! oldSolvFile.empty() )
1106 cmd.push_back( oldSolvFile.asString() );
1107
1108 cmd.push_back( "-o" );
1109 cmd.push_back( tmpsolv.path().asString() );
1110
1112 std::string errdetail;
1113
1114 for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() ) {
1115 WAR << " " << output;
1116 if ( errdetail.empty() ) {
1117 errdetail = prog.command();
1118 errdetail += '\n';
1119 }
1120 errdetail += output;
1121 }
1122
1123 int ret = prog.close();
1124 if ( ret != 0 )
1125 {
1126 Exception ex(str::form("Failed to cache rpm database (%d).", ret));
1127 ex.remember( errdetail );
1128 ZYPP_THROW(ex);
1129 }
1130
1132 if ( ret != 0 )
1133 ZYPP_THROW(Exception("Failed to move cache to final destination"));
1134 // if this fails, don't bother throwing exceptions
1135 filesystem::chmod( rpmsolv, 0644 );
1136
1137 rpmstatus.saveToCookieFile(rpmsolvcookie);
1138
1139 // We keep it.
1141 sat::updateSolvFileIndex( rpmsolv ); // content digest for zypper bash completion
1142
1143 // system-hook: Finally send notification to plugins
1144 if ( root() == "/" )
1145 {
1147 plugins.load( ZConfig::instance().pluginsPath()/"system" );
1148 if ( plugins )
1149 plugins.send( PluginFrame( "PACKAGESETCHANGED" ) );
1150 }
1151 }
1152 else
1153 {
1154 // On the fly add missing solv.idx files for bash completion.
1155 if ( ! PathInfo(base/"solv.idx").isExist() )
1157 }
1158 return build_rpm_solv;
1159 }
1160
1162 {
1163 load( false );
1164 }
1165
1167 {
1168 Repository system( sat::Pool::instance().findSystemRepo() );
1169 if ( system )
1170 system.eraseFromPool();
1171 }
1172
1174 {
1175 bool newCache = buildCache();
1176 MIL << "New cache built: " << (newCache?"true":"false") <<
1177 ", force loading: " << (force?"true":"false") << endl;
1178
1179 // now add the repos to the pool
1180 sat::Pool satpool( sat::Pool::instance() );
1181 Pathname rpmsolv( solvfilesPath() / "solv" );
1182 MIL << "adding " << rpmsolv << " to pool(" << satpool.systemRepoAlias() << ")" << endl;
1183
1184 // Providing an empty system repo, unload any old content
1185 Repository system( sat::Pool::instance().findSystemRepo() );
1186
1187 if ( system && ! system.solvablesEmpty() )
1188 {
1189 if ( newCache || force )
1190 {
1191 system.eraseFromPool(); // invalidates system
1192 }
1193 else
1194 {
1195 return; // nothing to do
1196 }
1197 }
1198
1199 if ( ! system )
1200 {
1201 system = satpool.systemRepo();
1202 }
1203
1204 try
1205 {
1206 MIL << "adding " << rpmsolv << " to system" << endl;
1207 system.addSolv( rpmsolv );
1208 }
1209 catch ( const Exception & exp )
1210 {
1211 ZYPP_CAUGHT( exp );
1212 MIL << "Try to handle exception by rebuilding the solv-file" << endl;
1213 clearCache();
1214 buildCache();
1215
1216 system.addSolv( rpmsolv );
1217 }
1218 satpool.rootDir( _root );
1219
1220 // (Re)Load the requested locales et al.
1221 // If the requested locales are empty, we leave the pool untouched
1222 // to avoid undoing changes the application applied. We expect this
1223 // to happen on a bare metal installation only. An already existing
1224 // target should be loaded before its settings are changed.
1225 {
1227 if ( ! requestedLocales.empty() )
1228 {
1229 satpool.initRequestedLocales( requestedLocales );
1230 }
1231 }
1232 {
1233 if ( ! PathInfo( _autoInstalledFile.file() ).isExist() )
1234 {
1235 // Initialize from history, if it does not exist
1237 if ( PathInfo( historyFile ).isExist() )
1238 {
1240 SolvIdentFile::Data onSystemByAuto;
1241 for_( it, system.solvablesBegin(), system.solvablesEnd() )
1242 {
1243 IdString ident( (*it).ident() );
1244 if ( onSystemByUser.find( ident ) == onSystemByUser.end() )
1245 onSystemByAuto.insert( ident );
1246 }
1247 _autoInstalledFile.setData( onSystemByAuto );
1248 }
1249 // on the fly removed any obsolete SoftLocks file
1250 filesystem::unlink( home() / "SoftLocks" );
1251 }
1252 // read from AutoInstalled file
1254 for ( const auto & idstr : _autoInstalledFile.data() )
1255 q.push( idstr.id() );
1256 satpool.setAutoInstalled( q );
1257 }
1258
1259 // Load the needreboot package specs
1260 {
1262 needrebootSpec.addProvides( Capability("installhint(reboot-needed)") );
1263 needrebootSpec.addProvides( Capability("kernel") );
1264
1265 Pathname needrebootFile { Pathname::assertprefix( root(), ZConfig::instance().needrebootFile() ) };
1266 if ( PathInfo( needrebootFile ).isFile() )
1267 needrebootSpec.parseFrom( needrebootFile );
1268
1270 if ( PathInfo( needrebootDir ).isDir() )
1271 {
1272 static const StrMatcher isRpmConfigBackup( "\\.rpm(new|save|orig)$", Match::REGEX );
1273
1275 [&]( const Pathname & dir_r, const char *const str_r )->bool
1276 {
1277 if ( ! isRpmConfigBackup( str_r ) )
1278 {
1279 Pathname needrebootFile { needrebootDir / str_r };
1280 if ( PathInfo( needrebootFile ).isFile() )
1281 needrebootSpec.parseFrom( needrebootFile );
1282 }
1283 return true;
1284 });
1285 }
1286 satpool.setNeedrebootSpec( std::move(needrebootSpec) );
1287 }
1288
1289 if ( ZConfig::instance().apply_locks_file() )
1290 {
1292 if ( ! hardLocks.empty() )
1293 {
1294 ResPool::instance().setHardLockQueries( hardLocks );
1295 }
1296 }
1297
1298 // now that the target is loaded, we can cache the flavor
1300
1301 MIL << "Target loaded: " << system.solvablesSize() << " resolvables" << endl;
1302 }
1303
1305 //
1306 // COMMIT
1307 //
1310 {
1311 // ----------------------------------------------------------------- //
1313 bool explicitDryRun = policy_r.dryRun(); // explicit dry run will trigger a fileconflict check, implicit (download-only) not.
1314
1315 ShutdownLock lck("zypp", "Zypp commit running.");
1316
1317 // Fake outstanding YCP fix: Honour restriction to media 1
1318 // at installation, but install all remaining packages if post-boot.
1319 if ( policy_r.restrictToMedia() > 1 )
1320 policy_r.allMedia();
1321
1322 if ( policy_r.downloadMode() == DownloadDefault ) {
1323 if ( root() == "/" )
1324 policy_r.downloadMode(DownloadInHeaps);
1325 else {
1326 if ( policy_r.singleTransModeEnabled() )
1327 policy_r.downloadMode(DownloadInAdvance);
1328 else
1329 policy_r.downloadMode(DownloadAsNeeded);
1330 }
1331 }
1332 // DownloadOnly implies dry-run.
1333 else if ( policy_r.downloadMode() == DownloadOnly )
1334 policy_r.dryRun( true );
1335 // ----------------------------------------------------------------- //
1336
1337 MIL << "TargetImpl::commit(<pool>, " << policy_r << ")" << endl;
1338
1340 // Compute transaction:
1342 ZYppCommitResult result( root() );
1343 result.rTransaction() = pool_r.resolver().getTransaction();
1344 result.rTransaction().order();
1345 // steps: this is our todo-list
1347 if ( policy_r.restrictToMedia() )
1348 {
1349 // Collect until the 1st package from an unwanted media occurs.
1350 // Further collection could violate install order.
1351 MIL << "Restrict to media number " << policy_r.restrictToMedia() << endl;
1352 for_( it, result.transaction().begin(), result.transaction().end() )
1353 {
1354 if ( makeResObject( *it )->mediaNr() > 1 )
1355 break;
1356 steps.push_back( *it );
1357 }
1358 }
1359 else
1360 {
1361 result.rTransactionStepList().insert( steps.end(), result.transaction().begin(), result.transaction().end() );
1362 }
1363 MIL << "Todo: " << result << endl;
1364
1366 // Prepare execution of commit plugins:
1369
1370 if ( ( root() == "/" || zypp::env::TRANSACTIONAL_UPDATE() ) && ! policy_r.dryRun() )
1371 {
1372 commitPlugins.load( ZConfig::instance().pluginsPath()/"commit" );
1373 }
1374 if ( commitPlugins )
1375 commitPlugins.send( transactionPluginFrame( "COMMITBEGIN", steps ) );
1376
1378 // Write out a testcase if we're in dist upgrade mode.
1380 if ( pool_r.resolver().upgradeMode() || pool_r.resolver().upgradingRepos() )
1381 {
1382 if ( ! policy_r.dryRun() )
1383 {
1385 }
1386 else
1387 {
1388 DBG << "dryRun: Not writing upgrade testcase." << endl;
1389 }
1390 }
1391
1393 // Store non-package data:
1395 if ( ! policy_r.dryRun() )
1396 {
1398 // requested locales
1399 _requestedLocalesFile.setLocales( pool_r.getRequestedLocales() );
1400 // autoinstalled
1401 {
1403 for ( sat::Queue::value_type id : result.rTransaction().autoInstalled() )
1404 newdata.insert( IdString(id) );
1406 }
1407 // hard locks
1408 if ( ZConfig::instance().apply_locks_file() )
1409 {
1411 pool_r.getHardLockQueries( newdata );
1413 }
1414 }
1415 else
1416 {
1417 DBG << "dryRun: Not storing non-package data." << endl;
1418 }
1419
1421 // First collect and display all messages
1422 // associated with patches to be installed.
1424 if ( ! policy_r.dryRun() )
1425 {
1426 for_( it, steps.begin(), steps.end() )
1427 {
1428 if ( ! it->satSolvable().isKind<Patch>() )
1429 continue;
1430
1431 PoolItem pi( *it );
1432 if ( ! pi.status().isToBeInstalled() )
1433 continue;
1434
1435 Patch::constPtr patch( asKind<Patch>(pi.resolvable()) );
1436 if ( ! patch ||patch->message().empty() )
1437 continue;
1438
1439 MIL << "Show message for " << patch << endl;
1441 if ( ! report->show( patch ) )
1442 {
1443 WAR << "commit aborted by the user" << endl;
1445 }
1446 }
1447 }
1448 else
1449 {
1450 DBG << "dryRun: Not checking patch messages." << endl;
1451 }
1452
1454 // Remove/install packages.
1456
1457 bool singleTransMode = policy_r.singleTransModeEnabled();
1458
1459 DBG << "commit log file is set to: " << HistoryLog::fname() << endl;
1460 if ( ! policy_r.dryRun() || policy_r.downloadMode() == DownloadOnly )
1461 {
1462 // Prepare the package cache. Pass all items requiring download.
1464 packageCache.setCommitList( steps.begin(), steps.end() );
1465
1466 bool miss = false;
1467 if ( policy_r.downloadMode() != DownloadAsNeeded )
1468 {
1469 // Preload the cache. Until now this means pre-loading all packages.
1470 // Once DownloadInHeaps is fully implemented, this will change and
1471 // we may actually have more than one heap.
1472 for_( it, steps.begin(), steps.end() )
1473 {
1474 switch ( it->stepType() )
1475 {
1478 // proceed: only install actionas may require download.
1479 break;
1480
1481 default:
1482 // next: no download for or non-packages and delete actions.
1483 continue;
1484 break;
1485 }
1486
1487 PoolItem pi( *it );
1488 if ( pi->isKind<Package>() || pi->isKind<SrcPackage>() )
1489 {
1491 try
1492 {
1493 localfile = packageCache.get( pi );
1494 localfile.resetDispose(); // keep the package file in the cache
1495 }
1496 catch ( const AbortRequestException & exp )
1497 {
1498 it->stepStage( sat::Transaction::STEP_ERROR );
1499 miss = true;
1500 WAR << "commit cache preload aborted by the user" << endl;
1502 break;
1503 }
1504 catch ( const SkipRequestException & exp )
1505 {
1506 ZYPP_CAUGHT( exp );
1507 it->stepStage( sat::Transaction::STEP_ERROR );
1508 miss = true;
1509 WAR << "Skipping cache preload package " << pi->asKind<Package>() << " in commit" << endl;
1510 continue;
1511 }
1512 catch ( const Exception & exp )
1513 {
1514 // bnc #395704: missing catch causes abort.
1515 // TODO see if packageCache fails to handle errors correctly.
1516 ZYPP_CAUGHT( exp );
1517 it->stepStage( sat::Transaction::STEP_ERROR );
1518 miss = true;
1519 INT << "Unexpected Error: Skipping cache preload package " << pi->asKind<Package>() << " in commit" << endl;
1520 continue;
1521 }
1522 }
1523 }
1524 packageCache.preloaded( true ); // try to avoid duplicate infoInCache CBs in commit
1525 }
1526
1527 if ( miss )
1528 {
1529 ERR << "Some packages could not be provided. Aborting commit."<< endl;
1530 }
1531 else
1532 {
1533 if ( ! policy_r.dryRun() )
1534 {
1535 if ( policy_r.singleTransModeEnabled() ) {
1537 } else {
1538 // if cache is preloaded, check for file conflicts
1540 commit( policy_r, packageCache, result );
1541 }
1542 }
1543 else
1544 {
1545 DBG << "dryRun/downloadOnly: Not installing/deleting anything." << endl;
1546 if ( explicitDryRun ) {
1547 if ( policy_r.singleTransModeEnabled() ) {
1548 // single trans mode does a test install via rpm
1550 } else {
1551 // if cache is preloaded, check for file conflicts
1553 }
1554 }
1555 }
1556 }
1557 }
1558 else
1559 {
1560 DBG << "dryRun: Not downloading/installing/deleting anything." << endl;
1561 if ( explicitDryRun ) {
1562 // if cache is preloaded, check for file conflicts
1564 }
1565 }
1566
1567 {
1568 // NOTE: Removing rpm in a transaction, rpm removes the /var/lib/rpm compat symlink.
1569 // We re-create it, in case it was lost to prevent legacy tools from accidentally
1570 // assuming no database is present.
1571 if ( ! PathInfo(_root/"/var/lib/rpm",PathInfo::LSTAT).isExist()
1572 && PathInfo(_root/"/usr/lib/sysimage/rpm").isDir() ) {
1573 WAR << "(rpm removed in commit?) Inject missing /var/lib/rpm compat symlink to /usr/lib/sysimage/rpm" << endl;
1574 filesystem::assert_dir( _root/"/var/lib" );
1575 filesystem::symlink( "../../usr/lib/sysimage/rpm", _root/"/var/lib/rpm" );
1576 }
1577 }
1578
1580 // Send result to commit plugins:
1582 if ( commitPlugins )
1583 commitPlugins.send( transactionPluginFrame( "COMMITEND", steps ) );
1584
1586 // Try to rebuild solv file while rpm database is still in cache
1588 if ( ! policy_r.dryRun() )
1589 {
1590 buildCache();
1591 }
1592
1593 MIL << "TargetImpl::commit(<pool>, " << policy_r << ") returns: " << result << endl;
1594 return result;
1595 }
1596
1598 //
1599 // COMMIT internal
1600 //
1602 namespace
1603 {
1604 struct NotifyAttemptToModify
1605 {
1606 NotifyAttemptToModify( ZYppCommitResult & result_r ) : _result( result_r ) {}
1607
1608 void operator()()
1609 { if ( _guard ) { _result.attemptToModify( true ); _guard = false; } }
1610
1611 TrueBool _guard;
1612 ZYppCommitResult & _result;
1613 };
1614 } // namespace
1615
1619 {
1620 // steps: this is our todo-list
1621 ZYppCommitResult::TransactionStepList & steps( result_r.rTransactionStepList() );
1622 MIL << "TargetImpl::commit(<list>" << policy_r << ")" << steps.size() << endl;
1623
1625
1626 // Send notification once upon 1st call to rpm
1627 NotifyAttemptToModify attemptToModify( result_r );
1628
1629 bool abort = false;
1630
1631 // bsc#1181328: Some systemd tools require /proc to be mounted
1632 AssertProcMounted assertProcMounted( _root );
1633 AssertDevMounted assertDevMounted( _root ); // also /dev
1634
1636 std::vector<sat::Solvable> successfullyInstalledPackages;
1637 TargetImpl::PoolItemList remaining;
1638
1639 for_( step, steps.begin(), steps.end() )
1640 {
1641 PoolItem citem( *step );
1642 if ( step->stepType() == sat::Transaction::TRANSACTION_IGNORE )
1643 {
1644 if ( citem->isKind<Package>() )
1645 {
1646 // for packages this means being obsoleted (by rpm)
1647 // thius no additional action is needed.
1648 step->stepStage( sat::Transaction::STEP_DONE );
1649 continue;
1650 }
1651 }
1652
1653 if ( citem->isKind<Package>() )
1654 {
1655 Package::constPtr p = citem->asKind<Package>();
1656 if ( citem.status().isToBeInstalled() )
1657 {
1659 try
1660 {
1662 }
1663 catch ( const AbortRequestException &e )
1664 {
1665 WAR << "commit aborted by the user" << endl;
1666 abort = true;
1667 step->stepStage( sat::Transaction::STEP_ERROR );
1668 break;
1669 }
1670 catch ( const SkipRequestException &e )
1671 {
1672 ZYPP_CAUGHT( e );
1673 WAR << "Skipping package " << p << " in commit" << endl;
1674 step->stepStage( sat::Transaction::STEP_ERROR );
1675 continue;
1676 }
1677 catch ( const Exception &e )
1678 {
1679 // bnc #395704: missing catch causes abort.
1680 // TODO see if packageCache fails to handle errors correctly.
1681 ZYPP_CAUGHT( e );
1682 INT << "Unexpected Error: Skipping package " << p << " in commit" << endl;
1683 step->stepStage( sat::Transaction::STEP_ERROR );
1684 continue;
1685 }
1686
1687 // create a installation progress report proxy
1688 RpmInstallPackageReceiver progress( citem.resolvable() );
1689 progress.connect(); // disconnected on destruction.
1690
1691 bool success = false;
1692 rpm::RpmInstFlags flags( policy_r.rpmInstFlags() & rpm::RPMINST_JUSTDB );
1693 // Why force and nodeps?
1694 //
1695 // Because zypp builds the transaction and the resolver asserts that
1696 // everything is fine.
1697 // We use rpm just to unpack and register the package in the database.
1698 // We do this step by step, so rpm is not aware of the bigger context.
1699 // So we turn off rpms internal checks, because we do it inside zypp.
1700 flags |= rpm::RPMINST_NODEPS;
1701 flags |= rpm::RPMINST_FORCE;
1702 //
1703 if (p->multiversionInstall()) flags |= rpm::RPMINST_NOUPGRADE;
1704 if (policy_r.dryRun()) flags |= rpm::RPMINST_TEST;
1705 if (policy_r.rpmExcludeDocs()) flags |= rpm::RPMINST_EXCLUDEDOCS;
1706 if (policy_r.rpmNoSignature()) flags |= rpm::RPMINST_NOSIGNATURE;
1707
1708 attemptToModify();
1709 try
1710 {
1714
1715 if ( progress.aborted() )
1716 {
1717 WAR << "commit aborted by the user" << endl;
1718 localfile.resetDispose(); // keep the package file in the cache
1719 abort = true;
1720 step->stepStage( sat::Transaction::STEP_ERROR );
1721 break;
1722 }
1723 else
1724 {
1725 if ( citem.isNeedreboot() ) {
1726 auto rebootNeededFile = root() / "/run/reboot-needed";
1729 }
1730
1731 success = true;
1732 step->stepStage( sat::Transaction::STEP_DONE );
1733 }
1734 }
1735 catch ( Exception & excpt_r )
1736 {
1738 localfile.resetDispose(); // keep the package file in the cache
1739
1740 if ( policy_r.dryRun() )
1741 {
1742 WAR << "dry run failed" << endl;
1743 step->stepStage( sat::Transaction::STEP_ERROR );
1744 break;
1745 }
1746 // else
1747 if ( progress.aborted() )
1748 {
1749 WAR << "commit aborted by the user" << endl;
1750 abort = true;
1751 }
1752 else
1753 {
1754 WAR << "Install failed" << endl;
1755 }
1756 step->stepStage( sat::Transaction::STEP_ERROR );
1757 break; // stop
1758 }
1759
1760 if ( success && !policy_r.dryRun() )
1761 {
1762 citem.status().resetTransact( ResStatus::USER );
1763 successfullyInstalledPackages.push_back( citem.satSolvable() );
1764 step->stepStage( sat::Transaction::STEP_DONE );
1765 }
1766 }
1767 else
1768 {
1769 RpmRemovePackageReceiver progress( citem.resolvable() );
1770 progress.connect(); // disconnected on destruction.
1771
1772 bool success = false;
1773 rpm::RpmInstFlags flags( policy_r.rpmInstFlags() & rpm::RPMINST_JUSTDB );
1774 flags |= rpm::RPMINST_NODEPS;
1775 if (policy_r.dryRun()) flags |= rpm::RPMINST_TEST;
1776
1777 attemptToModify();
1778 try
1779 {
1780 rpm().removePackage( p, flags, &postTransCollector );
1782
1783 if ( progress.aborted() )
1784 {
1785 WAR << "commit aborted by the user" << endl;
1786 abort = true;
1787 step->stepStage( sat::Transaction::STEP_ERROR );
1788 break;
1789 }
1790 else
1791 {
1792 success = true;
1793 step->stepStage( sat::Transaction::STEP_DONE );
1794 }
1795 }
1796 catch (Exception & excpt_r)
1797 {
1799 if ( progress.aborted() )
1800 {
1801 WAR << "commit aborted by the user" << endl;
1802 abort = true;
1803 step->stepStage( sat::Transaction::STEP_ERROR );
1804 break;
1805 }
1806 // else
1807 WAR << "removal of " << p << " failed";
1808 step->stepStage( sat::Transaction::STEP_ERROR );
1809 }
1810 if ( success && !policy_r.dryRun() )
1811 {
1812 citem.status().resetTransact( ResStatus::USER );
1813 step->stepStage( sat::Transaction::STEP_DONE );
1814 }
1815 }
1816 }
1817 else if ( ! policy_r.dryRun() ) // other resolvables (non-Package)
1818 {
1819 // Status is changed as the buddy package buddy
1820 // gets installed/deleted. Handle non-buddies only.
1821 if ( ! citem.buddy() )
1822 {
1823 if ( citem->isKind<Product>() )
1824 {
1825 Product::constPtr p = citem->asKind<Product>();
1826 if ( citem.status().isToBeInstalled() )
1827 {
1828 ERR << "Can't install orphan product without release-package! " << citem << endl;
1829 }
1830 else
1831 {
1832 // Deleting the corresponding product entry is all we con do.
1833 // So the product will no longer be visible as installed.
1834 std::string referenceFilename( p->referenceFilename() );
1835 if ( referenceFilename.empty() )
1836 {
1837 ERR << "Can't remove orphan product without 'referenceFilename'! " << citem << endl;
1838 }
1839 else
1840 {
1841 Pathname referencePath { Pathname("/etc/products.d") / referenceFilename }; // no root prefix for rpmdb lookup!
1842 if ( ! rpm().hasFile( referencePath.asString() ) )
1843 {
1844 // If it's not owned by a package, we can delete it.
1845 referencePath = Pathname::assertprefix( _root, referencePath ); // now add a root prefix
1846 if ( filesystem::unlink( referencePath ) != 0 )
1847 ERR << "Delete orphan product failed: " << referencePath << endl;
1848 }
1849 else
1850 {
1851 WAR << "Won't remove orphan product: '/etc/products.d/" << referenceFilename << "' is owned by a package." << endl;
1852 }
1853 }
1854 }
1855 }
1856 else if ( citem->isKind<SrcPackage>() && citem.status().isToBeInstalled() )
1857 {
1858 // SrcPackage is install-only
1861 }
1862
1863 citem.status().resetTransact( ResStatus::USER );
1864 step->stepStage( sat::Transaction::STEP_DONE );
1865 }
1866
1867 } // other resolvables
1868
1869 } // for
1870
1871 // Process any remembered %posttrans and/or %transfiletrigger(postun|in)
1872 // scripts. If aborting, at least log if scripts were omitted.
1873 if ( not abort )
1874 postTransCollector.executeScripts( rpm() );
1875 else
1876 postTransCollector.discardScripts();
1877
1878 // Check presence of update scripts/messages. If aborting,
1879 // at least log omitted scripts.
1880 if ( ! successfullyInstalledPackages.empty() )
1881 {
1882 if ( ! RunUpdateScripts( _root, ZConfig::instance().update_scriptsPath(),
1884 {
1885 WAR << "Commit aborted by the user" << endl;
1886 abort = true;
1887 }
1888 // send messages after scripts in case some script generates output,
1889 // that should be kept in t %ghost message file.
1890 RunUpdateMessages( _root, ZConfig::instance().update_messagesPath(),
1892 result_r );
1893 }
1894
1895 // jsc#SLE-5116: Log patch status changes to history
1896 // NOTE: Should be the last action as it may need to reload
1897 // the Target in case of an incomplete transaction.
1898 logPatchStatusChanges( result_r.transaction(), *this );
1899
1900 if ( abort )
1901 {
1902 HistoryLog().comment( "Commit was aborted." );
1904 }
1905 }
1906
1907
1914 struct SendSingleTransReport : public callback::SendReport<rpm::SingleTransReport>
1915 {
1917 void sendLogline( const std::string & line_r, ReportType::loglevel level_r = ReportType::loglevel::msg )
1918 {
1919 callback::UserData data { ReportType::contentLogline };
1920 data.set( "line", std::cref(line_r) );
1921 data.set( "level", level_r );
1922 report( data );
1923 }
1925 void sendLoglineRpm( const std::string & line_r, unsigned rpmlevel_r )
1926 {
1927 auto u2rpmlevel = []( unsigned rpmlevel_r ) -> ReportType::loglevel {
1928 switch ( rpmlevel_r ) {
1929 case RPMLOG_EMERG: [[fallthrough]]; // system is unusable
1930 case RPMLOG_ALERT: [[fallthrough]]; // action must be taken immediately
1931 case RPMLOG_CRIT: // critical conditions
1932 return ReportType::loglevel::crt;
1933 case RPMLOG_ERR: // error conditions
1934 return ReportType::loglevel::err;
1935 case RPMLOG_WARNING: // warning conditions
1936 return ReportType::loglevel::war;
1937 default: [[fallthrough]];
1938 case RPMLOG_NOTICE: [[fallthrough]]; // normal but significant condition
1939 case RPMLOG_INFO: // informational
1940 return ReportType::loglevel::msg;
1941 case RPMLOG_DEBUG:
1942 return ReportType::loglevel::dbg;
1943 }
1944 };
1946 }
1947
1948 private:
1950 { (*this)->report( userData_r ); }
1951 };
1952
1953 const callback::UserData::ContentType rpm::SingleTransReport::contentLogline { "zypp-rpm", "logline" };
1954
1955 const callback::UserData::ContentType rpm::InstallResolvableReportSA::contentRpmout( "zypp-rpm","installpkgsa" );
1956 const callback::UserData::ContentType rpm::RemoveResolvableReportSA::contentRpmout( "zypp-rpm","removepkgsa" );
1957 const callback::UserData::ContentType rpm::CommitScriptReportSA::contentRpmout( "zypp-rpm","scriptsa" );
1958 const callback::UserData::ContentType rpm::TransactionReportSA::contentRpmout( "zypp-rpm","transactionsa" );
1959 const callback::UserData::ContentType rpm::CleanupPackageReportSA::contentRpmout( "zypp-rpm","cleanupkgsa" );
1960
1962 {
1963 SendSingleTransReport report; // active throughout the whole rpm transaction
1964
1965 // steps: this is our todo-list
1966 ZYppCommitResult::TransactionStepList & steps( result_r.rTransactionStepList() );
1967 MIL << "TargetImpl::commit(<list>" << policy_r << ")" << steps.size() << endl;
1968
1970
1971 // Send notification once upon calling rpm
1972 NotifyAttemptToModify attemptToModify( result_r );
1973
1974 // let zypper know we executed in one big transaction so in case of failures it can show extended error information
1975 result_r.setSingleTransactionMode( true );
1976
1977 // bsc#1181328: Some systemd tools require /proc to be mounted
1978 AssertProcMounted assertProcMounted( _root );
1979 AssertDevMounted assertDevMounted( _root ); // also /dev
1980
1981 // Why nodeps?
1982 //
1983 // Because zypp builds the transaction and the resolver asserts that
1984 // everything is fine, or the user decided to ignore problems.
1985 rpm::RpmInstFlags flags( policy_r.rpmInstFlags()
1987 // skip signature checks, we did that already
1990 // ignore untrusted keys since we already checked those earlier
1992
1993 proto::target::Commit commit;
1994 commit.flags = flags;
1995 commit.ignoreArch = ( !ZConfig::instance().systemArchitecture().compatibleWith( ZConfig::instance().defaultSystemArchitecture() ) );
1996 commit.arch = ZConfig::instance().systemArchitecture().asString();
1997 commit.dbPath = rpm().dbPath().asString();
1998 commit.root = rpm().root().asString();
1999 commit.lockFilePath = ZYppFactory::lockfileDir().asString();
2000
2001 bool abort = false;
2002 zypp::AutoDispose<std::unordered_map<int, ManagedFile>> locCache([]( std::unordered_map<int, ManagedFile> &data ){
2003 for ( auto &[_, value] : data ) {
2004 (void)_; // unsused; for older g++ versions
2005 value.resetDispose();
2006 }
2007 data.clear();
2008 });
2009
2010 // fill the transaction
2011 for ( int stepId = 0; (ZYppCommitResult::TransactionStepList::size_type)stepId < steps.size() && !abort ; ++stepId ) {
2012 auto &step = steps[stepId];
2013 PoolItem citem( step );
2014 if ( step.stepType() == sat::Transaction::TRANSACTION_IGNORE ) {
2015 if ( citem->isKind<Package>() )
2016 {
2017 // for packages this means being obsoleted (by rpm)
2018 // thius no additional action is needed.
2019 step.stepStage( sat::Transaction::STEP_DONE );
2020 continue;
2021 }
2022 }
2023
2024 if ( citem->isKind<Package>() ) {
2025 Package::constPtr p = citem->asKind<Package>();
2026 if ( citem.status().isToBeInstalled() )
2027 {
2028 try {
2030
2031 proto::target::InstallStep tStep;
2032 tStep.stepId = stepId;
2033 tStep.pathname = locCache.value()[stepId]->asString();
2034 tStep.multiversion = p->multiversionInstall() ;
2035
2036 commit.transactionSteps.push_back( std::move(tStep) );
2037 }
2038 catch ( const AbortRequestException &e )
2039 {
2040 WAR << "commit aborted by the user" << endl;
2041 abort = true;
2043 break;
2044 }
2045 catch ( const SkipRequestException &e )
2046 {
2047 ZYPP_CAUGHT( e );
2048 WAR << "Skipping package " << p << " in commit" << endl;
2050 continue;
2051 }
2052 catch ( const Exception &e )
2053 {
2054 // bnc #395704: missing catch causes abort.
2055 // TODO see if packageCache fails to handle errors correctly.
2056 ZYPP_CAUGHT( e );
2057 INT << "Unexpected Error: Skipping package " << p << " in commit" << endl;
2059 continue;
2060 }
2061 } else {
2062
2063 proto::target::RemoveStep tStep;
2064 tStep.stepId = stepId;
2065 tStep.name = p->name();
2066 tStep.version = p->edition().version();
2067 tStep.release = p->edition().release();
2068 tStep.arch = p->arch().asString();
2069 commit.transactionSteps.push_back(std::move(tStep));
2070
2071 }
2072 } else if ( citem->isKind<SrcPackage>() && citem.status().isToBeInstalled() ) {
2073 // SrcPackage is install-only
2075
2076 try {
2077 // provide on local disk
2079
2080 proto::target::InstallStep tStep;
2081 tStep.stepId = stepId;
2082 tStep.pathname = locCache.value()[stepId]->asString();
2083 tStep.multiversion = false;
2084 commit.transactionSteps.push_back(std::move(tStep));
2085
2086 } catch ( const Exception &e ) {
2087 ZYPP_CAUGHT( e );
2088 INT << "Unexpected Error: Skipping package " << p << " in commit" << endl;
2090 continue;
2091 }
2092 }
2093 }
2094
2095 std::vector<sat::Solvable> successfullyInstalledPackages;
2096
2097 if ( commit.transactionSteps.size() ) {
2098
2099 // create the event loop early
2101
2102 attemptToModify();
2103
2104 const std::vector<int> interceptedSignals {
2105 SIGINT,
2106 SIGTERM,
2107 SIGHUP,
2108 SIGQUIT
2109 };
2110
2111 auto unixSignals = loop->eventDispatcher()->unixSignalSource();
2112 unixSignals->sigReceived ().connect ([]( int signum ){
2113 // translator: %1% is the received unix signal name, %2% is the numerical value of the received signal
2114 JobReport::error ( str::Format(_("Received signal :\"%1% (%2%)\", to ensure the consistency of the system it is not possible to cancel a running rpm transaction.") ) % strsignal(signum) % signum );
2115 });
2116 for( const auto &sig : interceptedSignals )
2117 unixSignals->addSignal ( sig );
2118
2119 Deferred cleanupSigs([&](){
2120 for( const auto &sig : interceptedSignals )
2121 unixSignals->removeSignal ( sig );
2122 });
2123
2124 // transaction related variables:
2125 //
2126 // the index of the step in the transaction list that we currenty execute.
2127 // this can be -1
2128 int currentStepId = -1;
2129
2130 // sync flag, every time zypp-rpm finishes executing a step it writes a tag into
2131 // the script fd, once we receive it we set this flag to true and ignore all output
2132 // that is written to the pipe ( aside from buffering it ) until we finalize the current report
2133 // and start a new one
2134 bool gotEndOfScript = false;
2135
2136 // the possible reports we emit during the transaction
2137 std::unique_ptr<callback::SendReport <rpm::TransactionReportSA>> transactionreport;
2138 std::unique_ptr<callback::SendReport <rpm::InstallResolvableReportSA>> installreport;
2139 std::unique_ptr<callback::SendReport <rpm::RemoveResolvableReportSA>> uninstallreport;
2140 std::unique_ptr<callback::SendReport <rpm::CommitScriptReportSA>> scriptreport;
2141 std::unique_ptr<callback::SendReport <rpm::CleanupPackageReportSA>> cleanupreport;
2142
2143 // this will be set if we receive a transaction error description
2144 std::optional<proto::target::TransactionError> transactionError;
2145
2146 // infos about the currently executed script, empty if no script is currently executed
2147 std::string currentScriptType;
2148 std::string currentScriptPackage;
2149
2150 // buffer to collect rpm output per report, this will be written to the log once the
2151 // report ends
2152 std::string rpmmsg;
2153
2154 // maximum number of lines that we are buffering in rpmmsg
2155 constexpr auto MAXRPMMESSAGELINES = 10000;
2156
2157 // current number of lines in the rpmmsg line buffer. This is capped to MAXRPMMESSAGELINES
2158 unsigned lineno = 0;
2159
2160 // the sources to communicate with zypp-rpm, we will associate pipes with them further down below
2163
2164 // this will be the communication channel, will be created once the process starts and
2165 // we can receive data
2166 zyppng::StompFrameStreamRef msgStream;
2167
2168
2169 // helper function that sends RPM output to the currently active report, writing a warning to the log
2170 // if there is none
2171 const auto &sendRpmLineToReport = [&]( const std::string &line ){
2172
2173 const auto &sendLogRep = [&]( auto &report, const auto &cType ){
2175 if ( currentStepId >= 0 )
2176 cmdout.set( "solvable", steps.at(currentStepId).satSolvable() );
2177 cmdout.set( "line", line );
2178 report->report(cmdout);
2179 };
2180
2181 if ( installreport ) {
2182 sendLogRep( (*installreport), rpm::InstallResolvableReportSA::contentRpmout );
2183 } else if ( uninstallreport ) {
2184 sendLogRep( (*uninstallreport), rpm::RemoveResolvableReportSA::contentRpmout );
2185 } else if ( scriptreport ) {
2186 sendLogRep( (*scriptreport), rpm::CommitScriptReportSA::contentRpmout );
2187 } else if ( transactionreport ) {
2188 sendLogRep( (*transactionreport), rpm::TransactionReportSA::contentRpmout );
2189 } else if ( cleanupreport ) {
2190 sendLogRep( (*cleanupreport), rpm::CleanupPackageReportSA::contentRpmout );
2191 } else {
2192 WAR << "Got rpm output without active report " << line; // no endl! - readLine does not trim
2193 }
2194
2195 // remember rpm output
2196 if ( lineno >= MAXRPMMESSAGELINES ) {
2197 if ( line.find( " scriptlet failed, " ) == std::string::npos ) // always log %script errors
2198 return;
2199 }
2200 rpmmsg += line;
2201 if ( line.back() != '\n' )
2202 rpmmsg += '\n';
2203 };
2204
2205
2206 // callback and helper function to process data that is received on the script FD
2207 const auto &processDataFromScriptFd = [&](){
2208
2209 while ( scriptSource->canReadLine() ) {
2210
2211 if ( gotEndOfScript )
2212 return;
2213
2214 std::string l = scriptSource->readLine().asString();
2215 if( str::endsWith( l, endOfScriptTag ) ) {
2216 gotEndOfScript = true;
2217 std::string::size_type rawsize { l.size() - endOfScriptTag.size() };
2218 if ( not rawsize )
2219 return;
2220 l = l.substr( 0, rawsize );
2221 }
2222 L_DBG("zypp-rpm") << "[rpm> " << l; // no endl! - readLine does not trim
2224 }
2225 };
2226 scriptSource->sigReadyRead().connect( processDataFromScriptFd );
2227
2228 // helper function that just waits until the end of script tag was received on the scriptSource
2229 const auto &waitForScriptEnd = [&]() {
2230
2231 // nothing to wait for
2232 if ( gotEndOfScript )
2233 return;
2234
2235 // we process all available data
2237
2238 // end of script is always sent by zypp-rpm, we need to wait for it to keep order
2239 while ( scriptSource->readFdOpen() && scriptSource->canRead() && !gotEndOfScript ) {
2240 // readyRead will trigger processDataFromScriptFd so no need to call it again
2241 // we still got nothing, lets wait for more
2242 scriptSource->waitForReadyRead( 100 );
2243 }
2244 };
2245
2246 const auto &aboutToStartNewReport = [&](){
2247
2249 ERR << "There is still a running report, this is a bug" << std::endl;
2250 assert(false);
2251 }
2252
2253 gotEndOfScript = false;
2254 };
2255
2256 const auto &writeRpmMsgToHistory = [&](){
2257 if ( rpmmsg.size() == 0 )
2258 return;
2259
2260 if ( lineno >= MAXRPMMESSAGELINES )
2261 rpmmsg += "[truncated]\n";
2262
2263 std::ostringstream sstr;
2264 sstr << "rpm output:" << endl << rpmmsg << endl;
2265 HistoryLog().comment(sstr.str());
2266 };
2267
2268 // helper function that closes the current report and cleans up the ressources
2269 const auto &finalizeCurrentReport = [&]() {
2270 sat::Transaction::Step *step = nullptr;
2272 if ( currentStepId >= 0 ) {
2273 step = &steps.at(currentStepId);
2274 resObj = makeResObject( step->satSolvable() );
2275 }
2276
2277 if ( installreport ) {
2279 if ( step->stepStage() == sat::Transaction::STEP_ERROR ) {
2280
2282 str::form("%s install failed", step->ident().c_str()),
2283 true /*timestamp*/);
2284
2286
2287 ( *installreport)->finish( resObj, rpm::InstallResolvableReportSA::INVALID );
2288 } else {
2289 ( *installreport)->progress( 100, resObj );
2290 ( *installreport)->finish( resObj, rpm::InstallResolvableReportSA::NO_ERROR );
2291
2292 if ( currentStepId >= 0 )
2293 locCache.value().erase( currentStepId );
2294 successfullyInstalledPackages.push_back( step->satSolvable() );
2295
2296 PoolItem citem( *step );
2297 if ( !( flags & rpm::RPMINST_TEST ) ) {
2298 // @TODO are we really doing this just for install?
2299 if ( citem.isNeedreboot() ) {
2300 auto rebootNeededFile = root() / "/run/reboot-needed";
2303 }
2304 citem.status().resetTransact( ResStatus::USER );
2306 }
2307
2309 str::form("%s installed ok", step->ident().c_str()),
2310 true /*timestamp*/);
2311
2313 }
2314 }
2315 if ( uninstallreport ) {
2317 if ( step->stepStage() == sat::Transaction::STEP_ERROR ) {
2318
2320 str::form("%s uninstall failed", step->ident().c_str()),
2321 true /*timestamp*/);
2322
2324
2325 ( *uninstallreport)->finish( resObj, rpm::RemoveResolvableReportSA::INVALID );
2326 } else {
2327 ( *uninstallreport)->progress( 100, resObj );
2328 ( *uninstallreport)->finish( resObj, rpm::RemoveResolvableReportSA::NO_ERROR );
2329
2330 PoolItem citem( *step );
2332
2334 str::form("%s removed ok", step->ident().c_str()),
2335 true /*timestamp*/);
2336
2338 }
2339 }
2340 if ( scriptreport ) {
2342 ( *scriptreport)->progress( 100, resObj );
2343 ( *scriptreport)->finish( resObj, rpm::CommitScriptReportSA::NO_ERROR );
2344 }
2345 if ( transactionreport ) {
2347 ( *transactionreport)->progress( 100 );
2348 ( *transactionreport)->finish( rpm::TransactionReportSA::NO_ERROR );
2349 }
2350 if ( cleanupreport ) {
2352 ( *cleanupreport)->progress( 100 );
2353 ( *cleanupreport)->finish( rpm::CleanupPackageReportSA::NO_ERROR );
2354 }
2355 currentStepId = -1;
2356 lineno = 0;
2357 rpmmsg.clear();
2358 currentScriptType.clear();
2359 currentScriptPackage.clear();
2365 };
2366
2367 // This sets up the process and pushes the required transactions steps to it
2368 // careful when changing code here, zypp-rpm relies on the exact order data is transferred:
2369 //
2370 // 1) Size of the commit message , sizeof(zyppng::rpc::HeaderSizeType)
2371 // 2) The Commit Proto message, directly serialized to the FD, without Envelope
2372 // 3) 2 writeable FDs that are set up by the parent Process when forking. The first FD is to be used for message sending, the second one for script output
2373
2374 constexpr std::string_view zyppRpmBinary(ZYPP_RPM_BINARY);
2375
2376 const char *argv[] = {
2377 //"gdbserver",
2378 //"localhost:10001",
2379 zyppRpmBinary.data(),
2380 nullptr
2381 };
2383
2384 // we set up a pipe to communicate with the process, it is too dangerous to use stdout since librpm
2385 // might print to it.
2387 if ( !messagePipe )
2388 ZYPP_THROW( target::rpm::RpmSubprocessException( "Failed to create message pipe" ) );
2389
2390 // open a pipe that we are going to use to receive script output, this is a librpm feature, there is no other
2391 // way than a FD to redirect that output
2393 if ( !scriptPipe )
2394 ZYPP_THROW( target::rpm::RpmSubprocessException( "Failed to create scriptfd" ) );
2395
2396 prog->addFd( messagePipe->writeFd );
2397 prog->addFd( scriptPipe->writeFd );
2398
2399 // set up the AsyncDataSource to read script output
2400 if ( !scriptSource->openFds( std::vector<int>{ scriptPipe->readFd } ) )
2401 ZYPP_THROW( target::rpm::RpmSubprocessException( "Failed to open scriptFD to subprocess" ) );
2402
2403 const auto &processMessages = [&] ( ) {
2404
2405 // lambda function that parses the passed message type and checks if the stepId is a valid offset
2406 // in the steps list.
2407 const auto &checkMsgWithStepId = [&steps]( auto &p ){
2408 if ( !p ) {
2409 ERR << "Failed to parse message from zypp-rpm." << std::endl;
2410 return false;
2411 }
2412
2413 auto id = p->stepId;
2414 if ( id < 0 || id >= steps.size() ) {
2415 ERR << "Received invalid stepId: " << id << " in " << p->typeName << " message from zypp-rpm, ignoring." << std::endl;
2416 return false;
2417 }
2418 return true;
2419 };
2420
2421 while ( const auto &m = msgStream->nextMessage() ) {
2422
2423 // due to librpm behaviour we need to make sense of the order of messages we receive
2424 // because we first get a PackageFinished BEFORE getting a PackageError, same applies to
2425 // Script related messages. What we do is remember the current step we are in and only close
2426 // the step when we get the start of the next one
2427 const auto &mName = m->command();
2428 if ( mName == proto::target::RpmLog::typeName ) {
2429
2430 const auto &p = proto::target::RpmLog::fromStompMessage (*m);
2431 if ( !p ) {
2432 ERR << "Failed to parse " << proto::target::RpmLog::typeName << " message from zypp-rpm." << std::endl;
2433 continue;
2434 }
2435 ( p->level >= RPMLOG_ERR ? L_ERR("zypp-rpm")
2436 : p->level >= RPMLOG_WARNING ? L_WAR("zypp-rpm")
2437 : L_DBG("zypp-rpm") ) << "[rpm " << p->level << "> " << p->line; // no endl! - readLine does not trim
2438 report.sendLoglineRpm( p->line, p->level );
2439
2440 } else if ( mName == proto::target::PackageBegin::typeName ) {
2442
2443 const auto &p = proto::target::PackageBegin::fromStompMessage(*m);
2444 if ( !checkMsgWithStepId( p ) )
2445 continue;
2446
2448
2449 auto & step = steps.at( p->stepId );
2450 currentStepId = p->stepId;
2451 if ( step.stepType() == sat::Transaction::TRANSACTION_ERASE ) {
2452 uninstallreport = std::make_unique< callback::SendReport <rpm::RemoveResolvableReportSA> > ();
2453 ( *uninstallreport )->start( makeResObject( step.satSolvable() ) );
2454 } else {
2455 installreport = std::make_unique< callback::SendReport <rpm::InstallResolvableReportSA> > ();
2456 ( *installreport )->start( makeResObject( step.satSolvable() ) );
2457 }
2458
2459 } else if ( mName == proto::target::PackageFinished::typeName ) {
2460 const auto &p = proto::target::PackageFinished::fromStompMessage(*m);
2461 if ( !checkMsgWithStepId( p ) )
2462 continue;
2463
2464 // here we only set the step stage to done, we however need to wait for the next start in order to send
2465 // the finished report since there might be a error pending to be reported
2466 steps[ p->stepId ].stepStage( sat::Transaction::STEP_DONE );
2467
2468 } else if ( mName == proto::target::PackageProgress::typeName ) {
2469 const auto &p = proto::target::PackageProgress::fromStompMessage(*m);
2470 if ( !checkMsgWithStepId( p ) )
2471 continue;
2472
2473 if ( uninstallreport )
2474 (*uninstallreport)->progress( p->amount, makeResObject( steps.at( p->stepId ) ));
2475 else if ( installreport )
2476 (*installreport)->progress( p->amount, makeResObject( steps.at( p->stepId ) ));
2477 else
2478 ERR << "Received a " << mName << " message but there is no corresponding report running." << std::endl;
2479
2480 } else if ( mName == proto::target::PackageError::typeName ) {
2481 const auto &p = proto::target::PackageError::fromStompMessage(*m);
2482 if ( !checkMsgWithStepId( p ) )
2483 continue;
2484
2485 if ( p->stepId >= 0 && p->stepId < steps.size() )
2486 steps[ p->stepId ].stepStage( sat::Transaction::STEP_ERROR );
2487
2489
2490 } else if ( mName == proto::target::ScriptBegin::typeName ) {
2492
2493 const auto &p = proto::target::ScriptBegin::fromStompMessage(*m);
2494 if ( !p ) {
2495 ERR << "Failed to parse " << proto::target::ScriptBegin::typeName << " message from zypp-rpm." << std::endl;
2496 continue;
2497 }
2498
2500
2502 const auto stepId = p->stepId;
2503 if ( stepId >= 0 && stepId < steps.size() ) {
2504 resPtr = makeResObject( steps.at(stepId).satSolvable() );
2505 }
2506
2507 currentStepId = p->stepId;
2508 scriptreport = std::make_unique< callback::SendReport <rpm::CommitScriptReportSA> > ();
2509 currentScriptType = p->scriptType;
2510 currentScriptPackage = p->scriptPackage;
2511 (*scriptreport)->start( currentScriptType, currentScriptPackage, resPtr );
2512
2513 } else if ( mName == proto::target::ScriptFinished::typeName ) {
2514
2515 // we just read the message, we do not act on it because a ScriptError is reported after ScriptFinished
2516
2517 } else if ( mName == proto::target::ScriptError::typeName ) {
2518
2519 const auto &p = proto::target::ScriptError::fromStompMessage(*m);
2520 if ( !p ) {
2521 ERR << "Failed to parse " << proto::target::ScriptError::typeName << " message from zypp-rpm." << std::endl;
2522 continue;
2523 }
2524
2526 const auto stepId = p->stepId;
2527 if ( stepId >= 0 && stepId < steps.size() ) {
2528 resPtr = makeResObject( steps.at(stepId).satSolvable() );
2529
2530 if ( p->fatal ) {
2531 steps.at( stepId ).stepStage( sat::Transaction::STEP_ERROR );
2532 }
2533
2534 }
2535
2537 str::form("Failed to execute %s script for %s ", currentScriptType.c_str(), currentScriptPackage.size() ? currentScriptPackage.c_str() : "unknown" ),
2538 true /*timestamp*/);
2539
2541
2542 if ( !scriptreport ) {
2543 ERR << "Received a ScriptError message, but there is no running report. " << std::endl;
2544 continue;
2545 }
2546
2547 // before killing the report we need to wait for the script end tag
2549 (*scriptreport)->finish( resPtr, p->fatal ? rpm::CommitScriptReportSA::CRITICAL : rpm::CommitScriptReportSA::WARN );
2550
2551 // manually reset the current report since we already sent the finish(), rest will be reset by the new start
2553 currentStepId = -1;
2554
2555 } else if ( mName == proto::target::CleanupBegin::typeName ) {
2557
2558 const auto &beg = proto::target::CleanupBegin::fromStompMessage(*m);
2559 if ( !beg ) {
2560 ERR << "Failed to parse " << proto::target::CleanupBegin::typeName << " message from zypp-rpm." << std::endl;
2561 continue;
2562 }
2563
2565 cleanupreport = std::make_unique< callback::SendReport <rpm::CleanupPackageReportSA> > ();
2566 (*cleanupreport)->start( beg->nvra );
2567 } else if ( mName == proto::target::CleanupFinished::typeName ) {
2568
2570
2571 } else if ( mName == proto::target::CleanupProgress::typeName ) {
2572 const auto &prog = proto::target::CleanupProgress::fromStompMessage(*m);
2573 if ( !prog ) {
2574 ERR << "Failed to parse " << proto::target::CleanupProgress::typeName << " message from zypp-rpm." << std::endl;
2575 continue;
2576 }
2577
2578 if ( !cleanupreport ) {
2579 ERR << "Received a CleanupProgress message, but there is no running report. " << std::endl;
2580 continue;
2581 }
2582
2583 (*cleanupreport)->progress( prog->amount );
2584
2585 } else if ( mName == proto::target::TransBegin::typeName ) {
2587
2588 const auto &beg = proto::target::TransBegin::fromStompMessage(*m);
2589 if ( !beg ) {
2590 ERR << "Failed to parse " << proto::target::TransBegin::typeName << " message from zypp-rpm." << std::endl;
2591 continue;
2592 }
2593
2595 transactionreport = std::make_unique< callback::SendReport <rpm::TransactionReportSA> > ();
2596 (*transactionreport)->start( beg->name );
2597 } else if ( mName == proto::target::TransFinished::typeName ) {
2598
2600
2601 } else if ( mName == proto::target::TransProgress::typeName ) {
2602 const auto &prog = proto::target::TransProgress::fromStompMessage(*m);
2603 if ( !prog ) {
2604 ERR << "Failed to parse " << proto::target::TransProgress::typeName << " message from zypp-rpm." << std::endl;
2605 continue;
2606 }
2607
2608 if ( !transactionreport ) {
2609 ERR << "Received a TransactionProgress message, but there is no running report. " << std::endl;
2610 continue;
2611 }
2612
2613 (*transactionreport)->progress( prog->amount );
2614 } else if ( mName == proto::target::TransactionError::typeName ) {
2615
2616 const auto &error = proto::target::TransactionError::fromStompMessage(*m);
2617 if ( !error ) {
2618 ERR << "Failed to parse " << proto::target::TransactionError::typeName << " message from zypp-rpm." << std::endl;
2619 continue;
2620 }
2621
2622 // this value is checked later
2623 transactionError = std::move(*error);
2624
2625 } else {
2626 ERR << "Received unexpected message from zypp-rpm: "<< m->command() << ", ignoring" << std::endl;
2627 return;
2628 }
2629
2630 }
2631 };
2632
2633 // setup the rest when zypp-rpm is running
2634 prog->sigStarted().connect( [&](){
2635
2636 // close the ends of the pipes we do not care about
2637 messagePipe->unrefWrite();
2638 scriptPipe->unrefWrite();
2639
2640 // read the stdout and stderr and forward it to our log
2641 prog->connectFunc( &zyppng::IODevice::sigChannelReadyRead, [&]( int channel ){
2642 while( prog->canReadLine( channel ) ) {
2643 L_ERR("zypp-rpm") << ( channel == zyppng::Process::StdOut ? "<stdout> " : "<stderr> " ) << prog->channelReadLine( channel ).asStringView(); // no endl! - readLine does not trim
2644 }
2645 });
2646
2647 // this is the source for control messages from zypp-rpm , we will get structured data information
2648 // in form of STOMP messages
2649 if ( !msgSource->openFds( std::vector<int>{ messagePipe->readFd }, prog->stdinFd() ) )
2650 ZYPP_THROW( target::rpm::RpmSubprocessException( "Failed to open read stream to subprocess" ) );
2651
2654
2655 const auto &msg = commit.toStompMessage();
2656 if ( !msg )
2657 std::rethrow_exception ( msg.error() );
2658
2659 if ( !msgStream->sendMessage( *msg ) ) {
2660 prog->stop( SIGKILL );
2661 ZYPP_THROW( target::rpm::RpmSubprocessException( "Failed to write commit to subprocess" ) );
2662 }
2663 });
2664
2665 // track the childs lifetime
2666 int zyppRpmExitCode = -1;
2667 prog->connectFunc( &zyppng::Process::sigFinished, [&]( int code ){
2668 zyppRpmExitCode = code;
2669 loop->quit();
2670 });
2671
2672 if ( !prog->start( argv ) ) {
2673 HistoryLog().comment( "Commit was aborted, failed to run zypp-rpm" );
2675 }
2676
2677 loop->run();
2678
2679 if ( msgStream ) {
2680 // pull all messages from the IO device
2681 msgStream->readAllMessages();
2682
2683 // make sure to read ALL available messages
2685 }
2686
2687 // we will not receive a new start message , so we need to manually finalize the last report
2689
2690 // make sure to read all data from the log source
2691 bool readMsgs = false;
2692 while( prog->canReadLine( zyppng::Process::StdErr ) ) {
2693 readMsgs = true;
2694 MIL << "zypp-rpm: " << prog->channelReadLine( zyppng::Process::StdErr ).asStringView();
2695 }
2696 while( prog->canReadLine( zyppng::Process::StdOut ) ) {
2697 readMsgs = true;
2698 MIL << "zypp-rpm: " << prog->channelReadLine( zyppng::Process::StdOut ).asStringView();
2699 }
2700
2701 while ( scriptSource->canReadLine() ) {
2702 readMsgs = true;
2703 MIL << "rpm-script-fd: " << scriptSource->readLine().asStringView();
2704 }
2705 if ( scriptSource->bytesAvailable() > 0 ) {
2706 readMsgs = true;
2707 MIL << "rpm-script-fd: " << scriptSource->readAll().asStringView();
2708 }
2709 if ( readMsgs )
2710 MIL << std::endl;
2711
2712 switch ( zyppRpmExitCode ) {
2713 // we need to look at the summary, handle finishedwitherrors like no error here
2714 case zypprpm::NoError:
2715 case zypprpm::RpmFinishedWithError:
2716 break;
2717 case zypprpm::RpmFinishedWithTransactionError: {
2718 // here zypp-rpm sent us a error description
2719 if ( transactionError ) {
2720
2721 std::ostringstream sstr;
2722 sstr << _("Executing the transaction failed because of the following problems:") << "\n";
2723 for ( const auto & err : transactionError->problems ) {
2724 sstr << " " << err << "\n";
2725 }
2726 sstr << std::endl;
2728
2729 } else {
2730 ZYPP_THROW( rpm::RpmTransactionFailedException("RPM failed with a unexpected error, check the logs for more information.") );
2731 }
2732 break;
2733 }
2734 case zypprpm::FailedToOpenDb:
2735 ZYPP_THROW( rpm::RpmDbOpenException( rpm().root(), rpm().dbPath() ) );
2736 break;
2737 case zypprpm::WrongHeaderSize:
2738 case zypprpm::WrongMessageFormat:
2739 ZYPP_THROW( rpm::RpmSubprocessException("Failed to communicate with zypp-rpm, this is most likely a bug. Consider to fall back to legacy transaction strategy.") );
2740 break;
2741 case zypprpm::RpmInitFailed:
2742 ZYPP_THROW( rpm::RpmInitException( rpm().root(), rpm().dbPath() ) );
2743 break;
2744 case zypprpm::FailedToReadPackage:
2745 ZYPP_THROW( rpm::RpmSubprocessException("zypp-rpm was unable to read a package, check the logs for more information.") );
2746 break;
2747 case zypprpm::FailedToAddStepToTransaction:
2748 ZYPP_THROW( rpm::RpmSubprocessException("zypp-rpm failed to build the transaction, check the logs for more information.") );
2749 break;
2750 case zypprpm::RpmOrderFailed:
2751 ZYPP_THROW( rpm::RpmSubprocessException("zypp-rpm failed to order the transaction, check the logs for more information.") );
2752 break;
2753 case zypprpm::FailedToCreateLock:
2754 ZYPP_THROW( rpm::RpmSubprocessException("zypp-rpm failed to create its lockfile, check the logs for more information.") );
2755 break;
2756 }
2757
2758 for ( int stepId = 0; (ZYppCommitResult::TransactionStepList::size_type)stepId < steps.size() && !abort; ++stepId ) {
2759 auto &step = steps[stepId];
2760 PoolItem citem( step );
2761
2762 if ( step.stepStage() == sat::Transaction::STEP_TODO ) {
2763 // other resolvables (non-Package) that are not handled by zypp-rpm
2764 if ( !citem->isKind<Package>() && !policy_r.dryRun() ) {
2765 // Status is changed as the buddy package buddy
2766 // gets installed/deleted. Handle non-buddies only.
2767 if ( ! citem.buddy() && citem->isKind<Product>() ) {
2768 Product::constPtr p = citem->asKind<Product>();
2769
2770 if ( citem.status().isToBeInstalled() ) {
2771 ERR << "Can't install orphan product without release-package! " << citem << endl;
2772 } else {
2773 // Deleting the corresponding product entry is all we con do.
2774 // So the product will no longer be visible as installed.
2775 std::string referenceFilename( p->referenceFilename() );
2776
2777 if ( referenceFilename.empty() ) {
2778 ERR << "Can't remove orphan product without 'referenceFilename'! " << citem << endl;
2779 } else {
2780 Pathname referencePath { Pathname("/etc/products.d") / referenceFilename }; // no root prefix for rpmdb lookup!
2781
2782 if ( ! rpm().hasFile( referencePath.asString() ) ) {
2783 // If it's not owned by a package, we can delete it.
2784 referencePath = Pathname::assertprefix( _root, referencePath ); // now add a root prefix
2785 if ( filesystem::unlink( referencePath ) != 0 )
2786 ERR << "Delete orphan product failed: " << referencePath << endl;
2787 } else {
2788 WAR << "Won't remove orphan product: '/etc/products.d/" << referenceFilename << "' is owned by a package." << endl;
2789 }
2790 }
2791 }
2792 citem.status().resetTransact( ResStatus::USER );
2793 step.stepStage( sat::Transaction::STEP_DONE );
2794 }
2795 }
2796 }
2797 }
2798 }
2799
2800 // Check presence of update scripts/messages. If aborting,
2801 // at least log omitted scripts.
2802 if ( ! successfullyInstalledPackages.empty() )
2803 {
2804 if ( ! RunUpdateScripts( _root, ZConfig::instance().update_scriptsPath(),
2806 {
2807 WAR << "Commit aborted by the user" << endl;
2808 abort = true;
2809 }
2810 // send messages after scripts in case some script generates output,
2811 // that should be kept in t %ghost message file.
2812 RunUpdateMessages( _root, ZConfig::instance().update_messagesPath(),
2814 result_r );
2815 }
2816
2817 // jsc#SLE-5116: Log patch status changes to history
2818 // NOTE: Should be the last action as it may need to reload
2819 // the Target in case of an incomplete transaction.
2820 logPatchStatusChanges( result_r.transaction(), *this );
2821
2822 if ( abort ) {
2823 HistoryLog().comment( "Commit was aborted." );
2825 }
2826 }
2827
2829
2831 {
2832 return _rpm;
2833 }
2834
2835 bool TargetImpl::providesFile (const std::string & path_str, const std::string & name_str) const
2836 {
2837 return _rpm.hasFile(path_str, name_str);
2838 }
2839
2841 namespace
2842 {
2844 {
2846 PathInfo baseproduct( Pathname::assertprefix( root_r, "/etc/products.d/baseproduct" ) );
2847
2848 if ( baseproduct.isFile() )
2849 {
2850 try
2851 {
2853 }
2854 catch ( const Exception & excpt )
2855 {
2856 ZYPP_CAUGHT( excpt );
2857 }
2858 }
2859 else if ( PathInfo( Pathname::assertprefix( root_r, "/etc/products.d" ) ).isDir() )
2860 {
2861 ERR << "baseproduct symlink is dangling or missing: " << baseproduct << endl;
2862 }
2863 return ret;
2864 }
2865
2866 inline Pathname staticGuessRoot( const Pathname & root_r )
2867 {
2868 if ( root_r.empty() )
2869 {
2870 // empty root: use existing Target or assume "/"
2871 Pathname ret ( ZConfig::instance().systemRoot() );
2872 if ( ret.empty() )
2873 return Pathname("/");
2874 return ret;
2875 }
2876 return root_r;
2877 }
2878
2879 inline std::string firstNonEmptyLineIn( const Pathname & file_r )
2880 {
2881 std::ifstream idfile( file_r.c_str() );
2882 for( iostr::EachLine in( idfile ); in; in.next() )
2883 {
2884 std::string line( str::trim( *in ) );
2885 if ( ! line.empty() )
2886 return line;
2887 }
2888 return std::string();
2889 }
2890 } // namespace
2892
2894 {
2895 ResPool pool(ResPool::instance());
2896 for_( it, pool.byKindBegin<Product>(), pool.byKindEnd<Product>() )
2897 {
2898 Product::constPtr p = (*it)->asKind<Product>();
2899 if ( p->isTargetDistribution() )
2900 return p;
2901 }
2902 return nullptr;
2903 }
2904
2906 {
2908 const Target_constPtr target( getZYpp()->getTarget() );
2909 if ( target && target->root() == needroot )
2910 return target->requestedLocales();
2911 return RequestedLocalesFile( home(needroot) / "RequestedLocales" ).locales();
2912 }
2913
2915 {
2916 MIL << "updateAutoInstalled if changed..." << endl;
2918 for ( auto id : sat::Pool::instance().autoInstalled() )
2919 newdata.insert( IdString(id) ); // explicit ctor!
2920 _autoInstalledFile.setData( std::move(newdata) );
2921 }
2922
2924 { return baseproductdata( _root ).registerTarget(); }
2925 // static version:
2927 { return baseproductdata( staticGuessRoot(root_r) ).registerTarget(); }
2928
2930 { return baseproductdata( _root ).registerRelease(); }
2931 // static version:
2933 { return baseproductdata( staticGuessRoot(root_r) ).registerRelease();}
2934
2936 { return baseproductdata( _root ).registerFlavor(); }
2937 // static version:
2939 { return baseproductdata( staticGuessRoot(root_r) ).registerFlavor();}
2940
2942 {
2945 ret.shortName = pdata.shortName();
2946 ret.summary = pdata.summary();
2947 return ret;
2948 }
2949 // static version:
2951 {
2954 ret.shortName = pdata.shortName();
2955 ret.summary = pdata.summary();
2956 return ret;
2957 }
2958
2960 {
2961 if ( _distributionVersion.empty() )
2962 {
2964 if ( !_distributionVersion.empty() )
2965 MIL << "Remember distributionVersion = '" << _distributionVersion << "'" << endl;
2966 }
2967 return _distributionVersion;
2968 }
2969 // static version
2971 {
2972 std::string distributionVersion = baseproductdata( staticGuessRoot(root_r) ).edition().version();
2973 if ( distributionVersion.empty() )
2974 {
2975 // ...But the baseproduct method is not expected to work on RedHat derivatives.
2976 // On RHEL, Fedora and others the "product version" is determined by the first package
2977 // providing 'system-release'. This value is not hardcoded in YUM and can be configured
2978 // with the $distroverpkg variable.
2980 if ( ZConfig::instance().systemRoot() == Pathname() )
2981 {
2982 try
2983 {
2984 tmprpmdb.reset( new rpm::RpmDb );
2985 tmprpmdb->initDatabase( /*default ctor uses / but no additional keyring exports */ );
2986 }
2987 catch( ... )
2988 {
2989 return "";
2990 }
2991 }
2992 rpm::librpmDb::db_const_iterator it;
2993 if ( it.findByProvides( ZConfig::instance().distroverpkg() ) )
2994 distributionVersion = it->tag_version();
2995 }
2996 return distributionVersion;
2997 }
2998
2999
3001 {
3002 return firstNonEmptyLineIn( home() / "LastDistributionFlavor" );
3003 }
3004 // static version:
3006 {
3007 return firstNonEmptyLineIn( staticGuessRoot(root_r) / "/var/lib/zypp/LastDistributionFlavor" );
3008 }
3009
3011 namespace
3012 {
3013 std::string guessAnonymousUniqueId( const Pathname & root_r )
3014 {
3015 // bsc#1024741: Omit creating a new uid for chrooted systems (if it already has one, fine)
3016 std::string ret( firstNonEmptyLineIn( root_r / "/var/lib/zypp/AnonymousUniqueId" ) );
3017 if ( ret.empty() && root_r != "/" )
3018 {
3019 // if it has nonoe, use the outer systems one
3020 ret = firstNonEmptyLineIn( "/var/lib/zypp/AnonymousUniqueId" );
3021 }
3022 return ret;
3023 }
3024 }
3025
3027 {
3028 return guessAnonymousUniqueId( root() );
3029 }
3030 // static version:
3032 {
3034 }
3035
3037
3039 {
3040 MIL << "New VendorAttr: " << vendorAttr_r << endl;
3041 _vendorAttr = std::move(vendorAttr_r);
3042 }
3044
3046 {
3047 // provide on local disk
3049 // create a installation progress report proxy
3051 progress.connect(); // disconnected on destruction.
3052 // install it
3054 }
3055
3057 {
3058 // provide on local disk
3061 return prov.provideSrcPackage( srcPackage_r );
3062 }
3064 } // namespace target
3067} // namespace zypp
#define idstr(V)
#define MAXRPMMESSAGELINES
Definition RpmDb.cc:65
Pathname _mountpoint
#define SUBST_IF(PAT, VAL)
ZYppCommitResult & _result
TrueBool _guard
Architecture.
Definition Arch.h:37
const std::string & asString() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition Arch.cc:499
Reference counted access to a Tp object calling a custom Dispose function when the last AutoDispose h...
Definition AutoDispose.h:95
reference value() const
Reference to the Tp object.
void resetDispose()
Set no dispose function.
void reset()
Reset to default Ctor values.
A sat capability.
Definition Capability.h:63
Mime type like 'type/subtype' classification of content.
Definition ContentType.h:30
Store and operate on date (time_t).
Definition Date.h:33
static Date now()
Return the current time.
Definition Date.h:78
Edition represents [epoch:]version[-release]
Definition Edition.h:61
std::string version() const
Version.
Definition Edition.cc:94
std::string release() const
Release.
Definition Edition.cc:110
epoch_t epoch() const
Epoch.
Definition Edition.cc:82
Base class for Exception.
Definition Exception.h:147
Execute a program and give access to its io An object of this class encapsulates the execution of an ...
std::vector< std::string > Arguments
Writing the zypp history file.
Definition HistoryLog.h:57
void stampCommand()
Log info about the current process.
static void setRoot(const Pathname &root)
Set new root directory to the default history log file path.
void remove(const PoolItem &pi)
Log removal of a package.
static const Pathname & fname()
Get the current log file path.
void install(const PoolItem &pi)
Log installation (or update) of a package.
void comment(const std::string &comment, bool timestamp=false)
Log a comment (even multiline).
Access to the sat-pools string space.
Definition IdString.h:44
std::string asString() const
Conversion to std::string
Definition IdString.h:99
@ REGEX
Regular Expression.
Definition StrMatcher.h:48
Package interface.
Definition Package.h:34
TraitsType::constPtrType constPtr
Definition Package.h:39
Class representing a patch.
Definition Patch.h:38
TraitsType::constPtrType constPtr
Definition Patch.h:43
Parallel execution of stateful PluginScripts.
Command frame for communication with PluginScript.
Definition PluginFrame.h:42
Combining sat::Solvable and ResStatus.
Definition PoolItem.h:51
Product interface.
Definition Product.h:34
TraitsType::constPtrType constPtr
Definition Product.h:39
Track changing files or directories.
Definition RepoStatus.h:41
static RepoStatus fromCookieFile(const Pathname &path)
Reads the status from a cookie file.
Global ResObject pool.
Definition ResPool.h:62
static ResPool instance()
Singleton ctor.
Definition ResPool.cc:38
byKind_iterator byKindEnd(const ResKind &kind_r) const
Definition ResPool.h:269
byKind_iterator byKindBegin(const ResKind &kind_r) const
Definition ResPool.h:262
EstablishedStates::ChangedPseudoInstalled ChangedPseudoInstalled
Map holding pseudo installed items where current and established status differ.
Definition ResPool.h:342
TraitsType::constPtrType constPtr
Definition Resolvable.h:59
Attempts to create a lock to prevent the system from going into hibernate/shutdown.
SrcPackage interface.
Definition SrcPackage.h:30
TraitsType::constPtrType constPtr
Definition SrcPackage.h:36
String matching (STRING|SUBSTRING|GLOB|REGEX).
Definition StrMatcher.h:298
Definition of vendor equivalence.
Definition VendorAttr.h:61
Interim helper class to collect global options and settings.
Definition ZConfig.h:69
static ZConfig & instance()
Singleton ctor.
Definition ZConfig.cc:925
Options and policies for ZYpp::commit.
Result returned from ZYpp::commit.
const Pathname & root() const
Remembered root directory of the target.
TransactionStepList & rTransactionStepList()
Manipulate transactionStepList.
std::vector< sat::Transaction::Step > TransactionStepList
const sat::Transaction & transaction() const
The full transaction list.
sat::Transaction & rTransaction()
Manipulate transaction.
static zypp::Pathname lockfileDir()
Typesafe passing of user data via callbacks.
Definition UserData.h:40
bool set(const std::string &key_r, AnyType val_r)
Set the value for key (nonconst version always returns true).
Definition UserData.h:119
zypp::ContentType ContentType
Definition UserData.h:51
Wrapper class for stat/lstat.
Definition PathInfo.h:222
bool isExist() const
Return whether valid stat info exists.
Definition PathInfo.h:282
Pathname dirname() const
Return all but the last component od this path.
Definition Pathname.h:126
const char * c_str() const
String representation.
Definition Pathname.h:112
const std::string & asString() const
String representation.
Definition Pathname.h:93
bool empty() const
Test for an empty path.
Definition Pathname.h:116
static Pathname assertprefix(const Pathname &root_r, const Pathname &path_r)
Return path_r prefixed with root_r, unless it is already prefixed.
Definition Pathname.cc:272
Provide a new empty temporary file and delete it when no longer needed.
Definition TmpPath.h:128
static TmpFile makeSibling(const Pathname &sibling_r)
Provide a new empty temporary directory as sibling.
Definition TmpPath.cc:220
Data returned by ProductFileReader.
bool empty() const
Whether this is an empty object without valid data.
static ProductFileData scanFile(const Pathname &file_r)
Parse one file (or symlink) and return the ProductFileData parsed.
Provides files from different repos.
Global sat-pool.
Definition Pool.h:47
static Pool instance()
Singleton ctor.
Definition Pool.h:55
Libsolv Id queue wrapper.
Definition Queue.h:36
detail::IdType value_type
Definition Queue.h:39
Define a set of Solvables by ident and provides.
A Solvable object within the sat Pool.
Definition Solvable.h:54
A single step within a Transaction.
Libsolv transaction wrapper.
Definition Transaction.h:52
const_iterator end() const
Iterator behind the last TransactionStep.
StringQueue autoInstalled() const
Return the ident strings of all packages that would be auto-installed after the transaction is run.
const_iterator begin() const
Iterator to the first TransactionStep.
bool order()
Order transaction steps for commit.
@ TRANSACTION_MULTIINSTALL
[M] Install(multiversion) item (
Definition Transaction.h:67
@ TRANSACTION_INSTALL
[+] Install(update) item
Definition Transaction.h:66
@ TRANSACTION_IGNORE
[ ] Nothing (includes implicit deletes due to obsoletes and non-package actions)
Definition Transaction.h:64
@ TRANSACTION_ERASE
[-] Delete item
Definition Transaction.h:65
@ STEP_DONE
[OK] success
Definition Transaction.h:74
@ STEP_TODO
[__] unprocessed
Definition Transaction.h:73
Target::commit helper optimizing package provision.
void setData(const Data &data_r)
Store new Data.
const Data & data() const
Return the data.
pool::PoolTraits::HardLockQueries Data
Save and restore locale set from file.
const LocaleSet & locales() const
Return the loacale set.
void setLocales(const LocaleSet &locales_r)
Store a new locale set.
void tryLevel(target::rpm::InstallResolvableReport::RpmLevel level_r)
Extract and remember posttrans scripts for later execution.
bool aborted() const
Returns true if removing is aborted during progress.
const Data & data() const
Return the data.
std::unordered_set< IdString > Data
void setData(const Data &data_r)
Store new Data.
const Pathname & file() const
Return the file path.
Base class for concrete Target implementations.
Definition TargetImpl.h:54
std::string targetDistributionRelease() const
This is register.release attribute of the installed base product.
const VendorAttr & vendorAttr() const
The targets current vendor equivalence settings.
Definition TargetImpl.h:199
std::string targetDistribution() const
This is register.target attribute of the installed base product.
std::list< PoolItem > PoolItemList
list of pool items
Definition TargetImpl.h:59
LocaleSet requestedLocales() const
Languages to be supported by the system.
Definition TargetImpl.h:155
void updateAutoInstalled()
Update the database of autoinstalled packages.
ManagedFile provideSrcPackage(const SrcPackage_constPtr &srcPackage_r)
Provides a source package on the Target.
Pathname _root
Path to the target.
Definition TargetImpl.h:222
RequestedLocalesFile _requestedLocalesFile
Requested Locales database.
Definition TargetImpl.h:226
void createLastDistributionFlavorCache() const
generates a cache of the last product flavor
std::string _distributionVersion
Cache distributionVersion.
Definition TargetImpl.h:232
rpm::RpmDb _rpm
RPM database.
Definition TargetImpl.h:224
~TargetImpl() override
Dtor.
rpm::RpmDb & rpm()
The RPM database.
Pathname solvfilesPath() const
The solv file location actually in use (default or temp).
Definition TargetImpl.h:92
std::string distributionVersion() const
This is version attribute of the installed base product.
void createAnonymousId() const
generates the unique anonymous id which is called when creating the target
SolvIdentFile _autoInstalledFile
user/auto installed database
Definition TargetImpl.h:228
Product::constPtr baseProduct() const
returns the target base installed product, also known as the distribution or platform.
Target::DistributionLabel distributionLabel() const
This is shortName and summary attribute of the installed base product.
bool providesFile(const std::string &path_str, const std::string &name_str) const
If the package is installed and provides the file Needed to evaluate split provides during Resolver::...
HardLocksFile _hardLocksFile
Hard-Locks database.
Definition TargetImpl.h:230
Pathname root() const
The root set for this target.
Definition TargetImpl.h:116
void load(bool force=true)
std::string distributionFlavor() const
This is flavor attribute of the installed base product but does not require the target to be loaded a...
void commitInSingleTransaction(const ZYppCommitPolicy &policy_r, CommitPackageCache &packageCache_r, ZYppCommitResult &result_r)
Commit ordered changes (internal helper)
void installSrcPackage(const SrcPackage_constPtr &srcPackage_r)
Install a source package on the Target.
ZYppCommitResult commit(ResPool pool_r, const ZYppCommitPolicy &policy_r)
Commit changes in the pool.
VendorAttr _vendorAttr
vendor equivalence settings.
Definition TargetImpl.h:234
Pathname home() const
The directory to store things.
Definition TargetImpl.h:120
void commitFindFileConflicts(const ZYppCommitPolicy &policy_r, ZYppCommitResult &result_r)
Commit helper checking for file conflicts after download.
Pathname defaultSolvfilesPath() const
The systems default solv file location.
std::string anonymousUniqueId() const
anonymous unique id
TargetImpl(const Pathname &root_r="/", bool doRebuild_r=false)
Ctor.
bool solvfilesPathIsTemp() const
Whether we're using a temp.
Definition TargetImpl.h:96
std::string targetDistributionFlavor() const
This is register.flavor attribute of the installed base product.
Interface to the rpm program.
Definition RpmDb.h:50
void installPackage(const Pathname &filename, RpmInstFlags flags=RPMINST_NONE)
install rpm package
Definition RpmDb.cc:1664
void initDatabase(Pathname root_r=Pathname(), bool doRebuild_r=false)
Prepare access to the rpm database below root_r.
Definition RpmDb.cc:273
const Pathname & root() const
Definition RpmDb.h:91
void removePackage(const std::string &name_r, RpmInstFlags flags=RPMINST_NONE)
remove rpm package
Definition RpmDb.cc:1870
const Pathname & dbPath() const
Definition RpmDb.h:99
void closeDatabase()
Block further access to the rpm database and go back to uninitialized state.
Definition RpmDb.cc:363
bool hasFile(const std::string &file_r, const std::string &name_r="") const
Return true if at least one package owns a certain file (name_r empty) Return true if package name_r ...
Definition RpmDb.cc:974
static Ptr create()
SignalProxy< void(uint)> sigChannelReadyRead()
Definition iodevice.cc:373
static Ptr create()
Definition process.cpp:49
SignalProxy< void(int)> sigFinished()
Definition process.cpp:294
SignalProxy< void()> sigMessageReceived()
static Ptr create(IODevice::Ptr iostr)
@ UNKNOWN
Definition richtext.cc:49
Namespace intended to collect all environment variables we use.
Definition Env.h:23
bool TRANSACTIONAL_UPDATE()
Definition TargetImpl.cc:84
int chmod(const Pathname &path, mode_t mode)
Like 'chmod'.
Definition PathInfo.cc:1097
int symlink(const Pathname &oldpath, const Pathname &newpath)
Like 'symlink'.
Definition PathInfo.cc:860
const StrMatcher & matchNoDots()
Convenience returning StrMatcher( "[^.]*", Match::GLOB )
Definition PathInfo.cc:26
int assert_file(const Pathname &path, unsigned mode)
Create an empty file if it does not yet exist.
Definition PathInfo.cc:1191
int recursive_rmdir(const Pathname &path)
Like 'rm -r DIR'.
Definition PathInfo.cc:417
int unlink(const Pathname &path)
Like 'unlink'.
Definition PathInfo.cc:705
int readdir(std::list< std::string > &retlist_r, const Pathname &path_r, bool dots_r)
Return content of directory via retlist.
Definition PathInfo.cc:610
int dirForEach(const Pathname &dir_r, const StrMatcher &matcher_r, function< bool(const Pathname &, const char *const)> fnc_r)
Definition PathInfo.cc:32
int addmod(const Pathname &path, mode_t mode)
Add the mode bits to the file given by path.
Definition PathInfo.cc:1109
int assert_dir(const Pathname &path, unsigned mode)
Like 'mkdir -p'.
Definition PathInfo.cc:324
int readlink(const Pathname &symlink_r, Pathname &target_r)
Like 'readlink'.
Definition PathInfo.cc:929
std::string md5sum(const Pathname &file)
Compute a files md5sum.
Definition PathInfo.cc:1029
int rename(const Pathname &oldpath, const Pathname &newpath)
Like 'rename'.
Definition PathInfo.cc:747
int touch(const Pathname &path)
Change file's modification and access times.
Definition PathInfo.cc:1242
std::string getline(std::istream &str)
Read one line from stream.
Definition IOStream.cc:33
std::string toJSON(void)
Definition Json.h:136
void updateSolvFileIndex(const Pathname &solvfile_r)
Create solv file content digest for zypper bash completion.
Definition Pool.cc:286
bool hasPrefix(const C_Str &str_r, const C_Str &prefix_r)
Return whether str_r has prefix prefix_r.
Definition String.h:1026
std::string toLower(const std::string &s)
Return lowercase version of s.
Definition String.cc:178
bool startsWith(const C_Str &str_r, const C_Str &prefix_r)
alias for hasPrefix
Definition String.h:1084
bool endsWith(const C_Str &str_r, const C_Str &prefix_r)
alias for hasSuffix
Definition String.h:1091
std::string form(const char *format,...) __attribute__((format(printf
Printf style construction of std::string.
Definition String.cc:37
bool strToBool(const C_Str &str, bool default_r)
Parse str into a bool depending on the default value.
Definition String.h:429
unsigned splitEscaped(const C_Str &line_r, TOutputIterator result_r, const C_Str &sepchars_r=" \t", bool withEmpty=false)
Split line_r into words with respect to escape delimeters.
Definition String.h:594
std::string trim(const std::string &s, const Trim trim_r)
Definition String.cc:224
void XRunUpdateMessages(const Pathname &root_r, const Pathname &messagesPath_r, const std::vector< sat::Solvable > &checkPackages_r, ZYppCommitResult &result_r)
std::string rpmDbStateHash(const Pathname &root_r)
void writeUpgradeTestcase()
static bool fileMissing(const Pathname &pathname)
helper functor
void updateFileContent(const Pathname &filename, boost::function< bool()> condition, boost::function< std::string()> value)
updates the content of filename if condition is true, setting the content the the value returned by v...
RepoStatus rpmDbRepoStatus(const Pathname &root_r)
static std::string generateRandomId()
generates a random id using uuidgen
Easy-to use interface to the ZYPP dependency resolver.
std::unordered_set< Locale > LocaleSet
Definition Locale.h:29
ResObject::Ptr makeResObject(const sat::Solvable &solvable_r)
Create ResObject from sat::Solvable.
Definition ResObject.cc:43
std::list< UpdateNotificationFile > UpdateNotifications
std::string asString(const Patch::Category &obj)
Definition Patch.cc:122
@ DownloadInHeaps
Similar to DownloadInAdvance, but try to split the transaction into heaps, where at the end of each h...
@ DownloadOnly
Just download all packages to the local cache.
@ DownloadAsNeeded
Alternating download and install.
@ DownloadInAdvance
First download all packages to the local cache.
@ DownloadDefault
libzypp will decide what to do.
zypp::IdString IdString
Definition idstring.h:16
static bool error(const std::string &msg_r, const UserData &userData_r=UserData())
send error text
JSON array.
Definition Json.h:257
JSON object.
Definition Json.h:322
static PoolImpl & myPool()
Definition PoolImpl.cc:184
Convenient building of std::string with boost::format.
Definition String.h:253
Convenience SendReport<rpm::SingleTransReport> wrapper.
void report(const callback::UserData &userData_r)
void sendLoglineRpm(const std::string &line_r, unsigned rpmlevel_r)
Convenience to send a contentLogline translating a rpm loglevel.
void sendLogline(const std::string &line_r, ReportType::loglevel level_r=ReportType::loglevel::msg)
Convenience to send a contentLogline.
static std::optional< Pipe > create(int flags=0)
#define NON_COPYABLE(CLASS)
Delete copy ctor and copy assign.
Definition Easy.h:59
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition Easy.h:28
#define NON_MOVABLE(CLASS)
Delete move ctor and move assign.
Definition Easy.h:69
#define ZYPP_CAUGHT(EXCPT)
Drops a logline telling the Exception was caught (in order to handle it).
Definition Exception.h:440
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition Exception.h:424
#define _(MSG)
Definition Gettext.h:39
#define L_ERR(GROUP)
Definition Logger.h:109
#define DBG
Definition Logger.h:97
#define MIL
Definition Logger.h:98
#define ERR
Definition Logger.h:100
#define L_WAR(GROUP)
Definition Logger.h:108
#define WAR
Definition Logger.h:99
#define L_DBG(GROUP)
Definition Logger.h:106
#define INT
Definition Logger.h:102
#define IMPL_PTR_TYPE(NAME)
Interface to gettext.