~ubuntu-core-dev/update-manager/main

« back to all changes in this revision

Viewing changes to DistUpgrade/DistUpgradeController.py

merge from move-changelogs branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# DistUpgradeController.py 
 
2
#  
 
3
#  Copyright (c) 2004-2008 Canonical
 
4
#  
 
5
#  Author: Michael Vogt <michael.vogt@ubuntu.com>
 
6
 
7
#  This program is free software; you can redistribute it and/or 
 
8
#  modify it under the terms of the GNU General Public License as 
 
9
#  published by the Free Software Foundation; either version 2 of the
 
10
#  License, or (at your option) any later version.
 
11
 
12
#  This program is distributed in the hope that it will be useful,
 
13
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
#  GNU General Public License for more details.
 
16
 
17
#  You should have received a copy of the GNU General Public License
 
18
#  along with this program; if not, write to the Free Software
 
19
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 
20
#  USA
 
21
 
 
22
 
 
23
from __future__ import absolute_import, print_function
 
24
 
 
25
import warnings
 
26
warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning)
 
27
import apt
 
28
import apt_pkg
 
29
import sys
 
30
import os
 
31
import subprocess
 
32
import locale
 
33
import logging
 
34
import shutil
 
35
import glob
 
36
import time
 
37
import copy
 
38
try:
 
39
    # >= 3.0
 
40
    from configparser import NoOptionError
 
41
    if sys.version >= '3.2':
 
42
        from configparser import ConfigParser as SafeConfigParser
 
43
    else:
 
44
        from configparser import SafeConfigParser
 
45
except ImportError:
 
46
    # < 3.0
 
47
    from ConfigParser import SafeConfigParser, NoOptionError
 
48
from .utils import (country_mirror,
 
49
                    url_downloadable,
 
50
                    check_and_fix_xbit,
 
51
                    get_arch,
 
52
                    iptables_active,
 
53
                    inside_chroot,
 
54
                    get_string_with_no_auth_from_source_entry,
 
55
                    is_child_of_process_name)
 
56
from string import Template
 
57
try:
 
58
    from urllib.parse import urlsplit
 
59
except ImportError:
 
60
    from urlparse import urlsplit
 
61
 
 
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
 
68
 
 
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
 
76
 
 
77
from .sourceslist import SourcesList, is_mirror
 
78
from .distro import get_distro, NoDistroTemplateException
 
79
 
 
80
from .DistUpgradeGettext import gettext as _
 
81
from .DistUpgradeGettext import ngettext
 
82
import gettext
 
83
 
 
84
from .DistUpgradeCache import (CacheExceptionDpkgInterrupted,
 
85
                               CacheExceptionLockingFailed,
 
86
                               NotEnoughFreeSpaceError)
 
87
from .DistUpgradeApport import run_apport
 
88
 
 
89
REBOOT_REQUIRED_FILE = "/var/run/reboot-required"
 
90
 
 
91
class NoBackportsFoundException(Exception):
 
92
    pass
 
93
 
 
94
 
 
95
class DistUpgradeController(object):
 
96
    """ this is the controller that does most of the work """
 
97
    
 
98
    def __init__(self, distUpgradeView, options=None, datadir=None):
 
99
        # setup the paths
 
100
        localedir = "/usr/share/locale/"
 
101
        if datadir == None:
 
102
            datadir = os.getcwd()
 
103
            localedir = os.path.join(datadir,"mo")
 
104
        self.datadir = datadir
 
105
        self.options = options
 
106
 
 
107
        # init gettext
 
108
        gettext.bindtextdomain("update-manager",localedir)
 
109
        gettext.textdomain("update-manager")
 
110
 
 
111
        # setup the view
 
112
        logging.debug("Using '%s' view" % distUpgradeView.__class__.__name__)
 
113
        self._view = distUpgradeView
 
114
        self._view.updateStatus(_("Reading cache"))
 
115
        self.cache = None
 
116
 
 
117
        if not self.options or self.options.withNetwork == None:
 
118
            self.useNetwork = True
 
119
        else:
 
120
            self.useNetwork = self.options.withNetwork
 
121
        if options:
 
122
            cdrompath = options.cdromPath
 
123
        else:
 
124
            cdrompath = None
 
125
        self.aptcdrom = AptCdrom(distUpgradeView, cdrompath)
 
126
 
 
127
        # the configuration
 
128
        self.config = DistUpgradeConfig(datadir)
 
129
        self.sources_backup_ext = "."+self.config.get("Files","BackupExt")
 
130
 
 
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))
 
135
 
 
136
        # aufs stuff
 
137
        aufsOptionsAndEnvironmentSetup(self.options, self.config)
 
138
 
 
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()
 
