~ubuntu-core-dev/ubuntu-release-upgrader/trunk

« back to all changes in this revision

Viewing changes to DistUpgrade/DistUpgradeCache.py

  • Committer: Balint Reczey
  • Date: 2019-12-17 20:29:55 UTC
  • Revision ID: balint.reczey@canonical.com-20191217202955-nqe4xz2c54s60y59
Moved to git at https://git.launchpad.net/ubuntu-release-upgrader

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# DistUpgradeCache.py
2
 
#
3
 
#  Copyright (c) 2004-2008 Canonical
4
 
#
5
 
#  Author: Michael Vogt <michael.vogt@ubuntu.com>
6
 
#
7
 
#  This program is free software; you can redistribute it and/or
8
 
#  modify it under the terms of the GNU General Public License as
9
 
#  published by the Free Software Foundation; either version 2 of the
10
 
#  License, or (at your option) any later version.
11
 
#
12
 
#  This program is distributed in the hope that it will be useful,
13
 
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
#  GNU General Public License for more details.
16
 
#
17
 
#  You should have received a copy of the GNU General Public License
18
 
#  along with this program; if not, write to the Free Software
19
 
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20
 
#  USA
21
 
 
22
 
import apt
23
 
import apt_pkg
24
 
import glob
25
 
import locale
26
 
import os
27
 
import re
28
 
import logging
29
 
import time
30
 
import datetime
31
 
import threading
32
 
import configparser
33
 
from subprocess import Popen, PIPE
34
 
 
35
 
from .DistUpgradeGettext import gettext as _
36
 
from .DistUpgradeGettext import ngettext
37
 
 
38
 
from .utils import inside_chroot
39
 
 
40
 
class CacheException(Exception):
41
 
    pass
42
 
 
43
 
 
44
 
class CacheExceptionLockingFailed(CacheException):
45
 
    pass
46
 
 
47
 
 
48
 
class CacheExceptionDpkgInterrupted(CacheException):
49
 
    pass
50
 
 
51
 
 
52
 
def estimate_kernel_initrd_size_in_boot():
53
 
    """estimate the amount of space used by the kernel and initramfs in /boot,
54
 
    including a safety margin
55
 
    """
56
 
    kernel = 0
57
 
    initrd = 0
58
 
    kver = os.uname()[2]
59
 
    for f in glob.glob("/boot/*%s*" % kver):
60
 
        if f == '/boot/initrd.img-%s' % kver:
61
 
            initrd += os.path.getsize(f)
62
 
        # don't include in the estimate any files that are left behind by
63
 
        # an interrupted package manager run
64
 
        elif (f.find('initrd.img') >= 0 or f.find('.bak') >= 0
65
 
              or f.find('.dpkg-') >= 0):
66
 
            continue
67
 
        else:
68
 
            kernel += os.path.getsize(f)
69
 
    if kernel == 0:
70
 
        logging.warning(
71
 
            "estimate_kernel_initrd_size_in_boot() returned '0' for kernel?")
72
 
        kernel = 28*1024*1024
73
 
    if initrd == 0:
74
 
        logging.warning(
75
 
            "estimate_kernel_initrd_size_in_boot() returned '0' for initrd?")
76
 
        initrd = 45*1024*1024
77
 
    # add small safety buffer
78
 
    kernel += 1*1024*1024
79
 
    # safety buffer as a percentage of the existing initrd's size
80
 
    initrd_buffer = 1*1024*1024
81
 
    if initrd * 0.05 > initrd_buffer:
82
 
        initrd_buffer = initrd * 0.05
83
 
    initrd += initrd_buffer
84
 
    return kernel,initrd
85
 
KERNEL_SIZE, INITRD_SIZE = estimate_kernel_initrd_size_in_boot()
86
 
 
87
 
 
88
 
class FreeSpaceRequired(object):
89
 
    """ FreeSpaceRequired object:
90
 
 
91
 
    This exposes:
92
 
    - the total size required (size_total)
93
 
    - the dir that requires the space (dir)
94
 
    - the additional space that is needed (size_needed)
95
 
    """
96
 
    def __init__(self, size_total, dir, size_needed):
97
 
        self.size_total = size_total
98
 
        self.dir = dir
99
 
        self.size_needed = size_needed
100
 
    def __str__(self):
101
 
        return "FreeSpaceRequired Object: Dir: %s size_total: %s size_needed: %s" % (self.dir, self.size_total, self.size_needed)
102
 
 
103
 
 
104
 
class NotEnoughFreeSpaceError(CacheException):
105
 
    """
106
 
    Exception if there is not enough free space for this operation
107
 
 
108
 
    """
109
 
    def __init__(self, free_space_required_list):
110
 
        self.free_space_required_list = free_space_required_list
111
 
 
112
 
 
113
 
class MyCache(apt.Cache):
114
 
    ReInstReq = 1
115
 
    HoldReInstReq = 3
116
 
 
117
 
    # init
118
 
    def __init__(self, config, view, quirks, progress=None, lock=True):
119
 
        self.to_install = []
120
 
        self.to_remove = []
121
 
        self.view = view
122
 
        self.quirks = quirks
123
 
        self.lock = False
124
 
        self.partialUpgrade = False
125
 
        self.config = config
126
 
        self.metapkgs = self.config.getlist("Distro", "MetaPkgs")
127
 
        # acquire lock
128
 
        self._listsLock = -1
129
 
        if lock:
130
 
            try:
131
 
                apt_pkg.pkgsystem_lock()
132
 
                self.lock_lists_dir()
133
 
                self.lock = True
134
 
            except SystemError as e:
135
 
                # checking for this is ok, its not translatable
136
 
                if "dpkg --configure -a" in str(e):
137
 
                    raise CacheExceptionDpkgInterrupted(e)
138
 
                raise CacheExceptionLockingFailed(e)
139
 
        # Do not create the cache until we know it is not locked
140
 
        apt.Cache.__init__(self, progress)
141
 
        # a list of regexp that are not allowed to be removed
142
 
        self.removal_blacklist = config.getListFromFile("Distro", "RemovalBlacklistFile")
143
 
        self.uname = Popen(["uname", "-r"], stdout=PIPE,
144
 
                           universal_newlines=True).communicate()[0].strip()
145
 
        self._initAptLog()
146
 
        # from hardy on we use recommends by default, so for the
147
 
        # transition to the new dist we need to enable them now
148
 
        if (config.get("Sources", "From") == "hardy" and
149
 
            not "RELEASE_UPGRADE_NO_RECOMMENDS" in os.environ):
150
 
            apt_pkg.config.set("APT::Install-Recommends", "true")
151
 
 
152
 
 
153
 
        apt_pkg.config.set("APT::AutoRemove::SuggestsImportant", "false")
154
 
 
155
 
    def _apply_dselect_upgrade(self):
156
 
        """ honor the dselect install state """
157
 
        for pkg in self:
158
 
            if pkg.is_installed:
159
 
                continue
160
 
            if pkg._pkg.selected_state == apt_pkg.SELSTATE_INSTALL:
161
 
                # upgrade() will take care of this
162
 
                pkg.mark_install(auto_inst=False, auto_fix=False)
163
 
 
164
 
    @property
165
 
    def req_reinstall_pkgs(self):
166
 
        " return the packages not downloadable packages in reqreinst state "
167
 
        reqreinst = set()
168
 
        for pkg in self:
