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

« back to all changes in this revision

Viewing changes to DistUpgrade/DistUpgradeQuirks.py

merge from move-changelogs branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# DistUpgradeQuirks.py 
 
2
#  
 
3
#  Copyright (c) 2004-2010 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
from __future__ import absolute_import
 
23
 
 
24
import apt
 
25
import atexit
 
26
import glob
 
27
import logging
 
28
import os
 
29
import re
 
30
import hashlib
 
31
import shutil
 
32
import sys
 
33
import subprocess
 
34
from subprocess import PIPE, Popen
 
35
from hashlib import md5
 
36
 
 
37
from .utils import lsmod, get_arch
 
38
 
 
39
from .DistUpgradeGettext import gettext as _
 
40
from computerjanitor.plugin import PluginManager
 
41
 
 
42
class DistUpgradeQuirks(object):
 
43
    """
 
44
    This class collects the various quirks handlers that can
 
45
    be hooked into to fix/work around issues that the individual
 
46
    releases have
 
47
    """
 
48
    
 
49
    def __init__(self, controller, config):
 
50
        self.controller = controller
 
51
        self._view = controller._view
 
52
        self.config = config
 
53
        self.uname = Popen(["uname","-r"],stdout=PIPE).communicate()[0].strip()
 
54
        self.arch = get_arch()
 
55
        self.plugin_manager = PluginManager(self.controller, ["./plugins"])
 
56
 
 
57
    # the quirk function have the name:
 
58
    #  $Name (e.g. PostUpgrade)
 
59
    #  $todist$Name (e.g. intrepidPostUpgrade)
 
60
    #  $from_$fromdist$Name (e.g. from_dapperPostUpgrade)
 
61
    def run(self, quirksName):
 
62
        """
 
63
        Run the specific quirks handler, the follow handlers are supported:
 
64
        - PreCacheOpen: run *before* the apt cache is opened the first time
 
65
                        to set options that affect the cache
 
66
        - PostInitialUpdate: run *before* the sources.list is rewritten but
 
67
                             after a initial apt-get update
 
68
        - PostDistUpgradeCache: run *after* the dist-upgrade was calculated
 
69
                                in the cache
 
70
        - StartUpgrade: before the first package gets installed (but the
 
71
                        download is finished)
 
72
        - PostUpgrade: run *after* the upgrade is finished successfully and 
 
73
                       packages got installed
 
74
        - PostCleanup: run *after* the cleanup (orphaned etc) is finished
 
75
        """
 
76
        # we do not run any quirks in partialUpgrade mode
 
77
        if self.controller._partialUpgrade:
 
78
            logging.info("not running quirks in partialUpgrade mode")
 
79
            return
 
80
        # first check for matching plugins
 
81
        for condition in [
 
82
            quirksName,
 
83
            "%s%s" %  (self.config.get("Sources","To"), quirksName),
 
84
            "from_%s%s" % (self.config.get("Sources","From"), quirksName)
 
85
            ]:
 
86
            for plugin in self.plugin_manager.get_plugins(condition):
 
87
                logging.debug("running quirks plugin %s" % plugin)
 
88
                plugin.do_cleanup_cruft()
 
89
        
 
90
        # run the handler that is common to all dists
 
91
        funcname = "%s" % quirksName
 
92
        func = getattr(self, funcname, None)
 
93
        if func is not None:
 
94
            logging.debug("quirks: running %s" % funcname)
 
95
            func()
 
96
 
 
97
        # run the quirksHandler to-dist
 
98
        funcname = "%s%s" % (self.config.get("Sources","To"), quirksName)
 
99
        func = getattr(self, funcname, None)
 
100
        if func is not None:
 
101
            logging.debug("quirks: running %s" % funcname)
 
102
            func()
 
103
 
 
104
        # now run the quirksHandler from_${FROM-DIST}Quirks
 
105
        funcname = "from_%s%s" % (self.config.get("Sources","From"), quirksName)
 
106
        func = getattr(self, funcname, None)
 
107
        if func is not None:
 
108
            logging.debug("quirks: running %s" % funcname)
 
109
            func()
 
110
 
 
111
    # individual quirks handler that run *before* the cache is opened
 
112
    def PreCacheOpen(self):
 
113
        """ run before the apt cache is opened the first time """
 
114
        logging.debug("running Quirks.PreCacheOpen")
 
115
 
 
116
    def oneiricPreCacheOpen(self):
 
117
        logging.debug("running Quirks.oneiricPreCacheOpen")
 
118
        # enable i386 multiach temporarely during the upgrade if on amd64
 
119
        # this needs to be done very early as libapt caches the result
 
120
        # of the "getArchitectures()" call in aptconfig and its not possible
 
121
        # currently to invalidate this cache
 
122
        if apt.apt_pkg.config.find("Apt::Architecture") == "amd64":
 
123
            logging.debug("multiarch: enabling i386 as a additional architecture")
 
124
            apt.apt_pkg.config.set("Apt::Architectures::", "i386")
 
125
            # increase case size to workaround bug in natty apt that
 
126
            # may cause segfault on cache grow
 
127
            apt.apt_pkg.config.set("APT::Cache-Start", str(48*1024*1024))
 
128
 
 
129
 
 
130
    # individual quirks handler when the dpkg run is finished ---------
 
131
    def PostCleanup(self):
 
132
        " run after cleanup " 
 
133
        logging.debug("running Quirks.PostCleanup")
 
134
 
 
135
    def from_dapperPostUpgrade(self):
 
136
        " this works around quirks for dapper->hardy upgrades "
 
137
        logging.debug("running Controller.from_dapperQuirks handler")
 
138
        self._rewriteFstab()
 
139
        self._checkAdminGroup()
 
140
        
 
141
    def intrepidPostUpgrade(self):
 
142
        " this applies rules for the hardy->intrepid upgrade "
 
143
        logging.debug("running Controller.intrepidQuirks handler")
 
144
        self._addRelatimeToFstab()
 
145
 
 
146
    def gutsyPostUpgrade(self):
 
147
        """ this function works around quirks in the feisty->gutsy upgrade """
 
148
        logging.debug("running Controller.gutsyQuirks handler")
 
149
 
 
150
    def feistyPostUpgrade(self):
 
151
        """ this function works around quirks in the edgy->feisty upgrade """
 
152
        logging.debug("running Controller.feistyQuirks handler")
 
153
        self._rewriteFstab()
 
154
        self._checkAdminGroup()
 
155
 
 
156
    def karmicPostUpgrade(self):
 
157
        """ this function works around quirks in the jaunty->karmic upgrade """
 
158
        logging.debug("running Controller.karmicPostUpgrade handler")
 
159
        self._ntfsFstabFixup()
 
160
        self._checkLanguageSupport()
 
161
 
 
162
    # quirks when run when the initial apt-get update was run ----------------
 
163
    def from_lucidPostInitialUpdate(self):
 
164
        """ Quirks that are run before the sources.list is updated to the
 
165
            new distribution when upgrading from a lucid system (either
 
166
            to maverick or the new LTS)
 
167
        """
 
168
        logging.debug("running %s" %  sys._getframe().f_code.co_name)
 
169
        # systems < i686 will not upgrade
 
170
        self._test_and_fail_on_non_i686()
 
171
        self._test_and_warn_on_i8xx()
 
172
 
 
173
    def oneiricPostInitialUpdate(self):
 
174
        self._test_and_warn_on_i8xx()
 
175
 
 
176
    def lucidPostInitialUpdate(self):
 
177
        """ quirks that are run before the sources.list is updated to lucid """
 
178
        logging.debug("running %s" %  sys._getframe().f_code.co_name)
 
179
        # upgrades on systems with < arvm6 CPUs will break
 
180
        self._test_and_fail_on_non_arm_v6()
 
181
        # vserver+upstart are problematic
 
182
        self._test_and_warn_if_vserver()
 
183
        # fglrx dropped support for some cards
 
184
        self._test_and_warn_on_dropped_fglrx_support()
 