144
 
 
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")
 
149
 
 
150
        # we run in full upgrade mode by default
 
151
        self._partialUpgrade = False
 
152
        
 
153
        # install the quirks handler
 
154
        self.quirks = DistUpgradeQuirks(self, self.config)
 
155
 
 
156
        # setup env var 
 
157
        os.environ["RELEASE_UPGRADE_IN_PROGRESS"] = "1"
 
158
        os.environ["PYCENTRAL_FORCE_OVERWRITE"] = "1"
 
159
        os.environ["PATH"] = "%s:%s" % (os.getcwd()+"/imported",
 
160
                                        os.environ["PATH"])
 
161
        check_and_fix_xbit("./imported/invoke-rc.d")
 
162
 
 
163
        # set max retries
 
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 
 
167
        #                                          linux/binfmts.h)
 
168
        apt_pkg.config.set("Dpkg::MaxArgs", str(64*1024))
 
169
        apt_pkg.config.set("Dpkg::MaxArgBytes", str(128*1024))
 
170
 
 
171
        # smaller to avoid hangs
 
172
        apt_pkg.config.set("Acquire::http::Timeout","20")
 
173
        apt_pkg.config.set("Acquire::ftp::Timeout","20")
 
174
 
 
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")
 
178
 
 
179
        # forced obsoletes
 
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')]
 
188
        # debugging
 
189
        #apt_pkg.config.set("DPkg::Options::","--debug=0077")
 
190
 
 
191
        # apt cron job
 
192
        self._aptCronJobPerms = 0o755
 
193
 
 
194
    def openCache(self, lock=True):
 
195
        logging.debug("openCache()")
 
196
        if self.cache is None:
 
197
            self.quirks.run("PreCacheOpen")
 
198
        else:
 
199
            self.cache.releaseLock()
 
200
            self.cache.unlockListsDir()
 
201
        try:
 
202
            self.cache = MyCache(self.config,
 
203
                                 self._view,
 
204
                                 self.quirks,
 
205
                                 self._view.getOpCacheProgress(),
 
206
                                 lock)
 
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,
 
218
                                 self._view,
 
219
                                 self.quirks,
 
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."));
 
229
            sys.exit(1)
 
230
        self.cache.partialUpgrade = self._partialUpgrade
 
231
        logging.debug("/openCache(), new cache size %i" % len(self.cache))
 
232
 
 
233
    def _viewSupportsSSH(self):
 
234
      """
 
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)
 
238
      """
 
239
      supported = self.config.getlist("View","SupportSSH")
 
240
      if self._view.__class__.__name__ in supported:
 
241
          return True
 
242
      return False
 
243
 
 
244
    def _sshMagic(self):
 
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
 
248
            of trouble)
 
249
        """
 
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 "
 
260
                                   "that does "
 
261
                                   "not support this. Please try a text "
 
262
                                   "mode upgrade with 'do-release-upgrade'."
 
263
                                   "\n\n"
 
264
                                   "The upgrade will "
 
265
                                   "abort now. Please try without ssh.")
 
266
                                 )
 
267
                sys.exit(1)
 
268
                return False
 
269
            # ask for a spare one to start (and below 1024)
 
270
            port = 1022
 
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)
 
280
            # abort
 
281
            if res == False:
 
282
                sys.exit(1)
 
283
            res = subprocess.call(["/usr/sbin/sshd",
 
284
                                   "-o", "PidFile=%s" % pidfile,
 
285
                                   "-p",str(port)])
 
286
            if res == 0:
 
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"
 
292
                           ) % port
 
293
                if iptables_active():
 
294
                    cmd = "iptables -I INPUT -p tcp --dport %s -j ACCEPT" % port
 
295
                    descr += _(
 
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)
 
301
        return True
 
302
 
 
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
 
306
            of ourself
 
307
        """  
 
308
        from .MetaRelease import MetaReleaseCore
 
309
        from .DistUpgradeFetcherSelf import DistUpgradeFetcherSelf
 
310
        # check if we run from a LTS 
 
311
        forceLTS=False
 
312
        if (self.release == "dapper" or
 
313
            self.release == "hardy" or
 
314
            self.release == "lucid" or
 
315
            self.release == "precise"):
 
316
            forceLTS=True
 
317
        m = MetaReleaseCore(useDevelopmentRelease=False,
 
318
                            forceLTS=forceLTS)
 
319
        # this will timeout eventually
 
320
        while m.downloading:
 
321
            self._view.processEvents()
 
322
            time.sleep(0.1)
 
323
        if m.new_dist is None:
 
324
            logging.error("No new dist found")
 
325
            return False
 
326
        # we have a new dist
 
327
        progress = self._view.getAcquireProgress()
 
