]> git.ipfire.org Git - pakfire.git/commitdiff
Check disk space before doing some action.
authorMichael Tremer <michael.tremer@ipfire.org>
Mon, 3 Oct 2011 21:30:50 +0000 (23:30 +0200)
committerMichael Tremer <michael.tremer@ipfire.org>
Mon, 3 Oct 2011 21:30:50 +0000 (23:30 +0200)
Pakfire does now check if there is enough space to download
all package files and after that if there is enough space to
transform the transaction.

python/pakfire/packages/file.py
python/pakfire/system.py [new file with mode: 0644]
python/pakfire/transaction.py

index 6b5f22ea3ac52b9148b2cb8694a2057d43d70c5b..43f40738e7cbc93176a4c6f6760a09c6d03a0b8e 100644 (file)
@@ -357,6 +357,13 @@ class FilePackage(Package):
                                line = line.split()
                                name = line[0]
 
+                               # Parse the size information.
+                               try:
+                                       file.size = int(line[2])
+                               except ValueError:
+                                       print "PARSE ERROR", line[2]
+                                       file.size = 0
+
                                # XXX need to parse the rest of the information from the
                                # file
 
diff --git a/python/pakfire/system.py b/python/pakfire/system.py
new file mode 100644 (file)
index 0000000..a5479b2
--- /dev/null
@@ -0,0 +1,174 @@
+#!/usr/bin/python
+###############################################################################
+#                                                                             #
+# Pakfire - The IPFire package management system                              #
+# Copyright (C) 2011 Pakfire development team                                 #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+from __future__ import division
+
+import os
+
+class Mountpoints(object):
+       def __init__(self, pakfire, root="/"):
+               self.pakfire = pakfire
+
+               self._mountpoints = []
+
+               # Scan for all mountpoints on the system.
+               self._scan(root)
+
+       def __iter__(self):
+               return iter(self._mountpoints)
+
+       def _scan(self, root):
+               # Get the real path of root.
+               root = os.path.realpath(root)
+
+               # If root is not equal to /, we are in a chroot and
+               # our root must be a mountpoint to count files.
+               if not root == "/":
+                       mp = Mountpoint(self.pakfire, "/", root=root)
+                       self._mountpoints.append(mp)
+
+               f = open("/proc/mounts")
+
+               for line in f.readlines():
+                       line = line.split()
+
+                       # The mountpoint is the second argument.
+                       mountpoint = line[1]
+
+                       # Skip all mountpoints that are not in our root directory.
+                       if not mountpoint.startswith(root):
+                               continue
+
+                       mountpoint = os.path.relpath(mountpoint, root)
+                       if mountpoint == ".":
+                               mountpoint = "/"
+                       else:
+                               mountpoint = os.path.join("/", mountpoint)
+
+                       mp = Mountpoint(self.pakfire, mountpoint, root=root)
+
+                       if not mp in self._mountpoints:
+                               self._mountpoints.append(mp)
+
+               f.close()
+
+               # Sort all mountpoints for better searching.
+               self._mountpoints.sort()
+
+       def add_pkg(self, pkg):
+               for file in pkg.filelist:
+                       self.add(file)
+
+       def rem_pkg(self, pkg):
+               for file in pkg.filelist:
+                       self.rem(file)
+
+       def add(self, file):
+               for mp in reversed(self._mountpoints):
+                       # Check if the file is located on this mountpoint.
+                       if not file.name.startswith(mp.path):
+                               continue
+
+                       # Add file to this mountpoint.
+                       mp.add(file)
+                       break
+
+       def rem(self, file):
+               for mp in reversed(self._mountpoints):
+                       # Check if the file is located on this mountpoint.
+                       if not file.name.startswith(mp.path):
+                               continue
+
+                       # Remove file from this mountpoint.
+                       mp.rem(file)
+                       break
+
+
+class Mountpoint(object):
+       def __init__(self, pakfire, path, root="/"):
+               self.pakfire = pakfire
+               self.path = path
+               self.root = root
+
+               # Cache the statvfs call of the mountpoint.
+               self.__stat = None
+
+               # Save the amount of data that is used or freed.
+               self.disk_usage = 0
+
+       def __cmp__(self, other):
+               return cmp(self.fullpath, other.fullpath)
+
+       @property
+       def fullpath(self):
+               path = self.path
+               while path.startswith("/"):
+                       path = path[1:]
+
+               return os.path.join(self.root, path)
+
+       @property
+       def stat(self):
+               if self.__stat is None:
+                       # Find the next mountpoint, because we cannot
+                       # statvfs any path in the FS.
+                       path = os.path.realpath(self.fullpath)
+
+                       # Walk to root until we find a mountpoint.
+                       while not os.path.ismount(path):
+                               path = os.path.dirname(path)
+
+                       # See what we can get.
+                       self.__stat = os.statvfs(path)
+
+               return self.__stat
+
+       @property
+       def free(self):
+               return self.stat.f_bavail * self.stat.f_bsize
+
+       @property
+       def space_needed(self):
+               if self.disk_usage > 0:
+                       return self.disk_usage
+
+               return 0
+
+       @property
+       def space_left(self):
+               return self.free - self.space_needed
+
+       def add(self, file):
+               assert file.name.startswith(self.path)
+
+               # Round filesize to 4k blocks.
+               block_size = 4096
+
+               blocks = file.size // block_size
+               if file.size % block_size:
+                       blocks += 1
+
+               self.disk_usage += blocks * block_size
+
+       def rem(self, file):
+               assert file.name.startswith(self.path)
+
+               self.disk_usage += file.size
index 77e738e73e20fc0b3ea3ba6412f979b78d4bf52a..487e2795e3f03e4102b1878f5a81668f48a4c731 100644 (file)
@@ -28,6 +28,7 @@ import time
 import i18n
 import packages
 import satsolver