185
 
 
186
    # quirks when the cache upgrade calculation is finished -------------------
 
187
    def from_dapperPostDistUpgradeCache(self):
 
188
        self.hardyPostDistUpgradeCache()
 
189
        self.gutsyPostDistUpgradeCache()
 
190
        self.feistyPostDistUpgradeCache()
 
191
        self.edgyPostDistUpgradeCache()
 
192
 
 
193
    def from_hardyPostDistUpgradeCache(self):
 
194
        """ this function works around quirks in upgrades from hardy """
 
195
        logging.debug("running %s" %  sys._getframe().f_code.co_name)
 
196
        # ensure 386 -> generic transition happens
 
197
        self._kernel386TransitionCheck()
 
198
        # ensure kubuntu-kde4-desktop transition
 
199
        self._kubuntuDesktopTransition()
 
200
        # evms got removed after hardy, warn and abort
 
201
        if self._usesEvmsInMounts():
 
202
            logging.error("evms in use in /etc/fstab")
 
203
            self._view.error(_("evms in use"),
 
204
                             _("Your system uses the 'evms' volume manager "
 
205
                               "in /proc/mounts. "
 
206
                               "The 'evms' software is no longer supported, "
 
207
                               "please switch it off and run the upgrade "
 
208
                               "again when this is done."))
 
209
            self.controller.abort()
 
210
        # check if "wl" module is loaded and if so, install bcmwl-kernel-source
 
211
        self._checkAndInstallBroadcom()
 
212
        # langpacks got re-organized in 9.10
 
213
        self._dealWithLanguageSupportTransition()
 
214
        # nvidia-71, nvidia-96 got dropped
 
215
        self._test_and_warn_on_old_nvidia()
 
216
        # new nvidia needs a CPU with sse support
 
217
        self._test_and_warn_on_nvidia_and_no_sse()
 
218
 
 
219
    def nattyPostDistUpgradeCache(self):
 
220
        """
 
221
        this function works around quirks in the 
 
222
        maverick -> natty cache upgrade calculation
 
223
        """
 
224
        self._add_kdegames_card_extra_if_installed()
 
225
 
 
226
    def maverickPostDistUpgradeCache(self):
 
227
        """
 
228
        this function works around quirks in the 
 
229
        lucid->maverick upgrade calculation
 
230
        """
 
231
        self._add_extras_repository()
 
232
        self._gutenprint_fixup()
 
233
 
 
234
    def karmicPostDistUpgradeCache(self):
 
235
        """ 
 
236
        this function works around quirks in the 
 
237
        jaunty->karmic upgrade calculation
 
238
        """
 
239
        # check if "wl" module is loaded and if so, install
 
240
        # bcmwl-kernel-source (this is needed for lts->lts as well)
 
241
        self._checkAndInstallBroadcom()
 
242
        self._dealWithLanguageSupportTransition()
 
243
        self._kernel386TransitionCheck()
 
244
        self._mysqlClusterCheck()
 
245
 
 
246
    def jauntyPostDistUpgradeCache(self):
 
247
        """ 
 
248
        this function works around quirks in the 
 
249
        intrepid->jaunty upgrade calculation
 
250
        """
 
251
        logging.debug("running %s" %  sys._getframe().f_code.co_name)
 
252
        # bug 332328 - make sure pidgin-libnotify is upgraded
 
253
        for pkg in ["pidgin-libnotify"]:
 
254
            if (self.controller.cache.has_key(pkg) and
 
255
                self.controller.cache[pkg].is_installed and
 
256
                not self.controller.cache[pkg].marked_upgrade):
 
257
                logging.debug("forcing '%s' upgrade" % pkg)
 
258
                self.controller.cache[pkg].mark_upgrade()
 
259
        # deal with kipi/gwenview/kphotoalbum
 
260
        for pkg in ["gwenview","digikam"]:
 
261
            if (self.controller.cache.has_key(pkg) and
 
262
                self.controller.cache[pkg].is_installed and
 
263
                not self.controller.cache[pkg].marked_upgrade):
 
264
                logging.debug("forcing libkipi '%s' upgrade" % pkg)
 
265
                if self.controller.cache.has_key("libkipi0"):
 
266
                    logging.debug("removing  libkipi0)")
 
267
                    self.controller.cache["libkipi0"].mark_delete()
 
268
                self.controller.cache[pkg].mark_upgrade()
 
269
        
 
270
    def intrepidPostDistUpgradeCache(self):
 
271
        """ 
 
272
        this function works around quirks in the 
 
273
        hardy->intrepid upgrade 
 
274
        """
 
275
        logging.debug("running %s" %  sys._getframe().f_code.co_name)
 
276
        # kdelibs4-dev is unhappy (#279621)
 
277
        fromp = "kdelibs4-dev"
 
278
        to = "kdelibs5-dev"
 
279
        if (self.controller.cache.has_key(fromp) and 
 
280
            self.controller.cache[fromp].is_installed and
 
281
            self.controller.cache.has_key(to)):
 
282
            self.controller.cache.mark_install(to, "kdelibs4-dev -> kdelibs5-dev transition")
 
283
 
 
284
    def hardyPostDistUpgradeCache(self):
 
285
        """ 
 
286
        this function works around quirks in the 
 
287
        {dapper,gutsy}->hardy upgrade 
 
288
        """
 
289
        logging.debug("running %s" %  sys._getframe().f_code.co_name)
 
290
        # deal with gnome-translator and help apt with the breaks
 
291
        if (self.controller.cache.has_key("nautilus") and
 
292
            self.controller.cache["nautilus"].is_installed and
 
293
            not self.controller.cache["nautilus"].marked_upgrade):
 
294
            # uninstallable and gutsy apt is unhappy about this
 
295
            # breaks because it wants to upgrade it and gives up
 
296
            # if it can't
 
297
            for broken in ("link-monitor-applet"):
 
298
                if self.controller.cache.has_key(broken) and self.controller.cache[broken].is_installed:
 
299
                    self.controller.cache[broken].mark_delete()
 
300
            self.controller.cache["nautilus"].mark_install()
 
301
        # evms gives problems, remove it if it is not in use
 
302
        self._checkAndRemoveEvms()
 
303
        # give the language-support-* packages a extra kick
 
304
        # (if we have network, otherwise this will not work)
 
305
        if self.config.get("Options","withNetwork") == "True":
 
306
            for pkg in self.controller.cache:
 
307
                if (pkg.name.startswith("language-support-") and
 
308
                    pkg.is_installed and
 
309
                    not pkg.marked_upgrade):
 
310
                    self.controller.cache.mark_install(pkg.name,"extra language-support- kick")
 
311
 
 
312
    def gutsyPostDistUpgradeCache(self):
 
313
        """ this function works around quirks in the feisty->gutsy upgrade """
 
314
        logging.debug("running %s" %  sys._getframe().f_code.co_name)
 
315
        # lowlatency kernel flavour vanished from feisty->gutsy
 
316
        try:
 
317
            (version, build, flavour) = self.uname.split("-")
 
318
            if (flavour == 'lowlatency' or 
 
319
                flavour == '686' or
 
320
                flavour == 'k7'):
 
321
                kernel = "linux-image-generic"
 
322
                if not (self.controller.cache[kernel].is_installed or self.controller.cache[kernel].marked_install):
 
323
                    logging.debug("Selecting new kernel '%s'" % kernel)
 
324
                    self.controller.cache[kernel].mark_install()
 
325
        except Exception as e:
 
326
            logging.warning("problem while transitioning lowlatency kernel (%s)" % e)
 
327
        # fix feisty->gutsy utils-linux -> nfs-common transition (LP: #141559)
 
328
        try:
 
329
            for line in open("/proc/mounts"):
 
330
                line = line.strip()
 
331
                if line == '' or line.startswith("#"):
 
332
                    continue
 
333
                try:
 
334
                    (device, mount_point, fstype, options, a, b) = line.split()
 
335
                except Exception as e:
 
336
                    logging.error("can't parse line '%s'" % line)
 
