1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/misc/CheckAccessDeleted.cc
14 #include <unordered_set>
15 #include "zypp/base/LogTools.h"
16 #include "zypp/base/String.h"
17 #include "zypp/base/Gettext.h"
18 #include "zypp/base/Exception.h"
20 #include "zypp/PathInfo.h"
21 #include "zypp/ExternalProgram.h"
22 #include "zypp/base/Regex.h"
23 #include "zypp/base/IOStream.h"
24 #include "zypp/base/InputStream.h"
26 #include "zypp/misc/CheckAccessDeleted.h"
30 #undef ZYPP_BASE_LOGGER_LOGGROUP
31 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
33 ///////////////////////////////////////////////////////////////////
35 { /////////////////////////////////////////////////////////////////
37 ///////////////////////////////////////////////////////////////////
39 { /////////////////////////////////////////////////////////////////
41 // lsof output lines are a sequence of NUL terminated fields,
42 // where the 1st char determines the fields type.
44 // (pcuL) pid command userid loginname
45 // (ftkn).filedescriptor type linkcount filename
47 /////////////////////////////////////////////////////////////////
49 /** lsof output line + files extracted so far for this PID */
50 typedef std::pair<std::string,std::unordered_set<std::string>> CacheEntry;
52 /** Add \c cache to \c data if the process is accessing deleted files.
53 * \c pid string in \c cache is the proc line \c (pcuLR), \c files
54 * are already in place. Always clear the \c cache.files!
56 inline void addDataIf( std::vector<CheckAccessDeleted::ProcInfo> & data_r, const CacheEntry & cache_r )
58 const auto & filelist( cache_r.second );
60 if ( filelist.empty() )
63 // at least one file access so keep it:
64 data_r.push_back( CheckAccessDeleted::ProcInfo() );
65 CheckAccessDeleted::ProcInfo & pinfo( data_r.back() );
66 pinfo.files.insert( pinfo.files.begin(), filelist.begin(), filelist.end() );
68 const std::string & pline( cache_r.first );
69 std::string commandname; // pinfo.command if still needed...
70 for_( ch, pline.begin(), pline.end() )
78 pinfo.ppid = &*(ch+1);
81 pinfo.puid = &*(ch+1);
84 pinfo.login = &*(ch+1);
87 if ( pinfo.command.empty() )
88 commandname = &*(ch+1);
91 if ( *ch == '\n' ) break; // end of data
92 do { ++ch; } while ( *ch != '\0' ); // skip to next field
95 if ( pinfo.command.empty() )
97 // the lsof command name might be truncated, so we prefer /proc/<pid>/exe
98 pinfo.command = filesystem::readlink( Pathname("/proc")/pinfo.pid/"exe" ).basename();
99 if ( pinfo.command.empty() )
100 pinfo.command = std::move(commandname);
105 /** Add file to cache if it refers to a deleted executable or library file:
106 * - Either the link count \c(k) is \c 0, or no link cout is present.
107 * - The type \c (t) is set to \c REG or \c DEL
108 * - The filedescriptor \c (f) is set to \c txt, \c mem or \c DEL
110 inline void addCacheIf( CacheEntry & cache_r, const std::string & line_r, bool verbose_r )
116 for_( ch, line_r.c_str(), ch+line_r.size() )
121 if ( *(ch+1) != '0' ) // skip non-zero link counts
134 if ( *ch == '\n' ) break; // end of data
135 do { ++ch; } while ( *ch != '\0' ); // skip to next field
138 if ( !t || !f || !n )
139 return; // wrong filedescriptor/type/name
141 if ( !( ( *t == 'R' && *(t+1) == 'E' && *(t+2) == 'G' && *(t+3) == '\0' )
142 || ( *t == 'D' && *(t+1) == 'E' && *(t+2) == 'L' && *(t+3) == '\0' ) ) )
143 return; // wrong type
145 if ( !( ( *f == 'm' && *(f+1) == 'e' && *(f+2) == 'm' && *(f+3) == '\0' )
146 || ( *f == 't' && *(f+1) == 'x' && *(f+2) == 't' && *(f+3) == '\0' )
147 || ( *f == 'D' && *(f+1) == 'E' && *(f+2) == 'L' && *(f+3) == '\0' )
148 || ( *f == 'l' && *(f+1) == 't' && *(f+2) == 'x' && *(f+3) == '\0' ) ) )
149 return; // wrong filedescriptor type
151 if ( str::contains( n, "(stat: Permission denied)" ) )
152 return; // Avoid reporting false positive due to insufficient permission.
156 if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
157 return; // Try to avoid reporting false positive unless verbose.
160 if ( *f == 'm' || *f == 'D' ) // skip some wellknown nonlibrary memorymapped files
162 static const char * black[] = {
168 for_( it, arrayBegin( black ), arrayEnd( black ) )
170 if ( str::hasPrefix( n, *it ) )
174 // Add if no duplicate
175 cache_r.second.insert( n );
178 /////////////////////////////////////////////////////////////////
179 /// \class FilterRunsInLXC
180 /// \brief Functor guessing whether \a PID is running in a container.
182 /// Assumme using different \c pid namespace than \c self.
183 /////////////////////////////////////////////////////////////////
184 struct FilterRunsInLXC
186 bool operator()( pid_t pid_r ) const
187 { return( nsIno( pid_r, "pid" ) != pidNS ); }
190 : pidNS( nsIno( "self", "pid" ) )
193 static inline ino_t nsIno( const std::string & pid_r, const std::string & ns_r )
194 { return PathInfo("/proc/"+pid_r+"/ns/"+ns_r).ino(); }
196 static inline ino_t nsIno( pid_t pid_r, const std::string & ns_r )
197 { return nsIno( asString(pid_r), ns_r ); }
203 void lsofdebug( const Pathname & file_r )
205 std::ifstream infile( file_r.c_str() );
206 USR << infile << endl;
207 std::vector<std::string> fields;
209 for( iostr::EachLine in( infile ); in; in.next() )
211 std::string field( *in );
212 if ( field[0] == 'f' || field[0] == 'p' )
214 if ( !fields.empty() )
217 std::string line( str::join( fields, "\n" ) );
218 for ( char & c : line )
219 { if ( c == '\n' ) c = '\0'; }
220 line.push_back( '\n' );
222 size_t sze = cache.second.size();
223 addCacheIf( cache, line, false );
224 if ( sze != cache.second.size() )
225 USR << fields << endl;
229 if ( field[0] == 'p' )
231 fields.push_back( field );
233 else if ( !fields.empty() )
235 fields.push_back( field );
240 /////////////////////////////////////////////////////////////////
242 ///////////////////////////////////////////////////////////////////
244 CheckAccessDeleted::size_type CheckAccessDeleted::check( bool verbose_r )
248 static const char* argv[] =
250 "lsof", "-n", "-FpcuLRftkn0", NULL
252 ExternalProgram prog( argv, ExternalProgram::Discard_Stderr );
254 // cachemap: PID => (deleted files)
255 // NOTE: omit PIDs running in a (lxc/docker) container
256 std::map<pid_t,CacheEntry> cachemap;
258 FilterRunsInLXC runsInLXC;
259 for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
261 // NOTE: line contains '\0' separeated fields!
262 if ( line[0] == 'p' )
264 str::strtonum( line.c_str()+1, cachepid ); // line is "p<PID>\0...."
265 if ( !runsInLXC( cachepid ) )
266 cachemap[cachepid].first.swap( line );
268 cachepid = 0; // ignore this pid
272 addCacheIf( cachemap[cachepid], line, verbose_r );
276 int ret = prog.close();
281 ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
283 Exception err( str::form("Executing 'lsof' failed (%d).", ret) );
284 err.remember( prog.execError() );
288 std::vector<ProcInfo> data;
289 for ( const auto & cached : cachemap )
291 addDataIf( data, cached.second );
297 std::string CheckAccessDeleted::findService( pid_t pid_r )
300 p.pid = str::numstring( pid_r );
304 ///////////////////////////////////////////////////////////////////
306 { /////////////////////////////////////////////////////////////////
307 /////////////////////////////////////////////////////////////////
309 ///////////////////////////////////////////////////////////////////
311 std::string CheckAccessDeleted::ProcInfo::service() const
313 static const str::regex rx( "[0-9]+:name=systemd:/system.slice/(.*/)?(.*).service$" );
316 iostr::simpleParseFile( InputStream( Pathname("/proc")/pid/"cgroup" ),
317 [&]( int num_r, std::string line_r )->bool
319 if ( str::regex_match( line_r, what, rx ) )
322 return false; // stop after match
329 /******************************************************************
331 ** FUNCTION NAME : operator<<
332 ** FUNCTION TYPE : std::ostream &
334 std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
336 return dumpRange( str << "CheckAccessDeleted ",
341 /******************************************************************
343 ** FUNCTION NAME : operator<<
344 ** FUNCTION TYPE : std::ostream &
346 std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
348 if ( obj.pid.empty() )
349 return str << "<NoProc>";
351 return dumpRangeLine( str << obj.command
361 /////////////////////////////////////////////////////////////////
363 ///////////////////////////////////////////////////////////////////