47
49
# the reboot required flag file used by packages
48
50
REBOOT_REQUIRED_FILE = "/var/run/reboot-required"
49
51
MAIL_BINARY = "/usr/bin/mail"
52
SENDMAIL_BINARY = "/usr/sbin/sendmail"
50
53
DISTRO_CODENAME = lsb_release.get_distro_information()['CODENAME']
51
54
DISTRO_ID = lsb_release.get_distro_information()['ID']
53
class MyCache(apt.Cache):
55
apt.Cache.__init__(self)
58
assert (self._depcache.inst_count == 0 and
59
self._depcache.broken_count == 0 and
60
self._depcache.del_count == 0)
56
# set from the sigint signal handler
57
SIGNAL_STOP_REQUEST=False
61
""" context manager for unlocking the apt lock while cache.commit()
66
apt_pkg.pkgsystem_unlock()
69
def __exit__(self, exc_type, exc_value, exc_tb):
71
apt_pkg.pkgsystem_unlock()
76
def signal_handler(signal, frame):
77
logging.warn("SIGUSR1 recieved, will stop")
78
global SIGNAL_STOP_REQUEST
79
SIGNAL_STOP_REQUEST=True
62
81
def substitute(line):
63
82
""" substitude known mappings and return a new string
65
Currently supported "${distro-release}
84
Currently supported ${distro-release}
67
86
mapping = {"distro_codename" : get_distro_codename(),
68
87
"distro_id" : get_distro_id(),
87
103
(distro_id, distro_codename) = s.split(':')
89
105
(distro_id, distro_codename) = s.split()
90
allowed_origins.append((substitute(distro_id),
91
substitute(distro_codename)))
92
return allowed_origins
106
# convert to new format
107
allowed_origins.append("o=%s,a=%s" % (substitute(distro_id),
108
substitute(distro_codename)))
109
return allowed_origins
111
def get_allowed_origins():
112
""" return a list of allowed origins from apt.conf
114
This will take substitutions (like distro_id) into account.
116
allowed_origins = get_allowed_origins_legacy()
117
for s in apt_pkg.config.value_list("Unattended-Upgrade::Origins-Pattern"):
118
allowed_origins.append(substitute(s))
119
return allowed_origins
121
def match_whitelist_string(whitelist, origin):
123
take a whitelist string in the form "origin=Debian,label=Debian-Security"
124
and match against the given python-apt origin. A empty whitelist string
125
never matches anything.
127
whitelist = whitelist.strip()
129
logging.warn("empty match string matches nothing")
132
# make "\," the html quote equivalent
133
whitelist = whitelist.replace("\,", "%2C")
134
for token in whitelist.split(","):
135
# strip and unquote the "," back
136
(what, value) = [s.strip().replace("%2C",",")
137
for s in token.split("=")]
138
#logging.debug("matching '%s'='%s' against '%s'" % (what, value, origin))
139
# first char is apt-cache policy output, send is the name
140
# in the Release file
141
if what in ("o", "origin"):
142
res &= (value == origin.origin)
143
elif what in ("l", "label"):
144
res &= (value == origin.label)
145
elif what in ("a", "suite", "archive"):
146
res &= (value == origin.archive)
147
elif what in ("c", "component"):
148
res &= (value == origin.component)
149
elif what in ("site",):
150
res &= (value == origin.site)
153
def upgrade_normal(cache, pkgs_to_upgrade, logfile_dpkg):
159
except SystemError,e:
162
logging.info(_("All upgrades installed"))
164
logging.error(_("Installing the upgrades failed!"))
165
logging.error(_("error message: '%s'") % error)
166
logging.error(_("dpkg returned a error! See '%s' for details") % \
170
def upgrade_in_minimal_steps(cache, pkgs_to_upgrade, logfile_dpkg=""):
171
# setup signal handler
172
signal.signal(signal.SIGUSR1, signal_handler)
174
# to upgrade contains the package names
175
to_upgrade = set(pkgs_to_upgrade)
178
smallest_partition = to_upgrade
179
for pkgname in to_upgrade:
180
if SIGNAL_STOP_REQUEST:
181
logging.warn("SIGNAL recieved, stopping")
184
if pkg.is_upgradable:
186
elif not pkg.is_installed:
190
changes = [pkg.name for pkg in cache.get_changes()]
191
if len(changes) == 1:
192
logging.debug("found leaf package %s" % pkg.name)
193
smallest_partition = changes
195
if len(changes) < len(smallest_partition):
196
logging.debug("found partition of size %s (%s)" % (len(changes), changes))
197
smallest_partition = changes
200
logging.debug("applying set %s" % smallest_partition)
201
rewind_cache(cache, [cache[name] for name in smallest_partition])
206
raise Exception("cache.commit() returned false")
209
logging.error(_("Installing the upgrades failed!"))
210
logging.error(_("error message: '%s'") % e)
211
logging.error(_("dpkg returned a error! See '%s' for details") % \
214
to_upgrade = to_upgrade-set(smallest_partition)
215
logging.debug("left to upgrade %s" % to_upgrade)
216
if len(to_upgrade) == 0:
217
logging.info(_("All upgrades installed"))
94
221
def is_allowed_origin(pkg, allowed_origins):
95
222
if not pkg.candidate:
97
224
for origin in pkg.candidate.origins:
98
225
for allowed in allowed_origins:
99
if origin.origin == allowed[0] and origin.archive == allowed[1]:
226
if match_whitelist_string(allowed, origin):
221
def setup_apt_listchanges():
222
" deal with apt-listchanges "
223
conf = "/etc/apt/listchanges.conf"
349
def setup_apt_listchanges(conf="/etc/apt/listchanges.conf"):
350
""" deal with apt-listchanges """
224
351
if os.path.exists(conf):
225
352
# check if mail is used by apt-listchanges
226
353
cf = ConfigParser.ConfigParser()
228
if cf.has_section("apt") and cf.has_option("apt","frontend"):
355
if cf.has_section("apt") and cf.has_option("apt", "frontend"):
229
356
frontend = cf.get("apt","frontend")
230
if frontend == "mail" and os.path.exists("/usr/sbin/sendmail"):
357
if frontend == "mail" and os.path.exists(SENDMAIL_BINARY):
231
358
# mail frontend and sendmail, we are fine
232
359
logging.debug("apt-listchanges is set to mail frontend, ignoring")
234
361
# setup env (to play it safe) and return
235
os.putenv("APT_LISTCHANGES_FRONTEND","none");
362
os.environ["APT_LISTCHANGES_FRONTEND"] = "none"
237
364
def send_summary_mail(pkgs, res, pkgs_kept_back, mem_log, logfile_dpkg):
238
365
" send mail (if configured in Unattended-Upgrades::Mail) "
278
405
logging.debug("mail returned: %s" % ret)
283
parser = OptionParser()
284
parser.add_option("-d", "--debug",
285
action="store_true", dest="debug", default=False,
286
help=_("print debug messages"))
287
parser.add_option("", "--dry-run",
288
action="store_true", default=False,
289
help=_("Simulation, download but do not install"))
290
(options, args) = parser.parse_args()
408
def _setup_alternative_rootdir(rootdir):
409
# clear system unattended-upgrade stuff
410
apt_pkg.config.clear("Unattended-Upgrade")
411
# read rootdir (taken from apt.Cache, but we need to run it
412
# here before the cache gets initialized
413
if os.path.exists(rootdir+"/etc/apt/apt.conf"):
414
apt_pkg.read_config_file(apt_pkg.config,
415
rootdir + "/etc/apt/apt.conf")
416
if os.path.isdir(rootdir+"/etc/apt/apt.conf.d"):
417
apt_pkg.read_config_dir(apt_pkg.config,
418
rootdir + "/etc/apt/apt.conf.d")
421
logdir= apt_pkg.config.find_dir(
422
"Unattended-Upgrade::LogDir",
424
apt_pkg.config.find_dir("APT::UnattendedUpgrades::LogDir",
425
"/var/log/unattended-upgrades/"))
428
def _setup_logging(options):
430
logdir = _get_logdir()
431
logfile = os.path.join(
434
"Unattended-Upgrade::LogFile",
436
apt_pkg.config.find("APT::UnattendedUpgrades::LogFile",
437
"unattended-upgrades.log")))
439
if not options.dry_run and not os.path.exists(os.path.dirname(logfile)):
440
os.makedirs(os.path.dirname(logfile))
442
logging.basicConfig(level=logging.INFO,
443
format='%(asctime)s %(levelname)s %(message)s',
447
def main(options, rootdir=""):
451
_setup_alternative_rootdir(rootdir)
453
_setup_logging(options)
293
456
logger = logging.getLogger()
462
625
now = datetime.datetime.now()
463
logfile_dpkg = logdir+'unattended-upgrades-dpkg_%s.log' % now.isoformat('_')
626
logfile_dpkg = os.path.join(
627
_get_logdir(), 'unattended-upgrades-dpkg_%s.log' % now.isoformat('_'))
464
628
logging.info(_("Writing dpkg log to '%s'") % logfile_dpkg)
465
629
fd = os.open(logfile_dpkg, os.O_RDWR|os.O_CREAT, 0644)
466
630
old_stdout = os.dup(1)
467
631
old_stderr = os.dup(2)
471
# create a new package-manager. the blacklist may have changed
472
# the markings in the depcache
473
pm = apt_pkg.PackageManager(cache._depcache)
474
if not pm.get_archives(fetcher,list,recs):
475
logging.error(_("pm.GetArchives() failed"))
476
# run the fetcher again (otherwise local file://
477
# URIs are unhappy (see LP: #56832)
481
apt_pkg.pkgsystem_unlock()
482
except SystemError, e:
484
# lock for the shutdown check - its fine if the system
485
# is shutdown while downloading but not so much while installing
486
apt_pkg.get_lock("/var/run/unattended-upgrades.lock")
487
# now do the actual install
490
res = pm.do_install()
491
except SystemError,e:
493
res = pm.RESULT_FAILED
495
os.dup2(old_stdout, 1)
496
os.dup2(old_stderr, 2)
498
if res == pm.RESULT_FAILED:
499
logging.error(_("Installing the upgrades failed!"))
500
logging.error(_("error message: '%s'") % e)
501
logging.error(_("dpkg returned a error! See '%s' for details") % logfile_dpkg)
503
logging.info(_("All upgrades installed"))
636
# lock for the shutdown check - its fine if the system
637
# is shutdown while downloading but not so much while installing
638
apt_pkg.get_lock("/var/run/unattended-upgrades.lock")
640
if (options.minimal_upgrade_steps or
641
apt_pkg.config.find_b("Unattended-Upgrades::MinimalSteps", False)):
642
open("/var/run/unattended-upgrades.pid", "w").write("%s" % os.getpid())
643
# try upgrade all "pkgs" in minimal steps
644
pkg_install_success = upgrade_in_minimal_steps(
645
cache, [pkg.name for pkg in pkgs_to_upgrade], logfile_dpkg)
647
pkg_install_success = upgrade_normal(
648
cache, [pkg.name for pkg in pkgs_to_upgrade], logfile_dpkg)
650
# print unhandled exceptions here this way, while stderr is redirected
651
os.write(old_stderr, "Exception: %s" % e)
654
os.dup2(old_stdout, 1)
655
os.dup2(old_stderr, 2)
505
657
# send a mail (if needed)
506
pkg_install_success = (res != pm.RESULT_FAILED)
507
send_summary_mail(pkgs, pkg_install_success, pkgs_kept_back, mem_log, logfile_dpkg)
658
if not options.dry_run:
660
pkgs, pkg_install_success, pkgs_kept_back, mem_log, logfile_dpkg)
509
662
# auto-reboot (if required and the config for this is set
510
663
if (apt_pkg.config.find_b("Unattended-Upgrade::Automatic-Reboot", False) and
519
672
gettext.bindtextdomain(localesApp, localesDir)
520
673
gettext.textdomain(localesApp)
676
parser = OptionParser()
677
parser.add_option("-d", "--debug",
678
action="store_true", dest="debug", default=False,
679
help=_("print debug messages"))
680
parser.add_option("", "--dry-run",
681
action="store_true", default=False,
682
help=_("Simulation, download but do not install"))
683
parser.add_option("", "--minimal_upgrade_steps",
684
action="store_true", default=False,
685
help=_("Upgrade in minimal steps (and allow interrupting with SIGINT"))
686
(options, args) = parser.parse_args()
522
688
if os.getuid() != 0:
523
689
print _("You need to be root to run this application")
526
if not os.path.exists("/var/log/unattended-upgrades"):
527
os.makedirs("/var/log/unattended-upgrades")
530
logdir = apt_pkg.config.find_dir("APT::UnattendedUpgrades::LogDir",
531
"/var/log/unattended-upgrades/")
532
logfile = logdir+apt_pkg.config.find("APT::UnattendedUpgrades::LogFile",
533
"unattended-upgrades.log")
534
logging.basicConfig(level=logging.INFO,
535
format='%(asctime)s %(levelname)s %(message)s',
694
subprocess.call(["ionice","-c3", "-p",str(os.getpid())])
537
696
# run the main code