6d63abfb57e19efb4c0dac7e9e6900d2e9454d74
[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/target/rpm/librpmDb.h"
22
23 #include "zypp/misc/CheckAccessDeleted.h"
24
25 using std::endl;
26
27 #undef ZYPP_BASE_LOGGER_LOGGROUP
28 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::misc"
29
30 ///////////////////////////////////////////////////////////////////
31 namespace zypp
32 { /////////////////////////////////////////////////////////////////
33
34   ///////////////////////////////////////////////////////////////////
35   namespace
36   { /////////////////////////////////////////////////////////////////
37     //
38     // lsof output lines are a sequence of NUL terminated fields,
39     // where the 1st char determines the fiels type.
40     //
41     // (pcuL) pid command userid loginname
42     // (ftkn).filedescriptor type linkcount filename
43     //
44     /////////////////////////////////////////////////////////////////
45
46     /** lsof output line + files extracted so far for this PID */
47     typedef std::pair<std::string,std::unordered_set<std::string>> CacheEntry;
48
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!
52     */
53     inline void addDataIf( std::vector<CheckAccessDeleted::ProcInfo> & data_r, const CacheEntry & cache_r )
54     {
55       const auto & filelist( cache_r.second );
56
57       if ( filelist.empty() )
58         return;
59
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() );
64
65       const std::string & pline( cache_r.first );
66       for_( ch, pline.begin(), pline.end() )
67       {
68         switch ( *ch )
69         {
70           case 'p':
71             pinfo.pid = &*(ch+1);
72             break;
73           case 'R':
74             pinfo.ppid = &*(ch+1);
75             break;
76           case 'u':
77             pinfo.puid = &*(ch+1);
78             break;
79           case 'L':
80             pinfo.login = &*(ch+1);
81             break;
82           case 'c':
83             pinfo.command = &*(ch+1);
84             break;
85         }
86         if ( *ch == '\n' ) break;               // end of data
87         do { ++ch; } while ( *ch != '\0' );     // skip to next field
88       }
89
90       if ( pinfo.command.size() == 15 )
91       {
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();
96       }
97       //MIL << " Take " << pinfo << endl;
98     }
99
100
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
105     */
106     inline void addCacheIf( CacheEntry & cache_r, const std::string & line_r, bool verbose_r  )
107     {
108       const char * f = 0;
109       const char * t = 0;
110       const char * n = 0;
111
112       for_( ch, line_r.c_str(), ch+line_r.size() )
113       {
114         switch ( *ch )
115         {
116           case 'k':
117             if ( *(ch+1) != '0' )       // skip non-zero link counts
118               return;
119             break;
120           case 'f':
121             f = ch+1;
122             break;
123           case 't':
124             t = ch+1;
125             break;
126           case 'n':
127             n = ch+1;
128             break;
129         }
130         if ( *ch == '\n' ) break;               // end of data
131         do { ++ch; } while ( *ch != '\0' );     // skip to next field
132       }
133
134       if ( !t || !f || !n )
135         return; // wrong filedescriptor/type/name
136
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
140
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
146
147       if ( str::contains( n, "(stat: Permission denied)" ) )
148         return; // Avoid reporting false positive due to insufficient permission.
149
150       if ( ! verbose_r )
151       {
152         if ( ! ( str::contains( n, "/lib" ) || str::contains( n, "bin/" ) ) )
153           return; // Try to avoid reporting false positive unless verbose.
154       }
155
156       if ( *f == 'm' || *f == 'D' )     // skip some wellknown nonlibrary memorymapped files
157       {
158         static const char * black[] = {
159             "/SYSV"
160           , "/var/run/"
161           , "/dev/"
162         };
163         for_( it, arrayBegin( black ), arrayEnd( black ) )
164         {
165           if ( str::hasPrefix( n, *it ) )
166             return;
167         }
168       }
169       // Add if no duplicate
170       cache_r.second.insert( n );
171     }
172
173     /////////////////////////////////////////////////////////////////
174     /// \class FilterRunsInLXC
175     /// \brief Functor guessing whether \a PID is running in a container.
176     ///
177     /// Assumme using different \c pid namespace than \c self.
178     /////////////////////////////////////////////////////////////////
179     struct FilterRunsInLXC
180     {
181       bool operator()( pid_t pid_r ) const
182       { return( nsIno( pid_r, "pid" ) != pidNS ); }
183
184       FilterRunsInLXC()
185       : pidNS( nsIno( "self", "pid" ) )
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     };
196
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'.
201      */
202     bool lsofNoOptKi()
203     {
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.
207       struct TmpUnblock {
208         TmpUnblock()
209         : _wasBlocked( librpmDb::isBlocked() )
210         { if ( _wasBlocked ) librpmDb::unblockAccess(); }
211         ~TmpUnblock()
212         { if ( _wasBlocked ) librpmDb::blockAccess(); }
213       private:
214         bool _wasBlocked;
215       } tmpUnblock;
216
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") ) );
219     }
220
221     /////////////////////////////////////////////////////////////////
222   } // namespace
223   ///////////////////////////////////////////////////////////////////
224
225   CheckAccessDeleted::size_type CheckAccessDeleted::check( bool verbose_r )
226   {
227     _data.clear();
228
229     static const char* argv[] = { "lsof", "-n", "-FpcuLRftkn0", "-K", "i", NULL };
230     if ( lsofNoOptKi() )
231       argv[3] = NULL;
232     ExternalProgram prog( argv, ExternalProgram::Discard_Stderr );
233
234     // cachemap: PID => (deleted files)
235     // NOTE: omit PIDs running in a (lxc/docker) container
236     std::map<pid_t,CacheEntry> cachemap;
237     pid_t cachepid = 0;
238     FilterRunsInLXC runsInLXC;
239     for( std::string line = prog.receiveLine(); ! line.empty(); line = prog.receiveLine() )
240     {
241       // NOTE: line contains '\0' separeated fields!
242       if ( line[0] == 'p' )
243       {
244         str::strtonum( line.c_str()+1, cachepid );      // line is "p<PID>\0...."
245         if ( !runsInLXC( cachepid ) )
246           cachemap[cachepid].first.swap( line );
247         else
248           cachepid = 0; // ignore this pid
249       }
250       else if ( cachepid )
251       {
252         addCacheIf( cachemap[cachepid], line, verbose_r );
253       }
254     }
255
256     int ret = prog.close();
257     if ( ret != 0 )
258     {
259       if ( ret == 129 )
260       {
261         ZYPP_THROW( Exception(_("Please install package 'lsof' first.") ) );
262       }
263       Exception err( str::form("Executing 'lsof' failed (%d).", ret) );
264       err.remember( prog.execError() );
265       ZYPP_THROW( err );
266     }
267
268     std::vector<ProcInfo> data;
269     for ( const auto & cached : cachemap )
270     {
271       addDataIf( data, cached.second );
272     }
273     _data.swap( data );
274     return _data.size();
275   }
276
277   std::string CheckAccessDeleted::findService( const Pathname & command_r )
278   {
279     ProcInfo p;
280     p.command = command_r.basename();
281     return p.service();
282   }
283   std::string CheckAccessDeleted::findService( const char * command_r )
284   { return findService( Pathname( command_r ) ); }
285
286   std::string CheckAccessDeleted::findService( const std::string & command_r )
287   { return findService( Pathname( command_r ) ); }
288
289   std::string CheckAccessDeleted::findService( pid_t pid_r )
290   { return findService( filesystem::readlink( Pathname("/proc")/str::numstring(pid_r)/"exe" ) ); }
291
292   ///////////////////////////////////////////////////////////////////
293   namespace
294   { /////////////////////////////////////////////////////////////////
295     /////////////////////////////////////////////////////////////////
296   } // namespace
297   ///////////////////////////////////////////////////////////////////
298
299   std::string CheckAccessDeleted::ProcInfo::service() const
300   {
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.
305
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() )
310         return command;
311     }
312     { // init.d script with name + 'd'
313       std::string alt( command+"d" );
314       PathInfo pi( initD/alt );
315       if ( pi.isFile() && pi.isX() )
316         return alt;
317     }
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 );
323       WAR <<pi << endl;
324       if ( pi.isFile() && pi.isX() )
325         return alt;
326     }
327     return std::string();
328   }
329
330   /******************************************************************
331   **
332   **    FUNCTION NAME : operator<<
333   **    FUNCTION TYPE : std::ostream &
334   */
335   std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted & obj )
336   {
337     return dumpRange( str << "CheckAccessDeleted ",
338                       obj.begin(),
339                       obj.end() );
340   }
341
342    /******************************************************************
343   **
344   **    FUNCTION NAME : operator<<
345   **    FUNCTION TYPE : std::ostream &
346   */
347   std::ostream & operator<<( std::ostream & str, const CheckAccessDeleted::ProcInfo & obj )
348   {
349     if ( obj.pid.empty() )
350       return str << "<NoProc>";
351
352     return dumpRangeLine( str << obj.command
353                               << '<' << obj.pid
354                               << '|' << obj.ppid
355                               << '|' << obj.puid
356                               << '|' << obj.login
357                               << '>',
358                           obj.files.begin(),
359                           obj.files.end() );
360   }
361
362  /////////////////////////////////////////////////////////////////
363 } // namespace zypp
364 ///////////////////////////////////////////////////////////////////