337
                    continue
 
338
                if "nfs" in fstype:
 
339
                    logging.debug("found nfs mount in line '%s', marking nfs-common for install " % line)
 
340
                    self.controller.cache["nfs-common"].mark_install()
 
341
                    break
 
342
        except Exception as e:
 
343
            logging.warning("problem while transitioning util-linux -> nfs-common (%s)" % e)
 
344
 
 
345
    def feistyPostDistUpgradeCache(self):
 
346
        """ this function works around quirks in the edgy->feisty upgrade """
 
347
        logging.debug("running %s" %  sys._getframe().f_code.co_name)
 
348
        # ndiswrapper changed again *sigh*
 
349
        for (fr, to) in [("ndiswrapper-utils-1.8","ndiswrapper-utils-1.9")]:
 
350
            if self.controller.cache.has_key(fr) and self.controller.cache.has_key(to):
 
351
                if self.controller.cache[fr].is_installed and not self.controller.cache[to].marked_install:
 
352
                    try:
 
353
                        self.controller.cache.mark_install(to,"%s->%s quirk upgrade rule" % (fr, to))
 
354
                    except SystemError as e:
 
355
                        logging.warning("Failed to apply %s->%s install (%s)" % (fr, to, e))
 
356
            
 
357
 
 
358
    def edgyPostDistUpgradeCache(self):
 
359
        """ this function works around quirks in the dapper->edgy upgrade """
 
360
        logging.debug("running %s" %  sys._getframe().f_code.co_name)
 
361
        for pkg in self.controller.cache:
 
362
            # deal with the python2.4-$foo -> python-$foo transition
 
363
            if (pkg.name.startswith("python2.4-") and
 
364
                pkg.is_installed and
 
365
                not pkg.marked_upgrade):
 
366
                basepkg = "python-"+pkg.name[len("python2.4-"):]
 
367
                if (self.controller.cache.has_key(basepkg) and 
 
368
                    self.controller.cache[basepkg].candidateDownloadable and
 
369
                    not self.controller.cache[basepkg].marked_install):
 
370
                    try:
 
371
                        self.controller.cache.mark_install(basepkg,
 
372
                                         "python2.4->python upgrade rule")
 
373
                    except SystemError as e:
 
374
                        logging.debug("Failed to apply python2.4->python install: %s (%s)" % (basepkg, e))
 
375
            # xserver-xorg-input-$foo gives us trouble during the upgrade too
 
376
            if (pkg.name.startswith("xserver-xorg-input-") and
 
377
                pkg.is_installed and
 
378
                not pkg.marked_upgrade):
 
379
                try:
 
380
                    self.controller.cache.mark_install(pkg.name, "xserver-xorg-input fixup rule")
 
381
                except SystemError as e:
 
382
                    logging.debug("Failed to apply fixup: %s (%s)" % (pkg.name, e))
 
383
            
 
384
        # deal with held-backs that are unneeded
 
385
        for pkgname in ["hpijs", "bzr", "tomboy"]:
 
386
            if (self.controller.cache.has_key(pkgname) and self.controller.cache[pkgname].is_installed and
 
387
                self.controller.cache[pkgname].isUpgradable and not self.controller.cache[pkgname].marked_upgrade):
 
388
                try:
 
389
                    self.controller.cache.mark_install(pkgname,"%s quirk upgrade rule" % pkgname)
 
390
                except SystemError as e:
 
391
                    logging.debug("Failed to apply %s install (%s)" % (pkgname,e))
 
392
        # libgl1-mesa-dri from xgl.compiz.info (and friends) breaks the
 
393
        # upgrade, work around this here by downgrading the package
 
394
        if self.controller.cache.has_key("libgl1-mesa-dri"):
 
395
            pkg = self.controller.cache["libgl1-mesa-dri"]
 
396
            # the version from the compiz repo has a "6.5.1+cvs20060824" ver
 
397
            if (pkg.candidateVersion == pkg.installedVersion and
 
398
                "+cvs2006" in pkg.candidateVersion):
 
399
                for ver in pkg._pkg.VersionList:
 
400
                    # the "official" edgy version has "6.5.1~20060817-0ubuntu3"
 
401
                    if "~2006" in ver.VerStr:
 
402
                        # ensure that it is from a trusted repo
 
403
                        for (VerFileIter, index) in ver.FileList:
 
404
                            indexfile = self.controller.cache._list.FindIndex(VerFileIter)
 
405
                            if indexfile and indexfile.IsTrusted:
 
406
                                logging.info("Forcing downgrade of libgl1-mesa-dri for xgl.compz.info installs")
 
407
                                self.controller.cache._depcache.SetCandidateVer(pkg._pkg, ver)
 
408
                                break
 
409
                                    
 
410
        # deal with general if $foo is installed, install $bar
 
411
        for (fr, to) in [("xserver-xorg-driver-all","xserver-xorg-video-all")]:
 
412
            if self.controller.cache.has_key(fr) and self.controller.cache.has_key(to):
 
413
                if self.controller.cache[fr].is_installed and not self.controller.cache[to].marked_install:
 
414
                    try:
 
415
                        self.controller.cache.mark_install(to,"%s->%s quirk upgrade rule" % (fr, to))
 
416
                    except SystemError as e:
 
417
                        logging.debug("Failed to apply %s->%s install (%s)" % (fr, to, e))
 
418
                    
 
419
    def dapperPostDistUpgradeCache(self):
 
420
        """ this function works around quirks in the breezy->dapper upgrade """
 
421
        logging.debug("running %s" %  sys._getframe().f_code.co_name)
 
422
        if (self.controller.cache.has_key("nvidia-glx") and self.controller.cache["nvidia-glx"].is_installed and
 
423
            self.controller.cache.has_key("nvidia-settings") and self.controller.cache["nvidia-settings"].is_installed):
 
424
            logging.debug("nvidia-settings and nvidia-glx is installed")
 
425
            self.controller.cache.mark_remove("nvidia-settings")
 
426
            self.controller.cache.mark_install("nvidia-glx")
 
427
 
 
428
    # run right before the first packages get installed
 
429
    def StartUpgrade(self):
 
430
        self._applyPatches()
 
431
        self._removeOldApportCrashes()
 
432
        self._removeBadMaintainerScripts()
 
433
        self._killUpdateNotifier()
 
434
        self._killKBluetooth()
 
435
        self._killScreensaver()
 
436
        self._pokeScreensaver()
 
437
        self._stopDocvertConverter()
 
438
    def oneiricStartUpgrade(self):
 
439
        logging.debug("oneiric StartUpgrade quirks")
 
440
        # fix grub issue
 
441
        if (os.path.exists("/usr/sbin/update-grub") and
 
442
            not os.path.exists("/etc/kernel/postinst.d/zz-update-grub")):
 
443
            # create a version of zz-update-grub to avoid depending on
 
444
            # the upgrade order. if that file is missing, we may end
 
445
            # up generating a broken grub.cfg
 
446
            targetdir = "/etc/kernel/postinst.d"
 
447
            if not os.path.exists(targetdir):
 
448
                os.makedirs(targetdir)
 
449
            logging.debug("copying zz-update-grub into %s" % targetdir)
 
450
            shutil.copy("zz-update-grub", targetdir)
 
451
            os.chmod(os.path.join(targetdir, "zz-update-grub"), 0o755)
 
452
        # enable multiarch permanently
 
453
        if apt.apt_pkg.config.find("Apt::Architecture") == "amd64":
 
454
            self._enable_multiarch(foreign_arch="i386")
 
455
            
 
456
    def from_hardyStartUpgrade(self):
 
457
        logging.debug("from_hardyStartUpgrade quirks")
 
458
        self._stopApparmor()
 
459
    def jauntyStartUpgrade(self):
 
460
        self._createPycentralPkgRemove()
 
461
        # hal/NM triggers problem, if the old (intrepid) hal gets
 
462
        # triggered for a restart this causes NM to drop all connections
 
463
        # because (old) hal thinks it has no devices anymore (LP: #327053)
 
