Imported Upstream version 15.21.0
[platform/upstream/libzypp.git] / zypp / DiskUsageCounter.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/DiskUsageCounter.cc
10  *
11  */
12 extern "C"
13 {
14 #include <sys/statvfs.h>
15 }
16
17 #include <iostream>
18 #include <fstream>
19
20 #include "zypp/base/Easy.h"
21 #include "zypp/base/LogTools.h"
22 #include "zypp/base/DtorReset.h"
23 #include "zypp/base/String.h"
24
25 #include "zypp/DiskUsageCounter.h"
26 #include "zypp/ExternalProgram.h"
27 #include "zypp/sat/Pool.h"
28 #include "zypp/sat/detail/PoolImpl.h"
29
30 using std::endl;
31
32 ///////////////////////////////////////////////////////////////////
33 namespace zypp
34 { /////////////////////////////////////////////////////////////////
35
36   ///////////////////////////////////////////////////////////////////
37   namespace
38   { /////////////////////////////////////////////////////////////////
39
40     DiskUsageCounter::MountPointSet calcDiskUsage( DiskUsageCounter::MountPointSet result, const Bitmap & installedmap_r )
41     {
42       if ( result.empty() )
43       {
44         // partitioning is not set
45         return result;
46       }
47
48       sat::Pool satpool( sat::Pool::instance() );
49
50       // init libsolv result vector with mountpoints
51       static const ::DUChanges _initdu = { 0, 0, 0, 0 };
52       std::vector< ::DUChanges> duchanges( result.size(), _initdu );
53       {
54         unsigned idx = 0;
55         for_( it, result.begin(), result.end() )
56         {
57           duchanges[idx].path = it->dir.c_str();
58           if ( it->growonly )
59             duchanges[idx].flags |= DUCHANGES_ONLYADD;
60           ++idx;
61         }
62       }
63       // now calc...
64       ::pool_calc_duchanges( satpool.get(),
65                              const_cast<Bitmap &>(installedmap_r),
66                              &duchanges[0],
67                              duchanges.size() );
68
69       // and process the result
70       {
71         unsigned idx = 0;
72         for_( it, result.begin(), result.end() )
73         {
74           static const ByteCount blockAdjust( 2, ByteCount::K ); // (files * blocksize) / (2 * 1K)
75           it->pkg_size = it->used_size          // current usage
76                        + duchanges[idx].kbytes  // package data size
77                        + ( duchanges[idx].files * it->block_size / blockAdjust ); // half block per file
78           ++idx;
79         }
80       }
81
82       return result;
83     }
84
85     /////////////////////////////////////////////////////////////////
86   } // namespace
87   ///////////////////////////////////////////////////////////////////
88
89   DiskUsageCounter::MountPointSet DiskUsageCounter::disk_usage( const ResPool & pool_r ) const
90   {
91     Bitmap bitmap( Bitmap::poolSize );
92
93     // build installedmap (installed != transact)
94     // stays installed or gets installed
95     for_( it, pool_r.begin(), pool_r.end() )
96     {
97       if ( it->status().isInstalled() != it->status().transacts() )
98       {
99         bitmap.set( sat::asSolvable()(*it).id() );
100       }
101     }
102     return calcDiskUsage( _mps, bitmap );
103   }
104
105   DiskUsageCounter::MountPointSet DiskUsageCounter::disk_usage( sat::Solvable solv_r ) const
106   {
107     Bitmap bitmap( Bitmap::poolSize );
108     bitmap.set( solv_r.id() );
109
110     // temp. unset @system Repo
111     DtorReset tmp( sat::Pool::instance().get()->installed );
112     sat::Pool::instance().get()->installed = nullptr;
113
114     return calcDiskUsage( _mps, bitmap );
115   }
116
117   DiskUsageCounter::MountPointSet DiskUsageCounter::disk_usage( const Bitmap & bitmap_r ) const
118   {
119     // temp. unset @system Repo
120     DtorReset tmp( sat::Pool::instance().get()->installed );
121     sat::Pool::instance().get()->installed = nullptr;
122
123     return calcDiskUsage( _mps, bitmap_r );
124   }
125
126   DiskUsageCounter::MountPointSet DiskUsageCounter::detectMountPoints( const std::string & rootdir )
127   {
128     DiskUsageCounter::MountPointSet ret;
129
130     typedef std::map<ulong, MountPoint> Btrfsfilter;
131     Btrfsfilter btrfsfilter;    // see btrfs hack below
132
133       std::ifstream procmounts( "/proc/mounts" );
134
135       if ( !procmounts ) {
136         WAR << "Unable to read /proc/mounts" << std::endl;
137       } else {
138
139         std::string prfx;
140         if ( rootdir != "/" )
141           prfx = rootdir; // rootdir not /
142
143         while ( procmounts ) {
144           std::string l = str::getline( procmounts );
145           if ( !(procmounts.fail() || procmounts.bad()) ) {
146             // data to consume
147
148             // rootfs           /               rootfs          rw 0 0
149             // /dev/root        /               reiserfs        rw 0 0
150             // proc             /proc           proc            rw 0 0
151             // devpts           /dev/pts        devpts          rw 0 0
152             // /dev/hda5        /boot           ext2            rw 0 0
153             // shmfs            /dev/shm        shm             rw 0 0
154             // usbdevfs         /proc/bus/usb   usbdevfs        rw 0 0
155
156             std::vector<std::string> words;
157             str::split( l, std::back_inserter(words) );
158
159             if ( words.size() < 3 ) {
160               WAR << "Suspicious entry in /proc/mounts: " << l << std::endl;
161               continue;
162             }
163
164             //
165             // Filter devices without '/' (proc,shmfs,..)
166             //
167             if ( words[0].find( '/' ) == std::string::npos ) {
168               DBG << "Discard mount point : " << l << std::endl;
169               continue;
170             }
171
172             // remove /proc entry
173             if (words[0] == "/proc")
174             {
175               DBG << "Discard /proc filesystem: " << l << std::endl;
176               continue;
177             }
178
179             //
180             // Filter mountpoints not at or below _rootdir
181             //
182             std::string mp = words[1];
183             if ( prfx.size() ) {
184               if ( mp.compare( 0, prfx.size(), prfx ) != 0 ) {
185                 // mountpoint not below rootdir
186                 DBG << "Unwanted mount point : " << l << std::endl;
187                 continue;
188               }
189               // strip prfx
190               mp.erase( 0, prfx.size() );
191               if ( mp.empty() ) {
192                 mp = "/";
193               } else if ( mp[0] != '/' ) {
194                 // mountpoint not below rootdir
195                 DBG << "Unwanted mount point : " << l << std::endl;
196                 continue;
197               }
198             }
199
200             //
201             // Filter cdrom
202             //
203             if ( words[2] == "iso9660" ) {
204               DBG << "Discard cdrom : " << l << std::endl;
205               continue;
206             }
207
208             if ( words[2] == "vfat" || words[2] == "fat" || words[2] == "ntfs" || words[2] == "ntfs-3g")
209             {
210               MIL << words[1] << " contains ignored fs (" << words[2] << ')' << std::endl;
211               continue;
212             }
213
214             //
215             // Filter some common unwanted mountpoints
216             //
217             const char * mpunwanted[] = {
218               "/mnt", "/media", "/mounts", "/floppy", "/cdrom",
219               "/suse", "/tmp", "/var/tmp", "/var/adm/mount", "/var/adm/YaST",
220               /*last*/0/*entry*/
221             };
222
223             const char ** nomp = mpunwanted;
224             for ( ; *nomp; ++nomp ) {
225               std::string pre( *nomp );
226               if ( mp.compare( 0, pre.size(), pre ) == 0 // mp has prefix pre
227                    && ( mp.size() == pre.size() || mp[pre.size()] == '/' ) ) {
228                 break;
229               }
230             }
231             if ( *nomp ) {
232               DBG << "Filter mount point : " << l << std::endl;
233               continue;
234             }
235
236             //
237             // Check whether mounted readonly
238             //
239             MountPoint::HintFlags hints;
240
241             std::vector<std::string> flags;
242             str::split( words[3], std::back_inserter(flags), "," );
243
244             for ( unsigned i = 0; i < flags.size(); ++i ) {
245               if ( flags[i] == "ro" ) {
246                 hints |= MountPoint::Hint_readonly;
247                 break;
248               }
249             }
250             if ( hints.testFlag( MountPoint::Hint_readonly ) ) {
251               DBG << "Filter ro mount point : " << l << std::endl;
252               continue;
253             }
254
255             //
256             // check for snapshotting btrfs
257             //
258             bool btrfshack = false;
259             if ( words[2] == "btrfs" )
260             {
261               btrfshack = true;
262               if ( geteuid() != 0 )
263               {
264                 DBG << "Assume snapshots on " << words[1] << ": non-root user can't check" << std::endl;
265                 hints |= MountPoint::Hint_growonly;
266               }
267               else
268               {
269                 // For now just check whether there is
270                 // at least one snapshot on the volume:
271                 ExternalProgram prog({"btrfs","subvolume","list","-s",words[1]});
272                 std::string line( prog.receiveLine() );
273                 if ( ! line.empty() )
274                 {
275                   DBG << "Found a snapshot on " << words[1] << ": " << line; // has trailing std::endl
276                   hints |= MountPoint::Hint_growonly;
277                 }
278                 prog.kill();
279               }
280             }
281
282             //
283             // statvfs (full path!) and get the data
284             //
285             struct statvfs sb;
286             if ( statvfs( words[1].c_str(), &sb ) != 0 ) {
287               WAR << "Unable to statvfs(" << words[1] << "); errno " << errno << std::endl;
288               ret.insert( DiskUsageCounter::MountPoint( mp, words[2], 0LL, 0LL, 0LL, 0LL, hints ) );
289             }
290             else
291             {
292               //
293               // Filter zero sized devices (bnc#769819)
294               //
295               if ( sb.f_blocks == 0 || sb.f_bsize == 0 )
296               {
297                 DBG << "Filter zero-sized mount point : " << l << std::endl;
298                 continue;
299               }
300               if ( btrfshack )
301               {
302                 // HACK:
303                 // Collect just the top/1st mountpoint of each btrfs volume
304                 // (by file system ID). This filters away nested subvolumes
305                 // which otherwise break per package disk usage computation.
306                 // FIX: Computation must learn to handle multiple mount points
307                 // contributing to the same file system.
308                 MountPoint & bmp( btrfsfilter[sb.f_fsid] );
309                 if ( bmp.fstype.empty() )       // 1st occurance
310                 {
311                   bmp = DiskUsageCounter::MountPoint( mp, words[2], sb.f_bsize,
312                                                       ((long long)sb.f_blocks)*sb.f_bsize/1024,
313                                                       ((long long)(sb.f_blocks - sb.f_bfree))*sb.f_bsize/1024, 0LL, hints );
314                 }
315                 else if ( bmp.dir > mp )
316                   bmp.dir = mp;
317                 continue;
318               }
319               ret.insert( DiskUsageCounter::MountPoint( mp, words[2], sb.f_bsize,
320                 ((long long)sb.f_blocks)*sb.f_bsize/1024,
321                 ((long long)(sb.f_blocks - sb.f_bfree))*sb.f_bsize/1024, 0LL, hints ) );
322             }
323           }
324         }
325     }
326
327     // collect filtered btrfs volumes
328     for ( auto && bmp : btrfsfilter )
329       ret.insert( std::move(bmp.second) );
330
331     return ret;
332   }
333
334   DiskUsageCounter::MountPointSet DiskUsageCounter::justRootPartition()
335   {
336     DiskUsageCounter::MountPointSet ret;
337     ret.insert( DiskUsageCounter::MountPoint() );
338     return ret;
339   }
340
341   std::ostream & operator<<( std::ostream & str, const DiskUsageCounter::MountPoint & obj )
342   {
343      str << "dir:[" << obj.dir << "] [ bs: " << obj.blockSize()
344         << " ts: " << obj.totalSize()
345         << " us: " << obj.usedSize()
346         << " (+-: " << obj.commitDiff()
347         << ")" << (obj.readonly?"r":"") << (obj.growonly?"g":"") << " " << obj.fstype << "]";
348     return str;
349   }
350
351   std::ostream & operator<<( std::ostream & str, const DiskUsageCounter::MountPointSet & obj )
352   { return dumpRange( str, obj.begin(), obj.end() ); }
353
354   /////////////////////////////////////////////////////////////////
355 } // namespace zypp
356 ///////////////////////////////////////////////////////////////////