- fixed disk usage counting of updated packages (#308362)
[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/Logger.h"
21 #include "zypp/base/String.h"
22
23 #include "zypp/DiskUsageCounter.h"
24 #include "zypp/Package.h"
25
26
27 ///////////////////////////////////////////////////////////////////
28 namespace zypp
29 { /////////////////////////////////////////////////////////////////
30
31   DiskUsageCounter::MountPointSet DiskUsageCounter::disk_usage(const ResPool &pool)
32   {
33     MountPointSet result = mps;
34
35     if (mps.empty())
36     {
37       // partitioning is not set
38       return result;
39     }
40
41     // set used size after commit to the current used size
42     for (MountPointSet::iterator mpit = result.begin();
43       mpit != result.end();
44       mpit++)
45     {
46       mpit->pkg_size = mpit->used_size;
47     }
48
49     // iterate through all packages
50     for (ResPool::byKind_iterator it = pool.byKindBegin(ResTraits<Package>::kind);
51       it != pool.byKindEnd(ResTraits<Package>::kind);
52       ++it)
53     {
54       bool inst = it->status().isToBeInstalled();
55       bool rem = it->status().isToBeUninstalled();
56
57       // if the package is not selected for installation or removing
58       // it can't affect disk usage
59       if (inst || rem)
60       {
61         Package::constPtr pkg = asKind<Package>( it->resolvable() );
62         DiskUsage du = pkg->diskusage();
63         DiskUsage du_another_package;
64         Edition edition_another_package;
65
66         // the same package has been selected for installation
67         bool found_installed = false;
68         // the same package has been selected for uninstallation
69         bool found_to_install = false;
70
71         // the du is empty or the package is selected for installation (check whether the package is already installed)
72         if (du.size() == 0 || inst)
73         {
74             // disk usage is unknown for already installed packages
75             // find the same package from any installation source
76             std::string name = (*it)->name();
77
78             for (ResPool::byName_iterator nameit = pool.byNameBegin(name);
79               nameit != pool.byNameEnd(name);
80               ++nameit)
81             {
82                 // is version and architecture same?
83                 if (isKind<Package>(nameit->resolvable()))
84                 {
85                     // found a package
86                     Package::constPtr pkg_from_source = asKind<Package>( nameit->resolvable() );
87
88                     if (nameit->status().isToBeInstalled())
89                     {
90                         found_to_install = true;
91                     }
92
93                     // check the version
94                     if ((*it)->edition() == (*nameit)->edition() && (*it)->arch() == (*nameit)->arch())
95                     {
96                         if (inst)
97                         {
98                             if (nameit->status().isInstalled() && !nameit->status().isToBeUninstalled())
99                             {
100                                 found_installed = true;
101                                 XXX << name << '-' << (*it)->edition() << ": found already installed package (" << (*nameit)->edition() << ")" << std::endl;
102                             }
103                         }
104                         else
105                         {
106                             // the package will be uninstalled and du is empty, try to use du from another object
107                             du = pkg_from_source->diskusage();
108                             if (du.size() > 0)
109                             {
110                                 XXX << name << '-' << (*it)->edition() << ": using DiskUsage from another Package object (" << (*nameit)->edition() << ")" << std::endl;
111                                 break;
112                             }
113                         }
114                     }
115                     else
116                     {
117                         if (inst && nameit->status().isInstalled() && !nameit->status().isToBeUninstalled())
118                         {
119                             // just freshen the package, don't change du statistics
120                             found_installed = true;
121                             XXX << name << '-' << (*it)->edition() << ": found already installed package (" << (*nameit)->edition() << ")" << std::endl;
122                         }
123                         else if (pkg_from_source->diskusage().size() > 0)
124                         {
125                             // found different version of the package, remember the disk usage
126                             // it will be used the same version is not found
127                             du_another_package = pkg_from_source->diskusage();
128                             edition_another_package = (*nameit)->edition();
129                         }
130                     }
131                 }
132             }
133
134             // don't subtract the disk usage for updated package
135             if (du.size() == 0 && du_another_package.size() > 0 && !(rem && found_to_install))
136             {
137                 XXX << name << '-' << (*it)->edition() << ": using DU info from version " << edition_another_package << std::endl;
138                 du = du_another_package;
139             }
140         }
141
142         // don't modify du if the installed package is already installed (freshening)
143         if (du.size() > 0 && !(inst && found_installed))
144         {
145           // iterate trough all mount points, add usage to each directory
146           // directory tree must be processed from leaves to the root directory
147           // so iterate in reverse order so e.g. /usr is used before /
148           for (MountPointSet::reverse_iterator mpit = result.rbegin();
149             mpit != result.rend();
150             mpit++)
151           {
152             // get usage for the mount point
153             DiskUsage::Entry entry = du.extract(mpit->dir);
154
155             // add or subtract it to the current value
156             if (inst)
157             {
158                 mpit->pkg_size += entry._size;
159             }
160             else // the package will be uninstalled
161             {
162                 mpit->pkg_size -= entry._size;
163             }
164           }
165         }
166       }
167     }
168
169     return result;
170   }
171
172
173   DiskUsageCounter::MountPointSet DiskUsageCounter::detectMountPoints(const std::string &rootdir)
174   {
175     DiskUsageCounter::MountPointSet ret;
176
177       std::ifstream procmounts( "/proc/mounts" );
178
179       if ( !procmounts ) {
180         WAR << "Unable to read /proc/mounts" << std::endl;
181       } else {
182
183         std::string prfx;
184         if ( rootdir != "/" )
185           prfx = rootdir; // rootdir not /
186
187         while ( procmounts ) {
188           std::string l = str::getline( procmounts );
189           if ( !(procmounts.fail() || procmounts.bad()) ) {
190             // data to consume
191
192             // rootfs   /               rootfs          rw 0 0
193             // /dev/root        /               reiserfs        rw 0 0
194             // proc     /proc           proc            rw 0 0
195             // devpts   /dev/pts        devpts          rw 0 0
196             // /dev/hda5        /boot           ext2            rw 0 0
197             // shmfs    /dev/shm        shm             rw 0 0
198             // usbdevfs         /proc/bus/usb   usbdevfs        rw 0 0
199
200             std::vector<std::string> words;
201             str::split( l, std::back_inserter(words) );
202
203             if ( words.size() < 3 ) {
204               WAR << "Suspicious entry in /proc/mounts: " << l << std::endl;
205               continue;
206             }
207
208             //
209             // Filter devices without '/' (proc,shmfs,..)
210             //
211             if ( words[0].find( '/' ) == std::string::npos ) {
212               DBG << "Discard mount point : " << l << std::endl;
213               continue;
214             }
215
216             //
217             // Filter mountpoints not at or below _rootdir
218             //
219             std::string mp = words[1];
220             if ( prfx.size() ) {
221               if ( mp.compare( 0, prfx.size(), prfx ) != 0 ) {
222                 // mountpoint not below rootdir
223                 DBG << "Unwanted mount point : " << l << std::endl;
224                 continue;
225               }
226               // strip prfx
227               mp.erase( 0, prfx.size() );
228               if ( mp.empty() ) {
229                 mp = "/";
230               } else if ( mp[0] != '/' ) {
231                 // mountpoint not below rootdir
232                 DBG << "Unwanted mount point : " << l << std::endl;
233                 continue;
234               }
235             }
236
237             //
238             // Filter cdrom
239             //
240             if ( words[2] == "iso9660" ) {
241               DBG << "Discard cdrom : " << l << std::endl;
242               continue;
243             }
244
245             //
246             // Filter some common unwanted mountpoints
247             //
248             const char * mpunwanted[] = {
249               "/mnt", "/media", "/mounts", "/floppy", "/cdrom",
250               "/suse", "/var/tmp", "/var/adm/mount", "/var/adm/YaST",
251               /*last*/0/*entry*/
252             };
253
254             const char ** nomp = mpunwanted;
255             for ( ; *nomp; ++nomp ) {
256               std::string pre( *nomp );
257               if ( mp.compare( 0, pre.size(), pre ) == 0 // mp has prefix pre
258                    && ( mp.size() == pre.size() || mp[pre.size()] == '/' ) ) {
259                 break;
260               }
261             }
262             if ( *nomp ) {
263               DBG << "Filter mount point : " << l << std::endl;
264               continue;
265             }
266
267             //
268             // Check whether mounted readonly
269             //
270             bool ro = false;
271             std::vector<std::string> flags;
272             str::split( words[3], std::back_inserter(flags), "," );
273
274             for ( unsigned i = 0; i < flags.size(); ++i ) {
275               if ( flags[i] == "ro" ) {
276                 ro = true;
277                 break;
278               }
279             }
280             if ( ro ) {
281               DBG << "Filter ro mount point : " << l << std::endl;
282               continue;
283             }
284
285             //
286             // statvfs (full path!) and get the data
287             //
288             struct statvfs sb;
289             if ( statvfs( words[1].c_str(), &sb ) != 0 ) {
290               WAR << "Unable to statvfs(" << words[1] << "); errno " << errno << std::endl;
291               ret.insert( DiskUsageCounter::MountPoint( mp ) );
292             }
293             else
294             {
295               ret.insert( DiskUsageCounter::MountPoint( mp, sb.f_bsize,
296                 ((long long)sb.f_blocks)*sb.f_bsize/1024,
297                 ((long long)(sb.f_blocks - sb.f_bfree))*sb.f_bsize/1024, 0LL, ro ) );
298             }
299           }
300         }
301     }
302
303     return ret;
304   }
305
306   std::ostream & operator<<( std::ostream & str, const DiskUsageCounter::MountPoint & obj )
307   {
308     str << "dir:[" << obj.dir << "] [ bs: " << obj.block_size << " ts: " << obj.total_size << "]" << std::endl;
309     return str;
310   }
311
312 } // namespace zypp
313 ///////////////////////////////////////////////////////////////////