328
        fetcher = DistUpgradeFetcherSelf(new_dist=m.new_dist,
 
329
                                         progress=progress,
 
330
                                         options=self.options,
 
331
                                         view=self._view)
 
332
        fetcher.run()
 
333
 
 
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).
 
338
        """
 
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'))
 
343
            try:
 
344
                expected_default = config.get('DEFAULT', 'default-version')
 
345
            except NoOptionError:
 
346
                logging.debug("no default version for python found in '%s'" % config)
 
347
                return False
 
348
            try:
 
349
                fs_default_version = os.readlink('/usr/bin/python')
 
350
            except OSError as e:
 
351
                logging.error("os.readlink failed (%s)" % e)
 
352
                return False
 
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)))
 
355
                return False
 
356
        return True
 
357
 
 
358
 
 
359
    def prepare(self):
 
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)))
 
370
            sys.exit(1)
 
371
 
 
372
        # setup aufs
 
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 "
 
379
                                   "environment."))
 
380
                return False
 
381
 
 
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 "
 
388
                                     "reboot.\n\n"
 
389
                                     "*No* changes written to a system directory "
 
390
                                     "from now until the next reboot are "
 
391
                                     "permanent.") % aufs_rw_dir)
 
392
 
 
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)
 
406
 
 
407
        # do the ssh check and warn if we run under ssh
 
408
        self._sshMagic()
 
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."))
 
415
            sys.exit(1)
 
416
        # open cache
 
417
        try:
 
418
            self.openCache()
 
419
        except SystemError as e:
 
420
            logging.error("openCache() failed: '%s'" % e)
 
421
            return False
 
422
        if not self.cache.sanityCheck(self._view):
 
423
            return False
 
424
 
 
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()
 
428
        if self.serverMode:
 
429
            os.environ["RELEASE_UPGRADE_MODE"] = "server"
 
430
        else:
 
431
            os.environ["RELEASE_UPGRADE_MODE"] = "desktop"
 
432
 
 
433
        if not self.checkViewDepends():
 
434
            logging.error("checkViewDepends() failed")
 
435
            return False
 
436
 
 
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."))
 
445
            self.abort()
 
446
 
 
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)
 
451
                self._view.error(
 
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 "
 
455
                      "continue.\n"
 
456
                      "Please make sure that the system directory is "
 
457
                      "writable.") % systemdir)
 
458
                self.abort()
 
459
            
 
460
 
 
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 "
 
474
                                                "upgrading.\n"
 
475
                                                "If you answer 'no' here, the network is not "
 
476
                                                "used at all."),
 
477
                                              'Yes')
 
478
            self.useNetwork = res
 
479
            self.config.set("Options","withNetwork", str(self.useNetwork))
 
480
            logging.debug("useNetwork: '%s' (selected by user)" % res)
 
481
            if res:
 
482
                self._tryUpdateSelf()
 
483
        return True
 
484
 
 
485
    def _sourcesListEntryDownloadable(self, entry):
 
486
        """
 
487
        helper that checks if a sources.list entry points to 
 
488
        something downloadable
 
