~rbalint/update-manager/lp-1795898-upgrade-workaround

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#!/usr/bin/python

from __future__ import print_function

import optparse
import datetime
import distro_info
import os
import re
import subprocess
import sys

import apt
from UpdateManager.Core.utils import twrap, get_dist

# set locale early so that the subsequent imports have localized
# strings
import locale
try:
    locale.setlocale(locale.LC_ALL, "")
except:
    pass

from gettext import gettext as _
import gettext
gettext.textdomain("update-manager")


from HweSupportStatus.consts import (
    Messages,
    LTS_EOL_DATE,
    HWE_EOL_DATE,
    NEXT_LTS_DOT1_DATE,
)


# HWE stack with a short support period
HWE_UNSUPPORTED_BACKPORTS = (
    "-lts-utopic",
    "-lts-vivid",
    "-lts-wily"
)
# from https://wiki.ubuntu.com/Kernel/LTSEnablementStack
UNSUPPORTED_KERNEL_IMAGE_REGEX = \
    r'linux-image.*-(3\.16|3\.19|4\.2)(\.[0-9]+)?-.*'

# HWE stack with a long support period
HWE_SUPPORTED_BACKPORT = "-lts-xenial"
SUPPORTED_KERNEL_IMAGE_REGEX = r'linux-image.*-4\.4(\.[0-9]+)?-.*'


KERNEL_METAPKGS = (
    "linux-generic",
    "linux-image-generic",
    "linux-signed-generic",
    "linux-signed-image-generic",
)
XORG_METAPKGS = (
    "xserver-xorg",
    "libgl1-mesa-glx",
    # LP: #1610434 - Ubuntu GNOME needed libwayland
    "libwayland-egl1-mesa",
)
VBOX_METAPKGS = (
    "virtualbox-guest-utils",
    "virtualbox-guest-source"
)
METAPKGS = KERNEL_METAPKGS + XORG_METAPKGS + VBOX_METAPKGS


class Package:
    """A lightweight apt package """
    def __init__(self, name, version, arch, foreign=False):
        self.name = name
        self.installed_version = version
        self.arch = arch
        self.foreign = foreign


def find_hwe_packages(installed_packages):
    unsupported_hwe_packages = set()
    supported_hwe_packages = set()
    for pkg in installed_packages:
        # metapackages and X are marked with the -lts-$distro string
        for name in HWE_UNSUPPORTED_BACKPORTS:
            if pkg.name.endswith(name):
                unsupported_hwe_packages.add(pkg)
        # The individual backported kernels have names like
        #   linux-image-3.11.0-17-generic
        # so we match via a regexp.
        #
        # The linux-image-generic-lts-$distro metapkg has additional
        #  dependencies (like linux-firmware) so we can't just walk the
        # dependency chain.
        if re.match(UNSUPPORTED_KERNEL_IMAGE_REGEX, pkg.name):
            unsupported_hwe_packages.add(pkg)
        # SUPPORTED
        if pkg.name.endswith(HWE_SUPPORTED_BACKPORT):
            supported_hwe_packages.add(pkg)
        if re.match(SUPPORTED_KERNEL_IMAGE_REGEX, pkg.name):
            supported_hwe_packages.add(pkg)
    return unsupported_hwe_packages, supported_hwe_packages


def is_unsupported_hwe_kernel_running(unsupported_hwe_package):
    # kernels do not conflict with each other, so we need to check
    # what version is actually running
    running_kernel_ver = os.uname()[2]
    # the running kernel without the abi or buildver
    running_kernel_ver = running_kernel_ver.split("-")[0]
    for pkg in unsupported_hwe_package:
        if not pkg.name.startswith("linux-"):
            continue
        # we only care about the version, not abi or build
        if pkg.installed_version.startswith(running_kernel_ver):
            return True
    return False


def is_unsupported_xstack_running(unsupported_hwe_packages):
    # the HWE xstacks conflict with each other, so we can simply test
    # for existence in the installed unsupported hwe packages
    for pkg in unsupported_hwe_packages:
        for xorg_meta in XORG_METAPKGS:
            if pkg.name.startswith(xorg_meta):
                return True
    return False


def find_supported_replacement_hwe_packages(unsupported_hwe_packages,
                                            installed_packages):
    unsupported_metapkg_names = set()
    replacement_names = set()
    for metapkg in METAPKGS:
        for unsupported_backport in HWE_UNSUPPORTED_BACKPORTS:
            metapkg_name = metapkg + unsupported_backport
            for pkg in unsupported_hwe_packages:
                if pkg.name == metapkg_name:
                    replacement_name = metapkg + HWE_SUPPORTED_BACKPORT
                    if (replacement_name, pkg.arch) not in \
                            [(p.name, p.arch) for p in installed_packages]:
                        if pkg.foreign:
                            replacement_name += ':' + pkg.arch
                        replacement_names.add(replacement_name)
                    unsupported_metapkg_names.add(metapkg_name)
    return unsupported_metapkg_names, replacement_names


def is_unsupported_hwe_running(unsupported_hwe_packages):
    return (is_unsupported_hwe_kernel_running(unsupported_hwe_packages) or
            is_unsupported_xstack_running(unsupported_hwe_packages))


