Imported Upstream version 16.3.2
[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 <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"
18
19 #include "zypp/PathInfo.h"
20 #include "zypp/ExternalProgram.h"
21 #include "zypp/base/Regex.h"
22 #include "zypp/base/IOStream.h"
23 #include "zypp/base/InputStream.h"
24
25 #include "zypp/misc/CheckAccessDeleted.h"
26
27 using std::endl;
28
29 #undef ZYPP_BASE_LOGGER_LOGGROUP
30 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
31
32 ///////////////////////////////////////////////////////////////////
33 namespace zypp
34 { /////////////////////////////////////////////////////////////////
35
36   ///////////////////////////////////////////////////////////////////
37   namespace
38   { /////////////////////////////////////////////////////////////////
39     //
40     // lsof output lines are a sequence of NUL terminated fields,
41     // where the 1st char determines the fields type.
42     //
43     // (pcuL) pid command userid loginname
44     // (ftkn).filedescriptor type linkcount filename
45     //
46     /////////////////////////////////////////////////////////////////
47
48     /** lsof output line + files extracted so far for this PID */
49     typedef std::pair<std::string,std::unordered_set<std::string>> CacheEntry;
50
51     /** Add \c cache to \c data if the process is accessing deleted files.
52      * \c pid string in \c cache is the proc line \c (pcuLR), \c files
53      * are already in place. Always clear the \c cache.files!
54     */
55     inline void addDataIf( std::vector<CheckAccessDeleted::ProcInfo> & data_r, const CacheEntry & cache_r )
56     {
57       const auto & filelist( cache_r.second );
58
59       if ( filelist.empty() )
60         return;
61
62       // at least one file access so keep it:
63       data_r.push_back( CheckAccessDeleted::ProcInfo() );
64       CheckAccessDeleted::ProcInfo & pinfo( data_r.back() );
65       pinfo.files.insert( pinfo.files.begin(), filelist.begin(), filelist.end() );
66
67       const std::string & pline( cache_r.first );
68       std::string commandname;  // pinfo.command if still needed...
69       for_( ch, pline.begin(), pline.end() )
70       {
71         switch ( *ch )
72         {
73           case 'p':
74             pinfo.pid = &*(ch+1);
75             break;
76           case 'R':
77             pinfo.ppid = &*(ch+1);
78             break;
79           case 'u':
80             pinfo.puid = &*(ch+1);
81             break;
82           case 'L':
83             pinfo.login = &*(ch+1);
84             break;
85           case 'c':
86             if ( pinfo.command.empty() )
87               commandname = &*(ch+1);
88             break;
89         }
90         if ( *ch == '\n' ) break;               // end of data
91         do { ++ch; } while ( *ch != '\0' );     // skip to next field
92       }
93
94       if ( pinfo.command.empty() )
95       {
96         // the lsof command name might be truncated, so we prefer /proc/<pid>/exe
97         pinfo.command = filesystem::readlink( Pathname("/proc")/pinfo.pid/"exe" ).basename();
98         if ( pinfo.command.empty() )
99           pinfo.command = std::move(commandname);
100       }
101     }
102
103
104     /** Add file to cache if it refers to a deleted executable or library file:
105      * - Either the link count \c(k) is \c 0, or no link cout is present.
106      * - The type \c (t) is set to \c REG or \c DEL
107      * - The filedescriptor \c (f) is set to \c txt, \c mem or \c DEL
108     */
109     inline void addCacheIf( CacheEntry & cache_r, const std::string & line_r, bool verbose_r  )
110     {
111       const char * f = 0;
112       const char * t = 0;
113       const char * n = 0;
114
115       for_( ch, line_r.c_str(), ch+line_r.size() )
116       {
117         switch ( *ch )
118         {
119           case 'k':
120             if ( *(ch+1) != '0' )       // skip non-zero link counts
121               return;
122             break;
123           case 'f':
124             f = ch+1;
125             break;
126           case 't':
127             t = ch+1;
128             break;
129           case 'n':
130             n = ch+1;
131             break;
132         }
133         if ( *ch == '\n' ) break;               // end of data
134         do { ++ch; } while ( *ch != '\0' );     // skip to next field
135       }
136
137       if ( !t || !f || !n )
138         return; // wrong filedescriptor/type/name
139
140       if ( !(    ( *t == 'R' && *(t+1) == 'E' && *(t+2) == 'G' && *(t+3) == '\0' )
141               || ( *t == 'D' && *(t+1) == 'E' && *(t+2) == 'L' && *(t+3) == '\0' ) ) )
142         return; // wrong type
143
144       if ( !(    ( *f == 'm' && *(f+1) == 'e' && *(f+2) == 'm' && *(f+3) == '\0' )
145               || ( *f == 't' && *(f+1) == 'x' && *(f+2) == 't' && *(f+3) == '\0' )
146               || ( *f == 'D' && *(f+1) == 'E' && *(f+2) == 'L' && *(f+3) == '\0' )
147               || ( *f == 'l' && *(f+1) == 't' && *(f+2) == 'x' && *(f+3) == '\0' ) ) )
148         return; // wrong filedescriptor type
149
150       if ( str::contains( n, "(stat: Permission denied)" ) )
151         return; // Avoid reporting false positive due to insufficient permission.
152
153       if ( ! verbose_r )
154       {
155         if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
156           return; // Try to avoid reporting false positive unless verbose.
157       }
158
159       if ( *f == 'm' || *f == 'D' )     // skip some wellknown nonlibrary memorymapped files
160       {
161         static const char * black[] = {
162             "/SYSV"
163           , "/var/run/"
164           , "/dev/"
165         };
166         for_( it, arrayBegin( black ), arrayEnd( black ) )
167         {
168           if ( str::hasPrefix( n, *it ) )
169             return;
170         }
171       }
172       // Add if no duplicate
173       cache_r.second.insert( n );
174     }
175
176     /////////////////////////////////////////////////////////////////
177     /// \class FilterRunsInLXC
178     /// \brief Functor guessing whether \a PID is running in a container.
179     ///
180     /// Asumme a using different \c pid/mnt namespace than \c self.
181     /////////////////////////////////////////////////////////////////
182     struct FilterRunsInLXC
183     {
184       bool operator()( pid_t pid_r ) const
185       { return( nsIno( pid_r, "pid" ) != pidNS || nsIno( pid_r, "mnt" ) != mntNS ); }
186
187       FilterRunsInLXC()
188       : pidNS( nsIno( "self", "pid" ) )
189       , mntNS( nsIno( "self", "mnt" ) )
190       {}
191
192       static inline ino_t nsIno( const std::string & pid_r, const std::string & ns_r )
193       { return PathInfo("/proc/"+pid_r+"/ns/"+ns_r).ino(); }
194
195       static inline ino_t nsIno( pid_t pid_r, const std::string & ns_r )
196       { return  nsIno( asString(pid_r), ns_r ); }
197
198       ino_t pidNS;
199       ino_t mntNS;
200     };
201
202     /////////////////////////////////////////////////////////////////
203   } // namespace
204   ///////////////////////////////////////////////////////////////////
205
206   CheckAccessDeleted::size_type CheckAccessDeleted::check( bool verbose_r )
207   {
208     _data.clear();
209
210     static const char* argv[] =
211     {
212       "lsof", "-n", "-FpcuLRftkn0", NULL
213     };
214     ExternalProgram prog( argv, ExternalProgram::Discard_Stderr );
215
216     // cachemap: PID => (deleted files)
217     // NOTE: omit PIDs running in a (lxc/docker) container
218     std::map<pid_t,CacheEntry> cachemap;
219     pid_t cachepid = 0;
220     FilterRunsInLXC runsInLXC;
221     for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
222     {
223       // NOTE: line contains '\0' separeated fields!
224       if ( line[0] == 'p' )
225       {
226         str::strtonum( line.c_str()+1, cachepid );      // line is "p<PID>\0...."
227         if ( !runsInLXC( cachepid ) )
228           cachemap[cachepid].first.swap( line );
229         else
230           cachepid = 0; // ignore this pid
231       }
232       else if ( cachepid )
233       {
234         addCacheIf( cachemap[cachepid], line, verbose_r );
235       }
236     }
237
238     int ret = prog.close();
239     if ( ret != 0 )
240     {
241       if ( ret == 129 )
242       {
243         ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
244       }
245       Exception err( str::form("Executing 'lsof' failed (%d).", ret) );
246       err.remember( prog.execError() );
247       ZYPP_THROW( err );
248     }
249
250     std::vector<ProcInfo> data;
251     for ( const auto & cached : cachemap )
252     {
253       addDataIf( data, cached.second );
254     }
255     _data.swap( data );
256     return _data.size();
257   }
258
259   std::string CheckAccessDeleted::findService( pid_t pid_r )
260   {
261     ProcInfo p;
262     p.pid = str::numstring( pid_r );
263     return p.service();
264   }
265
266   ///////////////////////////////////////////////////////////////////
267   namespace
268   { /////////////////////////////////////////////////////////////////
269     /////////////////////////////////////////////////////////////////
270   } // namespace
271   ///////////////////////////////////////////////////////////////////
272
273   std::string CheckAccessDeleted::ProcInfo::service() const
274   {
275     static const str::regex rx( "[0-9]+:name=systemd:/system.slice/(.*/)?(.*).service$" );
276     str::smatch what;
277     std::string ret;
278     iostr::simpleParseFile( InputStream( Pathname("/proc")/pid/"cgroup" ),
279                             [&]( int num_r, std::string line_r )->bool
280                             {
281                               if ( str::regex_match( line_r, what, rx ) )
282                               {
283                                 ret = what[2];
284                                 return false;   // stop after match
285                               }
286                               return true;
287                             } );
288     return ret;
289   }
290
291   /******************************************************************
292   **
293   **    FUNCTION NAME : operator<<
294   **    FUNCTION TYPE : std::ostream &
295   */
296   std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
297   {
298     return dumpRange( str << "CheckAccessDeleted ",
299                       obj.begin(),
300                       obj.end() );
301   }
302
303    /******************************************************************
304   **
305   **    FUNCTION NAME : operator<<
306   **    FUNCTION TYPE : std::ostream &
307   */
308   std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
309   {
310     if ( obj.pid.empty() )
311       return str << "<NoProc>";
312
313     return dumpRangeLine( str << obj.command
314                               << '<' << obj.pid
315                               << '|' << obj.ppid
316                               << '|' << obj.puid
317                               << '|' << obj.login
318                               << '>',
319                           obj.files.begin(),
320                           obj.files.end() );
321   }
322
323  /////////////////////////////////////////////////////////////////
324 } // namespace zypp
325 ///////////////////////////////////////////////////////////////////