libzypp  17.14.0
CheckAccessDeleted.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 <unordered_set>
15 #include <iterator>
16 #include <stdio.h>
17 #include "zypp/base/LogTools.h"
18 #include "zypp/base/String.h"
19 #include "zypp/base/Gettext.h"
20 #include "zypp/base/Exception.h"
21 
22 #include "zypp/PathInfo.h"
23 #include "zypp/ExternalProgram.h"
24 #include "zypp/base/Regex.h"
25 #include "zypp/base/IOStream.h"
26 #include "zypp/base/InputStream.h"
28 
30 
31 using std::endl;
32 
33 #undef ZYPP_BASE_LOGGER_LOGGROUP
34 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
35 
37 namespace zypp
38 {
39 
41  namespace
42  {
43  //
44  // lsof output lines are a sequence of NUL terminated fields,
45  // where the 1st char determines the fields type.
46  //
47  // (pcuL) pid command userid loginname
48  // (ftkn).filedescriptor type linkcount filename
49  //
51 
53  typedef std::pair<std::string,std::unordered_set<std::string>> CacheEntry;
54 
61  struct FilterRunsInLXC
62  {
63  bool operator()( pid_t pid_r ) const
64  { return( nsIno( pid_r, "pid" ) != pidNS ); }
65 
66  FilterRunsInLXC()
67  : pidNS( nsIno( "self", "pid" ) )
68  {}
69 
70  static inline ino_t nsIno( const std::string & pid_r, const std::string & ns_r )
71  { return PathInfo("/proc/"+pid_r+"/ns/"+ns_r).ino(); }
72 
73  static inline ino_t nsIno( pid_t pid_r, const std::string & ns_r )
74  { return nsIno( asString(pid_r), ns_r ); }
75 
76  ino_t pidNS;
77  };
78 
84  bool lsofNoOptKi()
85  {
86  using target::rpm::librpmDb;
87  // RpmDb access is blocked while the Target is not initialized.
88  // Launching the Target just for this query would be an overkill.
89  struct TmpUnblock {
90  TmpUnblock()
91  : _wasBlocked( librpmDb::isBlocked() )
92  { if ( _wasBlocked ) librpmDb::unblockAccess(); }
93  ~TmpUnblock()
94  { if ( _wasBlocked ) librpmDb::blockAccess(); }
95  private:
96  bool _wasBlocked;
97  } tmpUnblock;
98 
99  librpmDb::db_const_iterator it;
100  return( it.findPackage( "lsof" ) && it->tag_edition() < Edition("4.90") && !it->tag_provides().count( Capability("backported-option-Ki") ) );
101  }
102 
103  } //namespace
105 
107  {
108  public:
110 
111  bool addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap = nullptr );
112  void addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap = nullptr );
113 
114  std::map<pid_t,CacheEntry> filterInput( externalprogram::ExternalDataSource &source );
115  CheckAccessDeleted::size_type createProcInfo( const std::map<pid_t,CacheEntry> &in );
116 
117  std::vector<CheckAccessDeleted::ProcInfo> _data;
118  bool _fromLsofFileMode = false; // Set if we currently process data from a debug file
119  bool _verbose = false;
120 
121  std::map<pid_t,std::vector<std::string>> debugMap; //will contain all used lsof files after filtering
123  };
124 
126  {
127  Impl *myClone = new Impl( *this );
128  return myClone;
129  }
130 
135  inline bool CheckAccessDeleted::Impl::addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap )
136  {
137  const auto & filelist( cache_r.second );
138 
139  if ( filelist.empty() )
140  return false;
141 
142  // at least one file access so keep it:
143  _data.push_back( CheckAccessDeleted::ProcInfo() );
144  CheckAccessDeleted::ProcInfo & pinfo( _data.back() );
145  pinfo.files.insert( pinfo.files.begin(), filelist.begin(), filelist.end() );
146 
147  const std::string & pline( cache_r.first );
148  std::string commandname; // pinfo.command if still needed...
149  std::ostringstream pLineStr; //rewrite the first line in debug cache
150  for_( ch, pline.begin(), pline.end() )
151  {
152  switch ( *ch )
153  {
154  case 'p':
155  pinfo.pid = &*(ch+1);
156  if ( debMap )
157  pLineStr <<&*(ch)<<'\0';
158  break;
159  case 'R':
160  pinfo.ppid = &*(ch+1);
161  if ( debMap )
162  pLineStr <<&*(ch)<<'\0';
163  break;
164  case 'u':
165  pinfo.puid = &*(ch+1);
166  if ( debMap )
167  pLineStr <<&*(ch)<<'\0';
168  break;
169  case 'L':
170  pinfo.login = &*(ch+1);
171  if ( debMap )
172  pLineStr <<&*(ch)<<'\0';
173  break;
174  case 'c':
175  if ( pinfo.command.empty() ) {
176  commandname = &*(ch+1);
177  // the lsof command name might be truncated, so we prefer /proc/<pid>/exe
178  if (!_fromLsofFileMode)
179  pinfo.command = filesystem::readlink( Pathname("/proc")/pinfo.pid/"exe" ).basename();
180  if ( pinfo.command.empty() )
181  pinfo.command = std::move(commandname);
182  if ( debMap )
183  pLineStr <<'c'<<pinfo.command<<'\0';
184  }
185  break;
186  }
187  if ( *ch == '\n' ) break; // end of data
188  do { ++ch; } while ( *ch != '\0' ); // skip to next field
189  }
190 
191  //replace the data in the debug cache as well
192  if ( debMap ) {
193  pLineStr<<endl;
194  debMap->front() = pLineStr.str();
195  }
196 
197  //entry was added
198  return true;
199  }
200 
201 
207  inline void CheckAccessDeleted::Impl::addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap )
208  {
209  const char * f = 0;
210  const char * t = 0;
211  const char * n = 0;
212 
213  for_( ch, line_r.c_str(), ch+line_r.size() )
214  {
215  switch ( *ch )
216  {
217  case 'k':
218  if ( *(ch+1) != '0' ) // skip non-zero link counts
219  return;
220  break;
221  case 'f':
222  f = ch+1;
223  break;
224  case 't':
225  t = ch+1;
226  break;
227  case 'n':
228  n = ch+1;
229  break;
230  }
231  if ( *ch == '\n' ) break; // end of data
232  do { ++ch; } while ( *ch != '\0' ); // skip to next field
233  }
234 
235  if ( !t || !f || !n )
236  return; // wrong filedescriptor/type/name
237 
238  if ( !( ( *t == 'R' && *(t+1) == 'E' && *(t+2) == 'G' && *(t+3) == '\0' )
239  || ( *t == 'D' && *(t+1) == 'E' && *(t+2) == 'L' && *(t+3) == '\0' ) ) )
240  return; // wrong type
241 
242  if ( !( ( *f == 'm' && *(f+1) == 'e' && *(f+2) == 'm' && *(f+3) == '\0' )
243  || ( *f == 't' && *(f+1) == 'x' && *(f+2) == 't' && *(f+3) == '\0' )
244  || ( *f == 'D' && *(f+1) == 'E' && *(f+2) == 'L' && *(f+3) == '\0' )
245  || ( *f == 'l' && *(f+1) == 't' && *(f+2) == 'x' && *(f+3) == '\0' ) ) )
246  return; // wrong filedescriptor type
247 
248  if ( str::contains( n, "(stat: Permission denied)" ) )
249  return; // Avoid reporting false positive due to insufficient permission.
250 
251  if ( ! _verbose )
252  {
253  if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
254  return; // Try to avoid reporting false positive unless verbose.
255  }
256 
257  if ( *f == 'm' || *f == 'D' ) // skip some wellknown nonlibrary memorymapped files
258  {
259  static const char * black[] = {
260  "/SYSV"
261  , "/var/"
262  , "/dev/"
263  , "/tmp/"
264  , "/proc/"
265  , "/memfd:"
266  };
267  for_( it, arrayBegin( black ), arrayEnd( black ) )
268  {
269  if ( str::hasPrefix( n, *it ) )
270  return;
271  }
272  }
273  // Add if no duplicate
274  if ( debMap && cache_r.second.find(n) == cache_r.second.end() ) {
275  debMap->push_back(line_r);
276  }
277  cache_r.second.insert( n );
278  }
279 
281  : _pimpl(new Impl)
282  {
283  if ( doCheck_r ) check();
284  }
285 
286  CheckAccessDeleted::size_type CheckAccessDeleted::check( const Pathname &lsofOutput_r, bool verbose_r )
287  {
288  _pimpl->_verbose = verbose_r;
289  _pimpl->_fromLsofFileMode = true;
290 
291  FILE *inFile = fopen( lsofOutput_r.c_str(), "r" );
292  if ( !inFile ) {
293  ZYPP_THROW( Exception( str::Format("Opening input file %1% failed.") % lsofOutput_r.c_str() ) );
294  }
295 
296  //inFile is closed by ExternalDataSource
297  externalprogram::ExternalDataSource inSource( inFile, nullptr );
298  auto cache = _pimpl->filterInput( inSource );
299  return _pimpl->createProcInfo( cache );
300  }
301 
303  {
304  // cachemap: PID => (deleted files)
305  // NOTE: omit PIDs running in a (lxc/docker) container
306  std::map<pid_t,CacheEntry> cachemap;
307 
308  bool debugEnabled = !_debugFile.empty();
309 
310  pid_t cachepid = 0;
311  FilterRunsInLXC runsInLXC;
312  for( std::string line = source.receiveLine(); ! line.empty(); line = source.receiveLine() )
313  {
314  // NOTE: line contains '\0' separeated fields!
315  if ( line[0] == 'p' )
316  {
317  str::strtonum( line.c_str()+1, cachepid ); // line is "p<PID>\0...."
318  if ( _fromLsofFileMode || !runsInLXC( cachepid ) ) {
319  if ( debugEnabled ) {
320  auto &pidMad = debugMap[cachepid];
321  if ( pidMad.empty() )
322  debugMap[cachepid].push_back( line );
323  else
324  debugMap[cachepid].front() = line;
325  }
326  cachemap[cachepid].first.swap( line );
327  } else {
328  cachepid = 0; // ignore this pid
329  }
330  }
331  else if ( cachepid )
332  {
333  auto &dbgMap = debugMap[cachepid];
334  addCacheIf( cachemap[cachepid], line, debugEnabled ? &dbgMap : nullptr);
335  }
336  }
337  return cachemap;
338  }
339 
341  {
342  static const char* argv[] = { "lsof", "-n", "-FpcuLRftkn0", "-K", "i", NULL };
343  if ( lsofNoOptKi() )
344  argv[3] = NULL;
345 
346  _pimpl->_verbose = verbose_r;
347  _pimpl->_fromLsofFileMode = false;
348 
350  std::map<pid_t,CacheEntry> cachemap = _pimpl->filterInput( prog );
351 
352  int ret = prog.close();
353  if ( ret != 0 )
354  {
355  if ( ret == 129 )
356  {
357  ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
358  }
359  Exception err( str::Format("Executing 'lsof' failed (%1%).") % ret );
360  err.remember( prog.execError() );
361  ZYPP_THROW( err );
362  }
363 
364  return _pimpl->createProcInfo( cachemap );
365  }
366 
368  {
369  std::ofstream debugFileOut;
370  bool debugEnabled = false;
371  if ( !_debugFile.empty() ) {
372  debugFileOut.open( _debugFile.c_str() );
373  debugEnabled = debugFileOut.is_open();
374 
375  if ( !debugEnabled ) {
376  ERR<<"Unable to open debug file: "<<_debugFile<<endl;
377  }
378  }
379 
380  _data.clear();
381  for ( const auto &cached : in )
382  {
383  if (!debugEnabled)
384  addDataIf( cached.second);
385  else {
386  std::vector<std::string> *mapPtr = nullptr;
387 
388  auto dbgInfo = debugMap.find(cached.first);
389  if ( dbgInfo != debugMap.end() )
390  mapPtr = &(dbgInfo->second);
391 
392  if( !addDataIf( cached.second, mapPtr ) )
393  continue;
394 
395  for ( const std::string &dbgLine: dbgInfo->second ) {
396  debugFileOut.write( dbgLine.c_str(), dbgLine.length() );
397  }
398  }
399  }
400  return _data.size();
401  }
402 
404  {
405  return _pimpl->_data.empty();
406  }
407 
409  {
410  return _pimpl->_data.size();
411  }
412 
414  {
415  return _pimpl->_data.begin();
416  }
417 
419  {
420  return _pimpl->_data.end();
421  }
422 
424  {
425  _pimpl->_debugFile = filename_r;
426  }
427 
428  std::string CheckAccessDeleted::findService( pid_t pid_r )
429  {
430  ProcInfo p;
431  p.pid = str::numstring( pid_r );
432  return p.service();
433  }
434 
436  {
437  static const str::regex rx( "[0-9]+:name=systemd:/system.slice/(.*/)?(.*).service$" );
438  str::smatch what;
439  std::string ret;
440  iostr::simpleParseFile( InputStream( Pathname("/proc")/pid/"cgroup" ),
441  [&]( int num_r, std::string line_r )->bool
442  {
443  if ( str::regex_match( line_r, what, rx ) )
444  {
445  ret = what[2];
446  return false; // stop after match
447  }
448  return true;
449  } );
450  return ret;
451  }
452 
453  /******************************************************************
454  **
455  ** FUNCTION NAME : operator<<
456  ** FUNCTION TYPE : std::ostream &
457  */
458  std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
459  {
460  return dumpRange( str << "CheckAccessDeleted ",
461  obj.begin(),
462  obj.end() );
463  }
464 
465  /******************************************************************
466  **
467  ** FUNCTION NAME : operator<<
468  ** FUNCTION TYPE : std::ostream &
469  */
470  std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
471  {
472  if ( obj.pid.empty() )
473  return str << "<NoProc>";
474 
475  return dumpRangeLine( str << obj.command
476  << '<' << obj.pid
477  << '|' << obj.ppid
478  << '|' << obj.puid
479  << '|' << obj.login
480  << '>',
481  obj.files.begin(),
482  obj.files.end() );
483  }
484 
486 } // namespace zypp
bool addDataIf(const CacheEntry &cache_r, std::vector< std::string > *debMap=nullptr)
Add cache to data if the process is accessing deleted files.
Interface to gettext.
Data about one running process accessing deleted files.
Bidirectional stream to external data.
bool contains(const C_Str &str_r, const C_Str &val_r)
Locate substring case sensitive.
Definition: String.h:957
std::map< pid_t, CacheEntry > filterInput(externalprogram::ExternalDataSource &source)
std::string asString(const DefaultIntegral< Tp, TInitial > &obj)
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition: Exception.h:392
Regular expression.
Definition: Regex.h:86
std::map< pid_t, std::vector< std::string > > debugMap
int readlink(const Pathname &symlink_r, Pathname &target_r)
Like 'readlink'.
Definition: PathInfo.cc:877
void setDebugOutputFile(const Pathname &filename_r)
Writes all filtered process entries that make it into the final set into a file specified by filename...
const char * c_str() const
String representation.
Definition: Pathname.h:109
std::string command
process command name
String related utilities and Regular expression matching.
Helper to create and pass std::istream.
Definition: InputStream.h:56
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition: Easy.h:28
Convenient building of std::string with boost::format.
Definition: String.h:251
std::ostream & operator<<(std::ostream &str, const CheckAccessDeleted &obj)
#define ERR
Definition: Logger.h:81
void remember(const Exception &old_r)
Store an other Exception as history.
Definition: Exception.cc:105
CheckAccessDeleted::Impl * clone() const
bool empty() const
Test for an empty path.
Definition: Pathname.h:113
size_type check(bool verbose_r=false)
Check for running processes which access deleted executables or libraries.
RWCOW_pointer< Impl > _pimpl
const_iterator begin() const
int simpleParseFile(std::istream &str_r, ParseFlags flags_r, function< bool(int, std::string)> consume_r)
Simple lineparser optionally trimming and skipping comments.
Definition: IOStream.cc:124
const std::string & execError() const
Some detail telling why the execution failed, if it failed.
std::ostream & dumpRange(std::ostream &str, TIterator begin, TIterator end, const std::string &intro="{", const std::string &pfx="\n ", const std::string &sep="\n ", const std::string &sfx="\n", const std::string &extro="}")
Print range defined by iterators (multiline style).
Definition: LogTools.h:91
Execute a program and give access to its io An object of this class encapsulates the execution of an ...
std::list< PublicKeyData > _data
Definition: KeyRing.cc:145
std::vector< CheckAccessDeleted::ProcInfo > _data
TInt strtonum(const C_Str &str)
Parsing numbers from string.
Definition: String.h:387
std::string puid
process user ID
const_iterator end() const
#define _(MSG)
Definition: Gettext.h:37
std::string receiveLine()
Read one line from the input stream.
std::string numstring(char n, int w=0)
Definition: String.h:288
CheckAccessDeleted::size_type createProcInfo(const std::map< pid_t, CacheEntry > &in)
int close()
Wait for the progamm to complete.
#define arrayEnd(A)
Definition: Easy.h:43
#define arrayBegin(A)
Simple C-array iterator.
Definition: Easy.h:41
Regular expression match result.
Definition: Regex.h:145
Base class for Exception.
Definition: Exception.h:145
Check for running processes which access deleted executables or libraries.
CheckAccessDeleted(bool doCheck_r=true)
Default ctor performs check immediately.
std::ostream & dumpRangeLine(std::ostream &str, TIterator begin, TIterator end)
Print range defined by iterators (single line style).
Definition: LogTools.h:114
ino_t pidNS
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
Easy-to use interface to the ZYPP dependency resolver.
Definition: CodePitfalls.doc:1
bool hasPrefix(const C_Str &str_r, const C_Str &prefix_r)
Return whether str_r has prefix prefix_r.
Definition: String.h:1020
std::vector< ProcInfo >::const_iterator const_iterator
std::string login
process login name
void addCacheIf(CacheEntry &cache_r, const std::string &line_r, std::vector< std::string > *debMap=nullptr)
Add file to cache if it refers to a deleted executable or library file:
std::vector< std::string > files
list of deleted executables or libraries accessed
std::string ppid
parent process ID
std::string service() const
Guess if command was started by a systemd service script.
static std::string findService(pid_t pid_r)
Guess if pid was started by a systemd service script.