From: Michael Tremer Date: Mon, 3 Oct 2011 21:30:50 +0000 (+0200) Subject: Check disk space before doing some action. X-Git-Tag: 0.9.11~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8fe167108dc116f543c44a1756e8d4ac027df59e;p=pakfire.git Check disk space before doing some action. 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. --- diff --git a/python/pakfire/packages/file.py b/python/pakfire/packages/file.py index 6b5f22ea..43f40738 100644 --- a/python/pakfire/packages/file.py +++ b/python/pakfire/packages/file.py @@ -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 index 00000000..a5479b2b --- /dev/null +++ b/python/pakfire/system.py @@ -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 . # +# # +############################################################################### + +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 diff --git a/python/pakfire/transaction.py b/python/pakfire/transaction.py index 77e738e7..487e2795 100644 --- a/python/pakfire/transaction.py +++ b/python/pakfire/transaction.py @@ -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