1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/misc/CheckAccessDeleted.cc
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"
19 #include "zypp/PathInfo.h"
20 #include "zypp/ExternalProgram.h"
21 #include "zypp/target/rpm/librpmDb.h"
23 #include "zypp/misc/CheckAccessDeleted.h"
27 #undef ZYPP_BASE_LOGGER_LOGGROUP
28 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
30 ///////////////////////////////////////////////////////////////////
32 { /////////////////////////////////////////////////////////////////
34 ///////////////////////////////////////////////////////////////////
36 { /////////////////////////////////////////////////////////////////
38 // lsof output lines are a sequence of NUL terminated fields,
39 // where the 1st char determines the fiels type.
41 // (pcuL) pid command userid loginname
42 // (ftkn).filedescriptor type linkcount filename
44 /////////////////////////////////////////////////////////////////
46 /** lsof output line + files extracted so far for this PID */
47 typedef std::pair<std::string,std::unordered_set<std::string>> CacheEntry;
49 /** Add \c cache to \c data if the process is accessing deleted files.
50 * \c pid string in \c cache is the proc line \c (pcuLR), \c files
51 * are already in place. Always clear the \c cache.files!
53 inline void addDataIf( std::vector<CheckAccessDeleted::ProcInfo> & data_r, const CacheEntry & cache_r )
55 const auto & filelist( cache_r.second );
57 if ( filelist.empty() )
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() );
65 const std::string & pline( cache_r.first );
66 for_( ch, pline.begin(), pline.end() )
74 pinfo.ppid = &*(ch+1);
77 pinfo.puid = &*(ch+1);
80 pinfo.login = &*(ch+1);
83 pinfo.command = &*(ch+1);
86 if ( *ch == '\n' ) break; // end of data
87 do { ++ch; } while ( *ch != '\0' ); // skip to next field
90 if ( pinfo.command.size() == 15 )
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();
97 //MIL << " Take " << pinfo << endl;
101 /** Add file to cache if it refers to a deleted executable or library file:
102 * - Either the link count \c(k) is \c 0, or no link cout is present.
103 * - The type \c (t) is set to \c REG or \c DEL
104 * - The filedescriptor \c (f) is set to \c txt, \c mem or \c DEL
106 inline void addCacheIf( CacheEntry & cache_r, const std::string & line_r, bool verbose_r )
112 for_( ch, line_r.c_str(), ch+line_r.size() )
117 if ( *(ch+1) != '0' ) // skip non-zero link counts
130 if ( *ch == '\n' ) break; // end of data
131 do { ++ch; } while ( *ch != '\0' ); // skip to next field
134 if ( !t || !f || !n )
135 return; // wrong filedescriptor/type/name
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
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
147 if ( str::contains( n, "(stat: Permission denied)" ) )
148 return; // Avoid reporting false positive due to insufficient permission.
152 if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
153 return; // Try to avoid reporting false positive unless verbose.
156 if ( *f == 'm' || *f == 'D' ) // skip some wellknown nonlibrary memorymapped files
158 static const char * black[] = {
163 for_( it, arrayBegin( black ), arrayEnd( black ) )
165 if ( str::hasPrefix( n, *it ) )
169 // Add if no duplicate
170 cache_r.second.insert( n );
173 /////////////////////////////////////////////////////////////////
174 /// \class FilterRunsInLXC
175 /// \brief Functor guessing whether \a PID is running in a container.
177 /// Assumme using different \c pid namespace than \c self.
178 /////////////////////////////////////////////////////////////////
179 struct FilterRunsInLXC
181 bool operator()( pid_t pid_r ) const
182 { return( nsIno( pid_r, "pid" ) != pidNS ); }
185 : pidNS( nsIno( "self", "pid" ) )
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(); }
191 static inline ino_t nsIno( pid_t pid_r, const std::string & ns_r )
192 { return nsIno( asString(pid_r), ns_r ); }
197 /** bsc#1099847: Check for lsof version < 4.90 which does not support '-K i'
198 * Just a quick check to allow code15 libzypp runnig in a code12 environment.
199 * bsc#1036304: '-K i' was backported to older lsof versions, indicated by
200 * lsof providing 'backported-option-Ki'.
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.
209 : _wasBlocked( librpmDb::isBlocked() )
210 { if ( _wasBlocked ) librpmDb::unblockAccess(); }
212 { if ( _wasBlocked ) librpmDb::blockAccess(); }
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") ) );
221 /////////////////////////////////////////////////////////////////
223 ///////////////////////////////////////////////////////////////////
225 CheckAccessDeleted::size_type CheckAccessDeleted::check( bool verbose_r )
229 static const char* argv[] = { "lsof", "-n", "-FpcuLRftkn0", "-K", "i", NULL };
232 ExternalProgram prog( argv, ExternalProgram::Discard_Stderr );
234 // cachemap: PID => (deleted files)
235 // NOTE: omit PIDs running in a (lxc/docker) container
236 std::map<pid_t,CacheEntry> cachemap;
238 FilterRunsInLXC runsInLXC;
239 for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
241 // NOTE: line contains '\0' separeated fields!
242 if ( line[0] == 'p' )
244 str::strtonum( line.c_str()+1, cachepid ); // line is "p<PID>\0...."
245 if ( !runsInLXC( cachepid ) )
246 cachemap[cachepid].first.swap( line );
248 cachepid = 0; // ignore this pid
252 addCacheIf( cachemap[cachepid], line, verbose_r );
256 int ret = prog.close();
261 ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
263 Exception err( str::form("Executing 'lsof' failed (%d).", ret) );
264 err.remember( prog.execError() );
268 std::vector<ProcInfo> data;
269 for ( const auto & cached : cachemap )
271 addDataIf( data, cached.second );
277 std::string CheckAccessDeleted::findService( const Pathname & command_r )
280 p.command = command_r.basename();
283 std::string CheckAccessDeleted::findService( const char * command_r )
284 { return findService( Pathname( command_r ) ); }
286 std::string CheckAccessDeleted::findService( const std::string & command_r )
287 { return findService( Pathname( command_r ) ); }
289 std::string CheckAccessDeleted::findService( pid_t pid_r )
290 { return findService( filesystem::readlink( Pathname("/proc")/str::numstring(pid_r)/"exe" ) ); }
292 ///////////////////////////////////////////////////////////////////
294 { /////////////////////////////////////////////////////////////////
295 /////////////////////////////////////////////////////////////////
297 ///////////////////////////////////////////////////////////////////
299 std::string CheckAccessDeleted::ProcInfo::service() const
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.
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() )
312 { // init.d script with name + 'd'
313 std::string alt( command+"d" );
314 PathInfo pi( initD/alt );
315 if ( pi.isFile() && pi.isX() )
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 );
324 if ( pi.isFile() && pi.isX() )
327 return std::string();
330 /******************************************************************
332 ** FUNCTION NAME : operator<<
333 ** FUNCTION TYPE : std::ostream &
335 std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
337 return dumpRange( str << "CheckAccessDeleted ",
342 /******************************************************************
344 ** FUNCTION NAME : operator<<
345 ** FUNCTION TYPE : std::ostream &
347 std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
349 if ( obj.pid.empty() )
350 return str << "<NoProc>";
352 return dumpRangeLine( str << obj.command
362 /////////////////////////////////////////////////////////////////
364 ///////////////////////////////////////////////////////////////////