169
 
            if ((not pkg.candidate or not pkg.candidate.downloadable)
170
 
                and
171
 
                (pkg._pkg.inst_state == self.ReInstReq or
172
 
                 pkg._pkg.inst_state == self.HoldReInstReq)):
173
 
                reqreinst.add(pkg.name)
174
 
        return reqreinst
175
 
 
176
 
    def fix_req_reinst(self, view):
177
 
        " check for reqreinst state and offer to fix it "
178
 
        reqreinst = self.req_reinstall_pkgs
179
 
        if len(reqreinst) > 0:
180
 
            header = ngettext("Remove package in bad state",
181
 
                              "Remove packages in bad state",
182
 
                              len(reqreinst))
183
 
            summary = ngettext("The package '%s' is in an inconsistent "
184
 
                               "state and needs to be reinstalled, but "
185
 
                               "no archive can be found for it. "
186
 
                               "Do you want to remove this package "
187
 
                               "now to continue?",
188
 
                               "The packages '%s' are in an inconsistent "
189
 
                               "state and need to be reinstalled, but "
190
 
                               "no archives can be found for them. Do you "
191
 
                               "want to remove these packages now to "
192
 
                               "continue?",
193
 
                               len(reqreinst)) % ", ".join(reqreinst)
194
 
            if view.askYesNoQuestion(header, summary):
195
 
                self.release_lock()
196
 
                cmd = ["/usr/bin/dpkg", "--remove", "--force-remove-reinstreq"] + list(reqreinst)
197
 
                view.getTerminal().call(cmd)
198
 
                self.get_lock()
199
 
                return True
200
 
        return False
201
 
 
202
 
    # logging stuff
203
 
    def _initAptLog(self):
204
 
        " init logging, create log file"
205
 
        logdir = self.config.getWithDefault("Files", "LogDir",
206
 
                                            "/var/log/dist-upgrade")
207
 
        if not os.path.exists(logdir):
208
 
            os.makedirs(logdir)
209
 
        apt_pkg.config.set("Dir::Log", logdir)
210
 
        apt_pkg.config.set("Dir::Log::Terminal", "apt-term.log")
211
 
        self.logfd = os.open(os.path.join(logdir, "apt.log"),
212
 
                             os.O_RDWR | os.O_CREAT | os.O_APPEND, 0o644)
213
 
        now = datetime.datetime.now()
214
 
        header = "Log time: %s\n" % now
215
 
        os.write(self.logfd, header.encode("utf-8"))
216
 
 
217
 
        # turn on debugging in the cache
218
 
        apt_pkg.config.set("Debug::pkgProblemResolver", "true")
219
 
        apt_pkg.config.set("Debug::pkgDepCache::Marker", "true")
220
 
        apt_pkg.config.set("Debug::pkgDepCache::AutoInstall", "true")
221
 
    def _startAptResolverLog(self):
222
 
        if hasattr(self, "old_stdout"):
223
 
            os.close(self.old_stdout)
224
 
            os.close(self.old_stderr)
225
 
        self.old_stdout = os.dup(1)
226
 
        self.old_stderr = os.dup(2)
227
 
        os.dup2(self.logfd, 1)
228
 
        os.dup2(self.logfd, 2)
229
 
    def _stopAptResolverLog(self):
230
 
        os.fsync(1)
231
 
        os.fsync(2)
232
 
        os.dup2(self.old_stdout, 1)
233
 
        os.dup2(self.old_stderr, 2)
234
 
    # use this decorator instead of the _start/_stop stuff directly
235
 
    # FIXME: this should probably be a decorator class where all
236
 
    #        logging is moved into?
237
 
    def withResolverLog(f):
238
 
        " decorator to ensure that the apt output is logged "
239
 
        def wrapper(*args, **kwargs):
240
 
            args[0]._startAptResolverLog()
241
 
            res = f(*args, **kwargs)
242
 
            args[0]._stopAptResolverLog()
243
 
            return res
244
 
        return wrapper
245
 
 
246
 
    # properties
247
 
    @property
248
 
    def required_download(self):
249
 
        """ get the size of the packages that are required to download """
250
 
        pm = apt_pkg.PackageManager(self._depcache)
251
 
        fetcher = apt_pkg.Acquire()
252
 
        pm.get_archives(fetcher, self._list, self._records)
253
 
        return fetcher.fetch_needed
254
 
    @property
255
 
    def additional_required_space(self):
256
 
        """ get the size of the additional required space on the fs """
257
 
        return self._depcache.usr_size
258
 
    @property
259
 
    def additional_required_space_for_snaps(self):
260
 
        """ get the extra size needed to install the snap replacements """
261
 
        try:
262
 
            # update-manager uses DistUpgradeCache.MyCache as the base class
263
 
            # of its own MyCache version - but without actually calling our
264
 
            # constructor at all. This causes that the MyCache version from
265
 
            # update-manager has no self.quirks attribute while still calling
266
 
            # our default version of checkFreeSpace(). Since extra_snap_space
267
 
            # is only used on dist-upgrades, let's just not care and return 0
268
 
            # in this weird, undocumented case.
269
 
            return self.quirks.extra_snap_space
270
 
        except AttributeError:
271
 
            return 0
272
 
    @property
273
 
    def is_broken(self):
274
 
        """ is the cache broken """
275
 
        return self._depcache.broken_count > 0
276
 
 
277
 
    # methods
278
 
    def lock_lists_dir(self):
279
 
        name = apt_pkg.config.find_dir("Dir::State::Lists") + "lock"
280
 
        self._listsLock = apt_pkg.get_lock(name)
281
 
        if self._listsLock < 0:
282
 
            e = "Can not lock '%s' " % name
283
 
            raise CacheExceptionLockingFailed(e)
284
 
    def unlock_lists_dir(self):
285
 
        if self._listsLock > 0:
286
 
            os.close(self._listsLock)
287
 
            self._listsLock = -1
288
 
    def update(self, fprogress=None):
289
 
        """
290
 
        our own update implementation is required because we keep the lists
291
 
        dir lock
292
 
        """
293
 
        self.unlock_lists_dir()
294
 
        res = apt.Cache.update(self, fprogress)
295
 
        self.lock_lists_dir()
296
 
        if fprogress and fprogress.release_file_download_error:
297
 
            # FIXME: not ideal error message, but we just reuse a
298
 
            #        existing one here to avoid a new string
299
 
            raise IOError(_("The server may be overloaded"))
300
 
        if res == False:
301
 
            raise IOError("apt.cache.update() returned False, but did not raise exception?!?")
302
 
 
303
 
    def commit(self, fprogress, iprogress):
304
 
        logging.info("cache.commit()")
305
 
        if self.lock:
306
 
            self.release_lock()
307
 
        apt.Cache.commit(self, fprogress, iprogress)
308
 
 
309
 
    def release_lock(self, pkgSystemOnly=True):
310
 
        if self.lock:
311
 
            try:
312
 
                apt_pkg.pkgsystem_unlock()
313
 
                self.lock = False
314
 
            except SystemError as e:
315
 
                logging.debug("failed to SystemUnLock() (%s) " % e)
316
 
 
317
 
    def get_lock(self, pkgSystemOnly=True):
318
 
        if not self.lock:
319
 
            try:
320
 
                apt_pkg.pkgsystem_lock()
321
 
                self.lock = True
322
 
            except SystemError as e:
