3
# Copyright (c) 2004-2008 Canonical
5
# Author: Michael Vogt <michael.vogt@ubuntu.com>
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.
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.
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
33
from subprocess import Popen, PIPE
35
from .DistUpgradeGettext import gettext as _
36
from .DistUpgradeGettext import ngettext
38
from .utils import inside_chroot
40
class CacheException(Exception):
44
class CacheExceptionLockingFailed(CacheException):
48
class CacheExceptionDpkgInterrupted(CacheException):
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
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):
68
kernel += os.path.getsize(f)
71
"estimate_kernel_initrd_size_in_boot() returned '0' for kernel?")
75
"estimate_kernel_initrd_size_in_boot() returned '0' for initrd?")
77
# add small safety buffer
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
85
KERNEL_SIZE, INITRD_SIZE = estimate_kernel_initrd_size_in_boot()
88
class FreeSpaceRequired(object):
89
""" FreeSpaceRequired object:
92
- the total size required (size_total)
93
- the dir that requires the space (dir)
94
- the additional space that is needed (size_needed)
96
def __init__(self, size_total, dir, size_needed):
97
self.size_total = size_total
99
self.size_needed = size_needed
101
return "FreeSpaceRequired Object: Dir: %s size_total: %s size_needed: %s" % (self.dir, self.size_total, self.size_needed)
104
class NotEnoughFreeSpaceError(CacheException):
106
Exception if there is not enough free space for this operation
109
def __init__(self, free_space_required_list):
110
self.free_space_required_list = free_space_required_list
113
class MyCache(apt.Cache):
118
def __init__(self, config, view, quirks, progress=None, lock=True):
124
self.partialUpgrade = False
126
self.metapkgs = self.config.getlist("Distro", "MetaPkgs")
131
apt_pkg.pkgsystem_lock()
132
self.lock_lists_dir()
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()
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")
153
apt_pkg.config.set("APT::AutoRemove::SuggestsImportant", "false")
155
def _apply_dselect_upgrade(self):
156
""" honor the dselect install state """
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)
165
def req_reinstall_pkgs(self):
166
" return the packages not downloadable packages in reqreinst state "
169
if ((not pkg.candidate or not pkg.candidate.downloadable)
171
(pkg._pkg.inst_state == self.ReInstReq or
172
pkg._pkg.inst_state == self.HoldReInstReq)):
173
reqreinst.add(pkg.name)
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",
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 "
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 "
193
len(reqreinst)) % ", ".join(reqreinst)
194
if view.askYesNoQuestion(header, summary):
196
cmd = ["/usr/bin/dpkg", "--remove", "--force-remove-reinstreq"] + list(reqreinst)
197
view.getTerminal().call(cmd)
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):
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"))
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):
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()
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
255
def additional_required_space(self):
256
""" get the size of the additional required space on the fs """
257
return self._depcache.usr_size
259
def additional_required_space_for_snaps(self):
260
""" get the extra size needed to install the snap replacements """
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:
274
""" is the cache broken """
275
return self._depcache.broken_count > 0
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)
288
def update(self, fprogress=None):
290
our own update implementation is required because we keep the lists
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"))
301
raise IOError("apt.cache.update() returned False, but did not raise exception?!?")
303
def commit(self, fprogress, iprogress):
304
logging.info("cache.commit()")
307
apt.Cache.commit(self, fprogress, iprogress)
309
def release_lock(self, pkgSystemOnly=True):
312
apt_pkg.pkgsystem_unlock()
314
except SystemError as e:
315
logging.debug("failed to SystemUnLock() (%s) " % e)
317
def get_lock(self, pkgSystemOnly=True):
320
apt_pkg.pkgsystem_lock()
322
except SystemError as e:
323
logging.debug("failed to SystemLock() (%s) " % e)
325
def downloadable(self, pkg, useCandidate=True):
326
" check if the given pkg can be downloaded "
328
ver = self._depcache.get_candidate_ver(pkg._pkg)
330
ver = pkg._pkg.current_ver
332
logging.warning("no version information for '%s' (useCandidate=%s)" % (pkg.name, useCandidate))
334
return ver.downloadable
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))
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()
346
def create_snapshot(self):
347
""" create a snapshot of the current changes """
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)
357
self._depcache.init()
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():
366
for name in self.to_remove:
369
for name in self.to_install:
371
pkg.mark_install(auto_fix=False, auto_inst=False)
373
def need_server_mode(self):
375
This checks if we run on a desktop or a server install.
377
A server install has more freedoms, for a desktop install
378
we force a desktop meta package to be install on the upgrade.
380
We look for a installed desktop meta pkg and for key
381
dependencies, if none of those are installed we assume
384
#logging.debug("need_server_mode() run")
385
# check for the MetaPkgs (e.g. ubuntu-desktop)
386
metapkgs = self.config.getlist("Distro", "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)
392
# if it is not installed, but its key depends are installed
393
# we are done too (we auto-select the package later)
395
for pkg in self.config.getlist(key, "KeyDependencies"):
396
deps_found &= pkg in self and self[pkg].is_installed
398
logging.debug("need_server_mode(): run in 'desktop' mode, (because of key deps for '%s')" % key)
400
logging.debug("need_server_mode(): can not find a desktop meta package or key deps, running in server mode")
403
def sanity_check(self, view):
404
""" check if the cache is ok and if the required metapkgs
409
logging.debug("Have broken pkgs, trying to fix them")
412
view.error(_("Broken packages"),
413
_("Your system contains broken packages "
414
"that couldn't be fixed with this "
416
"Please fix them first using synaptic or "
417
"apt-get before proceeding."))
421
def mark_install(self, pkg, reason=""):
422
logging.debug("Installing '%s' (%s)" % (pkg, reason))
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)
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)
440
def mark_remove(self, pkg, reason=""):
441
logging.debug("Removing '%s' (%s)" % (pkg, reason))
443
self[pkg].mark_delete()
445
def mark_purge(self, pkg, reason=""):
446
logging.debug("Purging '%s' (%s)" % (pkg, reason))
448
self._depcache.mark_delete(self[pkg]._pkg, True)
450
def _keep_installed(self, pkgname, reason):
452
and self[pkgname].is_installed
453
and self[pkgname].marked_delete):
454
self.mark_install(pkgname, reason)
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)
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"):
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"):
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))
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")
509
def checkForNvidia(self):
511
this checks for nvidia hardware and checks what driver is needed
513
logging.debug("nvidiaUpdate()")
514
# if the free drivers would give us a equally hard time, we would
515
# never be able to release
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)
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")
534
logging.info("no old nvidia driver installed, installing no new")
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)
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)
546
except Exception as e:
547
logging.error("NvidiaDetection returned a error: %s" % e)
551
def _has_kernel_headers_installed(self):
553
if (pkg.name.startswith("linux-headers-") and
558
def checkForKernel(self):
559
""" check for the running kernel and try to ensure that we have
562
logging.debug("Kernel uname: '%s' " % self.uname)
564
(version, build, flavour) = self.uname.split("-")
565
except Exception as e:
566
logging.warning("Can't parse kernel uname: '%s' (self compiled?)" % e)
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!?!")
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")
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)
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)
594
# FIXME: make this a decorator (just like the withResolverLog())
595
def updateGUI(self, view, lock):
603
view.pulseProgress(finished=True)
607
def distUpgrade(self, view, serverMode, partialUpgrade):
609
lock = threading.Lock()
611
t = threading.Thread(target=self.updateGUI, args=(self.view, lock,))
614
# mvo: disabled as it casues to many errornous installs
615
#self._apply_dselect_upgrade()
617
# upgrade (and make sure this way that the cache is ok)
620
# check that everything in priority required is installed
623
# see if our KeepInstalled rules are honored
624
self.keep_installed_rule()
626
# check if we got a new kernel (if we are not inside a
629
logging.warning("skipping kernel checks because we run inside a chroot")
631
self.checkForKernel()
633
# check for nvidia stuff
634
self.checkForNvidia()
636
# and if we have some special rules
637
self.post_upgrade_rule()
639
# install missing meta-packages (if not in server upgrade mode)
640
self._keepBaseMetaPkgsInstalled(view)
642
# if this fails, a system error is raised
643
self._installMetaPkgs(view)
645
# see if it all makes sense, if not this function raises
646
self._verifyChanges()
648
except SystemError as e:
649
# this should go into a finally: line, see below for the
650
# rationale why it doesn't
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"
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
673
details += _("This is most likely a transient problem, "
674
"please try again later.")
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
683
self._stopAptResolverLog()
684
view.error(_("Could not calculate the upgrade"), details)
685
# may contain utf-8 (LP: #1310053)
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()
692
# would be nice to be able to use finally: here, but we need
693
# to run on python2.4 too
695
# wait for the gui-update thread to exit
699
# check the trust of the packages that are going to change
702
for pkg in self.get_changes():
703
if pkg.marked_delete:
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)
718
origins = pkg.candidate.origins
720
for origin in origins:
722
trusted |= origin.trusted
724
untrusted.append(pkg.name)
725
# check if the user overwrote the unauthenticated warning
727
b = self.config.getboolean("Distro", "AllowUnauthenticated")
729
logging.warning("AllowUnauthenticated set!")
731
except configparser.NoOptionError:
733
if len(downgrade) > 0:
735
logging.error("Packages to downgrade found: '%s'" %
737
if len(untrusted) > 0:
739
logging.error("Unauthenticated packages found: '%s'" %
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()
755
def _verifyChanges(self):
756
""" this function tests if the current changes don't violate
757
our constrains (blacklisted removals etc)
759
main_arch = apt_pkg.config.find("APT::Architecture")
760
removeEssentialOk = self.config.getlist("Distro", "RemoveEssentialOk")
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)
783
def _lookupPkgRecord(self, pkg):
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
790
ver = pkg._pcache._depcache.get_candidate_ver(pkg._pkg)
792
print("No candidate ver: ", pkg.name)
794
if ver.file_list is None:
795
print("No file_list for: %s " % self._pkg.name())
797
f, index = ver.file_list.pop(0)
798
pkg._pcache._records.lookup((f, index))
802
def installedTasks(self):
804
installed_tasks = set()
806
if not self._lookupPkgRecord(pkg):
807
logging.debug("no PkgRecord found for '%s', skipping " % pkg.name)
809
for line in pkg._pcache._records.record.split("\n"):
810
if line.startswith("Task:"):
811
for task in (line[len("Task:"):]).split(","):
813
if task not in tasks:
815
tasks[task].add(pkg.name)
818
for pkgname in tasks[task]:
819
if not self[pkgname].is_installed:
823
installed_tasks.add(task)
824
return installed_tasks
826
def installTasks(self, tasks):
827
logging.debug("running installTasks")
829
if pkg.marked_install or pkg.is_installed:
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)
835
for line in pkg._pcache._records.record.split("\n"):
836
if line.startswith("Task:"):
837
for task in (line[len("Task:"):]).split(","):
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")
847
def _installMetaPkgs(self, view):
849
def metaPkgInstalled():
851
internal helper that checks if at least one meta-pkg is
852
installed or marked install
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):
864
# now check for ubuntu-desktop, kubuntu-desktop, edubuntu-desktop
865
metapkgs = self.config.getlist("Distro", "MetaPkgs")
867
# we never go without ubuntu-base
868
for pkg in self.config.getlist("Distro", "BaseMetaPkgs"):
869
self[pkg].mark_install()
871
# every meta-pkg that is installed currently, will be marked
872
# install (that result in a upgrade and removes a mark_delete)
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))
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)
890
for pkg in self.config.getlist(key, "KeyDependencies"):
891
deps_found &= pkg in self and self[pkg].is_installed
893
logging.debug("guessing '%s' as missing meta-pkg" % key)
895
self[key].mark_install()
896
except (SystemError, KeyError) as e:
897
logging.error("failed to mark '%s' for install (%s)" %
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 "
906
logging.debug("marked_install: '%s' -> '%s'" % (key, self[key].marked_install))
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]))
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))
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)
937
if self._inRemovalBlacklist(pkgname):
938
logging.debug("skipping '%s' (in removalBlacklist)" % pkgname)
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)
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)
950
# check if we want to purge
952
purge = self.config.getboolean("Distro", "PurgeObsoletes")
953
except configparser.NoOptionError:
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():
963
self.create_snapshot()
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()
975
except (SystemError, KeyError) as e:
976
logging.warning("_tryMarkObsoleteForRemoval failed for '%s' (%s: %s)" % (pkgname, repr(e), e))
977
self.restore_snapshot()
981
def _getObsoletesPkgs(self):
982
" get all package names that are not downloadable "
983
obsolete_pkgs = set()
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
990
if not self.anyVersionDownloadable(pkg):
991
obsolete_pkgs.add(pkg.name)
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:
1001
def _getUnusedDependencies(self):
1002
" get all package names that are not downloadable "
1003
unused_dependencies = set()
1005
if pkg.is_installed and self._depcache.is_garbage(pkg._pkg):
1006
unused_dependencies.add(pkg.name)
1007
return unused_dependencies
1009
def get_installed_demoted_packages(self):
1010
""" return list of installed and demoted packages
1012
If a demoted package is a automatic install it will be skipped
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:
1025
pkg = self[demoted_pkgname]
1026
if (not pkg.is_installed or
1027
self._depcache.is_auto_installed(pkg._pkg) or
1030
installed_demotions.add(pkg)
1031
return list(installed_demotions)
1033
def _getForeignPkgs(self, allowed_origin, fromDist, toDist):
1034
""" get all packages that are installed from a foreign repo
1035
(and are actually downloadable)
1037
foreign_pkgs = set()
1039
if pkg.is_installed and self.downloadable(pkg):
1040
if not pkg.candidate:
1042
# assume it is foreign and see if it is from the
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:
1050
if toDist in origin.archive and \
1051
origin.origin == allowed_origin:
1054
foreign_pkgs.add(pkg.name)
1057
def checkFreeSpace(self, snapshots_in_use=False):
1059
this checks if we have enough free space on /var, /boot and /usr
1060
with the given cache
1062
Note: this can not be fully accurate if there are multiple
1063
mountpoints for /usr, /var, /boot
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
1073
""" return 'id' of a directory so that directories on the
1074
same filesystem get the same id (simply the mount_point)
1076
for mount_point in mounted:
1077
if d.startswith(mount_point):
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
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
1090
with open("/proc/mounts") as mounts:
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))
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):
1114
free = st.f_bavail * st.f_frsize
1116
logging.warning("directory '%s' does not exists" % d)
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]]
1123
logging.debug("Free space on %s: %s" %
1126
fs_free[d] = FreeSpace(free)
1128
logging.debug("fs_free contains: '%s'" % fs_free)
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
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))
1143
# space calculated per LP: #1646222
1144
space_in_boot = (kernel_count * KERNEL_SIZE
1145
+ (kernel_count + 1) * INITRD_SIZE)
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
1160
if pkg.marked_upgrade or pkg.marked_install:
1161
required_for_aufs += pkg.candidate.installed_size
1163
# add old size of the package if we use snapshots
1164
required_for_snapshots = 0.0
1165
if snapshots_in_use:
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)
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),
1186
# we are ensuring we have more than enough free space not less
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
1194
# check for space required violations
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
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()))
1218
if __name__ == "__main__":
1220
from .DistUpgradeConfigParser import DistUpgradeConfig
1221
from .DistUpgradeView import DistUpgradeView
1223
c = MyCache(DistUpgradeConfig("."), DistUpgradeView(), None)
1225
#print(c._identifyObsoleteKernels())
1226
print(c.checkFreeSpace())
1232
c.installTasks(["ubuntu-desktop"])
1233
print(c.get_changes())
1234
c.restore_snapshot()