libzypp  14.48.5
CheckAccessDeleted.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 \---------------------------------------------------------------------*/
12 #include <iostream>
13 #include <unordered_set>
14 #include "zypp/base/LogTools.h"
15 #include "zypp/base/String.h"
16 #include "zypp/base/Gettext.h"
17 #include "zypp/base/Exception.h"
18 
19 #include "zypp/PathInfo.h"
20 #include "zypp/ExternalProgram.h"
22 
24 
25 using std::endl;
26 
27 #undef ZYPP_BASE_LOGGER_LOGGROUP
28 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
29 
31 namespace zypp
32 {
33 
35  namespace
36  {
37  //
38  // lsof output lines are a sequence of NUL terminated fields,
39  // where the 1st char determines the fiels type.
40  //
41  // (pcuL) pid command userid loginname
42  // (ftkn).filedescriptor type linkcount filename
43  //
45 
47  typedef std::pair<std::string,std::unordered_set<std::string>> CacheEntry;
48 
53  inline void addDataIf( std::vector<CheckAccessDeleted::ProcInfo> & data_r, const CacheEntry & cache_r )
54  {
55  const auto & filelist( cache_r.second );
56 
57  if ( filelist.empty() )
58  return;
59 
60  // at least one file access so keep it:
61  data_r.push_back( CheckAccessDeleted::ProcInfo() );
62  CheckAccessDeleted::ProcInfo & pinfo( data_r.back() );
63  pinfo.files.insert( pinfo.files.begin(), filelist.begin(), filelist.end() );
64 
65  const std::string & pline( cache_r.first );
66  for_( ch, pline.begin(), pline.end() )
67  {
68  switch ( *ch )
69  {
70  case 'p':
71  pinfo.pid = &*(ch+1);
72  break;
73  case 'R':
74  pinfo.ppid = &*(ch+1);
75  break;
76  case 'u':
77  pinfo.puid = &*(ch+1);
78  break;
79  case 'L':
80  pinfo.login = &*(ch+1);
81  break;
82  case 'c':
83  pinfo.command = &*(ch+1);
84  break;
85  }
86  if ( *ch == '\n' ) break; // end of data
87  do { ++ch; } while ( *ch != '\0' ); // skip to next field
88  }
89 
90  if ( pinfo.command.size() == 15 )
91  {
92  // the command name might be truncated, so we check against /proc/<pid>/exe
93  Pathname command( filesystem::readlink( Pathname("/proc")/pinfo.pid/"exe" ) );
94  if ( ! command.empty() )
95  pinfo.command = command.basename();
96  }
97  //MIL << " Take " << pinfo << endl;
98  }
99 
100 
106  inline void addCacheIf( CacheEntry & cache_r, const std::string & line_r, bool verbose_r )
107  {
108  const char * f = 0;
109  const char * t = 0;
110  const char * n = 0;
111 
112  for_( ch, line_r.c_str(), ch+line_r.size() )
113  {
114  switch ( *ch )
115  {
116  case 'k':
117  if ( *(ch+1) != '0' ) // skip non-zero link counts
118  return;
119  break;
120  case 'f':
121  f = ch+1;
122  break;
123  case 't':
124  t = ch+1;
125  break;
126  case 'n':
127  n = ch+1;
128  break;
129  }
130  if ( *ch == '\n' ) break; // end of data
131  do { ++ch; } while ( *ch != '\0' ); // skip to next field
132  }
133 
134  if ( !t || !f || !n )
135  return; // wrong filedescriptor/type/name
136 
137  if ( !( ( *t == 'R' && *(t+1) == 'E' && *(t+2) == 'G' && *(t+3) == '\0' )
138  || ( *t == 'D' && *(t+1) == 'E' && *(t+2) == 'L' && *(t+3) == '\0' ) ) )
139  return; // wrong type
140 
141  if ( !( ( *f == 'm' && *(f+1) == 'e' && *(f+2) == 'm' && *(f+3) == '\0' )
142  || ( *f == 't' && *(f+1) == 'x' && *(f+2) == 't' && *(f+3) == '\0' )
143  || ( *f == 'D' && *(f+1) == 'E' && *(f+2) == 'L' && *(f+3) == '\0' )
144  || ( *f == 'l' && *(f+1) == 't' && *(f+2) == 'x' && *(f+3) == '\0' ) ) )
145  return; // wrong filedescriptor type
146 
147  if ( str::contains( n, "(stat: Permission denied)" ) )
148  return; // Avoid reporting false positive due to insufficient permission.
149 
150  if ( ! verbose_r )
151  {
152  if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
153  return; // Try to avoid reporting false positive unless verbose.
154  }
155 
156  if ( *f == 'm' || *f == 'D' ) // skip some wellknown nonlibrary memorymapped files
157  {
158  static const char * black[] = {
159  "/SYSV"
160  , "/var/run/"
161  , "/dev/"
162  };
163  for_( it, arrayBegin( black ), arrayEnd( black ) )
164  {
165  if ( str::hasPrefix( n, *it ) )
166  return;
167  }
168  }
169  // Add if no duplicate
170  cache_r.second.insert( n );
171  }
172 
179  struct FilterRunsInLXC
180  {
181  bool operator()( pid_t pid_r ) const
182  { return( nsIno( pid_r, "pid" ) != pidNS ); }
183 
184  FilterRunsInLXC()
185  : pidNS( nsIno( "self", "pid" ) )
186  {}
187 
188  static inline ino_t nsIno( const std::string & pid_r, const std::string & ns_r )
189  { return PathInfo("/proc/"+pid_r+"/ns/"+ns_r).ino(); }
190 
191  static inline ino_t nsIno( pid_t pid_r, const std::string & ns_r )
192  { return nsIno( asString(pid_r), ns_r ); }
193 
194  ino_t pidNS;
195  };
196 
202  bool lsofNoOptKi()
203  {
204  using target::rpm::librpmDb;
205  // RpmDb access is blocked while the Target is not initialized.
206  // Launching the Target just for this query would be an overkill.
207  struct TmpUnblock {
208  TmpUnblock()
209  : _wasBlocked( librpmDb::isBlocked() )
210  { if ( _wasBlocked ) librpmDb::unblockAccess(); }
211  ~TmpUnblock()
212  { if ( _wasBlocked ) librpmDb::blockAccess(); }
213  private:
214  bool _wasBlocked;
215  } tmpUnblock;
216 
217  librpmDb::db_const_iterator it;
218  return( it.findPackage( "lsof" ) && it->tag_edition() < Edition("4.90") && !it->tag_provides().count( Capability("backported-option-Ki") ) );
219  }
220 
222  } // namespace
224 
226  {
227  _data.clear();
228 
229  static const char* argv[] = { "lsof", "-n", "-FpcuLRftkn0", "-K", "i", NULL };
230  if ( lsofNoOptKi() )
231  argv[3] = NULL;
233 
234  // cachemap: PID => (deleted files)
235  // NOTE: omit PIDs running in a (lxc/docker) container
236  std::map<pid_t,CacheEntry> cachemap;
237  pid_t cachepid = 0;
238  FilterRunsInLXC runsInLXC;
239  for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
240  {
241  // NOTE: line contains '\0' separeated fields!
242  if ( line[0] == 'p' )
243  {
244  str::strtonum( line.c_str()+1, cachepid ); // line is "p<PID>\0...."
245  if ( !runsInLXC( cachepid ) )
246  cachemap[cachepid].first.swap( line );
247  else
248  cachepid = 0; // ignore this pid
249  }
250  else if ( cachepid )
251  {
252  addCacheIf( cachemap[cachepid], line, verbose_r );
253  }
254  }
255 
256  int ret = prog.close();
257  if ( ret != 0 )
258  {
259  if ( ret == 129 )
260  {
261  ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
262  }
263  Exception err( str::form("Executing 'lsof' failed (%d).", ret) );
264  err.remember( prog.execError() );
265  ZYPP_THROW( err );
266  }
267 
268  std::vector<ProcInfo> data;
269  for ( const auto & cached : cachemap )
270  {
271  addDataIf( data, cached.second );
272  }
273  _data.swap( data );
274  return _data.size();
275  }
276 
277  std::string CheckAccessDeleted::findService( const Pathname & command_r )
278  {
279  ProcInfo p;
280  p.command = command_r.basename();
281  return p.service();
282  }
283  std::string CheckAccessDeleted::findService( const char * command_r )
284  { return findService( Pathname( command_r ) ); }
285 
286  std::string CheckAccessDeleted::findService( const std::string & command_r )
287  { return findService( Pathname( command_r ) ); }
288 
289  std::string CheckAccessDeleted::findService( pid_t pid_r )
290  { return findService( filesystem::readlink( Pathname("/proc")/str::numstring(pid_r)/"exe" ) ); }
291 
293  namespace
294  {
295  } // namespace
298 
300  {
301  if ( command.empty() )
302  return std::string();
303  // TODO: This needs to be implemented smarter... be carefull
304  // as we don't know whether the target is up.
305 
306  static const Pathname initD( "/etc/init.d" );
307  { // init.d script with same name
308  PathInfo pi( initD/command );
309  if ( pi.isFile() && pi.isX() )
310  return command;
311  }
312  { // init.d script with name + 'd'
313  std::string alt( command+"d" );
314  PathInfo pi( initD/alt );
315  if ( pi.isFile() && pi.isX() )
316  return alt;
317  }
318  if ( *command.rbegin() == 'd' )
319  { // init.d script with name - trailing'd'
320  std::string alt( command );
321  alt.erase( alt.size()-1 );
322  PathInfo pi( initD/alt );
323  WAR <<pi << endl;
324  if ( pi.isFile() && pi.isX() )
325  return alt;
326  }
327  return std::string();
328  }
329 
330  /******************************************************************
331  **
332  ** FUNCTION NAME : operator<<
333  ** FUNCTION TYPE : std::ostream &
334  */
335  std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
336  {
337  return dumpRange( str << "CheckAccessDeleted ",
338  obj.begin(),
339  obj.end() );
340  }
341 
342  /******************************************************************
343  **
344  ** FUNCTION NAME : operator<<
345  ** FUNCTION TYPE : std::ostream &
346  */
347  std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
348  {
349  if ( obj.pid.empty() )
350  return str << "<NoProc>";
351 
352  return dumpRangeLine( str << obj.command
353  << '<' << obj.pid
354  << '|' << obj.ppid
355  << '|' << obj.puid
356  << '|' << obj.login
357  << '>',
358  obj.files.begin(),
359  obj.files.end() );
360  }
361 
363 } // namespace zypp
Interface to gettext.
Data about one running process accessing deleted files.
bool contains(const C_Str &str_r, const C_Str &val_r)
Locate substring case sensitive.
Definition: String.h:977
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition: Exception.h:320
int readlink(const Pathname &symlink_r, Pathname &target_r)
Like 'readlink'.
Definition: PathInfo.cc:862
std::vector< ProcInfo > _data
const std::string & execError() const
Some detail telling why the execution failed, if it failed.
std::string service() const
Guess if command was started by an /etc/init.d/ script.
std::string command
process command name
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition: Easy.h:27
std::ostream & operator<<(std::ostream &str, const CheckAccessDeleted &obj)
std::string form(const char *format,...) __attribute__((format(printf
Printf style construction of std::string.
Definition: String.cc:36
void remember(const Exception &old_r)
Store an other Exception as history.
Definition: Exception.cc:89
size_type check(bool verbose_r=false)
Check for running processes which access deleted executables or libraries.
std::ostream & dumpRange(std::ostream &str, _Iterator begin, _Iterator 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 ...
#define WAR
Definition: Logger.h:48
std::string puid
process user ID
#define _(MSG)
Definition: Gettext.h:29
std::string receiveLine()
Read one line from the input stream.
_It strtonum(const C_Str &str)
Parsing numbers from string.
Definition: String.h:396
std::ostream & dumpRangeLine(std::ostream &str, _Iterator begin, _Iterator end)
Print range defined by iterators (single line style).
Definition: LogTools.h:114
std::string numstring(char n, int w=0)
Definition: String.h:311
std::string asString(const Patch::SeverityFlag &obj)
Definition: Patch.cc:166
int close()
Wait for the progamm to complete.
#define arrayEnd(A)
Definition: Easy.h:42
#define arrayBegin(A)
Simple C-array iterator.
Definition: Easy.h:40
Base class for Exception.
Definition: Exception.h:143
Check for running processes which access deleted executables or libraries.
const_iterator end() const
ino_t pidNS
bool hasPrefix(const C_Str &str_r, const C_Str &prefix_r)
Return whether str_r has prefix prefix_r.
Definition: String.h:1035
const_iterator begin() const
std::string login
process login name
static std::string findService(const char *command_r)
Guess if command was started by an /etc/init.d/ script.
std::vector< std::string > files
list of deleted executables or libraries accessed
std::string ppid
parent process ID