323
 
                logging.debug("failed to SystemLock() (%s) " % e)
324
 
 
325
 
    def downloadable(self, pkg, useCandidate=True):
326
 
        " check if the given pkg can be downloaded "
327
 
        if useCandidate:
328
 
            ver = self._depcache.get_candidate_ver(pkg._pkg)
329
 
        else:
330
 
            ver = pkg._pkg.current_ver
331
 
        if ver == None:
332
 
            logging.warning("no version information for '%s' (useCandidate=%s)" % (pkg.name, useCandidate))
333
 
            return False
334
 
        return ver.downloadable
335
 
 
336
 
    def pkg_auto_removable(self, pkg):
337
 
        """ check if the pkg is auto-removable """
338
 
        return (pkg.is_installed and
339
 
                self._depcache.is_garbage(pkg._pkg))
340
 
 
341
 
    def fix_broken(self):
342
 
        """ try to fix broken dependencies on the system, may throw
343
 
            SystemError when it can't"""
344
 
        return self._depcache.fix_broken()
345
 
 
346
 
    def create_snapshot(self):
347
 
        """ create a snapshot of the current changes """
348
 
        self.to_install = []
349
 
        self.to_remove = []
350
 
        for pkg in self.get_changes():
351
 
            if pkg.marked_install or pkg.marked_upgrade:
352
 
                self.to_install.append(pkg.name)
353
 
            if pkg.marked_delete:
354
 
                self.to_remove.append(pkg.name)
355
 
 
356
 
    def clear(self):
357
 
        self._depcache.init()
358
 
 
359
 
    def restore_snapshot(self):
360
 
        """ restore a snapshot """
361
 
        actiongroup = apt_pkg.ActionGroup(self._depcache)
362
 
        # just make pyflakes shut up, later we need to use
363
 
        # with self.actiongroup():
364
 
        actiongroup
365
 
        self.clear()
366
 
        for name in self.to_remove:
367
 
            pkg = self[name]
368
 
            pkg.mark_delete()
369
 
        for name in self.to_install:
370
 
            pkg = self[name]
371
 
            pkg.mark_install(auto_fix=False, auto_inst=False)
372
 
 
373
 
    def need_server_mode(self):
374
 
        """
375
 
        This checks if we run on a desktop or a server install.
376
 
 
377
 
        A server install has more freedoms, for a desktop install
378
 
        we force a desktop meta package to be install on the upgrade.
379
 
 
380
 
        We look for a installed desktop meta pkg and for key
381
 
        dependencies, if none of those are installed we assume
382
 
        server mode
383
 
        """
384
 
        #logging.debug("need_server_mode() run")
385
 
        # check for the MetaPkgs (e.g. ubuntu-desktop)
386
 
        metapkgs = self.config.getlist("Distro", "MetaPkgs")
387
 
        for key in metapkgs:
388
 
            # if it is installed we are done
389
 
            if key in self and self[key].is_installed:
390
 
                logging.debug("need_server_mode(): run in 'desktop' mode, (because of pkg '%s')" % key)
391
 
                return False
392
 
            # if it is not installed, but its key depends are installed
393
 
            # we are done too (we auto-select the package later)
394
 
            deps_found = True
395
 
            for pkg in self.config.getlist(key, "KeyDependencies"):
396
 
                deps_found &= pkg in self and self[pkg].is_installed
397
 
            if deps_found:
398
 
                logging.debug("need_server_mode(): run in 'desktop' mode, (because of key deps for '%s')" % key)
399
 
                return False
400
 
        logging.debug("need_server_mode(): can not find a desktop meta package or key deps, running in server mode")
401
 
        return True
402
 
 
403
 
    def sanity_check(self, view):
404
 
        """ check if the cache is ok and if the required metapkgs
405
 
            are installed
406
 
        """
407
 
        if self.is_broken:
408
 
            try:
409
 
                logging.debug("Have broken pkgs, trying to fix them")
410
 
                self.fix_broken()
411
 
            except SystemError:
412
 
                view.error(_("Broken packages"),
413
 
                                 _("Your system contains broken packages "
414
 
                                   "that couldn't be fixed with this "
415
 
                                   "software. "
416
 
                                   "Please fix them first using synaptic or "
417
 
                                   "apt-get before proceeding."))
418
 
                return False
419
 
        return True
420
 
 
421
 
    def mark_install(self, pkg, reason=""):
422
 
        logging.debug("Installing '%s' (%s)" % (pkg, reason))
423
 
        if pkg in self:
424
 
            self[pkg].mark_install()
425
 
            if not (self[pkg].marked_install or self[pkg].marked_upgrade):
426
 
                logging.error("Installing/upgrading '%s' failed" % pkg)
427
 
                #raise SystemError("Installing '%s' failed" % pkg)
428
 
                return False
429
 
        return True
430
 
 
431
 
    def mark_upgrade(self, pkg, reason=""):
432
 
        logging.debug("Upgrading '%s' (%s)" % (pkg, reason))
433
 
        if pkg in self and self[pkg].is_installed:
434
 
            self[pkg].mark_upgrade()
435
 
            if not self[pkg].marked_upgrade:
436
 
                logging.error("Upgrading '%s' failed" % pkg)
437
 
                return False
438
 
        return True
439
 
 
440
 
    def mark_remove(self, pkg, reason=""):
441
 
        logging.debug("Removing '%s' (%s)" % (pkg, reason))
442
 
        if pkg in self:
443
 
            self[pkg].mark_delete()
444
 
 
445
 
    def mark_purge(self, pkg, reason=""):
446
 
        logging.debug("Purging '%s' (%s)" % (pkg, reason))
447
 
        if pkg in self:
448
 
            self._depcache.mark_delete(self[pkg]._pkg, True)
449
 
 
450
 
    def _keep_installed(self, pkgname, reason):
451
 
        if (pkgname in self
452
 
            and self[pkgname].is_installed
453
 
            and self[pkgname].marked_delete):
454
 
            self.mark_install(pkgname, reason)
455
 
 
456
 
    def keep_installed_rule(self):
457
 
        """ run after the dist-upgrade to ensure that certain
458
 
            packages are kept installed """
459
 
        # first the global list
460
 
        for pkgname in self.config.getlist("Distro", "KeepInstalledPkgs"):
461
 
            self._keep_installed(pkgname, "Distro KeepInstalledPkgs rule")
462
 
        # the the per-metapkg rules
463
 
        for key in self.metapkgs:
464
 
            if key in self and (self[key].is_installed or
465
 
                                self[key].marked_install):
466
 
                for pkgname in self.config.getlist(key, "KeepInstalledPkgs"):
467
 
                    self._keep_installed(pkgname, "%s KeepInstalledPkgs rule" % key)
468
 
 
469
 
        # only enforce section if we have a network. Otherwise we run
470
 
        # into CD upgrade issues for installed language packs etc
471
 
        if self.config.get("Options", "withNetwork") == "True":
472
 
            logging.debug("Running KeepInstalledSection rules")
473
 
            # now the KeepInstalledSection code
474
 
            for section in self.config.getlist("Distro", "KeepInstalledSection"):
475
 
                for pkg in self:
476
 
                    if (pkg.candidate and pkg.candidate.downloadable
477
 
                        and pkg.marked_delete
478
 
                        and pkg.candidate.section == section):
479
 
                        self._keep_installed(pkg.name, "Distro KeepInstalledSection rule: %s" % section)