464
        ap = "/var/lib/dpkg/info/hal.postinst"
 
465
        if os.path.exists(ap):
 
466
            # intrepid md5 of hal.postinst (jaunty one is different)
 
467
            # md5 jaunty 22c146857d751181cfe299a171fc11c9
 
468
            md5sum = "146145275900af343d990a4dea968d7c"
 
469
            if md5(open(ap).read()).hexdigest() == md5sum:
 
470
                logging.debug("removing bad script '%s'" % ap)
 
471
                os.unlink(ap)
 
472
 
 
473
    # helpers
 
474
    def _get_pci_ids(self):
 
475
        """ return a set of pci ids of the system (using lspci -n) """
 
476
        lspci = set()
 
477
        try:
 
478
            p = subprocess.Popen(["lspci","-n"],stdout=subprocess.PIPE)
 
479
        except OSError:
 
480
            return lspci
 
481
        for line in p.communicate()[0].split("\n"):
 
482
            if line:
 
483
                lspci.add(line.split()[2])
 
484
        return lspci
 
485
 
 
486
    def _test_and_warn_on_i8xx(self):
 
487
        I8XX_PCI_IDS = ["8086:7121", # i810
 
488
                        "8086:7125", # i810e
 
489
                        "8086:1132", # i815
 
490
                        "8086:3577", # i830
 
491
                        "8086:2562", # i845
 
492
                        "8086:3582", # i855
 
493
                        "8086:2572", # i865
 
494
                        ]
 
495
        lspci = self._get_pci_ids()
 
496
        if set(I8XX_PCI_IDS).intersection(lspci):
 
497
            res = self._view.askYesNoQuestion(
 
498
                _("Your graphics hardware may not be fully supported in "
 
499
                  "Ubuntu 12.04 LTS."),
 
500
                _("The support in Ubuntu 12.04 LTS for your Intel "
 
501
                  "graphics hardware is limited "
 
502
                  "and you may encounter problems after the upgrade. "
 
503
                  "For more information see "
 
504
                  "https://wiki.ubuntu.com/X/Bugs/UpdateManagerWarningForI8xx "
 
505
                  "Do you want to continue with the upgrade?")
 
506
                )
 
507
            if res == False:
 
508
                self.controller.abort()
 
509
 
 
510
    def _test_and_warn_on_nvidia_and_no_sse(self):
 
511
        """ The current 
 
512
        """
 
513
        # check if we have sse
 
514
        cache = self.controller.cache
 
515
        for pkgname in ["nvidia-glx-180", "nvidia-glx-185", "nvidia-glx-195"]:
 
516
            if (cache.has_key(pkgname) and 
 
517
                cache[pkgname].marked_install and
 
518
                self._checkVideoDriver("nvidia")):
 
519
                logging.debug("found %s video driver" % pkgname)
 
520
                if not self._cpuHasSSESupport():
 
521
                    logging.warning("nvidia driver that needs SSE but cpu has no SSE support")
 
522
                    res = self._view.askYesNoQuestion(_("Upgrading may reduce desktop "
 
523
                                        "effects, and performance in games "
 
524
                                        "and other graphically intensive "
 
525
                                        "programs."),
 
526
                                      _("This computer is currently using "
 
527
                                        "the NVIDIA 'nvidia' "
 
528
                                        "graphics driver. "
 
529
                                        "No version of this driver is "
 
530
                                        "available that works with your "
 
531
                                        "video card in Ubuntu "
 
532
                                        "10.04 LTS.\n\nDo you want to continue?"))
 
533
                    if res == False:
 
534
                        self.controller.abort()
 
535
                    # if the user continue, do not install the broken driver
 
536
                    # so that we can transiton him to the free "nv" one after
 
537
                    # the upgrade
 
538
                    self.controller.cache[pkgname].mark_keep()
 
539
        
 
540
 
 
541
    def _test_and_warn_on_old_nvidia(self):
 
542
        """ nvidia-glx-71 and -96 are no longer in the archive since 8.10 """
 
543
        # now check for nvidia and show a warning if needed
 
544
        cache = self.controller.cache
 
545
        for pkgname in ["nvidia-glx-71","nvidia-glx-96"]:
 
546
            if (cache.has_key(pkgname) and 
 
547
                cache[pkgname].marked_install and
 
548
                self._checkVideoDriver("nvidia")):
 
549
                logging.debug("found %s video driver" % pkgname)
 
550
                res = self._view.askYesNoQuestion(_("Upgrading may reduce desktop "
 
551
                                        "effects, and performance in games "
 
552
                                        "and other graphically intensive "
 
553
                                        "programs."),
 
554
                                      _("This computer is currently using "
 
555
                                        "the NVIDIA 'nvidia' "
 
556
                                        "graphics driver. "
 
557
                                        "No version of this driver is "
 
558
                                        "available that works with your "
 
559
                                        "video card in Ubuntu "
 
560
                                        "10.04 LTS.\n\nDo you want to continue?"))
 
561
                if res == False:
 
562
                    self.controller.abort()
 
563
                # if the user continue, do not install the broken driver
 
564
                # so that we can transiton him to the free "nv" one after
 
565
                # the upgrade
 
566
                self.controller.cache[pkgname].mark_keep()
 
567
 
 
568
    def _test_and_warn_on_dropped_fglrx_support(self):
 
569
        """
 
570
        Some cards are no longer supported by fglrx. Check if that
 
571
        is the case and warn
 
572
        """
 
573
        # this is to deal with the fact that support for some of the cards
 
574
        # that fglrx used to support got dropped
 
575
        if (self._checkVideoDriver("fglrx") and 
 
576
            not self._supportInModaliases("fglrx")):
 
577
             res = self._view.askYesNoQuestion(_("Upgrading may reduce desktop "
 
578
                                         "effects, and performance in games "
 
579
                                         "and other graphically intensive "
 
580
                                         "programs."),
 
581
                                       _("This computer is currently using "
 
582
                                         "the AMD 'fglrx' graphics driver. "
 
583
                                         "No version of this driver is "
 
584
                                         "available that works with your "
 
585
                                         "hardware in Ubuntu "
 
586
                                         "10.04 LTS.\n\nDo you want to continue?"))
 
587
             if res == False:
 
588
                 self.controller.abort()
 
589
             # if the user wants to continue we remove the fglrx driver
 
590
             # here because its no use (no support for this card)
 
591
             logging.debug("remove xorg-driver-fglrx,xorg-driver-fglrx-envy,fglrx-kernel-source")
 
592
             l=self.controller.config.getlist("Distro","PostUpgradePurge")
 
593
             l.append("xorg-driver-fglrx")
 
594
             l.append("xorg-driver-fglrx-envy")
 
595
             l.append("fglrx-kernel-source")
 
596
             l.append("fglrx-amdcccle")
 
597
             l.append("xorg-driver-fglrx-dev")
 
598
             l.append("libamdxvba1")
 
599
             self.controller.config.set("Distro","PostUpgradePurge",",".join(l))
 
600
 
 
601
    def _test_and_fail_on_non_i686(self):
 
602
        """
 
603
        Test and fail if the cpu is not i686 or more or if its a newer
 
604
        CPU but does not have the cmov feature (LP: #587186)
 
605
        """
 
606
        # check on i386 only
 
607
        if self.arch == "i386":
 
608
            logging.debug("checking for i586 CPU")
 
609
            if not self._cpu_is_i686_and_has_cmov():
 
610
                logging.error("not a i686 or no cmov")
 
611
                summary = _("No i686 CPU")
 
612
                msg = _("Your system uses an i586 CPU or a CPU that does "
 
613
                        "not have the 'cmov' extension. "
 
614
                        "All packages were built with "
 
615
                        "optimizations requiring i686 as the "
 
616
                        "minimal architecture. It is not possible to "
 
617
                        "upgrade your system to a new Ubuntu release "
 
618
                        "with this hardware.")
 
619
                self._view.error(summary, msg)
 
620
                self.controller.abort()
 
