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
|
# UpdateList.py
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
#
# Copyright (c) 2004-2012 Canonical
#
# Author: Michael Vogt <mvo@debian.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA
from __future__ import print_function
from gettext import gettext as _
import apt
import logging
import operator
import random
import subprocess
class UpdateOrigin(object):
def __init__(self, desc, importance):
self.packages = []
self.importance = importance
self.description = desc
class OriginsImportance:
# filed in by us
SECURITY = 10
UPDATES = 9
PROPOSED = 8
BACKPORTS = 7
ARCHIVE = 6
# this is filed in by MyCache
OTHER = 0
# this is used by us
OTHER_UNKNOWN = -1
class UpdateList(object):
"""
class that contains the list of available updates in
self.pkgs[origin] where origin is the user readable string
"""
# the key in the debian/control file used to add the phased
# updates percentage
PHASED_UPDATES_KEY = "Phased-Update-Percentage"
# the file that contains the uniq machine id
UNIQ_MACHINE_ID_FILE = "/var/lib/dbus/machine-id"
# the configuration key to turn phased-updates always on
ALWAYS_INCLUDE_PHASED_UPDATES = (
"Update-Manager::Always-Include-Phased-Updates")
def __init__(self, parent):
# a map of packages under their origin
try:
dist = subprocess.check_output(
["lsb_release", "-c", "-s"], universal_newlines=True).strip()
except subprocess.CalledProcessError as e:
print("Error in lsb_release: %s" % e)
dist = None
self.distUpgradeWouldDelete = 0
self.pkgs = {}
self.num_updates = 0
self.matcher = self.initMatcher(dist)
self.random = random.Random()
# a stable machine uniq id
with open(self.UNIQ_MACHINE_ID_FILE) as f:
self.machine_uniq_id = f.read()
def initMatcher(self, dist):
# (origin, archive, description, importance)
matcher_templates = [
(None, None, _("Other updates"), OriginsImportance.OTHER_UNKNOWN)
]
if dist:
matcher_templates += [
("%s-security" % dist, "Ubuntu",
_("Important security updates"), OriginsImportance.SECURITY),
("%s-updates" % dist, "Ubuntu",
_("Recommended updates"), OriginsImportance.UPDATES),
("%s-proposed" % dist, "Ubuntu",
_("Proposed updates"), OriginsImportance.PROPOSED),
("%s-backports" % dist, "Ubuntu",
_("Backports"), OriginsImportance.BACKPORTS),
(dist, "Ubuntu",
_("Distribution updates"), OriginsImportance.ARCHIVE),
]
matcher = {}
for (origin, archive, desc, importance) in matcher_templates:
matcher[(origin, archive)] = UpdateOrigin(desc, importance)
return matcher
def is_ignored_phased_update(self, pkg):
""" This will test if the pkg is a phased updated and if
it needs to get installed or ignored.
:return: True if the updates should be ignored
"""
# allow the admin to override this
if apt.apt_pkg.config.find_b(
self.ALWAYS_INCLUDE_PHASED_UPDATES, False):
return False
if self.PHASED_UPDATES_KEY in pkg.candidate.record:
# its important that we always get the same result on
# multiple runs of the update-manager, so we need to
# feed a seed that is a combination of the pkg/ver/machine
self.random.seed("%s-%s-%s" % (
pkg.name, pkg.candidate.version,
self.machine_uniq_id))
threshold = pkg.candidate.record[self.PHASED_UPDATES_KEY]
percentage = self.random.randint(0, 100)
if percentage > int(threshold):
logging.info("holding back phased update (%s < %s)" % (
threshold, percentage))
return True
return False
def update(self, cache):
self.held_back = []
# do the upgrade
self.distUpgradeWouldDelete = cache.saveDistUpgrade()
#dselect_upgrade_origin = UpdateOrigin(_("Previous selected"), 1)
# sort by origin
for pkg in cache:
if pkg.is_upgradable or pkg.marked_install:
if getattr(pkg.candidate, "origins", None) is None:
# can happen for e.g. locked packages
# FIXME: do something more sensible here (but what?)
print("WARNING: upgradable but no candidate.origins?!?: ",
pkg.name)
continue
# check where the package belongs
origin_node = cache.match_package_origin(pkg, self.matcher)
# see if its a phased update and *not* a security update
# or shadowing a security update
if (origin_node.importance != OriginsImportance.SECURITY and
self.is_ignored_phased_update(pkg)):
continue
if origin_node not in self.pkgs:
self.pkgs[origin_node] = []
self.pkgs[origin_node].append(pkg)
self.num_updates = self.num_updates + 1
if pkg.is_upgradable and not (pkg.marked_upgrade or
pkg.marked_install):
self.held_back.append(pkg.name)
for l in self.pkgs.keys():
self.pkgs[l].sort(key=operator.attrgetter("name"))
self.keepcount = cache._depcache.keep_count
|