480
 
            for key in self.metapkgs:
481
 
                if key in self and (self[key].is_installed or
482
 
                                    self[key].marked_install):
483
 
                    for section in self.config.getlist(key, "KeepInstalledSection"):
484
 
                        for pkg in self:
485
 
                            if (pkg.candidate and pkg.candidate.downloadable
486
 
                                and pkg.marked_delete and
487
 
                                pkg.candidate.section == section):
488
 
                                self._keep_installed(pkg.name, "%s KeepInstalledSection rule: %s" % (key, section))
489
 
 
490
 
 
491
 
    def post_upgrade_rule(self):
492
 
        " run after the upgrade was done in the cache "
493
 
        for (rule, action) in [("Install", self.mark_install),
494
 
                               ("Upgrade", self.mark_upgrade),
495
 
                               ("Remove", self.mark_remove),
496
 
                               ("Purge", self.mark_purge)]:
497
 
            # first the global list
498
 
            for pkg in self.config.getlist("Distro", "PostUpgrade%s" % rule):
499
 
                action(pkg, "Distro PostUpgrade%s rule" % rule)
500
 
            for key in self.metapkgs:
501
 
                if key in self and (self[key].is_installed or
502
 
                                    self[key].marked_install):
503
 
                    for pkg in self.config.getlist(key, "PostUpgrade%s" % rule):
504
 
                        action(pkg, "%s PostUpgrade%s rule" % (key, rule))
505
 
        # run the quirks handlers
506
 
        if not self.partialUpgrade:
507
 
            self.quirks.run("PostDistUpgradeCache")
508
 
 
509
 
    def checkForNvidia(self):
510
 
        """
511
 
        this checks for nvidia hardware and checks what driver is needed
512
 
        """
513
 
        logging.debug("nvidiaUpdate()")
514
 
        # if the free drivers would give us a equally hard time, we would
515
 
        # never be able to release
516
 
        try:
517
 
            from NvidiaDetector.nvidiadetector import NvidiaDetection
518
 
        except (ImportError, SyntaxError) as e:
519
 
            # SyntaxError is temporary until the port of NvidiaDetector to
520
 
            # Python 3 is in the archive.
521
 
            logging.error("NvidiaDetector can not be imported %s" % e)
522
 
            return False
523
 
        try:
524
 
            # get new detection module and use the modalises files
525
 
            # from within the release-upgrader
526
 
            nv = NvidiaDetection(obsolete="./ubuntu-drivers-obsolete.pkgs")
527
 
            #nv = NvidiaDetection()
528
 
            # check if a binary driver is installed now
529
 
            for oldDriver in nv.oldPackages:
530
 
                if oldDriver in self and self[oldDriver].is_installed:
531
 
                    self.mark_remove(oldDriver, "old nvidia driver")
532
 
                    break
533
 
            else:
534
 
                logging.info("no old nvidia driver installed, installing no new")
535
 
                return False
536
 
            # check which one to use
537
 
            driver = nv.selectDriver()
538
 
            logging.debug("nv.selectDriver() returned '%s'" % driver)
539
 
            if not driver in self:
540
 
                logging.warning("no '%s' found" % driver)
541
 
                return False
542
 
            if not (self[driver].marked_install or self[driver].marked_upgrade):
543
 
                self[driver].mark_install()
544
 
                logging.info("installing %s as suggested by NvidiaDetector" % driver)
545
 
                return True
546
 
        except Exception as e:
547
 
            logging.error("NvidiaDetection returned a error: %s" % e)
548
 
        return False
549
 
 
550
 
 
551
 
    def _has_kernel_headers_installed(self):
552
 
        for pkg in self:
553
 
            if (pkg.name.startswith("linux-headers-") and
554
 
                pkg.is_installed):
555
 
                return True
556
 
        return False
557
 
 
558
 
    def checkForKernel(self):
559
 
        """ check for the running kernel and try to ensure that we have
560
 
            an updated version
561
 
        """
562
 
        logging.debug("Kernel uname: '%s' " % self.uname)
563
 
        try:
564
 
            (version, build, flavour) = self.uname.split("-")
565
 
        except Exception as e:
566
 
            logging.warning("Can't parse kernel uname: '%s' (self compiled?)" % e)
567
 
            return False
568
 
        # now check if we have a SMP system
569
 
        dmesg = Popen(["dmesg"], stdout=PIPE).communicate()[0]
570
 
        if b"WARNING: NR_CPUS limit" in dmesg:
571
 
            logging.debug("UP kernel on SMP system!?!")
572
 
        return True
573
 
 
574
 
    def checkPriority(self):
575
 
        # tuple of priorities we require to be installed
576
 
        need = ('required', )
577
 
        # stuff that its ok not to have
578
 
        removeEssentialOk = self.config.getlist("Distro", "RemoveEssentialOk")
579
 
        # check now
580
 
        for pkg in self:
581
 
            # WORKAROUND bug on the CD/python-apt #253255
582
 
            ver = pkg._pcache._depcache.get_candidate_ver(pkg._pkg)
583
 
            if ver and ver.priority == 0:
584
 
                logging.error("Package %s has no priority set" % pkg.name)
585
 
                continue
586
 
            if (pkg.candidate and pkg.candidate.downloadable and
587
 
                not (pkg.is_installed or pkg.marked_install) and
588
 
                not pkg.name in removeEssentialOk and
589
 
                # ignore multiarch priority required packages
590
 
                not ":" in pkg.name and
591
 
                pkg.candidate.priority in need):
592
 
                self.mark_install(pkg.name, "priority in required set '%s' but not scheduled for install" % need)
593
 
 
594
 
    # FIXME: make this a decorator (just like the withResolverLog())
595
 
    def updateGUI(self, view, lock):
596
 
        i = 0
597
 
        while lock.locked():
598
 
            if i % 15 == 0:
599
 
                view.pulseProgress()
600
 
            view.processEvents()
601
 
            time.sleep(0.02)
602
 
            i += 1
603
 
        view.pulseProgress(finished=True)
604
 
        view.processEvents()
605
 
 
606
 
    @withResolverLog
607
 
    def distUpgrade(self, view, serverMode, partialUpgrade):
608
 
        # keep the GUI alive
609
 
        lock = threading.Lock()
610
 
        lock.acquire()
611
 
        t = threading.Thread(target=self.updateGUI, args=(self.view, lock,))
612
 
        t.start()
613
 
        try:
614
 
            # mvo: disabled as it casues to many errornous installs
615
 
            #self._apply_dselect_upgrade()
616
 
 
617
 
            # upgrade (and make sure this way that the cache is ok)
618
 
            self.upgrade(True)
619
 
 
620
 
            # check that everything in priority required is installed
621
 
            self.checkPriority()
622
 
 
623
 
            # see if our KeepInstalled rules are honored
624
 
            self.keep_installed_rule()
625
 
 
626
 
            # check if we got a new kernel (if we are not inside a
627
 
            # chroot)
628
 
            if inside_chroot():
629
 
                logging.warning("skipping kernel checks because we run inside a chroot")
630
 
            else:
631
 
                self.checkForKernel()
632
 
 
633
 
            # check for nvidia stuff
634
 
            self.checkForNvidia()
635
 
 
636
 
            # and if we have some special rules
637
 
            self.post_upgrade_rule()
638
 
 
639
 
            # install missing meta-packages (if not in server upgrade mode)