621
 
 
622
    def _cpu_is_i686_and_has_cmov(self, cpuinfo_path="/proc/cpuinfo"):
 
623
        if not os.path.exists(cpuinfo_path):
 
624
            logging.error("cannot open %s ?!?" % cpuinfo_path)
 
625
            return True
 
626
        cpuinfo = open(cpuinfo_path).read()
 
627
        # check family
 
628
        if re.search("^cpu family\s*:\s*[345]\s*", cpuinfo, re.MULTILINE):
 
629
            logging.debug("found cpu family [345], no i686+")
 
630
            return False
 
631
        # check flags for cmov
 
632
        match = re.search("^flags\s*:\s*(.*)", cpuinfo, re.MULTILINE)
 
633
        if match:
 
634
            if not "cmov" in match.group(1).split():
 
635
                logging.debug("found flags '%s'" % match.group(1))
 
636
                logging.debug("can not find cmov in flags")
 
637
                return False
 
638
        return True
 
639
 
 
640
 
 
641
    def _test_and_fail_on_non_arm_v6(self):
 
642
        """ 
 
643
        Test and fail if the cpu is not a arm v6 or greater,
 
644
        from 9.10 on we do no longer support those CPUs
 
645
        """
 
646
        if self.arch == "armel":
 
647
            if not self._checkArmCPU():
 
648
                self._view.error(_("No ARMv6 CPU"),
 
649
                    _("Your system uses an ARM CPU that is older "
 
650
                      "than the ARMv6 architecture. "
 
651
                      "All packages in karmic were built with "
 
652
                      "optimizations requiring ARMv6 as the "
 
653
                      "minimal architecture. It is not possible to "
 
654
                      "upgrade your system to a new Ubuntu release "
 
655
                      "with this hardware."))
 
656
                self.controller.abort()
 
657
 
 
658
    def _test_and_warn_if_vserver(self):
 
659
        """
 
660
        upstart and vserver environments are not a good match, warn
 
661
        if we find one
 
662
        """
 
663
        # verver test (LP: #454783), see if there is a init around
 
664
        try:
 
665
            os.kill(1, 0)
 
666
        except:
 
667
            logging.warn("no init found")
 
668
            res = self._view.askYesNoQuestion(
 
669
                _("No init available"),
 
670
                _("Your system appears to be a virtualised environment "
 
671
                  "without an init daemon, e.g. Linux-VServer. "
 
672
                  "Ubuntu 10.04 LTS cannot function within this type of "
 
673
                  "environment, requiring an update to your virtual "
 
674
                  "machine configuration first.\n\n"
 
675
                  "Are you sure you want to continue?"))
 
676
            if res == False:
 
677
                self.controller.abort()
 
678
            self._view.processEvents()
 
679
 
 
680
    def _kubuntuDesktopTransition(self):
 
681
        """
 
682
        check if a key depends of kubuntu-kde4-desktop is installed
 
683
        and transition in this case as well
 
684
        """
 
685
        deps_found = False
 
686
        frompkg = "kubuntu-kde4-desktop"
 
687
        topkg = "kubuntu-desktop"
 
688
        if self.config.getlist(frompkg,"KeyDependencies"):
 
689
            deps_found = True
 
690
            for pkg in self.config.getlist(frompkg,"KeyDependencies"):
 
691
                deps_found &= (self.controller.cache.has_key(pkg) and
 
692
                               self.controller.cache[pkg].is_installed)
 
693
        if deps_found:
 
694
            logging.debug("transitioning %s to %s (via key depends)" % (frompkg, topkg))
 
695
            self.controller.cache[topkg].mark_install()
 
696
 
 
697
    def _mysqlClusterCheck(self):
 
698
        """
 
699
        check if ndb clustering is used and do not upgrade mysql
 
700
        if it is (LP: #450837)
 
701
        """
 
702
        logging.debug("_mysqlClusterCheck")
 
703
        if (self.controller.cache.has_key("mysql-server") and
 
704
            self.controller.cache["mysql-server"].is_installed):
 
705
            # taken from the mysql-server-5.1.preinst
 
706
            ret = subprocess.call([
 
707
                    "egrep", "-q", "-i", "-r",
 
708
                    "^[^#]*ndb.connectstring|^[:space:]*\[[:space:]*ndb_mgmd", 
 
709
                    "/etc/mysql/"])
 
710
            logging.debug("egrep returned %s" % ret)
 
711
            # if clustering is used, do not upgrade to 5.1 and 
 
712
            # remove mysql-{server,client}
 
713
            # metapackage and upgrade the 5.0 packages
 
714
            if ret == 0:
 
715
                logging.debug("mysql clustering in use, do not upgrade to 5.1")
 
716
                for pkg in ("mysql-server", "mysql-client"):
 
717
                    self.controller.cache.mark_remove(pkg, "clustering in use")
 
718
                    # mark mysql-{server,client}-5.0 as manual install (#453513)
 
719
                    depcache = self.controller.cache._depcache
 
720
                    for pkg in ["mysql-server-5.0", "mysql-client-5.0"]:
 
721
                        if pkg.is_installed and depcache.IsAutoInstalled(pkg._pkg):
 
722
                            logging.debug("marking '%s' manual installed" % pkg.name)
 
723
                            autoInstDeps = False
 
724
                            fromUser = True
 
725
                            depcache.Mark_install(pkg._pkg, autoInstDeps, fromUser)
 
726
            else:
 
727
                self.controller.cache.mark_upgrade("mysql-server", "no clustering in use")
 
728
 
 
729
    def _checkArmCPU(self):
 
730
        """
 
731
        parse /proc/cpuinfo and search for ARMv6 or greater
 
732
        """
 
733
        logging.debug("checking for ARM CPU version")
 
734
        if not os.path.exists("/proc/cpuinfo"):
 
735
            logging.error("cannot open /proc/cpuinfo ?!?")
 
736
            return False
 
737
        cpuinfo = open("/proc/cpuinfo")
 
738
        if re.search("^Processor\s*:\s*ARMv[45]", cpuinfo.read(), re.MULTILINE):
 
739
            return False
 
740
        return True
 
741
 
 
742
    def _dealWithLanguageSupportTransition(self):
 
743
        """
 
744
        In karmic the language-support-translations-* metapackages
 
745
        are gone and the direct dependencies will get marked for
 
746
        auto removal - mark them as manual instead
 
747
        """
 
748
        logging.debug("language-support-translations-* transition")
 
749
        for pkg in self.controller.cache:
 
750
            depcache = self.controller.cache._depcache
 
751
            if (pkg.name.startswith("language-support-translations") and
 
752
                pkg.is_installed):
 
753
                for dp_or in pkg.installedDependencies:
 
754
                    for dpname in dp_or.or_dependencies:
 
755
                        dp = self.controller.cache[dpname.name]
 
756
                        if dp.is_installed and depcache.IsAutoInstalled(dp._pkg):
 
757
                            logging.debug("marking '%s' manual installed" % dp.name)
 
758
                            autoInstDeps = False
 
759
                            fromUser = True
 
760
                            depcache.mark_install(dp._pkg, autoInstDeps, fromUser)
 
761
                            
 
762
    def _checkLanguageSupport(self):
 
763
        """
 
764
        check if the language support is fully installed and if
 
765
        not generate a update-notifier note on next login
 
766
        """
 
767
        if not os.path.exists("/usr/bin/check-language-support"):
 
768
            logging.debug("no check-language-support available")
 
769
            return
 
770
        p = subprocess.Popen(["check-language-support"],stdout=subprocess.PIPE)
 
771
        for pkgname in p.communicate()[0].split():
 
772
            if (self.controller.cache.has_key(pkgname) and
 
773
                not self.controller.cache[pkgname].is_installed):
 
774
                logging.debug("language support package '%s' missing" % pkgname)
 
775
                # check if kde/gnome and copy language-selector note
 
776
                base = "/usr/share/language-support/"
 
777
                target = "/var/lib/update-notifier/user.d"
 