def advice_about_hwe_status(unsupported_hwe_packages, supported_hwe_packages,
                            installed_packages, has_update_manager, today,
                            verbose):
    unsupported_hwe_stack_running = is_unsupported_hwe_running(
        unsupported_hwe_packages)
    unsupported_hwe_metapkgs, supported_replacement_hwe = \
        find_supported_replacement_hwe_packages(unsupported_hwe_packages,
                                                installed_packages)
    # we need the "-p" option until the next LTS point release is available
    if today < NEXT_LTS_DOT1_DATE:
        do_release_upgrade_option = "-p"
    else:
        do_release_upgrade_option = ""

    if unsupported_hwe_stack_running:
        if today < HWE_EOL_DATE:
            s = Messages.HWE_SUPPORT_ENDS
        else:
            s = Messages.HWE_SUPPORT_HAS_ENDED
        if has_update_manager:
            print(s + Messages.UM_UPGRADE)
        else:
            # bug #1341320 - if no metapkg is left we need to show
            #                what is no longer supported
            if supported_replacement_hwe:
                print(s + Messages.APT_UPGRADE % (
                    do_release_upgrade_option,
                    " ".join(supported_replacement_hwe)))
            else:
                print(s + Messages.APT_SHOW_UNSUPPORTED % (
                    " ".join([pkg.name for pkg in unsupported_hwe_packages])))

    # some unsupported package installed but not running and not superseded
    # - this is worth reporting
    elif (unsupported_hwe_packages and
          not supported_hwe_packages and
          not unsupported_hwe_stack_running):
        s = _("""
You have packages from the Hardware Enablement Stack (HWE) installed that
are going out of support on %s.
        """) % HWE_EOL_DATE
        if has_update_manager:
            print(s + Messages.UM_UPGRADE)
        else:
            print(s + Messages.APT_UPGRADE % (
                do_release_upgrade_option,
                " ".join(supported_replacement_hwe)))
    elif supported_hwe_packages:
        print(Messages.HWE_SUPPORTED)
    elif verbose:
        print(
            _("You are not running a system with a Hardware Enablement Stack. "
              "Your system is supported until %(month)s %(year)s.") % {
                  'month': LTS_EOL_DATE.strftime("%B"),
                  'year': LTS_EOL_DATE.year})


if __name__ == "__main__":
    parser = optparse.OptionParser(description=_("Check HWE support status"))
    parser.add_option('--quiet', action='store_true', default=False,
                      help="No output, exit code 10 on unsupported HWE "
                      "packages")
    parser.add_option('--verbose', action='store_true', default=False,
                      help="more verbose output")
    parser.add_option('--show-all-unsupported', action='store_true',
                      default=False,
                      help="Show unsupported HWE packages")
    parser.add_option('--show-replacements', action='store_true',
                      default=False,
                      help="show what packages need installing to be "
                      "supported")
    # hidden, only useful for testing
    parser.add_option(
        '--disable-hwe-check-semaphore-file',
        default="/var/lib/update-notifier/disable-hwe-eol-messages",
        help=optparse.SUPPRESS_HELP)
    options, args = parser.parse_args()

    if options.quiet:
        nullfd = os.open(os.devnull, os.O_WRONLY)
        os.dup2(nullfd, sys.stdout.fileno())

    # Check to see if we are an LTS release
    di = distro_info.UbuntuDistroInfo()
    codename = get_dist()
    lts = di.is_lts(codename)
    if not lts:
        if options.verbose:
            print("Only LTS releases have Hardware Enablement stacks",
                  file=sys.stderr)
        sys.exit(0)

    # request from PSE to be able to disable the hwe check via a special
    # semaphore file
    HWE_CHECK_DISABLED_FILE = options.disable_hwe_check_semaphore_file
    if os.path.exists(HWE_CHECK_DISABLED_FILE):
        if options.verbose:
            print("Forcefully disabled hwe-support-status via file %s" %
                  HWE_CHECK_DISABLED_FILE, file=sys.stderr)
        sys.exit(0)

    foreign_archs = set(subprocess.check_output(
        ['dpkg', '--print-foreign-architectures'],
        universal_newlines=True).split())

    # do the actual check
    installed_packages = set()
    today = datetime.date.today()
    tagf = apt.apt_pkg.TagFile("/var/lib/dpkg/status")
    while tagf.step():
        if tagf.section.find("Status", "") != "install ok installed":
            continue
        pkgname = tagf.section.find("Package")
        version = tagf.section.find("Version")
        arch = tagf.section.find("Architecture")
        foreign = arch in foreign_archs
        installed_packages.add(Package(pkgname, version, arch, foreign))

    has_update_manager = "update-manager" in [
        pkg.name for pkg in installed_packages]
    unsupported_hwe_packages, supported_hwe_packages = find_hwe_packages(
        installed_packages)

    if options.show_all_unsupported:
        if today > HWE_EOL_DATE:
            print(twrap(" ".join([
                pkg.foreign and pkg.name + ':' + pkg.arch or pkg.name
                        for pkg in unsupported_hwe_packages])))

    if options.show_replacements:
        unsupported, replacements = find_supported_replacement_hwe_packages(
            unsupported_hwe_packages, installed_packages)
        if replacements:
            print(" ".join(replacements))

    if not options.show_all_unsupported and not options.show_replacements:
        advice_about_hwe_status(
            unsupported_hwe_packages, supported_hwe_packages,
            installed_packages, has_update_manager, today,
            options.verbose)
    if is_unsupported_hwe_running(unsupported_hwe_packages) and \
            today > HWE_EOL_DATE:
        sys.exit(10)

    sys.exit(0)