640
 
            self._keepBaseMetaPkgsInstalled(view)
641
 
            if not serverMode:
642
 
                # if this fails, a system error is raised
643
 
                self._installMetaPkgs(view)
644
 
 
645
 
            # see if it all makes sense, if not this function raises
646
 
            self._verifyChanges()
647
 
 
648
 
        except SystemError as e:
649
 
            # this should go into a finally: line, see below for the
650
 
            # rationale why it doesn't
651
 
            lock.release()
652
 
            t.join()
653
 
            # the most likely problem is the 3rd party pkgs so don't address
654
 
            # foreignPkgs and devRelease being True
655
 
            details =  _("An unresolvable problem occurred while "
656
 
                         "calculating the upgrade.\n\n ")
657
 
            if self.config.get("Options", "foreignPkgs") == "True":
658
 
                details += _("This was likely caused by:\n"
659
 
                             " * Unofficial software packages not provided by Ubuntu\n"
660
 
                             "Please use the tool 'ppa-purge' from the ppa-purge \n"
661
 
                             "package to remove software from a Launchpad PPA and \n"
662
 
                             "try the upgrade again.\n"
663
 
                             "\n")
664
 
            elif self.config.get("Options", "foreignPkgs") == "False" and \
665
 
                self.config.get("Options", "devRelease") == "True":
666
 
                details +=  _("This was caused by:\n"
667
 
                              " * Upgrading to a pre-release version of Ubuntu\n"
668
 
                              "This is most likely a transient problem, \n"
669
 
                              "please try again later.\n")
670
 
            # we never have partialUpgrades (including removes) on a stable system
671
 
            # with only ubuntu sources so we do not recommend reporting a bug
672
 
            if partialUpgrade:
673
 
                details += _("This is most likely a transient problem, "
674
 
                             "please try again later.")
675
 
            else:
676
 
                details += _("If none of this applies, then please report this bug using "
677
 
                             "the command 'ubuntu-bug ubuntu-release-upgrader-core' in a terminal. ")
678
 
                details += _("If you want to investigate this yourself the log files in "
679
 
                             "'/var/log/dist-upgrade' will contain details about the upgrade. "
680
 
                             "Specifically, look at 'main.log' and 'apt.log'.")
681
 
            # make the error text available again on stdout for the
682
 
            # text frontend
683
 
            self._stopAptResolverLog()
684
 
            view.error(_("Could not calculate the upgrade"), details)
685
 
            # may contain utf-8 (LP: #1310053)
686
 
            error_msg = str(e)
687
 
            logging.error("Dist-upgrade failed: '%s'", error_msg)
688
 
            # start the resolver log again because this is run with
689
 
            # the withResolverLog decorator
690
 
            self._startAptResolverLog()
691
 
            return False
692
 
        # would be nice to be able to use finally: here, but we need
693
 
        # to run on python2.4 too
694
 
        #finally:
695
 
        # wait for the gui-update thread to exit
696
 
        lock.release()
697
 
        t.join()
698
 
 
699
 
        # check the trust of the packages that are going to change
700
 
        untrusted = []
701
 
        downgrade = []
702
 
        for pkg in self.get_changes():
703
 
            if pkg.marked_delete:
704
 
                continue
705
 
            # special case because of a bug in pkg.candidate.origins
706
 
            if pkg.marked_downgrade:
707
 
                downgrade.append(pkg.name)
708
 
                for ver in pkg._pkg.version_list:
709
 
                    # version is lower than installed one
710
 
                    if apt_pkg.version_compare(
711
 
                        ver.ver_str, pkg.installed.version) < 0:
712
 
                        for (verFileIter, index) in ver.file_list:
713
 
                            indexfile = pkg._pcache._list.find_index(verFileIter)
714
 
                            if indexfile and not indexfile.is_trusted:
715
 
                                untrusted.append(pkg.name)
716
 
                                break
717
 
                continue
718
 
            origins = pkg.candidate.origins
719
 
            trusted = False
720
 
            for origin in origins:
721
 
                #print(origin)
722
 
                trusted |= origin.trusted
723
 
            if not trusted:
724
 
                untrusted.append(pkg.name)
725
 
        # check if the user overwrote the unauthenticated warning
726
 
        try:
727
 
            b = self.config.getboolean("Distro", "AllowUnauthenticated")
728
 
            if b:
729
 
                logging.warning("AllowUnauthenticated set!")
730
 
                return True
731
 
        except configparser.NoOptionError:
732
 
            pass
733
 
        if len(downgrade) > 0:
734
 
            downgrade.sort()
735
 
            logging.error("Packages to downgrade found: '%s'" %
736
 
                          " ".join(downgrade))
737
 
        if len(untrusted) > 0:
738
 
            untrusted.sort()
739
 
            logging.error("Unauthenticated packages found: '%s'" %
740
 
                          " ".join(untrusted))
741
 
            # FIXME: maybe ask a question here? instead of failing?
742
 
            self._stopAptResolverLog()
743
 
            view.error(_("Error authenticating some packages"),
744
 
                       _("It was not possible to authenticate some "
745
 
                         "packages. This may be a transient network problem. "
746
 
                         "You may want to try again later. See below for a "
747
 
                         "list of unauthenticated packages."),
748
 
                       "\n".join(untrusted))
749
 
            # start the resolver log again because this is run with
750
 
            # the withResolverLog decorator
751
 
            self._startAptResolverLog()
752
 
            return False
753
 
        return True
754
 
 
755
 
    def _verifyChanges(self):
756
 
        """ this function tests if the current changes don't violate
757
 
            our constrains (blacklisted removals etc)
758
 
        """
759
 
        main_arch = apt_pkg.config.find("APT::Architecture")
760
 
        removeEssentialOk = self.config.getlist("Distro", "RemoveEssentialOk")
761
 
        # check changes
762
 
        for pkg in self.get_changes():
763
 
            if pkg.marked_delete and self._inRemovalBlacklist(pkg.name):
764
 
                logging.debug("The package '%s' is marked for removal but it's in the removal blacklist", pkg.name)
765
 
                raise SystemError(_("The package '%s' is marked for removal but it is in the removal blacklist.") % pkg.name)
766
 
            if pkg.marked_delete and (
767
 
                    pkg._pkg.essential == True and
768
 
                    pkg.installed.architecture in (main_arch, "all") and
769
 
                    not pkg.name in removeEssentialOk):
770
 
                logging.debug("The package '%s' is marked for removal but it's an ESSENTIAL package", pkg.name)
771
 
                raise SystemError(_("The essential package '%s' is marked for removal.") % pkg.name)
772
 
        # check bad-versions blacklist
773
 
        badVersions = self.config.getlist("Distro", "BadVersions")
774
 
        for bv in badVersions:
775
 
            (pkgname, ver) = bv.split("_")
776
 
            if (pkgname in self and self[pkgname].candidate and
777
 
                self[pkgname].candidate.version == ver and
778
 
                (self[pkgname].marked_install or
779
 
                 self[pkgname].marked_upgrade)):
780
 
                raise SystemError(_("Trying to install blacklisted version '%s'") % bv)
781
 
        return True
782
 
 
783
 
    def _lookupPkgRecord(self, pkg):
784
 
        """
785
 
        helper to make sure that the pkg._records is pointing to the right
786
 
        location - needed because python-apt 0.7.9 dropped the python-apt
787
 
        version but we can not yet use the new version because on upgrade
788
 
        the old version is still installed
789
 
        """