778
                for p in ("incomplete-language-support-gnome.note",
 
779
                          "incomplete-language-support-qt.note"):
 
780
                    if os.path.exists(os.path.join(base,p)):
 
781
                        shutil.copy(os.path.join(base,p), target)
 
782
                        return
 
783
 
 
784
    def _checkAndInstallBroadcom(self):
 
785
        """
 
786
        check for the 'wl' kernel module and install bcmwl-kernel-source
 
787
        if the module is loaded
 
788
        """
 
789
        logging.debug("checking for 'wl' module")
 
790
        if "wl" in lsmod():
 
791
            self.controller.cache.mark_install("bcmwl-kernel-source",
 
792
                                              "'wl' module found in lsmod")
 
793
 
 
794
    def _stopApparmor(self):
 
795
        """ /etc/init.d/apparmor stop (see bug #559433)"""
 
796
        if os.path.exists("/etc/init.d/apparmor"):
 
797
            logging.debug("/etc/init.d/apparmor stop")
 
798
            subprocess.call(["/etc/init.d/apparmor","stop"])
 
799
    def _stopDocvertConverter(self):
 
800
        " /etc/init.d/docvert-converter stop (see bug #450569)"
 
801
        if os.path.exists("/etc/init.d/docvert-converter"):
 
802
            logging.debug("/etc/init.d/docvert-converter stop")
 
803
            subprocess.call(["/etc/init.d/docvert-converter","stop"])
 
804
    def _killUpdateNotifier(self):
 
805
        "kill update-notifier"
 
806
        # kill update-notifier now to suppress reboot required
 
807
        if os.path.exists("/usr/bin/killall"):
 
808
            logging.debug("killing update-notifier")
 
809
            subprocess.call(["killall","-q","update-notifier"])
 
810
    def _killKBluetooth(self):
 
811
        """killall kblueplugd kbluetooth (riddel requested it)"""
 
812
        if os.path.exists("/usr/bin/killall"):
 
813
            logging.debug("killing kblueplugd kbluetooth4")
 
814
            subprocess.call(["killall", "-q", "kblueplugd", "kbluetooth4"])
 
815
    def _killScreensaver(self):
 
816
        """killall gnome-screensaver """
 
817
        if os.path.exists("/usr/bin/killall"):
 
818
            logging.debug("killing gnome-screensaver")
 
819
            subprocess.call(["killall", "-q", "gnome-screensaver"])
 
820
    def _pokeScreensaver(self):
 
821
        if os.path.exists("/usr/bin/xdg-screensaver") and os.environ.get('DISPLAY') :
 
822
            logging.debug("setup poke timer for the scrensaver")
 
823
            try:
 
824
                self._poke = subprocess.Popen(
 
825
                    "while true; do /usr/bin/xdg-screensaver reset >/dev/null 2>&1; sleep 30; done",
 
826
                    shell=True)
 
827
                atexit.register(self._stopPokeScreensaver)
 
828
            except:
 
829
                logging.exception("failed to setup screensaver poke")
 
830
    def _stopPokeScreensaver(self):
 
831
        if self._poke:
 
832
            res = False
 
833
            try:
 
834
                self._poke.terminate()
 
835
                res = self._poke.wait()
 
836
            except:
 
837
                logging.exception("failed to stop screensaver poke")
 
838
            self._poke = None
 
839
            return res
 
840
    def _removeBadMaintainerScripts(self):
 
841
        " remove bad/broken maintainer scripts (last resort) "
 
842
        # apache: workaround #95325 (edgy->feisty)
 
843
        # pango-libthai #103384 (edgy->feisty)
 
844
        bad_scripts = ["/var/lib/dpkg/info/apache2-common.prerm",
 
845
                       "/var/lib/dpkg/info/pango-libthai.postrm",
 
846
                       ]
 
847
        for ap in bad_scripts:
 
848
            if os.path.exists(ap):
 
849
                logging.debug("removing bad script '%s'" % ap)
 
850
                os.unlink(ap)
 
851
 
 
852
    def _createPycentralPkgRemove(self):
 
853
        """
 
854
        intrepid->jaunty, create /var/lib/pycentral/pkgremove flag file
 
855
        to help python-central so that it removes all preinst links
 
856
        on upgrade
 
857
        """
 
858
        logging.debug("adding pkgremove file")
 
859
        if not os.path.exists("/var/lib/pycentral/"):
 
860
            os.makedirs("/var/lib/pycentral")
 
861
        open("/var/lib/pycentral/pkgremove","w")
 
862
 
 
863
    def _removeOldApportCrashes(self):
 
864
        " remove old apport crash files "
 
865
        try:
 
866
            for f in glob.glob("/var/crash/*.crash"):
 
867
                logging.debug("removing old crash file '%s'" % f)
 
868
                os.unlink(f)
 
869
        except Exception as e:
 
870
            logging.warning("error during unlink of old crash files (%s)" % e)
 
871
 
 
872
    def _cpuHasSSESupport(self, cpuinfo="/proc/cpuinfo"):
 
873
        " helper that checks if the given cpu has sse support "
 
874
        if not os.path.exists(cpuinfo):
 
875
            return False
 
876
        for line in open(cpuinfo):
 
877
            if line.startswith("flags") and not " sse" in line:
 
878
                return False
 
879
        return True
 
880
 
 
881
    def _usesEvmsInMounts(self):
 
882
        " check if evms is used in /proc/mounts "
 
883
        logging.debug("running _usesEvmsInMounts")
 
884
        for line in open("/proc/mounts"):
 
885
            line = line.strip()
 
886
            if line == '' or line.startswith("#"):
 
887
                continue
 
888
            try:
 
889
                (device, mount_point, fstype, options, a, b) = line.split()
 
890
            except Exception:
 
891
                logging.error("can't parse line '%s'" % line)
 
892
                continue
 
893
            if "evms" in device:
 
894
                logging.debug("found evms device in line '%s', skipping " % line)
 
895
                return True
 
896
        return False
 
897
 
 
898
    def _checkAndRemoveEvms(self):
 
899
        " check if evms is in use and if not, remove it "
 
900
        logging.debug("running _checkAndRemoveEvms")
 
901
        if self._usesEvmsInMounts():
 
902
            return False
 
903
        # if not in use, nuke it
 
904
        for pkg in ["evms","libevms-2.5","libevms-dev",
 
905
                    "evms-ncurses", "evms-ha",
 
906
                    "evms-bootdebug",
 
907
                    "evms-gui", "evms-cli",
 
908
                    "linux-patch-evms"]:
 
909
            if self.controller.cache.has_key(pkg) and self.controller.cache[pkg].is_installed:
 
910
                self.controller.cache[pkg].mark_delete()
 
911
        return True
 
912
 
 
913
    def _addRelatimeToFstab(self):
 
914
        " add the relatime option to ext2/ext3 filesystems on upgrade "
 
915
        logging.debug("_addRelatime")
 
916
        replaced = False
 
917
        lines = []
 
918
        for line in open("/etc/fstab"):
 
919
            line = line.strip()
 
920
            if line == '' or line.startswith("#"):
 
921
                lines.append(line)
 
922
                continue
 
923
            try:
 
924
                (device, mount_point, fstype, options, a, b) = line.split()
 
925
            except Exception:
 
926
                logging.error("can't parse line '%s'" % line)
 
927
                lines.append(line)
 
928
                continue
 
929
            if (("ext2" in fstype or
 
930
                 "ext3" in fstype) and 
 
931
                (not "noatime" in options) and
 
932
                (not "relatime" in options) ):
 
933
                logging.debug("adding 'relatime' to line '%s' " % line)
 
934
                line = line.replace(options,"%s,relatime" % options)
 
935
                logging.debug("replaced line is '%s' " % line)
 
936
                replaced=True
 
937
            lines.append(line)
 
938
        # we have converted a line
 
939
        if replaced:
 
940
            logging.debug("writing new /etc/fstab")
 
941
            f=open("/etc/fstab.intrepid","w")
 
942
            f.write("\n".join(lines))
 
