37
38
warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning)
43
45
from gettext import gettext as _
47
# the reboot required flag file used by packages
48
REBOOT_REQUIRED_FILE = "/var/run/reboot-required"
49
MAIL_BINARY = "/usr/bin/mail"
50
DISTRO_CODENAME = lsb_release.get_distro_information()['CODENAME']
51
DISTRO_ID = lsb_release.get_distro_information()['ID']
45
53
class MyCache(apt.Cache):
46
54
def __init__(self):
47
apt.Cache.__init__(self, apt.progress.base.OpProgress())
55
apt.Cache.__init__(self)
49
57
self._depcache.init()
50
58
assert (self._depcache.inst_count == 0 and
51
59
self._depcache.broken_count == 0 and
52
60
self._depcache.del_count == 0)
63
""" substitude known mappings and return a new string
65
Currently supported "${distro-release}
67
mapping = {"distro_codename" : get_distro_codename(),
68
"distro_id" : get_distro_id(),
70
return string.Template(line).substitute(mapping)
72
def get_distro_codename():
73
return DISTRO_CODENAME
78
def get_allowed_origins():
79
""" return a list of allowed origins from apt.conf
81
This will take substitutions (like distro_id) into account.
84
for s in apt_pkg.config.value_list("Unattended-Upgrade::Allowed-Origins"):
85
(distro_id, distro_codename) = s.split()
86
allowed_origins.append((substitute(distro_id),
87
substitute(distro_codename)))
88
return allowed_origins
55
90
def is_allowed_origin(pkg, allowed_origins):
56
91
if not pkg.candidate:
68
103
if pkg.marked_delete:
104
logging.debug("pkg '%s' now marked delete" % pkg.name)
70
106
if pkg.marked_install or pkg.marked_upgrade:
71
107
if not is_allowed_origin(pkg, allowed_origins):
108
logging.debug("pkg '%s' not in allowed origin" % pkg.name)
73
110
if pkg.name in blacklist:
111
logging.debug("pkg '%s' blacklisted" % pkg.name)
113
if pkg._pkg.selected_state == apt_pkg.SELSTATE_HOLD:
114
logging.debug("pkg '%s' is on hold" % pkg.name)
80
121
control = apt_inst.DebFile(debfile).control.extractdata("control")
81
122
sections = apt_pkg.TagSection(control)
82
123
return sections["Package"]
83
except SystemError, e:
124
except (IOError, SystemError), e:
84
125
logging.error("failed to read deb file '%s' (%s)" % (debfile, e))
86
127
return debfile.split("_")[0]
88
def conffile_prompt(destFile):
129
# prefix is *only* needed for the build-in tests
130
def conffile_prompt(destFile, prefix=""):
89
131
logging.debug("check_conffile_prompt('%s')" % destFile)
90
132
pkgname = pkgname_from_deb(destFile)
91
133
status_file = apt_pkg.config.find("Dir::State::status")
100
142
for line in string.split(conffiles,"\n"):
101
143
logging.debug("conffile line: %s", line)
102
144
l = string.split(string.strip(line))
109
if os.path.exists(file) and obs != "obsolete":
110
current_md5 = apt_pkg.md5sum(open(file).read())
111
if current_md5 != md5:
151
# ignore if conffile is obsolete or does not exist
152
if obs == "obsolete" or not os.path.exists(prefix+conf_file):
154
# get conffile value from pkg
155
pkg_conffiles = apt_inst.debExtractControl(
156
open(destFile), "conffiles").split("\n")
157
if not conf_file in pkg_conffiles:
158
logging.debug("'%s' not in package conffiles '%s'" % (conf_file, pkg_conffiles))
160
# test against the installed file
161
current_md5 = apt_pkg.md5sum(open(prefix+conf_file).read())
162
logging.debug("current md5: %s" % current_md5)
163
# hashes are the same, no conffile prompt
164
if current_md5 == md5:
166
# calculate md5sum from the deb (may take a bit)
167
dpkg_cmd = ["dpkg-deb","--fsys-tarfile",destFile]
168
tar_cmd = ["tar","-x","-O", "-f","-", "."+conf_file]
170
dpkg_p = Popen(dpkg_cmd, stdout=PIPE)
171
tar_p = Popen(tar_cmd, stdin=dpkg_p.stdout, stdout=PIPE)
172
md5_p = Popen(md5_cmd, stdin=tar_p.stdout, stdout=PIPE)
173
pkg_md5sum = md5_p.communicate()[0].split()[0]
174
logging.debug("pkg_md5sum: %s" % pkg_md5sum)
175
# the md5sum in the deb is unchanged, this will not
176
# trigger a conffile prompt
177
if pkg_md5sum == md5:
179
# if we made it to this point:
180
# current_md5 != pkg_md5sum != md5
181
# and that will trigger a conffile prompt, we can
182
# stop processing at this point and just return True
116
187
def dpkg_conffile_prompt():
117
if "DPkg::Options" not in apt_pkg.config:
188
if not "DPkg::Options" in apt_pkg.config:
119
190
options = apt_pkg.config.value_list("DPkg::Options")
120
191
for option in map(string.strip, options):
158
229
# setup env (to play it safe) and return
159
230
os.putenv("APT_LISTCHANGES_FRONTEND","none");
232
def send_summary_mail(pkgs, res, pkgs_kept_back, mem_log, logfile_dpkg):
233
" send mail (if configured in Unattended-Upgrades::Mail) "
234
email = apt_pkg.config.find("Unattended-Upgrade::Mail", "")
237
if not os.path.exists(MAIL_BINARY):
238
logging.error(_("No '/usr/bin/mail', can not send mail. "
239
"You probably want to install the 'mailx' package."))
241
# Check if reboot-required flag is present
242
logging.debug("Sending mail with '%s' to '%s'" % (logfile_dpkg, email))
243
if os.path.isfile(REBOOT_REQUIRED_FILE):
244
subject = _("[reboot required] unattended-upgrades result for '%s'") % host()
246
subject = _("unattended-upgrades result for '%s'") % host()
247
mail = subprocess.Popen([MAIL_BINARY, "-s", subject,
248
email], stdin=subprocess.PIPE)
249
s = _("Unattended upgrade returned: %s\n\n") % res
250
if os.path.isfile(REBOOT_REQUIRED_FILE):
251
s += _("Warning: A reboot is required to complete this upgrade.\n\n")
252
s += _("Packages that are upgraded:\n")
253
s += " " + wrap(pkgs, 70, " ")
256
s += _("Packages with upgradable origin but kept back:\n")
257
s += " " + wrap(" ".join(pkgs_kept_back), 70, " ")
260
s += _("Package installation log:")+"\n"
261
s += open(logfile_dpkg).read()
263
s += _("Unattended-upgrades log:\n")
264
s += mem_log.getvalue()
268
logging.debug("mail returned: %s" % ret)
165
274
parser.add_option("-d", "--debug",
166
275
action="store_true", dest="debug", default=False,
167
276
help=_("print debug messages"))
277
parser.add_option("", "--dry-run",
278
action="store_true", default=False,
279
help=_("Simulation, download but do not install"))
168
280
(options, args) = parser.parse_args()
283
logger = logging.getLogger()
169
285
if options.debug:
170
logging.getLogger().setLevel(logging.DEBUG)
173
#dldir = "/tmp/pyapt-test"
176
# os.mkdir(dldir+"/partial")
179
#apt_pkg.config.set("Dir::Cache::archives",dldir)
286
logger.setLevel(logging.DEBUG)
287
stderr_handler = logging.StreamHandler()
288
logger.addHandler(stderr_handler)
289
if apt_pkg.config.find("Unattended-Upgrade::Mail", ""):
290
mem_log_handler = logging.StreamHandler(mem_log)
291
logger.addHandler(mem_log_handler)
181
293
# format (origin, archive), e.g. ("Ubuntu","dapper-security")
182
allowed_origins = map(string.split, apt_pkg.config.value_list("Unattended-Upgrade::Allowed-Origins"))
294
allowed_origins = get_allowed_origins()
184
296
# pkgs that are (for some reason) not save to install
185
297
blacklisted_pkgs = apt_pkg.config.value_list("Unattended-Upgrade::Package-Blacklist")
189
301
# display available origin
190
302
logging.info(_("Allowed origins are: %s") % map(str,allowed_origins))
306
apt_pkg.pkgsystem_lock()
307
except SystemError, e:
308
logging.error(_("Lock could not be acquired (another package "
309
"manager running?)"))
310
print _("Cache lock can not be acquired, exiting")
193
314
cache = MyCache()
194
315
if cache._depcache.broken_count > 0:
201
322
# find out about the packages that are upgradable (in a allowed_origin)
202
323
pkgs_to_upgrade = []
203
324
pkgs_kept_back = []
325
pkgs_auto_removable = set([pkg.name for pkg in cache
326
if pkg.is_auto_removable])
204
327
for pkg in cache:
205
328
if options.debug and pkg.is_upgradable:
206
329
logging.debug("Checking: %s (%s)" % (pkg.name, map(str, pkg.candidate.origins)))
217
340
pkgs_kept_back.append(pkg.name)
218
341
except SystemError, e:
220
logging.warning(_("package '%s' upgradable but fails to be marked for upgrade (%s)") % e)
221
rewind_cache(cache, pkgs_to_ugprade)
343
logging.warning(_("package '%s' upgradable but fails to be marked for upgrade (%s)") % (pkg.name, e))
344
rewind_cache(cache, pkgs_to_upgrade)
222
345
pkgs_kept_back.append(pkg.name)
252
375
print _("The URI '%s' failed to download, aborting") % item.desc_uri
253
376
logging.error(_("The URI '%s' failed to download, aborting") % item.desc_uri)
378
if not os.path.exists(item.destfile):
379
print _("Download finished, but file '%s' not there?!?" % item.destfile)
380
logging.error("Download finished, but file '%s' not there?!?" % item.destfile)
255
382
if not item.is_trusted:
256
383
blacklisted_pkgs.append(pkgname_from_deb(item.destfile))
257
384
if conffile_prompt(item.destfile):
288
415
logging.debug("dpkg is configured not to cause conffile prompts")
418
if apt_pkg.config.find_b("Unattended-Upgrade::Remove-Unused-Dependencies", False):
419
now_auto_removable = set([pkg.name for pkg in cache
420
if pkg.is_auto_removable])
421
for pkgname in now_auto_removable-pkgs_auto_removable:
422
logging.debug("marking %s for remove" % pkgname)
423
cache[pkgname].mark_delete()
424
logging.info(_("Packages that are auto removed: '%s'") %
425
" ".join(now_auto_removable-pkgs_auto_removable))
290
427
logging.debug("InstCount=%i DelCount=%i BrokenCout=%i" % (cache._depcache.inst_count, cache._depcache.del_count, cache._depcache.broken_count))
292
429
# exit if there is nothing to do and nothing to report
294
431
logging.info(_("No packages found that can be upgraded unattended"))
434
# check if we are in dry-run mode
436
logging.info("Option --dry-run given, *not* performing real actions")
437
apt_pkg.config.set("Debug::pkgDPkgPM","1")
297
439
# do the install based on the new list of pkgs
298
440
pkgs = " ".join([pkg.name for pkg in pkgs_to_upgrade])
299
441
logging.info(_("Packages that are upgraded: %s" % pkgs))
311
453
logfile_dpkg = logdir+'unattended-upgrades-dpkg_%s.log' % now.isoformat('_')
312
454
logging.info(_("Writing dpkg log to '%s'") % logfile_dpkg)
313
455
fd = os.open(logfile_dpkg, os.O_RDWR|os.O_CREAT, 0644)
456
old_stdout = os.dup(1)
457
old_stderr = os.dup(2)
319
apt_pkg.config.set("Debug::pkgDPkgPM","1")
320
461
# create a new package-manager. the blacklist may have changed
321
462
# the markings in the depcache
322
463
pm = apt_pkg.PackageManager(cache._depcache)
325
466
# run the fetcher again (otherwise local file://
326
467
# URIs are unhappy (see LP: #56832)
327
468
res = fetcher.run()
471
apt_pkg.pkgsystem_unlock()
472
except SystemError, e:
474
# lock for the shutdown check - its fine if the system
475
# is shutdown while downloading but not so much while installing
476
apt_pkg.get_lock("/var/run/unattended-upgrades.lock")
328
477
# now do the actual install
330
480
res = pm.do_install()
331
481
except SystemError,e:
483
res = pm.RESULT_FAILED
485
os.dup2(old_stdout, 1)
486
os.dup2(old_stderr, 2)
488
if res == pm.RESULT_FAILED:
332
489
logging.error(_("Installing the upgrades failed!"))
333
490
logging.error(_("error message: '%s'") % e)
336
if res == pm.RESULT_FAILED:
337
491
logging.error(_("dpkg returned a error! See '%s' for details") % logfile_dpkg)
339
493
logging.info(_("All upgrades installed"))
341
# check if we need to send a mail
342
email = apt_pkg.config.find("Unattended-Upgrade::Mail", "")
344
if not os.path.exists("/usr/bin/mail"):
345
logging.error(_("No '/usr/bin/mail', can not send mail. "
346
"You probably want to install the 'mailx' package."))
348
logging.debug("Sending mail with '%s' to '%s'" % (logfile_dpkg, email))
349
mail = subprocess.Popen(["/usr/bin/mail",
350
"-s", _("unattended-upgrades result "
351
"for '%s'") % host(),
353
stdin=subprocess.PIPE)
354
s = _("Unattended upgrade returned: %s\n\n") % (res != pm.ResultFailed)
355
s += _("Packages that are upgraded:\n")
356
s += " " + wrap(pkgs, 70, " ")
359
s += _("Packages with upgradable origin but kept back:\n")
360
s += " " + wrap(" ".join(pkgs_kept_back), 70, " ")
363
s += _("Package installation log:")
364
s += open(logfile_dpkg).read()
368
logging.debug("mail returned: %s" % ret)
495
# send a mail (if needed)
496
pkg_install_success = (res != pm.RESULT_FAILED)
497
send_summary_mail(pkgs, pkg_install_success, pkgs_kept_back, mem_log, logfile_dpkg)
499
# auto-reboot (if required and the config for this is set
500
if (apt_pkg.config.find_b("Unattended-Upgrade::Automatic-Reboot", False) and
501
os.path.exists(REBOOT_REQUIRED_FILE)):
502
logging.warning("Found %s, rebooting" % REBOOT_REQUIRED_FILE)
503
subprocess.call(["/sbin/reboot"])
371
506
if __name__ == "__main__":
372
507
localesApp="unattended-upgrades"