790
 
        ver = pkg._pcache._depcache.get_candidate_ver(pkg._pkg)
791
 
        if ver is None:
792
 
            print("No candidate ver: ", pkg.name)
793
 
            return False
794
 
        if ver.file_list is None:
795
 
            print("No file_list for: %s " % self._pkg.name())
796
 
            return False
797
 
        f, index = ver.file_list.pop(0)
798
 
        pkg._pcache._records.lookup((f, index))
799
 
        return True
800
 
 
801
 
    @property
802
 
    def installedTasks(self):
803
 
        tasks = {}
804
 
        installed_tasks = set()
805
 
        for pkg in self:
806
 
            if not self._lookupPkgRecord(pkg):
807
 
                logging.debug("no PkgRecord found for '%s', skipping " % pkg.name)
808
 
                continue
809
 
            for line in pkg._pcache._records.record.split("\n"):
810
 
                if line.startswith("Task:"):
811
 
                    for task in (line[len("Task:"):]).split(","):
812
 
                        task = task.strip()
813
 
                        if task not in tasks:
814
 
                            tasks[task] = set()
815
 
                        tasks[task].add(pkg.name)
816
 
        for task in tasks:
817
 
            installed = True
818
 
            for pkgname in tasks[task]:
819
 
                if not self[pkgname].is_installed:
820
 
                    installed = False
821
 
                    break
822
 
            if installed:
823
 
                installed_tasks.add(task)
824
 
        return installed_tasks
825
 
 
826
 
    def installTasks(self, tasks):
827
 
        logging.debug("running installTasks")
828
 
        for pkg in self:
829
 
            if pkg.marked_install or pkg.is_installed:
830
 
                continue
831
 
            self._lookupPkgRecord(pkg)
832
 
            if not (hasattr(pkg._pcache._records, "record") and pkg._pcache._records.record):
833
 
                logging.warning("can not find Record for '%s'" % pkg.name)
834
 
                continue
835
 
            for line in pkg._pcache._records.record.split("\n"):
836
 
                if line.startswith("Task:"):
837
 
                    for task in (line[len("Task:"):]).split(","):
838
 
                        task = task.strip()
839
 
                        if task in tasks:
840
 
                            pkg.mark_install()
841
 
        return True
842
 
 
843
 
    def _keepBaseMetaPkgsInstalled(self, view):
844
 
        for pkg in self.config.getlist("Distro", "BaseMetaPkgs"):
845
 
            self._keep_installed(pkg, "base meta package keep installed rule")
846
 
 
847
 
    def _installMetaPkgs(self, view):
848
 
 
849
 
        def metaPkgInstalled():
850
 
            """
851
 
            internal helper that checks if at least one meta-pkg is
852
 
            installed or marked install
853
 
            """
854
 
            for key in metapkgs:
855
 
                if key in self:
856
 
                    pkg = self[key]
857
 
                    if pkg.is_installed and pkg.marked_delete:
858
 
                        logging.debug("metapkg '%s' installed but marked_delete" % pkg.name)
859
 
                    if ((pkg.is_installed and not pkg.marked_delete)
860
 
                        or self[key].marked_install):
861
 
                        return True
862
 
            return False
863
 
 
864
 
        # now check for ubuntu-desktop, kubuntu-desktop, edubuntu-desktop
865
 
        metapkgs = self.config.getlist("Distro", "MetaPkgs")
866
 
 
867
 
        # we never go without ubuntu-base
868
 
        for pkg in self.config.getlist("Distro", "BaseMetaPkgs"):
869
 
            self[pkg].mark_install()
870
 
 
871
 
        # every meta-pkg that is installed currently, will be marked
872
 
        # install (that result in a upgrade and removes a mark_delete)
873
 
        for key in metapkgs:
874
 
            try:
875
 
                if (key in self and
876
 
                    self[key].is_installed and
877
 
                    self[key].is_upgradable):
878
 
                    logging.debug("Marking '%s' for upgrade" % key)
879
 
                    self[key].mark_upgrade()
880
 
            except SystemError as e:
881
 
                # warn here, but don't fail, its possible that meta-packages
882
 
                # conflict (like ubuntu-desktop vs xubuntu-desktop) LP: #775411
883
 
                logging.warning("Can't mark '%s' for upgrade (%s)" % (key, e))
884
 
 
885
 
        # check if we have a meta-pkg, if not, try to guess which one to pick
886
 
        if not metaPkgInstalled():
887
 
            logging.debug("none of the '%s' meta-pkgs installed" % metapkgs)
888
 
            for key in metapkgs:
889
 
                deps_found = True
890
 
                for pkg in self.config.getlist(key, "KeyDependencies"):
891
 
                    deps_found &= pkg in self and self[pkg].is_installed
892
 
                if deps_found:
893
 
                    logging.debug("guessing '%s' as missing meta-pkg" % key)
894
 
                    try:
895
 
                        self[key].mark_install()
896
 
                    except (SystemError, KeyError) as e:
897
 
                        logging.error("failed to mark '%s' for install (%s)" %
898
 
                                      (key, e))
899
 
                        view.error(_("Can't install '%s'") % key,
900
 
                                   _("It was impossible to install a "
901
 
                                     "required package. Please report "
902
 
                                     "this as a bug using "
903
 
                                     "'ubuntu-bug ubuntu-release-upgrader-core' in "
904
 
                                     "a terminal."))
905
 
                        return False
906
 
                    logging.debug("marked_install: '%s' -> '%s'" % (key, self[key].marked_install))
907
 
                    break
908
 
        # check if we actually found one
909
 
        if not metaPkgInstalled():
910
 
            meta_pkgs = ', '.join(metapkgs[0:-1])
911
 
            view.error(_("Can't guess meta-package"),
912
 
                       _("Your system does not contain a "
913
 
                         "%s or %s package and it was not "
914
 
                         "possible to detect which version of "
915
 
                         "Ubuntu you are running.\n "
916
 
                         "Please install one of the packages "
917
 
                         "above first using synaptic or "
918
 
                         "apt-get before proceeding.") %
919
 
                        (meta_pkgs, metapkgs[-1]))
920
 
            return False
921
 
        return True
922
 
 
923
 
    def _inRemovalBlacklist(self, pkgname):
924
 
        for expr in self.removal_blacklist:
925
 
            if re.compile(expr).match(pkgname):
926
 
                logging.debug("blacklist expr '%s' matches '%s'" % (expr, pkgname))
927
 
                return True
928
 
        return False
929
 
 
930
 
    @withResolverLog
931
 
    def tryMarkObsoleteForRemoval(self, pkgname, remove_candidates, foreign_pkgs):
932
 
        #logging.debug("tryMarkObsoleteForRemoval(): %s" % pkgname)
933
 
        # sanity check, first see if it looks like a running kernel pkg
934
 
        if pkgname.endswith(self.uname):
935
 
            logging.debug("skipping running kernel pkg '%s'" % pkgname)
936
 
            return False
937
 
        if self._inRemovalBlacklist(pkgname):
938
 
            logging.debug("skipping '%s' (in removalBlacklist)" % pkgname)
939
 
            return False
940
 
        # ensure we honor KeepInstalledSection here as well
941
 
        for section in self.config.getlist("Distro", "KeepInstalledSection"):
