1
# DistUpgradeController.py
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
23
from __future__ import absolute_import, print_function
26
warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning)
40
from configparser import NoOptionError
41
if sys.version >= '3.2':
42
from configparser import ConfigParser as SafeConfigParser
44
from configparser import SafeConfigParser
47
from ConfigParser import SafeConfigParser, NoOptionError
48
from .utils import (country_mirror,
54
get_string_with_no_auth_from_source_entry,
55
is_child_of_process_name)
56
from string import Template
58
from urllib.parse import urlsplit
60
from urlparse import urlsplit
62
from .DistUpgradeView import STEP_PREPARE, STEP_MODIFY_SOURCES, STEP_FETCH, STEP_INSTALL, STEP_CLEANUP, STEP_REBOOT
63
from .DistUpgradeCache import MyCache
64
from .DistUpgradeConfigParser import DistUpgradeConfig
65
from .DistUpgradeQuirks import DistUpgradeQuirks
66
from .DistUpgradeAptCdrom import AptCdrom
67
from .DistUpgradeAufs import setupAufs, aufsOptionsAndEnvironmentSetup
69
# workaround broken relative import in python-apt (LP: #871007), we
70
# want the local version of distinfo.py from oneiric, but because of
71
# a bug in python-apt we will get the natty version that does not
72
# know about "Component.parent_component" leading to a crash
73
from . import distinfo
74
from . import sourceslist
75
sourceslist.DistInfo = distinfo.DistInfo
77
from .sourceslist import SourcesList, is_mirror
78
from .distro import get_distro, NoDistroTemplateException
80
from .DistUpgradeGettext import gettext as _
81
from .DistUpgradeGettext import ngettext
84
from .DistUpgradeCache import (CacheExceptionDpkgInterrupted,
85
CacheExceptionLockingFailed,
86
NotEnoughFreeSpaceError)
87
from .DistUpgradeApport import run_apport
89
REBOOT_REQUIRED_FILE = "/var/run/reboot-required"
91
class NoBackportsFoundException(Exception):
95
class DistUpgradeController(object):
96
""" this is the controller that does most of the work """
98
def __init__(self, distUpgradeView, options=None, datadir=None):
100
localedir = "/usr/share/locale/"
102
datadir = os.getcwd()
103
localedir = os.path.join(datadir,"mo")
104
self.datadir = datadir
105
self.options = options
108
gettext.bindtextdomain("update-manager",localedir)
109
gettext.textdomain("update-manager")
112
logging.debug("Using '%s' view" % distUpgradeView.__class__.__name__)
113
self._view = distUpgradeView
114
self._view.updateStatus(_("Reading cache"))
117
if not self.options or self.options.withNetwork == None:
118
self.useNetwork = True
120
self.useNetwork = self.options.withNetwork
122
cdrompath = options.cdromPath
125
self.aptcdrom = AptCdrom(distUpgradeView, cdrompath)
128
self.config = DistUpgradeConfig(datadir)
129
self.sources_backup_ext = "."+self.config.get("Files","BackupExt")
131
# move some of the options stuff into the self.config,
132
# ConfigParser deals only with strings it seems *sigh*
133
self.config.add_section("Options")
134
self.config.set("Options","withNetwork", str(self.useNetwork))
137
aufsOptionsAndEnvironmentSetup(self.options, self.config)
139
# some constants here
140
self.fromDist = self.config.get("Sources","From")
141
self.toDist = self.config.get("Sources","To")
142
self.origin = self.config.get("Sources","ValidOrigin")
143
self.arch = get_arch()
145
# we run with --force-overwrite by default
146
if not os.environ.has_key("RELEASE_UPGRADE_NO_FORCE_OVERWRITE"):
147
logging.debug("enable dpkg --force-overwrite")
148
apt_pkg.config.set("DPkg::Options::","--force-overwrite")
150
# we run in full upgrade mode by default
151
self._partialUpgrade = False
153
# install the quirks handler
154
self.quirks = DistUpgradeQuirks(self, self.config)
157
os.environ["RELEASE_UPGRADE_IN_PROGRESS"] = "1"
158
os.environ["PYCENTRAL_FORCE_OVERWRITE"] = "1"
159
os.environ["PATH"] = "%s:%s" % (os.getcwd()+"/imported",
161
check_and_fix_xbit("./imported/invoke-rc.d")
164
maxRetries = self.config.getint("Network","MaxRetries")
165
apt_pkg.config.set("Acquire::Retries", str(maxRetries))
166
# max sizes for dpkgpm for large installs (see linux/limits.h and
168
apt_pkg.config.set("Dpkg::MaxArgs", str(64*1024))
169
apt_pkg.config.set("Dpkg::MaxArgBytes", str(128*1024))
171
# smaller to avoid hangs
172
apt_pkg.config.set("Acquire::http::Timeout","20")
173
apt_pkg.config.set("Acquire::ftp::Timeout","20")
175
# no list cleanup here otherwise a "cancel" in the upgrade
176
# will not restore the full state (lists will be missing)
177
apt_pkg.config.set("Apt::Get::List-Cleanup", "false")
180
self.forced_obsoletes = self.config.getlist("Distro","ForcedObsoletes")
181
# list of valid mirrors that we can add
182
self.valid_mirrors = self.config.getListFromFile("Sources","ValidMirrors")
183
# third party mirrors
184
self.valid_3p_mirrors = []
185
if self.config.has_section('ThirdPartyMirrors'):
186
self.valid_3p_mirrors = [pair[1] for pair in
187
self.config.items('ThirdPartyMirrors')]
189
#apt_pkg.config.set("DPkg::Options::","--debug=0077")
192
self._aptCronJobPerms = 0o755
194
def openCache(self, lock=True):
195
logging.debug("openCache()")
196
if self.cache is None:
197
self.quirks.run("PreCacheOpen")
199
self.cache.releaseLock()
200
self.cache.unlockListsDir()
202
self.cache = MyCache(self.config,
205
self._view.getOpCacheProgress(),
207
# alias name for the plugin interface code
208
self.apt_cache = self.cache
209
# if we get a dpkg error that it was interrupted, just
210
# run dpkg --configure -a
211
except CacheExceptionDpkgInterrupted as e:
212
logging.warning("dpkg interrupted, calling dpkg --configure -a")
213
cmd = ["/usr/bin/dpkg","--configure","-a"]
214
if os.environ.get("DEBIAN_FRONTEND") == "noninteractive":
215
cmd.append("--force-confold")
216
self._view.getTerminal().call(cmd)
217
self.cache = MyCache(self.config,
220
self._view.getOpCacheProgress())
221
except CacheExceptionLockingFailed as e:
222
logging.error("Cache can not be locked (%s)" % e)
223
self._view.error(_("Unable to get exclusive lock"),
224
_("This usually means that another "
225
"package management application "
226
"(like apt-get or aptitude) "
227
"already running. Please close that "
228
"application first."));
230
self.cache.partialUpgrade = self._partialUpgrade
231
logging.debug("/openCache(), new cache size %i" % len(self.cache))
233
def _viewSupportsSSH(self):
235
Returns True if this view support upgrades over ssh.
236
In theory all views should support it, but for savety
237
we do only allow text ssh upgrades (see LP: #322482)
239
supported = self.config.getlist("View","SupportSSH")
240
if self._view.__class__.__name__ in supported:
245
""" this will check for server mode and if we run over ssh.
246
if this is the case, we will ask and spawn a additional
247
daemon (to be sure we have a spare one around in case
250
pidfile = os.path.join("/var/run/release-upgrader-sshd.pid")
251
if (not os.path.exists(pidfile) and
252
os.path.isdir("/proc") and
253
is_child_of_process_name("sshd")):
254
# check if the frontend supports ssh upgrades (see lp: #322482)
255
if not self._viewSupportsSSH():
256
logging.error("upgrade over ssh not alllowed")
257
self._view.error(_("Upgrading over remote connection not supported"),
258
_("You are running the upgrade over a "
259
"remote ssh connection with a frontend "
261
"not support this. Please try a text "
262
"mode upgrade with 'do-release-upgrade'."
265
"abort now. Please try without ssh.")
269
# ask for a spare one to start (and below 1024)
271
res = self._view.askYesNoQuestion(
272
_("Continue running under SSH?"),
273
_("This session appears to be running under ssh. "
274
"It is not recommended to perform a upgrade "
275
"over ssh currently because in case of failure "
276
"it is harder to recover.\n\n"
277
"If you continue, an additional ssh daemon will be "
278
"started at port '%s'.\n"
279
"Do you want to continue?") % port)
283
res = subprocess.call(["/usr/sbin/sshd",
284
"-o", "PidFile=%s" % pidfile,
287
summary = _("Starting additional sshd")
288
descr = _("To make recovery in case of failure easier, an "
289
"additional sshd will be started on port '%s'. "
290
"If anything goes wrong with the running ssh "
291
"you can still connect to the additional one.\n"
293
if iptables_active():
294
cmd = "iptables -I INPUT -p tcp --dport %s -j ACCEPT" % port
296
"If you run a firewall, you may need to "
297
"temporarily open this port. As this is "
298
"potentially dangerous it's not done automatically. "
299
"You can open the port with e.g.:\n'%s'") % cmd
300
self._view.information(summary, descr)
303
def _tryUpdateSelf(self):
304
""" this is a helper that is run if we are started from a CD
305
and we have network - we will then try to fetch a update
308
from .MetaRelease import MetaReleaseCore
309
from .DistUpgradeFetcherSelf import DistUpgradeFetcherSelf
310
# check if we run from a LTS
312
if (self.release == "dapper" or
313
self.release == "hardy" or
314
self.release == "lucid" or
315
self.release == "precise"):
317
m = MetaReleaseCore(useDevelopmentRelease=False,
319
# this will timeout eventually
321
self._view.processEvents()
323
if m.new_dist is None:
324
logging.error("No new dist found")
327
progress = self._view.getAcquireProgress()
328
fetcher = DistUpgradeFetcherSelf(new_dist=m.new_dist,
330
options=self.options,
334
def _pythonSymlinkCheck(self):
335
""" sanity check that /usr/bin/python points to the default
336
python version. Users tend to modify this symlink, which
337
breaks stuff in obscure ways (Ubuntu #75557).
339
logging.debug("_pythonSymlinkCheck run")
340
if os.path.exists('/usr/share/python/debian_defaults'):
341
config = SafeConfigParser()
342
config.readfp(open('/usr/share/python/debian_defaults'))
344
expected_default = config.get('DEFAULT', 'default-version')
345
except NoOptionError:
346
logging.debug("no default version for python found in '%s'" % config)
349
fs_default_version = os.readlink('/usr/bin/python')
351
logging.error("os.readlink failed (%s)" % e)
353
if not fs_default_version in (expected_default, os.path.join('/usr/bin', expected_default)):
354
logging.debug("python symlink points to: '%s', but expected is '%s' or '%s'" % (fs_default_version, expected_default, os.path.join('/usr/bin', expected_default)))
360
""" initial cache opening, sanity checking, network checking """
361
# first check if that is a good upgrade
362
self.release = release = subprocess.Popen(["lsb_release","-c","-s"],
363
stdout=subprocess.PIPE).communicate()[0].strip()
364
logging.debug("lsb-release: '%s'" % release)
365
if not (release == self.fromDist or release == self.toDist):
366
logging.error("Bad upgrade: '%s' != '%s' " % (release, self.fromDist))
367
self._view.error(_("Can not upgrade"),
368
_("An upgrade from '%s' to '%s' is not "
369
"supported with this tool." % (release, self.toDist)))
373
if self.config.getWithDefault("Aufs", "EnableFullOverlay", False):
374
aufs_rw_dir = self.config.get("Aufs","RWDir")
375
if not setupAufs(aufs_rw_dir):
376
logging.error("aufs setup failed")
377
self._view.error(_("Sandbox setup failed"),
378
_("It was not possible to create the sandbox "
382
# all good, tell the user about the sandbox mode
383
logging.info("running in aufs overlay mode")
384
self._view.information(_("Sandbox mode"),
385
_("This upgrade is running in sandbox "
386
"(test) mode. All changes are written "
387
"to '%s' and will be lost on the next "
389
"*No* changes written to a system directory "
390
"from now until the next reboot are "
391
"permanent.") % aufs_rw_dir)
393
# setup backports (if we have them)
394
if self.options and self.options.havePrerequists:
395
backportsdir = os.getcwd()+"/backports"
396
logging.info("using backports in '%s' " % backportsdir)
397
logging.debug("have: %s" % glob.glob(backportsdir+"/*.udeb"))
398
if os.path.exists(backportsdir+"/usr/bin/dpkg"):
399
apt_pkg.config.set("Dir::Bin::dpkg",backportsdir+"/usr/bin/dpkg");
400
if os.path.exists(backportsdir+"/usr/lib/apt/methods"):
401
apt_pkg.config.set("Dir::Bin::methods",backportsdir+"/usr/lib/apt/methods")
402
conf = backportsdir+"/etc/apt/apt.conf.d/01ubuntu"
403
if os.path.exists(conf):
404
logging.debug("adding config '%s'" % conf)
405
apt_pkg.ReadConfigFile(apt_pkg.config, conf)
407
# do the ssh check and warn if we run under ssh
409
# check python version
410
if not self._pythonSymlinkCheck():
411
logging.error("pythonSymlinkCheck() failed, aborting")
412
self._view.error(_("Can not upgrade"),
413
_("Your python install is corrupted. "
414
"Please fix the '/usr/bin/python' symlink."))
419
except SystemError as e:
420
logging.error("openCache() failed: '%s'" % e)
422
if not self.cache.sanityCheck(self._view):
425
# now figure out if we need to go into desktop or
426
# server mode - we use a heuristic for this
427
self.serverMode = self.cache.needServerMode()
429
os.environ["RELEASE_UPGRADE_MODE"] = "server"
431
os.environ["RELEASE_UPGRADE_MODE"] = "desktop"
433
if not self.checkViewDepends():
434
logging.error("checkViewDepends() failed")
437
if os.path.exists("/usr/bin/debsig-verify"):
438
logging.error("debsig-verify is installed")
439
self._view.error(_("Package 'debsig-verify' is installed"),
440
_("The upgrade can not continue with that "
441
"package installed.\n"
442
"Please remove it with synaptic "
443
"or 'apt-get remove debsig-verify' first "
444
"and run the upgrade again."))
447
from .DistUpgradeMain import SYSTEM_DIRS
448
for systemdir in SYSTEM_DIRS:
449
if os.path.exists(systemdir) and not os.access(systemdir, os.W_OK):
450
logging.error("%s not writable" % systemdir)
452
_("Can not write to '%s'") % systemdir,
453
_("Its not possible to write to the system directory "
454
"'%s' on your system. The upgrade can not "
456
"Please make sure that the system directory is "
457
"writable.") % systemdir)
461
# FIXME: we may try to find out a bit more about the network
462
# connection here and ask more intelligent questions
463
if self.aptcdrom and self.options and self.options.withNetwork == None:
464
res = self._view.askYesNoQuestion(_("Include latest updates from the Internet?"),
465
_("The upgrade system can use the internet to "
466
"automatically download "
467
"the latest updates and install them during the "
468
"upgrade. If you have a network connection this is "
469
"highly recommended.\n\n"
470
"The upgrade will take longer, but when "
471
"it is complete, your system will be fully up to "
472
"date. You can choose not to do this, but you "
473
"should install the latest updates soon after "
475
"If you answer 'no' here, the network is not "
478
self.useNetwork = res
479
self.config.set("Options","withNetwork", str(self.useNetwork))
480
logging.debug("useNetwork: '%s' (selected by user)" % res)
482
self._tryUpdateSelf()
485
def _sourcesListEntryDownloadable(self, entry):
487
helper that checks if a sources.list entry points to
488
something downloadable
490
logging.debug("verifySourcesListEntry: %s" % entry)
491
# no way to verify without network
492
if not self.useNetwork:
493
logging.debug("skiping downloadable check (no network)")
495
# check if the entry points to something we can download
496
uri = "%s/dists/%s/Release" % (entry.uri, entry.dist)
497
return url_downloadable(uri, logging.debug)
499
def rewriteSourcesList(self, mirror_check=True):
500
logging.debug("rewriteSourcesList()")
502
sync_components = self.config.getlist("Sources","Components")
504
# skip mirror check if special environment is set
505
# (useful for server admins with internal repos)
506
if (self.config.getWithDefault("Sources","AllowThirdParty",False) or
507
"RELEASE_UPRADER_ALLOW_THIRD_PARTY" in os.environ):
508
logging.warning("mirror check skipped, *overriden* via config")
511
# check if we need to enable main
512
if mirror_check == True and self.useNetwork:
513
# now check if the base-meta pkgs are available in
514
# the archive or only available as "now"
515
# -> if not that means that "main" is missing and we
517
for pkgname in self.config.getlist("Distro","BaseMetaPkgs"):
518
if ((not pkgname in self.cache or
519
not self.cache[pkgname].candidate or
520
len(self.cache[pkgname].candidate.origins) == 0)
522
(self.cache[pkgname].candidate and
523
len(self.cache[pkgname].candidate.origins) == 1 and
524
self.cache[pkgname].candidate.origins[0].archive == "now")
526
logging.debug("BaseMetaPkg '%s' has no candidateOrigin" % pkgname)
528
distro = get_distro()
529
distro.get_sources(self.sources)
530
distro.enable_component("main")
531
except NoDistroTemplateException:
532
# fallback if everything else does not work,
533
# we replace the sources.list with a single
534
# line to ubuntu-main
535
logging.warning('get_distro().enable_component("man") failed, overwriting sources.list instead as last resort')
536
s = "# auto generated by update-manager"
537
s += "deb http://archive.ubuntu.com/ubuntu %s main restricted" % self.toDist
538
s += "deb http://archive.ubuntu.com/ubuntu %s-updates main restricted" % self.toDist
539
s += "deb http://security.ubuntu.com/ubuntu %s-security main restricted" % self.toDist
540
open("/etc/apt/sources.list","w").write(s)
543
# this must map, i.e. second in "from" must be the second in "to"
544
# (but they can be different, so in theory we could exchange
545
# component names here)
546
pockets = self.config.getlist("Sources","Pockets")
547
fromDists = [self.fromDist] + ["%s-%s" % (self.fromDist, x)
549
toDists = [self.toDist] + ["%s-%s" % (self.toDist,x)
551
self.sources_disabled = False
553
# look over the stuff we have
555
# collect information on what components (main,universe) are enabled for what distro (sub)version
556
# e.g. found_components = { 'hardy':set("main","restricted"), 'hardy-updates':set("main") }
557
self.found_components = {}
558
for entry in self.sources.list[:]:
560
# ignore invalid records or disabled ones
561
if entry.invalid or entry.disabled:
564
# we disable breezy cdrom sources to make sure that demoted
565
# packages are removed
566
if entry.uri.startswith("cdrom:") and entry.dist == self.fromDist:
567
logging.debug("disabled '%s' cdrom entry (dist == fromDist)" % entry)
568
entry.disabled = True
570
# check if there is actually a lists file for them available
571
# and disable them if not
572
elif entry.uri.startswith("cdrom:"):
574
listdir = apt_pkg.config.find_dir("Dir::State::lists")
575
if not os.path.exists("%s/%s%s_%s_%s" %
577
apt_pkg.URItoFileName(entry.uri),
581
logging.warning("disabling cdrom source '%s' because it has no Release file" % entry)
582
entry.disabled = True
585
# special case for archive.canonical.com that needs to
586
# be rewritten (for pre-gutsy upgrades)
587
cdist = "%s-commercial" % self.fromDist
588
if (not entry.disabled and
589
entry.uri.startswith("http://archive.canonical.com") and
590
entry.dist == cdist):
591
entry.dist = self.toDist
592
entry.comps = ["partner"]
593
logging.debug("transitioned commercial to '%s' " % entry)
596
# special case for landscape.canonical.com because they
597
# don't use a standard archive layout (gutsy->hardy)
598
if (not entry.disabled and
599
entry.uri.startswith("http://landscape.canonical.com/packages/%s" % self.fromDist)):
600
logging.debug("commenting landscape.canonical.com out")
601
entry.disabled = True
604
# handle upgrades from a EOL release and check if there
605
# is a supported release available
606
if (not entry.disabled and
607
"old-releases.ubuntu.com/" in entry.uri):
608
logging.debug("upgrade from old-releases.ubuntu.com detected")
609
# test country mirror first, then archive.u.c
610
for uri in ["http://%sarchive.ubuntu.com/ubuntu" % country_mirror(),
611
"http://archive.ubuntu.com/ubuntu"]:
612
test_entry = copy.copy(entry)
614
test_entry.dist = self.toDist
615
if self._sourcesListEntryDownloadable(test_entry):
616
logging.info("transition from old-release.u.c to %s" % uri)
620
logging.debug("examining: '%s'" % get_string_with_no_auth_from_source_entry(entry))
621
# check if it's a mirror (or official site)
622
validMirror = self.isMirror(entry.uri)
623
thirdPartyMirror = not mirror_check or self.isThirdPartyMirror(entry.uri)
624
if validMirror or thirdPartyMirror:
625
# disabled/security/commercial/extras are special cases
626
# we use validTo/foundToDist to figure out if we have a
627
# main archive mirror in the sources.list or if we
630
if (entry.disabled or
631
entry.type == "deb-src" or
632
"/security.ubuntu.com" in entry.uri or
633
"%s-security" % self.fromDist in entry.dist or
634
"/archive.canonical.com" in entry.uri or
635
"/extras.ubuntu.com" in entry.uri):
637
if entry.dist in toDists:
638
# so the self.sources.list is already set to the new
640
logging.debug("entry '%s' is already set to new dist" % get_string_with_no_auth_from_source_entry(entry))
641
foundToDist |= validTo
642
elif entry.dist in fromDists:
643
foundToDist |= validTo
644
entry.dist = toDists[fromDists.index(entry.dist)]
645
logging.debug("entry '%s' updated to new dist" % get_string_with_no_auth_from_source_entry(entry))
646
elif entry.type == 'deb-src':
649
# disable all entries that are official but don't
650
# point to either "to" or "from" dist
651
entry.disabled = True
652
self.sources_disabled = True
653
logging.debug("entry '%s' was disabled (unknown dist)" % get_string_with_no_auth_from_source_entry(entry))
655
# if we make it to this point, we have a official or third-party mirror
657
# check if the arch is powerpc or sparc and if so, transition
658
# to ports.ubuntu.com (powerpc got demoted in gutsy, sparc
660
if (entry.type == "deb" and
661
not "ports.ubuntu.com" in entry.uri and
662
(self.arch == "powerpc" or self.arch == "sparc")):
663
logging.debug("moving %s source entry to 'ports.ubuntu.com' " % self.arch)
664
entry.uri = "http://ports.ubuntu.com/ubuntu-ports/"
666
# gather what components are enabled and are inconsitent
667
for d in ["%s" % self.toDist,
668
"%s-updates" % self.toDist,
669
"%s-security" % self.toDist]:
670
# create entry if needed, ignore disabled
671
# entries and deb-src
672
self.found_components.setdefault(d,set())
673
if (not entry.disabled and entry.dist == d and
674
entry.type == "deb"):
675
for comp in entry.comps:
676
# only sync components we know about
677
if not comp in sync_components:
679
self.found_components[d].add(comp)
682
# disable anything that is not from a official mirror or a whitelisted third party
683
if entry.dist == self.fromDist:
684
entry.dist = self.toDist
685
entry.comment += " " + _("disabled on upgrade to %s") % self.toDist
686
entry.disabled = True
687
self.sources_disabled = True
688
logging.debug("entry '%s' was disabled (unknown mirror)" % get_string_with_no_auth_from_source_entry(entry))
690
# now go over the list again and check for missing components
691
# in $dist-updates and $dist-security and add them
692
for entry in self.sources.list[:]:
693
# skip all comps that are not relevant (including e.g. "hardy")
694
if (entry.invalid or entry.disabled or entry.type == "deb-src" or
695
entry.uri.startswith("cdrom:") or entry.dist == self.toDist):
697
# now check for "$dist-updates" and "$dist-security" and add any inconsistencies
698
if self.found_components.has_key(entry.dist):
699
component_diff = self.found_components[self.toDist]-self.found_components[entry.dist]
701
logging.info("fixing components inconsistency from '%s'" % get_string_with_no_auth_from_source_entry(entry))
702
entry.comps.extend(list(component_diff))
703
logging.info("to new entry '%s'" % get_string_with_no_auth_from_source_entry(entry))
704
del self.found_components[entry.dist]
707
def updateSourcesList(self):
708
logging.debug("updateSourcesList()")
709
self.sources = SourcesList(matcherPath=".")
710
if not self.rewriteSourcesList(mirror_check=True):
711
logging.error("No valid mirror found")
712
res = self._view.askYesNoQuestion(_("No valid mirror found"),
713
_("While scanning your repository "
714
"information no mirror entry for "
715
"the upgrade was found. "
716
"This can happen if you run a internal "
717
"mirror or if the mirror information is "
719
"Do you want to rewrite your "
720
"'sources.list' file anyway? If you choose "
721
"'Yes' here it will update all '%s' to '%s' "
723
"If you select 'No' the upgrade will cancel."
724
) % (self.fromDist, self.toDist))
726
# re-init the sources and try again
727
self.sources = SourcesList(matcherPath=".")
728
# its ok if rewriteSourcesList fails here if
729
# we do not use a network, the sources.list may be empty
730
if (not self.rewriteSourcesList(mirror_check=False)
731
and self.useNetwork):
732
#hm, still nothing useful ...
733
prim = _("Generate default sources?")
734
secon = _("After scanning your 'sources.list' no "
735
"valid entry for '%s' was found.\n\n"
736
"Should default entries for '%s' be "
737
"added? If you select 'No', the upgrade "
738
"will cancel.") % (self.fromDist, self.toDist)
739
if not self._view.askYesNoQuestion(prim, secon):
742
# add some defaults here
743
# FIXME: find mirror here
744
logging.info("generate new default sources.list")
745
uri = "http://archive.ubuntu.com/ubuntu"
746
comps = ["main","restricted"]
747
self.sources.add("deb", uri, self.toDist, comps)
748
self.sources.add("deb", uri, self.toDist+"-updates", comps)
749
self.sources.add("deb",
750
"http://security.ubuntu.com/ubuntu/",
751
self.toDist+"-security", comps)
755
# write (well, backup first ;) !
756
self.sources.backup(self.sources_backup_ext)
759
# re-check if the written self.sources are valid, if not revert and
761
# TODO: check if some main packages are still available or if we
762
# accidentally shot them, if not, maybe offer to write a standard
765
sourceslist = apt_pkg.SourceList()
766
sourceslist.read_main_list()
768
logging.error("Repository information invalid after updating (we broke it!)")
769
self._view.error(_("Repository information invalid"),
770
_("Upgrading the repository information "
771
"resulted in a invalid file so a bug "
772
"reporting process is being started."))
773
subprocess.Popen(["apport-bug", "update-manager"])
776
if self.sources_disabled:
777
self._view.information(_("Third party sources disabled"),
778
_("Some third party entries in your sources.list "
779
"were disabled. You can re-enable them "
780
"after the upgrade with the "
781
"'software-properties' tool or "
782
"your package manager."
786
def _logChanges(self):
788
logging.debug("About to apply the following changes")
794
for pkg in self.cache:
795
if pkg.marked_install: inst.append(pkg.name)
796
elif pkg.marked_upgrade: up.append(pkg.name)
797
elif pkg.marked_delete: rm.append(pkg.name)
798
elif (pkg.is_installed and pkg.is_upgradable): held.append(pkg.name)
799
elif pkg.is_installed and pkg.marked_keep: keep.append(pkg.name)
800
logging.debug("Keep at same version: %s" % " ".join(keep))
801
logging.debug("Upgradable, but held- back: %s" % " ".join(held))
802
logging.debug("Remove: %s" % " ".join(rm))
803
logging.debug("Install: %s" % " ".join(inst))
804
logging.debug("Upgrade: %s" % " ".join(up))
807
def doPostInitialUpdate(self):
808
# check if we have packages in ReqReinst state that are not
810
logging.debug("doPostInitialUpdate")
811
self.quirks.run("PostInitialUpdate")
812
if len(self.cache.reqReinstallPkgs) > 0:
813
logging.warning("packages in reqReinstall state, trying to fix")
814
self.cache.fixReqReinst(self._view)
816
if len(self.cache.reqReinstallPkgs) > 0:
817
reqreinst = self.cache.reqReinstallPkgs
818
header = ngettext("Package in inconsistent state",
819
"Packages in inconsistent state",
821
summary = ngettext("The package '%s' is in an inconsistent "
822
"state and needs to be reinstalled, but "
823
"no archive can be found for it. "
824
"Please reinstall the package manually "
825
"or remove it from the system.",
826
"The packages '%s' are in an inconsistent "
827
"state and need to be reinstalled, but "
828
"no archive can be found for them. "
829
"Please reinstall the packages manually "
830
"or remove them from the system.",
831
len(reqreinst)) % ", ".join(reqreinst)
832
self._view.error(header, summary)
834
# FIXME: check out what packages are downloadable etc to
835
# compare the list after the update again
836
self.obsolete_pkgs = self.cache._getObsoletesPkgs()
837
self.foreign_pkgs = self.cache._getForeignPkgs(self.origin, self.fromDist, self.toDist)
839
self.tasks = self.cache.installedTasks
840
logging.debug("Foreign: %s" % " ".join(self.foreign_pkgs))
841
logging.debug("Obsolete: %s" % " ".join(self.obsolete_pkgs))
844
def doUpdate(self, showErrors=True, forceRetries=None):
845
logging.debug("running doUpdate() (showErrors=%s)" % showErrors)
846
if not self.useNetwork:
847
logging.debug("doUpdate() will not use the network because self.useNetwork==false")
849
self.cache._list.read_main_list()
850
progress = self._view.getAcquireProgress()
851
# FIXME: also remove all files from the lists partial dir!
853
if forceRetries is not None:
854
maxRetries=forceRetries
856
maxRetries = self.config.getint("Network","MaxRetries")
857
while currentRetry < maxRetries:
859
self.cache.update(progress)
860
except (SystemError, IOError) as e:
861
logging.error("IOError/SystemError in cache.update(): '%s'. Retrying (currentRetry: %s)" % (e,currentRetry))
864
# no exception, so all was fine, we are done
867
logging.error("doUpdate() failed completely")
869
self._view.error(_("Error during update"),
870
_("A problem occurred during the update. "
871
"This is usually some sort of network "
872
"problem, please check your network "
873
"connection and retry."), "%s" % e)
877
def _checkFreeSpace(self):
878
" this checks if we have enough free space on /var and /usr"
879
err_sum = _("Not enough free disk space")
880
err_long= _("The upgrade has aborted. "
881
"The upgrade needs a total of %s free space on disk '%s'. "
882
"Please free at least an additional %s of disk "
884
"Empty your trash and remove temporary "
885
"packages of former installations using "
886
"'sudo apt-get clean'.")
888
if self.config.getWithDefault("FreeSpace","SkipCheck",False):
889
logging.warning("free space check skipped via config override")
892
with_snapshots = self._is_apt_btrfs_snapshot_supported()
894
self.cache.checkFreeSpace(with_snapshots)
895
except NotEnoughFreeSpaceError as e:
896
# ok, showing multiple error dialog sucks from the UI
897
# perspective, but it means we do not need to break the
899
for required in e.free_space_required_list:
900
self._view.error(err_sum, err_long % (required.size_total,
902
required.size_needed,
908
def askDistUpgrade(self):
909
self._view.updateStatus(_("Calculating the changes"))
910
if not self.cache.distUpgrade(self._view, self.serverMode, self._partialUpgrade):
914
if not self.cache.installTasks(self.tasks):
917
# show changes and confirm
918
changes = self.cache.get_changes()
919
self._view.processEvents()
921
# log the changes for debugging
923
self._view.processEvents()
925
# check if we have enough free space
926
if not self._checkFreeSpace():
928
self._view.processEvents()
931
self.installed_demotions = self.cache.get_installed_demoted_packages()
932
if len(self.installed_demotions) > 0:
933
self.installed_demotions.sort()
934
logging.debug("demoted: '%s'" % " ".join([x.name for x in self.installed_demotions]))
935
logging.debug("found components: %s" % self.found_components)
938
self._view.processEvents()
941
res = self._view.confirmChanges(_("Do you want to start the upgrade?"),
943
self.installed_demotions,
944
self.cache.requiredDownload)
947
def _disableAptCronJob(self):
948
if os.path.exists("/etc/cron.daily/apt"):
949
#self._aptCronJobPerms = os.stat("/etc/cron.daily/apt")[ST_MODE]
950
logging.debug("disabling apt cron job (%s)" % oct(self._aptCronJobPerms))
951
os.chmod("/etc/cron.daily/apt",0o644)
952
def _enableAptCronJob(self):
953
if os.path.exists("/etc/cron.daily/apt"):
954
logging.debug("enabling apt cron job")
955
os.chmod("/etc/cron.daily/apt", self._aptCronJobPerms)
957
def doDistUpgradeFetching(self):
958
# ensure that no apt cleanup is run during the download/install
959
self._disableAptCronJob()
962
fprogress = self._view.getAcquireProgress()
963
#iprogress = self._view.getInstallProgress(self.cache)
965
url = self.config.getWithDefault("Distro","SlideshowUrl",None)
968
lang = locale.getdefaultlocale()[0].split('_')[0]
970
logging.exception("getdefaultlocale")
972
self._view.getHtmlView().open("%s#locale=%s" % (url, lang))
973
# retry the fetching in case of errors
974
maxRetries = self.config.getint("Network","MaxRetries")
975
# FIXME: we get errors like
976
# "I wasn't able to locate file for the %s package"
977
# here sometimes. its unclear why and not reproducible, the
978
# current theory is that for some reason the file is not
979
# considered trusted at the moment
980
# pkgAcquireArchive::QueueNext() runs debReleaseIndex::IsTrused()
981
# (the later just checks for the existence of the .gpg file)
983
# the fact that we get a pm and fetcher here confuses something
985
# POSSIBLE workaround: keep the list-dir locked so that
986
# no apt-get update can run outside from the release
988
user_canceled = False
989
while currentRetry < maxRetries:
991
pm = apt_pkg.PackageManager(self.cache._depcache)
992
fetcher = apt_pkg.Acquire(fprogress)
993
self.cache._fetch_archives(fetcher, pm)
994
except apt.cache.FetchCancelledException as e:
995
logging.info("user canceled")
999
# fetch failed, will be retried
1000
logging.error("IOError in cache.commit(): '%s'. Retrying (currentTry: %s)" % (e,currentRetry))
1005
# maximum fetch-retries reached without a successful commit
1007
self._view.information(_("Upgrade canceled"),
1008
_("The upgrade will cancel now and the "
1009
"original system state will be restored. "
1010
"You can resume the upgrade at a later "
1013
logging.error("giving up on fetching after maximum retries")
1014
self._view.error(_("Could not download the upgrades"),
1015
_("The upgrade has aborted. Please check your "
1016
"Internet connection or "
1017
"installation media and try again. All files "
1018
"downloaded so far have been kept."),
1020
# abort here because we want our sources.list back
1021
self._enableAptCronJob()
1024
def enableApport(self, fname="/etc/default/apport"):
1025
""" enable apport """
1026
# startup apport just until the next reboot, it has the magic
1027
# "force_start" environment for this
1028
subprocess.call(["service","apport","start","force_start=1"])
1030
def _is_apt_btrfs_snapshot_supported(self):
1031
""" check if apt-btrfs-snapshot is usable """
1033
import apt_btrfs_snapshot
1037
apt_btrfs = apt_btrfs_snapshot.AptBtrfsSnapshot()
1038
res = apt_btrfs.snapshots_supported()
1040
logging.exception("failed to check btrfs support")
1042
logging.debug("apt btrfs snapshots supported: %s" % res)
1045
def _maybe_create_apt_btrfs_snapshot(self):
1046
""" create btrfs snapshot (if btrfs layout is there) """
1047
if not self._is_apt_btrfs_snapshot_supported():
1049
import apt_btrfs_snapshot
1050
apt_btrfs = apt_btrfs_snapshot.AptBtrfsSnapshot()
1051
prefix = "release-upgrade-%s-" % self.toDist
1052
res = apt_btrfs.create_btrfs_root_snapshot(prefix)
1053
logging.info("creating snapshot '%s' (success=%s)" % (prefix, res))
1055
def doDistUpgrade(self):
1056
# check if we want apport running during the upgrade
1057
if self.config.getWithDefault("Distro","EnableApport", False):
1060
# add debug code only here
1061
#apt_pkg.config.set("Debug::pkgDpkgPM", "1")
1062
#apt_pkg.config.set("Debug::pkgOrderList", "1")
1063
#apt_pkg.config.set("Debug::pkgPackageManager", "1")
1067
fprogress = self._view.getAcquireProgress()
1068
iprogress = self._view.getInstallProgress(self.cache)
1069
# retry the fetching in case of errors
1070
maxRetries = self.config.getint("Network","MaxRetries")
1071
if not self._partialUpgrade:
1072
self.quirks.run("StartUpgrade")
1073
# FIXME: take this into account for diskspace calculation
1074
self._maybe_create_apt_btrfs_snapshot()
1076
while currentRetry < maxRetries:
1078
res = self.cache.commit(fprogress,iprogress)
1079
logging.debug("cache.commit() returned %s" % res)
1080
except SystemError as e:
1081
logging.error("SystemError from cache.commit(): %s" % e)
1082
# if its a ordering bug we can cleanly revert to
1083
# the previous release, no packages have been installed
1084
# yet (LP: #328655, #356781)
1085
if os.path.exists("/var/run/update-manager-apt-exception"):
1086
e = open("/var/run/update-manager-apt-exception").read()
1087
logging.error("found exception: '%s'" % e)
1088
# if its a ordering bug we can cleanly revert but we need to write
1089
# a marker for the parent process to know its this kind of error
1090
pre_configure_errors = [
1091
"E:Internal Error, Could not perform immediate configuration",
1092
"E:Couldn't configure pre-depend "]
1093
for preconf_error in pre_configure_errors:
1094
if str(e).startswith(preconf_error):
1095
logging.debug("detected preconfigure error, restorting state")
1096
self._enableAptCronJob()
1097
# FIXME: strings are not good, but we are in string freeze
1099
msg = _("Error during commit")
1100
msg += "\n'%s'\n" % str(e)
1101
msg += _("Restoring original system state")
1102
self._view.error(_("Could not install the upgrades"), msg)
1103
# abort() exits cleanly
1106
# invoke the frontend now and show a error message
1107
msg = _("The upgrade has aborted. Your system "
1108
"could be in an unusable state. A recovery "
1109
"will run now (dpkg --configure -a).")
1110
if not self._partialUpgrade:
1111
if not run_apport():
1112
msg += _("\n\nPlease report this bug in a browser at "
1113
"http://bugs.launchpad.net/ubuntu/+source/update-manager/+filebug "
1114
"and attach the files in /var/log/dist-upgrade/ "
1115
"to the bug report.\n"
1117
self._view.error(_("Could not install the upgrades"), msg)
1118
# installing the packages failed, can't be retried
1119
cmd = ["dpkg","--configure","-a"]
1120
if os.environ.get("DEBIAN_FRONTEND") == "noninteractive":
1121
cmd.append("--force-confold")
1122
self._view.getTerminal().call(cmd)
1123
self._enableAptCronJob()
1125
except IOError as e:
1126
# fetch failed, will be retried
1127
logging.error("IOError in cache.commit(): '%s'. Retrying (currentTry: %s)" % (e,currentRetry))
1130
except OSError as e:
1131
logging.exception("cache.commit()")
1132
# deal gracefully with:
1133
# OSError: [Errno 12] Cannot allocate memory
1135
self._enableAptCronJob()
1136
msg = _("Error during commit")
1137
msg += "\n'%s'\n" % str(e)
1138
msg += _("Restoring original system state")
1139
self._view.error(_("Could not install the upgrades"), msg)
1140
# abort() exits cleanly
1142
# no exception, so all was fine, we are done
1143
self._enableAptCronJob()
1146
# maximum fetch-retries reached without a successful commit
1147
logging.error("giving up on fetching after maximum retries")
1148
self._view.error(_("Could not download the upgrades"),
1149
_("The upgrade has aborted. Please check your "\
1150
"Internet connection or "\
1151
"installation media and try again. "),
1153
# abort here because we want our sources.list back
1156
def doPostUpgrade(self):
1159
# run the quirks handler that does does like things adding
1160
# missing groups or similar work arounds, only do it on real
1162
self.quirks.run("PostUpgrade")
1163
# check out what packages are cruft now
1164
# use self.{foreign,obsolete}_pkgs here and see what changed
1165
now_obsolete = self.cache._getObsoletesPkgs()
1166
now_foreign = self.cache._getForeignPkgs(self.origin, self.fromDist, self.toDist)
1167
logging.debug("Obsolete: %s" % " ".join(now_obsolete))
1168
logging.debug("Foreign: %s" % " ".join(now_foreign))
1169
# now sanity check - if a base meta package is in the obsolete list now, that means
1170
# that something went wrong (see #335154) badly with the network. this should never happen, but it did happen
1171
# at least once so we add extra paranoia here
1172
for pkg in self.config.getlist("Distro","BaseMetaPkgs"):
1173
if pkg in now_obsolete:
1174
logging.error("the BaseMetaPkg '%s' is in the obsolete list, something is wrong, ignoring the obsoletes" % pkg)
1175
now_obsolete = set()
1177
# check if we actually want obsolete removal
1178
if not self.config.getWithDefault("Distro","RemoveObsoletes", True):
1179
logging.debug("Skipping obsolete Removal")
1182
# now get the meta-pkg specific obsoletes and purges
1183
for pkg in self.config.getlist("Distro","MetaPkgs"):
1184
if self.cache.has_key(pkg) and self.cache[pkg].is_installed:
1185
self.forced_obsoletes.extend(self.config.getlist(pkg,"ForcedObsoletes"))
1186
# now add the obsolete kernels to the forced obsoletes
1187
self.forced_obsoletes.extend(self.cache.identifyObsoleteKernels())
1188
logging.debug("forced_obsoletes: %s", self.forced_obsoletes)
1190
# mark packages that are now obsolete (and where not obsolete
1191
# before) to be deleted. make sure to not delete any foreign
1192
# (that is, not from ubuntu) packages
1194
# we can only do the obsoletes calculation here if we use a
1195
# network. otherwise after rewriting the sources.list everything
1196
# that is not on the CD becomes obsolete (not-downloadable)
1197
remove_candidates = now_obsolete - self.obsolete_pkgs
1199
# initial remove candidates when no network is used should
1200
# be the demotions to make sure we don't leave potential
1201
# unsupported software
1202
remove_candidates = set([p.name for p in self.installed_demotions])
1203
remove_candidates |= set(self.forced_obsoletes)
1205
# no go for the unused dependencies
1206
unused_dependencies = self.cache._getUnusedDependencies()
1207
logging.debug("Unused dependencies: %s" %" ".join(unused_dependencies))
1208
remove_candidates |= set(unused_dependencies)
1210
# see if we actually have to do anything here
1211
if not self.config.getWithDefault("Distro","RemoveObsoletes", True):
1212
logging.debug("Skipping RemoveObsoletes as stated in the config")
1213
remove_candidates = set()
1214
logging.debug("remove_candidates: '%s'" % remove_candidates)
1215
logging.debug("Start checking for obsolete pkgs")
1216
progress = self._view.getOpCacheProgress()
1217
for (i, pkgname) in enumerate(remove_candidates):
1218
progress.update((i/float(len(remove_candidates)))*100.0)
1219
if pkgname not in self.foreign_pkgs:
1220
self._view.processEvents()
1221
if not self.cache.tryMarkObsoleteForRemoval(pkgname, remove_candidates, self.foreign_pkgs):
1222
logging.debug("'%s' scheduled for remove but not safe to remove, skipping", pkgname)
1223
logging.debug("Finish checking for obsolete pkgs")
1227
changes = self.cache.get_changes()
1228
logging.debug("The following packages are marked for removal: %s" % " ".join([pkg.name for pkg in changes]))
1229
summary = _("Remove obsolete packages?")
1230
actions = [_("_Keep"), _("_Remove")]
1231
# FIXME Add an explanation about what obsolete packages are
1232
#explanation = _("")
1233
if (len(changes) > 0 and
1234
self._view.confirmChanges(summary, changes, [], 0, actions, False)):
1235
fprogress = self._view.getAcquireProgress()
1236
iprogress = self._view.getInstallProgress(self.cache)
1238
self.cache.commit(fprogress,iprogress)
1239
except (SystemError, IOError) as e:
1240
logging.error("cache.commit() in doPostUpgrade() failed: %s" % e)
1241
self._view.error(_("Error during commit"),
1242
_("A problem occurred during the clean-up. "
1243
"Please see the below message for more "
1246
# run stuff after cleanup
1247
self.quirks.run("PostCleanup")
1248
# run the post upgrade scripts that can do fixup like xorg.conf
1249
# fixes etc - only do on real upgrades
1250
if not self._partialUpgrade:
1251
self.runPostInstallScripts()
1254
def runPostInstallScripts(self):
1256
scripts that are run in any case after the distupgrade finished
1257
whether or not it was successful
1259
# now run the post-upgrade fixup scripts (if any)
1260
for script in self.config.getlist("Distro","PostInstallScripts"):
1261
if not os.path.exists(script):
1262
logging.warning("PostInstallScript: '%s' not found" % script)
1264
logging.debug("Running PostInstallScript: '%s'" % script)
1266
# work around kde tmpfile problem where it eats permissions
1267
check_and_fix_xbit(script)
1268
self._view.getTerminal().call([script], hidden=True)
1269
except Exception as e:
1270
logging.error("got error from PostInstallScript %s (%s)" % (script, e))
1273
""" abort the upgrade, cleanup (as much as possible) """
1274
logging.debug("abort called")
1275
if hasattr(self, "sources"):
1276
self.sources.restore_backup(self.sources_backup_ext)
1277
if hasattr(self, "aptcdrom"):
1278
self.aptcdrom.restoreBackup(self.sources_backup_ext)
1279
# generate a new cache
1280
self._view.updateStatus(_("Restoring original system state"))
1285
def _checkDep(self, depstr):
1286
" check if a given depends can be satisfied "
1287
for or_group in apt_pkg.ParseDepends(depstr):
1288
logging.debug("checking: '%s' " % or_group)
1289
for dep in or_group:
1293
if not self.cache.has_key(depname):
1294
logging.error("_checkDep: '%s' not in cache" % depname)
1296
inst = self.cache[depname]
1297
instver = inst.installedVersion
1298
if (instver != None and
1299
apt_pkg.CheckDep(instver,oper,ver) == True):
1301
logging.error("depends '%s' is not satisfied" % depstr)
1304
def checkViewDepends(self):
1305
" check if depends are satisfied "
1306
logging.debug("checkViewDepends()")
1308
# now check if anything from $foo-updates is required
1309
depends = self.config.getlist("View","Depends")
1310
depends.extend(self.config.getlist(self._view.__class__.__name__,
1313
logging.debug("depends: '%s'", dep)
1314
res &= self._checkDep(dep)
1316
# FIXME: instead of error out, fetch and install it
1318
self._view.error(_("Required depends is not installed"),
1319
_("The required dependency '%s' is not "
1320
"installed. " % dep))
1324
def _verifyBackports(self):
1325
# run update (but ignore errors in case the countrymirror
1326
# substitution goes wrong, real errors will be caught later
1327
# when the cache is searched for the backport packages)
1328
backportslist = self.config.getlist("PreRequists","Packages")
1330
noCache = apt_pkg.config.find("Acquire::http::No-Cache","false")
1331
maxRetries = self.config.getint("Network","MaxRetries")
1332
while i < maxRetries:
1333
self.doUpdate(showErrors=False)
1335
for pkgname in backportslist:
1336
if not self.cache.has_key(pkgname):
1337
logging.error("Can not find backport '%s'" % pkgname)
1338
raise NoBackportsFoundException(pkgname)
1339
if self._allBackportsAuthenticated(backportslist):
1341
# FIXME: move this to some more generic place
1342
logging.debug("setting a cache control header to turn off caching temporarily")
1343
apt_pkg.config.set("Acquire::http::No-Cache","true")
1346
logging.error("pre-requists item is NOT trusted, giving up")
1348
apt_pkg.config.set("Acquire::http::No-Cache",noCache)
1351
def _allBackportsAuthenticated(self, backportslist):
1352
# check if the user overwrote the check
1353
if apt_pkg.config.find_b("APT::Get::AllowUnauthenticated",False) == True:
1354
logging.warning("skip authentication check because of APT::Get::AllowUnauthenticated==true")
1357
b = self.config.getboolean("Distro","AllowUnauthenticated")
1360
except NoOptionError:
1362
for pkgname in backportslist:
1363
pkg = self.cache[pkgname]
1364
if not pkg.candidate:
1366
for cand in pkg.candidate.origins:
1373
def isMirror(self, uri):
1374
""" check if uri is a known mirror """
1375
# deal with username:password in a netloc
1376
raw_uri = uri.rstrip("/")
1377
scheme, netloc, path, query, fragment = urlsplit(raw_uri)
1379
netloc = netloc.split("@")[1]
1380
# construct new mirror url without the username/pw
1381
uri = "%s://%s%s" % (scheme, netloc, path)
1382
for mirror in self.valid_mirrors:
1383
mirror = mirror.rstrip("/")
1384
if is_mirror(mirror, uri):
1386
# deal with mirrors like
1387
# deb http://localhost:9977/security.ubuntu.com/ubuntu intrepid-security main restricted
1388
# both apt-debtorrent and apt-cacher use this (LP: #365537)
1389
mirror_host_part = mirror.split("//")[1]
1390
if uri.endswith(mirror_host_part):
1391
logging.debug("found apt-cacher/apt-torrent style uri %s" % uri)
1395
def isThirdPartyMirror(self, uri):
1396
" check if uri is a whitelisted third-party mirror "
1397
uri = uri.rstrip("/")
1398
for mirror in self.valid_3p_mirrors:
1399
mirror = mirror.rstrip("/")
1400
if is_mirror(mirror, uri):
1404
def _getPreReqMirrorLines(self, dumb=False):
1405
" get sources.list snippet lines for the current mirror "
1407
sources = SourcesList(matcherPath=".")
1408
for entry in sources.list:
1409
if entry.invalid or entry.disabled:
1411
if (entry.type == "deb" and
1412
entry.disabled == False and
1413
self.isMirror(entry.uri) and
1414
"main" in entry.comps and
1415
"%s-updates" % self.fromDist in entry.dist and
1416
not entry.uri.startswith("http://security.ubuntu.com") and
1417
not entry.uri.startswith("http://archive.ubuntu.com") ):
1418
new_line = "deb %s %s-updates main\n" % (entry.uri, self.fromDist)
1419
if not new_line in lines:
1421
# FIXME: do we really need "dumb" mode?
1422
#if (dumb and entry.type == "deb" and
1423
# "main" in entry.comps):
1424
# lines += "deb %s %s-proposed main\n" % (entry.uri, self.fromDist)
1427
def _addPreRequistsSourcesList(self, template, out, dumb=False):
1428
" add prerequists based on template into the path outfile "
1429
# go over the sources.list and try to find a valid mirror
1430
# that we can use to add the backports dir
1431
logging.debug("writing prerequists sources.list at: '%s' " % out)
1432
outfile = open(out, "w")
1433
mirrorlines = self._getPreReqMirrorLines(dumb)
1434
for line in open(template):
1435
template = Template(line)
1436
outline = template.safe_substitute(mirror=mirrorlines)
1437
outfile.write(outline)
1438
logging.debug("adding '%s' prerequists" % outline)
1442
def getRequiredBackports(self):
1443
" download the backports specified in DistUpgrade.cfg "
1444
logging.debug("getRequiredBackports()")
1446
backportsdir = os.path.join(os.getcwd(),"backports")
1447
if not os.path.exists(backportsdir):
1448
os.mkdir(backportsdir)
1449
backportslist = self.config.getlist("PreRequists","Packages")
1451
# FIXME: this needs to be ported
1452
# if we have them on the CD we are fine
1453
if self.aptcdrom and not self.useNetwork:
1454
logging.debug("Searching for pre-requists on CDROM")
1455
p = os.path.join(self.aptcdrom.cdrompath,
1456
"dists/stable/main/dist-upgrader/binary-%s/" % apt_pkg.config.find("APT::Architecture"))
1458
for deb in glob.glob(p+"*_*.deb"):
1459
logging.debug("found pre-req '%s' to '%s'" % (deb, backportsdir))
1460
found_pkgs.add(os.path.basename(deb).split("_")[0])
1461
# now check if we got all backports on the CD
1462
if not set(backportslist).issubset(found_pkgs):
1463
logging.error("Expected backports: '%s' but got '%s'" % (set(backportslist), found_pkgs))
1466
self.cache.releaseLock()
1467
p = subprocess.Popen(
1468
["/usr/bin/dpkg", "-i", ] + glob.glob(p+"*_*.deb"),
1469
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1473
self._view.pulseProgress()
1475
self._view.pulseProgress(finished=True)
1476
self.cache.getLock()
1477
logging.info("installing backport debs exit code '%s'" % res)
1478
logging.debug("dpkg output:\n%s" % p.communicate()[0])
1481
# and re-start itself when it done
1482
return self.setupRequiredBackports()
1484
# we support PreRequists/SourcesList-$arch sections here too
1486
# logic for mirror finding works list this:
1487
# - use the mirror template from the config, then: [done]
1489
# - try to find known mirror (isMirror) and prepend it [done]
1490
# - archive.ubuntu.com is always a fallback at the end [done]
1492
# see if we find backports with that
1493
# - if not, try guessing based on URI, Trust and Dist [done]
1494
# in existing sources.list (internal mirror with no
1495
# outside connection maybe)
1497
# make sure to remove file on cancel
1499
# FIXME: use the DistUpgradeFetcherCore logic
1500
# in mirror_from_sources_list() here
1501
# (and factor that code out into a helper)
1503
conf_option = "SourcesList"
1504
if self.config.has_option("PreRequists",conf_option+"-%s" % self.arch):
1505
conf_option = conf_option + "-%s" % self.arch
1506
prereq_template = self.config.get("PreRequists",conf_option)
1507
if not os.path.exists(prereq_template):
1508
logging.error("sourceslist not found '%s'" % prereq_template)
1510
outpath = os.path.join(apt_pkg.config.find_dir("Dir::Etc::sourceparts"), prereq_template)
1511
outfile = os.path.join(apt_pkg.config.find_dir("Dir::Etc::sourceparts"), prereq_template)
1512
self._addPreRequistsSourcesList(prereq_template, outfile)
1514
self._verifyBackports()
1515
except NoBackportsFoundException as e:
1516
self._addPreRequistsSourcesList(prereq_template, outfile, dumb=True)
1518
self._verifyBackports()
1519
except NoBackportsFoundException as e:
1520
logging.warning("no backport for '%s' found" % e)
1523
# FIXME: sanity check the origin (just for safety)
1524
for pkgname in backportslist:
1525
pkg = self.cache[pkgname]
1526
# look for the right version (backport)
1527
ver = self.cache._depcache.GetCandidateVer(pkg._pkg)
1529
logging.error("No candidate for '%s'" % pkgname)
1532
if ver.FileList == None:
1533
logging.error("No ver.FileList for '%s'" % pkgname)
1536
logging.debug("marking '%s' for install" % pkgname)
1538
pkg.mark_install(auto_inst=False, auto_fix=False)
1543
res = self.cache.commit(self._view.getAcquireProgress(),
1544
self._view.getInstallProgress(self.cache))
1545
except IOError as e:
1546
logging.error("fetchArchives returned '%s'" % e)
1548
except SystemError as e:
1549
logging.error("installArchives returned '%s'" % e)
1553
logging.warning("_fetchArchives for backports returned False")
1555
# all backports done, remove the pre-requirests.list file again
1558
except Exception as e:
1559
logging.error("failed to unlink pre-requists file: '%s'" % e)
1560
return self.setupRequiredBackports()
1562
# used by both cdrom/http fetcher
1563
def setupRequiredBackports(self):
1564
# ensure that the new release upgrader uses the latest python-apt
1565
# from the backport path
1566
os.environ["PYTHONPATH"] = "/usr/lib/release-upgrader-python-apt"
1567
# copy log so that it gets not overwritten
1569
shutil.copy("/var/log/dist-upgrade/main.log",
1570
"/var/log/dist-upgrade/main_pre_req.log")
1571
# now exec self again
1572
args = sys.argv + ["--have-prerequists"]
1574
args.append("--with-network")
1576
args.append("--without-network")
1577
logging.info("restarting upgrader")
1578
#print("restarting upgrader to make use of the backports")
1579
# work around kde being clever and removing the x bit
1580
check_and_fix_xbit(sys.argv[0])
1581
os.execve(sys.argv[0],args, os.environ)
1584
def fullUpgrade(self):
1585
# sanity check (check for ubuntu-desktop, brokenCache etc)
1586
self._view.updateStatus(_("Checking package manager"))
1587
self._view.setStep(STEP_PREPARE)
1589
if not self.prepare():
1590
logging.error("self.prepared() failed")
1591
self._view.error(_("Preparing the upgrade failed"),
1592
_("Preparing the system for the upgrade "
1593
"failed so a bug reporting process is "
1595
subprocess.Popen(["apport-bug", "update-manager"])
1598
# mvo: commented out for now, see #54234, this needs to be
1599
# refactored to use a arch=any tarball
1600
if (self.config.has_section("PreRequists") and
1602
self.options.havePrerequists == False):
1603
logging.debug("need backports")
1604
# get backported packages (if needed)
1605
if not self.getRequiredBackports():
1606
self._view.error(_("Getting upgrade prerequisites failed"),
1607
_("The system was unable to get the "
1608
"prerequisites for the upgrade. "
1609
"The upgrade will abort now and restore "
1610
"the original system state.\n"
1612
"Additionally, a bug reporting process is "
1614
subprocess.Popen(["apport-bug", "update-manager"])
1617
# run a "apt-get update" now, its ok to ignore errors,
1619
# a) we disable any third party sources later
1620
# b) we check if we have valid ubuntu sources later
1621
# after we rewrite the sources.list and do a
1622
# apt-get update there too
1623
# because the (unmodified) sources.list of the user
1624
# may contain bad/unreachable entries we run only
1625
# with a single retry
1626
self.doUpdate(showErrors=False, forceRetries=1)
1629
# do pre-upgrade stuff (calc list of obsolete pkgs etc)
1630
if not self.doPostInitialUpdate():
1633
# update sources.list
1634
self._view.setStep(STEP_MODIFY_SOURCES)
1635
self._view.updateStatus(_("Updating repository information"))
1636
if not self.updateSourcesList():
1639
# add cdrom (if we have one)
1640
if (self.aptcdrom and
1641
not self.aptcdrom.add(self.sources_backup_ext)):
1642
self._view.error(_("Failed to add the cdrom"),
1643
_("Sorry, adding the cdrom was not successful."))
1646
# then update the package index files
1647
if not self.doUpdate():
1650
# then open the cache (again)
1651
self._view.updateStatus(_("Checking package manager"))
1653
# re-check server mode because we got new packages (it may happen
1654
# that the system had no sources.list entries and therefore no
1655
# desktop file information)
1656
self.serverMode = self.cache.needServerMode()
1657
# do it here as we neeed to know if we are in server or client mode
1658
self.quirks.ensure_recommends_are_installed_on_desktops()
1659
# now check if we still have some key packages available/downloadable
1660
# after the update - if not something went seriously wrong
1661
# (this happend e.g. during the intrepid->jaunty upgrade for some
1662
# users when de.archive.ubuntu.com was overloaded)
1663
for pkg in self.config.getlist("Distro","BaseMetaPkgs"):
1664
if (not self.cache.has_key(pkg) or
1665
not self.cache.anyVersionDownloadable(self.cache[pkg])):
1666
# FIXME: we could offer to add default source entries here,
1667
# but we need to be careful to not duplicate them
1668
# (i.e. the error here could be something else than
1669
# missing sources entries but network errors etc)
1670
logging.error("No '%s' available/downloadable after sources.list rewrite+update" % pkg)
1671
self._view.error(_("Invalid package information"),
1672
_("After updating your package ",
1673
"information, the essential package '%s' "
1674
"could not be located. This may be "
1675
"because you have no official mirrors "
1676
"listed in your software sources, or "
1677
"because of excessive load on the mirror "
1678
"you are using. See /etc/apt/sources.list "
1679
"for the current list of configured "
1682
"In the case of an overloaded mirror, you "
1683
"may want to try the upgrade again later.")
1685
subprocess.Popen(["apport-bug", "update-manager"])
1688
# calc the dist-upgrade and see if the removals are ok/expected
1689
# do the dist-upgrade
1690
self._view.updateStatus(_("Calculating the changes"))
1691
if not self.askDistUpgrade():
1695
self._view.setStep(STEP_FETCH)
1696
self._view.updateStatus(_("Fetching"))
1697
if not self.doDistUpgradeFetching():
1700
# now do the upgrade
1701
self._view.setStep(STEP_INSTALL)
1702
self._view.updateStatus(_("Upgrading"))
1703
if not self.doDistUpgrade():
1704
# run the post install scripts (for stuff like UUID conversion)
1705
self.runPostInstallScripts()
1706
# don't abort here, because it would restore the sources.list
1707
self._view.information(_("Upgrade complete"),
1708
_("The upgrade has completed but there "
1709
"were errors during the upgrade "
1713
# do post-upgrade stuff
1714
self._view.setStep(STEP_CLEANUP)
1715
self._view.updateStatus(_("Searching for obsolete software"))
1716
self.doPostUpgrade()
1718
# comment out cdrom source
1720
self.aptcdrom.comment_out_cdrom_entry()
1722
# done, ask for reboot
1723
self._view.setStep(STEP_REBOOT)
1724
self._view.updateStatus(_("System upgrade is complete."))
1725
# FIXME should we look into /var/run/reboot-required here?
1726
if (not inside_chroot() and
1727
self._view.confirmRestart()):
1728
subprocess.Popen("/sbin/reboot")
1733
self._view.processEvents()
1734
return self.fullUpgrade()
1736
def doPartialUpgrade(self):
1737
" partial upgrade mode, useful for repairing "
1738
self._view.setStep(STEP_PREPARE)
1739
self._view.hideStep(STEP_MODIFY_SOURCES)
1740
self._view.hideStep(STEP_REBOOT)
1741
self._partialUpgrade = True
1743
if not self.doPostInitialUpdate():
1745
if not self.askDistUpgrade():
1747
self._view.setStep(STEP_FETCH)
1748
self._view.updateStatus(_("Fetching"))
1749
if not self.doDistUpgradeFetching():
1751
self._view.setStep(STEP_INSTALL)
1752
self._view.updateStatus(_("Upgrading"))
1753
if not self.doDistUpgrade():
1754
self._view.information(_("Upgrade complete"),
1755
_("The upgrade has completed but there "
1756
"were errors during the upgrade "
1759
self._view.setStep(STEP_CLEANUP)
1760
if not self.doPostUpgrade():
1761
self._view.information(_("Upgrade complete"),
1762
_("The upgrade has completed but there "
1763
"were errors during the upgrade "
1767
if os.path.exists(REBOOT_REQUIRED_FILE):
1768
# we can not talk to session management here, we run as root
1769
if self._view.confirmRestart():
1770
subprocess.Popen("/sbin/reboot")
1772
self._view.information(_("Upgrade complete"),
1773
_("The partial upgrade was completed."))
1777
if __name__ == "__main__":
1778
from .DistUpgradeViewText import DistUpgradeViewText
1779
logging.basicConfig(level=logging.DEBUG)
1780
v = DistUpgradeViewText()
1781
dc = DistUpgradeController(v)
1783
dc._disableAptCronJob()
1784
dc._enableAptCronJob()
1785
#dc._addRelatimeToFstab()
1787
#dc.askDistUpgrade()
1788
#dc._checkFreeSpace()
1790
#dc._checkAdminGroup()
1791
#dc._rewriteAptPeriodic(2)