489
        """
 
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)")
 
494
            return True
 
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)
 
498
 
 
499
    def rewriteSourcesList(self, mirror_check=True):
 
500
        logging.debug("rewriteSourcesList()")
 
501
 
 
502
        sync_components = self.config.getlist("Sources","Components")
 
503
 
 
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")
 
509
            mirror_check=False
 
510
 
 
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
 
516
            #    need to  enable it
 
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)
 
521
                    or
 
522
                    (self.cache[pkgname].candidate and
 
523
                     len(self.cache[pkgname].candidate.origins) == 1 and
 
524
                     self.cache[pkgname].candidate.origins[0].archive == "now")
 
525
                   ):
 
526
                    logging.debug("BaseMetaPkg '%s' has no candidateOrigin" % pkgname)
 
527
                    try:
 
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)
 
541
                    break
 
542
            
 
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) 
 
548
                                       for x in pockets]
 
549
        toDists = [self.toDist] + ["%s-%s" % (self.toDist,x) 
 
550
                                   for x in pockets]
 
551
        self.sources_disabled = False
 
552
 
 
553
        # look over the stuff we have
 
554
        foundToDist = False
 
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[:]:
 
559
 
 
560
            # ignore invalid records or disabled ones
 
561
            if entry.invalid or entry.disabled:
 
562
                continue
 
563
            
 
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
 
569
                continue
 
570
            # check if there is actually a lists file for them available
 
571
            # and disable them if not
 
572
            elif entry.uri.startswith("cdrom:"):
 
573
                # 
 
574
                listdir = apt_pkg.config.find_dir("Dir::State::lists")
 
575
                if not os.path.exists("%s/%s%s_%s_%s" % 
 
576
                                      (listdir,
 
577
                                       apt_pkg.URItoFileName(entry.uri),
 
578
                                       "dists",
 
579
                                       entry.dist,
 
580
                                       "Release")):
 
581
                    logging.warning("disabling cdrom source '%s' because it has no  Release file" % entry)
 
582
                    entry.disabled = True
 
583
                continue
 
584
 
 
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)
 
594
                continue
 
595
 
 
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
 
602
                continue
 
603
            
 
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)
 
613
                    test_entry.uri = uri
 
614
                    test_entry.dist = self.toDist
 
615
                    if self._sourcesListEntryDownloadable(test_entry):
 
616
                        logging.info("transition from old-release.u.c to %s" % uri)
 
617
                        entry.uri = uri
 
618
                        break
 
619
 
 
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 
 
628
                # need to add one
 
629
                validTo = True
 
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):
 
636
                    validTo = False
 
637
                if entry.dist in toDists:
 
638
                    # so the self.sources.list is already set to the new
 
639
                    # distro
 
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':
 
647
                    continue
 
648
                elif validMirror:
 
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))
 
654
 
 
655
                # if we make it to this point, we have a official or third-party mirror
 
656
 
 
657
                # check if the arch is powerpc or sparc and if so, transition
 
658
                # to ports.ubuntu.com (powerpc got demoted in gutsy, sparc
 
659
                # in hardy)
 
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/"
 
665
 
 
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:
 
678
                                continue
 
679
                            self.found_components[d].add(comp)
 
680
 
 
681
            else:
 
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))
 
689
 
 
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):
 
696
                continue
 
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]
 
700
                if component_diff:
 
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]
 
705
        return foundToDist
 
706
 
 
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 "
 
718
                               "out of date.\n\n"
 
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' "
 
722
                               "entries.\n"
 
723
                               "If you select 'No' the upgrade will cancel."
 
724
                               ) % (self.fromDist, self.toDist))
 
725
            if res:
 
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):
 
740
                        self.abort()
 
741
 
 
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)
 
752
            else:
 
753
                self.abort()
 
754
 
 
755
        # write (well, backup first ;) !
 
756
        self.sources.backup(self.sources_backup_ext)
 
757
        self.sources.save()
 
758
 
 
759
        # re-check if the written self.sources are valid, if not revert and
 
760
        # bail out
 
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
 
763
        #       sources.list?
 
764
        try:
 
765
            sourceslist = apt_pkg.SourceList()
 
766
            sourceslist.read_main_list()
 
767
        except SystemError:
 
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"])
 
774
            return False
 
775
 
 
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."
 
783
                               ))
 
784
        return True
 
785
 
 
786
    def _logChanges(self):
 
787
        # debugging output
 
788
        logging.debug("About to apply the following changes")
 
789
        inst = []
 
790
        up = []
 
791
        rm = []
 
792
        held = []
 
793
        keep = []
 
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))
 
805
        
 
806
 
 
807
    def doPostInitialUpdate(self):
 
808
        # check if we have packages in ReqReinst state that are not
 
809
        # downloadable
 
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)
 
815
            self.openCache()
 
816
        if len(self.cache.reqReinstallPkgs) > 0:
 
817
            reqreinst = self.cache.reqReinstallPkgs
 
818
            header = ngettext("Package in inconsistent state",
 
819
                              "Packages in inconsistent state",
 
820
                              len(reqreinst))
 
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)
 
833
            return False
 
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)
 
838
        if self.serverMode:
 
839
            self.tasks = self.cache.installedTasks
 
840
        logging.debug("Foreign: %s" % " ".join(self.foreign_pkgs))
 
841
        logging.debug("Obsolete: %s" % " ".join(self.obsolete_pkgs))
 
842
        return True
 
843
 
 
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")
 
848
            return True
 
849
        self.cache._list.read_main_list()
 
850
        progress = self._view.getAcquireProgress()
 
851
        # FIXME: also remove all files from the lists partial dir!
 
852
        currentRetry = 0
 
853
        if forceRetries is not None:
 
854
            maxRetries=forceRetries
 
855
        else:
 
856
            maxRetries = self.config.getint("Network","MaxRetries")
 
857
        while currentRetry < maxRetries:
 
858
            try:
 
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))
 
862
                currentRetry += 1
 
863
                continue
 
864
            # no exception, so all was fine, we are done
 
865
            return True
 
866
 
 
867
        logging.error("doUpdate() failed completely")
 
868
        if showErrors:
 
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)
 
874
        return False
 
875
 
 
876
 
 
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 "
 
883
                    "space on '%s'. "
 
884
                    "Empty your trash and remove temporary "
 
885
                    "packages of former installations using "
 
886
                    "'sudo apt-get clean'.")
 
887
        # allow override
 
888
        if self.config.getWithDefault("FreeSpace","SkipCheck",False):
 
889
            logging.warning("free space check skipped via config override")
 
890
            return True
 
891
        # do the check
 
892
        with_snapshots = self._is_apt_btrfs_snapshot_supported()
 
893
        try:
 
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
 
898
            # string freeze
 
899
            for required in e.free_space_required_list:
 
900
                self._view.error(err_sum, err_long % (required.size_total,
 
901
                                                      required.dir,
 
902
                                                      required.size_needed,
 
903
                                                      required.dir))
 
904
            return False
 
905
        return True
 
906
 
 
907
 
 
908
    def askDistUpgrade(self):
 
909
        self._view.updateStatus(_("Calculating the changes"))
 
910
        if not self.cache.distUpgrade(self._view, self.serverMode, self._partialUpgrade):
 
911
            return False
 
912
 
 
913
        if self.serverMode:
 
914
            if not self.cache.installTasks(self.tasks):
 
915
                return False
 
916
 
 
917
        # show changes and confirm
 
918
        changes = self.cache.get_changes()
 
919
        self._view.processEvents()
 
920
 
 
921
        # log the changes for debugging
 
922
        self._logChanges()
 
923
        self._view.processEvents()
 
924
 
 
925
        # check if we have enough free space 
 
926
        if not self._checkFreeSpace():
 
927
            return False
 
928
        self._view.processEvents()
 
929
 
 
930
        # get the demotions
 
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)
 
936
 
 
937
        # flush UI
 
938
        self._view.processEvents()
 
939
 
 
940
        # ask the user
 
941
        res = self._view.confirmChanges(_("Do you want to start the upgrade?"),
 
942
                                        changes,
 
943
                                        self.installed_demotions,
 
944
                                        self.cache.requiredDownload)
 
945
        return res
 
946
 
 
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)
 
956
 
 
957
    def doDistUpgradeFetching(self):
 
958
        # ensure that no apt cleanup is run during the download/install
 
959
        self._disableAptCronJob()
 
960
        # get the upgrade
 
961
        currentRetry = 0
 
962
        fprogress = self._view.getAcquireProgress()
 
963
        #iprogress = self._view.getInstallProgress(self.cache)
 
964
        # start slideshow
 
965
        url = self.config.getWithDefault("Distro","SlideshowUrl",None)
 
966
        if url:
 
967
            try:
 
968
                lang = locale.getdefaultlocale()[0].split('_')[0]
 
969
            except:
 
970
                logging.exception("getdefaultlocale")
 
971
                lang = "en"
 
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)
 
982
        #  OR 
 
983
        #  the fact that we get a pm and fetcher here confuses something
 
984
        #  in libapt?
 
985
        # POSSIBLE workaround: keep the list-dir locked so that 
 
986
        #          no apt-get update can run outside from the release
 
987
        #          upgrader 
 
988
        user_canceled = False
 
989
        while currentRetry < maxRetries:
 
990
            try:
 
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")
 
996
                user_canceled = True
 
997
                break
 
998
            except IOError as e:
 
999
                # fetch failed, will be retried
 
1000
                logging.error("IOError in cache.commit(): '%s'. Retrying (currentTry: %s)" % (e,currentRetry))
 
1001
                currentRetry += 1
 
1002
                continue
 
1003
            return True
 
1004
        
 
1005
        # maximum fetch-retries reached without a successful commit
 
1006
        if user_canceled:
 
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 "
 
1011
                                     "time."))
 
1012
        else:
 
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."),
 
1019
                             "%s" % e)
 
1020
        # abort here because we want our sources.list back
 
1021
        self._enableAptCronJob()
 
1022
        self.abort()
 
1023
 
 
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"])
 
1029
 
 
1030
    def _is_apt_btrfs_snapshot_supported(self):
 
1031
        """ check if apt-btrfs-snapshot is usable """
 
1032
        try:
 
1033
            import apt_btrfs_snapshot
 
1034
        except ImportError:
 
1035
            return
 
1036
        try:
 
1037
            apt_btrfs = apt_btrfs_snapshot.AptBtrfsSnapshot()
 
1038
            res = apt_btrfs.snapshots_supported()
 
1039
        except:
 
1040
            logging.exception("failed to check btrfs support")
 
1041
            return False
 
1042
        logging.debug("apt btrfs snapshots supported: %s" % res)
 
1043
        return res
 
1044
 
 
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():
 
1048
            return
 
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))
 
1054
        
 
1055
    def doDistUpgrade(self):
 
1056
        # check if we want apport running during the upgrade
 
1057
        if self.config.getWithDefault("Distro","EnableApport", False):
 
1058
            self.enableApport()
 
1059
 
 
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")
 
1064
  
 
1065
        # get the upgrade
 
1066
        currentRetry = 0
 
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()
 
1075
        res = False
 
1076
        while currentRetry < maxRetries:
 
1077
            try:
 
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
 
1098
                            # currently
 
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
 
1104
                            self.abort()
 
1105
                
 
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"
 
1116
                                 "%s" % e)
 
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()
 
1124
                return False
 
1125
            except IOError as e:
 
1126
                # fetch failed, will be retried
 
1127
                logging.error("IOError in cache.commit(): '%s'. Retrying (currentTry: %s)" % (e,currentRetry))
 
1128
                currentRetry += 1
 
1129
                continue
 
1130
            except OSError as e:
 
1131
                logging.exception("cache.commit()")
 
1132
                # deal gracefully with:
 
1133
                #  OSError: [Errno 12] Cannot allocate memory
 
1134
                if e.errno == 12:
 
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
 
1141
                    self.abort()
 
1142
            # no exception, so all was fine, we are done
 
1143
            self._enableAptCronJob()
 
1144
            return True
 
1145
        
 
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. "),
 
1152
                           "%s" % e)
 
1153
        # abort here because we want our sources.list back
 
1154
        self.abort()
 
1155
 
 
1156
    def doPostUpgrade(self):
 
1157
        # reopen cache
 
1158
        self.openCache()
 
1159
        # run the quirks handler that does does like things adding
 
1160
        # missing groups or similar work arounds, only do it on real
 
1161
        # upgrades
 
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()
 
1176
                break
 
1177
        # check if we actually want obsolete removal
 
1178
        if not self.config.getWithDefault("Distro","RemoveObsoletes", True):
 
1179
            logging.debug("Skipping obsolete Removal")
 
1180
            return True
 
1181
 
 
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)
 
1189
 
 
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
 
1193
        if self.useNetwork:
 
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
 
1198
        else:
 
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)
 
1204
 
 
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)
 
1209
 
 
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")
 
1224
        progress.done()
 
1225
 
 
1226
        # get changes
 
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)
 
1237
            try:
 
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 "
 
1244
                                   "information. "),
 
1245
                                   "%s" % e)
 
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()
 
1252
        return True
 
1253
 
 
1254
    def runPostInstallScripts(self):
 
1255
        """ 
 