942
 
            if pkgname in self and self[pkgname].installed.section == section:
943
 
                logging.debug("skipping '%s' (in KeepInstalledSection)" % pkgname)
944
 
                return False
945
 
        # if we don't have the package anyway, we are fine (this can
946
 
        # happen when forced_obsoletes are specified in the config file)
947
 
        if pkgname not in self:
948
 
            #logging.debug("package '%s' not in cache" % pkgname)
949
 
            return True
950
 
        # check if we want to purge
951
 
        try:
952
 
            purge = self.config.getboolean("Distro", "PurgeObsoletes")
953
 
        except configparser.NoOptionError:
954
 
            purge = False
955
 
 
956
 
        # this is a delete candidate, only actually delete,
957
 
        # if it dosn't remove other packages depending on it
958
 
        # that are not obsolete as well
959
 
        actiongroup = apt_pkg.ActionGroup(self._depcache)
960
 
        # just make pyflakes shut up, later we should use
961
 
        # with self.actiongroup():
962
 
        actiongroup
963
 
        self.create_snapshot()
964
 
        try:
965
 
            self[pkgname].mark_delete(purge=purge)
966
 
            self.view.processEvents()
967
 
            #logging.debug("marking '%s' for removal" % pkgname)
968
 
            for pkg in self.get_changes():
969
 
                if (pkg.name not in remove_candidates or
970
 
                      pkg.name in foreign_pkgs or
971
 
                      self._inRemovalBlacklist(pkg.name)):
972
 
                    logging.debug("package '%s' produces an unwanted removal '%s', skipping" % (pkgname, pkg.name))
973
 
                    self.restore_snapshot()
974
 
                    return False
975
 
        except (SystemError, KeyError) as e:
976
 
            logging.warning("_tryMarkObsoleteForRemoval failed for '%s' (%s: %s)" % (pkgname, repr(e), e))
977
 
            self.restore_snapshot()
978
 
            return False
979
 
        return True
980
 
 
981
 
    def _getObsoletesPkgs(self):
982
 
        " get all package names that are not downloadable "
983
 
        obsolete_pkgs = set()
984
 
        for pkg in self:
985
 
            if pkg.is_installed:
986
 
                # check if any version is downloadable. we need to check
987
 
                # for older ones too, because there might be
988
 
                # cases where e.g. firefox in gutsy-updates is newer
989
 
                # than hardy
990
 
                if not self.anyVersionDownloadable(pkg):
991
 
                    obsolete_pkgs.add(pkg.name)
992
 
        return obsolete_pkgs
993
 
 
994
 
    def anyVersionDownloadable(self, pkg):
995
 
        " helper that checks if any of the version of pkg is downloadable "
996
 
        for ver in pkg._pkg.version_list:
997
 
            if ver.downloadable:
998
 
                return True
999
 
        return False
1000
 
 
1001
 
    def _getUnusedDependencies(self):
1002
 
        " get all package names that are not downloadable "
1003
 
        unused_dependencies = set()
1004
 
        for pkg in self:
1005
 
            if pkg.is_installed and self._depcache.is_garbage(pkg._pkg):
1006
 
                unused_dependencies.add(pkg.name)
1007
 
        return unused_dependencies
1008
 
 
1009
 
    def get_installed_demoted_packages(self):
1010
 
        """ return list of installed and demoted packages
1011
 
 
1012
 
            If a demoted package is a automatic install it will be skipped
1013
 
        """
1014
 
        demotions = set()
1015
 
        demotions_file = self.config.get("Distro", "Demotions")
1016
 
        if os.path.exists(demotions_file):
1017
 
            with open(demotions_file) as demotions_f:
1018
 
                for line in demotions_f:
1019
 
                    if not line.startswith("#"):
1020
 
                        demotions.add(line.strip())
1021
 
        installed_demotions = set()
1022
 
        for demoted_pkgname in demotions:
1023
 
            if demoted_pkgname not in self:
1024
 
                continue
1025
 
            pkg = self[demoted_pkgname]
1026
 
            if (not pkg.is_installed or
1027
 
                self._depcache.is_auto_installed(pkg._pkg) or
1028
 
                pkg.marked_delete):
1029
 
                continue
1030
 
            installed_demotions.add(pkg)
1031
 
        return list(installed_demotions)
1032
 
 
1033
 
    def _getForeignPkgs(self, allowed_origin, fromDist, toDist):
1034
 
        """ get all packages that are installed from a foreign repo
1035
 
            (and are actually downloadable)
1036
 
        """
1037
 
        foreign_pkgs = set()
1038
 
        for pkg in self:
1039
 
            if pkg.is_installed and self.downloadable(pkg):
1040
 
                if not pkg.candidate:
1041
 
                    continue
1042
 
                # assume it is foreign and see if it is from the
1043
 
                # official archive
1044
 
                foreign = True
1045
 
                for origin in pkg.candidate.origins:
1046
 
                    # FIXME: use some better metric here
1047
 
                    if fromDist in origin.archive and \
1048
 
                           origin.origin == allowed_origin:
1049
 
                        foreign = False
1050
 
                    if toDist in origin.archive and \
1051
 
                           origin.origin == allowed_origin:
1052
 
                        foreign = False
1053
 
                if foreign:
1054
 
                    foreign_pkgs.add(pkg.name)
1055
 
        return foreign_pkgs
1056
 
 
1057
 
    def checkFreeSpace(self, snapshots_in_use=False):
1058
 
        """
1059
 
        this checks if we have enough free space on /var, /boot and /usr
1060
 
        with the given cache
1061
 
 
1062
 
        Note: this can not be fully accurate if there are multiple
1063
 
              mountpoints for /usr, /var, /boot
1064
 
        """
1065
 
 
1066
 
        class FreeSpace(object):
1067
 
            " helper class that represents the free space on each mounted fs "
1068
 
            def __init__(self, initialFree):
1069
 
                self.free = initialFree
1070
 
                self.need = 0
1071
 
 
1072
 
        def make_fs_id(d):
1073
 
            """ return 'id' of a directory so that directories on the
1074
 
                same filesystem get the same id (simply the mount_point)
1075
 
            """
1076
 
            for mount_point in mounted:
1077
 
                if d.startswith(mount_point):
1078
 
                    return mount_point
1079
 
            return "/"
1080
 
 
1081
 
        # this is all a bit complicated
1082
 
        # 1) check what is mounted (in mounted)
1083
 
        # 2) create FreeSpace objects for the dirs we are interested in
1084
 
        #    (mnt_map)
1085
 
        # 3) use the  mnt_map to check if we have enough free space and
1086
 
        #    if not tell the user how much is missing
1087
 
        mounted = []
1088
 
        mnt_map = {}
1089
 
        fs_free = {}
1090
 
        with open("/proc/mounts") as mounts:
1091
 
            for line in mounts:
1092
 
                try:
1093
 
                    (what, where, fs, options, a, b) = line.split()
1094
 
                except ValueError as e:
1095
 
                    logging.debug("line '%s' in /proc/mounts not understood (%s)" % (line, e))
1096
 
                    continue
1097
 
                if not where in mounted:
1098
 
                    mounted.append(where)
1099
 
        # make sure mounted is sorted by longest path
1100
 
        mounted.sort(key=len, reverse=True)
1101
 
        archivedir = apt_pkg.config.find_dir("Dir::Cache::archives")
