libzypp 17.31.23
RpmPostTransCollector.cc
Go to the documentation of this file.
1/*---------------------------------------------------------------------\
2| ____ _ __ __ ___ |
3| |__ / \ / / . \ . \ |
4| / / \ V /| _/ _/ |
5| / /__ | | | | | | |
6| /_____||_| |_| |_| |
7| |
8\---------------------------------------------------------------------*/
11#include <iostream>
12#include <fstream>
13#include <optional>
14#include <zypp/base/LogTools.h>
15#include <zypp/base/NonCopyable.h>
16#include <zypp/base/Gettext.h>
17#include <zypp/base/Regex.h>
18#include <zypp/base/IOStream.h>
21
22#include <zypp/TmpPath.h>
23#include <zypp/PathInfo.h>
24#include <zypp/HistoryLog.h>
25#include <zypp/ZYppCallbacks.h>
26#include <zypp/ExternalProgram.h>
29#include <zypp/ZConfig.h>
30#include <zypp/ZYppCallbacks.h>
31
32using std::endl;
33#undef ZYPP_BASE_LOGGER_LOGGROUP
34#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::posttrans"
35
37namespace zypp
38{
40 namespace target
41 {
47 {
48 friend std::ostream & operator<<( std::ostream & str, const Impl & obj );
49 friend std::ostream & dumpOn( std::ostream & str, const Impl & obj );
50
52 using ScriptList = std::list< std::pair<std::string,std::string> >;
53
55 struct Dumpfile
56 {
57 Dumpfile( Pathname dumpfile_r )
58 : _dumpfile { std::move(dumpfile_r) }
59 {}
60
62 size_t _numscripts = 0;
63 bool _runposttrans = true;
64 };
65
66 public:
67 Impl( const Pathname & root_r )
68 : _root( root_r )
69 , _myJobReport { "cmdout", "%posttrans" }
70 {}
71
73 {}
74
75 bool hasPosttransScript( const Pathname & rpmPackage_r )
76 { return bool(getHeaderIfPosttrans( rpmPackage_r )); }
77
78 void collectPosttransInfo( const Pathname & rpmPackage_r, const std::vector<std::string> & runposttrans_r )
79 { if ( not collectDumpPosttransLines( runposttrans_r ) ) collectScriptForPackage( rpmPackage_r ); }
80
81 void collectPosttransInfo( const std::vector<std::string> & runposttrans_r )
82 { collectDumpPosttransLines( runposttrans_r ); }
83
85 {
86 if ( pkg ) {
87 if ( not _scripts ) {
89 }
90
91 filesystem::TmpFile script( tmpDir(), pkg->ident() );
92 filesystem::addmod( script.path(), 0500 ); // script must be executable
93 script.autoCleanup( false ); // no autodelete; within a tmpdir
94 {
95 std::ofstream out( script.path().c_str() );
96 out << "#! " << pkg->tag_posttransprog() << endl
97 << pkg->tag_posttrans() << endl;
98 }
99
100 _scripts->push_back( std::make_pair( script.path().basename(), pkg->tag_name() ) );
101 MIL << "COLLECT posttrans: '" << PathInfo( script.path() ) << "' for package: '" << pkg->tag_name() << "'" << endl;
102 }
103 }
104
105 void collectScriptForPackage( const Pathname & rpmPackage_r )
106 { collectScriptFromHeader( getHeaderIfPosttrans( rpmPackage_r ) ); }
107
113 bool collectDumpPosttransLines( const std::vector<std::string> & runposttrans_r )
114 {
115 if ( runposttrans_r.empty() ) {
116 if ( _dumpfile and _dumpfile->_runposttrans ) {
117 MIL << "LOST dump_posttrans support" << endl;
118 _dumpfile->_runposttrans = false; // rpm was downgraded to a version not supporing --runposttrans
119 }
120 return false;
121 }
122
123 if ( not _dumpfile ) {
124 filesystem::TmpFile dumpfile( tmpDir(), "dumpfile" );
125 filesystem::addmod( dumpfile.path(), 0400 ); // dumpfile must be readable
126 dumpfile.autoCleanup( false ); // no autodelete; within a tmpdir
127 _dumpfile = Dumpfile( dumpfile.path() );
128 MIL << "COLLECT dump_posttrans to '" << _dumpfile->_dumpfile << endl;
129 }
130
131 std::ofstream out( _dumpfile->_dumpfile.c_str(), std::ios_base::app );
132 for ( const auto & s : runposttrans_r ) {
133 out << s << endl;
134 }
135 _dumpfile->_numscripts += runposttrans_r.size();
136 MIL << "COLLECT " << runposttrans_r.size() << " dump_posttrans lines" << endl;
137 return true;
138 }
139
149 {
150 if ( _dumpfile && not _dumpfile->_runposttrans ) {
151 // Here a downgraded rpm lost the ability to --runposttrans. Extract at least any
152 // missing %posttrans scripts collected in _dumpfile and prepend them to the _scripts.
153 MIL << "Extract missing %posttrans scripts and prepend them to the scripts." << endl;
154
155 // collectScriptFromHeader appends to _scripts, so we save here and append again later
156 std::optional<ScriptList> savedscripts;
157 if ( _scripts ) {
158 savedscripts = std::move(*_scripts);
159 _scripts = std::nullopt;
160 }
161
163 recallFromDumpfile( _dumpfile->_dumpfile, [&]( std::string n_r, std::string v_r, std::string r_r, std::string a_r ) -> void {
164 if ( it.findPackage( n_r, Edition( v_r, r_r ) ) && headerHasPosttrans( *it ) )
165 collectScriptFromHeader( *it );
166 } );
167
168 // append any savedscripts
169 if ( savedscripts ) {
170 if ( _scripts ) {
171 _scripts->splice( _scripts->end(), *savedscripts );
172 } else {
173 _scripts = std::move(*savedscripts);
174 }
175 }
176 _dumpfile = std::nullopt;
177 }
178
179 if ( not ( _scripts || _dumpfile ) )
180 return; // Nothing todo
181
182 // ProgressReport counting the scripts ( 0:preparation, 1->n:for n scripts, n+1: indicate success)
184 ProgressData scriptProgress( [&]() -> ProgressData::value_type {
186 if ( _scripts )
187 ret += _scripts->size();
188 if ( _dumpfile )
189 ret += _dumpfile->_numscripts;
190 return ret;
191 }() );
192 scriptProgress.sendTo( ProgressReportAdaptor( ProgressData::ReceiverFnc(), report ) );
193 // Translator: progress bar label
194 std::string scriptProgressName { _("Running post-transaction scripts") };
195 // Translator: progress bar label; %1% is a script identifier like '%posttrans(mypackage-2-0.noarch)'
196 str::Format fmtScriptProgressRun { _("Running %1% script") };
197 // Translator: headline; %1% is a script identifier like '%posttrans(mypackage-2-0.noarch)'
198 str::Format fmtRipoff { _("%1% script output:") };
199 std::string sendRipoff;
200
201 HistoryLog historylog;
202
203 // lambda to prepare reports for a new script
204 auto startNewScript = [&] ( const std::string & scriptident_r ) -> void {
205 // scriptident_r : script identifier like "%transfiletriggerpostun(istrigger-2-0.noarch)"
206 sendRipoff = fmtRipoff % scriptident_r;
207 scriptProgress.name( fmtScriptProgressRun % scriptident_r );
208 scriptProgress.incr();
209 };
210
211 // lambda to send script output to reports
212 auto sendScriptOutput = [&] ( const std::string & line_r ) -> void {
213 OnScopeExit cleanup; // in case we need it
214 if ( not sendRipoff.empty() ) {
215 historylog.comment( sendRipoff, true /*timestamp*/);
216 _myJobReport.set( "ripoff", std::cref(sendRipoff) );
217 cleanup.setDispose( [&]() -> void {
218 _myJobReport.erase( "ripoff" );
219 sendRipoff.clear();
220 } );
221 }
222 historylog.comment( line_r );
223 _myJobReport.info( line_r );
224 };
225
226 // send the initial progress report
227 scriptProgress.name( scriptProgressName );
228 scriptProgress.toMin();
229
230 // Scripts first...
231 if ( _scripts ) {
232 Pathname noRootScriptDir( ZConfig::instance().update_scriptsPath() / tmpDir().basename() );
233 // like rpm would report it (intentionally not translated and NL-terminated):
234 str::Format fmtScriptFailedMsg { "warning: %%posttrans(%1%) scriptlet failed, exit status %2%\n" };
235 str::Format fmtPosttrans { "%%posttrans(%1%)" };
236
237 while ( ! _scripts->empty() )
238 {
239 const auto &scriptPair = _scripts->front();
240 const std::string & script = scriptPair.first;
241 const std::string & pkgident( script.substr( 0, script.size()-6 ) ); // strip tmp file suffix[6]
242 startNewScript( fmtPosttrans % pkgident );
243
244 int npkgs = 0;
246 for ( it.findByName( scriptPair.second ); *it; ++it )
247 npkgs++;
248
249 MIL << "EXECUTE posttrans: " << script << " with argument: " << npkgs << endl;
251 "/bin/sh",
252 (noRootScriptDir/script).asString(),
253 str::numstring( npkgs )
254 };
255 ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout, false, -1, true, _root );
256
257 for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() ) {
258 sendScriptOutput( line );
259 }
260 //script was executed, remove it from the list
261 _scripts->pop_front();
262
263 int ret = prog.close();
264 if ( ret != 0 )
265 {
266 std::string msg { fmtScriptFailedMsg % pkgident % ret };
267 WAR << msg;
268 sendScriptOutput( msg ); // info!, as rpm would have reported it.
269 }
270 }
271 _scripts = std::nullopt;
272 }
273
274 // ...then 'rpm --runposttrans'
275 int res = 0; // Indicate a failed call to rpm itself! (a failed script is just a warning)
276 if ( _dumpfile ) {
277 res = rpm_r.runposttrans( _dumpfile->_dumpfile, [&] ( const std::string & line_r ) ->void {
278 if ( str::startsWith( line_r, "RIPOFF:" ) )
279 startNewScript( line_r.substr( 7 ) ); // new scripts ident sent by rpm
280 else
281 sendScriptOutput( line_r );
282 } );
283 if ( res != 0 )
284 _myJobReport.error( str::Format("rpm --runposttrans returned %1%.") % res );
285
286 _dumpfile = std::nullopt;
287 }
288
289 // send a final progress report
290 scriptProgress.name( scriptProgressName );
291 if ( res == 0 )
292 scriptProgress.toMax(); // Indicate 100%, in case Dumpfile::_numscripts estimation was off
293 return;
294 }
295
301 {
302 if ( not ( _scripts || _dumpfile ) )
303 return; // Nothing todo
304
305 str::Str msg;
306
307 if ( _scripts ) {
308 // Legacy format logs all collected %posttrans
309 msg << "%posttrans scripts skipped while aborting:" << endl;
310 for ( const auto & script : *_scripts )
311 {
312 WAR << "UNEXECUTED posttrans: " << script.first << endl;
313 const std::string & pkgident( script.first.substr( 0, script.first.size()-6 ) ); // strip tmp file suffix[6]
314 msg << " " << pkgident << "\n";
315 }
316 _scripts = std::nullopt;
317 }
318
319 if ( _dumpfile ) {
320 msg << "%posttrans and %transfiletrigger scripts are not executed when aborting!" << endl;
321 _dumpfile = std::nullopt;
322 }
323
324 HistoryLog historylog;
325 historylog.comment( msg, true /*timestamp*/);
326 _myJobReport.warning( msg );
327 }
328
329 private:
332 {
333 if ( !_ptrTmpdir ) _ptrTmpdir.reset( new filesystem::TmpDir( _root / ZConfig::instance().update_scriptsPath(), "posttrans" ) );
334 DBG << _ptrTmpdir->path() << endl;
335 return _ptrTmpdir->path();
336 }
337
340 {
341 bool ret = false;
342 if ( pkg_r ) {
343 std::string prog( pkg_r->tag_posttransprog() );
344 if ( not prog.empty() && prog != "<lua>" ) // by now leave lua to rpm
345 ret = true;
346 }
347 return ret;
348 }
349
354 {
355 if ( _headercache.first == rpmPackage_r )
356 return _headercache.second;
357
359 if ( ret ) {
360 if ( not headerHasPosttrans( ret ) )
361 ret = nullptr;
362 } else {
363 WAR << "Unexpectedly this is no package: " << rpmPackage_r << endl;
364 }
365 _headercache = std::make_pair( rpmPackage_r, ret );
366 return ret;
367 }
368
370 void recallFromDumpfile( const Pathname & dumpfile_r, std::function<void(std::string,std::string,std::string,std::string)> consume_r )
371 {
372 // dump_posttrans: install 10 terminfo-base-6.4.20230819-19.1.x86_64
373 static const str::regex rxInstalled { "^dump_posttrans: +install +[0-9]+ +(.+)-([^-]+)-([^-]+)\\.([^.]+)" };
374 str::smatch what;
375 iostr::forEachLine( InputStream( dumpfile_r ), [&]( int num_r, std::string line_r ) -> bool {
376 if( str::regex_match( line_r, what, rxInstalled ) )
377 consume_r( what[1], what[2], what[3], what[4] );
378 return true; // continue iostr::forEachLine
379 } );
380 }
381
382 private:
384 std::optional<ScriptList> _scripts;
385 std::optional<Dumpfile> _dumpfile;
386 boost::scoped_ptr<filesystem::TmpDir> _ptrTmpdir;
387
389
390 std::pair<Pathname,rpm::RpmHeader::constPtr> _headercache;
391 };
392
394 inline std::ostream & operator<<( std::ostream & str, const RpmPostTransCollector::Impl & obj )
395 { return str << "RpmPostTransCollector::Impl"; }
396
398 inline std::ostream & dumpOn( std::ostream & str, const RpmPostTransCollector::Impl & obj )
399 { return str << obj; }
400
402 //
403 // CLASS NAME : RpmPostTransCollector
404 //
406
408 : _pimpl( new Impl( root_r ) )
409 {}
410
412 {}
413
415 { return _pimpl->hasPosttransScript( rpmPackage_r ); }
416
417 void RpmPostTransCollector::collectPosttransInfo( const Pathname & rpmPackage_r, const std::vector<std::string> & runposttrans_r )
418 { _pimpl->collectPosttransInfo( rpmPackage_r, runposttrans_r ); }
419
420 void RpmPostTransCollector::collectPosttransInfo( const std::vector<std::string> & runposttrans_r )
421 { _pimpl->collectPosttransInfo( runposttrans_r ); }
422
424 { _pimpl->executeScripts( rpm_r ); }
425
427 { return _pimpl->discardScripts(); }
428
429 std::ostream & operator<<( std::ostream & str, const RpmPostTransCollector & obj )
430 { return str << *obj._pimpl; }
431
432 std::ostream & dumpOn( std::ostream & str, const RpmPostTransCollector & obj )
433 { return dumpOn( str, *obj._pimpl ); }
434
435 } // namespace target
437} // namespace zypp
void setDispose(const Dispose &dispose_r)
Set a new dispose function.
Definition: AutoDispose.h:245
Execute a program and give access to its io An object of this class encapsulates the execution of an ...
std::vector< std::string > Arguments
int close()
Wait for the progamm to complete.
Writing the zypp history file.
Definition: HistoryLog.h:57
void comment(const std::string &comment, bool timestamp=false)
Log a comment (even multiline).
Definition: HistoryLog.cc:190
Helper to create and pass std::istream.
Definition: inputstream.h:57
Maintain [min,max] and counter (value) for progress counting.
Definition: progressdata.h:132
long long value_type
Definition: progressdata.h:134
void sendTo(const ReceiverFnc &fnc_r)
Set ReceiverFnc.
Definition: progressdata.h:229
bool toMax()
Set counter value to current max value (unless no range).
Definition: progressdata.h:276
void name(const std::string &name_r)
Set counter name.
Definition: progressdata.h:225
function< bool(const ProgressData &)> ReceiverFnc
Most simple version of progress reporting The percentage in most cases.
Definition: progressdata.h:140
bool incr(value_type val_r=1)
Increment counter value (default by 1).
Definition: progressdata.h:264
bool toMin()
Set counter value to current min value.
Definition: progressdata.h:272
static ZConfig & instance()
Singleton ctor.
Definition: ZConfig.cc:922
void erase(const std::string &key_r)
Remove key from data.
Definition: UserData.h:141
bool set(const std::string &key_r, AnyType val_r)
Set the value for key (nonconst version always returns true).
Definition: UserData.h:118
std::string receiveLine()
Read one line from the input stream.
Wrapper class for stat/lstat.
Definition: PathInfo.h:221
const char * c_str() const
String representation.
Definition: Pathname.h:110
std::string basename() const
Return the last component of this path.
Definition: Pathname.h:128
Provide a new empty temporary directory and recursively delete it when no longer needed.
Definition: TmpPath.h:178
Provide a new empty temporary file and delete it when no longer needed.
Definition: TmpPath.h:128
Pathname path() const
Definition: TmpPath.cc:146
bool autoCleanup() const
Whether path is valid and deleted when the last reference drops.
Definition: TmpPath.cc:163
Regular expression.
Definition: Regex.h:95
Regular expression match result.
Definition: Regex.h:168
RpmPostTransCollector implementation.
UserDataJobReport _myJobReport
JobReport with ContentType "cmdout/%posttrans".
bool headerHasPosttrans(rpm::RpmHeader::constPtr pkg_r) const
Return whether RpmHeader has a posttrans.
void discardScripts()
Discard all remembered scrips.
void collectPosttransInfo(const std::vector< std::string > &runposttrans_r)
void recallFromDumpfile(const Pathname &dumpfile_r, std::function< void(std::string, std::string, std::string, std::string)> consume_r)
Retrieve "dump_posttrans: install" lines from dumpfile_r and pass n,v,r,a to the consumer_r.
rpm::RpmHeader::constPtr getHeaderIfPosttrans(const Pathname &rpmPackage_r)
Cache RpmHeader for consecutive hasPosttransScript / collectScriptForPackage calls.
boost::scoped_ptr< filesystem::TmpDir > _ptrTmpdir
Pathname tmpDir()
Lazy create tmpdir on demand.
bool collectDumpPosttransLines(const std::vector< std::string > &runposttrans_r)
Return whether runposttrans lines were collected.
void collectPosttransInfo(const Pathname &rpmPackage_r, const std::vector< std::string > &runposttrans_r)
void collectScriptFromHeader(rpm::RpmHeader::constPtr pkg)
friend std::ostream & operator<<(std::ostream &str, const Impl &obj)
std::list< std::pair< std::string, std::string > > ScriptList
<posttrans script basename, pkgname> pairs.
bool hasPosttransScript(const Pathname &rpmPackage_r)
void executeScripts(rpm::RpmDb &rpm_r)
Execute the remembered scripts.
std::ostream & dumpOn(std::ostream &str, const RpmPostTransCollector::Impl &obj)
Verbose stream output.
std::pair< Pathname, rpm::RpmHeader::constPtr > _headercache
std::ostream & operator<<(std::ostream &str, const RpmPostTransCollector::Impl &obj)
Stream output.
friend std::ostream & dumpOn(std::ostream &str, const Impl &obj)
void collectScriptForPackage(const Pathname &rpmPackage_r)
Extract and remember posttrans scripts for later execution.
RpmPostTransCollector(const Pathname &root_r)
Default ctor.
void collectPosttransInfo(const Pathname &rpmPackage_r, const std::vector< std::string > &runposttrans_r)
Extract and remember a packages posttrans script or dump_posttrans lines for later execution.
void executeScripts(rpm::RpmDb &rpm_r)
Execute the remembered scripts and/or or dump_posttrans lines.
RW_pointer< Impl > _pimpl
Implementation class.
bool hasPosttransScript(const Pathname &rpmPackage_r)
Test whether a package defines a posttrans script.
void discardScripts()
Discard all remembered scripts and/or or dump_posttrans lines.
Interface to the rpm program.
Definition: RpmDb.h:50
int runposttrans(const Pathname &filename_r, std::function< void(const std::string &)> output_r)
Run collected posttrans and transfiletrigger(postun|in) if rpm --runposttrans is supported.
Definition: RpmDb.cc:2034
static RpmHeader::constPtr readPackage(const Pathname &path, VERIFICATION verification=VERIFY)
Get an accessible packages data from disk.
Definition: RpmHeader.cc:212
intrusive_ptr< const RpmHeader > constPtr
Definition: RpmHeader.h:65
Subclass to retrieve database content.
Definition: librpmDb.h:344
bool findByName(const std::string &name_r)
Reset to iterate all packages with a certain name.
Definition: librpmDb.cc:776
Definition: Arch.h:361
String related utilities and Regular expression matching.
boost::noncopyable NonCopyable
Ensure derived classes cannot be copied.
Definition: NonCopyable.h:26
int addmod(const Pathname &path, mode_t mode)
Add the mode bits to the file given by path.
Definition: PathInfo.cc:1101
int forEachLine(std::istream &str_r, function< bool(int, std::string)> consume_r)
Simple lineparser: Call functor consume_r for each line.
Definition: IOStream.cc:100
std::string numstring(char n, int w=0)
Definition: String.h:289
bool regex_match(const std::string &s, smatch &matches, const regex &regex)
\relates regex \ingroup ZYPP_STR_REGEX \relates regex \ingroup ZYPP_STR_REGEX
Definition: Regex.h:70
std::ostream & dumpOn(std::ostream &str, const RpmPostTransCollector &obj)
std::ostream & operator<<(std::ostream &str, const CommitPackageCache &obj)
Easy-to use interface to the ZYPP dependency resolver.
Definition: CodePitfalls.doc:2
JobReport convenience sending this instance of UserData with each message.
bool error(const std::string &msg_r)
bool info(const std::string &msg_r)
bool warning(const std::string &msg_r)
Convenient building of std::string with boost::format.
Definition: String.h:253
Convenient building of std::string via std::ostringstream Basically a std::ostringstream autoconverti...
Definition: String.h:212
Data regarding the dumpfile used if rpm --runposttrans is supported.
bool _runposttrans
Set to false if rpm lost –runposttrans support during transaction.
Pathname _dumpfile
The file holding the collected dump_posttrans: lines.
size_t _numscripts
Number of scripts we collected (roughly estimated)
#define _(MSG)
Definition: Gettext.h:37
#define DBG
Definition: Logger.h:95
#define MIL
Definition: Logger.h:96
#define WAR
Definition: Logger.h:97