1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/misc/CheckAccessDeleted.cc
14 #include <unordered_set>
17 #include "zypp/base/LogTools.h"
18 #include "zypp/base/String.h"
19 #include "zypp/base/Gettext.h"
20 #include "zypp/base/Exception.h"
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 #include "zypp/misc/CheckAccessDeleted.h"
32 #undef ZYPP_BASE_LOGGER_LOGGROUP
33 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
35 ///////////////////////////////////////////////////////////////////
37 { /////////////////////////////////////////////////////////////////
39 ///////////////////////////////////////////////////////////////////
41 { /////////////////////////////////////////////////////////////////
43 // lsof output lines are a sequence of NUL terminated fields,
44 // where the 1st char determines the fields type.
46 // (pcuL) pid command userid loginname
47 // (ftkn).filedescriptor type linkcount filename
49 /////////////////////////////////////////////////////////////////
51 /** lsof output line + files extracted so far for this PID */
52 typedef std::pair<std::string,std::unordered_set<std::string>> CacheEntry;
54 /////////////////////////////////////////////////////////////////
55 /// \class FilterRunsInLXC
56 /// \brief Functor guessing whether \a PID is running in a container.
58 /// Assumme using different \c pid namespace than \c self.
59 /////////////////////////////////////////////////////////////////
60 struct FilterRunsInLXC
62 bool operator()( pid_t pid_r ) const
63 { return( nsIno( pid_r, "pid" ) != pidNS ); }
66 : pidNS( nsIno( "self", "pid" ) )
69 static inline ino_t nsIno( const std::string & pid_r, const std::string & ns_r )
70 { return PathInfo("/proc/"+pid_r+"/ns/"+ns_r).ino(); }
72 static inline ino_t nsIno( pid_t pid_r, const std::string & ns_r )
73 { return nsIno( asString(pid_r), ns_r ); }
78 /////////////////////////////////////////////////////////////////
80 class CheckAccessDeleted::Impl
83 CheckAccessDeleted::Impl *clone() const;
85 bool addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap = nullptr );
86 void addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap = nullptr );
88 std::map<pid_t,CacheEntry> filterInput( externalprogram::ExternalDataSource &source );
89 CheckAccessDeleted::size_type createProcInfo( const std::map<pid_t,CacheEntry> &in );
91 std::vector<CheckAccessDeleted::ProcInfo> _data;
92 bool _fromLsofFileMode = false; // Set if we currently process data from a debug file
93 bool _verbose = false;
95 std::map<pid_t,std::vector<std::string>> debugMap; //will contain all used lsof files after filtering
99 CheckAccessDeleted::Impl *CheckAccessDeleted::Impl::clone() const
101 Impl *myClone = new Impl( *this );
105 /** Add \c cache to \c data if the process is accessing deleted files.
106 * \c pid string in \c cache is the proc line \c (pcuLR), \c files
107 * are already in place. Always clear the \c cache.files!
109 inline bool CheckAccessDeleted::Impl::addDataIf( const CacheEntry & cache_r, std::vector<std::string> *debMap )
111 const auto & filelist( cache_r.second );
113 if ( filelist.empty() )
116 // at least one file access so keep it:
117 _data.push_back( CheckAccessDeleted::ProcInfo() );
118 CheckAccessDeleted::ProcInfo & pinfo( _data.back() );
119 pinfo.files.insert( pinfo.files.begin(), filelist.begin(), filelist.end() );
121 const std::string & pline( cache_r.first );
122 std::string commandname; // pinfo.command if still needed...
123 std::ostringstream pLineStr; //rewrite the first line in debug cache
124 for_( ch, pline.begin(), pline.end() )
129 pinfo.pid = &*(ch+1);
131 pLineStr <<&*(ch)<<'\0';
134 pinfo.ppid = &*(ch+1);
136 pLineStr <<&*(ch)<<'\0';
139 pinfo.puid = &*(ch+1);
141 pLineStr <<&*(ch)<<'\0';
144 pinfo.login = &*(ch+1);
146 pLineStr <<&*(ch)<<'\0';
149 if ( pinfo.command.empty() ) {
150 commandname = &*(ch+1);
151 // the lsof command name might be truncated, so we prefer /proc/<pid>/exe
152 if (!_fromLsofFileMode)
153 pinfo.command = filesystem::readlink( Pathname("/proc")/pinfo.pid/"exe" ).basename();
154 if ( pinfo.command.empty() )
155 pinfo.command = std::move(commandname);
157 pLineStr <<'c'<<pinfo.command<<'\0';
161 if ( *ch == '\n' ) break; // end of data
162 do { ++ch; } while ( *ch != '\0' ); // skip to next field
165 //replace the data in the debug cache as well
168 debMap->front() = pLineStr.str();
176 /** Add file to cache if it refers to a deleted executable or library file:
177 * - Either the link count \c(k) is \c 0, or no link cout is present.
178 * - The type \c (t) is set to \c REG or \c DEL
179 * - The filedescriptor \c (f) is set to \c txt, \c mem or \c DEL
181 inline void CheckAccessDeleted::Impl::addCacheIf( CacheEntry & cache_r, const std::string & line_r, std::vector<std::string> *debMap )
187 for_( ch, line_r.c_str(), ch+line_r.size() )
192 if ( *(ch+1) != '0' ) // skip non-zero link counts
205 if ( *ch == '\n' ) break; // end of data
206 do { ++ch; } while ( *ch != '\0' ); // skip to next field
209 if ( !t || !f || !n )
210 return; // wrong filedescriptor/type/name
212 if ( !( ( *t == 'R' && *(t+1) == 'E' && *(t+2) == 'G' && *(t+3) == '\0' )
213 || ( *t == 'D' && *(t+1) == 'E' && *(t+2) == 'L' && *(t+3) == '\0' ) ) )
214 return; // wrong type
216 if ( !( ( *f == 'm' && *(f+1) == 'e' && *(f+2) == 'm' && *(f+3) == '\0' )
217 || ( *f == 't' && *(f+1) == 'x' && *(f+2) == 't' && *(f+3) == '\0' )
218 || ( *f == 'D' && *(f+1) == 'E' && *(f+2) == 'L' && *(f+3) == '\0' )
219 || ( *f == 'l' && *(f+1) == 't' && *(f+2) == 'x' && *(f+3) == '\0' ) ) )
220 return; // wrong filedescriptor type
222 if ( str::contains( n, "(stat: Permission denied)" ) )
223 return; // Avoid reporting false positive due to insufficient permission.
227 if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
228 return; // Try to avoid reporting false positive unless verbose.
231 if ( *f == 'm' || *f == 'D' ) // skip some wellknown nonlibrary memorymapped files
233 static const char * black[] = {
240 for_( it, arrayBegin( black ), arrayEnd( black ) )
242 if ( str::hasPrefix( n, *it ) )
246 // Add if no duplicate
247 if ( debMap && cache_r.second.find(n) == cache_r.second.end() ) {
248 debMap->push_back(line_r);
250 cache_r.second.insert( n );
253 CheckAccessDeleted::CheckAccessDeleted( bool doCheck_r )
256 if ( doCheck_r ) check();
259 CheckAccessDeleted::size_type CheckAccessDeleted::check( const Pathname &lsofOutput_r, bool verbose_r )
261 _pimpl->_verbose = verbose_r;
262 _pimpl->_fromLsofFileMode = true;
264 FILE *inFile = fopen( lsofOutput_r.c_str(), "r" );
266 ZYPP_THROW( Exception( str::Format("Opening input file %1% failed.") % lsofOutput_r.c_str() ) );
269 //inFile is closed by ExternalDataSource
270 externalprogram::ExternalDataSource inSource( inFile, nullptr );
271 auto cache = _pimpl->filterInput( inSource );
272 return _pimpl->createProcInfo( cache );
275 std::map<pid_t,CacheEntry> CheckAccessDeleted::Impl::filterInput( externalprogram::ExternalDataSource &source )
277 // cachemap: PID => (deleted files)
278 // NOTE: omit PIDs running in a (lxc/docker) container
279 std::map<pid_t,CacheEntry> cachemap;
281 bool debugEnabled = !_debugFile.empty();
284 FilterRunsInLXC runsInLXC;
285 for( std::string line = source.receiveLine(); ! line.empty(); line = source.receiveLine() )
287 // NOTE: line contains '\0' separeated fields!
288 if ( line[0] == 'p' )
290 str::strtonum( line.c_str()+1, cachepid ); // line is "p<PID>\0...."
291 if ( _fromLsofFileMode || !runsInLXC( cachepid ) ) {
292 if ( debugEnabled ) {
293 auto &pidMad = debugMap[cachepid];
294 if ( pidMad.empty() )
295 debugMap[cachepid].push_back( line );
297 debugMap[cachepid].front() = line;
299 cachemap[cachepid].first.swap( line );
301 cachepid = 0; // ignore this pid
306 auto &dbgMap = debugMap[cachepid];
307 addCacheIf( cachemap[cachepid], line, debugEnabled ? &dbgMap : nullptr);
313 CheckAccessDeleted::size_type CheckAccessDeleted::check( bool verbose_r )
315 static const char* argv[] =
317 "lsof", "-n", "-FpcuLRftkn0", NULL
320 _pimpl->_verbose = verbose_r;
321 _pimpl->_fromLsofFileMode = false;
323 ExternalProgram prog( argv, ExternalProgram::Discard_Stderr );
324 std::map<pid_t,CacheEntry> cachemap = _pimpl->filterInput( prog );
326 int ret = prog.close();
331 ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
333 Exception err( str::Format("Executing 'lsof' failed (%1%).") % ret );
334 err.remember( prog.execError() );
338 return _pimpl->createProcInfo( cachemap );
341 CheckAccessDeleted::size_type CheckAccessDeleted::Impl::createProcInfo(const std::map<pid_t,CacheEntry> &in)
343 std::ofstream debugFileOut;
344 bool debugEnabled = false;
345 if ( !_debugFile.empty() ) {
346 debugFileOut.open( _debugFile.c_str() );
347 debugEnabled = debugFileOut.is_open();
349 if ( !debugEnabled ) {
350 ERR<<"Unable to open debug file: "<<_debugFile<<endl;
355 for ( const auto &cached : in )
358 addDataIf( cached.second);
360 std::vector<std::string> *mapPtr = nullptr;
362 auto dbgInfo = debugMap.find(cached.first);
363 if ( dbgInfo != debugMap.end() )
364 mapPtr = &(dbgInfo->second);
366 if( !addDataIf( cached.second, mapPtr ) )
369 for ( const std::string &dbgLine: dbgInfo->second ) {
370 debugFileOut.write( dbgLine.c_str(), dbgLine.length() );
377 bool CheckAccessDeleted::empty() const
379 return _pimpl->_data.empty();
382 CheckAccessDeleted::size_type CheckAccessDeleted::size() const
384 return _pimpl->_data.size();
387 CheckAccessDeleted::const_iterator CheckAccessDeleted::begin() const
389 return _pimpl->_data.begin();
392 CheckAccessDeleted::const_iterator CheckAccessDeleted::end() const
394 return _pimpl->_data.end();
397 void CheckAccessDeleted::setDebugOutputFile(const Pathname &filename_r)
399 _pimpl->_debugFile = filename_r;
402 std::string CheckAccessDeleted::findService( pid_t pid_r )
405 p.pid = str::numstring( pid_r );
409 std::string CheckAccessDeleted::ProcInfo::service() const
411 static const str::regex rx( "[0-9]+:name=systemd:/system.slice/(.*/)?(.*).service$" );
414 iostr::simpleParseFile( InputStream( Pathname("/proc")/pid/"cgroup" ),
415 [&]( int num_r, std::string line_r )->bool
417 if ( str::regex_match( line_r, what, rx ) )
420 return false; // stop after match
427 /******************************************************************
429 ** FUNCTION NAME : operator<<
430 ** FUNCTION TYPE : std::ostream &
432 std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
434 return dumpRange( str << "CheckAccessDeleted ",
439 /******************************************************************
441 ** FUNCTION NAME : operator<<
442 ** FUNCTION TYPE : std::ostream &
444 std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
446 if ( obj.pid.empty() )
447 return str << "<NoProc>";
449 return dumpRangeLine( str << obj.command
459 /////////////////////////////////////////////////////////////////
461 ///////////////////////////////////////////////////////////////////