1102
 
        aufs_rw_dir = "/tmp/"
1103
 
        if (hasattr(self, "config") and
1104
 
            self.config.getWithDefault("Aufs", "Enabled", False)):
1105
 
            aufs_rw_dir = self.config.get("Aufs", "RWDir")
1106
 
            if not os.path.exists(aufs_rw_dir):
1107
 
                os.makedirs(aufs_rw_dir)
1108
 
        logging.debug("cache aufs_rw_dir: %s" % aufs_rw_dir)
1109
 
        for d in ["/", "/usr", "/var", "/boot", archivedir, aufs_rw_dir, "/home", "/tmp/"]:
1110
 
            d = os.path.realpath(d)
1111
 
            fs_id = make_fs_id(d)
1112
 
            if os.path.exists(d):
1113
 
                st = os.statvfs(d)
1114
 
                free = st.f_bavail * st.f_frsize
1115
 
            else:
1116
 
                logging.warning("directory '%s' does not exists" % d)
1117
 
                free = 0
1118
 
            if fs_id in mnt_map:
1119
 
                logging.debug("Dir %s mounted on %s" %
1120
 
                              (d, mnt_map[fs_id]))
1121
 
                fs_free[d] = fs_free[mnt_map[fs_id]]
1122
 
            else:
1123
 
                logging.debug("Free space on %s: %s" %
1124
 
                              (d, free))
1125
 
                mnt_map[fs_id] = d
1126
 
                fs_free[d] = FreeSpace(free)
1127
 
        del mnt_map
1128
 
        logging.debug("fs_free contains: '%s'" % fs_free)
1129
 
 
1130
 
        # now calculate the space that is required on /boot
1131
 
        # we do this by checking how many linux-image-$ver packages
1132
 
        # are installed or going to be installed
1133
 
        kernel_count = 0
1134
 
        for pkg in self:
1135
 
            # we match against everything that looks like a kernel
1136
 
            # and add space check to filter out metapackages
1137
 
            if re.match("^linux-(image|image-debug)-[0-9.]*-.*", pkg.name):
1138
 
                # upgrade because early in the release cycle the major version
1139
 
                # may be the same or they might be -lts- kernels
1140
 
                if pkg.marked_install or pkg.marked_upgrade:
1141
 
                    logging.debug("%s (new-install) added with %s to boot space" % (pkg.name, KERNEL_SIZE))
1142
 
                    kernel_count += 1
1143
 
        # space calculated per LP: #1646222
1144
 
        space_in_boot = (kernel_count * KERNEL_SIZE
1145
 
                         + (kernel_count + 1) * INITRD_SIZE)
1146
 
 
1147
 
        # we check for various sizes:
1148
 
        # archivedir is where we download the debs
1149
 
        # /usr is assumed to get *all* of the install space (incorrect,
1150
 
        #      but as good as we can do currently + safety buffer
1151
 
        # /     has a small safety buffer as well
1152
 
        required_for_aufs = 0.0
1153
 
        if (hasattr(self, "config") and
1154
 
            self.config.getWithDefault("Aufs", "Enabled", False)):
1155
 
            logging.debug("taking aufs overlay into space calculation")
1156
 
            aufs_rw_dir = self.config.get("Aufs", "RWDir")
1157
 
            # if we use the aufs rw overlay all the space is consumed
1158
 
            # the overlay dir
1159
 
            for pkg in self:
1160
 
                if pkg.marked_upgrade or pkg.marked_install:
1161
 
                    required_for_aufs += pkg.candidate.installed_size
1162
 
 
1163
 
        # add old size of the package if we use snapshots
1164
 
        required_for_snapshots = 0.0
1165
 
        if snapshots_in_use:
1166
 
            for pkg in self:
1167
 
                if (pkg.is_installed and
1168
 
                    (pkg.marked_upgrade or pkg.marked_delete)):
1169
 
                    required_for_snapshots += pkg.installed.installed_size
1170
 
            logging.debug("additional space for the snapshots: %s" % required_for_snapshots)
1171
 
 
1172
 
        # sum up space requirements
1173
 
        for (dir, size) in [(archivedir, self.required_download),
1174
 
                            ("/usr", self.additional_required_space),
1175
 
                            # this is only >0 for the deb-to-snap quirks
1176
 
                            ("/var", self.additional_required_space_for_snaps),
1177
 
                            # plus 50M safety buffer in /usr
1178
 
                            ("/usr", 50*1024*1024),
1179
 
                            ("/boot", space_in_boot),
1180
 
                            ("/tmp", 5*1024*1024),   # /tmp for dkms LP: #427035
1181
 
                            ("/", 10*1024*1024),     # small safety buffer /
1182
 
                            (aufs_rw_dir, required_for_aufs),
1183
 
                            # if snapshots are in use
1184
 
                            ("/usr", required_for_snapshots),
1185
 
                           ]:
1186
 
            # we are ensuring we have more than enough free space not less
1187
 
            if size < 0:
1188
 
                continue
1189
 
            dir = os.path.realpath(dir)
1190
 
            logging.debug("dir '%s' needs '%s' of '%s' (%f)" % (dir, size, fs_free[dir], fs_free[dir].free))
1191
 
            fs_free[dir].free -= size
1192
 
            fs_free[dir].need += size
1193
 
 
1194
 
        # check for space required violations
1195
 
        required_list = {}
1196
 
        for dir in fs_free:
1197
 
            if fs_free[dir].free < 0:
1198
 
                # ensure unicode here (LP: #1172740)
1199
 
                free_at_least = apt_pkg.size_to_str(float(abs(fs_free[dir].free)+1))
1200
 
                if isinstance(free_at_least, bytes):
1201
 
                    free_at_least = free_at_least.decode(
1202
 
                        locale.getpreferredencoding())
1203
 
                free_needed = apt_pkg.size_to_str(fs_free[dir].need)
1204
 
                if isinstance(free_needed, bytes):
1205
 
                    free_needed = free_needed.decode(
1206
 
                        locale.getpreferredencoding())
1207
 
                # make_fs_id ensures we only get stuff on the same
1208
 
                # mountpoint, so we report the requirements only once
1209
 
                # per mountpoint
1210
 
                required_list[make_fs_id(dir)] = FreeSpaceRequired(free_needed, make_fs_id(dir), free_at_least)
1211
 
        # raise exception if free space check fails
1212
 
        if len(required_list) > 0:
1213
 
            logging.error("Not enough free space: %s" % [str(i) for i in required_list])
1214
 
            raise NotEnoughFreeSpaceError(list(required_list.values()))
1215
 
        return True
1216
 
 
1217
 
 
1218
 
if __name__ == "__main__":
1219
 
    import sys
1220
 
    from .DistUpgradeConfigParser import DistUpgradeConfig
1221
 
    from .DistUpgradeView import DistUpgradeView
1222
 
    print("foo")
1223
 
    c = MyCache(DistUpgradeConfig("."), DistUpgradeView(), None)
1224
 
    #c.checkForNvidia()
1225
 
    #print(c._identifyObsoleteKernels())
1226
 
    print(c.checkFreeSpace())
1227
 
    sys.exit()
1228
 
 
1229
 
    c.clear()
1230
 
    c.create_snapshot()
1231
 
    c.installedTasks
1232
 
    c.installTasks(["ubuntu-desktop"])
1233
 
    print(c.get_changes())
1234
 
    c.restore_snapshot()