943
            # add final newline (see LP: #279093)
 
944
            f.write("\n")
 
945
            f.close()
 
946
            os.rename("/etc/fstab.intrepid","/etc/fstab")
 
947
        return True
 
948
        
 
949
    def _ntfsFstabFixup(self, fstab="/etc/fstab"):
 
950
        """change PASS 1 to 0 for ntfs entries (#441242)"""
 
951
        logging.debug("_ntfsFstabFixup")
 
952
        replaced = False
 
953
        lines = []
 
954
        for line in open(fstab):
 
955
            line = line.strip()
 
956
            if line == '' or line.startswith("#"):
 
957
                lines.append(line)
 
958
                continue
 
959
            try:
 
960
                (device, mount_point, fstype, options, fdump, fpass) = line.split()
 
961
            except Exception:
 
962
                logging.error("can't parse line '%s'" % line)
 
963
                lines.append(line)
 
964
                continue
 
965
            if ("ntfs" in fstype and fpass == "1"):
 
966
                logging.debug("changing PASS for ntfs to 0 for '%s' " % line)
 
967
                if line[-1] == "1":
 
968
                    line = line[:-1] + "0"
 
969
                else:
 
970
                    logging.error("unexpected value in line")
 
971
                logging.debug("replaced line is '%s' " % line)
 
972
                replaced=True
 
973
            lines.append(line)
 
974
        # we have converted a line
 
975
        if replaced:
 
976
            suffix = ".jaunty"
 
977
            logging.debug("writing new /etc/fstab")
 
978
            f=open(fstab + suffix, "w")
 
979
            f.write("\n".join(lines))
 
980
            # add final newline (see LP: #279093)
 
981
            f.write("\n")
 
982
            f.close()
 
983
            os.rename(fstab+suffix, fstab)
 
984
        return True
 
985
        
 
986
 
 
987
    def _rewriteFstab(self):
 
988
        " convert /dev/{hd?,scd0} to /dev/cdrom for the feisty upgrade "
 
989
        logging.debug("_rewriteFstab()")
 
990
        replaced = 0
 
991
        lines = []
 
992
        # we have one cdrom to convert
 
993
        for line in open("/etc/fstab"):
 
994
            line = line.strip()
 
995
            if line == '' or line.startswith("#"):
 
996
                lines.append(line)
 
997
                continue
 
998
            try:
 
999
                (device, mount_point, fstype, options, a, b) = line.split()
 
1000
            except Exception:
 
1001
                logging.error("can't parse line '%s'" % line)
 
1002
                lines.append(line)
 
1003
                continue
 
1004
            # edgy kernel has /dev/cdrom -> /dev/hd?
 
1005
            # feisty kernel (for a lot of chipsets) /dev/cdrom -> /dev/scd0
 
1006
            # this breaks static mounting (LP#86424)
 
1007
            #
 
1008
            # we convert here to /dev/cdrom only if current /dev/cdrom
 
1009
            # points to the device in /etc/fstab already. this ensures
 
1010
            # that we don't break anything or that we get it wrong
 
1011
            # for systems with two (or more) cdroms. this is ok, because
 
1012
            # we convert under the old kernel
 
1013
            if ("iso9660" in fstype and
 
1014
                device != "/dev/cdrom" and
 
1015
                os.path.exists("/dev/cdrom") and
 
1016
                os.path.realpath("/dev/cdrom") == device
 
1017
                ):
 
1018
                logging.debug("replacing '%s' " % line)
 
1019
                line = line.replace(device,"/dev/cdrom")
 
1020
                logging.debug("replaced line is '%s' " % line)
 
1021
                replaced += 1
 
1022
            lines.append(line)
 
1023
        # we have converted a line (otherwise we would have exited already)
 
1024
        if replaced > 0:
 
1025
            logging.debug("writing new /etc/fstab")
 
1026
            shutil.copy("/etc/fstab","/etc/fstab.edgy")
 
1027
            f=open("/etc/fstab","w")
 
1028
            f.write("\n".join(lines))
 
1029
            # add final newline (see LP: #279093)
 
1030
            f.write("\n")
 
1031
            f.close()
 
1032
        return True
 
1033
 
 
1034
    def _checkAdminGroup(self):
 
1035
        " check if the current sudo user is in the admin group "
 
1036
        logging.debug("_checkAdminGroup")
 
1037
        import grp
 
1038
        try:
 
1039
            admin_group = grp.getgrnam("admin").gr_mem
 
1040
        except KeyError as e:
 
1041
            logging.warning("System has no admin group (%s)" % e)
 
1042
            subprocess.call(["addgroup","--system","admin"])
 
1043
        # double paranoia
 
1044
        try:
 
1045
            admin_group = grp.getgrnam("admin").gr_mem
 
1046
        except KeyError as e:
 
1047
            logging.warning("adding the admin group failed (%s)" % e)
 
1048
            return
 
1049
        # if the current SUDO_USER is not in the admin group
 
1050
        # we add him - this is no security issue because
 
1051
        # the user is already root so adding him to the admin group
 
1052
        # does not change anything
 
1053
        if (os.environ.has_key("SUDO_USER") and
 
1054
            not os.environ["SUDO_USER"] in admin_group):
 
1055
            admin_user = os.environ["SUDO_USER"]
 
1056
            logging.info("SUDO_USER=%s is not in admin group" % admin_user)
 
1057
            cmd = ["usermod","-a","-G","admin",admin_user]
 
1058
            res = subprocess.call(cmd)
 
1059
            logging.debug("cmd: %s returned %i" % (cmd, res))
 
1060
 
 
1061
    def _checkVideoDriver(self, name):
 
1062
        " check if the given driver is in use in xorg.conf "
 
1063
        XORG="/etc/X11/xorg.conf"
 
1064
        if not os.path.exists(XORG):
 
1065
            return False
 
1066
        for line in open(XORG):
 
1067
            s=line.split("#")[0].strip()
 
1068
            # check for fglrx driver entry
 
1069
            if (s.lower().startswith("driver") and
 
1070
                s.endswith('"%s"' % name)):
 
1071
                return True
 
1072
        return False
 
1073
 
 
1074
    def _applyPatches(self, patchdir="./patches"):
 
1075
        """
 
1076
        helper that applies the patches in patchdir. the format is
 
1077
        _path_to_file.md5sum
 
1078
        
 
1079
        and it will apply the diff to that file if the md5sum
 
1080
        matches
 
1081
        """
 
1082
        if not os.path.exists(patchdir):
 
1083
            logging.debug("no patchdir")
 
1084
            return
 
1085
        for f in os.listdir(patchdir):
 
1086
            # skip, not a patch file, they all end with .$md5sum
 
1087
            if not "." in f:
 
1088
                logging.debug("skipping '%s' (no '.')" % f)
 
1089
                continue
 
1090
            logging.debug("check if patch '%s' needs to be applied" % f)
 
1091
            (encoded_path, md5sum, result_md5sum) = f.rsplit(".", 2)
 
1092
            # FIXME: this is not clever and needs quoting support for
 
1093
            #        filenames with "_" in the name
 
1094
            path = encoded_path.replace("_","/")
 
1095
            logging.debug("target for '%s' is '%s' -> '%s'" % (
 
1096
                    f, encoded_path, path))
 
1097
            # target does not exist
 
1098
            if not os.path.exists(path):
 
1099
                logging.debug("target '%s' does not exist" % path)
 
1100
                continue
 
1101
            # check the input md5sum, this is not strictly needed as patch()
 
1102
            # will verify the result md5sum and discard the result if that
 
1103
            # does not match but this will remove a misleading error in the 
 
1104
            # logs
 
1105
            md5 = hashlib.md5()
 
1106
            md5.update(open(path).read())
 
1107
            if md5.hexdigest() == result_md5sum:
 
1108
                logging.debug("already at target hash, skipping '%s'" % path)
 
1109
                continue
 
1110
            elif md5.hexdigest() != md5sum:
 