1256
        scripts that are run in any case after the distupgrade finished 
 
1257
        whether or not it was successful
 
1258
        """
 
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)
 
1263
                continue
 
1264
            logging.debug("Running PostInstallScript: '%s'" % script)
 
1265
            try:
 
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))
 
1271
        
 
1272
    def abort(self):
 
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"))
 
1281
        self._view.abort()
 
1282
        self.openCache()
 
1283
        sys.exit(1)
 
1284
 
 
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:
 
1290
                depname = dep[0]
 
1291
                ver = dep[1]
 
1292
                oper = dep[2]
 
1293
                if not self.cache.has_key(depname):
 
1294
                    logging.error("_checkDep: '%s' not in cache" % depname)
 
1295
                    return False
 
1296
                inst = self.cache[depname]
 
1297
                instver = inst.installedVersion
 
1298
                if (instver != None and
 
1299
                    apt_pkg.CheckDep(instver,oper,ver) == True):
 
1300
                    return True
 
1301
        logging.error("depends '%s' is not satisfied" % depstr)
 
1302
        return False
 
1303
                
 
1304
    def checkViewDepends(self):
 
1305
        " check if depends are satisfied "
 
1306
        logging.debug("checkViewDepends()")
 
1307
        res = True
 
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__,
 
1311
                                           "Depends"))
 
1312
        for dep in depends:
 
1313
            logging.debug("depends: '%s'", dep)
 
1314
            res &= self._checkDep(dep)
 
1315
            if not res:
 
1316
                # FIXME: instead of error out, fetch and install it
 
1317
                #        here
 
1318
                self._view.error(_("Required depends is not installed"),
 
1319
                                 _("The required dependency '%s' is not "
 
1320
                                   "installed. " % dep))
 
1321
                sys.exit(1)
 
1322
        return res 
 
1323
 
 
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")
 
1329
        i=0
 
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)
 
1334
            self.openCache()
 
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):
 
1340
                break
 
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")
 
1344
            i += 1
 
1345
        if i == maxRetries:
 
1346
            logging.error("pre-requists item is NOT trusted, giving up")
 
1347
            return False
 
1348
        apt_pkg.config.set("Acquire::http::No-Cache",noCache)
 
1349
        return True
 
1350
 
 
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")
 
1355
            return True
 
1356
        try:
 
1357
            b = self.config.getboolean("Distro","AllowUnauthenticated")
 
1358
            if b:
 
1359
                return True
 
1360
        except NoOptionError:
 
1361
            pass
 
1362
        for pkgname in backportslist:
 
1363
            pkg = self.cache[pkgname]
 
1364
            if not pkg.candidate:
 
1365
                return False
 
1366
            for cand in pkg.candidate.origins:
 
1367
                if cand.trusted:
 
1368
                    break
 
1369
            else:
 
1370
                return False
 
1371
        return True
 
1372
 
 
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)
 
1378
        if "@" in netloc:
 
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):
 
1385
                return True
 
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)
 
1392
                return True
 
1393
        return False
 
1394
 
 
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):
 
1401
                return True
 
1402
        return False
 
1403
 
 
1404
    def _getPreReqMirrorLines(self, dumb=False):
 
1405
        " get sources.list snippet lines for the current mirror "
 
1406
        lines = ""
 
1407
        sources = SourcesList(matcherPath=".")
 
1408
        for entry in sources.list:
 
1409
            if entry.invalid or entry.disabled:
 
1410
                continue
 
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:
 
1420
                    lines += new_line
 
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)
 
1425
        return lines
 
1426
 
 
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)
 
1439
        outfile.close()
 
1440
        return True
 
1441
 
 
1442
    def getRequiredBackports(self):
 
1443
        " download the backports specified in DistUpgrade.cfg "
 
1444
        logging.debug("getRequiredBackports()")
 
1445
        res = True
 
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")
 
1450
 
 
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"))
 
1457
            found_pkgs = set()
 
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))
 
1464
                return False
 
1465
            # now install them
 
1466
            self.cache.releaseLock()
 
1467
            p = subprocess.Popen(
 
1468
                ["/usr/bin/dpkg", "-i", ] + glob.glob(p+"*_*.deb"),
 
1469
                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
 
1470
            res = None
 
1471
            while res is None:
 
1472
                res = p.poll()
 
1473
                self._view.pulseProgress()
 
1474
                time.sleep(0.02)
 
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])
 
1479
            if res != 0:
 
1480
                return False
 
1481
            # and re-start itself when it done
 
1482
            return self.setupRequiredBackports()
 
1483
 
 
1484
        # we support PreRequists/SourcesList-$arch sections here too
 
1485
        # 
 
1486
        # logic for mirror finding works list this:     
 
1487
        # - use the mirror template from the config, then: [done]
 
1488
        # 
 
1489
        #  - try to find known mirror (isMirror) and prepend it [done]
 
1490
        #  - archive.ubuntu.com is always a fallback at the end [done]
 
1491
        # 
 
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)
 
1496
        # 
 
1497
        # make sure to remove file on cancel
 
1498
        
 
1499
        # FIXME: use the DistUpgradeFetcherCore logic
 
1500
        #        in mirror_from_sources_list() here
 
1501
        #        (and factor that code out into a helper)
 
1502
 
 
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)
 
1509
            return False
 
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) 
 
1513
        try:
 
1514
            self._verifyBackports()
 
1515
        except NoBackportsFoundException as e:
 
1516
            self._addPreRequistsSourcesList(prereq_template, outfile, dumb=True) 
 
1517
            try:
 
1518
                self._verifyBackports()
 
1519
            except NoBackportsFoundException as e:
 
1520
                logging.warning("no backport for '%s' found" % e)
 
1521
            return False
 
1522
        
 
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)
 
1528
            if not ver:
 
1529
                logging.error("No candidate for '%s'" % pkgname)
 
1530
                os.unlink(outpath)
 
1531
                return False
 
1532
            if ver.FileList == None:
 
1533
                logging.error("No ver.FileList for '%s'" % pkgname)
 
1534
                os.unlink(outpath)
 
1535
                return False
 
1536
            logging.debug("marking '%s' for install" % pkgname)
 
1537
            # mark install
 
1538
            pkg.mark_install(auto_inst=False, auto_fix=False)
 
1539
 
 
1540
        # now get it
 
1541
        res = False
 
1542
        try:
 
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)
 
1547
            res = False
 
1548
        except SystemError as e:
 
1549
            logging.error("installArchives returned '%s'" % e)
 
1550
            res = False
 
1551
 
 
1552
        if res == False:
 
1553
            logging.warning("_fetchArchives for backports returned False")
 
1554
 
 
1555
        # all backports done, remove the pre-requirests.list file again
 
1556
        try:
 
1557
            os.unlink(outfile)
 
1558
        except Exception as e:
 
1559
            logging.error("failed to unlink pre-requists file: '%s'" % e)
 
1560
        return self.setupRequiredBackports()
 
1561
 
 
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
 
1568
        logging.shutdown()
 
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"]
 
1573
        if self.useNetwork:
 
1574
            args.append("--with-network")
 
1575
        else:
 
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)
 
1582
 
 
1583
    # this is the core
 
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)
 
1588
 
 
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 "
 
1594
                               "being started."))
 
1595
            subprocess.Popen(["apport-bug", "update-manager"])
 
1596
            sys.exit(1)
 
1597
 
 
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
 
1601
            self.options 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"
 
1611
                                   "\n"
 
1612
                                   "Additionally, a bug reporting process is "
 
1613
                                   "being started."))
 
1614
                subprocess.Popen(["apport-bug", "update-manager"])
 
1615
                self.abort()
 
1616
 
 
1617
        # run a "apt-get update" now, its ok to ignore errors, 
 
1618
        # because 
 
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)
 
1627
        self.openCache()
 
1628
 
 
1629
        # do pre-upgrade stuff (calc list of obsolete pkgs etc)
 
1630
        if not self.doPostInitialUpdate():
 
1631
            self.abort()
 
1632
 
 
1633
        # update sources.list
 
1634
        self._view.setStep(STEP_MODIFY_SOURCES)
 
1635
        self._view.updateStatus(_("Updating repository information"))
 
1636
        if not self.updateSourcesList():
 
1637
            self.abort()
 
1638
 
 
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."))
 
1644
            sys.exit(1)
 
1645
 
 
1646
        # then update the package index files
 
1647
        if not self.doUpdate():
 
1648
            self.abort()
 
1649
 
 
1650
        # then open the cache (again)
 
1651
        self._view.updateStatus(_("Checking package manager"))
 
1652
        self.openCache()
 
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 "
 
1680
                                   "software sources."
 
1681
                                   "\n"
 
1682
                                   "In the case of an overloaded mirror, you "
 
1683
                                   "may want to try the upgrade again later.")
 
1684
                                   % pkg)
 
1685
                subprocess.Popen(["apport-bug", "update-manager"])
 
1686
                self.abort()
 
1687
 
 
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():
 
1692
            self.abort()
 
1693
 
 
1694
        # fetch the stuff
 
1695
        self._view.setStep(STEP_FETCH)
 
1696
        self._view.updateStatus(_("Fetching"))
 
1697
        if not self.doDistUpgradeFetching():
 
1698
            self.abort()
 
1699
 
 
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 "
 
1710
                                     "process."))
 
1711
            sys.exit(1) 
 
1712
            
 
1713
        # do post-upgrade stuff
 
1714
        self._view.setStep(STEP_CLEANUP)
 
1715
        self._view.updateStatus(_("Searching for obsolete software"))
 
1716
        self.doPostUpgrade()
 
1717
 
 
1718
        # comment out cdrom source
 
1719
        if self.aptcdrom:
 
1720
            self.aptcdrom.comment_out_cdrom_entry()
 
1721
 
 
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")
 
1729
            sys.exit(0)
 
1730
        return True
 
1731
        
 
1732
    def run(self):
 
1733
        self._view.processEvents()
 
1734
        return self.fullUpgrade()
 
1735
    
 
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
 
1742
        self.prepare()
 
1743
        if not self.doPostInitialUpdate():
 
1744
            return False
 
1745
        if not self.askDistUpgrade():
 
1746
            return False
 
1747
        self._view.setStep(STEP_FETCH)
 
1748
        self._view.updateStatus(_("Fetching"))
 
1749
        if not self.doDistUpgradeFetching():
 
1750
            return False
 
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 "
 
1757
                                     "process."))
 
1758
            return False
 
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 "
 
1764
                                     "process."))
 
1765
            return False
 
1766
 
 
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")
 
1771
        else:
 
1772
            self._view.information(_("Upgrade complete"),
 
1773
                                   _("The partial upgrade was completed."))
 
1774
        return True
 
1775
 
 
1776
 
 
1777
if __name__ == "__main__":
 
1778
    from .DistUpgradeViewText import DistUpgradeViewText
 
1779
    logging.basicConfig(level=logging.DEBUG)
 
1780
    v = DistUpgradeViewText()
 
1781
    dc = DistUpgradeController(v)
 
1782
    #dc.openCache()
 
1783
    dc._disableAptCronJob()
 
1784
    dc._enableAptCronJob()
 
1785
    #dc._addRelatimeToFstab()
 
1786
    #dc.prepare()
 
1787
    #dc.askDistUpgrade()
 
1788
    #dc._checkFreeSpace()
 
1789
    #dc._rewriteFstab()
 
1790
    #dc._checkAdminGroup()
 
1791
    #dc._rewriteAptPeriodic(2)