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