diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/data/50unattended-upgrades.Debian unattended-upgrades-1.1ubuntu1.18.04.4/data/50unattended-upgrades.Debian --- unattended-upgrades-1.1ubuntu1.18.04.1/data/50unattended-upgrades.Debian 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/data/50unattended-upgrades.Debian 2018-07-13 08:36:23.000000000 +0000 @@ -106,3 +106,10 @@ // Specify syslog facility. Default is daemon // Unattended-Upgrade::SyslogFacility "daemon"; +// Download and install upgrades only on AC power +// (i.e. skip or gracefully stop updates on battery) +// Unattended-Upgrade::OnlyOnACPower "true"; + +// Download and install upgrades only on non-metered connection +// (i.e. skip or gracefully stop updates on a metered connection) +// Unattended-Upgrade::Skip-Updates-On-Metered-Connections "true"; diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/data/50unattended-upgrades.Devuan unattended-upgrades-1.1ubuntu1.18.04.4/data/50unattended-upgrades.Devuan --- unattended-upgrades-1.1ubuntu1.18.04.1/data/50unattended-upgrades.Devuan 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/data/50unattended-upgrades.Devuan 2018-07-13 08:36:23.000000000 +0000 @@ -108,3 +108,10 @@ // Specify syslog facility. Default is daemon // Unattended-Upgrade::SyslogFacility "daemon"; +// Download and install upgrades only on AC power +// (i.e. skip or gracefully stop updates on battery) +// Unattended-Upgrade::OnlyOnACPower "true"; + +// Download and install upgrades only on non-metered connection +// (i.e. skip or gracefully stop updates on a metered connection) +// Unattended-Upgrade::Skip-Updates-On-Metered-Connections "true"; diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/data/50unattended-upgrades.Raspbian unattended-upgrades-1.1ubuntu1.18.04.4/data/50unattended-upgrades.Raspbian --- unattended-upgrades-1.1ubuntu1.18.04.1/data/50unattended-upgrades.Raspbian 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/data/50unattended-upgrades.Raspbian 2018-07-13 08:36:23.000000000 +0000 @@ -97,3 +97,11 @@ // Use apt bandwidth limit feature, this example limits the download // speed to 70kb/sec //Acquire::http::Dl-Limit "70"; + +// Download and install upgrades only on AC power +// (i.e. skip or gracefully stop updates on battery) +// Unattended-Upgrade::OnlyOnACPower "true"; + +// Download and install upgrades only on non-metered connection +// (i.e. skip or gracefully stop updates on a metered connection) +// Unattended-Upgrade::Skip-Updates-On-Metered-Connections "true"; diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/data/50unattended-upgrades.SteamOS unattended-upgrades-1.1ubuntu1.18.04.4/data/50unattended-upgrades.SteamOS --- unattended-upgrades-1.1ubuntu1.18.04.1/data/50unattended-upgrades.SteamOS 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/data/50unattended-upgrades.SteamOS 2018-07-13 08:36:23.000000000 +0000 @@ -106,3 +106,10 @@ // Specify syslog facility. Default is daemon // Unattended-Upgrade::SyslogFacility "daemon"; +// Download and install upgrades only on AC power +// (i.e. skip or gracefully stop updates on battery) +// Unattended-Upgrade::OnlyOnACPower "true"; + +// Download and install upgrades only on non-metered connection +// (i.e. skip or gracefully stop updates on a metered connection) +// Unattended-Upgrade::Skip-Updates-On-Metered-Connections "true"; diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/data/50unattended-upgrades.Ubuntu unattended-upgrades-1.1ubuntu1.18.04.4/data/50unattended-upgrades.Ubuntu --- unattended-upgrades-1.1ubuntu1.18.04.1/data/50unattended-upgrades.Ubuntu 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/data/50unattended-upgrades.Ubuntu 2018-07-13 08:36:23.000000000 +0000 @@ -81,3 +81,11 @@ // Specify syslog facility. Default is daemon // Unattended-Upgrade::SyslogFacility "daemon"; + +// Download and install upgrades only on AC power +// (i.e. skip or gracefully stop updates on battery) +// Unattended-Upgrade::OnlyOnACPower "true"; + +// Download and install upgrades only on non-metered connection +// (i.e. skip or gracefully stop updates on a metered connection) +// Unattended-Upgrade::Skip-Updates-On-Metered-Connections "true"; diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/debian/changelog unattended-upgrades-1.1ubuntu1.18.04.4/debian/changelog --- unattended-upgrades-1.1ubuntu1.18.04.1/debian/changelog 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/debian/changelog 2018-07-13 08:36:23.000000000 +0000 @@ -1,3 +1,43 @@ +unattended-upgrades (1.1ubuntu1.18.04.4) bionic; urgency=medium + + * Redirect stderr output in upgrade-between-snapshots, too, otherwise it + breaks the test sometimes (LP: #1781446) + + -- Balint Reczey Fri, 13 Jul 2018 10:36:23 +0200 + +unattended-upgrades (1.1ubuntu1.18.04.3) bionic; urgency=medium + + * Redirect stderr output in upgrade-all-security, otherwise it breaks the + test (LP: #1781446) + + -- Balint Reczey Thu, 12 Jul 2018 23:57:28 +0200 + +unattended-upgrades (1.1ubuntu1.18.04.2) bionic; urgency=medium + + [ Balint Reczey ] + * Clear cache when autoremoval is invalid for a package set marked for + removal and clear cache after failed commits to return from a possibly + invalid state (LP: #1779157) + * Don't start or gracefully stop upgrade on battery (LP: #1773033) + * Skip updates on metered connections (Closes: #855570) (LP: #1781183) + * Add debian/tests/upgrade-all-security to install all current security updates. + On development releases this tests latest stable, on stable releases it tests + the release itself. + * Speed up unattended-upgrade (Closes: #892028, #899366) (LP: #1396787) + - Adjust candidates only for packages to be possibly installed + - Filter out packages cheaper when they are not from allowed origins + - Collect autoremovable packages, too, when looking for upgradable ones + - Measure time of running with --dry-run in autopkgtests + * Skip starting init.d script in debhelper-generated postinst part + (LP: #1778800) + + [ Ivan Kurnosov ] + * Fixed is_pkgname_in_blacklist to be side-effect free. (LP: #1781176) + Otherwise 'is_pkgname_in_blacklist' mutates the 'pkgs_kept_back' and + 'unattended-upgrades' treats the package as a blacklisted candidate + + -- Balint Reczey Thu, 12 Jul 2018 13:52:24 +0200 + unattended-upgrades (1.1ubuntu1.18.04.1) bionic; urgency=medium [ Michael Vogt ] diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/debian/control unattended-upgrades-1.1ubuntu1.18.04.4/debian/control --- unattended-upgrades-1.1ubuntu1.18.04.1/debian/control 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/debian/control 2018-07-13 08:36:23.000000000 +0000 @@ -6,10 +6,13 @@ Testsuite: autopkgtest Priority: optional Build-Depends: debhelper (>= 9.20160709), + gir1.2-glib-2.0, po-debconf, python, + python-gi, python3, python3-distutils-extra, + python3-gi, python3-setuptools Build-Depends-Indep: python-dev, python3-dev, @@ -17,6 +20,7 @@ pep8, pyflakes, python-apt, +# powermgmt-base, (tests disable on_ac_power checks) python3-apt, python-mock, python3-mock, @@ -29,8 +33,11 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, debconf, + gir1.2-glib-2.0, + powermgmt-base, python3, python3-apt, + python3-gi, ucf, lsb-release, lsb-base, diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/debian/rules unattended-upgrades-1.1ubuntu1.18.04.4/debian/rules --- unattended-upgrades-1.1ubuntu1.18.04.1/debian/rules 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/debian/rules 2018-07-13 08:36:23.000000000 +0000 @@ -22,7 +22,7 @@ override_dh_installinit: # We do not want to run the init script in the postinst/prerm, its # really only useful on shutdown, see Debian bug #645919. - dh_installinit -r + dh_installinit -r --no-start override_dh_auto_clean: # Sanity-check before upload. diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/debian/tests/common-functions unattended-upgrades-1.1ubuntu1.18.04.4/debian/tests/common-functions --- unattended-upgrades-1.1ubuntu1.18.04.1/debian/tests/common-functions 1970-01-01 00:00:00.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/debian/tests/common-functions 2018-07-13 08:36:23.000000000 +0000 @@ -0,0 +1,77 @@ +#!/bin/sh + + +check_arch() { + local architecture + architecture="$(dpkg --print-architecture)" + # run test only on amd64 because it tests only Python code anyway + case "$architecture" in + amd64) + echo "Running upgrade test on $architecture" + ;; + *) + echo "Skipping upgrade test on $architecture" + exit 0 + ;; + esac +} + +chroot_exec() { + local chroot_dir + chroot_dir="$1" + shift + + chroot "$chroot_dir" env http_proxy="$http_proxy" eatmydata "$@" +} + +do_debootstrap() { + local chroot_dir distro mirror + distro="$1" + chroot_dir="$2" + mirror="$3" + + debootstrap --include eatmydata,time "$@" + + mount --bind /dev/pts "$chroot_dir/dev/pts" + mount --bind /proc "$chroot_dir/proc" + trap "umount \"$chroot_dir/proc\"; umount \"$chroot_dir/dev/pts\"; rm -rf \"$chroot_dir\"" EXIT +} + +rebuild_python_apt() { + local chroot_dir + chroot_dir="$1" + + # save list of manually installed packages + chroot_exec "$chroot_dir" apt-mark showmanual > "$chroot_dir/tmp/manual" + + (cd "$chroot_dir"/root/ && apt-get source python-apt 2>&1) + chroot_exec "$chroot_dir" apt-get build-dep -y python-apt + chroot_exec "$chroot_dir" bash -c "cd /root/python-apt-* && env DEB_BUILD_OPTIONS=nocheck dpkg-buildpackage -us -uc && apt-get install -y ../*.deb" 2>&1 + + # remove packages installed as the side-effect of updating apt and python-apt + chroot_exec "$chroot_dir" apt-mark showmanual | comm -1 -3 "$chroot_dir/tmp/manual" - | chroot_exec "$chroot_dir" xargs apt-mark auto + chroot_exec "$chroot_dir" apt-get -y autoremove + chroot_exec "$chroot_dir" apt-get clean + rm "$chroot_dir"/tmp/manual +} + +run_u_u() { + local chroot_dir + chroot_dir="$1" + + # let the test pass on battery power, too + echo 'Unattended-Upgrade::OnlyOnACPower "false";' > "$chroot_dir/etc/apt/apt.conf.d/51unattended-upgrades-acpower" + echo 'Unattended-Upgrade::Skip-Updates-On-Metered-Connections "false";' > "$chroot_dir/etc/apt/apt.conf.d/51unattended-upgrades-metered" + + # enable a few features to test + + echo 'Unattended-Upgrade::Mail "root";' > "$chroot_dir/etc/apt/apt.conf.d/51unattended-upgrades-mail" + chroot_exec "$chroot_dir" apt-get update + chroot_exec "$chroot_dir" unattended-upgrade --download-only + chroot_exec "$chroot_dir" time unattended-upgrade --verbose --dry-run 2>&1 + chroot_exec "$chroot_dir" unattended-upgrade --verbose --debug + chroot_exec "$chroot_dir" time unattended-upgrade --verbose 2>&1 + echo "new packages marked as manually installed (should be none): " + chroot_exec "$chroot_dir" apt-mark showmanual | diff "$chroot_dir/tmp/manual" - + chroot_exec "$chroot_dir" perl -MMIME::QuotedPrint -pe '$_=MIME::QuotedPrint::decode($_);' /var/mail/mail +} diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/debian/tests/control unattended-upgrades-1.1ubuntu1.18.04.4/debian/tests/control --- unattended-upgrades-1.1ubuntu1.18.04.1/debian/tests/control 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/debian/tests/control 2018-07-13 08:36:23.000000000 +0000 @@ -2,6 +2,10 @@ Depends: @, @builddeps@ Restrictions: needs-root, isolation-container, needs-reboot +Tests: upgrade-all-security +Depends: @, @builddeps@, debootstrap, distro-info +Restrictions: needs-root, build-needed + Tests: upgrade-between-snapshots Depends: @, @builddeps@, debootstrap Restrictions: needs-root, build-needed diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/debian/tests/upgrade-all-security unattended-upgrades-1.1ubuntu1.18.04.4/debian/tests/upgrade-all-security --- unattended-upgrades-1.1ubuntu1.18.04.1/debian/tests/upgrade-all-security 1970-01-01 00:00:00.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/debian/tests/upgrade-all-security 2018-07-13 08:36:23.000000000 +0000 @@ -0,0 +1,69 @@ +#!/bin/sh + +# Test for installing all security updates on a the current release in a chroot. +# On development releases the latest stable release is tested since there are no +# security updates to test with. + +set -e + +chroot_dir=$AUTOPKGTEST_TMP/chroot + +distro="$(lsb_release -c -s)" +# fall back to latest release on any development release +if [ "$distro" = sid ] || lsb_release -d -s | grep -q 'development branch'; then + distro="$(distro-info -s)" +fi + +. debian/tests/common-functions + +check_arch + +env DEB_BUILD_OPTIONS=nocheck dpkg-buildpackage -us -uc 2>&1 +do_debootstrap "$distro" "$chroot_dir" + +if [ "$(dpkg-vendor --query Vendor)" = "Ubuntu" ]; then + sed -i "s/main/main universe/" "$chroot_dir/etc/apt/sources.list" +fi +sed "s/^deb /deb-src /" < "$chroot_dir/etc/apt/sources.list" > "$chroot_dir/etc/apt/sources.list.d/src.list" + +chroot_exec "$chroot_dir" apt-get update + +# install mailutils for testing u-u emai +chroot_exec "$chroot_dir" apt-get install -y mailutils exim4-daemon-light 2>&1 + +# add package set with many dependencies +# apt prints "W: APT had planned for dpkg to do more than it reported back" to stderr LP: #1647638 +chroot_exec "$chroot_dir" apt-get install -y xfce4 apparmor 2>&1 + +# build and install updated python-apt since the one in the snapshot has memory allocation issues +rebuild_python_apt "$chroot_dir" + +# install package version just built +cp ../unattended-upgrades_*.deb "$chroot_dir/tmp/" +chroot_exec "$chroot_dir" bash -c 'apt install -y /tmp/unattended-upgrades_*deb' 2>&1 + +sed "s/$distro/$distro-updates/" < "$chroot_dir/etc/apt/sources.list" > "$chroot_dir/etc/apt/sources.list.d/updates.list" +case "$(dpkg-vendor --query Vendor)" in + "Ubuntu") + sed "s/$distro/$distro-security/" < "$chroot_dir/etc/apt/sources.list" > "$chroot_dir/etc/apt/sources.list.d/security.list" + ;; + "Debian") + echo "deb http://security.debian.org/ $distro/updates main" > "$chroot_dir/etc/apt/sources.list.d/security.list" + ;; +esac + +chroot_exec "$chroot_dir" apt-get update + +# save list of manually installed packages +chroot_exec "$chroot_dir" apt-mark showmanual > "$chroot_dir/tmp/manual" + +# clean up to need less space for the test +chroot_exec "$chroot_dir" apt-get clean + +run_u_u "$chroot_dir" + +echo "Checking if there is anything left not upgraded:" +chroot_exec "$chroot_dir" apt-get upgrade --with-new-pkgs -s | tee "$chroot_dir/tmp/updates-left" + +! grep "/$distro-security " "$chroot_dir/tmp/updates-left" || (echo "Security upgrades are held back! Exiting..." && exit 1) + diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/debian/tests/upgrade-between-snapshots unattended-upgrades-1.1ubuntu1.18.04.4/debian/tests/upgrade-between-snapshots --- unattended-upgrades-1.1ubuntu1.18.04.1/debian/tests/upgrade-between-snapshots 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/debian/tests/upgrade-between-snapshots 2018-07-13 08:36:23.000000000 +0000 @@ -5,83 +5,57 @@ chroot_dir=$AUTOPKGTEST_TMP/chroot start_date=20171001T000000Z end_date=20171210T000000Z -chroot_exec="chroot $chroot_dir env http_proxy=$http_proxy eatmydata" -architecture=$(dpkg --print-architecture) +distro=sid -# run test only on amd64 because it tests only Python code and dependencies -# are used from the frozen historical Debian archive -case $architecture in - amd64) - echo "Running Debian snapshot upgrade test on $architecture" - ;; - *) - echo "Skipping Debian snapshot upgrade test on $architecture" - exit 0 - ;; -esac +. debian/tests/common-functions + +check_arch dpkg-buildpackage -us -uc 2>&1 -debootstrap --include eatmydata sid $chroot_dir "http://snapshot.debian.org/archive/debian/$start_date/ unstable main" -mount --bind /dev/pts $chroot_dir/dev/pts -mount --bind /proc $chroot_dir/proc -trap "umount $chroot_dir/proc; umount $chroot_dir/dev/pts; rm -rf $chroot_dir" EXIT - -sed -i 's/^deb /deb [check-valid-until=no] /' $chroot_dir/etc/apt/sources.list -echo "deb-src [check-valid-until=no] http://snapshot.debian.org/archive/debian/${start_date}/ sid main" >> $chroot_dir/etc/apt/sources.list -$chroot_exec apt-get update +do_debootstrap "$distro" "$chroot_dir" "http://snapshot.debian.org/archive/debian/$start_date/ unstable main" + +sed -i 's/^deb /deb [check-valid-until=no] /' "$chroot_dir/etc/apt/sources.list" +echo "deb-src [check-valid-until=no] http://snapshot.debian.org/archive/debian/${start_date}/ $distro main" >> "$chroot_dir/etc/apt/sources.list" +chroot_exec "$chroot_dir" apt-get update # install mailutils for testing u-u email and mark python3.5-minimal # because otherwise u-u autoremoves it while running python3.5 code -$chroot_exec apt-get install -y python3.5-minimal mailutils +chroot_exec "$chroot_dir" apt-get install -y python3.5-minimal mailutils 2>&1 # add package set with many dependencies -$chroot_exec apt-get install -y xfce4 - -# save list of manually installed packages -$chroot_exec apt-mark showmanual > $chroot_dir/tmp/manual +# apt prints "W: APT had planned for dpkg to do more than it reported back" to stderr LP: #1647638 +chroot_exec "$chroot_dir" apt-get install -y xfce4 2>&1 # build and install updated python-apt since the one in the snapshot has memory allocation issues -(cd $chroot_dir/root/ && apt-get source python-apt 2>&1) -$chroot_exec apt-get build-dep -y python-apt -$chroot_exec bash -c "cd /root/python-apt-* && env DEB_BUILD_OPTIONS=nocheck dpkg-buildpackage -us -uc && apt-get install -y ../*.deb" 2>&1 - -# remove packages installed as the side-effect of updating apt and python-apt -$chroot_exec apt-mark showmanual | comm -1 -3 $chroot_dir/tmp/manual - | $chroot_exec xargs apt-mark auto -$chroot_exec apt-get -y autoremove -$chroot_exec apt-get clean +rebuild_python_apt "$chroot_dir" # install package version just built -cp ../unattended-upgrades_*.deb $chroot_dir/tmp/ -$chroot_exec bash -c 'apt install -y /tmp/unattended-upgrades_*deb' 2>&1 +cp ../unattended-upgrades_*.deb "$chroot_dir/tmp/" +chroot_exec "$chroot_dir" bash -c 'apt install -y /tmp/unattended-upgrades_*deb' 2>&1 # save list of manually installed packages -$chroot_exec apt-mark showmanual > $chroot_dir/tmp/manual +chroot_exec "$chroot_dir" apt-mark showmanual > "$chroot_dir/tmp/manual" # hold a package to test if autoremoval honors that -$chroot_exec apt-mark hold libpoppler68 +chroot_exec "$chroot_dir" apt-mark hold libpoppler68 # also blacklist a package echo 'Unattended-Upgrade::Package-Blacklist {"libx265-13.*"};' > \ - $chroot_dir/etc/apt/apt.conf.d/51unattended-upgrades-blacklist + "$chroot_dir/etc/apt/apt.conf.d/51unattended-upgrades-blacklist" # clean up to need less space for the test -$chroot_exec apt-get clean +chroot_exec "$chroot_dir" apt-get clean # install Debian's configuration file to make this test work as expected even on other distributions -cp data/50unattended-upgrades.Debian $chroot_dir/etc/apt/apt.conf.d/50unattended-upgrades +cp data/50unattended-upgrades.Debian "$chroot_dir/etc/apt/apt.conf.d/50unattended-upgrades" + +# use new snapshot +sed -i "s/$start_date/$end_date/" "$chroot_dir/etc/apt/sources.list" -# enable a few features to test -sed -i 's|//Unattended-Upgrade::Mail |Unattended-Upgrade::Mail |' $chroot_dir/etc/apt/apt.conf.d/50unattended-upgrades -sed -i "s/$start_date/$end_date/" $chroot_dir/etc/apt/sources.list -$chroot_exec apt-get update -$chroot_exec unattended-upgrade --verbose --debug -echo "new packages marked as manually installed (should be none): " -$chroot_exec apt-mark showmanual | diff $chroot_dir/tmp/manual - -$chroot_exec perl -MMIME::QuotedPrint -pe '$_=MIME::QuotedPrint::decode($_);' /var/mail/mail +run_u_u "$chroot_dir" echo "libpoppler68 should be still installed and held:" -$chroot_exec dpkg -l libpoppler68 | grep '^hi' +chroot_exec "$chroot_dir" dpkg -l libpoppler68 | grep '^hi' echo "libx265-130 should be still installed thanks to the blacklist" -$chroot_exec dpkg -l libx265-130 | grep '^ii' - +chroot_exec "$chroot_dir" dpkg -l libx265-130 | grep '^ii' diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/debian/unattended-upgrades.service unattended-upgrades-1.1ubuntu1.18.04.4/debian/unattended-upgrades.service --- unattended-upgrades-1.1ubuntu1.18.04.1/debian/unattended-upgrades.service 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/debian/unattended-upgrades.service 2018-07-13 08:36:23.000000000 +0000 @@ -1,7 +1,6 @@ [Unit] Description=Unattended Upgrades Shutdown After=network.target local-fs.target -ConditionACPower=true RequiresMountsFor=/var/log /var/run /var/lib /boot Documentation=man:unattended-upgrade(8) diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/test/aptroot/etc/apt/apt.conf.d/50unattended-upgrades unattended-upgrades-1.1ubuntu1.18.04.4/test/aptroot/etc/apt/apt.conf.d/50unattended-upgrades --- unattended-upgrades-1.1ubuntu1.18.04.1/test/aptroot/etc/apt/apt.conf.d/50unattended-upgrades 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/test/aptroot/etc/apt/apt.conf.d/50unattended-upgrades 2018-07-13 08:36:23.000000000 +0000 @@ -34,3 +34,11 @@ // Use apt bandwidth limit feature, this example limits the download // speed to 70kb/sec //Acquire::http::Dl-Limit "70"; + +//Download and install upgrades only on AC power +// (i.e. skip or gracefully stop updates on battery) +Unattended-Upgrade::OnlyOnACPower "false"; + +// Download and install upgrades only on non-metered connection +// (i.e. skip or gracefully stop updates on a metered connection) +Unattended-Upgrade::Skip-Updates-On-Metered-Connections "false"; diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/test/root.untrusted/etc/apt/apt.conf unattended-upgrades-1.1ubuntu1.18.04.4/test/root.untrusted/etc/apt/apt.conf --- unattended-upgrades-1.1ubuntu1.18.04.1/test/root.untrusted/etc/apt/apt.conf 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/test/root.untrusted/etc/apt/apt.conf 2018-07-13 08:36:23.000000000 +0000 @@ -1,3 +1,5 @@ Unattended-Upgrade::Allowed-Origins { "Ubuntu:lucid-security"; }; +Unattended-Upgrade::OnlyOnACPower "false"; +Unattended-Upgrade::Skip-Updates-On-Metered-Connections "false"; diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/test/root.unused-deps/etc/apt/apt.conf unattended-upgrades-1.1ubuntu1.18.04.4/test/root.unused-deps/etc/apt/apt.conf --- unattended-upgrades-1.1ubuntu1.18.04.1/test/root.unused-deps/etc/apt/apt.conf 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/test/root.unused-deps/etc/apt/apt.conf 2018-07-13 08:36:23.000000000 +0000 @@ -1,6 +1,8 @@ +Unattended-Upgrade::OnlyOnACPower "false"; Unattended-Upgrade::Keep-Debs-After-Install "true"; Unattended-Upgrade::Allowed-Origins { "Ubuntu:lucid-security"; }; Unattended-Upgrade::Remove-New-Unused-Dependencies "true"; +Unattended-Upgrade::Skip-Updates-On-Metered-Connections "false"; diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/test/test_blacklisted_wrong_origin.py unattended-upgrades-1.1ubuntu1.18.04.4/test/test_blacklisted_wrong_origin.py --- unattended-upgrades-1.1ubuntu1.18.04.1/test/test_blacklisted_wrong_origin.py 1970-01-01 00:00:00.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/test/test_blacklisted_wrong_origin.py 2018-07-13 08:36:23.000000000 +0000 @@ -0,0 +1,43 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import sys +import unittest + +from mock import ( + Mock, +) + +from unattended_upgrade import calculate_upgradable_pkgs + + +class TestBlacklistedWrongOrigin(unittest.TestCase): + + @unittest.skipIf(sys.version_info[0] != 3, "only works on py3") + def test_if_origin_does_not_match_then_blacklist_is_not_checked(self): + origin = Mock() + origin.origin = "some-other-origin" + + pkg = Mock() + pkg.name = "postgresql" + pkg.is_upgradable = True + pkg.candidate = Mock() + pkg.candidate.origins = [origin] + + options = Mock() + + pkgs_to_upgrade, pkgs_kept_back = \ + calculate_upgradable_pkgs([pkg], + options, + ["o=allowed-origin"], + ["postgresql"], + []) + + self.assertListEqual([], pkgs_to_upgrade) + self.assertListEqual([], pkgs_kept_back) + + +if __name__ == "__main__": + import logging + logging.basicConfig(level=logging.DEBUG) + unittest.main() diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/test/test_dev_release.py unattended-upgrades-1.1ubuntu1.18.04.4/test/test_dev_release.py --- unattended-upgrades-1.1ubuntu1.18.04.1/test/test_dev_release.py 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/test/test_dev_release.py 2018-07-13 08:36:23.000000000 +0000 @@ -33,6 +33,7 @@ apt_conf = os.path.join(self.rootdir, "etc", "apt", "apt.conf") with open(apt_conf, "w") as fp: fp.write("""Unattended-Upgrade::DevRelease "false"; +Unattended-Upgrade::OnlyOnACPower "false"; """) # run it diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/test/test_remove_unused.py unattended-upgrades-1.1ubuntu1.18.04.4/test/test_remove_unused.py --- unattended-upgrades-1.1ubuntu1.18.04.1/test/test_remove_unused.py 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/test/test_remove_unused.py 2018-07-13 08:36:23.000000000 +0000 @@ -88,11 +88,13 @@ apt_conf = os.path.join(self.rootdir, "etc", "apt", "apt.conf") with open(apt_conf, "w") as fp: fp.write(""" +Unattended-Upgrade::OnlyOnACPower "false"; Unattended-Upgrade::Keep-Debs-After-Install "true"; Unattended-Upgrade::Allowed-Origins { "Ubuntu:lucid-security"; }; Unattended-Upgrade::Remove-Unused-Dependencies "true"; +Unattended-Upgrade::Skip-Updates-On-Metered-Connections "false"; """) options = MockOptions() unattended_upgrade.DISTRO_DESC = "Ubuntu 10.04" @@ -115,11 +117,13 @@ apt_conf = os.path.join(self.rootdir, "etc", "apt", "apt.conf") with open(apt_conf, "w") as fp: fp.write(""" +Unattended-Upgrade::OnlyOnACPower "false"; Unattended-Upgrade::Keep-Debs-After-Install "true"; Unattended-Upgrade::Allowed-Origins { "Ubuntu:lucid-security"; }; Unattended-Upgrade::Remove-New-Unused-Dependencies "true"; +Unattended-Upgrade::Skip-Updates-On-Metered-Connections "false"; """) options = MockOptions() unattended_upgrade.DISTRO_DESC = "Ubuntu 10.04" diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/test/test_untrusted.py unattended-upgrades-1.1ubuntu1.18.04.4/test/test_untrusted.py --- unattended-upgrades-1.1ubuntu1.18.04.1/test/test_untrusted.py 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/test/test_untrusted.py 2018-07-13 08:36:23.000000000 +0000 @@ -43,6 +43,8 @@ fp.write("""Unattended-Upgrade::Allowed-Origins { "Ubuntu:lucid-security"; }; +Unattended-Upgrade::OnlyOnACPower "false"; +Unattended-Upgrade::Skip-Updates-On-Metered-Connections "false"; """) # ensure there is no conffile_prompt check apt.apt_pkg.config.set("DPkg::Options::", "--force-confold") diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/test/unattended_upgrade.py unattended-upgrades-1.1ubuntu1.18.04.4/test/unattended_upgrade.py --- unattended-upgrades-1.1ubuntu1.18.04.1/test/unattended_upgrade.py 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/test/unattended_upgrade.py 2018-07-13 08:36:23.000000000 +0000 @@ -29,6 +29,7 @@ import fcntl import fnmatch import gettext +from gi.repository.Gio import NetworkMonitor import grp import io import locale @@ -113,11 +114,16 @@ pass +class NoAllowedOriginError(ValueError): + pass + + class UnattendedUpgradesCache(apt.Cache): def __init__(self, rootdir, allowed_origins): # type: (str, List[str]) -> None - self._cached_candidate_pkgnames = None # type: AbstractSet[str] + self._cached_candidate_pkgnames = set() # type: AbstractSet[str] + self.initial_autoremovable_pkgs = set() # type: AbstractSet[str] self.allowed_origins = allowed_origins apt.Cache.__init__(self, rootdir=rootdir) @@ -142,21 +148,6 @@ self.versioned_kernel_pkgs_regexp = None self.running_kernel_pkgs_regexp = None - # ensure we update the candidate versions - self.adjust_candidates() - - def open(self, progress=None): - # type: (apt.progress.base.Progress) -> None - apt.Cache.open(self, progress) - # ensure we update the candidate versions - self.adjust_candidates() - - def clear(self): - # type: () -> None - apt.Cache.clear(self) - # ensure we update the candidate versions - self.adjust_candidates() - def adjust_candidate(self, pkg): # type: (apt.Package) -> bool """ Adjust origin and return True if adjustment took place @@ -165,53 +156,63 @@ the security pocket but there is also a package in the updates pocket with a higher version number """ - new_cand = None - for ver in pkg.versions: - # ignore versions that the user marked with priority < 100 - # (and ensure we have a python-apt that supports this) - if (hasattr(ver, "policy_priority") and ver.policy_priority < 100): - logging.debug("ignoring ver %s with priority < 0" % ver) - continue - if is_allowed_origin(ver, self.allowed_origins): - # leave as soon as we have the highest new candidate - new_cand = ver - break - if new_cand and new_cand != pkg.candidate: - logging.debug("adjusting candidate version: %s" % new_cand) - pkg.candidate = new_cand - return True - return False + try: + new_cand = ver_in_allowed_origin(pkg, self.allowed_origins) + if new_cand != pkg.candidate: + logging.debug("adjusting candidate version: %s" % new_cand) + pkg.candidate = new_cand + return True + else: + return False + except NoAllowedOriginError: + return False - def adjust_candidates(self): - # tyoe: () -> None - """ Adjust all package candidates when needed saving cached package - names to self._cached_candidate_pkgnames + def call_adjusted(self, function, pkg, **kwargs): + """Call function, but with adjusting + packages in changes to come from allowed origins + + Note that as a side effect more package's candidate can be + adjusted than only the one's in the final changes set. """ - if self._cached_candidate_pkgnames is None: - # first run, collect package names - self._cached_candidate_pkgnames = set() - for pkg in self: - # important! this avoids downgrades below - if pkg.is_installed and not pkg.is_upgradable: - continue - # check if the candidate is already pointing to a allowed - # origin and if so, do not mess with it - if is_allowed_origin(pkg.candidate, self.allowed_origins): - continue - # check if we have a version in a allowed origin that is - # not the candidate - if self.adjust_candidate(pkg): - # ... and save it for next runs - self._cached_candidate_pkgnames.add(pkg.name) + new_pkgs_to_adjust = [] # List[str] + pkgs_with_no_allowed_origin = [] + + # adjust candidates in advance if needed + for pkg_name in self._cached_candidate_pkgnames: + self.adjust_candidate(self[pkg_name]) + + if function == apt.package.Package.mark_upgrade \ + and not pkg.is_upgradable: + raise NoAllowedOriginError + function(pkg, **kwargs) + changes = self.get_changes() + for marked_pkg in changes: + if not is_allowed_origin(marked_pkg.candidate, + self.allowed_origins): + try: + ver_in_allowed_origin(marked_pkg, + self.allowed_origins) + # important! this avoids downgrades below + if pkg.is_installed and not pkg.is_upgradable: + continue + new_pkgs_to_adjust.append(marked_pkg) + except NoAllowedOriginError: + pkgs_with_no_allowed_origin.append(marked_pkg) + + if new_pkgs_to_adjust: + for pkg_to_adjust in new_pkgs_to_adjust: + self.adjust_candidate(pkg_to_adjust) + self._cached_candidate_pkgnames.add(pkg_to_adjust.name) + self.call_adjusted(function, pkg, **kwargs) else: - # packages to be adjusted are already collected - for pkgname in self._cached_candidate_pkgnames: - pkg = self[pkgname] - # important! this avoids downgrades and also skips packages - # installed since previous adjustment runs - if pkg.is_installed and not pkg.is_upgradable: - continue - self.adjust_candidate(pkg) + if pkgs_with_no_allowed_origin: + raise NoAllowedOriginError + + def mark_upgrade_adjusted(self, pkg, **kwargs): + self.call_adjusted(apt.package.Package.mark_upgrade, pkg, **kwargs) + + def mark_install_adjusted(self, pkg, **kwargs): + self.call_adjusted(apt.package.Package.mark_install, pkg, **kwargs) class LogInstallProgress(apt.progress.base.InstallProgress): @@ -384,6 +385,27 @@ SIGNAL_STOP_REQUEST = True +def should_stop(): + # type: () -> bool + """ + Return True if u-u needs to stop due to signal received or due to the + system started to run on battery. + """ + if SIGNAL_STOP_REQUEST: + logging.warning("SIGNAL received, stopping") + return True + if apt_pkg.config.find_b("Unattended-Upgrade::OnlyOnACPower", True) and \ + subprocess.call("on_ac_power") == 1: + logging.warning("System is on battery power, stopping") + return True + if apt_pkg.config.find_b( + "Unattended-Upgrade::Skip-Updates-On-Metered-Connections", True): + if NetworkMonitor.get_network_metered(NetworkMonitor.get_default()): + logging.warning(_("System is on metered connection, stopping")) + return True + return False + + def substitute(line): # type: (str) -> str """ substitude known mappings and return a new string @@ -523,7 +545,7 @@ error = e if verbose: logging.exception("Exception happened during upgrade.") - + cache.clear() return res, error @@ -587,14 +609,14 @@ if pkgname not in to_upgrade: # pkg is upgraded in a previous set continue - if SIGNAL_STOP_REQUEST: - logging.warning("SIGNAL received, stopping") + if should_stop(): return False pkg = cache[pkgname] if pkg.is_upgradable: - pkg.mark_upgrade(from_user=not pkg.is_auto_installed) + cache.mark_upgrade_adjusted(pkg, + from_user=not pkg.is_auto_installed) elif not pkg.is_installed: - pkg.mark_install(from_user=False) + cache.mark_install_adjusted(pkg, from_user=False) else: continue # double check that we are not running into side effects like @@ -649,12 +671,28 @@ return False -def is_pkgname_in_blacklist(pkgname, blacklist, pkgs_kept_back): - # type: (str, List[str], List[str]) -> bool +def ver_in_allowed_origin(pkg, allowed_origins): + # type: (apt.Package, List[str]) -> apt.package.Version + for ver in pkg.versions: + # ignore versions that the user marked with priority < 100 + # (and ensure we have a python-apt that supports this) + try: + if ver.policy_priority < 100: + logging.debug("ignoring ver %s with priority < 0" % ver) + continue + except AttributeError: + pass + if is_allowed_origin(ver, allowed_origins): + # leave as soon as we have the highest new candidate + return ver + raise NoAllowedOriginError() + + +def is_pkgname_in_blacklist(pkgname, blacklist): + # type: (str, List[str]) -> bool for blacklist_regexp in blacklist: if re.match(blacklist_regexp, pkgname): logging.debug("skipping blacklisted package %s" % pkgname) - pkgs_kept_back.append(pkgname) return True return False @@ -674,7 +712,7 @@ def is_pkg_change_allowed(pkg, blacklist, whitelist): # type: (apt.Package, List[str], List[str]) -> bool - if is_pkgname_in_blacklist(pkg.name, blacklist, []): + if is_pkgname_in_blacklist(pkg.name, blacklist): logging.debug("pkg %s package has been blacklisted" % pkg.name) return False # a strict whitelist will not allow any changes not in the @@ -1240,7 +1278,16 @@ ): # type: (...) -> None try: - pkg.mark_upgrade(from_user=not pkg.is_auto_installed) + try: + # try to adjust pkg itself first, if that throws an exception it + # can't be upgraded on its own + cache.adjust_candidate(pkg) + if not pkg.is_upgradable: + return + except NoAllowedOriginError: + return + cache._cached_candidate_pkgnames.add(pkg.name) + cache.mark_upgrade_adjusted(pkg, from_user=not pkg.is_auto_installed) if check_changes_for_sanity(cache, allowed_origins, blacklisted_pkgs, whitelisted_pkgs, pkg): @@ -1258,7 +1305,7 @@ logging.debug("sanity check failed") rewind_cache(cache, pkgs_to_upgrade) pkgs_kept_back.append(pkg.name) - except SystemError as e: + except (SystemError, NoAllowedOriginError) as e: # can't upgrade logging.warning( _("package %s upgradable but fails to " @@ -1279,23 +1326,31 @@ # now do the actual upgrade for pkg in cache: + # cache already autoremovable packages to avoid the first filtering + # for them + if pkg.is_auto_removable: + cache.initial_autoremovable_pkgs.add(pkg.name) if options.debug and pkg.is_upgradable: logging.debug("Checking: %s (%s)" % ( pkg.name, getattr(pkg.candidate, "origins", []))) if (pkg.is_upgradable and - not is_pkgname_in_blacklist(pkg.name, blacklisted_pkgs, - pkgs_kept_back) and - is_pkgname_in_whitelist(pkg.name, whitelisted_pkgs) and - is_allowed_origin(pkg.candidate, allowed_origins)): - - try_to_upgrade(pkg, - pkgs_to_upgrade, - pkgs_kept_back, - cache, - allowed_origins, - blacklisted_pkgs, - whitelisted_pkgs) + is_pkgname_in_whitelist(pkg.name, whitelisted_pkgs)): + try: + ver_in_allowed_origin(pkg, allowed_origins) + except NoAllowedOriginError: + continue + + if not is_pkgname_in_blacklist(pkg.name, blacklisted_pkgs): + try_to_upgrade(pkg, + pkgs_to_upgrade, + pkgs_kept_back, + cache, + allowed_origins, + blacklisted_pkgs, + whitelisted_pkgs) + else: + pkgs_kept_back.append(pkg.name) return pkgs_to_upgrade, pkgs_kept_back @@ -1384,8 +1439,7 @@ pkgs_kept_installed = [] # type: List[str] if minimal_steps: for pkgname in auto_removable: - if SIGNAL_STOP_REQUEST: - logging.warning("SIGNAL received, stopping") + if should_stop(): pkgs_kept_installed = list(auto_removable - set(pkgs_removed)) return (False, pkgs_removed, pkgs_kept_installed) logging.debug("marking %s for removal" % pkgname) @@ -1422,6 +1476,8 @@ else: res = True cache.clear() + else: + cache.clear() if res: logging.info(_("Packages that were successfully auto-removed: %s"), @@ -1611,8 +1667,7 @@ # don't start downloading during shutdown # TODO: download files one by one and check for stop request after each of # them - if SIGNAL_STOP_REQUEST: - logging.warning("SIGNAL received, stopping") + if should_stop(): return 1 try: pm.get_archives(fetcher, list, recs) @@ -1683,7 +1738,8 @@ for pkg in old_pkgs_to_upgrade: logging.debug("Checking the black and whitelist: %s" % (pkg.name)) - pkg.mark_upgrade(from_user=not pkg.is_auto_installed) + cache.mark_upgrade_adjusted( + pkg, from_user=not pkg.is_auto_installed) if check_changes_for_sanity(cache, allowed_origins, blacklisted_pkgs, @@ -1695,7 +1751,9 @@ logging.info(_("package %s not upgraded"), pkg.name) cache.clear() for pkg2 in pkgs_to_upgrade: - pkg2.mark_upgrade(from_user=not pkg2.is_auto_installed) + cache.call_adjusted( + apt.package.Package.mark_upgrade, pkg2, + from_user=not pkg2.is_auto_installed) else: logging.debug("dpkg is configured not to cause conffile prompts") @@ -1706,7 +1764,7 @@ pass # auto-removals - auto_removable = get_auto_removable(cache) + auto_removable = cache.initial_autoremovable_pkgs kernel_pkgs_remove_success = True # type: bool kernel_pkgs_removed = [] # type: List[str] kernel_pkgs_kept_installed = [] # type: List[str] diff -Nru unattended-upgrades-1.1ubuntu1.18.04.1/unattended-upgrade unattended-upgrades-1.1ubuntu1.18.04.4/unattended-upgrade --- unattended-upgrades-1.1ubuntu1.18.04.1/unattended-upgrade 2018-06-06 23:30:55.000000000 +0000 +++ unattended-upgrades-1.1ubuntu1.18.04.4/unattended-upgrade 2018-07-13 08:36:23.000000000 +0000 @@ -29,6 +29,7 @@ import fcntl import fnmatch import gettext +from gi.repository.Gio import NetworkMonitor import grp import io import locale @@ -113,11 +114,16 @@ pass +class NoAllowedOriginError(ValueError): + pass + + class UnattendedUpgradesCache(apt.Cache): def __init__(self, rootdir, allowed_origins): # type: (str, List[str]) -> None - self._cached_candidate_pkgnames = None # type: AbstractSet[str] + self._cached_candidate_pkgnames = set() # type: AbstractSet[str] + self.initial_autoremovable_pkgs = set() # type: AbstractSet[str] self.allowed_origins = allowed_origins apt.Cache.__init__(self, rootdir=rootdir) @@ -142,21 +148,6 @@ self.versioned_kernel_pkgs_regexp = None self.running_kernel_pkgs_regexp = None - # ensure we update the candidate versions - self.adjust_candidates() - - def open(self, progress=None): - # type: (apt.progress.base.Progress) -> None - apt.Cache.open(self, progress) - # ensure we update the candidate versions - self.adjust_candidates() - - def clear(self): - # type: () -> None - apt.Cache.clear(self) - # ensure we update the candidate versions - self.adjust_candidates() - def adjust_candidate(self, pkg): # type: (apt.Package) -> bool """ Adjust origin and return True if adjustment took place @@ -165,53 +156,63 @@ the security pocket but there is also a package in the updates pocket with a higher version number """ - new_cand = None - for ver in pkg.versions: - # ignore versions that the user marked with priority < 100 - # (and ensure we have a python-apt that supports this) - if (hasattr(ver, "policy_priority") and ver.policy_priority < 100): - logging.debug("ignoring ver %s with priority < 0" % ver) - continue - if is_allowed_origin(ver, self.allowed_origins): - # leave as soon as we have the highest new candidate - new_cand = ver - break - if new_cand and new_cand != pkg.candidate: - logging.debug("adjusting candidate version: %s" % new_cand) - pkg.candidate = new_cand - return True - return False + try: + new_cand = ver_in_allowed_origin(pkg, self.allowed_origins) + if new_cand != pkg.candidate: + logging.debug("adjusting candidate version: %s" % new_cand) + pkg.candidate = new_cand + return True + else: + return False + except NoAllowedOriginError: + return False - def adjust_candidates(self): - # tyoe: () -> None - """ Adjust all package candidates when needed saving cached package - names to self._cached_candidate_pkgnames + def call_adjusted(self, function, pkg, **kwargs): + """Call function, but with adjusting + packages in changes to come from allowed origins + + Note that as a side effect more package's candidate can be + adjusted than only the one's in the final changes set. """ - if self._cached_candidate_pkgnames is None: - # first run, collect package names - self._cached_candidate_pkgnames = set() - for pkg in self: - # important! this avoids downgrades below - if pkg.is_installed and not pkg.is_upgradable: - continue - # check if the candidate is already pointing to a allowed - # origin and if so, do not mess with it - if is_allowed_origin(pkg.candidate, self.allowed_origins): - continue - # check if we have a version in a allowed origin that is - # not the candidate - if self.adjust_candidate(pkg): - # ... and save it for next runs - self._cached_candidate_pkgnames.add(pkg.name) + new_pkgs_to_adjust = [] # List[str] + pkgs_with_no_allowed_origin = [] + + # adjust candidates in advance if needed + for pkg_name in self._cached_candidate_pkgnames: + self.adjust_candidate(self[pkg_name]) + + if function == apt.package.Package.mark_upgrade \ + and not pkg.is_upgradable: + raise NoAllowedOriginError + function(pkg, **kwargs) + changes = self.get_changes() + for marked_pkg in changes: + if not is_allowed_origin(marked_pkg.candidate, + self.allowed_origins): + try: + ver_in_allowed_origin(marked_pkg, + self.allowed_origins) + # important! this avoids downgrades below + if pkg.is_installed and not pkg.is_upgradable: + continue + new_pkgs_to_adjust.append(marked_pkg) + except NoAllowedOriginError: + pkgs_with_no_allowed_origin.append(marked_pkg) + + if new_pkgs_to_adjust: + for pkg_to_adjust in new_pkgs_to_adjust: + self.adjust_candidate(pkg_to_adjust) + self._cached_candidate_pkgnames.add(pkg_to_adjust.name) + self.call_adjusted(function, pkg, **kwargs) else: - # packages to be adjusted are already collected - for pkgname in self._cached_candidate_pkgnames: - pkg = self[pkgname] - # important! this avoids downgrades and also skips packages - # installed since previous adjustment runs - if pkg.is_installed and not pkg.is_upgradable: - continue - self.adjust_candidate(pkg) + if pkgs_with_no_allowed_origin: + raise NoAllowedOriginError + + def mark_upgrade_adjusted(self, pkg, **kwargs): + self.call_adjusted(apt.package.Package.mark_upgrade, pkg, **kwargs) + + def mark_install_adjusted(self, pkg, **kwargs): + self.call_adjusted(apt.package.Package.mark_install, pkg, **kwargs) class LogInstallProgress(apt.progress.base.InstallProgress): @@ -384,6 +385,27 @@ SIGNAL_STOP_REQUEST = True +def should_stop(): + # type: () -> bool + """ + Return True if u-u needs to stop due to signal received or due to the + system started to run on battery. + """ + if SIGNAL_STOP_REQUEST: + logging.warning("SIGNAL received, stopping") + return True + if apt_pkg.config.find_b("Unattended-Upgrade::OnlyOnACPower", True) and \ + subprocess.call("on_ac_power") == 1: + logging.warning("System is on battery power, stopping") + return True + if apt_pkg.config.find_b( + "Unattended-Upgrade::Skip-Updates-On-Metered-Connections", True): + if NetworkMonitor.get_network_metered(NetworkMonitor.get_default()): + logging.warning(_("System is on metered connection, stopping")) + return True + return False + + def substitute(line): # type: (str) -> str """ substitude known mappings and return a new string @@ -523,7 +545,7 @@ error = e if verbose: logging.exception("Exception happened during upgrade.") - + cache.clear() return res, error @@ -587,14 +609,14 @@ if pkgname not in to_upgrade: # pkg is upgraded in a previous set continue - if SIGNAL_STOP_REQUEST: - logging.warning("SIGNAL received, stopping") + if should_stop(): return False pkg = cache[pkgname] if pkg.is_upgradable: - pkg.mark_upgrade(from_user=not pkg.is_auto_installed) + cache.mark_upgrade_adjusted(pkg, + from_user=not pkg.is_auto_installed) elif not pkg.is_installed: - pkg.mark_install(from_user=False) + cache.mark_install_adjusted(pkg, from_user=False) else: continue # double check that we are not running into side effects like @@ -649,12 +671,28 @@ return False -def is_pkgname_in_blacklist(pkgname, blacklist, pkgs_kept_back): - # type: (str, List[str], List[str]) -> bool +def ver_in_allowed_origin(pkg, allowed_origins): + # type: (apt.Package, List[str]) -> apt.package.Version + for ver in pkg.versions: + # ignore versions that the user marked with priority < 100 + # (and ensure we have a python-apt that supports this) + try: + if ver.policy_priority < 100: + logging.debug("ignoring ver %s with priority < 0" % ver) + continue + except AttributeError: + pass + if is_allowed_origin(ver, allowed_origins): + # leave as soon as we have the highest new candidate + return ver + raise NoAllowedOriginError() + + +def is_pkgname_in_blacklist(pkgname, blacklist): + # type: (str, List[str]) -> bool for blacklist_regexp in blacklist: if re.match(blacklist_regexp, pkgname): logging.debug("skipping blacklisted package %s" % pkgname) - pkgs_kept_back.append(pkgname) return True return False @@ -674,7 +712,7 @@ def is_pkg_change_allowed(pkg, blacklist, whitelist): # type: (apt.Package, List[str], List[str]) -> bool - if is_pkgname_in_blacklist(pkg.name, blacklist, []): + if is_pkgname_in_blacklist(pkg.name, blacklist): logging.debug("pkg %s package has been blacklisted" % pkg.name) return False # a strict whitelist will not allow any changes not in the @@ -1240,7 +1278,16 @@ ): # type: (...) -> None try: - pkg.mark_upgrade(from_user=not pkg.is_auto_installed) + try: + # try to adjust pkg itself first, if that throws an exception it + # can't be upgraded on its own + cache.adjust_candidate(pkg) + if not pkg.is_upgradable: + return + except NoAllowedOriginError: + return + cache._cached_candidate_pkgnames.add(pkg.name) + cache.mark_upgrade_adjusted(pkg, from_user=not pkg.is_auto_installed) if check_changes_for_sanity(cache, allowed_origins, blacklisted_pkgs, whitelisted_pkgs, pkg): @@ -1258,7 +1305,7 @@ logging.debug("sanity check failed") rewind_cache(cache, pkgs_to_upgrade) pkgs_kept_back.append(pkg.name) - except SystemError as e: + except (SystemError, NoAllowedOriginError) as e: # can't upgrade logging.warning( _("package %s upgradable but fails to " @@ -1279,23 +1326,31 @@ # now do the actual upgrade for pkg in cache: + # cache already autoremovable packages to avoid the first filtering + # for them + if pkg.is_auto_removable: + cache.initial_autoremovable_pkgs.add(pkg.name) if options.debug and pkg.is_upgradable: logging.debug("Checking: %s (%s)" % ( pkg.name, getattr(pkg.candidate, "origins", []))) if (pkg.is_upgradable and - not is_pkgname_in_blacklist(pkg.name, blacklisted_pkgs, - pkgs_kept_back) and - is_pkgname_in_whitelist(pkg.name, whitelisted_pkgs) and - is_allowed_origin(pkg.candidate, allowed_origins)): - - try_to_upgrade(pkg, - pkgs_to_upgrade, - pkgs_kept_back, - cache, - allowed_origins, - blacklisted_pkgs, - whitelisted_pkgs) + is_pkgname_in_whitelist(pkg.name, whitelisted_pkgs)): + try: + ver_in_allowed_origin(pkg, allowed_origins) + except NoAllowedOriginError: + continue + + if not is_pkgname_in_blacklist(pkg.name, blacklisted_pkgs): + try_to_upgrade(pkg, + pkgs_to_upgrade, + pkgs_kept_back, + cache, + allowed_origins, + blacklisted_pkgs, + whitelisted_pkgs) + else: + pkgs_kept_back.append(pkg.name) return pkgs_to_upgrade, pkgs_kept_back @@ -1384,8 +1439,7 @@ pkgs_kept_installed = [] # type: List[str] if minimal_steps: for pkgname in auto_removable: - if SIGNAL_STOP_REQUEST: - logging.warning("SIGNAL received, stopping") + if should_stop(): pkgs_kept_installed = list(auto_removable - set(pkgs_removed)) return (False, pkgs_removed, pkgs_kept_installed) logging.debug("marking %s for removal" % pkgname) @@ -1422,6 +1476,8 @@ else: res = True cache.clear() + else: + cache.clear() if res: logging.info(_("Packages that were successfully auto-removed: %s"), @@ -1611,8 +1667,7 @@ # don't start downloading during shutdown # TODO: download files one by one and check for stop request after each of # them - if SIGNAL_STOP_REQUEST: - logging.warning("SIGNAL received, stopping") + if should_stop(): return 1 try: pm.get_archives(fetcher, list, recs) @@ -1683,7 +1738,8 @@ for pkg in old_pkgs_to_upgrade: logging.debug("Checking the black and whitelist: %s" % (pkg.name)) - pkg.mark_upgrade(from_user=not pkg.is_auto_installed) + cache.mark_upgrade_adjusted( + pkg, from_user=not pkg.is_auto_installed) if check_changes_for_sanity(cache, allowed_origins, blacklisted_pkgs, @@ -1695,7 +1751,9 @@ logging.info(_("package %s not upgraded"), pkg.name) cache.clear() for pkg2 in pkgs_to_upgrade: - pkg2.mark_upgrade(from_user=not pkg2.is_auto_installed) + cache.call_adjusted( + apt.package.Package.mark_upgrade, pkg2, + from_user=not pkg2.is_auto_installed) else: logging.debug("dpkg is configured not to cause conffile prompts") @@ -1706,7 +1764,7 @@ pass # auto-removals - auto_removable = get_auto_removable(cache) + auto_removable = cache.initial_autoremovable_pkgs kernel_pkgs_remove_success = True # type: bool kernel_pkgs_removed = [] # type: List[str] kernel_pkgs_kept_installed = [] # type: List[str]