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