1111
                logging.warn("unexpected target md5sum, skipping: '%s'" % path)
 
1112
                continue
 
1113
            # patchable, do it
 
1114
            from .DistUpgradePatcher import patch
 
1115
            try:
 
1116
                patch(path, os.path.join(patchdir, f), result_md5sum)
 
1117
                logging.info("applied '%s' successfully" % f)
 
1118
            except Exception:
 
1119
                logging.exception("ed failed for '%s'" % f)
 
1120
                    
 
1121
    def _supportInModaliases(self, pkgname, lspci=None):
 
1122
        """ 
 
1123
        Check if pkgname will work on this hardware
 
1124
 
 
1125
        This helper will check with the modaliasesdir if the given 
 
1126
        pkg will work on this hardware (or the hardware given
 
1127
        via the lspci argument)
 
1128
        """
 
1129
        # get lspci info (if needed)
 
1130
        if not lspci:
 
1131
            lspci = self._get_pci_ids()
 
1132
        # get pkg
 
1133
        if (not pkgname in self.controller.cache or
 
1134
            not self.controller.cache[pkgname].candidate):
 
1135
            logging.warn("can not find '%s' in cache")
 
1136
            return False
 
1137
        pkg = self.controller.cache[pkgname]
 
1138
        for (module, pciid_list) in self._parse_modaliases_from_pkg_header(pkg.candidate.record):
 
1139
            for pciid in pciid_list:
 
1140
                m = re.match("pci:v0000(.+)d0000(.+)sv.*", pciid)
 
1141
                if m:
 
1142
                    matchid = "%s:%s" % (m.group(1), m.group(2))
 
1143
                    if matchid.lower() in lspci:
 
1144
                        logging.debug("found system pciid '%s' in modaliases" % matchid)
 
1145
                        return True
 
1146
        logging.debug("checking for %s support in modaliases but none found" % pkgname)
 
1147
        return False
 
1148
                    
 
1149
    def _parse_modaliases_from_pkg_header(self, pkgrecord):
 
1150
        """ return a list of (module1, (pciid, ...), (module2, (pciid, ...)))"""
 
1151
        if not "Modaliases" in pkgrecord:
 
1152
            return []
 
1153
        # split the string
 
1154
        modules = []
 
1155
        for m in pkgrecord["Modaliases"].split(")"):
 
1156
            m = m.strip(", ")
 
1157
            if not m:
 
1158
                continue
 
1159
            (module, pciids) = m.split("(")
 
1160
            modules.append ((module, [x.strip() for x in pciids.split(",")]))
 
1161
        return modules
 
1162
 
 
1163
    def _kernel386TransitionCheck(self):
 
1164
        """ test if the current kernel is 386 and if the system is 
 
1165
            capable of using a generic one instead (#353534)
 
1166
        """
 
1167
        logging.debug("_kernel386TransitionCheck")
 
1168
        # we test first if one of 386 is installed
 
1169
        # if so, check if the system could also work with -generic
 
1170
        # (we get that from base-installer) and try to installed
 
1171
        #  that)
 
1172
        for pkgname in ["linux-386", "linux-image-386"]:
 
1173
            if (self.controller.cache.has_key(pkgname) and
 
1174
                self.controller.cache[pkgname].is_installed):
 
1175
                working_kernels = self.controller.cache.getKernelsFromBaseInstaller()
 
1176
                upgrade_to = ["linux-generic", "linux-image-generic"]
 
1177
                for pkgname in upgrade_to:
 
1178
                    if pkgname in working_kernels:
 
1179
                        logging.debug("386 kernel installed, but generic kernel  will work on this machine")
 
1180
                        if self.controller.cache.mark_install(pkgname, "386 -> generic transition"):
 
1181
                            return
 
1182
        
 
1183
 
 
1184
    def _add_extras_repository(self):
 
1185
        logging.debug("_add_extras_repository")
 
1186
        cache = self.controller.cache
 
1187
        if not "ubuntu-extras-keyring" in cache:
 
1188
            logging.debug("no ubuntu-extras-keyring, no need to add repo")
 
1189
            return
 
1190
        if not (cache["ubuntu-extras-keyring"].marked_install or
 
1191
                cache["ubuntu-extras-keyring"].installed):
 
1192
            logging.debug("ubuntu-extras-keyring not installed/marked_install")
 
1193
            return
 
1194
        try:
 
1195
            import aptsources.sourceslist
 
1196
            sources = aptsources.sourceslist.SourcesList()
 
1197
            for entry in sources:
 
1198
                if "extras.ubuntu.com" in entry.uri:
 
1199
                    logging.debug("found extras.ubuntu.com, no need to add it")
 
1200
                    break
 
1201
            else:
 
1202
                logging.info("no extras.ubuntu.com, adding it to sources.list")
 
1203
                sources.add("deb","http://extras.ubuntu.com/ubuntu",
 
1204
                            self.controller.toDist, ["main"],
 
1205
                            "Third party developers repository")
 
1206
                sources.save()
 
1207
        except:
 
1208
            logging.exception("error adding extras.ubuntu.com")
 
1209
 
 
1210
    def _gutenprint_fixup(self):
 
1211
        """ foomatic-db-gutenprint get removed during the upgrade,
 
1212
            replace it with the compressed ijsgutenprint-ppds
 
1213
            (context is foomatic-db vs foomatic-db-compressed-ppds)
 
1214
        """
 
1215
        try:
 
1216
            cache = self.controller.cache
 
1217
            if ("foomatic-db-gutenprint" in cache and
 
1218
                cache["foomatic-db-gutenprint"].marked_delete and
 
1219
                "ijsgutenprint-ppds" in cache):
 
1220
                logging.info("installing ijsgutenprint-ppds")
 
1221
                cache.mark_install(
 
1222
                    "ijsgutenprint-ppds",
 
1223
                    "foomatic-db-gutenprint -> ijsgutenprint-ppds rule")
 
1224
        except:
 
1225
            logging.exception("_gutenprint_fixup failed")
 
1226
 
 
1227
    def _enable_multiarch(self, foreign_arch="i386"):
 
1228
        """ enable multiarch via /etc/dpkg/dpkg.cfg.d/multiarch """
 
1229
        cfg = "/etc/dpkg/dpkg.cfg.d/multiarch"
 
1230
        if not os.path.exists(cfg):
 
1231
            try:
 
1232
                os.makedirs("/etc/dpkg/dpkg.cfg.d/")
 
1233
            except OSError:
 
1234
                pass
 
1235
            open(cfg, "w").write("foreign-architecture %s\n" % foreign_arch)
 
1236
 
 
1237
    def _add_kdegames_card_extra_if_installed(self):
 
1238
        """ test if kdegames-card-data is installed and if so,
 
1239
            add kdegames-card-data-extra so that users do not 
 
1240
            loose functionality (LP: #745396)
 
1241
        """
 
1242
        try:
 
1243
            cache = self.controller.cache
 
1244
            if not ("kdegames-card-data" in cache or
 
1245
                    "kdegames-card-data-extra" in cache):
 
1246
                return
 
1247
            if (cache["kdegames-card-data"].is_installed or
 
1248
                cache["kdegames-card-data"].marked_install):
 
1249
                cache.mark_install(
 
1250
                    "kdegames-card-data-extra",
 
1251
                    "kdegames-card-data -> k-c-d-extra transition")
 
1252
        except:
 
1253
            logging.exception("_add_kdegames_card_extra_if_installed failed")
 
1254
        
 
1255
    def ensure_recommends_are_installed_on_desktops(self):
 
1256
        """ ensure that on a desktop install recommends are installed 
 
1257
            (LP: #759262)
 
1258
        """
 
1259
        import apt
 
1260
        if not self.controller.serverMode:
 
1261
            if not apt.apt_pkg.config.find_b("Apt::Install-Recommends"):
 
1262
                logging.warn("Apt::Install-Recommends was disabled, enabling it just for the upgrade")
 
1263
                apt.apt_pkg.config.set("Apt::Install-Recommends", "1")
 
1264