+import system
 import util
 
 from constants import *
@@ -49,6 +50,9 @@ class TransactionCheck(object):
                # Get a list of all installed files from the database.
                self.filelist = self.load_filelist()
 
+               # Get information about the mounted filesystems.
+               self.mountpoints = system.Mountpoints(self.pakfire, root=self.pakfire.path)
+
        @property
        def error_files(self):
                ret = {}
@@ -63,7 +67,15 @@ class TransactionCheck(object):
 
        @property
        def successful(self):
-               return not self.error_files
+               if self.error_files:
+                       return False
+
+               # Check if all mountpoints have enough space left.
+               for mp in self.mountpoints:
+                       if mp.space_left < 0:
+                               return False
+
+               return True
 
        def print_errors(self):
                for name, files in sorted(self.error_files.items()):
@@ -83,6 +95,14 @@ class TransactionCheck(object):
                                                (name, pkgs[0], i18n.list(pkgs[1:]))
                                )
 
+               for mp in self.mountpoints:
+                       if mp.space_left >= 0:
+                               continue
+
+                       print util.format_size(mp.free), util.format_size(mp.disk_usage)
+                       logging.critical(_("There is not enough space left on %(name)s. Need at least %(size)s to perform transaction.") \
+                               % { "name" : mp.path, "size" : util.format_size(mp.space_needed) })
+
        def load_filelist(self):
                filelist = {}
 
@@ -102,6 +122,9 @@ class TransactionCheck(object):
                        else:
                                self.filelist[file.name] = [file,]
 
+               # Add all filesize data to mountpoints.
+               self.mountpoints.add_pkg(pkg)
+
        def remove(self, pkg):
                for file in pkg.filelist:
                        if file.is_dir():
@@ -116,6 +139,9 @@ class TransactionCheck(object):
 
                                self.filelist[file.name].remove(f)
 
+               # Remove all filesize data from mountpoints.
+               self.mountpoints.rem_pkg(pkg)
+
        def update(self, pkg):
                self.install(pkg)
 
@@ -211,14 +237,24 @@ class Transaction(object):
                if not downloads:
                        return
 
-               logging.info(_("Downloading packages:"))
-               time_start = time.time()
-
                # Calculate downloadsize.
                download_size = 0
                for action in downloads:
                        download_size += action.pkg.size
 
+               # Get free space of the download location.
+               path = os.path.realpath(REPO_CACHE_DIR)
+               while not os.path.ismount(path):
+                       path = os.path.dirname(path)
+               path_stat = os.statvfs(path)
+
+               if download_size >= path_stat.f_bavail * path_stat.f_bsize:
+                       raise DownloadError, _("Not enough space to download %s of packages.") \
+                               % util.format_size(download_size)
+
+               logging.info(_("Downloading packages:"))
+               time_start = time.time()
+
                i = 0
                for action in downloads:
                        i += 1