libzypp  11.13.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/Exception.h"
17 
18 #include "zypp/PathInfo.h"
19 #include "zypp/ExternalProgram.h"
20 
22 
23 using std::endl;
24 
25 #undef ZYPP_BASE_LOGGER_LOGGROUP
26 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
27 
29 namespace zypp
30 {
31 
33  namespace
34  {
35  //
36  // lsof output lines are a sequence of NUL terminated fields,
37  // where the 1st char determines the fiels type.
38  //
39  // (pcuL) pid command userid loginname
40  // (ftkn).filedescriptor type linkcount filename
41  //
43 
45  typedef std::pair<std::string,std::unordered_set<std::string>> CacheEntry;
46 
51  inline void addDataIf( std::vector<CheckAccessDeleted::ProcInfo> & data_r, const CacheEntry & cache_r )
52  {
53  const auto & filelist( cache_r.second );
54 
55  if ( filelist.empty() )
56  return;
57 
58  // at least one file access so keep it:
59  data_r.push_back( CheckAccessDeleted::ProcInfo() );
60  CheckAccessDeleted::ProcInfo & pinfo( data_r.back() );
61  pinfo.files.insert( pinfo.files.begin(), filelist.begin(), filelist.end() );
62 
63  const std::string & pline( cache_r.first );
64  for_( ch, pline.begin(), pline.end() )
65  {
66  switch ( *ch )
67  {
68  case 'p':
69  pinfo.pid = &*(ch+1);
70  break;
71  case 'R':
72  pinfo.ppid = &*(ch+1);
73  break;
74  case 'u':
75  pinfo.puid = &*(ch+1);
76  break;
77  case 'L':
78  pinfo.login = &*(ch+1);
79  break;
80  case 'c':
81  pinfo.command = &*(ch+1);
82  break;
83  }
84  if ( *ch == '\n' ) break; // end of data
85  do { ++ch; } while ( *ch != '\0' ); // skip to next field
86  }
87 
88  if ( pinfo.command.size() == 15 )
89  {
90  // the command name might be truncated, so we check against /proc/<pid>/exe
91  Pathname command( filesystem::readlink( Pathname("/proc")/pinfo.pid/"exe" ) );
92  if ( ! command.empty() )
93  pinfo.command = command.basename();
94  }
95  //MIL << " Take " << pinfo << endl;
96  }
97 
98 
104  inline void addCacheIf( CacheEntry & cache_r, const std::string & line_r, bool verbose_r )
105  {
106  const char * f = 0;
107  const char * t = 0;
108  const char * n = 0;
109 
110  for_( ch, line_r.c_str(), ch+line_r.size() )
111  {
112  switch ( *ch )
113  {
114  case 'k':
115  if ( *(ch+1) != '0' ) // skip non-zero link counts
116  return;
117  break;
118  case 'f':
119  f = ch+1;
120  break;
121  case 't':
122  t = ch+1;
123  break;
124  case 'n':
125  n = ch+1;
126  break;
127  }
128  if ( *ch == '\n' ) break; // end of data
129  do { ++ch; } while ( *ch != '\0' ); // skip to next field
130  }
131 
132  if ( !t || !f || !n )
133  return; // wrong filedescriptor/type/name
134 
135  if ( !( ( *t == 'R' && *(t+1) == 'E' && *(t+2) == 'G' && *(t+3) == '\0' )
136  || ( *t == 'D' && *(t+1) == 'E' && *(t+2) == 'L' && *(t+3) == '\0' ) ) )
137  return; // wrong type
138 
139  if ( !( ( *f == 'm' && *(f+1) == 'e' && *(f+2) == 'm' && *(f+3) == '\0' )
140  || ( *f == 't' && *(f+1) == 'x' && *(f+2) == 't' && *(f+3) == '\0' )
141  || ( *f == 'D' && *(f+1) == 'E' && *(f+2) == 'L' && *(f+3) == '\0' )
142  || ( *f == 'l' && *(f+1) == 't' && *(f+2) == 'x' && *(f+3) == '\0' ) ) )
143  return; // wrong filedescriptor type
144 
145  if ( str::contains( n, "(stat: Permission denied)" ) )
146  return; // Avoid reporting false positive due to insufficient permission.
147 
148  if ( ! verbose_r )
149  {
150  if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
151  return; // Try to avoid reporting false positive unless verbose.
152  }
153 
154  if ( *f == 'm' || *f == 'D' ) // skip some wellknown nonlibrary memorymapped files
155  {
156  static const char * black[] = {
157  "/SYSV"
158  , "/var/run/"
159  , "/dev/"
160  };
161  for_( it, arrayBegin( black ), arrayEnd( black ) )
162  {
163  if ( str::hasPrefix( n, *it ) )
164  return;
165  }
166  }
167  // Add if no duplicate
168  cache_r.second.insert( n );
169  }
171  } // namespace
173 
175  {
176  _data.clear();
177 
178  static const char* argv[] =
179  {
180  "lsof", "-n", "-FpcuLRftkn0", NULL
181  };
183 
184  // cachemap: PID => (deleted files)
185  std::map<pid_t,CacheEntry> cachemap;
186  pid_t cachepid;
187  for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
188  {
189  // NOTE: line contains '\0' separeated fields!
190  if ( line[0] == 'p' )
191  {
192  str::strtonum( line.c_str()+1, cachepid ); // line is "p<PID>\0...."
193  cachemap[cachepid].first.swap( line );
194  }
195  else
196  {
197  addCacheIf( cachemap[cachepid], line, verbose_r );
198  }
199  }
200 
201  int ret = prog.close();
202  if ( ret != 0 )
203  {
204  Exception err( str::form("Executing 'lsof' failed (%d).", ret) );
205  err.remember( prog.execError() );
206  ZYPP_THROW( err );
207  }
208 
209  std::vector<ProcInfo> data;
210  for ( const auto & cached : cachemap )
211  {
212  addDataIf( data, cached.second );
213  }
214  _data.swap( data );
215  return _data.size();
216  }
217 
218  std::string CheckAccessDeleted::findService( const Pathname & command_r )
219  {
220  ProcInfo p;
221  p.command = command_r.basename();
222  return p.service();
223  }
224  std::string CheckAccessDeleted::findService( const char * command_r )
225  { return findService( Pathname( command_r ) ); }
226 
227  std::string CheckAccessDeleted::findService( const std::string & command_r )
228  { return findService( Pathname( command_r ) ); }
229 
230  std::string CheckAccessDeleted::findService( pid_t pid_r )
231  { return findService( filesystem::readlink( Pathname("/proc")/str::numstring(pid_r)/"exe" ) ); }
232 
234  namespace
235  {
236 
237  } // namespace
239 
241  {
242  if ( command.empty() )
243  return std::string();
244  // TODO: This needs to be implemented smarter... be carefull
245  // as we don't know whether the target is up.
246 
247  static const Pathname initD( "/etc/init.d" );
248  { // init.d script with same name
249  PathInfo pi( initD/command );
250  if ( pi.isFile() && pi.isX() )
251  return command;
252  }
253  { // init.d script with name + 'd'
254  std::string alt( command+"d" );
255  PathInfo pi( initD/alt );
256  if ( pi.isFile() && pi.isX() )
257  return alt;
258  }
259  if ( *command.rbegin() == 'd' )
260  { // init.d script with name - trailing'd'
261  std::string alt( command );
262  alt.erase( alt.size()-1 );
263  PathInfo pi( initD/alt );
264  WAR <<pi << endl;
265  if ( pi.isFile() && pi.isX() )
266  return alt;
267  }
268  return std::string();
269  }
270 
271  /******************************************************************
272  **
273  ** FUNCTION NAME : operator<<
274  ** FUNCTION TYPE : std::ostream &
275  */
276  std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
277  {
278  return dumpRange( str << "CheckAccessDeleted ",
279  obj.begin(),
280  obj.end() );
281  }
282 
283  /******************************************************************
284  **
285  ** FUNCTION NAME : operator<<
286  ** FUNCTION TYPE : std::ostream &
287  */
288  std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
289  {
290  if ( obj.pid.empty() )
291  return str << "<NoProc>";
292 
293  return dumpRangeLine( str << obj.command
294  << '<' << obj.pid
295  << '|' << obj.ppid
296  << '|' << obj.puid
297  << '|' << obj.login
298  << '>',
299  obj.files.begin(),
300  obj.files.end() );
301  }
302 
304 } // namespace zypp