07819da610b0056772d1cbb85b924b99bf3603ed
[platform/upstream/libzypp.git] / zypp / misc / CheckAccessDeleted.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/misc/CheckAccessDeleted.cc
10  *
11 */
12 #include <iostream>
13 #include <fstream>
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"
19
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"
25
26 #include "zypp/misc/CheckAccessDeleted.h"
27
28 using std::endl;
29
30 #undef ZYPP_BASE_LOGGER_LOGGROUP
31 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
32
33 ///////////////////////////////////////////////////////////////////
34 namespace zypp
35 { /////////////////////////////////////////////////////////////////
36
37   ///////////////////////////////////////////////////////////////////
38   namespace
39   { /////////////////////////////////////////////////////////////////
40     //
41     // lsof output lines are a sequence of NUL terminated fields,
42     // where the 1st char determines the fields type.
43     //
44     // (pcuL) pid command userid loginname
45     // (ftkn).filedescriptor type linkcount filename
46     //
47     /////////////////////////////////////////////////////////////////
48
49     /** lsof output line + files extracted so far for this PID */
50     typedef std::pair<std::string,std::unordered_set<std::string>> CacheEntry;
51
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!
55     */
56     inline void addDataIf( std::vector<CheckAccessDeleted::ProcInfo> & data_r, const CacheEntry & cache_r )
57     {
58       const auto & filelist( cache_r.second );
59
60       if ( filelist.empty() )
61         return;
62
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() );
67
68       const std::string & pline( cache_r.first );
69       std::string commandname;  // pinfo.command if still needed...
70       for_( ch, pline.begin(), pline.end() )
71       {
72         switch ( *ch )
73         {
74           case 'p':
75             pinfo.pid = &*(ch+1);
76             break;
77           case 'R':
78             pinfo.ppid = &*(ch+1);
79             break;
80           case 'u':
81             pinfo.puid = &*(ch+1);
82             break;
83           case 'L':
84             pinfo.login = &*(ch+1);
85             break;
86           case 'c':
87             if ( pinfo.command.empty() )
88               commandname = &*(ch+1);
89             break;
90         }
91         if ( *ch == '\n' ) break;               // end of data
92         do { ++ch; } while ( *ch != '\0' );     // skip to next field
93       }
94
95       if ( pinfo.command.empty() )
96       {
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);
101       }
102     }
103
104
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
109     */
110     inline void addCacheIf( CacheEntry & cache_r, const std::string & line_r, bool verbose_r  )
111     {
112       const char * f = 0;
113       const char * t = 0;
114       const char * n = 0;
115
116       for_( ch, line_r.c_str(), ch+line_r.size() )
117       {
118         switch ( *ch )
119         {
120           case 'k':
121             if ( *(ch+1) != '0' )       // skip non-zero link counts
122               return;
123             break;
124           case 'f':
125             f = ch+1;
126             break;
127           case 't':
128             t = ch+1;
129             break;
130           case 'n':
131             n = ch+1;
132             break;
133         }
134         if ( *ch == '\n' ) break;               // end of data
135         do { ++ch; } while ( *ch != '\0' );     // skip to next field
136       }
137
138       if ( !t || !f || !n )
139         return; // wrong filedescriptor/type/name
140
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
144
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
150
151       if ( str::contains( n, "(stat: Permission denied)" ) )
152         return; // Avoid reporting false positive due to insufficient permission.
153
154       if ( ! verbose_r )
155       {
156         if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
157           return; // Try to avoid reporting false positive unless verbose.
158       }
159
160       if ( *f == 'm' || *f == 'D' )     // skip some wellknown nonlibrary memorymapped files
161       {
162         static const char * black[] = {
163             "/SYSV"
164           , "/var/run/"
165           , "/var/lib/sss/"
166           , "/dev/"
167         };
168         for_( it, arrayBegin( black ), arrayEnd( black ) )
169         {
170           if ( str::hasPrefix( n, *it ) )
171             return;
172         }
173       }
174       // Add if no duplicate
175       cache_r.second.insert( n );
176     }
177
178     /////////////////////////////////////////////////////////////////
179     /// \class FilterRunsInLXC
180     /// \brief Functor guessing whether \a PID is running in a container.
181     ///
182     /// Assumme using different \c pid namespace than \c self.
183     /////////////////////////////////////////////////////////////////
184     struct FilterRunsInLXC
185     {
186       bool operator()( pid_t pid_r ) const
187       { return( nsIno( pid_r, "pid" ) != pidNS ); }
188
189       FilterRunsInLXC()
190       : pidNS( nsIno( "self", "pid" ) )
191       {}
192
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(); }
195
196       static inline ino_t nsIno( pid_t pid_r, const std::string & ns_r )
197       { return  nsIno( asString(pid_r), ns_r ); }
198
199       ino_t pidNS;
200     };
201
202 #if 0
203     void lsofdebug( const Pathname & file_r )
204     {
205       std::ifstream infile( file_r.c_str() );
206       USR << infile << endl;
207       std::vector<std::string> fields;
208       CacheEntry cache;
209       for( iostr::EachLine in( infile ); in; in.next() )
210       {
211         std::string field( *in );
212         if ( field[0] == 'f' || field[0] == 'p' )
213         {
214           if ( !fields.empty() )
215           {
216             // consume
217             std::string line( str::join( fields, "\n" ) );
218             for ( char & c : line )
219             { if ( c == '\n' ) c = '\0'; }
220             line.push_back( '\n' );
221
222             size_t sze = cache.second.size();
223             addCacheIf( cache, line, false );
224             if ( sze != cache.second.size() )
225               USR << fields << endl;
226
227             fields.clear();
228           }
229           if ( field[0] == 'p' )
230             continue;
231           fields.push_back( field );
232         }
233         else if ( !fields.empty() )
234         {
235           fields.push_back( field );
236         }
237       }
238     }
239 #endif
240     /////////////////////////////////////////////////////////////////
241   } // namespace
242   ///////////////////////////////////////////////////////////////////
243
244   CheckAccessDeleted::size_type CheckAccessDeleted::check( bool verbose_r )
245   {
246     _data.clear();
247
248     static const char* argv[] =
249     {
250       "lsof", "-n", "-FpcuLRftkn0", NULL
251     };
252     ExternalProgram prog( argv, ExternalProgram::Discard_Stderr );
253
254     // cachemap: PID => (deleted files)
255     // NOTE: omit PIDs running in a (lxc/docker) container
256     std::map<pid_t,CacheEntry> cachemap;
257     pid_t cachepid = 0;
258     FilterRunsInLXC runsInLXC;
259     for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
260     {
261       // NOTE: line contains '\0' separeated fields!
262       if ( line[0] == 'p' )
263       {
264         str::strtonum( line.c_str()+1, cachepid );      // line is "p<PID>\0...."
265         if ( !runsInLXC( cachepid ) )
266           cachemap[cachepid].first.swap( line );
267         else
268           cachepid = 0; // ignore this pid
269       }
270       else if ( cachepid )
271       {
272         addCacheIf( cachemap[cachepid], line, verbose_r );
273       }
274     }
275
276     int ret = prog.close();
277     if ( ret != 0 )
278     {
279       if ( ret == 129 )
280       {
281         ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
282       }
283       Exception err( str::form("Executing 'lsof' failed (%d).", ret) );
284       err.remember( prog.execError() );
285       ZYPP_THROW( err );
286     }
287
288     std::vector<ProcInfo> data;
289     for ( const auto & cached : cachemap )
290     {
291       addDataIf( data, cached.second );
292     }
293     _data.swap( data );
294     return _data.size();
295   }
296
297   std::string CheckAccessDeleted::findService( pid_t pid_r )
298   {
299     ProcInfo p;
300     p.pid = str::numstring( pid_r );
301     return p.service();
302   }
303
304   ///////////////////////////////////////////////////////////////////
305   namespace
306   { /////////////////////////////////////////////////////////////////
307     /////////////////////////////////////////////////////////////////
308   } // namespace
309   ///////////////////////////////////////////////////////////////////
310
311   std::string CheckAccessDeleted::ProcInfo::service() const
312   {
313     static const str::regex rx( "[0-9]+:name=systemd:/system.slice/(.*/)?(.*).service$" );
314     str::smatch what;
315     std::string ret;
316     iostr::simpleParseFile( InputStream( Pathname("/proc")/pid/"cgroup" ),
317                             [&]( int num_r, std::string line_r )->bool
318                             {
319                               if ( str::regex_match( line_r, what, rx ) )
320                               {
321                                 ret = what[2];
322                                 return false;   // stop after match
323                               }
324                               return true;
325                             } );
326     return ret;
327   }
328
329   /******************************************************************
330   **
331   **    FUNCTION NAME : operator<<
332   **    FUNCTION TYPE : std::ostream &
333   */
334   std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
335   {
336     return dumpRange( str << "CheckAccessDeleted ",
337                       obj.begin(),
338                       obj.end() );
339   }
340
341    /******************************************************************
342   **
343   **    FUNCTION NAME : operator<<
344   **    FUNCTION TYPE : std::ostream &
345   */
346   std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
347   {
348     if ( obj.pid.empty() )
349       return str << "<NoProc>";
350
351     return dumpRangeLine( str << obj.command
352                               << '<' << obj.pid
353                               << '|' << obj.ppid
354                               << '|' << obj.puid
355                               << '|' << obj.login
356                               << '>',
357                           obj.files.begin(),
358                           obj.files.end() );
359   }
360
361  /////////////////////////////////////////////////////////////////
362 } // namespace zypp
363 ///////////////////////////////////////////////////////////////////