./Changelog 0000644 0000000 0000000 00000015323 13475213717 011541 0 ustar root root 2007-02-14:
- automatically generate the .py file from the .ui files
2007-02-14:
- fix the $dist-proposed handling
- fix in $dist-commercial handling (LP#66783)
- updated demotions
2007-02-13:
- fix in the reboot code (lp: #84538)
- make some string more distro neutral
2007-02-07:
- add support for the cdrom upgrader to update itself
2007-02-05:
- use the new python-apt aptsources api
- server mode has magic to deal with runing under ssh
2007-02-02:
- merged the KDE frontend (thanks Riddell)
- added tasks support
- add dist-upgrade --mode=server
2007-02-01:
- fix apport integration
2007-01-29:
- fixes in the InstallProgress.error() method
- DistUpgrade/DistUpgradeView.py: fix InstallProgress refactoring
- updated obsoletes
- auto-generate the codename for the upgrade based on the build-system coden (lp: #77620)
- various bugfixes
- apport support
2006-12-12:
- rewrote the _checkFreeSpace() and add better checking
for /boot (#70683)
2006-11-28:
- add quirks rule to install ndiswrapper-utils-1.9 if 1.8 is
installed
2006-11-27:
- fix caption in main glade file
- use "Dpkg::StopOnError","False" option
2006-11-23:
- initial feisty upload
2006-10-28:
- catch errors when load_icon() does not work
2006-10-27:
- reset self.read so that we do not loop endlessly when dpkg
sends unexpected data (lp: #68553)
2006-10-26:
- make sure that xserver-xorg-video-all get installed if
xserver-xorg-driver-all was installed before (lp: #58424)
2006-10-21:
- comment out old cdrom sources
- demotions updated
2006-10-21:
- fix incorrect arguments in fixup logging (lp: #67311)
- more error logging
- fix upgrade problems for people with unofficial compiz
repositories (lp: #58424)
- rosetta i18n updates
- uploaded
2006-10-17:
- ensure bzr, tomboy and xserver-xorg-input-* are properly
upgraded
- don't fail if dpkg sents unexpected status lines (lp: #66013)
2006-10-16:
- remove leftover references to ubuntu-base and
use ubuntu-minimal and ubuntu-standard instead
- updated translations from rosetta
2006-10-13:
- log held-back as well
2006-10-12:
- check if cdrom.lst actually exists before copying it
2006-10-11:
- keep pixbuf loader reference around so that we
have one after the upgrade when the old
/usr/lib/gtk-2.0/loader/2.4.0/ loader is gone.
This fixes the problem of missing stock-icons
after the upgrade. Also revalidate the theme
in each step.
2006-10-10:
- fix time calculation
- fix kubuntu upgrade case
2006-10-06:
- fix source.list rewrite corner case bug (#64159)
2006-10-04:
- improve the space checking/logging
2006-09-29:
- typo fix (thanks to Jane Silber) (lp: #62946)
2006-09-28:
- bugfix in the cdromupgrade script
2006-09-27:
- uploaded a version that only reverts the backport fetching
but no other changes compared to 2006-09-23
2006-09-27:
- embarrassing bug cdromupgrade.sh
2006-09-26:
- comment out the getRequiredBackport code because we will
not use Breaks for the dapper->edgy upgrade yet
(see #54234 for the rationale)
- updated demotions.cfg for dapper->edgy
- special case the packages affected by the Breaks changes
- make sure that no translations get lost during the upgrade
(thanks to mdz for pointing this out)
- bugfixes
2006-09-23:
- support fetching backports of selected packages first and
use them for the upgrade (needed to support Breaks)
- fetch/use apt/dpkg/python-apt backports for the upgrade
2006-09-06:
- increased the "free-space-savety-buffer" to 100MB
2006-09-05:
- added "RemoveEssentialOk" option and put "sysvinit" into it
2006-09-04:
- set Debug::pkgDepCache::AutoInstall as debug option too
- be more robust against failure from the locales
- remove libgl1-mesa (no longer needed on edgy)
2006-09-03:
- fix in the cdromupgrade script path detection
2006-09-01:
- make the cdromupgrade wrapper work with the compressed version
of the upgrader as put onto the CD
- uploaded
2006-08-30:
- fixes to the cdromupgrade wrapper
2006-08-29:
- always enable the "main" component to make sure it is available
- add download estimated time
- add --cdrom switch to make cdrom based dist-upgrades possible
- better error reporting
- moved the logging into the /var/log/dist-upgrade/ dir
- change the obsoletes calculation when run without network and
consider demotions as obsoletes then (because we can't really
use the "pkg.downloadable" hint without network)
- uploaded
2006-08-18:
- sort the demoted software list
2006-07-31:
- updated to edgy
- uploadedd
2006-05-31:
- fix bug in the free space calculation (#47092)
- updated ReleaseAnnouncement
- updated translations
- fix a missing bindtextdomain
- fix a incorrect ngettext usage
- added quirks handler to fix nvidia-glx issue (#47017)
Thanks to the amazing Kiko for helping improve this!
2006-05-24:
- if the initial "update()" fails, just exit, don't try
to restore the old sources.list (nothing was modified yet)
Ubuntu: #46712
- fix a bug in the sourcesList rewriting (ubuntu: #46245)
- expand the terminal when no libgnome2-perl is installed
because debconf might want to ask questions (ubuntu: #46214)
- disable the breezy cdrom source to make removal of demoted
packages work properly (ubuntu: #46336)
- translations updated from rosetta
- fixed a bug in the demotions calculation (ubuntu: #46245)
- typos fixed and translations unfuzzied (ubuntu: #46792,#46464)
- upload
2006-05-12:
- space checking improved (ubuntu: #43948)
- show software that was demoted from main -> universe
- improve the remaining time reporting
- translation updates
- upload
2006-05-09:
- upload
2006-05-08:
- fix error when asking for media-change (ubuntu: 43442,43728)
2006-05-02:
- upload
2006-04-28:
- add more sanity checking, if no valid mirror is found in the
sources.list ask for "dumb" rewrite
- if nothing valid was found after a dumb rewrite, add official
sources
- don't report install TIMEOUT over and over in the log
- report what package caused a install TIMEOUT
2006-04-27:
- add a additonal sanity check after the rewriting of the sources.list
(check for BaseMetaPkgs still in the cache)
- on abort reopen() the cache to force writing a new
/var/cache/apt/pkgcache.bin
- use a much more compelte mirror list (based on the information
from https://wiki.ubuntu.com/Archive)
2006-04-25:
- make sure that DistUpgradeView.getTerminal().call() actually
waits until the command has finished (dpkg --configure -a)
2006-04-18:
- add logging to the sources.list modification code
- general logging improvements (thanks to Xavier Poinsard)
./DevelReleaseAnnouncement 0000644 0000000 0000000 00000003203 13502463664 014556 0 ustar root root = Welcome to the Ubuntu 'Bionic Beaver' development release =
''This release is still in development.''
''Do not install it on production machines.''
Thanks for your interest in this development release of Ubuntu.
The Ubuntu developers are moving very quickly to bring you the
absolute latest and greatest software the Open Source Community has to
offer. This development release brings you a taste of the newest features
for the next version of Ubuntu.
== Testing ==
Please help to test this development snapshot and report problems back to the
developers. For more information about testing Ubuntu, please read:
http://www.ubuntu.com/testing
== Reporting Bugs ==
This development release of Ubuntu contains bugs. If you want to help
out with bugs, the Bug Squad is always looking for help. Please read the
following information about reporting bugs:
http://help.ubuntu.com/community/ReportingBugs
Then report bugs using apport in Ubuntu. For example:
ubuntu-bug linux
will open a bug report in Launchpad regarding the linux package. Your
comments, bug reports, patches and suggestions will help fix bugs and improve
future releases.
== Participate in Ubuntu ==
If you would like to help shape Ubuntu, take a look at the list of
ways you can participate at
http://www.ubuntu.com/community/participate/
== More Information ==
You can find out more about Ubuntu on the Ubuntu website and Ubuntu
wiki.
http://www.ubuntu.com/
http://wiki.ubuntu.com/
To sign up for Ubuntu development announcements, please
subscribe to Ubuntu's development announcement list at:
http://lists.ubuntu.com/mailman/listinfo/ubuntu-devel-announce
./DevelReleaseAnnouncement.html 0000644 0000000 0000000 00000004602 14114454126 015516 0 ustar root root
Welcome to the Ubuntu 'Bionic Beaver' development release
Welcome to the Ubuntu 'Bionic Beaver' development release
This release is still in development.
Do not install it on production machines.
Thanks for your interest in this development release of Ubuntu.
The Ubuntu developers are moving very quickly to bring you the
absolute latest and greatest software the Open Source Community has to
offer. This development release brings you a taste of the newest features
for the next version of Ubuntu.
Testing
Please help to test this development snapshot and report problems back to the
developers. For more information about testing Ubuntu, please read:
http://www.ubuntu.com/testing
Reporting Bugs
This development release of Ubuntu contains bugs. If you want to help
out with bugs, the Bug Squad is always looking for help. Please read the
following information about reporting bugs:
http://help.ubuntu.com/community/ReportingBugs
Then report bugs using apport in Ubuntu. For example:
ubuntu-bug linux
will open a bug report in Launchpad regarding the linux package. Your
comments, bug reports, patches and suggestions will help fix bugs and improve
future releases.
Participate in Ubuntu
If you would like to help shape Ubuntu, take a look at the list of
ways you can participate at
http://www.ubuntu.com/community/participate/
More Information
You can find out more about Ubuntu on the Ubuntu website and Ubuntu
wiki.
http://www.ubuntu.com/
http://wiki.ubuntu.com/
To sign up for Ubuntu development announcements, please
subscribe to Ubuntu's development announcement list at:
http://lists.ubuntu.com/mailman/listinfo/ubuntu-devel-announce
./DistUpgradeApport.py 0000644 0000000 0000000 00000011710 13475213717 013676 0 ustar root root
import os
import logging
import subprocess
import sys
import gettext
import errno
APPORT_WHITELIST = {
"apt.log": "Aptlog",
"apt-term.log": "Apttermlog",
"apt-clone_system_state.tar.gz": "Aptclonesystemstate.tar.gz",
"history.log": "Historylog",
"lspci.txt": "Lspcitxt",
"main.log": "Mainlog",
"term.log": "Termlog",
"screenlog.0": "Screenlog",
"xorg_fixup.log": "Xorgfixup",
}
def _apport_append_logfiles(report, logdir="/var/log/dist-upgrade/"):
dirname = 'VarLogDistupgrade'
for fname in APPORT_WHITELIST:
f = os.path.join(logdir, fname)
if not os.path.isfile(f) or os.path.getsize(f) == 0:
continue
ident = dirname + APPORT_WHITELIST[fname]
if os.access(f, os.R_OK):
report[ident] = (open(f), )
elif os.path.exists(f):
try:
from apport.hookutils import root_command_output
report[ident] = root_command_output(
["cat", '%s' % f], decode_utf8=False)
except ImportError:
logging.error("failed to import apport python module, "
"can't include: %s" % ident)
def apport_crash(type, value, tb):
logging.debug("running apport_crash()")
if "RELEASE_UPRADER_NO_APPORT" in os.environ:
logging.debug("RELEASE_UPRADER_NO_APPORT env set")
return False
try:
# we don't depend on python3-apport because of servers
from apport_python_hook import apport_excepthook
from apport.report import Report
except ImportError as e:
logging.error("failed to import apport python module, can't "
"generate crash: %s" % e)
return False
from .DistUpgradeVersion import VERSION
# we pretend we are do-release-upgrade
sys.argv[0] = "/usr/bin/do-release-upgrade"
apport_excepthook(type, value, tb)
# now add the files in /var/log/dist-upgrade/*
if os.path.exists('/var/crash/_usr_bin_do-release-upgrade.0.crash'):
report = Report()
report.setdefault('Tags', 'dist-upgrade')
release = 'Ubuntu %s' % VERSION[0:5]
report.setdefault('DistroRelease', release)
# use the version of the release-upgrader tarball, not the installed
# package
report.setdefault('Package', 'ubuntu-release-upgrader-core 1:%s' %
VERSION)
_apport_append_logfiles(report)
report.add_to_existing(
'/var/crash/_usr_bin_do-release-upgrade.0.crash')
return True
def apport_pkgfailure(pkg, errormsg):
logging.debug("running apport_pkgfailure() %s: %s", pkg, errormsg)
if "RELEASE_UPRADER_NO_APPORT" in os.environ:
logging.debug("RELEASE_UPRADER_NO_APPORT env set")
return False
LOGDIR = "/var/log/dist-upgrade/"
s = "/usr/share/apport/package_hook"
# we do not report followup errors from earlier failures
# dpkg messages will not be translated if DPKG_UNTRANSLATED_MESSAGES is
# set which it is by default so check for the English message first
if "dependency problems - leaving unconfigured" in errormsg:
logging.debug("dpkg error because of dependency problems, not "
"reporting against %s " % pkg)
return False
needle = gettext.dgettext(
'dpkg', "dependency problems - leaving unconfigured")
if needle in errormsg:
logging.debug("dpkg error because of dependency problems, not "
"reporting against %s " % pkg)
return False
# we do not run apport_pkgfailure for full disk errors
if os.strerror(errno.ENOSPC) in errormsg:
logging.debug("dpkg error because of full disk, not reporting "
"against %s " % pkg)
return False
if os.path.exists(s):
args = [s, "-p", pkg]
args.extend(["--tags", "dist-upgrade"])
for fname in APPORT_WHITELIST:
args.extend(["-l", os.path.join(LOGDIR, fname)])
try:
p = subprocess.Popen(args, stdin=subprocess.PIPE,
universal_newlines=True)
p.stdin.write(errormsg)
p.stdin.close()
#p.wait()
except Exception as e:
logging.warning("Failed to run apport (%s)" % e)
return False
return True
return False
def run_apport():
" run apport, check if we have a display "
if "RELEASE_UPRADER_NO_APPORT" in os.environ:
logging.debug("RELEASE_UPRADER_NO_APPORT env set")
return False
if "DISPLAY" in os.environ:
# update-notifier will notify about the crash
return True
elif os.path.exists("/usr/bin/apport-cli"):
try:
return (subprocess.call("/usr/bin/apport-cli") == 0)
except Exception:
logging.exception("Unable to launch '/usr/bin/apport-cli'")
return False
logging.debug("can't find apport")
return False
if __name__ == "__main__":
apport_crash(None, None, None)
./DistUpgradeAptCdrom.py 0000644 0000000 0000000 00000030622 13476257563 014155 0 ustar root root # DistUpgradeAptCdrom.py
#
# Copyright (c) 2008 Canonical
#
# Author: Michael Vogt
#
# 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
import re
import os
import apt
import apt_pkg
import logging
import gzip
import shutil
import subprocess
import sys
import tempfile
from gettext import gettext as _
class AptCdromError(Exception):
""" base exception for apt cdrom errors """
pass
class AptCdrom(object):
""" represents a apt cdrom object """
def __init__(self, view, path):
self.view = view
self.cdrompath = path
# the directories we found on disk with signatures, packages and i18n
self.packages = set()
self.signatures = set()
self.i18n = set()
apt_pkg.init_config()
def restore_backup(self, backup_ext):
""" restore the backup copy of the cdroms.list file
(*not* sources.list)!
"""
cdromstate = os.path.join(apt_pkg.config.find_dir("Dir::State"),
apt_pkg.config.find("Dir::State::cdroms"))
if os.path.exists(cdromstate + backup_ext):
shutil.copy(cdromstate + backup_ext, cdromstate)
# mvo: we don't have to care about restoring the sources.list here
# because aptsources will do this for us anyway
def comment_out_cdrom_entry(self):
""" comment out the cdrom entry """
diskname = self._readDiskName()
pentry = self._generateSourcesListLine(diskname, self.packages)
sourceslist = apt_pkg.config.find_file("Dir::Etc::sourcelist")
with open(sourceslist) as f:
content = f.read()
content = content.replace(pentry, "# %s" % pentry)
with open(sourceslist, "w") as f:
f.write(content)
def _scanCD(self):
"""
scan the CD for interessting files and return them as:
(packagesfiles, signaturefiles, i18nfiles)
"""
packages = set()
signatures = set()
i18n = set()
for root, dirs, files in os.walk(self.cdrompath, topdown=True):
if (root.endswith("debian-installer") or
root.endswith("dist-upgrader")):
del dirs[:]
continue
elif ".aptignr" in files:
continue
elif "Packages" in files:
packages.add(os.path.join(root, "Packages"))
elif "Packages.gz" in files:
packages.add(os.path.join(root, "Packages.gz"))
elif "Sources" in files or "Sources.gz" in files:
logging.error(
"Sources entry found in %s but not supported" % root)
elif "Release.gpg" in files:
signatures.add(os.path.join(root, "Release.gpg"))
elif "i18n" in dirs:
for f in os.listdir(os.path.join(root, "i18n")):
i18n.add(os.path.join(root, "i18n", f))
# there is nothing under pool but deb packages (no
# indexfiles, so we skip that here
elif os.path.split(root)[1] == "pool":
del dirs[:]
return (packages, signatures, i18n)
def _writeDatabase(self):
""" update apts cdrom.list """
dbfile = apt_pkg.config.find_file("Dir::State::cdroms")
cdrom = apt_pkg.Cdrom()
id = cdrom.ident(apt.progress.base.CdromProgress())
label = self._readDiskName()
with open(dbfile, "a") as out:
out.write('CD::%s "%s";\n' % (id, label))
out.write('CD::%s::Label "%s";\n' % (id, label))
def _dropArch(self, packages):
""" drop architectures that are not ours """
# create a copy
packages = set(packages)
# now go over the packagesdirs and drop stuff that is not
# our binary-$arch
arch = apt_pkg.config.find("APT::Architecture")
for d in set(packages):
if "/binary-" in d and arch not in d:
packages.remove(d)
return packages
def _readDiskName(self):
# default to cdrompath if there is no name
diskname = self.cdrompath
info = os.path.join(self.cdrompath, ".disk", "info")
if os.path.exists(info):
with open(info) as f:
diskname = f.read()
for special in ('"', ']', '[', '_'):
diskname = diskname.replace(special, '_')
return diskname
def _generateSourcesListLine(self, diskname, packages):
# see apts indexcopy.cc:364 for details
path = ""
dist = ""
comps = []
for d in packages:
# match(1) is the path, match(2) the dist
# and match(3) the components
m = re.match("(.*)/dists/([^/]*)/(.*)/binary-*", d)
if not m:
raise AptCdromError(
_("Could not calculate sources.list entry"))
path = m.group(1)
dist = m.group(2)
comps.append(m.group(3))
if not path or not comps:
return None
comps.sort()
pentry = "deb cdrom:[%s]/ %s %s" % (diskname, dist, " ".join(comps))
return pentry
def _copyTranslations(self, translations, targetdir=None):
if not targetdir:
targetdir = apt_pkg.config.find_dir("Dir::State::lists")
diskname = self._readDiskName()
for f in translations:
fname = apt_pkg.uri_to_filename(
"cdrom:[%s]/%s" % (diskname, f[f.find("dists"):]))
outf = os.path.join(targetdir, os.path.splitext(fname)[0])
if f.endswith(".gz"):
with gzip.open(f) as g, open(outf, "wb") as out:
# uncompress in 64k chunks
while True:
s = g.read(64000)
out.write(s)
if s == b"":
break
else:
shutil.copy(f, outf)
return True
def _copyPackages(self, packages, targetdir=None):
if not targetdir:
targetdir = apt_pkg.config.find_dir("Dir::State::lists")
# CopyPackages()
diskname = self._readDiskName()
for f in packages:
fname = apt_pkg.uri_to_filename(
"cdrom:[%s]/%s" % (diskname, f[f.find("dists"):]))
outf = os.path.join(targetdir, os.path.splitext(fname)[0])
if f.endswith(".gz"):
with gzip.open(f) as g, open(outf, "wb") as out:
# uncompress in 64k chunks
while True:
s = g.read(64000)
out.write(s)
if s == b"":
break
else:
shutil.copy(f, outf)
return True
def _verifyRelease(self, signatures):
" verify the signatues and hashes "
for sig in signatures:
basepath = os.path.split(sig)[0]
# do gpg checking
releasef = os.path.splitext(sig)[0]
verify_env = os.environ.copy()
cmd = ["apt-key", "--quiet", "verify", sig, releasef]
with tempfile.NamedTemporaryFile() as fp:
fp.write(apt_pkg.config.dump())
verify_env["APT_CONFIG"] = fp.name
ret = subprocess.call(cmd, env=verify_env)
if not (ret == 0):
return False
# now do the hash sum checks
with open(releasef) as f:
t = apt_pkg.TagFile(f)
t.step()
sha256_section = t.section["SHA256"]
for entry in sha256_section.split("\n"):
(hash, size, name) = entry.split()
f = os.path.join(basepath, name)
if not os.path.exists(f):
logging.info("ignoring missing '%s'" % f)
continue
with open(f) as fp:
sum = apt_pkg.sha256sum(fp)
if not (sum == hash):
logging.error(
"hash sum mismatch expected %s but got %s" % (
hash, sum))
return False
return True
def _copyRelease(self, signatures, targetdir=None):
" copy the release file "
if not targetdir:
targetdir = apt_pkg.config.find_dir("Dir::State::lists")
diskname = self._readDiskName()
for sig in signatures:
releasef = os.path.splitext(sig)[0]
# copy both Release and Release.gpg
for f in (sig, releasef):
fname = apt_pkg.uri_to_filename(
"cdrom:[%s]/%s" % (diskname, f[f.find("dists"):]))
shutil.copy(f, os.path.join(targetdir, fname))
return True
def _doAdd(self):
" reimplement pkgCdrom::Add() in python "
# os.walk() will not follow symlinks so we don't need
# pkgCdrom::Score() and not dropRepeats() that deal with
# killing the links
(self.packages, self.signatures, self.i18n) = self._scanCD()
self.packages = self._dropArch(self.packages)
if len(self.packages) == 0:
logging.error("no useable indexes found on CD, wrong ARCH?")
raise AptCdromError(
_("Unable to locate any package files, "
"perhaps this is not a Ubuntu Disc or the wrong "
"architecture?"))
# CopyAndVerify
if self._verifyRelease(self.signatures):
self._copyRelease(self.signatures)
# copy the packages and translations
self._copyPackages(self.packages)
self._copyTranslations(self.i18n)
# add CD to cdroms.list "database" and update sources.list
diskname = self._readDiskName()
if not diskname:
logging.error("no .disk/ directory found")
return False
debline = self._generateSourcesListLine(diskname, self.packages)
# prepend to the sources.list
sourceslist = apt_pkg.config.find_file("Dir::Etc::sourcelist")
with open(sourceslist) as f:
content = f.read()
with open(sourceslist, "w") as f:
f.write(
"# added by the release upgrader\n%s\n%s" %
(debline, content))
self._writeDatabase()
return True
def add(self, backup_ext=None):
" add a cdrom to apt's database "
logging.debug("AptCdrom.add() called with '%s'", self.cdrompath)
# do backup (if needed) of the cdroms.list file
if backup_ext:
cdromstate = os.path.join(
apt_pkg.config.find_dir("Dir::State"),
apt_pkg.config.find("Dir::State::cdroms"))
if os.path.exists(cdromstate):
shutil.copy(cdromstate, cdromstate + backup_ext)
# do the actual work
apt_pkg.config.set("Acquire::cdrom::mount", self.cdrompath)
apt_pkg.config.set("APT::CDROM::NoMount", "true")
# FIXME: add cdrom progress here for the view
#progress = self.view.getCdromProgress()
try:
res = self._doAdd()
except (SystemError, AptCdromError) as e:
logging.error("can't add cdrom: %s" % e)
self.view.error(_("Failed to add the CD"),
_("There was a error adding the CD, the "
"upgrade will abort. Please report this as "
"a bug if this is a valid Ubuntu CD.\n\n"
"The error message was:\n'%s'") % e)
return False
logging.debug("AptCdrom.add() returned: %s" % res)
return res
def __bool__(self):
""" helper to use this as 'if cdrom:' """
return self.cdrompath is not None
if sys.version < '3':
__nonzero__ = __bool__
./DistUpgradeCache.py 0000644 0000000 0000000 00000151060 13502463664 013436 0 ustar root root # DistUpgradeCache.py
#
# Copyright (c) 2004-2008 Canonical
#
# Author: Michael Vogt
#
# 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
import apt
import apt_pkg
import glob
import locale
import os
import re
import logging
import time
import datetime
import threading
import configparser
from subprocess import Popen, PIPE
from .DistUpgradeGettext import gettext as _
from .DistUpgradeGettext import ngettext
from .utils import inside_chroot
class CacheException(Exception):
pass
class CacheExceptionLockingFailed(CacheException):
pass
class CacheExceptionDpkgInterrupted(CacheException):
pass
def estimate_kernel_initrd_size_in_boot():
"""estimate the amount of space used by the kernel and initramfs in /boot,
including a safety margin
"""
kernel = 0
initrd = 0
kver = os.uname()[2]
for f in glob.glob("/boot/*%s*" % kver):
if f == '/boot/initrd.img-%s' % kver:
initrd += os.path.getsize(f)
# don't include in the estimate any files that are left behind by
# an interrupted package manager run
elif (f.find('initrd.img') >= 0 or f.find('.bak') >= 0
or f.find('.dpkg-') >= 0):
continue
else:
kernel += os.path.getsize(f)
if kernel == 0:
logging.warning(
"estimate_kernel_initrd_size_in_boot() returned '0' for kernel?")
kernel = 28*1024*1024
if initrd == 0:
logging.warning(
"estimate_kernel_initrd_size_in_boot() returned '0' for initrd?")
initrd = 45*1024*1024
# add small safety buffer
kernel += 1*1024*1024
# safety buffer as a percentage of the existing initrd's size
initrd_buffer = 1*1024*1024
if initrd * 0.05 > initrd_buffer:
initrd_buffer = initrd * 0.05
initrd += initrd_buffer
return kernel,initrd
KERNEL_SIZE, INITRD_SIZE = estimate_kernel_initrd_size_in_boot()
class FreeSpaceRequired(object):
""" FreeSpaceRequired object:
This exposes:
- the total size required (size_total)
- the dir that requires the space (dir)
- the additional space that is needed (size_needed)
"""
def __init__(self, size_total, dir, size_needed):
self.size_total = size_total
self.dir = dir
self.size_needed = size_needed
def __str__(self):
return "FreeSpaceRequired Object: Dir: %s size_total: %s size_needed: %s" % (self.dir, self.size_total, self.size_needed)
class NotEnoughFreeSpaceError(CacheException):
"""
Exception if there is not enough free space for this operation
"""
def __init__(self, free_space_required_list):
self.free_space_required_list = free_space_required_list
class MyCache(apt.Cache):
ReInstReq = 1
HoldReInstReq = 3
# init
def __init__(self, config, view, quirks, progress=None, lock=True):
self.to_install = []
self.to_remove = []
self.view = view
self.quirks = quirks
self.lock = False
self.partialUpgrade = False
self.config = config
self.metapkgs = self.config.getlist("Distro", "MetaPkgs")
# acquire lock
self._listsLock = -1
if lock:
try:
apt_pkg.pkgsystem_lock()
self.lock_lists_dir()
self.lock = True
except SystemError as e:
# checking for this is ok, its not translatable
if "dpkg --configure -a" in str(e):
raise CacheExceptionDpkgInterrupted(e)
raise CacheExceptionLockingFailed(e)
# Do not create the cache until we know it is not locked
apt.Cache.__init__(self, progress)
# a list of regexp that are not allowed to be removed
self.removal_blacklist = config.getListFromFile("Distro", "RemovalBlacklistFile")
self.uname = Popen(["uname", "-r"], stdout=PIPE,
universal_newlines=True).communicate()[0].strip()
self._initAptLog()
# from hardy on we use recommends by default, so for the
# transition to the new dist we need to enable them now
if (config.get("Sources", "From") == "hardy" and
not "RELEASE_UPGRADE_NO_RECOMMENDS" in os.environ):
apt_pkg.config.set("APT::Install-Recommends", "true")
apt_pkg.config.set("APT::AutoRemove::SuggestsImportant", "false")
def _apply_dselect_upgrade(self):
""" honor the dselect install state """
for pkg in self:
if pkg.is_installed:
continue
if pkg._pkg.selected_state == apt_pkg.SELSTATE_INSTALL:
# upgrade() will take care of this
pkg.mark_install(auto_inst=False, auto_fix=False)
@property
def req_reinstall_pkgs(self):
" return the packages not downloadable packages in reqreinst state "
reqreinst = set()
for pkg in self:
if ((not pkg.candidate or not pkg.candidate.downloadable)
and
(pkg._pkg.inst_state == self.ReInstReq or
pkg._pkg.inst_state == self.HoldReInstReq)):
reqreinst.add(pkg.name)
return reqreinst
def fix_req_reinst(self, view):
" check for reqreinst state and offer to fix it "
reqreinst = self.req_reinstall_pkgs
if len(reqreinst) > 0:
header = ngettext("Remove package in bad state",
"Remove packages in bad state",
len(reqreinst))
summary = ngettext("The package '%s' is in an inconsistent "
"state and needs to be reinstalled, but "
"no archive can be found for it. "
"Do you want to remove this package "
"now to continue?",
"The packages '%s' are in an inconsistent "
"state and need to be reinstalled, but "
"no archives can be found for them. Do you "
"want to remove these packages now to "
"continue?",
len(reqreinst)) % ", ".join(reqreinst)
if view.askYesNoQuestion(header, summary):
self.release_lock()
cmd = ["/usr/bin/dpkg", "--remove", "--force-remove-reinstreq"] + list(reqreinst)
view.getTerminal().call(cmd)
self.get_lock()
return True
return False
# logging stuff
def _initAptLog(self):
" init logging, create log file"
logdir = self.config.getWithDefault("Files", "LogDir",
"/var/log/dist-upgrade")
if not os.path.exists(logdir):
os.makedirs(logdir)
apt_pkg.config.set("Dir::Log", logdir)
apt_pkg.config.set("Dir::Log::Terminal", "apt-term.log")
self.logfd = os.open(os.path.join(logdir, "apt.log"),
os.O_RDWR | os.O_CREAT | os.O_APPEND, 0o644)
now = datetime.datetime.now()
header = "Log time: %s\n" % now
os.write(self.logfd, header.encode("utf-8"))
# turn on debugging in the cache
apt_pkg.config.set("Debug::pkgProblemResolver", "true")
apt_pkg.config.set("Debug::pkgDepCache::Marker", "true")
apt_pkg.config.set("Debug::pkgDepCache::AutoInstall", "true")
def _startAptResolverLog(self):
if hasattr(self, "old_stdout"):
os.close(self.old_stdout)
os.close(self.old_stderr)
self.old_stdout = os.dup(1)
self.old_stderr = os.dup(2)
os.dup2(self.logfd, 1)
os.dup2(self.logfd, 2)
def _stopAptResolverLog(self):
os.fsync(1)
os.fsync(2)
os.dup2(self.old_stdout, 1)
os.dup2(self.old_stderr, 2)
# use this decorator instead of the _start/_stop stuff directly
# FIXME: this should probably be a decorator class where all
# logging is moved into?
def withResolverLog(f):
" decorator to ensure that the apt output is logged "
def wrapper(*args, **kwargs):
args[0]._startAptResolverLog()
res = f(*args, **kwargs)
args[0]._stopAptResolverLog()
return res
return wrapper
# properties
@property
def required_download(self):
""" get the size of the packages that are required to download """
pm = apt_pkg.PackageManager(self._depcache)
fetcher = apt_pkg.Acquire()
pm.get_archives(fetcher, self._list, self._records)
return fetcher.fetch_needed
@property
def additional_required_space(self):
""" get the size of the additional required space on the fs """
return self._depcache.usr_size
@property
def is_broken(self):
""" is the cache broken """
return self._depcache.broken_count > 0
# methods
def lock_lists_dir(self):
name = apt_pkg.config.find_dir("Dir::State::Lists") + "lock"
self._listsLock = apt_pkg.get_lock(name)
if self._listsLock < 0:
e = "Can not lock '%s' " % name
raise CacheExceptionLockingFailed(e)
def unlock_lists_dir(self):
if self._listsLock > 0:
os.close(self._listsLock)
self._listsLock = -1
def update(self, fprogress=None):
"""
our own update implementation is required because we keep the lists
dir lock
"""
self.unlock_lists_dir()
res = apt.Cache.update(self, fprogress)
self.lock_lists_dir()
if fprogress and fprogress.release_file_download_error:
# FIXME: not ideal error message, but we just reuse a
# existing one here to avoid a new string
raise IOError(_("The server may be overloaded"))
if res == False:
raise IOError("apt.cache.update() returned False, but did not raise exception?!?")
def commit(self, fprogress, iprogress):
logging.info("cache.commit()")
if self.lock:
self.release_lock()
apt.Cache.commit(self, fprogress, iprogress)
def release_lock(self, pkgSystemOnly=True):
if self.lock:
try:
apt_pkg.pkgsystem_unlock()
self.lock = False
except SystemError as e:
logging.debug("failed to SystemUnLock() (%s) " % e)
def get_lock(self, pkgSystemOnly=True):
if not self.lock:
try:
apt_pkg.pkgsystem_lock()
self.lock = True
except SystemError as e:
logging.debug("failed to SystemLock() (%s) " % e)
def downloadable(self, pkg, useCandidate=True):
" check if the given pkg can be downloaded "
if useCandidate:
ver = self._depcache.get_candidate_ver(pkg._pkg)
else:
ver = pkg._pkg.current_ver
if ver == None:
logging.warning("no version information for '%s' (useCandidate=%s)" % (pkg.name, useCandidate))
return False
return ver.downloadable
def pkg_auto_removable(self, pkg):
""" check if the pkg is auto-removable """
return (pkg.is_installed and
self._depcache.is_garbage(pkg._pkg))
def fix_broken(self):
""" try to fix broken dependencies on the system, may throw
SystemError when it can't"""
return self._depcache.fix_broken()
def create_snapshot(self):
""" create a snapshot of the current changes """
self.to_install = []
self.to_remove = []
for pkg in self.get_changes():
if pkg.marked_install or pkg.marked_upgrade:
self.to_install.append(pkg.name)
if pkg.marked_delete:
self.to_remove.append(pkg.name)
def clear(self):
self._depcache.init()
def restore_snapshot(self):
""" restore a snapshot """
actiongroup = apt_pkg.ActionGroup(self._depcache)
# just make pyflakes shut up, later we need to use
# with self.actiongroup():
actiongroup
self.clear()
for name in self.to_remove:
pkg = self[name]
pkg.mark_delete()
for name in self.to_install:
pkg = self[name]
pkg.mark_install(auto_fix=False, auto_inst=False)
def need_server_mode(self):
"""
This checks if we run on a desktop or a server install.
A server install has more freedoms, for a desktop install
we force a desktop meta package to be install on the upgrade.
We look for a installed desktop meta pkg and for key
dependencies, if none of those are installed we assume
server mode
"""
#logging.debug("need_server_mode() run")
# check for the MetaPkgs (e.g. ubuntu-desktop)
metapkgs = self.config.getlist("Distro", "MetaPkgs")
for key in metapkgs:
# if it is installed we are done
if key in self and self[key].is_installed:
logging.debug("need_server_mode(): run in 'desktop' mode, (because of pkg '%s')" % key)
return False
# if it is not installed, but its key depends are installed
# we are done too (we auto-select the package later)
deps_found = True
for pkg in self.config.getlist(key, "KeyDependencies"):
deps_found &= pkg in self and self[pkg].is_installed
if deps_found:
logging.debug("need_server_mode(): run in 'desktop' mode, (because of key deps for '%s')" % key)
return False
logging.debug("need_server_mode(): can not find a desktop meta package or key deps, running in server mode")
return True
def sanity_check(self, view):
""" check if the cache is ok and if the required metapkgs
are installed
"""
if self.is_broken:
try:
logging.debug("Have broken pkgs, trying to fix them")
self.fix_broken()
except SystemError:
view.error(_("Broken packages"),
_("Your system contains broken packages "
"that couldn't be fixed with this "
"software. "
"Please fix them first using synaptic or "
"apt-get before proceeding."))
return False
return True
def mark_install(self, pkg, reason=""):
logging.debug("Installing '%s' (%s)" % (pkg, reason))
if pkg in self:
self[pkg].mark_install()
if not (self[pkg].marked_install or self[pkg].marked_upgrade):
logging.error("Installing/upgrading '%s' failed" % pkg)
#raise SystemError("Installing '%s' failed" % pkg)
return False
return True
def mark_upgrade(self, pkg, reason=""):
logging.debug("Upgrading '%s' (%s)" % (pkg, reason))
if pkg in self and self[pkg].is_installed:
self[pkg].mark_upgrade()
if not self[pkg].marked_upgrade:
logging.error("Upgrading '%s' failed" % pkg)
return False
return True
def mark_remove(self, pkg, reason=""):
logging.debug("Removing '%s' (%s)" % (pkg, reason))
if pkg in self:
self[pkg].mark_delete()
def mark_purge(self, pkg, reason=""):
logging.debug("Purging '%s' (%s)" % (pkg, reason))
if pkg in self:
self._depcache.mark_delete(self[pkg]._pkg, True)
def _keep_installed(self, pkgname, reason):
if (pkgname in self
and self[pkgname].is_installed
and self[pkgname].marked_delete):
self.mark_install(pkgname, reason)
def keep_installed_rule(self):
""" run after the dist-upgrade to ensure that certain
packages are kept installed """
# first the global list
for pkgname in self.config.getlist("Distro", "KeepInstalledPkgs"):
self._keep_installed(pkgname, "Distro KeepInstalledPkgs rule")
# the the per-metapkg rules
for key in self.metapkgs:
if key in self and (self[key].is_installed or
self[key].marked_install):
for pkgname in self.config.getlist(key, "KeepInstalledPkgs"):
self._keep_installed(pkgname, "%s KeepInstalledPkgs rule" % key)
# only enforce section if we have a network. Otherwise we run
# into CD upgrade issues for installed language packs etc
if self.config.get("Options", "withNetwork") == "True":
logging.debug("Running KeepInstalledSection rules")
# now the KeepInstalledSection code
for section in self.config.getlist("Distro", "KeepInstalledSection"):
for pkg in self:
if (pkg.candidate and pkg.candidate.downloadable
and pkg.marked_delete and pkg.section == section):
self._keep_installed(pkg.name, "Distro KeepInstalledSection rule: %s" % section)
for key in self.metapkgs:
if key in self and (self[key].is_installed or
self[key].marked_install):
for section in self.config.getlist(key, "KeepInstalledSection"):
for pkg in self:
if (pkg.candidate and pkg.candidate.downloadable
and pkg.marked_delete and
pkg.section == section):
self._keep_installed(pkg.name, "%s KeepInstalledSection rule: %s" % (key, section))
def post_upgrade_rule(self):
" run after the upgrade was done in the cache "
for (rule, action) in [("Install", self.mark_install),
("Upgrade", self.mark_upgrade),
("Remove", self.mark_remove),
("Purge", self.mark_purge)]:
# first the global list
for pkg in self.config.getlist("Distro", "PostUpgrade%s" % rule):
action(pkg, "Distro PostUpgrade%s rule" % rule)
for key in self.metapkgs:
if key in self and (self[key].is_installed or
self[key].marked_install):
for pkg in self.config.getlist(key, "PostUpgrade%s" % rule):
action(pkg, "%s PostUpgrade%s rule" % (key, rule))
# run the quirks handlers
if not self.partialUpgrade:
self.quirks.run("PostDistUpgradeCache")
def identifyObsoleteKernels(self):
# we have a funny policy that we remove security updates
# for the kernel from the archive again when a new ABI
# version hits the archive. this means that we have
# e.g.
# linux-image-2.6.24-15-generic
# is obsolete when
# linux-image-2.6.24-19-generic
# is available
# ...
# This code tries to identify the kernels that can be removed
logging.debug("identifyObsoleteKernels()")
obsolete_kernels = set()
version = self.config.get("KernelRemoval", "Version")
basenames = self.config.getlist("KernelRemoval", "BaseNames")
types = self.config.getlist("KernelRemoval", "Types")
for pkg in self:
for base in basenames:
basename = "%s-%s-" % (base, version)
for type in types:
if (pkg.name.startswith(basename) and
pkg.name.endswith(type) and
pkg.is_installed):
if (pkg.name == "%s-%s" % (base, self.uname)):
logging.debug("skipping running kernel %s" % pkg.name)
continue
logging.debug("removing obsolete kernel '%s'" % pkg.name)
obsolete_kernels.add(pkg.name)
logging.debug("identifyObsoleteKernels found '%s'" % obsolete_kernels)
return obsolete_kernels
def checkForNvidia(self):
"""
this checks for nvidia hardware and checks what driver is needed
"""
logging.debug("nvidiaUpdate()")
# if the free drivers would give us a equally hard time, we would
# never be able to release
try:
from NvidiaDetector.nvidiadetector import NvidiaDetection
except (ImportError, SyntaxError) as e:
# SyntaxError is temporary until the port of NvidiaDetector to
# Python 3 is in the archive.
logging.error("NvidiaDetector can not be imported %s" % e)
return False
try:
# get new detection module and use the modalises files
# from within the release-upgrader
nv = NvidiaDetection(obsolete="./ubuntu-drivers-obsolete.pkgs")
#nv = NvidiaDetection()
# check if a binary driver is installed now
for oldDriver in nv.oldPackages:
if oldDriver in self and self[oldDriver].is_installed:
self.mark_remove(oldDriver, "old nvidia driver")
break
else:
logging.info("no old nvidia driver installed, installing no new")
return False
# check which one to use
driver = nv.selectDriver()
logging.debug("nv.selectDriver() returned '%s'" % driver)
if not driver in self:
logging.warning("no '%s' found" % driver)
return False
if not (self[driver].marked_install or self[driver].marked_upgrade):
self[driver].mark_install()
logging.info("installing %s as suggested by NvidiaDetector" % driver)
return True
except Exception as e:
logging.error("NvidiaDetection returned a error: %s" % e)
return False
def _has_kernel_headers_installed(self):
for pkg in self:
if (pkg.name.startswith("linux-headers-") and
pkg.is_installed):
return True
return False
def checkForKernel(self):
""" check for the running kernel and try to ensure that we have
an updated version
"""
logging.debug("Kernel uname: '%s' " % self.uname)
try:
(version, build, flavour) = self.uname.split("-")
except Exception as e:
logging.warning("Can't parse kernel uname: '%s' (self compiled?)" % e)
return False
# now check if we have a SMP system
dmesg = Popen(["dmesg"], stdout=PIPE).communicate()[0]
if b"WARNING: NR_CPUS limit" in dmesg:
logging.debug("UP kernel on SMP system!?!")
return True
def checkPriority(self):
# tuple of priorities we require to be installed
need = ('required', )
# stuff that its ok not to have
removeEssentialOk = self.config.getlist("Distro", "RemoveEssentialOk")
# check now
for pkg in self:
# WORKAROUND bug on the CD/python-apt #253255
ver = pkg._pcache._depcache.get_candidate_ver(pkg._pkg)
if ver and ver.priority == 0:
logging.error("Package %s has no priority set" % pkg.name)
continue
if (pkg.candidate and pkg.candidate.downloadable and
not (pkg.is_installed or pkg.marked_install) and
not pkg.name in removeEssentialOk and
# ignore multiarch priority required packages
not ":" in pkg.name and
pkg.candidate.priority in need):
self.mark_install(pkg.name, "priority in required set '%s' but not scheduled for install" % need)
# FIXME: make this a decorator (just like the withResolverLog())
def updateGUI(self, view, lock):
i = 0
while lock.locked():
if i % 15 == 0:
view.pulseProgress()
view.processEvents()
time.sleep(0.02)
i += 1
view.pulseProgress(finished=True)
view.processEvents()
@withResolverLog
def distUpgrade(self, view, serverMode, partialUpgrade):
# keep the GUI alive
lock = threading.Lock()
lock.acquire()
t = threading.Thread(target=self.updateGUI, args=(self.view, lock,))
t.start()
try:
# mvo: disabled as it casues to many errornous installs
#self._apply_dselect_upgrade()
# upgrade (and make sure this way that the cache is ok)
self.upgrade(True)
# check that everything in priority required is installed
self.checkPriority()
# see if our KeepInstalled rules are honored
self.keep_installed_rule()
# check if we got a new kernel (if we are not inside a
# chroot)
if inside_chroot():
logging.warning("skipping kernel checks because we run inside a chroot")
else:
self.checkForKernel()
# check for nvidia stuff
self.checkForNvidia()
# and if we have some special rules
self.post_upgrade_rule()
# install missing meta-packages (if not in server upgrade mode)
self._keepBaseMetaPkgsInstalled(view)
if not serverMode:
# if this fails, a system error is raised
self._installMetaPkgs(view)
# see if it all makes sense, if not this function raises
self._verifyChanges()
except SystemError as e:
# this should go into a finally: line, see below for the
# rationale why it doesn't
lock.release()
t.join()
# the most likely problem is the 3rd party pkgs so don't address
# foreignPkgs and devRelease being True
details = _("An unresolvable problem occurred while "
"calculating the upgrade.\n\n ")
if self.config.get("Options", "foreignPkgs") == "True":
details += _("This was likely caused by:\n"
" * Unofficial software packages not provided by Ubuntu\n"
"Please use the tool 'ppa-purge' from the ppa-purge \n"
"package to remove software from a Launchpad PPA and \n"
"try the upgrade again.\n"
"\n")
elif self.config.get("Options", "foreignPkgs") == "False" and \
self.config.get("Options", "devRelease") == "True":
details += _("This was caused by:\n"
" * Upgrading to a pre-release version of Ubuntu\n"
"This is most likely a transient problem, \n"
"please try again later.\n")
# we never have partialUpgrades (including removes) on a stable system
# with only ubuntu sources so we do not recommend reporting a bug
if partialUpgrade:
details += _("This is most likely a transient problem, "
"please try again later.")
else:
details += _("If none of this applies, then please report this bug using "
"the command 'ubuntu-bug ubuntu-release-upgrader-core' in a terminal.")
details += _("If you want to investigate this yourself the log files in "
"'/var/log/dist-upgrade' will contain details about the upgrade. "
"Specifically, look at 'main.log' and 'apt.log'.")
# make the error text available again on stdout for the
# text frontend
self._stopAptResolverLog()
view.error(_("Could not calculate the upgrade"), details)
# may contain utf-8 (LP: #1310053)
error_msg = str(e)
logging.error("Dist-upgrade failed: '%s'", error_msg)
# start the resolver log again because this is run with
# the withResolverLog decorator
self._startAptResolverLog()
return False
# would be nice to be able to use finally: here, but we need
# to run on python2.4 too
#finally:
# wait for the gui-update thread to exit
lock.release()
t.join()
# check the trust of the packages that are going to change
untrusted = []
downgrade = []
for pkg in self.get_changes():
if pkg.marked_delete:
continue
# special case because of a bug in pkg.candidate.origins
if pkg.marked_downgrade:
downgrade.append(pkg.name)
for ver in pkg._pkg.version_list:
# version is lower than installed one
if apt_pkg.version_compare(
ver.ver_str, pkg.installed.version) < 0:
for (verFileIter, index) in ver.file_list:
indexfile = pkg._pcache._list.find_index(verFileIter)
if indexfile and not indexfile.is_trusted:
untrusted.append(pkg.name)
break
continue
origins = pkg.candidate.origins
trusted = False
for origin in origins:
#print(origin)
trusted |= origin.trusted
if not trusted:
untrusted.append(pkg.name)
# check if the user overwrote the unauthenticated warning
try:
b = self.config.getboolean("Distro", "AllowUnauthenticated")
if b:
logging.warning("AllowUnauthenticated set!")
return True
except configparser.NoOptionError as e:
pass
if len(downgrade) > 0:
downgrade.sort()
logging.error("Packages to downgrade found: '%s'" %
" ".join(downgrade))
if len(untrusted) > 0:
untrusted.sort()
logging.error("Unauthenticated packages found: '%s'" %
" ".join(untrusted))
# FIXME: maybe ask a question here? instead of failing?
self._stopAptResolverLog()
view.error(_("Error authenticating some packages"),
_("It was not possible to authenticate some "
"packages. This may be a transient network problem. "
"You may want to try again later. See below for a "
"list of unauthenticated packages."),
"\n".join(untrusted))
# start the resolver log again because this is run with
# the withResolverLog decorator
self._startAptResolverLog()
return False
return True
def _verifyChanges(self):
""" this function tests if the current changes don't violate
our constrains (blacklisted removals etc)
"""
main_arch = apt_pkg.config.find("APT::Architecture")
removeEssentialOk = self.config.getlist("Distro", "RemoveEssentialOk")
# check changes
for pkg in self.get_changes():
if pkg.marked_delete and self._inRemovalBlacklist(pkg.name):
logging.debug("The package '%s' is marked for removal but it's in the removal blacklist", pkg.name)
raise SystemError(_("The package '%s' is marked for removal but it is in the removal blacklist.") % pkg.name)
if pkg.marked_delete and (
pkg._pkg.essential == True and
pkg.installed.architecture in (main_arch, "all") and
not pkg.name in removeEssentialOk):
logging.debug("The package '%s' is marked for removal but it's an ESSENTIAL package", pkg.name)
raise SystemError(_("The essential package '%s' is marked for removal.") % pkg.name)
# check bad-versions blacklist
badVersions = self.config.getlist("Distro", "BadVersions")
for bv in badVersions:
(pkgname, ver) = bv.split("_")
if (pkgname in self and self[pkgname].candidate and
self[pkgname].candidate.version == ver and
(self[pkgname].marked_install or
self[pkgname].marked_upgrade)):
raise SystemError(_("Trying to install blacklisted version '%s'") % bv)
return True
def _lookupPkgRecord(self, pkg):
"""
helper to make sure that the pkg._records is pointing to the right
location - needed because python-apt 0.7.9 dropped the python-apt
version but we can not yet use the new version because on upgrade
the old version is still installed
"""
ver = pkg._pcache._depcache.get_candidate_ver(pkg._pkg)
if ver is None:
print("No candidate ver: ", pkg.name)
return False
if ver.file_list is None:
print("No file_list for: %s " % self._pkg.name())
return False
f, index = ver.file_list.pop(0)
pkg._pcache._records.lookup((f, index))
return True
@property
def installedTasks(self):
tasks = {}
installed_tasks = set()
for pkg in self:
if not self._lookupPkgRecord(pkg):
logging.debug("no PkgRecord found for '%s', skipping " % pkg.name)
continue
for line in pkg._pcache._records.record.split("\n"):
if line.startswith("Task:"):
for task in (line[len("Task:"):]).split(","):
task = task.strip()
if task not in tasks:
tasks[task] = set()
tasks[task].add(pkg.name)
for task in tasks:
installed = True
for pkgname in tasks[task]:
if not self[pkgname].is_installed:
installed = False
break
if installed:
installed_tasks.add(task)
return installed_tasks
def installTasks(self, tasks):
logging.debug("running installTasks")
for pkg in self:
if pkg.marked_install or pkg.is_installed:
continue
self._lookupPkgRecord(pkg)
if not (hasattr(pkg._pcache._records, "record") and pkg._pcache._records.record):
logging.warning("can not find Record for '%s'" % pkg.name)
continue
for line in pkg._pcache._records.record.split("\n"):
if line.startswith("Task:"):
for task in (line[len("Task:"):]).split(","):
task = task.strip()
if task in tasks:
pkg.mark_install()
return True
def _keepBaseMetaPkgsInstalled(self, view):
for pkg in self.config.getlist("Distro", "BaseMetaPkgs"):
self._keep_installed(pkg, "base meta package keep installed rule")
def _installMetaPkgs(self, view):
def metaPkgInstalled():
"""
internal helper that checks if at least one meta-pkg is
installed or marked install
"""
for key in metapkgs:
if key in self:
pkg = self[key]
if pkg.is_installed and pkg.marked_delete:
logging.debug("metapkg '%s' installed but marked_delete" % pkg.name)
if ((pkg.is_installed and not pkg.marked_delete)
or self[key].marked_install):
return True
return False
# now check for ubuntu-desktop, kubuntu-desktop, edubuntu-desktop
metapkgs = self.config.getlist("Distro", "MetaPkgs")
# we never go without ubuntu-base
for pkg in self.config.getlist("Distro", "BaseMetaPkgs"):
self[pkg].mark_install()
# every meta-pkg that is installed currently, will be marked
# install (that result in a upgrade and removes a mark_delete)
for key in metapkgs:
try:
if (key in self and
self[key].is_installed and
self[key].is_upgradable):
logging.debug("Marking '%s' for upgrade" % key)
self[key].mark_upgrade()
except SystemError as e:
# warn here, but don't fail, its possible that meta-packages
# conflict (like ubuntu-desktop vs xubuntu-desktop) LP: #775411
logging.warning("Can't mark '%s' for upgrade (%s)" % (key, e))
# check if we have a meta-pkg, if not, try to guess which one to pick
if not metaPkgInstalled():
logging.debug("none of the '%s' meta-pkgs installed" % metapkgs)
for key in metapkgs:
deps_found = True
for pkg in self.config.getlist(key, "KeyDependencies"):
deps_found &= pkg in self and self[pkg].is_installed
if deps_found:
logging.debug("guessing '%s' as missing meta-pkg" % key)
try:
self[key].mark_install()
except (SystemError, KeyError) as e:
logging.error("failed to mark '%s' for install (%s)" %
(key, e))
view.error(_("Can't install '%s'") % key,
_("It was impossible to install a "
"required package. Please report "
"this as a bug using "
"'ubuntu-bug ubuntu-release-upgrader-core' in "
"a terminal."))
return False
logging.debug("marked_install: '%s' -> '%s'" % (key, self[key].marked_install))
break
# check if we actually found one
if not metaPkgInstalled():
meta_pkgs = ', '.join(metapkgs[0:-1])
view.error(_("Can't guess meta-package"),
_("Your system does not contain a "
"%s or %s package and it was not "
"possible to detect which version of "
"Ubuntu you are running.\n "
"Please install one of the packages "
"above first using synaptic or "
"apt-get before proceeding.") %
(meta_pkgs, metapkgs[-1]))
return False
return True
def _inRemovalBlacklist(self, pkgname):
for expr in self.removal_blacklist:
if re.compile(expr).match(pkgname):
logging.debug("blacklist expr '%s' matches '%s'" % (expr, pkgname))
return True
return False
@withResolverLog
def tryMarkObsoleteForRemoval(self, pkgname, remove_candidates, foreign_pkgs):
#logging.debug("tryMarkObsoleteForRemoval(): %s" % pkgname)
# sanity check, first see if it looks like a running kernel pkg
if pkgname.endswith(self.uname):
logging.debug("skipping running kernel pkg '%s'" % pkgname)
return False
if self._inRemovalBlacklist(pkgname):
logging.debug("skipping '%s' (in removalBlacklist)" % pkgname)
return False
# ensure we honor KeepInstalledSection here as well
for section in self.config.getlist("Distro", "KeepInstalledSection"):
if pkgname in self and self[pkgname].section == section:
logging.debug("skipping '%s' (in KeepInstalledSection)" % pkgname)
return False
# if we don't have the package anyway, we are fine (this can
# happen when forced_obsoletes are specified in the config file)
if pkgname not in self:
#logging.debug("package '%s' not in cache" % pkgname)
return True
# check if we want to purge
try:
purge = self.config.getboolean("Distro", "PurgeObsoletes")
except configparser.NoOptionError as e:
purge = False
# this is a delete candidate, only actually delete,
# if it dosn't remove other packages depending on it
# that are not obsolete as well
actiongroup = apt_pkg.ActionGroup(self._depcache)
# just make pyflakes shut up, later we should use
# with self.actiongroup():
actiongroup
self.create_snapshot()
try:
self[pkgname].mark_delete(purge=purge)
self.view.processEvents()
#logging.debug("marking '%s' for removal" % pkgname)
for pkg in self.get_changes():
if (pkg.name not in remove_candidates or
pkg.name in foreign_pkgs or
self._inRemovalBlacklist(pkg.name)):
logging.debug("package '%s' has unwanted removals, skipping" % pkgname)
self.restore_snapshot()
return False
except (SystemError, KeyError) as e:
logging.warning("_tryMarkObsoleteForRemoval failed for '%s' (%s: %s)" % (pkgname, repr(e), e))
self.restore_snapshot()
return False
return True
def _getObsoletesPkgs(self):
" get all package names that are not downloadable "
obsolete_pkgs = set()
for pkg in self:
if pkg.is_installed:
# check if any version is downloadable. we need to check
# for older ones too, because there might be
# cases where e.g. firefox in gutsy-updates is newer
# than hardy
if not self.anyVersionDownloadable(pkg):
obsolete_pkgs.add(pkg.name)
return obsolete_pkgs
def anyVersionDownloadable(self, pkg):
" helper that checks if any of the version of pkg is downloadable "
for ver in pkg._pkg.version_list:
if ver.downloadable:
return True
return False
def _getUnusedDependencies(self):
" get all package names that are not downloadable "
unused_dependencies = set()
for pkg in self:
if pkg.is_installed and self._depcache.is_garbage(pkg._pkg):
unused_dependencies.add(pkg.name)
return unused_dependencies
def get_installed_demoted_packages(self):
""" return list of installed and demoted packages
If a demoted package is a automatic install it will be skipped
"""
demotions = set()
demotions_file = self.config.get("Distro", "Demotions")
if os.path.exists(demotions_file):
with open(demotions_file) as demotions_f:
for line in demotions_f:
if not line.startswith("#"):
demotions.add(line.strip())
installed_demotions = set()
for demoted_pkgname in demotions:
if demoted_pkgname not in self:
continue
pkg = self[demoted_pkgname]
if (not pkg.is_installed or
self._depcache.is_auto_installed(pkg._pkg) or
pkg.marked_delete):
continue
installed_demotions.add(pkg)
return list(installed_demotions)
def _getForeignPkgs(self, allowed_origin, fromDist, toDist):
""" get all packages that are installed from a foreign repo
(and are actually downloadable)
"""
foreign_pkgs = set()
for pkg in self:
if pkg.is_installed and self.downloadable(pkg):
if not pkg.candidate:
continue
# assume it is foreign and see if it is from the
# official archive
foreign = True
for origin in pkg.candidate.origins:
# FIXME: use some better metric here
if fromDist in origin.archive and \
origin.origin == allowed_origin:
foreign = False
if toDist in origin.archive and \
origin.origin == allowed_origin:
foreign = False
if foreign:
foreign_pkgs.add(pkg.name)
return foreign_pkgs
def checkFreeSpace(self, snapshots_in_use=False):
"""
this checks if we have enough free space on /var, /boot and /usr
with the given cache
Note: this can not be fully accurate if there are multiple
mountpoints for /usr, /var, /boot
"""
class FreeSpace(object):
" helper class that represents the free space on each mounted fs "
def __init__(self, initialFree):
self.free = initialFree
self.need = 0
def make_fs_id(d):
""" return 'id' of a directory so that directories on the
same filesystem get the same id (simply the mount_point)
"""
for mount_point in mounted:
if d.startswith(mount_point):
return mount_point
return "/"
# this is all a bit complicated
# 1) check what is mounted (in mounted)
# 2) create FreeSpace objects for the dirs we are interested in
# (mnt_map)
# 3) use the mnt_map to check if we have enough free space and
# if not tell the user how much is missing
mounted = []
mnt_map = {}
fs_free = {}
with open("/proc/mounts") as mounts:
for line in mounts:
try:
(what, where, fs, options, a, b) = line.split()
except ValueError as e:
logging.debug("line '%s' in /proc/mounts not understood (%s)" % (line, e))
continue
if not where in mounted:
mounted.append(where)
# make sure mounted is sorted by longest path
mounted.sort(key=len, reverse=True)
archivedir = apt_pkg.config.find_dir("Dir::Cache::archives")
aufs_rw_dir = "/tmp/"
if (hasattr(self, "config") and
self.config.getWithDefault("Aufs", "Enabled", False)):
aufs_rw_dir = self.config.get("Aufs", "RWDir")
if not os.path.exists(aufs_rw_dir):
os.makedirs(aufs_rw_dir)
logging.debug("cache aufs_rw_dir: %s" % aufs_rw_dir)
for d in ["/", "/usr", "/var", "/boot", archivedir, aufs_rw_dir, "/home", "/tmp/"]:
d = os.path.realpath(d)
fs_id = make_fs_id(d)
if os.path.exists(d):
st = os.statvfs(d)
free = st.f_bavail * st.f_frsize
else:
logging.warning("directory '%s' does not exists" % d)
free = 0
if fs_id in mnt_map:
logging.debug("Dir %s mounted on %s" %
(d, mnt_map[fs_id]))
fs_free[d] = fs_free[mnt_map[fs_id]]
else:
logging.debug("Free space on %s: %s" %
(d, free))
mnt_map[fs_id] = d
fs_free[d] = FreeSpace(free)
del mnt_map
logging.debug("fs_free contains: '%s'" % fs_free)
# now calculate the space that is required on /boot
# we do this by checking how many linux-image-$ver packages
# are installed or going to be installed
kernel_count = 0
for pkg in self:
# we match against everything that looks like a kernel
# and add space check to filter out metapackages
if re.match("^linux-(image|image-debug)-[0-9.]*-.*", pkg.name):
# upgrade because early in the release cycle the major version
# may be the same or they might be -lts- kernels
if pkg.marked_install or pkg.marked_upgrade:
logging.debug("%s (new-install) added with %s to boot space" % (pkg.name, KERNEL_SIZE))
kernel_count += 1
# space calculated per LP: #1646222
space_in_boot = (kernel_count * KERNEL_SIZE
+ (kernel_count + 1) * INITRD_SIZE)
# we check for various sizes:
# archivedir is where we download the debs
# /usr is assumed to get *all* of the install space (incorrect,
# but as good as we can do currently + safety buffer
# / has a small safety buffer as well
required_for_aufs = 0.0
if (hasattr(self, "config") and
self.config.getWithDefault("Aufs", "Enabled", False)):
logging.debug("taking aufs overlay into space calculation")
aufs_rw_dir = self.config.get("Aufs", "RWDir")
# if we use the aufs rw overlay all the space is consumed
# the overlay dir
for pkg in self:
if pkg.marked_upgrade or pkg.marked_install:
required_for_aufs += pkg.candidate.installed_size
# add old size of the package if we use snapshots
required_for_snapshots = 0.0
if snapshots_in_use:
for pkg in self:
if (pkg.is_installed and
(pkg.marked_upgrade or pkg.marked_delete)):
required_for_snapshots += pkg.installed.installed_size
logging.debug("additional space for the snapshots: %s" % required_for_snapshots)
# sum up space requirements
for (dir, size) in [(archivedir, self.required_download),
("/usr", self.additional_required_space),
# plus 50M safety buffer in /usr
("/usr", 50*1024*1024),
("/boot", space_in_boot),
("/tmp", 5*1024*1024), # /tmp for dkms LP: #427035
("/", 10*1024*1024), # small safety buffer /
(aufs_rw_dir, required_for_aufs),
# if snapshots are in use
("/usr", required_for_snapshots),
]:
# we are ensuring we have more than enough free space not less
if size < 0:
continue
dir = os.path.realpath(dir)
logging.debug("dir '%s' needs '%s' of '%s' (%f)" % (dir, size, fs_free[dir], fs_free[dir].free))
fs_free[dir].free -= size
fs_free[dir].need += size
# check for space required violations
required_list = {}
for dir in fs_free:
if fs_free[dir].free < 0:
# ensure unicode here (LP: #1172740)
free_at_least = apt_pkg.size_to_str(float(abs(fs_free[dir].free)+1))
if isinstance(free_at_least, bytes):
free_at_least = free_at_least.decode(
locale.getpreferredencoding())
free_needed = apt_pkg.size_to_str(fs_free[dir].need)
if isinstance(free_needed, bytes):
free_needed = free_needed.decode(
locale.getpreferredencoding())
# make_fs_id ensures we only get stuff on the same
# mountpoint, so we report the requirements only once
# per mountpoint
required_list[make_fs_id(dir)] = FreeSpaceRequired(free_needed, make_fs_id(dir), free_at_least)
# raise exception if free space check fails
if len(required_list) > 0:
logging.error("Not enough free space: %s" % [str(i) for i in required_list])
raise NotEnoughFreeSpaceError(list(required_list.values()))
return True
if __name__ == "__main__":
import sys
from .DistUpgradeConfigParser import DistUpgradeConfig
from .DistUpgradeView import DistUpgradeView
print("foo")
c = MyCache(DistUpgradeConfig("."), DistUpgradeView(), None)
#c.checkForNvidia()
#print(c._identifyObsoleteKernels())
print(c.checkFreeSpace())
sys.exit()
c.clear()
c.create_snapshot()
c.installedTasks
c.installTasks(["ubuntu-desktop"])
print(c.get_changes())
c.restore_snapshot()
./DistUpgradeConfigParser.py 0000644 0000000 0000000 00000007375 13502463664 015026 0 ustar root root # DistUpgradeConfigParser.py
#
# Copyright (c) 2004-2014 Canonical
#
# Author: Michael Vogt
#
# 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 configparser import NoOptionError, NoSectionError
from configparser import ConfigParser as SafeConfigParser
import subprocess
import os.path
import logging
import glob
CONFIG_OVERRIDE_DIR = "/etc/update-manager/release-upgrades.d"
class DistUpgradeConfig(SafeConfigParser):
def __init__(self, datadir, name="DistUpgrade.cfg",
override_dir=None, defaults_dir=None):
SafeConfigParser.__init__(self)
# we support a config overwrite, if DistUpgrade.cfg.dapper exists
# and the user runs dapper, that one will be used
from_release = subprocess.Popen(
["lsb_release", "-c", "-s"], stdout=subprocess.PIPE,
universal_newlines=True).communicate()[0].strip()
self.datadir = datadir
if os.path.exists(name + "." + from_release):
name = name + "." + from_release
maincfg = os.path.join(datadir, name)
# defaults are read first
self.config_files = []
if defaults_dir:
for cfg in glob.glob(defaults_dir + "/*.cfg"):
self.config_files.append(cfg)
# our config file
self.config_files += [maincfg]
# overrides are read later
if override_dir is None:
override_dir = CONFIG_OVERRIDE_DIR
if override_dir is not None:
for cfg in glob.glob(override_dir + "/*.cfg"):
self.config_files.append(cfg)
self.read(self.config_files)
def getWithDefault(self, section, option, default):
try:
if type(default) == bool:
return self.getboolean(section, option)
elif type(default) == float:
return self.getfloat(section, option)
elif type(default) == int:
return self.getint(section, option)
return self.get(section, option)
except (NoSectionError, NoOptionError):
return default
def getlist(self, section, option):
try:
tmp = self.get(section, option)
except (NoSectionError, NoOptionError):
return []
items = [x.strip() for x in tmp.split(",")]
return items
def getListFromFile(self, section, option):
try:
filename = self.get(section, option)
except NoOptionError:
return []
p = os.path.join(self.datadir, filename)
if not os.path.exists(p):
logging.error("getListFromFile: no '%s' found" % p)
with open(p) as f:
items = [x.strip() for x in f]
return [s for s in items if not s.startswith("#") and not s == ""]
if __name__ == "__main__":
c = DistUpgradeConfig(".")
print(c.getlist("Distro", "MetaPkgs"))
print(c.getlist("Distro", "ForcedPurges"))
print(c.getListFromFile("Sources", "ValidMirrors"))
print(c.getWithDefault("Distro", "EnableApport", True))
print(c.set("Distro", "Foo", "False"))
print(c.getWithDefault("Distro", "Foo", True))
./DistUpgradeController.py 0000644 0000000 0000000 00000311724 13736703407 014564 0 ustar root root # DistUpgradeController.py
#
# Copyright (c) 2004-2008 Canonical
#
# Author: Michael Vogt
#
# 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
import apt
import apt_pkg
import sys
import os
import subprocess
import locale
import logging
import shutil
import glob
import time
import copy
from configparser import NoOptionError
from configparser import ConfigParser as SafeConfigParser
from .telemetry import get as get_telemetry
from .utils import (country_mirror,
url_downloadable,
check_and_fix_xbit,
get_arch,
iptables_active,
inside_chroot,
get_string_with_no_auth_from_source_entry,
is_child_of_process_name,
inhibit_sleep)
from string import Template
from urllib.parse import urlsplit
from .DistUpgradeView import Step
from .DistUpgradeCache import MyCache
from .DistUpgradeConfigParser import DistUpgradeConfig
from .DistUpgradeQuirks import DistUpgradeQuirks
from .DistUpgradeAptCdrom import AptCdrom
# workaround broken relative import in python-apt (LP: #871007), we
# want the local version of distinfo.py from oneiric, but because of
# a bug in python-apt we will get the natty version that does not
# know about "Component.parent_component" leading to a crash
from . import distinfo
from . import sourceslist
sourceslist.DistInfo = distinfo.DistInfo
from .sourceslist import SourcesList, is_mirror
from .distro import get_distro, NoDistroTemplateException
from .DistUpgradeGettext import gettext as _
from .DistUpgradeGettext import ngettext
import gettext
from .DistUpgradeCache import (CacheExceptionDpkgInterrupted,
CacheExceptionLockingFailed,
NotEnoughFreeSpaceError)
from .DistUpgradeApport import run_apport
REBOOT_REQUIRED_FILE = "/var/run/reboot-required"
def component_ordering_key(a):
""" key() function for sorted to ensure "correct" component ordering """
ordering = ["main", "restricted", "universe", "multiverse"]
try:
return ordering.index(a)
except ValueError:
# ensure to sort behind the "official" components, order is not
# really important for those
return len(ordering)+1
class NoBackportsFoundException(Exception):
pass
class DistUpgradeController(object):
""" this is the controller that does most of the work """
def __init__(self, distUpgradeView, options=None, datadir=None):
# setup the paths
localedir = "/usr/share/locale/"
if datadir == None or datadir == '.':
datadir = os.getcwd()
localedir = os.path.join(datadir,"mo")
self.datadir = datadir
self.options = options
# init gettext
gettext.bindtextdomain("ubuntu-release-upgrader",localedir)
gettext.textdomain("ubuntu-release-upgrader")
# setup the view
logging.debug("Using '%s' view" % distUpgradeView.__class__.__name__)
self._view = distUpgradeView
self._view.updateStatus(_("Reading cache"))
self.cache = None
self.fetcher = None
if not self.options or self.options.withNetwork == None:
self.useNetwork = True
else:
self.useNetwork = self.options.withNetwork
if options:
cdrompath = options.cdromPath
else:
cdrompath = None
self.aptcdrom = AptCdrom(distUpgradeView, cdrompath)
# the configuration
self.config = DistUpgradeConfig(datadir)
self.sources_backup_ext = "."+self.config.get("Files","BackupExt")
# move some of the options stuff into the self.config,
# ConfigParser deals only with strings it seems *sigh*
self.config.add_section("Options")
self.config.set("Options","withNetwork", str(self.useNetwork))
if self.options:
if self.options.devel_release:
self.config.set("Options","devRelease", "True")
else:
self.config.set("Options","devRelease", "False")
# some constants here
self.fromDist = self.config.get("Sources","From")
self.toDist = self.config.get("Sources","To")
self.origin = self.config.get("Sources","ValidOrigin")
self.arch = get_arch()
# we run with --force-overwrite by default
if "RELEASE_UPGRADE_NO_FORCE_OVERWRITE" not in os.environ:
logging.debug("enable dpkg --force-overwrite")
apt_pkg.config.set("DPkg::Options::","--force-overwrite")
# we run in full upgrade mode by default
self._partialUpgrade = False
# install the quirks handler
self.quirks = DistUpgradeQuirks(self, self.config)
# install a logind sleep inhibitor
self.inhibitor_fd = inhibit_sleep()
# setup env var
os.environ["RELEASE_UPGRADE_IN_PROGRESS"] = "1"
os.environ["PYCENTRAL_FORCE_OVERWRITE"] = "1"
os.environ["PATH"] = "%s:%s" % (os.getcwd()+"/imported",
os.environ["PATH"])
check_and_fix_xbit("./imported/invoke-rc.d")
# set max retries
maxRetries = self.config.getint("Network","MaxRetries")
apt_pkg.config.set("Acquire::Retries", str(maxRetries))
# max sizes for dpkgpm for large installs (see linux/limits.h and
# linux/binfmts.h)
apt_pkg.config.set("Dpkg::MaxArgs", str(64*1024))
apt_pkg.config.set("Dpkg::MaxArgBytes", str(128*1024))
# smaller to avoid hangs
apt_pkg.config.set("Acquire::http::Timeout","20")
apt_pkg.config.set("Acquire::ftp::Timeout","20")
# no list cleanup here otherwise a "cancel" in the upgrade
# will not restore the full state (lists will be missing)
apt_pkg.config.set("Apt::Get::List-Cleanup", "false")
# forced obsoletes
self.forced_obsoletes = self.config.getlist("Distro","ForcedObsoletes")
# list of valid mirrors that we can add
self.valid_mirrors = self.config.getListFromFile("Sources","ValidMirrors")
# third party mirrors
self.valid_3p_mirrors = []
if self.config.has_section('ThirdPartyMirrors'):
self.valid_3p_mirrors = [pair[1] for pair in
self.config.items('ThirdPartyMirrors')]
# debugging
#apt_pkg.config.set("DPkg::Options::","--debug=0077")
# apt cron job
self._aptCronJobPerms = 0o755
# for inhibiting idle
self._uid = ''
self._user_env = {}
def openCache(self, lock=True, restore_sources_list_on_fail=False):
logging.debug("openCache()")
if self.cache is None:
self.quirks.run("PreCacheOpen")
else:
self.cache.release_lock()
self.cache.unlock_lists_dir()
# this loop will try getting the lock a couple of times
MAX_LOCK_RETRIES = 20
lock_retry = 0
while True:
try:
# exit here once the cache is ready
return self._openCache(lock)
except CacheExceptionLockingFailed as e:
# wait a bit
lock_retry += 1
self._view.processEvents()
time.sleep(0.1)
logging.debug(
"failed to lock the cache, retrying (%i)" % lock_retry)
# and give up after some time
if lock_retry > MAX_LOCK_RETRIES:
logging.error("Cache can not be locked (%s)" % e)
self._view.error(_("Unable to get exclusive lock"),
_("This usually means that another "
"package management application "
"(like apt-get or aptitude) "
"already running. Please close that "
"application first."));
if restore_sources_list_on_fail:
self.abort()
else:
sys.exit(1)
def _openCache(self, lock):
try:
self.cache = MyCache(self.config,
self._view,
self.quirks,
self._view.getOpCacheProgress(),
lock)
# alias name for the plugin interface code
self.apt_cache = self.cache
# if we get a dpkg error that it was interrupted, just
# run dpkg --configure -a
except CacheExceptionDpkgInterrupted:
logging.warning("dpkg interrupted, calling dpkg --configure -a")
cmd = ["/usr/bin/dpkg","--configure","-a"]
if os.environ.get("DEBIAN_FRONTEND") == "noninteractive":
cmd.append("--force-confold")
self._view.getTerminal().call(cmd)
self.cache = MyCache(self.config,
self._view,
self.quirks,
self._view.getOpCacheProgress())
self.cache.partialUpgrade = self._partialUpgrade
logging.debug("/openCache(), new cache size %i" % len(self.cache))
def _viewSupportsSSH(self):
"""
Returns True if this view support upgrades over ssh.
In theory all views should support it, but for savety
we do only allow text ssh upgrades (see LP: #322482)
"""
supported = self.config.getlist("View","SupportSSH")
if self._view.__class__.__name__ in supported:
return True
return False
def _sshMagic(self):
""" this will check for server mode and if we run over ssh.
if this is the case, we will ask and spawn a additional
daemon (to be sure we have a spare one around in case
of trouble)
"""
pidfile = os.path.join("/var/run/release-upgrader-sshd.pid")
if (not os.path.exists(pidfile) and
os.path.isdir("/proc") and
is_child_of_process_name("sshd")):
# check if the frontend supports ssh upgrades (see lp: #322482)
if not self._viewSupportsSSH():
logging.error("upgrade over ssh not allowed")
self._view.error(_("Upgrading over remote connection not supported"),
_("You are running the upgrade over a "
"remote ssh connection with a frontend "
"that does "
"not support this. Please try a text "
"mode upgrade with 'do-release-upgrade'."
"\n\n"
"The upgrade will "
"abort now. Please try without ssh.")
)
sys.exit(1)
return False
# ask for a spare one to start (and below 1024)
port = 1022
res = self._view.askYesNoQuestion(
_("Continue running under SSH?"),
_("This session appears to be running under ssh. "
"It is not recommended to perform a upgrade "
"over ssh currently because in case of failure "
"it is harder to recover.\n\n"
"If you continue, an additional ssh daemon will be "
"started at port '%s'.\n"
"Do you want to continue?") % port)
# abort
if res == False:
sys.exit(1)
res = subprocess.call(["/usr/sbin/sshd",
"-o", "PidFile=%s" % pidfile,
"-p",str(port)])
if res == 0:
summary = _("Starting additional sshd")
descr = _("To make recovery in case of failure easier, an "
"additional sshd will be started on port '%s'. "
"If anything goes wrong with the running ssh "
"you can still connect to the additional one.\n"
) % port
if iptables_active():
cmd = "iptables -I INPUT -p tcp --dport %s -j ACCEPT" % port
descr += _(
"If you run a firewall, you may need to "
"temporarily open this port. As this is "
"potentially dangerous it's not done automatically. "
"You can open the port with e.g.:\n'%s'") % cmd
self._view.information(summary, descr)
return True
def _tryUpdateSelf(self):
""" this is a helper that is run if we are started from a CD
and we have network - we will then try to fetch a update
of ourself
"""
from .MetaRelease import MetaReleaseCore
from .DistUpgradeFetcherSelf import DistUpgradeFetcherSelf
# check if we run from a LTS
forceLTS=False
if (self.release == "dapper" or
self.release == "hardy" or
self.release == "lucid" or
self.release == "precise"):
forceLTS=True
m = MetaReleaseCore(useDevelopmentRelease=False,
forceLTS=forceLTS)
# this will timeout eventually
self._view.processEvents()
while not m.downloaded.wait(0.1):
self._view.processEvents()
if m.new_dist is None:
logging.error("No new dist found")
return False
# we have a new dist
progress = self._view.getAcquireProgress()
fetcher = DistUpgradeFetcherSelf(new_dist=m.new_dist,
progress=progress,
options=self.options,
view=self._view)
fetcher.run()
def _pythonSymlinkCheck(self):
""" sanity check that /usr/bin/python points to the default
python version. Users tend to modify this symlink, which
breaks stuff in obscure ways (Ubuntu #75557).
"""
logging.debug("_pythonSymlinkCheck run")
binaries_and_dirnames = [("python", "python"), ("python2", "python"),
("python3", "python3")]
for binary, dirname in binaries_and_dirnames:
debian_defaults = '/usr/share/%s/debian_defaults' % dirname
if os.path.exists(debian_defaults):
config = SafeConfigParser()
with open(debian_defaults) as f:
config.readfp(f)
try:
expected_default = config.get('DEFAULT', 'default-version')
except NoOptionError:
logging.debug("no default version for %s found in '%s'" %
(binary, config))
return False
try:
fs_default_version = os.readlink('/usr/bin/%s' % binary)
except OSError as e:
logging.error("os.readlink failed (%s)" % e)
return False
if not fs_default_version in (expected_default, os.path.join('/usr/bin', expected_default)) \
and not (binary == 'python' and fs_default_version in ('python2', '/usr/bin/python2')):
logging.debug("%s symlink points to: '%s', but expected is '%s' or '%s'" %
(binary, fs_default_version, expected_default, os.path.join('/usr/bin', expected_default)))
return False
return True
def prepare(self):
""" initial cache opening, sanity checking, network checking """
# first check if that is a good upgrade
self.release = release = subprocess.Popen(["lsb_release","-c","-s"],
stdout=subprocess.PIPE,
universal_newlines=True).communicate()[0].strip()
logging.debug("lsb-release: '%s'" % release)
if not (release == self.fromDist or release == self.toDist):
logging.error("Bad upgrade: '%s' != '%s' " % (release, self.fromDist))
self._view.error(_("Can not upgrade"),
_("An upgrade from '%s' to '%s' is not "
"supported with this tool." % (release, self.toDist)))
sys.exit(1)
# setup backports (if we have them)
if self.options and self.options.havePrerequists:
backportsdir = os.getcwd()+"/backports"
logging.info("using backports in '%s' " % backportsdir)
logging.debug("have: %s" % glob.glob(backportsdir+"/*.udeb"))
if os.path.exists(backportsdir+"/usr/bin/dpkg"):
apt_pkg.config.set("Dir::Bin::dpkg",backportsdir+"/usr/bin/dpkg");
if os.path.exists(backportsdir+"/usr/lib/apt/methods"):
apt_pkg.config.set("Dir::Bin::methods",backportsdir+"/usr/lib/apt/methods")
conf = backportsdir+"/etc/apt/apt.conf.d/01ubuntu"
if os.path.exists(conf):
logging.debug("adding config '%s'" % conf)
apt_pkg.read_config_file(apt_pkg.config, conf)
# do the ssh check and warn if we run under ssh
self._sshMagic()
# check python version
if not self._pythonSymlinkCheck():
logging.error("pythonSymlinkCheck() failed, aborting")
self._view.error(_("Can not upgrade"),
_("Your python3 install is corrupted. "
"Please fix the '/usr/bin/python3' symlink."))
sys.exit(1)
# open cache
try:
self.openCache()
except SystemError as e:
logging.error("openCache() failed: '%s'" % e)
return False
if not self.cache.sanity_check(self._view):
return False
# now figure out if we need to go into desktop or
# server mode - we use a heuristic for this
self.serverMode = self.cache.need_server_mode()
if self.serverMode:
os.environ["RELEASE_UPGRADE_MODE"] = "server"
else:
os.environ["RELEASE_UPGRADE_MODE"] = "desktop"
if not self.checkViewDepends():
logging.error("checkViewDepends() failed")
return False
from .DistUpgradeMain import SYSTEM_DIRS
for systemdir in SYSTEM_DIRS:
if os.path.exists(systemdir) and not os.access(systemdir, os.W_OK):
logging.error("%s not writable" % systemdir)
self._view.error(
_("Can not write to '%s'") % systemdir,
_("Its not possible to write to the system directory "
"'%s' on your system. The upgrade can not "
"continue.\n"
"Please make sure that the system directory is "
"writable.") % systemdir)
self.abort()
# FIXME: we may try to find out a bit more about the network
# connection here and ask more intelligent questions
if self.aptcdrom and self.options and self.options.withNetwork == None:
res = self._view.askYesNoQuestion(_("Include latest updates from the Internet?"),
_("The upgrade system can use the internet to "
"automatically download "
"the latest updates and install them during the "
"upgrade. If you have a network connection this is "
"highly recommended.\n\n"
"The upgrade will take longer, but when "
"it is complete, your system will be fully up to "
"date. You can choose not to do this, but you "
"should install the latest updates soon after "
"upgrading.\n"
"If you answer 'no' here, the network is not "
"used at all."),
'Yes')
self.useNetwork = res
self.config.set("Options","withNetwork", str(self.useNetwork))
logging.debug("useNetwork: '%s' (selected by user)" % res)
if res:
self._tryUpdateSelf()
return True
def _sourcesListEntryDownloadable(self, entry):
"""
helper that checks if a sources.list entry points to
something downloadable
"""
logging.debug("verifySourcesListEntry: %s" % entry)
# no way to verify without network
if not self.useNetwork:
logging.debug("skipping downloadable check (no network)")
return True
# check if the entry points to something we can download
uri = "%s/dists/%s/Release" % (entry.uri, entry.dist)
return url_downloadable(uri, logging.debug)
def rewriteSourcesList(self, mirror_check=True):
if mirror_check:
logging.debug("rewriteSourcesList() with mirror_check")
else:
logging.debug("rewriteSourcesList()")
sync_components = self.config.getlist("Sources","Components")
# skip mirror check if special environment is set
# (useful for server admins with internal repos)
if (self.config.getWithDefault("Sources","AllowThirdParty",False) or
"RELEASE_UPGRADER_ALLOW_THIRD_PARTY" in os.environ):
logging.warning("mirror check skipped, *overriden* via config")
mirror_check=False
# check if we need to enable main
main_was_missing = False
if mirror_check == True and self.useNetwork:
# now check if the base-meta pkgs are available in
# the archive or only available as "now"
# -> if not that means that "main" is missing and we
# need to enable it
logging.debug(self.config.getlist("Distro", "BaseMetaPkgs"))
for pkgname in self.config.getlist("Distro", "BaseMetaPkgs"):
logging.debug("Checking pkg: %s" % pkgname)
if ((not pkgname in self.cache or
not self.cache[pkgname].candidate or
len(self.cache[pkgname].candidate.origins) == 0)
or
(self.cache[pkgname].candidate and
len(self.cache[pkgname].candidate.origins) == 1 and
self.cache[pkgname].candidate.origins[0].archive == "now")
):
logging.debug("BaseMetaPkg '%s' has no candidate.origins" % pkgname)
try:
distro = get_distro()
distro.get_sources(self.sources)
distro.enable_component("main")
main_was_missing = True
logging.debug('get_distro().enable_component("main") succeeded')
except NoDistroTemplateException as e:
logging.exception('NoDistroTemplateException raised: %s' % e)
# fallback if everything else does not work,
# we replace the sources.list with lines to
# main and restricted
logging.debug('get_distro().enable_component("main") failed, overwriting sources.list instead as last resort')
comment = " auto generated by ubuntu-release-upgrader"
comps = ["main", "restricted"]
uri = "http://archive.ubuntu.com/ubuntu"
self.sources.add("deb", uri, self.toDist, comps,
comment)
self.sources.add("deb", uri, self.toDist+"-updates",
comps, comment)
self.sources.add("deb",
"http://security.ubuntu.com/ubuntu",
self.toDist+"-security", comps,
comment)
break
# this must map, i.e. second in "from" must be the second in "to"
# (but they can be different, so in theory we could exchange
# component names here)
pockets = self.config.getlist("Sources","Pockets")
fromDists = [self.fromDist] + ["%s-%s" % (self.fromDist, x)
for x in pockets]
toDists = [self.toDist] + ["%s-%s" % (self.toDist,x)
for x in pockets]
self.sources_disabled = False
# Special quirk to remove extras.ubuntu.com
new_list = []
for entry in self.sources.list[:]:
if "/extras.ubuntu.com" in entry.uri:
continue
if entry.line.startswith(
"## This software is not part of Ubuntu, but is offered by third-party"):
continue
if entry.line.startswith(
"## developers who want to ship their latest software."):
continue
new_list.append(entry)
self.sources.list = new_list
# look over the stuff we have
foundToDist = False
# collect information on what components (main,universe) are enabled for what distro (sub)version
# e.g. found_components = { 'hardy':set("main","restricted"), 'hardy-updates':set("main") }
self.found_components = {}
entry_uri_test_results = {}
for entry in self.sources.list[:]:
if entry.uri not in entry_uri_test_results:
entry_uri_test_results[entry.uri] = 'unknown'
# ignore invalid records or disabled ones
if entry.invalid or entry.disabled:
continue
# we disable breezy cdrom sources to make sure that demoted
# packages are removed
if entry.uri.startswith("cdrom:") and entry.dist == self.fromDist:
logging.debug("disabled '%s' cdrom entry (dist == fromDist)" % entry)
entry.disabled = True
continue
# check if there is actually a lists file for them available
# and disable them if not
elif entry.uri.startswith("cdrom:"):
#
listdir = apt_pkg.config.find_dir("Dir::State::lists")
if not os.path.exists("%s/%s%s_%s_%s" %
(listdir,
apt_pkg.uri_to_filename(entry.uri),
"dists",
entry.dist,
"Release")):
logging.warning("disabling cdrom source '%s' because it has no Release file" % entry)
entry.disabled = True
continue
# special case for archive.canonical.com that needs to
# be rewritten (for pre-gutsy upgrades)
cdist = "%s-commercial" % self.fromDist
if (not entry.disabled and
entry.uri.startswith("http://archive.canonical.com") and
entry.dist == cdist):
entry.dist = self.toDist
entry.comps = ["partner"]
logging.debug("transitioned commercial to '%s' " % entry)
continue
# special case for landscape.canonical.com because they
# don't use a standard archive layout (gutsy->hardy)
# XXX - Is this still relevant?
if (not entry.disabled and
entry.uri.startswith("http://landscape.canonical.com/packages/%s" % self.fromDist)):
logging.debug("commenting landscape.canonical.com out")
entry.disabled = True
continue
# Disable proposed on upgrade to a development release.
if (not entry.disabled and self.options
and self.options.devel_release == True and
"%s-proposed" % self.fromDist in entry.dist):
logging.debug("upgrade to development release, disabling proposed")
entry.dist = "%s-proposed" % self.toDist
entry.comment += _("Not for humans during development stage of release %s") % self.toDist
entry.disabled = True
continue
# handle upgrades from a EOL release and check if there
# is a supported release available
if (not entry.disabled and
"old-releases.ubuntu.com/" in entry.uri):
logging.debug("upgrade from old-releases.ubuntu.com detected")
# test country mirror first, then archive.u.c
for uri in ["http://%sarchive.ubuntu.com/ubuntu" % country_mirror(),
"http://archive.ubuntu.com/ubuntu"]:
test_entry = copy.copy(entry)
test_entry.uri = uri
test_entry.dist = self.toDist
if self._sourcesListEntryDownloadable(test_entry):
logging.info("transition from old-release.u.c to %s" % uri)
entry.uri = uri
if entry.uri not in entry_uri_test_results:
entry_uri_test_results[entry.uri] = 'passed'
break
logging.debug("examining: '%s'" % get_string_with_no_auth_from_source_entry(entry))
# check if it's a mirror (or official site)
validMirror = self.isMirror(entry.uri)
thirdPartyMirror = not mirror_check or self.isThirdPartyMirror(entry.uri)
if validMirror or thirdPartyMirror:
# disabled/security/commercial/extras are special cases
# we use validTo/foundToDist to figure out if we have a
# main archive mirror in the sources.list or if we
# need to add one
validTo = True
if (entry.disabled or
entry.type == "deb-src" or
"/security.ubuntu.com" in entry.uri or
"%s-security" % self.fromDist in entry.dist or
"%s-backports" % self.fromDist in entry.dist or
"/archive.canonical.com" in entry.uri):
validTo = False
if entry.dist in toDists:
# so the self.sources.list is already set to the new
# distro
logging.debug("entry '%s' is already set to new dist" % get_string_with_no_auth_from_source_entry(entry))
foundToDist |= validTo
elif entry.dist in fromDists:
if entry_uri_test_results[entry.uri] == 'unknown':
foundToDist |= validTo
# check to see whether the archive provides the new dist
test_entry = copy.copy(entry)
test_entry.dist = self.toDist
if not self._sourcesListEntryDownloadable(test_entry):
entry_uri_test_results[entry.uri] = 'failed'
else:
entry_uri_test_results[entry.uri] = 'passed'
if entry_uri_test_results[entry.uri] == 'failed':
entry.disabled = True
self.sources_disabled = True
logging.debug("entry '%s' was disabled (no Release file)" % get_string_with_no_auth_from_source_entry(entry))
else:
foundToDist |= validTo
entry.dist = toDists[fromDists.index(entry.dist)]
logging.debug("entry '%s' updated to new dist" % get_string_with_no_auth_from_source_entry(entry))
elif entry.type == 'deb-src':
continue
elif validMirror:
# disable all entries that are official but don't
# point to either "to" or "from" dist
entry.disabled = True
self.sources_disabled = True
logging.debug("entry '%s' was disabled (unknown dist)" % get_string_with_no_auth_from_source_entry(entry))
# if we make it to this point, we have an official or third-party mirror
# XXX - is this still relevant?
# check if the arch is powerpc or sparc and if so, transition
# to ports.ubuntu.com (powerpc got demoted in gutsy, sparc
# in hardy)
if (entry.type == "deb" and
not "ports.ubuntu.com" in entry.uri and
(self.arch == "powerpc" or self.arch == "sparc")):
logging.debug("moving %s source entry to 'ports.ubuntu.com' " % self.arch)
entry.uri = "http://ports.ubuntu.com/ubuntu-ports/"
# gather what components are enabled and are inconsistent
for d in ["%s" % self.toDist,
"%s-updates" % self.toDist,
"%s-security" % self.toDist]:
# create entry if needed, ignore disabled
# entries and deb-src
self.found_components.setdefault(d, set())
if (not entry.disabled and entry.dist == d and
entry.type == "deb"):
for comp in entry.comps:
# only sync components we know about
if not comp in sync_components:
continue
self.found_components[d].add(comp)
else:
# disable anything that is not from a official mirror or a whitelisted third party
if entry.dist == self.fromDist:
entry.dist = self.toDist
disable_comment = " " + _("disabled on upgrade to %s") % self.toDist
if isinstance(entry.comment, bytes):
entry.comment += disable_comment.encode('UTF-8')
else:
entry.comment += disable_comment
entry.disabled = True
self.sources_disabled = True
logging.debug("entry '%s' was disabled (unknown mirror)" % get_string_with_no_auth_from_source_entry(entry))
# if its not a valid mirror and we manually added main, be
# nice and add pockets and components corresponding to what we
# disabled.
if main_was_missing:
if entry.dist in fromDists:
entry.dist = toDists[fromDists.index(entry.dist)]
if entry.dist not in toDists:
continue # Unknown target, do not add this
# gather what components are enabled and are inconsistent
for d in ["%s" % self.toDist,
"%s-updates" % self.toDist,
"%s-security" % self.toDist]:
# create entry if needed, ignore deb-src entries
self.found_components.setdefault(d, set())
if entry.dist == d and entry.type == "deb":
for comp in entry.comps:
# only sync components we know about
if not comp in sync_components:
continue
self.found_components[d].add(comp)
logging.debug("Adding entry: %s %s %s" % (entry.type, entry.dist, entry.comps))
uri = "http://archive.ubuntu.com/ubuntu"
comment = " auto generated by ubuntu-release-upgrader"
self.sources.add(entry.type, uri, entry.dist, entry.comps, comment)
# now go over the list again and check for missing components
# in $dist-updates and $dist-security and add them
for entry in self.sources.list[:]:
# skip all comps that are not relevant (including e.g. "hardy")
if (entry.invalid or entry.disabled or entry.type == "deb-src" or
entry.uri.startswith("cdrom:") or entry.dist == self.toDist):
continue
# now check for "$dist-updates" and "$dist-security" and add any inconsistencies
if entry.dist in self.found_components:
component_diff = self.found_components[self.toDist]-self.found_components[entry.dist]
if component_diff:
logging.info("fixing components inconsistency from '%s'" % get_string_with_no_auth_from_source_entry(entry))
# extend and make sure to keep order
entry.comps.extend(
sorted(component_diff, key=component_ordering_key))
logging.info("to new entry '%s'" % get_string_with_no_auth_from_source_entry(entry))
del self.found_components[entry.dist]
return foundToDist
def updateSourcesList(self):
logging.debug("updateSourcesList()")
self.sources = SourcesList(matcherPath=self.datadir)
if not any(e.type == "deb" and e.dist == self.fromDist for e in self.sources):
res = self._view.askYesNoQuestion(_("No valid sources.list entry found"),
_("While scanning your repository "
"information no entry about %s could be "
"found.\n\n"
"An upgrade might not succeed.\n\n"
"Do you want to continue anyway?") % self.fromDist)
if not res:
self.abort()
# backup first!
self.sources.backup(self.sources_backup_ext)
if not self.rewriteSourcesList(mirror_check=True):
logging.error("No valid mirror found")
res = self._view.askYesNoQuestion(_("No valid mirror found"),
_("While scanning your repository "
"information no mirror entry for "
"the upgrade was found. "
"This can happen if you run an internal "
"mirror or if the mirror information is "
"out of date.\n\n"
"Do you want to rewrite your "
"'sources.list' file anyway? If you choose "
"'Yes' here it will update all '%s' to '%s' "
"entries.\n"
"If you select 'No' the upgrade will cancel."
) % (self.fromDist, self.toDist))
if res:
# re-init the sources and try again
self.sources = SourcesList(matcherPath=self.datadir)
# its ok if rewriteSourcesList fails here if
# we do not use a network, the sources.list may be empty
if (not self.rewriteSourcesList(mirror_check=False)
and self.useNetwork):
#hm, still nothing useful ...
prim = _("Generate default sources?")
secon = _("After scanning your 'sources.list' no "
"valid entry for '%s' was found.\n\n"
"Should default entries for '%s' be "
"added? If you select 'No', the upgrade "
"will cancel.") % (self.fromDist, self.toDist)
if not self._view.askYesNoQuestion(prim, secon):
self.abort()
# add some defaults here
# FIXME: find mirror here
logging.info("Generated new default sources.list")
uri = "http://archive.ubuntu.com/ubuntu"
comps = ["main","restricted"]
self.sources.add("deb", uri, self.toDist, comps)
self.sources.add("deb", uri, self.toDist+"-updates", comps)
self.sources.add("deb",
"http://security.ubuntu.com/ubuntu/",
self.toDist+"-security", comps)
else:
self.abort()
# now write
self.sources.save()
# re-check if the written self.sources are valid, if not revert and
# bail out
# TODO: check if some main packages are still available or if we
# accidentally shot them, if not, maybe offer to write a standard
# sources.list?
try:
sourceslist = apt_pkg.SourceList()
sourceslist.read_main_list()
except SystemError:
logging.error("Repository information invalid after updating (we broke it!)")
if os.path.exists("/usr/bin/apport-bug"):
self._view.error(_("Repository information invalid"),
_("Upgrading the repository information "
"resulted in a invalid file so a bug "
"reporting process is being started."))
subprocess.Popen(["apport-bug", "ubuntu-release-upgrader-core"])
else:
self._view.error(_("Repository information invalid"),
_("Upgrading the repository information "
"resulted in a invalid file. To report "
"a bug install apport and then execute "
"'apport-bug ubuntu-release-upgrader'."))
logging.error("Missing apport-bug, bug report not "
"autocreated")
return False
if self.sources_disabled:
self._view.information(_("Third party sources disabled"),
_("Some third party entries in your sources.list "
"were disabled. You can re-enable them "
"after the upgrade with the "
"'software-properties' tool or "
"your package manager."
))
get_telemetry().set_using_third_party_sources(self.sources_disabled)
return True
def _logChanges(self):
# debugging output
logging.debug("About to apply the following changes")
inst = []
up = []
rm = []
held = []
keep = []
for pkg in self.cache:
if pkg.marked_install: inst.append(pkg.name)
elif pkg.marked_upgrade: up.append(pkg.name)
elif pkg.marked_delete: rm.append(pkg.name)
elif (pkg.is_installed and pkg.is_upgradable): held.append(pkg.name)
elif pkg.is_installed and pkg.marked_keep: keep.append(pkg.name)
logging.debug("Keep at same version: %s" % " ".join(keep))
logging.debug("Upgradable, but held- back: %s" % " ".join(held))
logging.debug("Remove: %s" % " ".join(rm))
logging.debug("Install: %s" % " ".join(inst))
logging.debug("Upgrade: %s" % " ".join(up))
def doPostInitialUpdate(self):
# check if we have packages in ReqReinst state that are not
# downloadable
logging.debug("doPostInitialUpdate")
self.quirks.run("PostInitialUpdate")
if not self.cache:
return False
if len(self.cache.req_reinstall_pkgs) > 0:
logging.warning("packages in reqReinstall state, trying to fix")
self.cache.fix_req_reinst(self._view)
self.openCache()
if len(self.cache.req_reinstall_pkgs) > 0:
reqreinst = self.cache.req_reinstall_pkgs
header = ngettext("Package in inconsistent state",
"Packages in inconsistent state",
len(reqreinst))
summary = ngettext("The package '%s' is in an inconsistent "
"state and needs to be reinstalled, but "
"no archive can be found for it. "
"Please reinstall the package manually "
"or remove it from the system.",
"The packages '%s' are in an inconsistent "
"state and need to be reinstalled, but "
"no archive can be found for them. "
"Please reinstall the packages manually "
"or remove them from the system.",
len(reqreinst)) % ", ".join(reqreinst)
self._view.error(header, summary)
return False
# Log MetaPkgs installed to see if there is more than one.
meta_pkgs = []
for pkg in self.config.getlist("Distro","MetaPkgs"):
if pkg in self.cache and self.cache[pkg].is_installed:
meta_pkgs.append(pkg)
logging.debug("MetaPkgs: %s" % " ".join(sorted(meta_pkgs)))
# FIXME: check out what packages are downloadable etc to
# compare the list after the update again
self.obsolete_pkgs = self.cache._getObsoletesPkgs()
self.foreign_pkgs = self.cache._getForeignPkgs(self.origin, self.fromDist, self.toDist)
# If a PPA has already been disabled the pkgs won't be considered
# foreign
if len(self.foreign_pkgs) > 0:
self.config.set("Options","foreignPkgs", "True")
else:
self.config.set("Options","foreignPkgs", "False")
if self.serverMode:
self.tasks = self.cache.installedTasks
logging.debug("Foreign: %s" % " ".join(sorted(self.foreign_pkgs)))
logging.debug("Obsolete: %s" % " ".join(sorted(self.obsolete_pkgs)))
return True
def doUpdate(self, showErrors=True, forceRetries=None):
logging.debug("running doUpdate() (showErrors=%s)" % showErrors)
if not self.useNetwork:
logging.debug("doUpdate() will not use the network because self.useNetwork==false")
return True
self.cache._list.read_main_list()
progress = self._view.getAcquireProgress()
# FIXME: also remove all files from the lists partial dir!
currentRetry = 0
if forceRetries is not None:
maxRetries=forceRetries
else:
maxRetries = self.config.getint("Network","MaxRetries")
# LP: #1321959
error_msg = ""
while currentRetry < maxRetries:
try:
self.cache.update(progress)
except (SystemError, IOError) as e:
error_msg = str(e)
logging.error("IOError/SystemError in cache.update(): '%s'. Retrying (currentRetry: %s)" % (e,currentRetry))
currentRetry += 1
continue
# no exception, so all was fine, we are done
return True
logging.error("doUpdate() failed completely")
if showErrors:
self._view.error(_("Error during update"),
_("A problem occurred during the update. "
"This is usually some sort of network "
"problem, please check your network "
"connection and retry."), "%s" % error_msg)
return False
def _checkBootEfi(self):
" check that /boot/efi is a mounted partition on an EFI system"
# Not an UEFI system
if not os.path.exists("/sys/firmware/efi"):
logging.debug("Not an UEFI system")
return True
# Stuff we know about that would write to the ESP
bootloaders = ["shim-signed", "grub-efi-amd64", "grub-efi-ia32", "grub-efi-arm", "grub-efi-arm64", "sicherboot"]
if not any(bl in self.cache and self.cache[bl].is_installed for bl in bootloaders):
logging.debug("UEFI system, but no UEFI grub installed")
return True
mounted=False
with open("/proc/mounts") as mounts:
for line in mounts:
line=line.strip()
try:
(what, where, fs, options, a, b) = line.split()
except ValueError as e:
logging.debug("line '%s' in /proc/mounts not understood (%s)" % (line, e))
continue
if where != "/boot/efi":
continue
mounted=True
if "rw" in options.split(","):
logging.debug("Found writable ESP %s", line)
return True
if not mounted:
self._view.error(_("EFI System Partition (ESP) not usable"),
_("Your EFI System Partition (ESP) is not "
"mounted at /boot/efi. Please ensure that "
"it is properly configured and try again."))
else:
self._view.error(_("EFI System Partition (ESP) not usable"),
_("The EFI System Partition (ESP) mounted at "
"/boot/efi is not writable. Please mount "
"this partition read-write and try again."))
return False
def _checkFreeSpace(self):
" this checks if we have enough free space on /var and /usr"
err_sum = _("Not enough free disk space")
# TRANSLATORS: you can change the order of the sentence,
# make sure to keep all {str_*} string untranslated.
err_msg = _("The upgrade has aborted. "
"The upgrade needs a total of {str_total} free space on disk '{str_dir}'. "
"Please free at least an additional {str_needed} of disk "
"space on '{str_dir}'. {str_remedy}")
# specific ways to resolve lack of free space
remedy_archivedir = _("Remove temporary packages of former "
"installations using 'sudo apt clean'.")
remedy_boot = _("You can remove old kernels using "
"'sudo apt autoremove' and you could also "
"set COMPRESS=xz in "
"/etc/initramfs-tools/initramfs.conf to "
"reduce the size of your initramfs.")
remedy_root = _("Empty your trash and remove temporary "
"packages of former installations using "
"'sudo apt-get clean'.")
remedy_tmp = _("Reboot to clean up files in /tmp.")
remedy_usr = _("")
# allow override
if self.config.getWithDefault("FreeSpace","SkipCheck",False):
logging.warning("free space check skipped via config override")
return True
# do the check
with_snapshots = self._is_apt_btrfs_snapshot_supported()
try:
self.cache.checkFreeSpace(with_snapshots)
except NotEnoughFreeSpaceError as e:
# ok, showing multiple error dialog sucks from the UI
# perspective, but it means we do not need to break the
# string freeze
archivedir = apt_pkg.config.find_dir("Dir::Cache::archives")
err_long = ""
remedy = {archivedir: remedy_archivedir,
'/var': remedy_archivedir,
'/boot': remedy_boot,
'/': remedy_root,
'/tmp': remedy_tmp,
'/usr': remedy_usr}
for req in e.free_space_required_list:
if err_long != "":
err_long += " "
if req.dir in remedy:
err_long += err_msg.format(str_total=req.size_total, str_dir=req.dir,
str_needed=req.size_needed,
str_remedy=remedy[req.dir])
else:
err_long += err_msg.format(str_total=req.size_total, str_dir=req.dir,
str_needed=req.size_needed,
str_remedy='')
self._view.error(err_sum, err_long)
return False
return True
def calcDistUpgrade(self):
self._view.updateStatus(_("Calculating the changes"))
if not self.cache.distUpgrade(self._view, self.serverMode, self._partialUpgrade):
return False
if self.serverMode:
if not self.cache.installTasks(self.tasks):
return False
# show changes and confirm
changes = self.cache.get_changes()
self._view.processEvents()
# log the changes for debugging
self._logChanges()
self._view.processEvents()
# check if we have enough free space
if not self._checkFreeSpace():
return False
# check that ESP is sane
if not self._checkBootEfi():
return False
self._view.processEvents()
# get the demotions
self.installed_demotions = self.cache.get_installed_demoted_packages()
if len(self.installed_demotions) > 0:
self.installed_demotions.sort()
logging.debug("demoted: '%s'" % " ".join([x.name for x in self.installed_demotions]))
logging.debug("found components: %s" % self.found_components)
# flush UI
self._view.processEvents()
return changes
def askDistUpgrade(self):
changes = self.calcDistUpgrade()
if not changes:
return False
# ask the user
res = self._view.confirmChanges(_("Do you want to start the upgrade?"),
changes,
self.installed_demotions,
self.cache.required_download)
return res
def _disableAptCronJob(self):
if os.path.exists("/etc/cron.daily/apt"):
#self._aptCronJobPerms = os.stat("/etc/cron.daily/apt")[ST_MODE]
logging.debug("disabling apt cron job (%s)" % oct(self._aptCronJobPerms))
os.chmod("/etc/cron.daily/apt",0o644)
def _enableAptCronJob(self):
if os.path.exists("/etc/cron.daily/apt"):
logging.debug("enabling apt cron job")
os.chmod("/etc/cron.daily/apt", self._aptCronJobPerms)
def doDistUpgradeFetching(self):
# ensure that no apt cleanup is run during the download/install
self._disableAptCronJob()
# get the upgrade
currentRetry = 0
fprogress = self._view.getAcquireProgress()
#iprogress = self._view.getInstallProgress(self.cache)
# start slideshow
url = self.config.getWithDefault("Distro","SlideshowUrl",None)
if url:
try:
lang = locale.getdefaultlocale()[0].split('_')[0]
except:
logging.exception("getdefaultlocale")
lang = "en"
self._view.getHtmlView().open("%s#locale=%s" % (url, lang))
# retry the fetching in case of errors
maxRetries = self.config.getint("Network","MaxRetries")
# FIXME: we get errors like
# "I wasn't able to locate file for the %s package"
# here sometimes. its unclear why and not reproducible, the
# current theory is that for some reason the file is not
# considered trusted at the moment
# pkgAcquireArchive::QueueNext() runs debReleaseIndex::IsTrused()
# (the later just checks for the existence of the .gpg file)
# OR
# the fact that we get a pm and fetcher here confuses something
# in libapt?
# POSSIBLE workaround: keep the list-dir locked so that
# no apt-get update can run outside from the release
# upgrader
user_canceled = False
# LP: #1102593 - In Python 3, the targets of except clauses get `del`d
# from the current namespace after the exception is handled, so we
# must assign it to a different variable in order to use it after
# the while loop.
exception = None
while currentRetry < maxRetries:
try:
pm = apt_pkg.PackageManager(self.cache._depcache)
self.fetcher = apt_pkg.Acquire(fprogress)
self.cache._fetch_archives(self.fetcher, pm)
except apt.cache.FetchCancelledException as e:
logging.info("user canceled")
user_canceled = True
exception = e
break
except IOError as e:
# fetch failed, will be retried
logging.error("IOError in cache.commit(): '%s'. Retrying (currentTry: %s)" % (e,currentRetry))
currentRetry += 1
exception = e
continue
return True
# maximum fetch-retries reached without a successful commit
if user_canceled:
self._view.information(_("Upgrade canceled"),
_("The upgrade will cancel now and the "
"original system state will be restored. "
"You can resume the upgrade at a later "
"time."))
else:
logging.error("giving up on fetching after maximum retries")
self._view.error(_("Could not download the upgrades"),
_("The upgrade has aborted. Please check your "
"Internet connection or "
"installation media and try again. All files "
"downloaded so far have been kept."),
"%s" % exception)
# abort here because we want our sources.list back
self._enableAptCronJob()
self.abort()
def _is_apt_btrfs_snapshot_supported(self):
""" check if apt-btrfs-snapshot is usable """
try:
import apt_btrfs_snapshot
except ImportError:
return
try:
apt_btrfs = apt_btrfs_snapshot.AptBtrfsSnapshot()
res = apt_btrfs.snapshots_supported()
except:
logging.exception("failed to check btrfs support")
return False
logging.debug("apt btrfs snapshots supported: %s" % res)
return res
def _maybe_create_apt_btrfs_snapshot(self):
""" create btrfs snapshot (if btrfs layout is there) """
if not self._is_apt_btrfs_snapshot_supported():
return
import apt_btrfs_snapshot
apt_btrfs = apt_btrfs_snapshot.AptBtrfsSnapshot()
prefix = "release-upgrade-%s-" % self.toDist
res = apt_btrfs.create_btrfs_root_snapshot(prefix)
logging.info("creating snapshot '%s' (success=%s)" % (prefix, res))
def doDistUpgradeSimulation(self):
backups = {}
backups["dir::bin::dpkg"] = [apt_pkg.config["dir::bin::dpkg"]]
apt_pkg.config["dir::bin::dpkg"] = "/bin/true"
for lst in "dpkg::pre-invoke", "dpkg::pre-install-pkgs", "dpkg::post-invoke", "dpkg::post-install-pkgs":
backups[lst + "::"] = apt_pkg.config.value_list(lst)
apt_pkg.config.clear(lst)
try:
return self.doDistUpgrade()
finally:
for lst in backups:
for item in backups[lst]:
apt_pkg.config.set(lst, item)
def doDistUpgrade(self):
# add debug code only here
#apt_pkg.config.set("Debug::pkgDpkgPM", "1")
#apt_pkg.config.set("Debug::pkgOrderList", "1")
#apt_pkg.config.set("Debug::pkgPackageManager", "1")
# get the upgrade
currentRetry = 0
fprogress = self._view.getAcquireProgress()
iprogress = self._view.getInstallProgress(self.cache)
# retry the fetching in case of errors
maxRetries = self.config.getint("Network","MaxRetries")
if not self._partialUpgrade:
self.quirks.run("StartUpgrade")
# FIXME: take this into account for diskspace calculation
self._maybe_create_apt_btrfs_snapshot()
res = False
exception = None
while currentRetry < maxRetries:
try:
res = self.cache.commit(fprogress,iprogress)
logging.debug("cache.commit() returned %s" % res)
except SystemError as e:
logging.error("SystemError from cache.commit(): %s" % e)
exception = e
# if its a ordering bug we can cleanly revert to
# the previous release, no packages have been installed
# yet (LP: #328655, #356781)
if os.path.exists("/var/run/ubuntu-release-upgrader-apt-exception"):
with open("/var/run/ubuntu-release-upgrader-apt-exception") as f:
e = f.read()
logging.error("found exception: '%s'" % e)
# if its a ordering bug we can cleanly revert but we need to write
# a marker for the parent process to know its this kind of error
pre_configure_errors = [
"E:Internal Error, Could not perform immediate configuration",
"E:Couldn't configure pre-depend "]
for preconf_error in pre_configure_errors:
if str(e).startswith(preconf_error):
logging.debug("detected preconfigure error, restorting state")
self._enableAptCronJob()
# FIXME: strings are not good, but we are in string freeze
# currently
msg = _("Error during commit")
msg += "\n'%s'\n" % str(e)
msg += _("Restoring original system state")
self._view.error(_("Could not install the upgrades"), msg)
# abort() exits cleanly
self.abort()
# invoke the frontend now and show a error message
msg = _("The upgrade has aborted. Your system "
"could be in an unusable state. A recovery "
"will run now (dpkg --configure -a).")
if not self._partialUpgrade:
if not run_apport():
msg += _("\n\nPlease report this bug in a browser at "
"http://bugs.launchpad.net/ubuntu/+source/ubuntu-release-upgrader/+filebug "
"and attach the files in /var/log/dist-upgrade/ "
"to the bug report.\n"
"%s" % e)
self._view.error(_("Could not install the upgrades"), msg)
# installing the packages failed, can't be retried
cmd = ["/usr/bin/dpkg","--configure","-a"]
if os.environ.get("DEBIAN_FRONTEND") == "noninteractive":
cmd.append("--force-confold")
self._view.getTerminal().call(cmd)
self._enableAptCronJob()
return False
except IOError as e:
# fetch failed, will be retried
logging.error("IOError in cache.commit(): '%s'. Retrying (currentTry: %s)" % (e,currentRetry))
currentRetry += 1
exception = e
continue
except OSError as e:
logging.exception("cache.commit()")
# deal gracefully with:
# OSError: [Errno 12] Cannot allocate memory
exception = e
if e.errno == 12:
self._enableAptCronJob()
msg = _("Error during commit")
msg += "\n'%s'\n" % str(e)
msg += _("Restoring original system state")
self._view.error(_("Could not install the upgrades"), msg)
# abort() exits cleanly
self.abort()
# no exception, so all was fine, we are done
self._enableAptCronJob()
return True
# maximum fetch-retries reached without a successful commit
logging.error("giving up on fetching after maximum retries")
self._view.error(_("Could not download the upgrades"),
_("The upgrade has aborted. Please check your "\
"Internet connection or "\
"installation media and try again. "),
"%s" % exception)
# abort here because we want our sources.list back
self.abort()
def doPostUpgrade(self):
get_telemetry().add_stage('POSTUPGRADE')
# clean up downloaded packages
archivedir = os.path.dirname(
apt_pkg.config.find_dir("Dir::Cache::archives"))
for item in self.fetcher.items:
if os.path.dirname(os.path.abspath(item.destfile)) == archivedir:
try:
os.unlink(item.destfile)
except OSError:
pass
# reopen cache
self.openCache()
# run the quirks handler that does does like things adding
# missing groups or similar work arounds, only do it on real
# upgrades
self.quirks.run("PostUpgrade")
# check out what packages are cruft now
# use self.{foreign,obsolete}_pkgs here and see what changed
self._view.setStep(Step.CLEANUP)
self._view.updateStatus(_("Searching for obsolete software"))
now_obsolete = self.cache._getObsoletesPkgs()
now_foreign = self.cache._getForeignPkgs(self.origin, self.fromDist, self.toDist)
logging.debug("Obsolete: %s" % " ".join(sorted(now_obsolete)))
logging.debug("Foreign: %s" % " ".join(sorted(now_foreign)))
# now sanity check - if a base meta package is in the obsolete list now, that means
# that something went wrong (see #335154) badly with the network. this should never happen, but it did happen
# at least once so we add extra paranoia here
for pkg in self.config.getlist("Distro","BaseMetaPkgs"):
if pkg in now_obsolete:
logging.error("the BaseMetaPkg '%s' is in the obsolete list, something is wrong, ignoring the obsoletes" % pkg)
now_obsolete = set()
break
# check if we actually want obsolete removal
if not self.config.getWithDefault("Distro","RemoveObsoletes", True):
logging.debug("Skipping obsolete Removal")
return True
# now get the meta-pkg specific obsoletes and purges
for pkg in self.config.getlist("Distro","MetaPkgs"):
if pkg in self.cache and self.cache[pkg].is_installed:
self.forced_obsoletes.extend(self.config.getlist(pkg,"ForcedObsoletes"))
# now add the obsolete kernels to the forced obsoletes
self.forced_obsoletes.extend(self.cache.identifyObsoleteKernels())
logging.debug("forced_obsoletes: %s" % self.forced_obsoletes)
# mark packages that are now obsolete (and where not obsolete
# before) to be deleted. make sure to not delete any foreign
# (that is, not from ubuntu) packages
if self.useNetwork:
# we can only do the obsoletes calculation here if we use a
# network. otherwise after rewriting the sources.list everything
# that is not on the CD becomes obsolete (not-downloadable)
remove_candidates = now_obsolete - self.obsolete_pkgs
else:
# initial remove candidates when no network is used should
# be the demotions to make sure we don't leave potential
# unsupported software
remove_candidates = set([p.name for p in self.installed_demotions])
remove_candidates |= set(self.forced_obsoletes)
# now go for the unused dependencies
unused_dependencies = self.cache._getUnusedDependencies()
logging.debug("Unused dependencies: %s" %" ".join(unused_dependencies))
remove_candidates |= set(unused_dependencies)
# see if we actually have to do anything here
if not self.config.getWithDefault("Distro","RemoveObsoletes", True):
logging.debug("Skipping RemoveObsoletes as stated in the config")
remove_candidates = set()
logging.debug("remove_candidates: '%s'" % remove_candidates)
logging.debug("Start checking for obsolete pkgs")
progress = self._view.getOpCacheProgress()
for (i, pkgname) in enumerate(remove_candidates):
progress.update((i/float(len(remove_candidates)))*100.0)
if pkgname not in self.foreign_pkgs:
self._view.processEvents()
if not self.cache.tryMarkObsoleteForRemoval(pkgname, remove_candidates, self.foreign_pkgs):
logging.debug("'%s' scheduled for remove but not safe to remove, skipping", pkgname)
logging.debug("Finish checking for obsolete pkgs")
progress.done()
# get changes
changes = self.cache.get_changes()
logging.debug("The following packages are marked for removal: %s" % " ".join([pkg.name for pkg in changes]))
summary = _("Remove obsolete packages?")
actions = [_("_Keep"), _("_Remove")]
# FIXME Add an explanation about what obsolete packages are
#explanation = _("")
if (len(changes) > 0 and
self._view.confirmChanges(summary, changes, [], 0, actions, False)):
fprogress = self._view.getAcquireProgress()
iprogress = self._view.getInstallProgress(self.cache)
try:
self.cache.commit(fprogress,iprogress)
except (SystemError, IOError) as e:
logging.error("cache.commit() in doPostUpgrade() failed: %s" % e)
self._view.error(_("Error during commit"),
_("A problem occurred during the clean-up. "
"Please see the below message for more "
"information. "),
"%s" % e)
# run stuff after cleanup
self.quirks.run("PostCleanup")
# run the post upgrade scripts that can do fixup like xorg.conf
# fixes etc - only do on real upgrades
if not self._partialUpgrade:
self.runPostInstallScripts()
return True
def runPostInstallScripts(self):
"""
scripts that are run in any case after the distupgrade finished
whether or not it was successful
Cache lock is released during script runs in the event that the
PostInstallScripts require apt or dpkg changes.
"""
if self.cache:
self.cache.release_lock()
self.cache.unlock_lists_dir()
# now run the post-upgrade fixup scripts (if any)
for script in self.config.getlist("Distro","PostInstallScripts"):
if not os.path.exists(script):
logging.warning("PostInstallScript: '%s' not found" % script)
continue
logging.debug("Running PostInstallScript: '%s'" % script)
try:
# work around kde tmpfile problem where it eats permissions
check_and_fix_xbit(script)
self._view.getTerminal().call([script], hidden=True)
except Exception as e:
logging.error("got error from PostInstallScript %s (%s)" % (script, e))
if self.cache:
self.cache.get_lock()
def abort(self):
""" abort the upgrade, cleanup (as much as possible) """
logging.debug("abort called")
if hasattr(self, "sources"):
self.sources.restore_backup(self.sources_backup_ext)
if hasattr(self, "aptcdrom"):
self.aptcdrom.restore_backup(self.sources_backup_ext)
# generate a new cache
self._view.updateStatus(_("Restoring original system state"))
self._view.abort()
self.openCache()
sys.exit(1)
def _checkDep(self, depstr):
" check if a given depends can be satisfied "
for or_group in apt_pkg.parse_depends(depstr):
logging.debug("checking: '%s' " % or_group)
for dep in or_group:
depname = dep[0]
ver = dep[1]
oper = dep[2]
if depname not in self.cache:
logging.error("_checkDep: '%s' not in cache" % depname)
return False
inst = self.cache[depname]
instver = getattr(inst.installed, "version", None)
if (instver != None and
apt_pkg.check_dep(instver,oper,ver) == True):
return True
logging.error("depends '%s' is not satisfied" % depstr)
return False
def checkViewDepends(self):
" check if depends are satisfied "
logging.debug("checkViewDepends()")
res = True
# now check if anything from $foo-updates is required
depends = self.config.getlist("View","Depends")
depends.extend(self.config.getlist(self._view.__class__.__name__,
"Depends"))
for dep in depends:
logging.debug("depends: '%s'", dep)
res &= self._checkDep(dep)
if not res:
# FIXME: instead of error out, fetch and install it
# here
self._view.error(_("Required depends is not installed"),
_("The required dependency '%s' is not "
"installed. " % dep))
sys.exit(1)
return res
def _verifyBackports(self):
# run update (but ignore errors in case the countrymirror
# substitution goes wrong, real errors will be caught later
# when the cache is searched for the backport packages)
backportslist = self.config.getlist("PreRequists","Packages")
i=0
noCache = apt_pkg.config.find("Acquire::http::No-Cache","false")
maxRetries = self.config.getint("Network","MaxRetries")
while i < maxRetries:
self.doUpdate(showErrors=False)
self.openCache()
for pkgname in backportslist:
if pkgname not in self.cache:
logging.error("Can not find backport '%s'" % pkgname)
raise NoBackportsFoundException(pkgname)
if self._allBackportsAuthenticated(backportslist):
break
# FIXME: move this to some more generic place
logging.debug("setting a cache control header to turn off caching temporarily")
apt_pkg.config.set("Acquire::http::No-Cache","true")
i += 1
if i == maxRetries:
logging.error("pre-requists item is NOT trusted, giving up")
return False
apt_pkg.config.set("Acquire::http::No-Cache",noCache)
return True
def _allBackportsAuthenticated(self, backportslist):
# check if the user overwrote the check
if apt_pkg.config.find_b("APT::Get::AllowUnauthenticated",False) == True:
logging.warning("skip authentication check because of APT::Get::AllowUnauthenticated==true")
return True
try:
b = self.config.getboolean("Distro","AllowUnauthenticated")
if b:
return True
except NoOptionError:
pass
for pkgname in backportslist:
pkg = self.cache[pkgname]
if not pkg.candidate:
return False
for cand in pkg.candidate.origins:
if cand.trusted:
break
else:
return False
return True
def isMirror(self, uri):
""" check if uri is a known mirror """
# deal with username:password in a netloc
raw_uri = uri.rstrip("/")
scheme, netloc, path, query, fragment = urlsplit(raw_uri)
if "@" in netloc:
netloc = netloc.split("@")[1]
# construct new mirror url without the username/pw
uri = "%s://%s%s" % (scheme, netloc, path)
for mirror in self.valid_mirrors:
mirror = mirror.rstrip("/")
if is_mirror(mirror, uri):
return True
# deal with mirrors like
# deb http://localhost:9977/security.ubuntu.com/ubuntu intrepid-security main restricted
# both apt-debtorrent and apt-cacher use this (LP: #365537)
mirror_host_part = mirror.split("//")[1]
if uri.endswith(mirror_host_part):
logging.debug("found apt-cacher/apt-torrent style uri %s" % uri)
return True
return False
def isThirdPartyMirror(self, uri):
" check if uri is a whitelisted third-party mirror "
uri = uri.rstrip("/")
for mirror in self.valid_3p_mirrors:
mirror = mirror.rstrip("/")
if is_mirror(mirror, uri):
return True
return False
def _getPreReqMirrorLines(self, dumb=False):
" get sources.list snippet lines for the current mirror "
lines = ""
sources = SourcesList(matcherPath=".")
for entry in sources.list:
if entry.invalid or entry.disabled:
continue
if (entry.type == "deb" and
entry.disabled == False and
self.isMirror(entry.uri) and
"main" in entry.comps and
"%s-updates" % self.fromDist in entry.dist and
not entry.uri.startswith("http://security.ubuntu.com") and
not entry.uri.startswith("http://archive.ubuntu.com") ):
new_line = "deb %s %s-updates main\n" % (entry.uri, self.fromDist)
if not new_line in lines:
lines += new_line
# FIXME: do we really need "dumb" mode?
#if (dumb and entry.type == "deb" and
# "main" in entry.comps):
# lines += "deb %s %s-proposed main\n" % (entry.uri, self.fromDist)
return lines
def _addPreRequistsSourcesList(self, template, out, dumb=False):
" add prerequists based on template into the path outfile "
# go over the sources.list and try to find a valid mirror
# that we can use to add the backports dir
logging.debug("writing prerequists sources.list at: '%s' " % out)
mirrorlines = self._getPreReqMirrorLines(dumb)
with open(out, "w") as outfile, open(template) as infile:
for line in infile:
template = Template(line)
outline = template.safe_substitute(mirror=mirrorlines)
outfile.write(outline)
logging.debug("adding '%s' prerequists" % outline)
return True
def getRequiredBackports(self):
" download the backports specified in DistUpgrade.cfg "
logging.debug("getRequiredBackports()")
res = True
backportsdir = os.path.join(os.getcwd(),"backports")
if not os.path.exists(backportsdir):
os.mkdir(backportsdir)
backportslist = self.config.getlist("PreRequists","Packages")
# FIXME: this needs to be ported
# if we have them on the CD we are fine
if self.aptcdrom and not self.useNetwork:
logging.debug("Searching for pre-requists on CDROM")
p = os.path.join(self.aptcdrom.cdrompath,
"dists/stable/main/dist-upgrader/binary-%s/" % apt_pkg.config.find("APT::Architecture"))
found_pkgs = set()
for deb in glob.glob(p+"*_*.deb"):
logging.debug("found pre-req '%s' to '%s'" % (deb, backportsdir))
found_pkgs.add(os.path.basename(deb).split("_")[0])
# now check if we got all backports on the CD
if not set(backportslist).issubset(found_pkgs):
logging.error("Expected backports: '%s' but got '%s'" % (set(backportslist), found_pkgs))
return False
# now install them
self.cache.release_lock()
p = subprocess.Popen(
["/usr/bin/dpkg", "-i", ] + glob.glob(p+"*_*.deb"),
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
universal_newlines=True)
res = None
while res is None:
res = p.poll()
self._view.pulseProgress()
time.sleep(0.02)
self._view.pulseProgress(finished=True)
self.cache.get_lock()
logging.info("installing backport debs exit code '%s'" % res)
logging.debug("dpkg output:\n%s" % p.communicate()[0])
if res != 0:
return False
# and re-start itself when it done
return self.setupRequiredBackports()
# we support PreRequists/SourcesList-$arch sections here too
#
# logic for mirror finding works list this:
# - use the mirror template from the config, then: [done]
#
# - try to find known mirror (isMirror) and prepend it [done]
# - archive.ubuntu.com is always a fallback at the end [done]
#
# see if we find backports with that
# - if not, try guessing based on URI, Trust and Dist [done]
# in existing sources.list (internal mirror with no
# outside connection maybe)
#
# make sure to remove file on cancel
# FIXME: use the DistUpgradeFetcherCore logic
# in mirror_from_sources_list() here
# (and factor that code out into a helper)
conf_option = "SourcesList"
if self.config.has_option("PreRequists",conf_option+"-%s" % self.arch):
conf_option = conf_option + "-%s" % self.arch
prereq_template = self.config.get("PreRequists",conf_option)
if not os.path.exists(prereq_template):
logging.error("sourceslist not found '%s'" % prereq_template)
return False
outpath = os.path.join(apt_pkg.config.find_dir("Dir::Etc::sourceparts"), prereq_template)
outfile = os.path.join(apt_pkg.config.find_dir("Dir::Etc::sourceparts"), prereq_template)
self._addPreRequistsSourcesList(prereq_template, outfile)
try:
self._verifyBackports()
except NoBackportsFoundException as e:
self._addPreRequistsSourcesList(prereq_template, outfile, dumb=True)
try:
self._verifyBackports()
except NoBackportsFoundException as e:
logging.warning("no backport for '%s' found" % e)
return False
# FIXME: sanity check the origin (just for safety)
for pkgname in backportslist:
pkg = self.cache[pkgname]
# look for the right version (backport)
ver = self.cache._depcache.get_candidate_ver(pkg._pkg)
if not ver:
logging.error("No candidate for '%s'" % pkgname)
os.unlink(outpath)
return False
if ver.file_list == None:
logging.error("No ver.file_list for '%s'" % pkgname)
os.unlink(outpath)
return False
logging.debug("marking '%s' for install" % pkgname)
# mark install
pkg.mark_install(auto_inst=False, auto_fix=False)
# now get it
res = False
try:
res = self.cache.commit(self._view.getAcquireProgress(),
self._view.getInstallProgress(self.cache))
except IOError as e:
logging.error("fetch_archives returned '%s'" % e)
res = False
except SystemError as e:
logging.error("install_archives returned '%s'" % e)
res = False
if res == False:
logging.warning("_fetch_archives for backports returned False")
# all backports done, remove the pre-requirests.list file again
try:
os.unlink(outfile)
except Exception as e:
logging.error("failed to unlink pre-requists file: '%s'" % e)
return self.setupRequiredBackports()
# used by both cdrom/http fetcher
def setupRequiredBackports(self):
# ensure that the new release upgrader uses the latest python-apt
# from the backport path
os.environ["PYTHONPATH"] = "/usr/lib/release-upgrader-python-apt"
# copy log so that it gets not overwritten
logging.shutdown()
shutil.copy("/var/log/dist-upgrade/main.log",
"/var/log/dist-upgrade/main_pre_req.log")
# now exec self again
args = sys.argv + ["--have-prerequists"]
if self.useNetwork:
args.append("--with-network")
else:
args.append("--without-network")
logging.info("restarting upgrader")
#print("restarting upgrader to make use of the backports")
# work around kde being clever and removing the x bit
check_and_fix_xbit(sys.argv[0])
os.execve(sys.argv[0],args, os.environ)
# this is the core
def fullUpgrade(self):
# sanity check (check for ubuntu-desktop, brokenCache etc)
self._view.updateStatus(_("Checking package manager"))
self._view.setStep(Step.PREPARE)
if not self.prepare():
logging.error("self.prepare() failed")
if os.path.exists("/usr/bin/apport-bug"):
self._view.error(_("Preparing the upgrade failed"),
_("Preparing the system for the upgrade "
"failed so a bug reporting process is "
"being started."))
subprocess.Popen(["apport-bug", "ubuntu-release-upgrader-core"])
else:
self._view.error(_("Preparing the upgrade failed"),
_("Preparing the system for the upgrade "
"failed. To report a bug install apport "
"and then execute 'apport-bug "
"ubuntu-release-upgrader'."))
logging.error("Missing apport-bug, bug report not "
"autocreated")
self.abort()
# mvo: commented out for now, see #54234, this needs to be
# refactored to use a arch=any tarball
if (self.config.has_section("PreRequists") and
self.options and
self.options.havePrerequists == False):
logging.debug("need backports")
# get backported packages (if needed)
if not self.getRequiredBackports():
if os.path.exists("/usr/bin/apport-bug"):
self._view.error(_("Getting upgrade prerequisites failed"),
_("The system was unable to get the "
"prerequisites for the upgrade. "
"The upgrade will abort now and restore "
"the original system state.\n"
"\n"
"Additionally, a bug reporting process is "
"being started."))
subprocess.Popen(["apport-bug", "ubuntu-release-upgrader-core"])
else:
self._view.error(_("Getting upgrade prerequisites failed"),
_("The system was unable to get the "
"prerequisites for the upgrade. "
"The upgrade will abort now and restore "
"the original system state.\n"
"\n"
"To report a bug install apport and "
"then execute 'apport-bug "
"ubuntu-release-upgrader'."))
logging.error("Missing apport-bug, bug report not "
"autocreated")
self.abort()
# run a "apt-get update" now, its ok to ignore errors,
# because
# a) we disable any third party sources later
# b) we check if we have valid ubuntu sources later
# after we rewrite the sources.list and do a
# apt-get update there too
# because the (unmodified) sources.list of the user
# may contain bad/unreachable entries we run only
# with a single retry
self.doUpdate(showErrors=False, forceRetries=1)
self.openCache()
# do pre-upgrade stuff (calc list of obsolete pkgs etc)
if not self.doPostInitialUpdate():
self.abort()
# update sources.list
self._view.setStep(Step.MODIFY_SOURCES)
self._view.updateStatus(_("Updating repository information"))
if not self.updateSourcesList():
self.abort()
# add cdrom (if we have one)
if (self.aptcdrom and
not self.aptcdrom.add(self.sources_backup_ext)):
self._view.error(_("Failed to add the cdrom"),
_("Sorry, adding the cdrom was not successful."))
self.abort()
# then update the package index files
if not self.doUpdate():
self.abort()
# then open the cache (again)
self._view.updateStatus(_("Checking package manager"))
# if something fails here (e.g. locking the cache) we need to
# restore the system state (LP: #1052605)
self.openCache(restore_sources_list_on_fail=True)
# re-check server mode because we got new packages (it may happen
# that the system had no sources.list entries and therefore no
# desktop file information)
self.serverMode = self.cache.need_server_mode()
# do it here as we need to know if we are in server or client mode
self.quirks.ensure_recommends_are_installed_on_desktops()
# now check if we still have some key packages available/downloadable
# after the update - if not something went seriously wrong
# (this happend e.g. during the intrepid->jaunty upgrade for some
# users when de.archive.ubuntu.com was overloaded)
for pkg in self.config.getlist("Distro","BaseMetaPkgs"):
if (pkg not in self.cache or
not self.cache.anyVersionDownloadable(self.cache[pkg])):
# FIXME: we could offer to add default source entries here,
# but we need to be careful to not duplicate them
# (i.e. the error here could be something else than
# missing sources entries but network errors etc)
logging.error("No '%s' available/downloadable after sources.list rewrite+update" % pkg)
if pkg not in self.cache:
logging.error("'%s' was not in the cache" % pkg)
if not self.cache.anyVersionDownloadable(self.cache[pkg]):
logging.error("'%s' was not downloadable" % pkg)
self._view.error(_("Invalid package information"),
_("After updating your package "
"information, the essential package '%s' "
"could not be located. This may be "
"because you have no official mirrors "
"listed in your software sources, or "
"because of excessive load on the mirror "
"you are using. See /etc/apt/sources.list "
"for the current list of configured "
"software sources."
"\n"
"In the case of an overloaded mirror, you "
"may want to try the upgrade again later.")
% pkg)
if os.path.exists("/usr/bin/apport-bug"):
subprocess.Popen(["apport-bug", "ubuntu-release-upgrader-core"])
else:
logging.error("Missing apport-bug, bug report not "
"autocreated")
self.abort()
# calc the dist-upgrade and see if the removals are ok/expected
# do the dist-upgrade
self._view.updateStatus(_("Calculating the changes"))
if not self.askDistUpgrade():
self.abort()
self._inhibitIdle()
# fetch the stuff
self._view.setStep(Step.FETCH)
self._view.updateStatus(_("Fetching"))
if not self.doDistUpgradeFetching():
self._enableAptCronJob()
self.abort()
# simulate an upgrade
self._view.setStep(Step.INSTALL)
self._view.updateStatus(_("Upgrading"))
if not self.doDistUpgradeSimulation():
self._view.error(_("Upgrade infeasible"),
_("The upgrade could not be completed, there "
"were errors during the upgrade "
"process."))
self.abort()
# Just upgrade libc6 first
self.cache.clear()
libc6_possible = False
try:
self.cache["libc6"].mark_install()
libc6_possible = True
except SystemError as e:
if "pkgProblemResolver" in str(e):
logging.debug("Unable to mark libc6 alone for install.")
pass
if libc6_possible:
self._view.setStep(Step.INSTALL)
self._view.updateStatus(_("Upgrading"))
if not self.doDistUpgrade():
# don't abort here, because it would restore the sources.list
self._view.information(_("Upgrade incomplete"),
_("The upgrade has partially completed but there "
"were errors during the upgrade "
"process."))
# do not abort because we are part of the way through the process
sys.exit(1)
# Reopen ask above
self.openCache(restore_sources_list_on_fail=True)
self.serverMode = self.cache.need_server_mode()
self.quirks.ensure_recommends_are_installed_on_desktops()
self._view.updateStatus(_("Calculating the changes"))
if not self.calcDistUpgrade():
self.abort()
# now do the upgrade
self._view.setStep(Step.INSTALL)
self._view.updateStatus(_("Upgrading"))
if not self.doDistUpgrade():
# run the post install scripts (for stuff like UUID conversion)
self.runPostInstallScripts()
# don't abort here, because it would restore the sources.list
self._view.information(_("Upgrade complete"),
_("The upgrade has completed but there "
"were errors during the upgrade "
"process."))
# do not abort because we are part of the way through the process
sys.exit(1)
# do post-upgrade stuff
self.doPostUpgrade()
# comment out cdrom source
if self.aptcdrom:
self.aptcdrom.comment_out_cdrom_entry()
# remove upgrade-available notice
if os.path.exists("/var/lib/ubuntu-release-upgrader/release-upgrade-available"):
os.unlink("/var/lib/ubuntu-release-upgrader/release-upgrade-available")
# done, ask for reboot
self._view.setStep(Step.REBOOT)
self._view.updateStatus(_("System upgrade is complete."))
get_telemetry().done()
# FIXME should we look into /var/run/reboot-required here?
if (not inside_chroot() and
self._view.confirmRestart()):
subprocess.Popen("/sbin/reboot")
sys.exit(0)
return True
def run(self):
self._view.processEvents()
return self.fullUpgrade()
def doPartialUpgrade(self):
" partial upgrade mode, useful for repairing "
self._view.setStep(Step.PREPARE)
self._view.hideStep(Step.MODIFY_SOURCES)
self._view.hideStep(Step.REBOOT)
self._partialUpgrade = True
self.prepare()
if not self.doPostInitialUpdate():
return False
if not self.askDistUpgrade():
return False
self._view.setStep(Step.FETCH)
self._view.updateStatus(_("Fetching"))
if not self.doDistUpgradeFetching():
return False
self._view.setStep(Step.INSTALL)
self._view.updateStatus(_("Upgrading"))
if not self.doDistUpgrade():
self._view.information(_("Upgrade complete"),
_("The upgrade has completed but there "
"were errors during the upgrade "
"process."))
return False
if not self.doPostUpgrade():
self._view.information(_("Upgrade complete"),
_("The upgrade has completed but there "
"were errors during the upgrade "
"process."))
return False
if os.path.exists(REBOOT_REQUIRED_FILE):
# we can not talk to session management here, we run as root
if self._view.confirmRestart():
subprocess.Popen("/sbin/reboot")
else:
self._view.information(_("Upgrade complete"),
_("The partial upgrade was completed."))
return True
def _inhibitIdle(self):
if os.path.exists("/usr/bin/gnome-session-inhibit"):
self._uid = os.environ.get('SUDO_UID', '')
if not self._uid:
self._uid = os.environ.get('PKEXEC_UID', '')
if not self._uid:
logging.debug("failed to determine user upgrading")
logging.error("failed to inhibit gnome-session idle")
return
self._getUserEnv()
if not self._user_env:
return
#seteuid so dbus user session can be accessed
os.seteuid(int(self._uid))
logging.debug("inhibit gnome-session idle")
try:
xdg_desktop = self._user_env.get("XDG_CURRENT_DESKTOP", "")
if not xdg_desktop:
logging.debug("failed to find XDG_CURRENT_DESKTOP")
logging.error("failed to inhibit gnome-session idle")
return
subprocess.Popen(["gnome-session-inhibit", "--inhibit",
"idle", "--inhibit-only"],
env=self._user_env)
self._view.information(_("Lock screen disabled"),
_("Your lock screen has been "
"disabled and will remain "
"disabled until you reboot."))
except (OSError, ValueError):
logging.exception("failed to inhibit gnome-session idle")
os.seteuid(os.getuid())
def _getUserEnv(self):
try:
pid = subprocess.check_output(["pgrep", "-u", self._uid,
"gnome-session"])
pid = pid.decode().split('\n')[0]
with open('/proc/' + pid + '/environ', 'r') as f:
data = f.read().split('\x00')
for line in data:
if len(line):
env = line.split('=', 1)
self._user_env[env[0]] = env[1]
except subprocess.CalledProcessError as e:
if e.returncode == 1:
logging.debug("gnome-session not running for user")
else:
logging.exception("failed to read user env")
if __name__ == "__main__":
from .DistUpgradeViewText import DistUpgradeViewText
logging.basicConfig(level=logging.DEBUG)
v = DistUpgradeViewText()
dc = DistUpgradeController(v)
#dc.openCache()
dc._disableAptCronJob()
dc._enableAptCronJob()
#dc._addRelatimeToFstab()
#dc.prepare()
#dc.askDistUpgrade()
#dc._checkFreeSpace()
#dc._rewriteFstab()
#dc._checkAdminGroup()
#dc._rewriteAptPeriodic(2)
./DistUpgradeFetcher.py 0000644 0000000 0000000 00000013516 13476257563 014027 0 ustar root root # DistUpgradeFetcher.py
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
#
# Copyright (c) 2006 Canonical
#
# Author: Michael Vogt
#
# 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 gi.repository import Gtk, Gdk
from .ReleaseNotesViewer import ReleaseNotesViewer
from .utils import error
from .DistUpgradeFetcherCore import DistUpgradeFetcherCore
from .SimpleGtk3builderApp import SimpleGtkbuilderApp
from gettext import gettext as _
from urllib.request import urlopen
from urllib.error import HTTPError
import os
import socket
class DistUpgradeFetcherGtk(DistUpgradeFetcherCore):
def __init__(self, new_dist, progress, parent, datadir):
DistUpgradeFetcherCore.__init__(self, new_dist, progress)
uifile = os.path.join(datadir, "gtkbuilder", "ReleaseNotes.ui")
self.widgets = SimpleGtkbuilderApp(uifile, "ubuntu-release-upgrader")
self.window_main = parent
def error(self, summary, message):
return error(self.window_main, summary, message)
def runDistUpgrader(self):
os.execv(self.script, [self.script] + self.run_options)
def showReleaseNotes(self):
# first try showing the webkit version, this may fail (return None
# because e.g. there is no webkit installed)
res = self._try_show_release_notes_webkit()
if res is not None:
return res
else:
# fallback to text
return self._try_show_release_notes_textview()
def _try_show_release_notes_webkit(self):
if self.new_dist.releaseNotesHtmlUri is not None:
try:
from .ReleaseNotesViewerWebkit import ReleaseNotesViewerWebkit
webkit_release_notes = ReleaseNotesViewerWebkit(
self.new_dist.releaseNotesHtmlUri)
webkit_release_notes.show()
self.widgets.scrolled_notes.add(webkit_release_notes)
res = self.widgets.dialog_release_notes.run()
self.widgets.dialog_release_notes.hide()
if res == Gtk.ResponseType.OK:
return True
return False
except ImportError:
pass
return None
def _try_show_release_notes_textview(self):
# FIXME: care about i18n! (append -$lang or something)
if self.new_dist.releaseNotesURI is not None:
uri = self._expandUri(self.new_dist.releaseNotesURI)
if self.window_main:
self.window_main.set_sensitive(False)
self.window_main.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.WATCH))
while Gtk.events_pending():
Gtk.main_iteration()
# download/display the release notes
# FIXME: add some progress reporting here
res = Gtk.ResponseType.CANCEL
timeout = socket.getdefaulttimeout()
try:
socket.setdefaulttimeout(5)
release_notes = urlopen(uri)
notes = release_notes.read().decode("UTF-8", "replace")
textview_release_notes = ReleaseNotesViewer(notes)
textview_release_notes.show()
self.widgets.scrolled_notes.add(textview_release_notes)
release_widget = self.widgets.dialog_release_notes
release_widget.set_transient_for(self.window_main)
res = self.widgets.dialog_release_notes.run()
self.widgets.dialog_release_notes.hide()
except HTTPError:
primary = "%s" % \
_("Could not find the release notes")
secondary = _("The server may be overloaded. ")
dialog = Gtk.MessageDialog(self.window_main,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
Gtk.ButtonsType.CLOSE, "")
dialog.set_title("")
dialog.set_markup(primary)
dialog.format_secondary_text(secondary)
dialog.run()
dialog.destroy()
except IOError:
primary = "%s" % \
_("Could not download the release notes")
secondary = _("Please check your internet connection.")
dialog = Gtk.MessageDialog(self.window_main,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
Gtk.ButtonsType.CLOSE, "")
dialog.set_title("")
dialog.set_markup(primary)
dialog.format_secondary_text(secondary)
dialog.run()
dialog.destroy()
socket.setdefaulttimeout(timeout)
if self.window_main:
self.window_main.set_sensitive(True)
self.window_main.get_window().set_cursor(None)
# user clicked cancel
if res == Gtk.ResponseType.OK:
return True
return False
./DistUpgradeFetcherCore.py 0000644 0000000 0000000 00000026320 13476257563 014635 0 ustar root root # DistUpgradeFetcherCore.py
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
#
# Copyright (c) 2006 Canonical
#
# Author: Michael Vogt
#
# 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 string import Template
import os
import apt_pkg
import logging
import tarfile
import tempfile
import shutil
import sys
import subprocess
from gettext import gettext as _
from aptsources.sourceslist import SourcesList
from .utils import get_dist, url_downloadable, country_mirror
class DistUpgradeFetcherCore(object):
" base class (without GUI) for the upgrade fetcher "
DEFAULT_MIRROR = "http://archive.ubuntu.com/ubuntu"
DEFAULT_COMPONENT = "main"
DEBUG = "DEBUG_UPDATE_MANAGER" in os.environ
def __init__(self, new_dist, progress):
self.new_dist = new_dist
self.current_dist_name = get_dist()
self._progress = progress
# options to pass to the release upgrader when it is run
self.run_options = []
def _debug(self, msg):
" helper to show debug information "
if self.DEBUG:
sys.stderr.write(msg + "\n")
def showReleaseNotes(self):
return True
def error(self, summary, message):
""" dummy implementation for error display, should be overwriten
by subclasses that want to more fancy method
"""
print(summary)
print(message)
return False
def authenticate(self):
if self.new_dist.upgradeToolSig:
f = self.tmpdir + "/" + os.path.basename(self.new_dist.upgradeTool)
sig = self.tmpdir + "/" + os.path.basename(
self.new_dist.upgradeToolSig)
print(_("authenticate '%(file)s' against '%(signature)s' ") % {
'file': os.path.basename(f),
'signature': os.path.basename(sig)})
if self.gpgauthenticate(f, sig):
return True
return False
def gpgauthenticate(self, file, signature,
keyring=None):
""" authenticated a file against a given signature, if no keyring
is given use the apt default keyring
"""
gpg = ["apt-key"]
if keyring:
gpg += ["--keyring", keyring]
gpg += ["verify", signature, file]
ret = subprocess.call(gpg, stderr=subprocess.PIPE)
return ret == 0
def extractDistUpgrader(self):
# extract the tarball
fname = os.path.join(self.tmpdir, os.path.basename(self.uri))
print(_("extracting '%s'") % os.path.basename(fname))
if not os.path.exists(fname):
return False
try:
tar = tarfile.open(self.tmpdir + "/" +
os.path.basename(self.uri), "r")
for tarinfo in tar:
tar.extract(tarinfo)
tar.close()
except tarfile.ReadError as e:
logging.error("failed to open tarfile (%s)" % e)
return False
return True
def verifyDistUprader(self):
# FIXME: check an internal dependency file to make sure
# that the script will run correctly
# see if we have a script file that we can run
self.script = script = "%s/%s" % (self.tmpdir, self.new_dist.name)
if not os.path.exists(script):
return self.error(_("Could not run the upgrade tool"),
_("Could not run the upgrade tool") + ". " +
_("This is most likely a bug in the upgrade "
"tool. Please report it as a bug using the "
"command 'ubuntu-bug "
"ubuntu-release-upgrader-core'."))
return True
def mirror_from_sources_list(self, uri, default_uri):
"""
try to figure what the mirror is from current sources.list
do this by looing for matching DEFAULT_COMPONENT, current dist
in sources.list and then doing a http HEAD/ftp size request
to see if the uri is available on this server
"""
self._debug("mirror_from_sources_list: %s" % self.current_dist_name)
sources = SourcesList(withMatcher=False)
seen = set()
for e in sources.list:
if e.disabled or e.invalid or not e.type == "deb":
continue
# check if we probed this mirror already
if e.uri in seen:
continue
# we are using the main mirror already, so we are fine
if (e.uri.startswith(default_uri) and
e.dist == self.current_dist_name and
self.DEFAULT_COMPONENT in e.comps):
return uri
elif (e.dist == self.current_dist_name and "main" in e.comps):
mirror_uri = e.uri + uri[len(default_uri):]
if url_downloadable(mirror_uri, self._debug):
return mirror_uri
seen.add(e.uri)
self._debug("no mirror found")
return ""
def _expandUri(self, uri):
"""
expand the uri so that it uses a mirror if the url starts
with a well known string (like archive.ubuntu.com)
"""
# try to guess the mirror from the sources.list
if uri.startswith(self.DEFAULT_MIRROR):
self._debug("trying to find suitable mirror")
new_uri = self.mirror_from_sources_list(uri, self.DEFAULT_MIRROR)
if new_uri:
return new_uri
# if that fails, use old method
uri_template = Template(uri)
m = country_mirror()
new_uri = uri_template.safe_substitute(countrymirror=m)
# be paranoid and check if the given uri is really downloadable
try:
if not url_downloadable(new_uri, self._debug):
raise Exception("failed to download %s" % new_uri)
except Exception as e:
self._debug("url '%s' could not be downloaded" % e)
# else fallback to main server
new_uri = uri_template.safe_substitute(countrymirror='')
return new_uri
def fetchDistUpgrader(self):
" download the tarball with the upgrade script "
tmpdir = tempfile.mkdtemp(prefix="ubuntu-release-upgrader-")
self.tmpdir = tmpdir
os.chdir(tmpdir)
logging.debug("using tmpdir: '%s'" % tmpdir)
# turn debugging on here (if required)
if self.DEBUG > 0:
apt_pkg.config.set("Debug::Acquire::http", "1")
apt_pkg.config.set("Debug::Acquire::ftp", "1")
#os.listdir(tmpdir)
fetcher = apt_pkg.Acquire(self._progress)
if self.new_dist.upgradeToolSig is not None:
uri = self._expandUri(self.new_dist.upgradeToolSig)
af1 = apt_pkg.AcquireFile(fetcher,
uri,
descr=_("Upgrade tool signature"))
# reference it here to shut pyflakes up
af1
if self.new_dist.upgradeTool is not None:
self.uri = self._expandUri(self.new_dist.upgradeTool)
af2 = apt_pkg.AcquireFile(fetcher,
self.uri,
descr=_("Upgrade tool"))
# reference it here to shut pyflakes up
af2
result = fetcher.run()
if result != fetcher.RESULT_CONTINUE:
logging.warning("fetch result != continue (%s)" % result)
return False
# check that both files are really there and non-null
for f in [os.path.basename(self.new_dist.upgradeToolSig),
os.path.basename(self.new_dist.upgradeTool)]:
if not (os.path.exists(f) and os.path.getsize(f) > 0):
logging.warning("file '%s' missing" % f)
return False
return True
return False
def runDistUpgrader(self):
args = [self.script] + self.run_options
if os.getuid() != 0:
os.execv("/usr/bin/sudo", ["sudo", "-E"] + args)
else:
os.execv(self.script, args)
def cleanup(self):
# cleanup
os.chdir("..")
# del tmpdir
shutil.rmtree(self.tmpdir)
def run(self):
# see if we have release notes
if not self.showReleaseNotes():
return
if not self.fetchDistUpgrader():
self.error(_("Failed to fetch"),
_("Fetching the upgrade failed. There may be a network "
"problem. "))
return
if not self.authenticate():
self.error(_("Authentication failed"),
_("Authenticating the upgrade failed. There may be a "
"problem with the network or with the server. "))
self.cleanup()
return
if not self.extractDistUpgrader():
self.error(_("Failed to extract"),
_("Extracting the upgrade failed. There may be a "
"problem with the network or with the server. "))
return
if not self.verifyDistUprader():
self.error(_("Verification failed"),
_("Verifying the upgrade failed. There may be a "
"problem with the network or with the server. "))
self.cleanup()
return
try:
# check if we can execute, if we run it via sudo we will
# not know otherwise, pkexec will not raise a exception
if not os.access(self.script, os.X_OK):
ex = OSError("Can not execute '%s'" % self.script)
ex.errno = 13
raise ex
self.runDistUpgrader()
except OSError as e:
if e.errno == 13:
self.error(_("Can not run the upgrade"),
_("This usually is caused by a system where /tmp "
"is mounted noexec. Please remount without "
"noexec and run the upgrade again."))
return False
else:
self.error(_("Can not run the upgrade"),
_("The error message is '%s'.") % e.strerror)
return True
if __name__ == "__main__":
d = DistUpgradeFetcherCore(None, None)
# print(d.authenticate('/tmp/Release','/tmp/Release.gpg'))
print("got mirror: '%s'" %
d.mirror_from_sources_list(
"http://archive.ubuntu.com/ubuntu/dists/intrepid-proposed/main/"
"dist-upgrader-all/0.93.34/intrepid.tar.gz",
"http://archive.ubuntu.com/ubuntu"))
./DistUpgradeFetcherKDE.py 0000644 0000000 0000000 00000023336 13476257563 014354 0 ustar root root # DistUpgradeFetcherKDE.py
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
#
# Copyright (c) 2008 Canonical Ltd
# Copyright (c) 2014-2018 Harald Sitter
#
# Author: Jonathan Riddell
#
# 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, see .
try:
# 14.04 has a broken pyqt5, so don't even try to import it and require
# pyqt4.
# In 14.04 various signals in pyqt5 can not be connected because it thinks
# the signal does not exist or has an incompatible signature. Since this
# potentially renders the GUI entirely broken and pyqt5 was not actively
# used back then it is fair to simply require qt4 on trusty systems.
from .utils import get_dist
if get_dist() == 'trusty':
raise ImportError
from PyQt5 import uic
from PyQt5.QtCore import QTranslator, PYQT_VERSION, \
QLocale
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox, \
QApplication
except ImportError:
from PyKDE4.kdeui import KIcon, KMessageBox, KStandardGuiItem
from PyQt4.QtGui import QDialog, QDialogButtonBox, QApplication, QIcon
from PyQt4.QtCore import PYQT_VERSION
from PyQt4 import uic
import apt_pkg
from DistUpgrade.DistUpgradeFetcherCore import DistUpgradeFetcherCore
from gettext import gettext as _
from urllib.request import urlopen
from urllib.error import HTTPError
import os
import apt
from .QUrlOpener import QUrlOpener
# TODO: uifile resolution is an utter mess and should be revised globally for
# both the fetcher and the upgrader GUI.
# TODO: make this a singleton
# We have no globally constructed QApplication available so we need to
# make sure that one is created when needed. Since from a module POV
# this can be happening in any order of the two classes this function takes
# care of it for the classes, the classes only hold a ref to the qapp returned
# to prevent it from getting GC'd, so in essence this is a singleton scoped to
# the longest lifetime of an instance from the Qt GUI. Since the lifetime is
# pretty much equal to the process' one we might as well singleton up.
def _ensureQApplication():
if not QApplication.instance():
# Force environment to make sure Qt uses suitable theming and UX.
os.environ["QT_PLATFORM_PLUGIN"] = "kde"
# For above settings to apply automatically we need to indicate that we
# are inside a full KDE session.
os.environ["KDE_FULL_SESSION"] = "TRUE"
# We also need to indicate version as otherwise KDElibs3 compatibility
# might kick in such as in QIconLoader.cpp:QString fallbackTheme.
os.environ["KDE_SESSION_VERSION"] = "5"
# Pretty much all of the above but for Qt5
os.environ["QT_QPA_PLATFORMTHEME"] = "kde"
app = QApplication(["ubuntu-release-upgrader"])
# Try to load default Qt translations so we don't have to worry about
# QStandardButton translations.
# FIXME: make sure we dep on l10n
translator = QTranslator(app)
if type(PYQT_VERSION) == int:
translator.load(QLocale.system(), 'qt', '_',
'/usr/share/qt5/translations')
else:
translator.load(QLocale.system(), 'qt', '_',
'/usr/share/qt4/translations')
app.installTranslator(translator)
return app
return QApplication.instance()
# Qt 5 vs. KDELibs4 compat functions
def _warning(text):
if type(PYQT_VERSION) == int:
QMessageBox.warning(None, "", text)
else:
KMessageBox.sorry(None, text, "")
def _icon(name):
if type(PYQT_VERSION) == int:
return QIcon.fromTheme(name)
else:
return KIcon(name)
class DistUpgradeFetcherKDE(DistUpgradeFetcherCore):
def __init__(self, new_dist, progress, parent, datadir):
DistUpgradeFetcherCore.__init__(self, new_dist, progress)
self.app = _ensureQApplication()
self.app.setWindowIcon(_icon("system-software-update"))
self.datadir = datadir
QUrlOpener().setupUrlHandles()
QApplication.processEvents()
def error(self, summary, message):
if type(PYQT_VERSION) == int:
QMessageBox.critical(None, summary, message)
else:
KMessageBox.sorry(None, message, summary)
def runDistUpgrader(self):
# now run it with sudo
if os.getuid() != 0:
os.execv("/usr/bin/pkexec",
["pkexec",
self.script + " --frontend=DistUpgradeViewKDE"])
else:
os.execv(self.script,
[self.script, "--frontend=DistUpgradeViewKDE"] +
self.run_options)
def showReleaseNotes(self):
# FIXME: care about i18n! (append -$lang or something)
# TODO: ^ what is this supposed to mean?
self.dialog = QDialog()
uic.loadUi(self.datadir + "/dialog_release_notes.ui", self.dialog)
upgradeButton = self.dialog.buttonBox.button(QDialogButtonBox.Ok)
upgradeButton.setText(_("&Upgrade"))
upgradeButton.setIcon(_icon("dialog-ok"))
cancelButton = self.dialog.buttonBox.button(QDialogButtonBox.Cancel)
cancelButton.setText(_("&Cancel"))
cancelButton.setIcon(_icon("dialog-cancel"))
self.dialog.setWindowTitle(_("Release Notes"))
self.dialog.show()
if self.new_dist.releaseNotesHtmlUri is not None:
uri = self._expandUri(self.new_dist.releaseNotesHtmlUri)
# download/display the release notes
# TODO: add some progress reporting here
result = None
try:
release_notes = urlopen(uri)
notes = release_notes.read().decode("UTF-8", "replace")
self.dialog.scrolled_notes.setText(notes)
result = self.dialog.exec_()
except HTTPError:
primary = "%s" % \
_("Could not find the release notes")
secondary = _("The server may be overloaded. ")
_warning(primary + "
" + secondary)
except IOError:
primary = "%s" % \
_("Could not download the release notes")
secondary = _("Please check your internet connection.")
_warning(primary + "
" + secondary)
# user clicked cancel
if result == QDialog.Accepted:
return True
return False
class KDEAcquireProgressAdapter(apt.progress.base.AcquireProgress):
def __init__(self, parent, datadir, label):
self.app = _ensureQApplication()
self.dialog = QDialog()
uiFile = os.path.join(datadir, "fetch-progress.ui")
uic.loadUi(uiFile, self.dialog)
self.dialog.setWindowTitle(_("Upgrade"))
self.dialog.installingLabel.setText(label)
self.dialog.buttonBox.rejected.connect(self.abort)
# This variable is used as return value for AcquireProgress pulses.
# Setting it to False will abort the Acquire and consequently the
# entire fetcher.
self._continue = True
QApplication.processEvents()
def abort(self):
self._continue = False
def start(self):
self.dialog.installingLabel.setText(
_("Downloading additional package files..."))
self.dialog.installationProgress.setValue(0)
self.dialog.show()
def stop(self):
self.dialog.hide()
def pulse(self, owner):
apt.progress.base.AcquireProgress.pulse(self, owner)
self.dialog.installationProgress.setValue(
(self.current_bytes + self.current_items) /
float(self.total_bytes + self.total_items) * 100)
current_item = self.current_items + 1
if current_item > self.total_items:
current_item = self.total_items
label_text = _("Downloading additional package files...")
if self.current_cps > 0:
label_text += _("File %s of %s at %sB/s") % (
self.current_items, self.total_items,
apt_pkg.size_to_str(self.current_cps))
else:
label_text += _("File %s of %s") % (
self.current_items, self.total_items)
self.dialog.installingLabel.setText(label_text)
QApplication.processEvents()
return self._continue
def mediaChange(self, medium, drive):
msg = _("Please insert '%s' into the drive '%s'") % (medium, drive)
if type(PYQT_VERSION) == int:
change = QMessageBox.question(None, _("Media Change"), msg,
QMessageBox.Ok, QMessageBox.Cancel)
if change == QMessageBox.Ok:
return True
else:
change = KMessageBox.questionYesNo(None, _("Media Change"),
_("Media Change") + "
" +
msg, KStandardGuiItem.ok(),
KStandardGuiItem.cancel())
if change == KMessageBox.Yes:
return True
return False
./DistUpgradeFetcherSelf.py 0000644 0000000 0000000 00000004020 13475213717 014617 0 ustar root root # DistUpgradeFetcherSelf.py
#
# Copyright (c) 2007-2012 Canonical
#
# Author: Michael Vogt
#
# 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
import logging
import shutil
from .DistUpgradeFetcherCore import DistUpgradeFetcherCore
class DistUpgradeFetcherSelf(DistUpgradeFetcherCore):
def __init__(self, new_dist, progress, options, view):
DistUpgradeFetcherCore.__init__(self, new_dist, progress)
self.view = view
# user chose to use the network, otherwise it would not be
# possible to download self
self.run_options += ["--with-network"]
# make sure to run self with proper options
if options.cdromPath is not None:
self.run_options += ["--cdrom=%s" % options.cdromPath]
if options.frontend is not None:
self.run_options += ["--frontend=%s" % options.frontend]
def error(self, summary, message):
return self.view.error(summary, message)
def runDistUpgrader(self):
" overwrite to ensure that the log is copied "
# copy log so it isn't overwritten
logging.info("runDistUpgrader() called, re-exec self")
logging.shutdown()
shutil.copy("/var/log/dist-upgrade/main.log",
"/var/log/dist-upgrade/main_update_self.log")
# re-exec self
DistUpgradeFetcherCore.runDistUpgrader(self)
./DistUpgradeGettext.py 0000644 0000000 0000000 00000005743 13475213717 014066 0 ustar root root # DistUpgradeGettext.py - safe wrapper around gettext
#
# Copyright (c) 2008 Canonical
#
# Author: Michael Vogt
#
# 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
import logging
import gettext as mygettext
_gettext_method = "gettext"
_ngettext_method = "ngettext"
def _verify(message, translated):
"""
helper that verifies that the message and the translated
message have the same number (and type) of % args
"""
arguments_in_message = message.count("%") - message.count("\%")
arguments_in_translation = translated.count("%") - translated.count("\%")
return arguments_in_message == arguments_in_translation
_translation_singleton = None
def _translation():
"""Return a suitable gettext.*Translations instance."""
global _translation_singleton
if _translation_singleton is None:
domain = mygettext.textdomain()
_translation_singleton = mygettext.translation(
domain, mygettext.bindtextdomain(domain), fallback=True)
return _translation_singleton
def unicode_gettext(translation, message):
return getattr(translation, _gettext_method)(message)
def unicode_ngettext(translation, singular, plural, n):
return getattr(translation, _ngettext_method)(singular, plural, n)
def gettext(message):
"""
version of gettext that logs errors but does not crash on incorrect
number of arguments
"""
if message == "":
return ""
translated_msg = unicode_gettext(_translation(), message)
if not _verify(message, translated_msg):
logging.error(
"incorrect translation for message '%s' to '%s' "
"(wrong number of arguments)" % (message, translated_msg))
return message
return translated_msg
def ngettext(msgid1, msgid2, n):
"""
version of ngettext that logs errors but does not crash on incorrect
number of arguments
"""
translated_msg = unicode_ngettext(_translation(), msgid1, msgid2, n)
if not _verify(msgid1, translated_msg):
logging.error(
"incorrect translation for ngettext message "
"'%s' plural: '%s' to '%s' (wrong number of arguments)" % (
msgid1, msgid2, translated_msg))
# dumb fallback to not crash
if n == 1:
return msgid1
return msgid2
return translated_msg
./DistUpgradeMain.py 0000644 0000000 0000000 00000022145 13476257563 013331 0 ustar root root # DistUpgradeMain.py
#
# Copyright (c) 2004-2008 Canonical
#
# Author: Michael Vogt
#
# 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
import apt
import atexit
import gettext
import glob
import logging
import os
import shutil
import subprocess
import sys
from datetime import datetime
from optparse import OptionParser
from gettext import gettext as _
# dirs that the packages will touch, this is needed for the sanity check
# before the upgrade
SYSTEM_DIRS = ["/bin",
"/boot",
"/etc",
"/initrd",
"/lib",
"/lib32", # ???
"/lib64", # ???
"/sbin",
"/usr",
"/var",
]
from .DistUpgradeConfigParser import DistUpgradeConfig
def do_commandline():
" setup option parser and parse the commandline "
parser = OptionParser()
parser.add_option("-c", "--cdrom", dest="cdromPath", default=None,
help=_("Use the given path to search for a cdrom with upgradable packages"))
parser.add_option("--have-prerequists", dest="havePrerequists",
action="store_true", default=False)
parser.add_option("--with-network", dest="withNetwork",action="store_true")
parser.add_option("--without-network", dest="withNetwork",action="store_false")
parser.add_option("--frontend", dest="frontend",default=None,
help=_("Use frontend. Currently available: \n"\
"DistUpgradeViewText, DistUpgradeViewGtk, DistUpgradeViewKDE"))
parser.add_option("--mode", dest="mode",default="desktop",
help=_("*DEPRECATED* this option will be ignored"))
parser.add_option("--partial", dest="partial", default=False,
action="store_true",
help=_("Perform a partial upgrade only (no sources.list rewriting)"))
parser.add_option("--disable-gnu-screen", action="store_true",
default=False,
help=_("Disable GNU screen support"))
parser.add_option("--datadir", dest="datadir", default=".",
help=_("Set datadir"))
parser.add_option("--devel-release", action="store_true",
dest="devel_release", default=False,
help=_("Upgrade to the development release"))
return parser.parse_args()
def setup_logging(options, config):
" setup the logging "
logdir = config.getWithDefault("Files","LogDir","/var/log/dist-upgrade/")
if not os.path.exists(logdir):
os.mkdir(logdir)
# check if logs exists and move logs into place
if glob.glob(logdir+"/*.log"):
now = datetime.now()
backup_dir = logdir+"/%04i%02i%02i-%02i%02i" % (now.year,now.month,now.day,now.hour,now.minute)
if not os.path.exists(backup_dir):
os.mkdir(backup_dir)
for f in glob.glob(logdir+"/*.log"):
shutil.move(f, os.path.join(backup_dir,os.path.basename(f)))
fname = os.path.join(logdir,"main.log")
# do not overwrite the default main.log
if options.partial:
fname += ".partial"
with open(fname, "a"):
pass
logging.basicConfig(level=logging.DEBUG,
filename=fname,
format='%(asctime)s %(levelname)s %(message)s',
filemode='w')
# log what config files are in use here to detect user
# changes
logging.info("Using config files '%s'" % config.config_files)
logging.info("uname information: '%s'" % " ".join(os.uname()))
cache = apt.apt_pkg.Cache(None)
apt_version = cache['apt'].current_ver.ver_str
logging.info("apt version: '%s'" % apt_version)
logging.info("python version: '%s'" % sys.version)
return logdir
def save_system_state(logdir):
# save package state to be able to re-create failures
try:
from .apt_clone import AptClone
except ImportError:
logging.error("failed to import AptClone")
return
target = os.path.join(logdir, "apt-clone_system_state.tar.gz")
logging.debug("creating statefile: '%s'" % target)
# this file may contain sensitive data so ensure we create with the
# right umask
old_umask = os.umask(0o0066)
clone = AptClone()
clone.save_state(sourcedir="/", target=target, with_dpkg_status=True,
scrub_sources=True)
# reset umask
os.umask(old_umask)
# lspci output
try:
s=subprocess.Popen(["lspci","-nn"], stdout=subprocess.PIPE,
universal_newlines=True).communicate()[0]
with open(os.path.join(logdir, "lspci.txt"), "w") as f:
f.write(s)
except OSError as e:
logging.debug("lspci failed: %s" % e)
def setup_view(options, config, logdir):
" setup view based on the config and commandline "
# the commandline overwrites the configfile
for requested_view in [options.frontend]+config.getlist("View","View"):
if not requested_view:
continue
try:
# this should work with py3 and py2.7
from importlib import import_module
# use relative imports
view_modul = import_module("."+requested_view, "DistUpgrade")
# won't work with py3
#view_modul = __import__(requested_view, globals())
view_class = getattr(view_modul, requested_view)
instance = view_class(logdir=logdir, datadir=options.datadir)
break
except Exception as e:
logging.warning("can't import view '%s' (%s)" % (requested_view,e))
print("can't load %s (%s)" % (requested_view, e))
else:
logging.error("No view can be imported, aborting")
print("No view can be imported, aborting")
sys.exit(1)
return instance
def run_new_gnu_screen_window_or_reattach():
""" check if there is a upgrade already running inside gnu screen,
if so, reattach
if not, create new screen window
"""
SCREENNAME = "ubuntu-release-upgrade-screen-window"
# get the active screen sockets
try:
out = subprocess.Popen(
["screen","-ls"], stdout=subprocess.PIPE,
universal_newlines=True).communicate()[0]
logging.debug("screen returned: '%s'" % out)
except OSError:
logging.info("screen could not be run")
return
# check if a release upgrade is among them
if SCREENNAME in out:
logging.info("found active screen session, re-attaching")
# if we have it, attach to it
os.execv("/usr/bin/screen", ["screen", "-d", "-r", "-p", SCREENNAME])
# otherwise re-exec inside screen with (-L) for logging enabled
os.environ["RELEASE_UPGRADER_NO_SCREEN"]="1"
# unset escape key to avoid confusing people who are not used to
# screen. people who already run screen will not be affected by this
# unset escape key with -e, enable log with -L, set name with -S
cmd = ["screen",
"-e", "\\0\\0",
"-c", "screenrc",
"-S", SCREENNAME]+sys.argv
logging.info("re-exec inside screen: '%s'" % cmd)
os.execv("/usr/bin/screen", cmd)
def main():
""" main method """
# commandline setup and config
(options, args) = do_commandline()
config = DistUpgradeConfig(options.datadir)
logdir = setup_logging(options, config)
from .DistUpgradeVersion import VERSION
logging.info("release-upgrader version '%s' started" % VERSION)
# ensure that DistUpgradeView translations are displayed
gettext.textdomain("ubuntu-release-upgrader")
if options.datadir is None or options.datadir == '.':
localedir = os.path.join(os.getcwd(), "mo")
gettext.bindtextdomain("ubuntu-release-upgrader", localedir)
# create view and app objects
view = setup_view(options, config, logdir)
# gnu screen support
if (view.needs_screen and
not "RELEASE_UPGRADER_NO_SCREEN" in os.environ and
not options.disable_gnu_screen):
run_new_gnu_screen_window_or_reattach()
from .DistUpgradeController import DistUpgradeController
app = DistUpgradeController(view, options, datadir=options.datadir)
atexit.register(app._enableAptCronJob)
# partial upgrade only
if options.partial:
if not app.doPartialUpgrade():
sys.exit(1)
sys.exit(0)
# save system state (only if not doing just a partial upgrade)
save_system_state(logdir)
# full upgrade, return error code for success/failure
if app.run():
return 0
return 1
./DistUpgradePatcher.py 0000644 0000000 0000000 00000007662 13475213717 014032 0 ustar root root # DistUpgradeEdPatcher.py
#
# Copyright (c) 2011 Canonical
#
# Author: Michael Vogt
#
# 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
import hashlib
import re
class PatchError(Exception):
""" Error during the patch process """
pass
def patch(orig, edpatch, result_md5sum=None):
""" python implementation of enough "ed" to apply ed-style
patches. Note that this patches in memory so its *not*
suitable for big files
"""
# we only have two states, waiting for command or reading data
(STATE_EXPECT_COMMAND,
STATE_EXPECT_DATA) = range(2)
# this is inefficient for big files
with open(orig, encoding="UTF-8") as f:
orig_lines = f.readlines()
start = end = 0
# we start in wait-for-commend state
state = STATE_EXPECT_COMMAND
with open(edpatch, encoding="UTF-8") as f:
lines = f.readlines()
for line in lines:
if state == STATE_EXPECT_COMMAND:
# in commands get rid of whitespace,
line = line.strip()
# check if we have a substitute command
if line.startswith("s/"):
# strip away the "s/"
line = line[2:]
# chop off the flags at the end
subs, flags = line.rsplit("/", 1)
if flags:
raise PatchError("flags for s// not supported yet")
# get the actual substitution regexp and replacement and
# execute it
regexp, sep, repl = subs.partition("/")
new, count = re.subn(regexp, repl, orig_lines[start], count=1)
orig_lines[start] = new
continue
# otherwise the last char is the command
command = line[-1]
# read address
(start_str, sep, end_str) = line[:-1].partition(",")
# ed starts with 1 while python with 0
start = int(start_str)
start -= 1
# if we don't have end, set it to the next line
if end_str is "":
end = start + 1
else:
end = int(end_str)
# interpret command
if command == "c":
del orig_lines[start:end]
state = STATE_EXPECT_DATA
start -= 1
elif command == "a":
# not allowed to have a range in append
state = STATE_EXPECT_DATA
elif command == "d":
del orig_lines[start:end]
else:
raise PatchError("unknown command: '%s'" % line)
elif state == STATE_EXPECT_DATA:
# this is the data end marker
if line == ".\n":
state = STATE_EXPECT_COMMAND
else:
# copy line verbatim and increase position
start += 1
orig_lines.insert(start, line)
# done with the patching, (optional) verify and write result
result = "".join(orig_lines)
if result_md5sum:
md5 = hashlib.md5()
md5.update(result.encode("UTF-8"))
if md5.hexdigest() != result_md5sum:
raise PatchError("the md5sum after patching is not correct")
with open(orig, "w", encoding="UTF-8") as f:
f.write(result)
return True
./DistUpgradeQuirks.py 0000644 0000000 0000000 00000136604 14037644533 013720 0 ustar root root # DistUpgradeQuirks.py
#
# Copyright (c) 2004-2010 Canonical
#
# Author: Michael Vogt
#
# 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
import apt
import atexit
import glob
import logging
import os
import re
import hashlib
import subprocess
from subprocess import PIPE, Popen
from .utils import get_arch
from .DistUpgradeGettext import gettext as _
try:
from janitor.plugincore.manager import PluginManager
except ImportError:
# janitor is not available, so create a no-op plugin manager.
class PluginManager(object):
def __init__(self, *args, **kws):
pass
def get_plugins(self, *args, **kws):
return []
class DistUpgradeQuirks(object):
"""
This class collects the various quirks handlers that can
be hooked into to fix/work around issues that the individual
releases have
"""
def __init__(self, controller, config):
self.controller = controller
self._view = controller._view
self.config = config
self.uname = Popen(["uname", "-r"], stdout=PIPE,
universal_newlines=True).communicate()[0].strip()
self.arch = get_arch()
self.plugin_manager = PluginManager(self.controller, ["./plugins"])
self.extra_snap_space = 0
self._poke = None
self._uid = ''
self._user_env = {}
self._snapstore_reachable = False
self._snap_list = None
self._from_version = None
self._to_version = None
# the quirk function have the name:
# $Name (e.g. PostUpgrade)
# $todist$Name (e.g. intrepidPostUpgrade)
# $from_$fromdist$Name (e.g. from_dapperPostUpgrade)
def run(self, quirksName):
"""
Run the specific quirks handler, the follow handlers are supported:
- PreCacheOpen: run *before* the apt cache is opened the first time
to set options that affect the cache
- PostInitialUpdate: run *before* the sources.list is rewritten but
after an initial apt-get update
- PostDistUpgradeCache: run *after* the dist-upgrade was calculated
in the cache
- StartUpgrade: before the first package gets installed (but the
download is finished)
- PostUpgrade: run *after* the upgrade is finished successfully and
packages got installed
- PostCleanup: run *after* the cleanup (orphaned etc) is finished
"""
# we do not run any quirks in partialUpgrade mode
if self.controller._partialUpgrade:
logging.info("not running quirks in partialUpgrade mode")
return
to_release = self.config.get("Sources", "To")
from_release = self.config.get("Sources", "From")
# first check for matching plugins
for condition in [
quirksName,
"%s%s" % (to_release, quirksName),
"from_%s%s" % (from_release, quirksName)
]:
for plugin in self.plugin_manager.get_plugins(condition):
logging.debug("running quirks plugin %s" % plugin)
plugin.do_cleanup_cruft()
# run the handler that is common to all dists
funcname = "%s" % quirksName
func = getattr(self, funcname, None)
if func is not None:
logging.debug("quirks: running %s" % funcname)
func()
# run the quirksHandler to-dist
funcname = "%s%s" % (to_release, quirksName)
func = getattr(self, funcname, None)
if func is not None:
logging.debug("quirks: running %s" % funcname)
func()
# now run the quirksHandler from_${FROM-DIST}Quirks
funcname = "from_%s%s" % (from_release, quirksName)
func = getattr(self, funcname, None)
if func is not None:
logging.debug("quirks: running %s" % funcname)
func()
# individual quirks handler that run *before* the cache is opened
def PreCacheOpen(self):
""" run before the apt cache is opened the first time """
logging.debug("running Quirks.PreCacheOpen")
# individual quirks handler that run *after* the cache is opened
def bionicPostInitialUpdate(self):
# PreCacheOpen would be better but controller.abort fails terribly
""" run after the apt cache is opened the first time """
logging.debug("running Quirks.bionicPostInitialUpdate")
self._get_from_and_to_version()
cache = self.controller.cache
self._test_and_warn_if_ros_installed(cache)
if 'snapd' not in cache:
logging.debug("package required for Quirk not in cache")
return
if cache['snapd'].is_installed:
self._checkStoreConnectivity()
# If the snap store is accessible, at the same time calculate the
# extra size needed by to-be-installed snaps. This also prepares
# the snaps-to-install list for the actual upgrade.
if self._snapstore_reachable:
self._calculateSnapSizeRequirements()
def bionicPostUpgrade(self):
logging.debug("running Quirks.bionicPostUpgrade")
cache = self.controller.cache
if 'snapd' not in cache:
logging.debug("package required for Quirk not in cache")
return
if cache['snapd'].is_installed and \
self._snapstore_reachable:
self._replaceDebsAndSnaps()
# individual quirks handler when the dpkg run is finished ---------
def PostCleanup(self):
" run after cleanup "
logging.debug("running Quirks.PostCleanup")
# run right before the first packages get installed
def StartUpgrade(self):
logging.debug("running Quirks.StartUpgrade")
self._applyPatches()
self._removeOldApportCrashes()
self._killUpdateNotifier()
self._killKBluetooth()
self._killScreensaver()
self._pokeScreensaver()
self._inhibitIdle()
self._stopDocvertConverter()
# individual quirks handler that run *after* the dist-upgrade was
# calculated in the cache
def PostDistUpgradeCache(self):
""" run after calculating the dist-upgrade """
logging.debug("running Quirks.PostDistUpgradeCache")
self._install_linux_metapackage()
# helpers
def _get_pci_ids(self):
""" return a set of pci ids of the system (using lspci -n) """
lspci = set()
try:
p = subprocess.Popen(["lspci", "-n"], stdout=subprocess.PIPE,
universal_newlines=True)
except OSError:
return lspci
for line in p.communicate()[0].split("\n"):
if line:
lspci.add(line.split()[2])
return lspci
def _get_from_and_to_version(self):
self._from_version = '16.04'
self._to_version = '18.04'
def _test_and_warn_for_unity_3d_support(self):
UNITY_SUPPORT_TEST = "/usr/lib/nux/unity_support_test"
if (not os.path.exists(UNITY_SUPPORT_TEST) or
"DISPLAY" not in os.environ):
return
# see if there is a running unity, that service is used by both 2d,3d
return_code = subprocess.call(
["ps", "-C", "unity-panel-service"], stdout=open(os.devnull, "w"))
if return_code != 0:
logging.debug(
"_test_and_warn_for_unity_3d_support: no unity running")
return
# if we are here, we need to test and warn
return_code = subprocess.call([UNITY_SUPPORT_TEST])
logging.debug(
"_test_and_warn_for_unity_3d_support '%s' returned '%s'" % (
UNITY_SUPPORT_TEST, return_code))
if return_code != 0:
res = self._view.askYesNoQuestion(
_("Your graphics hardware may not be fully supported in "
"Ubuntu 14.04."),
_("Running the 'unity' desktop environment is not fully "
"supported by your graphics hardware. You will maybe end "
"up in a very slow environment after the upgrade. Our "
"advice is to keep the LTS version for now. For more "
"information see "
"https://wiki.ubuntu.com/X/Bugs/"
"UpdateManagerWarningForUnity3D "
"Do you still want to continue with the upgrade?")
)
if not res:
self.controller.abort()
def _test_and_warn_on_i8xx(self):
I8XX_PCI_IDS = ["8086:7121", # i810
"8086:7125", # i810e
"8086:1132", # i815
"8086:3577", # i830
"8086:2562", # i845
"8086:3582", # i855
"8086:2572", # i865
]
lspci = self._get_pci_ids()
if set(I8XX_PCI_IDS).intersection(lspci):
res = self._view.askYesNoQuestion(
_("Your graphics hardware may not be fully supported in "
"Ubuntu 12.04 LTS."),
_("The support in Ubuntu 12.04 LTS for your Intel "
"graphics hardware is limited "
"and you may encounter problems after the upgrade. "
"For more information see "
"https://wiki.ubuntu.com/X/Bugs/UpdateManagerWarningForI8xx "
"Do you want to continue with the upgrade?")
)
if not res:
self.controller.abort()
def _test_and_warn_on_dropped_fglrx_support(self):
"""
Some cards are no longer supported by fglrx. Check if that
is the case and warn
"""
# this is to deal with the fact that support for some of the cards
# that fglrx used to support got dropped
if (self._checkVideoDriver("fglrx") and
not self._supportInModaliases("fglrx")):
res = self._view.askYesNoQuestion(
_("Upgrading may reduce desktop "
"effects, and performance in games "
"and other graphically intensive "
"programs."),
_("This computer is currently using "
"the AMD 'fglrx' graphics driver. "
"No version of this driver is "
"available that works with your "
"hardware in Ubuntu 10.04 LTS.\n\n"
"Do you want to continue?"))
if not res:
self.controller.abort()
# if the user wants to continue we remove the fglrx driver
# here because its no use (no support for this card)
removals = [
"xorg-driver-fglrx",
"xorg-driver-fglrx-envy",
"fglrx-kernel-source",
"fglrx-amdcccle",
"xorg-driver-fglrx-dev",
"libamdxvba1"
]
logging.debug("remove %s" % ", ".join(removals))
postupgradepurge = self.controller.config.getlist(
"Distro",
"PostUpgradePurge")
for remove in removals:
postupgradepurge.append(remove)
self.controller.config.set("Distro", "PostUpgradePurge",
",".join(postupgradepurge))
def _test_and_fail_on_non_i686(self):
"""
Test and fail if the cpu is not i686 or more or if its a newer
CPU but does not have the cmov feature (LP: #587186)
"""
# check on i386 only
if self.arch == "i386":
logging.debug("checking for i586 CPU")
if not self._cpu_is_i686_and_has_cmov():
logging.error("not a i686 or no cmov")
summary = _("No i686 CPU")
msg = _("Your system uses an i586 CPU or a CPU that does "
"not have the 'cmov' extension. "
"All packages were built with "
"optimizations requiring i686 as the "
"minimal architecture. It is not possible to "
"upgrade your system to a new Ubuntu release "
"with this hardware.")
self._view.error(summary, msg)
self.controller.abort()
def _cpu_is_i686_and_has_cmov(self, cpuinfo_path="/proc/cpuinfo"):
if not os.path.exists(cpuinfo_path):
logging.error("cannot open %s ?!?" % cpuinfo_path)
return True
with open(cpuinfo_path) as f:
cpuinfo = f.read()
# check family
if re.search("^cpu family\s*:\s*[345]\s*", cpuinfo, re.MULTILINE):
logging.debug("found cpu family [345], no i686+")
return False
# check flags for cmov
match = re.search("^flags\s*:\s*(.*)", cpuinfo, re.MULTILINE)
if match:
if "cmov" not in match.group(1).split():
logging.debug("found flags '%s'" % match.group(1))
logging.debug("can not find cmov in flags")
return False
return True
def _test_and_fail_on_non_arm_v6(self):
"""
Test and fail if the cpu is not a arm v6 or greater,
from 9.10 on we do no longer support those CPUs
"""
if self.arch == "armel":
if not self._checkArmCPU():
self._view.error(
_("No ARMv6 CPU"),
_("Your system uses an ARM CPU that is older "
"than the ARMv6 architecture. "
"All packages in karmic were built with "
"optimizations requiring ARMv6 as the "
"minimal architecture. It is not possible to "
"upgrade your system to a new Ubuntu release "
"with this hardware."))
self.controller.abort()
def _test_and_warn_if_vserver(self):
"""
upstart and vserver environments are not a good match, warn
if we find one
"""
# verver test (LP: #454783), see if there is a init around
try:
os.kill(1, 0)
except OSError:
logging.warning("no init found")
res = self._view.askYesNoQuestion(
_("No init available"),
_("Your system appears to be a virtualised environment "
"without an init daemon, e.g. Linux-VServer. "
"Ubuntu 10.04 LTS cannot function within this type of "
"environment, requiring an update to your virtual "
"machine configuration first.\n\n"
"Are you sure you want to continue?"))
if not res:
self.controller.abort()
self._view.processEvents()
def _test_and_warn_if_ros_installed(self, cache):
"""
Test and warn if ROS is installed. A given ROS release only
supports specific Ubuntu releases, and can cause the upgrade
to fail in an overly-cryptic manner.
"""
# These are the root ROS 1 and 2 dependencies as of 07/27/2020
ros_package_patterns = set()
for package_name in (
"catkin",
"rosboost-cfg",
"rosclean",
"ros-environment",
"ros-workspace"):
ros_package_patterns.add(
re.compile("ros-[^\-]+-%s" % package_name))
ros_is_installed = False
for pkg in cache:
if ros_is_installed:
break
for pattern in ros_package_patterns:
if pattern.match(pkg.name):
if pkg.is_installed or pkg.marked_install:
ros_is_installed = True
break
if ros_is_installed:
res = self._view.askYesNoQuestion(
_("The Robot Operating System (ROS) is installed"),
_("It appears that ROS is currently installed. Each ROS "
"release is very strict about the versions of Ubuntu "
"it supports, and Ubuntu upgrades can fail if that "
"guidance isn't followed. Before continuing, please "
"either uninstall ROS, or ensure the ROS release you "
"have installed supports the version of Ubuntu to "
"which you're upgrading.\n\n"
"For ROS 1 releases, refer to REP 3:\n"
"https://www.ros.org/reps/rep-0003.html\n\n"
"For ROS 2 releases, refer to REP 2000:\n"
"https://www.ros.org/reps/rep-2000.html\n\n"
"Are you sure you want to continue?"))
if not res:
self.controller.abort()
def _checkArmCPU(self):
"""
parse /proc/cpuinfo and search for ARMv6 or greater
"""
logging.debug("checking for ARM CPU version")
if not os.path.exists("/proc/cpuinfo"):
logging.error("cannot open /proc/cpuinfo ?!?")
return False
with open("/proc/cpuinfo") as f:
cpuinfo = f.read()
if re.search("^Processor\s*:\s*ARMv[45]", cpuinfo,
re.MULTILINE):
return False
return True
def _stopApparmor(self):
""" /etc/init.d/apparmor stop (see bug #559433)"""
if os.path.exists("/etc/init.d/apparmor"):
logging.debug("/etc/init.d/apparmor stop")
subprocess.call(["/etc/init.d/apparmor", "stop"])
def _stopDocvertConverter(self):
" /etc/init.d/docvert-converter stop (see bug #450569)"
if os.path.exists("/etc/init.d/docvert-converter"):
logging.debug("/etc/init.d/docvert-converter stop")
subprocess.call(["/etc/init.d/docvert-converter", "stop"])
def _killUpdateNotifier(self):
"kill update-notifier"
# kill update-notifier now to suppress reboot required
if os.path.exists("/usr/bin/killall"):
logging.debug("killing update-notifier")
subprocess.call(["killall", "-q", "update-notifier"])
def _killKBluetooth(self):
"""killall kblueplugd kbluetooth (riddel requested it)"""
if os.path.exists("/usr/bin/killall"):
logging.debug("killing kblueplugd kbluetooth4")
subprocess.call(["killall", "-q", "kblueplugd", "kbluetooth4"])
def _killScreensaver(self):
"""killall gnome-screensaver """
if os.path.exists("/usr/bin/killall"):
logging.debug("killing gnome-screensaver")
subprocess.call(["killall", "-q", "gnome-screensaver"])
def _pokeScreensaver(self):
if (os.path.exists("/usr/bin/xdg-screensaver") and
os.environ.get('DISPLAY')):
logging.debug("setup poke timer for the scrensaver")
cmd = "while true;"
cmd += " do /usr/bin/xdg-screensaver reset >/dev/null 2>&1;"
cmd += " sleep 30; done"
try:
self._poke = subprocess.Popen(cmd, shell=True)
atexit.register(self._stopPokeScreensaver)
except (OSError, ValueError):
logging.exception("failed to setup screensaver poke")
def _getUserEnv(self):
try:
pid = subprocess.check_output(["pgrep", "-u", self._uid,
"gnome-session"])
pid = pid.decode().split('\n')[0]
with open('/proc/' + pid + '/environ', 'r') as f:
data = f.read().split('\x00')
for line in data:
if len(line):
env = line.split('=', 1)
self._user_env[env[0]] = env[1]
except subprocess.CalledProcessError as e:
if e.returncode == 1:
logging.debug("gnome-session not running for user")
else:
logging.exception("failed to read user env")
def _inhibitIdle(self):
if os.path.exists("/usr/bin/gnome-session-inhibit"):
self._uid = os.environ.get('SUDO_UID', '')
if not self._uid:
self._uid = os.environ.get('PKEXEC_UID', '')
if not self._uid:
logging.debug("failed to determine user upgrading")
logging.error("failed to inhibit gnome-session idle")
return
self._getUserEnv()
if not self._user_env:
return
#seteuid so dbus user session can be accessed
os.seteuid(int(self._uid))
logging.debug("inhibit gnome-session idle")
try:
xdg_desktop = self._user_env.get("XDG_CURRENT_DESKTOP", "")
if not xdg_desktop:
logging.debug("failed to find XDG_CURRENT_DESKTOP")
logging.error("failed to inhibit gnome-session idle")
return
xdg_desktop = xdg_desktop.split(':')
idle = subprocess.Popen(["gnome-session-inhibit", "--inhibit",
"idle", "--inhibit-only"],
env=self._user_env)
# leave the inhibitor in place on Ubuntu GNOME, since the
# lock screen will be broken after upgrade (LP: #1565178)
for desktop in xdg_desktop:
if "GNOME" not in desktop:
atexit.register(idle.terminate)
except (OSError, ValueError):
logging.exception("failed to inhibit gnome-session idle")
os.seteuid(os.getuid())
def _stopPokeScreensaver(self):
res = False
if self._poke is not None:
try:
self._poke.terminate()
res = self._poke.wait()
except OSError:
logging.exception("failed to stop screensaver poke")
self._poke = None
return res
def _removeOldApportCrashes(self):
" remove old apport crash files and whoopsie control files "
try:
for ext in ['.crash', '.upload', '.uploaded']:
for f in glob.glob("/var/crash/*%s" % ext):
logging.debug("removing old %s file '%s'" % (ext, f))
os.unlink(f)
except Exception as e:
logging.warning("error during unlink of old crash files (%s)" % e)
def _checkStoreConnectivity(self):
""" check for connectivity to the snap store to install snaps"""
res = False
snap_env = os.environ.copy()
snap_env["LANG"] = "C.UTF-8"
connected = Popen(["snap", "debug", "connectivity"], stdout=PIPE,
stderr=PIPE, env=snap_env,
universal_newlines=True).communicate()
if re.search("^ \* PASS", connected[0], re.MULTILINE):
self._snapstore_reachable = True
return
# can't connect
elif re.search("^ \*.*unreachable", connected[0], re.MULTILINE):
logging.error("No snap store connectivity")
res = self._view.askYesNoQuestion(
_("Connection to Snap Store failed"),
_("Your system does not have a connection to the Snap "
"Store. For the best upgrade experience make sure "
"that your system can connect to api.snapcraft.io.\n"
"Do you still want to continue with the upgrade?")
)
# debug command not available
elif 'error: unknown command' in connected[1]:
logging.error("snap debug command not available")
res = self._view.askYesNoQuestion(
_("Outdated snapd package"),
_("Your system does not have the latest version of snapd. "
"Please update the version of snapd on your system to "
"improve the upgrade experience.\n"
"Do you still want to continue with the upgrade?")
)
# not running as root
elif 'error: access denied' in connected[1]:
res = False
logging.error("Not running as root!")
if not res:
self.controller.abort()
def _calculateSnapSizeRequirements(self):
import json
import urllib.request
from urllib.error import URLError
# first fetch the list of snap-deb replacements that will be needed
# and store them for future reference, along with other data we'll
# need in the process
self._prepare_snap_replacement_data()
# now perform direct API calls to the store, requesting size
# information for each of the snaps needing installation
self._view.updateStatus(_("Calculating snap size requirements"))
for snap, snap_object in self._snap_list.items():
if snap_object['command'] != 'install':
continue
action = {
"instance-key": "upgrade-size-check",
"action": "download",
"snap-id": snap_object['snap-id'],
"channel": snap_object['channel'],
}
data = {
"context": [],
"actions": [action],
}
req = urllib.request.Request(
url='https://api.snapcraft.io/v2/snaps/refresh',
data=bytes(json.dumps(data), encoding='utf-8'))
req.add_header('Snap-Device-Series', '16')
req.add_header('Content-type', 'application/json')
req.add_header('Snap-Device-Architecture', self.arch)
try:
response = urllib.request.urlopen(req).read()
if isinstance(response, bytes):
response = response.decode('utf-8')
info = json.loads(response)
size = int(info['results'][0]['snap']['download']['size'])
except (KeyError, URLError, ValueError):
logging.debug("Failed fetching size of snap %s" % snap)
continue
self.extra_snap_space += size
def _replaceDebsAndSnaps(self):
""" install a snap and mark its corresponding package for removal """
self._view.updateStatus(_("Processing snap replacements"))
# _snap_list should be populated by the earlier
# _calculateSnapSizeRequirements call.
from collections import OrderedDict
ordered_snap_list = OrderedDict()
# these need to be first as other snaps depend on them and snapd won't
# use the latest/stable/ubuntu-18.04 channel
if 'gnome-3-34-1804' in self._snap_list.keys():
ordered_snap_list['gnome-3-34-1804'] = \
self._snap_list['gnome-3-34-1804']
self._snap_list.pop('gnome-3-34-1804')
if 'gtk-common-themes' in self._snap_list.keys():
ordered_snap_list['gtk-common-themes'] = \
self._snap_list['gtk-common-themes']
self._snap_list.pop('gtk-common-themes')
for snap, snap_object in self._snap_list.items():
ordered_snap_list[snap] = snap_object
for snap, snap_object in ordered_snap_list.items():
command = snap_object['command']
if command == 'refresh':
self._view.updateStatus(_("refreshing snap %s" % snap))
popenargs = ["snap", command,
"--channel", snap_object['channel'], snap]
elif command == 'remove':
self._view.updateStatus(_("removing snap %s" % snap))
popenargs = ["snap", command, snap]
else:
self._view.updateStatus(_("installing snap %s" % snap))
popenargs = ["snap", command,
"--channel", snap_object['channel'], snap]
try:
self._view.processEvents()
proc = subprocess.run(
popenargs,
stdout=subprocess.PIPE,
check=True)
self._view.processEvents()
except subprocess.CalledProcessError:
logging.debug("%s of snap %s failed" % (command, snap))
continue
if proc.returncode == 0:
logging.debug("%s of snap %s succeeded" % (command, snap))
if command == 'install' and snap_object['deb']:
self.controller.forced_obsoletes.append(snap_object['deb'])
def _checkPae(self):
" check PAE in /proc/cpuinfo "
# upgrade from Precise will fail if PAE is not in cpu flags
logging.debug("_checkPae")
pae = 0
with open('/proc/cpuinfo') as f:
cpuinfo = f.read()
if re.search("^flags\s+:.* pae ", cpuinfo, re.MULTILINE):
pae = 1
if not pae:
logging.error("no pae in /proc/cpuinfo")
summary = _("PAE not enabled")
msg = _("Your system uses a CPU that does not have PAE enabled. "
"Ubuntu only supports non-PAE systems up to Ubuntu "
"12.04. To upgrade to a later version of Ubuntu, you "
"must enable PAE (if this is possible) see:\n"
"http://help.ubuntu.com/community/EnablingPAE")
self._view.error(summary, msg)
self.controller.abort()
def _checkVideoDriver(self, name):
" check if the given driver is in use in xorg.conf "
XORG = "/etc/X11/xorg.conf"
if not os.path.exists(XORG):
return False
with open(XORG) as f:
lines = f.readlines()
for line in lines:
s = line.split("#")[0].strip()
# check for fglrx driver entry
if (s.lower().startswith("driver") and
s.endswith('"%s"' % name)):
return True
return False
def _applyPatches(self, patchdir="./patches"):
"""
helper that applies the patches in patchdir. the format is
_path_to_file.md5sum and it will apply the diff to that file if the
md5sum matches
"""
if not os.path.exists(patchdir):
logging.debug("no patchdir")
return
for f in os.listdir(patchdir):
# skip, not a patch file, they all end with .$md5sum
if "." not in f:
logging.debug("skipping '%s' (no '.')" % f)
continue
logging.debug("check if patch '%s' needs to be applied" % f)
(encoded_path, md5sum, result_md5sum) = f.rsplit(".", 2)
# FIXME: this is not clever and needs quoting support for
# filenames with "_" in the name
path = encoded_path.replace("_", "/")
logging.debug("target for '%s' is '%s' -> '%s'" % (
f, encoded_path, path))
# target does not exist
if not os.path.exists(path):
logging.debug("target '%s' does not exist" % path)
continue
# check the input md5sum, this is not strictly needed as patch()
# will verify the result md5sum and discard the result if that
# does not match but this will remove a misleading error in the
# logs
md5 = hashlib.md5()
with open(path, "rb") as fd:
md5.update(fd.read())
if md5.hexdigest() == result_md5sum:
logging.debug("already at target hash, skipping '%s'" % path)
continue
elif md5.hexdigest() != md5sum:
logging.warning("unexpected target md5sum, skipping: '%s'"
% path)
continue
# patchable, do it
from .DistUpgradePatcher import patch
try:
patch(path, os.path.join(patchdir, f), result_md5sum)
logging.info("applied '%s' successfully" % f)
except Exception:
logging.exception("ed failed for '%s'" % f)
def _supportInModaliases(self, pkgname, lspci=None):
"""
Check if pkgname will work on this hardware
This helper will check with the modaliasesdir if the given
pkg will work on this hardware (or the hardware given
via the lspci argument)
"""
# get lspci info (if needed)
if not lspci:
lspci = self._get_pci_ids()
# get pkg
if (pkgname not in self.controller.cache or
not self.controller.cache[pkgname].candidate):
logging.warning("can not find '%s' in cache")
return False
pkg = self.controller.cache[pkgname]
for (module, pciid_list) in \
self._parse_modaliases_from_pkg_header(pkg.candidate.record):
for pciid in pciid_list:
m = re.match("pci:v0000(.+)d0000(.+)sv.*", pciid)
if m:
matchid = "%s:%s" % (m.group(1), m.group(2))
if matchid.lower() in lspci:
logging.debug("found system pciid '%s' in modaliases"
% matchid)
return True
logging.debug("checking for %s support in modaliases but none found"
% pkgname)
return False
def _parse_modaliases_from_pkg_header(self, pkgrecord):
""" return a list of (module1, (pciid, ...), ...)"""
if "Modaliases" not in pkgrecord:
return []
# split the string
modules = []
for m in pkgrecord["Modaliases"].split(")"):
m = m.strip(", ")
if not m:
continue
(module, pciids) = m.split("(")
modules.append((module, [x.strip() for x in pciids.split(",")]))
return modules
def _add_extras_repository(self):
logging.debug("_add_extras_repository")
cache = self.controller.cache
if "ubuntu-extras-keyring" not in cache:
logging.debug("no ubuntu-extras-keyring, no need to add repo")
return
if not (cache["ubuntu-extras-keyring"].marked_install or
cache["ubuntu-extras-keyring"].installed):
logging.debug("ubuntu-extras-keyring not installed/marked_install")
return
try:
import aptsources.sourceslist
sources = aptsources.sourceslist.SourcesList()
for entry in sources:
if "extras.ubuntu.com" in entry.uri:
logging.debug("found extras.ubuntu.com, no need to add it")
break
else:
logging.info("no extras.ubuntu.com, adding it to sources.list")
sources.add("deb", "http://extras.ubuntu.com/ubuntu",
self.controller.toDist, ["main"],
"Third party developers repository")
sources.save()
except Exception:
logging.exception("error adding extras.ubuntu.com")
def _gutenprint_fixup(self):
""" foomatic-db-gutenprint get removed during the upgrade,
replace it with the compressed ijsgutenprint-ppds
(context is foomatic-db vs foomatic-db-compressed-ppds)
"""
try:
cache = self.controller.cache
if ("foomatic-db-gutenprint" in cache and
cache["foomatic-db-gutenprint"].marked_delete and
"ijsgutenprint-ppds" in cache):
logging.info("installing ijsgutenprint-ppds")
cache.mark_install(
"ijsgutenprint-ppds",
"foomatic-db-gutenprint -> ijsgutenprint-ppds rule")
except Exception:
logging.exception("_gutenprint_fixup failed")
def _enable_multiarch(self, foreign_arch="i386"):
""" enable multiarch via /etc/dpkg/dpkg.cfg.d/multiarch """
cfg = "/etc/dpkg/dpkg.cfg.d/multiarch"
if not os.path.exists(cfg):
try:
os.makedirs("/etc/dpkg/dpkg.cfg.d/")
except OSError:
pass
with open(cfg, "w") as f:
f.write("foreign-architecture %s\n" % foreign_arch)
def _is_greater_than(self, term1, term2):
""" copied from ubuntu-drivers common """
# We don't want to take into account
# the flavour
pattern = re.compile('(.+)-([0-9]+)-(.+)')
match1 = pattern.match(term1)
match2 = pattern.match(term2)
if match1:
term1 = '%s-%s' % (match1.group(1),
match1.group(2))
term2 = '%s-%s' % (match2.group(1),
match2.group(2))
logging.debug('Comparing %s with %s' % (term1, term2))
return apt.apt_pkg.version_compare(term1, term2) > 0
def _get_linux_metapackage(self, cache, headers):
""" Get the linux headers or linux metapackage
copied from ubuntu-drivers-common
"""
suffix = headers and '-headers' or ''
pattern = re.compile('linux-image-(.+)-([0-9]+)-(.+)')
source_pattern = re.compile('linux-(.+)')
metapackage = ''
version = ''
for pkg in cache:
if ('linux-image' in pkg.name and 'extra' not in pkg.name and
(pkg.is_installed or pkg.marked_install)):
match = pattern.match(pkg.name)
# Here we filter out packages such as
# linux-generic-lts-quantal
if match:
source = pkg.candidate.record['Source']
current_version = '%s-%s' % (match.group(1),
match.group(2))
# See if the current version is greater than
# the greatest that we've found so far
if self._is_greater_than(current_version,
version):
version = current_version
match_source = source_pattern.match(source)
# Set the linux-headers metapackage
if '-lts-' in source and match_source:
# This is the case of packages such as
# linux-image-3.5.0-18-generic which
# comes from linux-lts-quantal.
# Therefore the linux-headers-generic
# metapackage would be wrong here and
# we should use
# linux-headers-generic-lts-quantal
# instead
metapackage = 'linux%s-%s-%s' % (
suffix,
match.group(3),
match_source.group(1))
else:
# The scheme linux-headers-$flavour works
# well here
metapackage = 'linux%s-%s' % (
suffix,
match.group(3))
return metapackage
def _install_linux_metapackage(self):
""" Ensure the linux metapackage is installed for the newest_kernel
installed. (LP: #1509305)
"""
cache = self.controller.cache
linux_metapackage = self._get_linux_metapackage(cache, False)
# Seen on errors.u.c with linux-rpi2 metapackage
# https://errors.ubuntu.com/problem/994bf05fae85fbcd44f721495db6518f2d5a126d
if linux_metapackage not in cache:
logging.info("linux metapackage (%s) not available" %
linux_metapackage)
return
# install the package if it isn't installed
if not cache[linux_metapackage].is_installed:
logging.info("installing linux metapackage: %s" %
linux_metapackage)
reason = "linux metapackage may have been accidentally uninstalled"
cache.mark_install(linux_metapackage, reason)
def ensure_recommends_are_installed_on_desktops(self):
""" ensure that on a desktop install recommends are installed
(LP: #759262)
"""
if not self.controller.serverMode:
if not apt.apt_pkg.config.find_b("Apt::Install-Recommends"):
msg = "Apt::Install-Recommends was disabled,"
msg += " enabling it just for the upgrade"
logging.warning(msg)
apt.apt_pkg.config.set("Apt::Install-Recommends", "1")
def _prepare_snap_replacement_data(self):
""" Helper function fetching all required info for the deb-to-snap
migration: version strings for upgrade (from and to) and the list
of snaps (with actions).
"""
import json
self._snap_list = {}
from_channel = "stable/ubuntu-%s" % self._from_version
to_channel = "stable/ubuntu-%s" % self._to_version
seeded_snaps = {}
unseeded_snaps = {}
try:
current_path = os.path.dirname(os.path.abspath(__file__))
d2s_file = open(current_path + '/deb2snap.json', 'r')
d2s = json.load(d2s_file)
d2s_file.close()
for snap in d2s["seeded"]:
seed = d2s["seeded"][snap]
metapkg = seed.get("metapkg", None)
if metapkg not in self.controller.cache:
continue
if metapkg and \
self.controller.cache[metapkg].is_installed is False:
continue
deb = seed.get("deb", None)
from_chan = seed.get("from_channel", from_channel)
to_chan = seed.get("to_channel", to_channel)
seeded_snaps[snap] = (deb, from_chan, to_chan)
for snap in d2s["unseeded"]:
unseed = d2s["unseeded"][snap]
deb = unseed.get("deb", None)
metapkg = unseed.get("metapkg", None)
if metapkg not in self.controller.cache:
continue
if metapkg and \
self.controller.cache[metapkg].is_installed is False:
continue
from_chan = unseed.get("from_channel", from_channel)
unseeded_snaps[snap] = (deb, from_chan)
except Exception as e:
logging.warning("error reading deb2snap.json file (%s)" % e)
snap_list = ''
# list the installed snaps and add them to seeded ones
snap_list = subprocess.Popen(["snap", "list"],
universal_newlines=True,
stdout=subprocess.PIPE).communicate()
if snap_list:
# first line of output is a header and the last line is empty
snaps_installed = [line.split()[0]
for line in snap_list[0].split('\n')[1:-1]]
for snap in snaps_installed:
if snap in seeded_snaps or snap in unseeded_snaps:
continue
else:
seeded_snaps[snap] = (None, from_channel, to_channel)
self._view.updateStatus(_("Checking for installed snaps"))
for snap, (deb, from_channel, to_channel) in seeded_snaps.items():
snap_object = {}
# check to see if the snap is already installed
snap_info = subprocess.Popen(["snap", "info", snap],
universal_newlines=True,
stdout=subprocess.PIPE).communicate()
self._view.processEvents()
if re.search("^installed: ", snap_info[0], re.MULTILINE):
logging.debug("Snap %s is installed" % snap)
# its not tracking the release channel so don't refresh
if not re.search(r"^tracking:.*%s" % from_channel,
snap_info[0], re.MULTILINE):
logging.debug("Snap %s is not tracking the release channel"
% snap)
continue
snap_object['command'] = 'refresh'
else:
# Do not replace packages not installed
cache = self.controller.cache
if (deb and (deb not in cache or not cache[deb].is_installed)):
logging.debug("Deb package %s is not installed. Skipping "
"snap package %s installation" % (deb, snap))
continue
match = re.search(r"snap-id:\s*(\w*)", snap_info[0])
if not match:
logging.debug("Could not parse snap-id for the %s snap"
% snap)
continue
snap_object['command'] = 'install'
snap_object['deb'] = deb
snap_object['snap-id'] = match.group(1)
snap_object['channel'] = to_channel
self._snap_list[snap] = snap_object
for snap, (deb, from_channel) in unseeded_snaps.items():
snap_object = {}
# check to see if the snap is already installed
snap_info = subprocess.Popen(["snap", "info", snap],
universal_newlines=True,
stdout=subprocess.PIPE).communicate()
self._view.processEvents()
if re.search("^installed: ", snap_info[0], re.MULTILINE):
logging.debug("Snap %s is installed" % snap)
# its not tracking the release channel so don't remove
if not re.search(r"^tracking:.*%s" % from_channel,
snap_info[0], re.MULTILINE):
logging.debug("Snap %s is not tracking the release channel"
% snap)
continue
snap_object['command'] = 'remove'
# check if this snap is being used by any other snaps
conns = subprocess.Popen(["snap", "connections", snap],
universal_newlines=True,
stdout=subprocess.PIPE).communicate()
self._view.processEvents()
for conn in conns[0].split('\n'):
conn_cols = conn.split()
if len(conn_cols) != 4:
continue
plug = conn_cols[1]
slot = conn_cols[2]
if slot.startswith(snap + ':'):
plug_snap = plug.split(':')[0]
if plug_snap != '-' and \
plug_snap not in unseeded_snaps:
logging.debug("Snap %s is being used by %s. "
"Switching it to stable track"
% (snap, plug_snap))
snap_object['command'] = 'refresh'
snap_object['channel'] = 'stable'
break
self._snap_list[snap] = snap_object
return self._snap_list
./DistUpgradeVersion.py 0000644 0000000 0000000 00000000025 14114454126 014043 0 ustar root root VERSION = '18.04.45'
./DistUpgradeView.py 0000644 0000000 0000000 00000040312 13502463664 013342 0 ustar root root # DistUpgradeView.py
#
# Copyright (c) 2004,2005 Canonical
#
# Author: Michael Vogt
#
# 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 .DistUpgradeGettext import gettext as _
from .DistUpgradeGettext import ngettext
from .telemetry import get as get_telemetry
import apt
from enum import Enum
import errno
import os
import apt_pkg
import locale
import logging
import signal
import select
from .DistUpgradeApport import apport_pkgfailure
try:
locale.setlocale(locale.LC_ALL, "")
(code, ENCODING) = locale.getdefaultlocale()
except:
logging.exception("getting the encoding failed")
ENCODING = "utf-8" #pyflakes
# if there is no encoding, setup UTF-8
if not ENCODING:
ENCODING = "utf-8"
os.putenv("LC_CTYPE", "C.UTF-8")
try:
locale.setlocale(locale.LC_CTYPE, "C.UTF-8")
except locale.error:
pass
# log locale information
logging.info("locale: '%s' '%s'" % locale.getlocale())
def FuzzyTimeToStr(sec):
" return the time a bit fuzzy (no seconds if time > 60 secs "
#print("FuzzyTimeToStr: ", sec)
sec = int(sec)
days = sec//(60*60*24)
hours = sec//(60*60) % 24
minutes = (sec//60) % 60
seconds = sec % 60
# 0 seonds remaining looks wrong and its "fuzzy" anyway
if seconds == 0:
seconds = 1
# string map to make the re-ordering possible
map = { "str_days" : "",
"str_hours" : "",
"str_minutes" : "",
"str_seconds" : ""
}
# get the fragments, this is not ideal i18n wise, but its
# difficult to do it differently
if days > 0:
map["str_days"] = ngettext("%li day","%li days", days) % days
if hours > 0:
map["str_hours"] = ngettext("%li hour","%li hours", hours) % hours
if minutes > 0:
map["str_minutes"] = ngettext("%li minute","%li minutes", minutes) % minutes
map["str_seconds"] = ngettext("%li second","%li seconds", seconds) % seconds
# now assemble the string
if days > 0:
# Don't print str_hours if it's an empty string, see LP: #288912
if map["str_hours"] == '':
return map["str_days"]
# TRANSLATORS: you can alter the ordering of the remaining time
# information here if you shuffle %(str_days)s %(str_hours)s %(str_minutes)s
# around. Make sure to keep all '$(str_*)s' in the translated string
# and do NOT change anything appart from the ordering.
#
# %(str_hours)s will be either "1 hour" or "2 hours" depending on the
# plural form
#
# Note: most western languages will not need to change this
return _("%(str_days)s %(str_hours)s") % map
# display no minutes for time > 3h, see LP: #144455
elif hours > 3:
return map["str_hours"]
# when we are near the end, become more precise again
elif hours > 0:
# Don't print str_minutes if it's an empty string, see LP: #288912
if map["str_minutes"] == '':
return map["str_hours"]
# TRANSLATORS: you can alter the ordering of the remaining time
# information here if you shuffle %(str_hours)s %(str_minutes)s
# around. Make sure to keep all '$(str_*)s' in the translated string
# and do NOT change anything appart from the ordering.
#
# %(str_hours)s will be either "1 hour" or "2 hours" depending on the
# plural form
#
# Note: most western languages will not need to change this
return _("%(str_hours)s %(str_minutes)s") % map
elif minutes > 0:
return map["str_minutes"]
return map["str_seconds"]
class AcquireProgress(apt.progress.base.AcquireProgress):
def __init__(self):
super(AcquireProgress, self).__init__()
self.est_speed = 0.0
def start(self):
super(AcquireProgress, self).start()
self.est_speed = 0.0
self.eta = 0.0
self.percent = 0.0
self.release_file_download_error = False
def update_status(self, uri, descr, shortDescr, status):
super(AcquireProgress, self).update_status(uri, descr, shortDescr, status)
# FIXME: workaround issue in libapt/python-apt that does not
# raise a exception if *all* files fails to download
if status == apt_pkg.STAT_FAILED:
logging.warning("update_status: dlFailed on '%s' " % uri)
if uri.endswith("Release.gpg") or uri.endswith("Release"):
# only care about failures from network, not gpg, bzip, those
# are different issues
for net in ["http","ftp","mirror"]:
if uri.startswith(net):
self.release_file_download_error = True
break
# required, otherwise the lucid version of python-apt gets really
# unhappy, its expecting this function for apt.progress.base.AcquireProgress
def pulse_items(self, arg):
return True
def pulse(self, owner=None):
super(AcquireProgress, self).pulse(owner)
self.percent = (((self.current_bytes + self.current_items) * 100.0) /
float(self.total_bytes + self.total_items))
if self.current_cps > self.est_speed:
self.est_speed = (self.est_speed+self.current_cps)/2.0
if self.current_cps > 0:
self.eta = ((self.total_bytes - self.current_bytes) /
float(self.current_cps))
return True
def isDownloadSpeedEstimated(self):
return (self.est_speed != 0)
def estimatedDownloadTime(self, required_download):
""" get the estimated download time """
if self.est_speed == 0:
timeModem = required_download/(56*1024/8) # 56 kbit
timeDSL = required_download/(1024*1024/8) # 1Mbit = 1024 kbit
s= _("This download will take about %s with a 1Mbit DSL connection "
"and about %s with a 56k modem.") % (FuzzyTimeToStr(timeDSL), FuzzyTimeToStr(timeModem))
return s
# if we have a estimated speed, use it
s = _("This download will take about %s with your connection. ") % FuzzyTimeToStr(required_download/self.est_speed)
return s
class InstallProgress(apt.progress.base.InstallProgress):
""" Base class for InstallProgress that supports some fancy
stuff like apport integration
"""
def __init__(self):
apt.progress.base.InstallProgress.__init__(self)
self.master_fd = None
def wait_child(self):
"""Wait for child progress to exit.
The return values is the full status returned from os.waitpid()
(not only the return code).
"""
while True:
try:
select.select([self.statusfd], [], [], self.select_timeout)
except select.error as e:
if e.args[0] != errno.EINTR:
raise
self.update_interface()
try:
(pid, res) = os.waitpid(self.child_pid, os.WNOHANG)
if pid == self.child_pid:
break
except OSError as e:
if e.errno != errno.EINTR:
raise
if e.errno == errno.ECHILD:
break
return res
def run(self, pm):
pid = self.fork()
if pid == 0:
# child, ignore sigpipe, there are broken scripts out there
# like etckeeper (LP: #283642)
signal.signal(signal.SIGPIPE,signal.SIG_IGN)
try:
res = pm.do_install(self.writefd)
except Exception as e:
print("Exception during pm.DoInstall(): ", e)
logging.exception("Exception during pm.DoInstall()")
with open("/var/run/ubuntu-release-upgrader-apt-exception","w") as f:
f.write(str(e))
os._exit(pm.RESULT_FAILED)
os._exit(res)
self.child_pid = pid
res = os.WEXITSTATUS(self.wait_child())
return res
def error(self, pkg, errormsg):
" install error from a package "
apt.progress.base.InstallProgress.error(self, pkg, errormsg)
logging.error("got an error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
if "/" in pkg:
pkg = os.path.basename(pkg)
if "_" in pkg:
pkg = pkg.split("_")[0]
# now run apport
apport_pkgfailure(pkg, errormsg)
class DumbTerminal(object):
def call(self, cmd, hidden=False):
" expects a command in the subprocess style (as a list) "
import subprocess
subprocess.call(cmd)
class DummyHtmlView(object):
def open(self, url):
pass
def show(self):
pass
def hide(self):
pass
class Step(Enum):
PREPARE = 1
MODIFY_SOURCES = 2
FETCH = 3
INSTALL = 4
CLEANUP = 5
REBOOT = 6
N = 7
# Declare these translatable strings from the .ui files here so that
# xgettext picks them up.
( _("Preparing to upgrade"),
_("Getting new software channels"),
_("Getting new packages"),
_("Installing the upgrades"),
_("Cleaning up"),
)
class DistUpgradeView(object):
" abstraction for the upgrade view "
def __init__(self):
self.needs_screen = False
pass
def getOpCacheProgress(self):
" return a OpProgress() subclass for the given graphic"
return apt.progress.base.OpProgress()
def getAcquireProgress(self):
" return an acquire progress object "
return AcquireProgress()
def getInstallProgress(self, cache=None):
" return a install progress object "
return InstallProgress()
def getTerminal(self):
return DumbTerminal()
def getHtmlView(self):
return DummyHtmlView()
def updateStatus(self, msg):
""" update the current status of the distUpgrade based
on the current view
"""
pass
def abort(self):
""" provide a visual feedback that the upgrade was aborted """
pass
def setStep(self, step):
""" we have 6 steps current for a upgrade:
1. Analyzing the system
2. Updating repository information
3. fetch packages
3. Performing the upgrade
4. Post upgrade stuff
5. Complete
"""
get_telemetry().add_stage(step.name)
pass
def hideStep(self, step):
" hide a certain step from the GUI "
pass
def showStep(self, step):
" show a certain step from the GUI "
pass
def confirmChanges(self, summary, changes, demotions, downloadSize,
actions=None, removal_bold=True):
""" display the list of changed packages (apt.Package) and
return if the user confirms them
"""
self.confirmChangesMessage = ""
self.demotions = demotions
self.toInstall = []
self.toReinstall = []
self.toUpgrade = []
self.toRemove = []
self.toRemoveAuto = []
self.toDowngrade = []
for pkg in changes:
if pkg.marked_install:
self.toInstall.append(pkg)
elif pkg.marked_upgrade:
self.toUpgrade.append(pkg)
elif pkg.marked_reinstall:
self.toReinstall.append(pkg)
elif pkg.marked_delete:
if pkg._pcache._depcache.is_auto_installed(pkg._pkg):
self.toRemoveAuto.append(pkg)
else:
self.toRemove.append(pkg)
elif pkg.marked_downgrade:
self.toDowngrade.append(pkg)
# do not bother the user with a different treeview
self.toInstall = self.toInstall + self.toReinstall
# sort it
self.toInstall.sort()
self.toUpgrade.sort()
self.toRemove.sort()
self.toRemoveAuto.sort()
self.toDowngrade.sort()
# now build the message (the same for all frontends)
msg = "\n"
pkgs_remove = len(self.toRemove) + len(self.toRemoveAuto)
pkgs_inst = len(self.toInstall) + len(self.toReinstall)
pkgs_upgrade = len(self.toUpgrade)
# FIXME: show detailed packages
if len(self.demotions) > 0:
msg += ngettext(
"%(amount)d installed package is no longer supported by Canonical. "
"You can still get support from the community.",
"%(amount)d installed packages are no longer supported by "
"Canonical. You can still get support from the community.",
len(self.demotions)) % { 'amount' : len(self.demotions) }
msg += "\n\n"
if pkgs_remove > 0:
# FIXME: make those two separate lines to make it clear
# that the "%" applies to the result of ngettext
msg += ngettext("%d package is going to be removed.",
"%d packages are going to be removed.",
pkgs_remove) % pkgs_remove
msg += " "
if pkgs_inst > 0:
msg += ngettext("%d new package is going to be "
"installed.",
"%d new packages are going to be "
"installed.",pkgs_inst) % pkgs_inst
msg += " "
if pkgs_upgrade > 0:
msg += ngettext("%d package is going to be upgraded.",
"%d packages are going to be upgraded.",
pkgs_upgrade) % pkgs_upgrade
msg +=" "
if downloadSize > 0:
downloadSizeStr = apt_pkg.size_to_str(downloadSize)
if isinstance(downloadSizeStr, bytes):
downloadSizeStr = downloadSizeStr.decode(ENCODING)
msg += _("\n\nYou have to download a total of %s. ") % (
downloadSizeStr)
msg += self.getAcquireProgress().estimatedDownloadTime(downloadSize)
if ((pkgs_upgrade + pkgs_inst) > 0) and ((pkgs_upgrade + pkgs_inst + pkgs_remove) > 100):
if self.getAcquireProgress().isDownloadSpeedEstimated():
msg += "\n\n%s" % _( "Installing the upgrade "
"can take several hours. Once the download "
"has finished, the process cannot be canceled.")
else:
msg += "\n\n%s" % _( "Fetching and installing the upgrade "
"can take several hours. Once the download "
"has finished, the process cannot be canceled.")
else:
if pkgs_remove > 100:
msg += "\n\n%s" % _( "Removing the packages "
"can take several hours. ")
# Show an error if no actions are planned
if (pkgs_upgrade + pkgs_inst + pkgs_remove) < 1:
# FIXME: this should go into DistUpgradeController
summary = _("The software on this computer is up to date.")
msg = _("There are no upgrades available for your system. "
"The upgrade will now be canceled.")
self.error(summary, msg)
return False
# set the message
self.confirmChangesMessage = msg
return True
def askYesNoQuestion(self, summary, msg, default='No'):
" ask a Yes/No question and return True on 'Yes' "
pass
def confirmRestart(self):
" generic ask about the restart, can be overridden "
summary = _("Reboot required")
msg = _("The upgrade is finished and "
"a reboot is required. "
"Do you want to do this "
"now?")
return self.askYesNoQuestion(summary, msg)
def error(self, summary, msg, extended_msg=None):
" display a error "
pass
def information(self, summary, msg, extended_msg=None):
" display a information msg"
pass
def processEvents(self):
""" process gui events (to keep the gui alive during a long
computation """
pass
def pulseProgress(self, finished=False):
""" do a progress pulse (e.g. bounce a bar back and forth, show
a spinner)
"""
pass
def showDemotions(self, summary, msg, demotions):
"""
show demoted packages to the user, default implementation
is to just show a information dialog
"""
self.information(summary, msg, "\n".join(demotions))
if __name__ == "__main__":
fp = AcquireProgress()
fp.pulse()
./DistUpgradeViewGtk3.py 0000644 0000000 0000000 00000101314 13502463664 014073 0 ustar root root # DistUpgradeViewGtk3.py
#
# Copyright (c) 2011 Canonical
#
# Author: Michael Vogt
#
# 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
import gi
vte291 = False
try:
gi.require_version("Vte", "2.91")
from gi.repository import Vte
vte291 = True
except Exception as e:
gi.require_version("Vte", "2.90")
# COMPAT: Dear upstream, this compat code below will be duplicated in
# all python-vte using applications. Love, Michael
from gi.repository import Vte
Vte.Pty.new_sync = Vte.Pty.new
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Pango
import sys
import locale
import logging
import time
import subprocess
import apt
import apt_pkg
import os
from .DistUpgradeApport import run_apport, apport_crash
from .DistUpgradeView import DistUpgradeView, FuzzyTimeToStr, InstallProgress, AcquireProgress
from .telemetry import get as get_telemetry
from .SimpleGtk3builderApp import SimpleGtkbuilderApp
import gettext
from .DistUpgradeGettext import gettext as _
class GtkCdromProgressAdapter(apt.progress.base.CdromProgress):
""" Report the cdrom add progress
Subclass this class to implement cdrom add progress reporting
"""
def __init__(self, parent):
self.status = parent.label_status
self.progress = parent.progressbar_cache
self.parent = parent
def update(self, text, step):
""" update is called regularly so that the gui can be redrawn """
if text:
self.status.set_text(text)
self.progress.set_fraction(step.value/float(self.totalSteps))
while Gtk.events_pending():
Gtk.main_iteration()
def ask_cdrom_name(self):
return (False, "")
def change_cdrom(self):
return False
class GtkOpProgress(apt.progress.base.OpProgress):
def __init__(self, progressbar):
self.progressbar = progressbar
#self.progressbar.set_pulse_step(0.01)
#self.progressbar.pulse()
self.fraction = 0.0
def update(self, percent=None):
super(GtkOpProgress, self).update(percent)
#if self.percent > 99:
# self.progressbar.set_fraction(1)
#else:
# self.progressbar.pulse()
new_fraction = self.percent/100.0
if abs(self.fraction-new_fraction) > 0.1:
self.fraction = new_fraction
self.progressbar.set_fraction(self.fraction)
while Gtk.events_pending():
Gtk.main_iteration()
def done(self):
self.progressbar.set_text(" ")
class GtkAcquireProgressAdapter(AcquireProgress):
# FIXME: we really should have some sort of "we are at step"
# xy in the gui
# FIXME2: we need to thing about mediaCheck here too
def __init__(self, parent):
super(GtkAcquireProgressAdapter, self).__init__()
# if this is set to false the download will cancel
self.status = parent.label_status
self.progress = parent.progressbar_cache
self.parent = parent
self.canceled = False
self.button_cancel = parent.button_fetch_cancel
self.button_cancel.connect('clicked', self.cancelClicked)
def cancelClicked(self, widget):
logging.debug("cancelClicked")
self.canceled = True
def media_change(self, medium, drive):
#print("mediaChange %s %s" % (medium, drive))
msg = _("Please insert '%s' into the drive '%s'") % (medium,drive)
dialog = Gtk.MessageDialog(parent=self.parent.window_main,
flags=Gtk.DialogFlags.MODAL,
type=Gtk.MessageType.QUESTION,
buttons=Gtk.ButtonsType.OK_CANCEL)
dialog.set_markup(msg)
res = dialog.run()
dialog.set_title("")
dialog.destroy()
if res == Gtk.ResponseType.OK:
return True
return False
def start(self):
#logging.debug("start")
super(GtkAcquireProgressAdapter, self).start()
self.progress.set_fraction(0)
self.status.show()
self.button_cancel.show()
def stop(self):
#logging.debug("stop")
self.progress.set_text(" ")
self.status.set_text(_("Fetching is complete"))
self.button_cancel.hide()
def pulse(self, owner):
super(GtkAcquireProgressAdapter, self).pulse(owner)
# only update if there is a noticable change
if abs(self.percent-self.progress.get_fraction()*100.0) > 0.1:
self.progress.set_fraction(self.percent/100.0)
currentItem = self.current_items + 1
if currentItem > self.total_items:
currentItem = self.total_items
if self.current_cps > 0:
current_cps = apt_pkg.size_to_str(self.current_cps)
if isinstance(current_cps, bytes):
current_cps = current_cps.decode(
locale.getpreferredencoding())
self.status.set_text(_("Fetching file %li of %li at %sB/s") % (
currentItem, self.total_items, current_cps))
self.progress.set_text(_("About %s remaining") % FuzzyTimeToStr(
self.eta))
else:
self.status.set_text(_("Fetching file %li of %li") % (
currentItem, self.total_items))
self.progress.set_text(" ")
while Gtk.events_pending():
Gtk.main_iteration()
return (not self.canceled)
class GtkInstallProgressAdapter(InstallProgress):
# timeout with no status change when the terminal is expanded
# automatically
TIMEOUT_TERMINAL_ACTIVITY = 300
def __init__(self,parent):
InstallProgress.__init__(self)
self._cache = None
self.label_status = parent.label_status
self.progress = parent.progressbar_cache
self.expander = parent.expander_terminal
self.term = parent._term
self.term.connect("child-exited", self.child_exited)
self.parent = parent
# setup the child waiting
# some options for dpkg to make it die less easily
apt_pkg.config.set("DPkg::StopOnError","False")
def start_update(self):
InstallProgress.start_update(self)
self.finished = False
# FIXME: add support for the timeout
# of the terminal (to display something useful then)
# -> longer term, move this code into python-apt
self.label_status.set_text(_("Applying changes"))
self.progress.set_fraction(0.0)
self.progress.set_text(" ")
self.expander.set_sensitive(True)
self.term.show()
self.term.connect("contents-changed", self._on_term_content_changed)
# if no libgtk2-perl is installed show the terminal
frontend= os.environ.get("DEBIAN_FRONTEND") or "gnome"
if frontend == "gnome" and self._cache:
if (not "libgtk2-perl" in self._cache or
not self._cache["libgtk2-perl"].is_installed):
frontend = "dialog"
self.expander.set_expanded(True)
self.env = ["VTE_PTY_KEEP_FD=%s"% self.writefd,
"APT_LISTCHANGES_FRONTEND=none"]
if "DEBIAN_FRONTEND" not in os.environ:
self.env.append("DEBIAN_FRONTEND=%s" % frontend)
# do a bit of time-keeping
self.start_time = 0.0
self.time_ui = 0.0
self.last_activity = 0.0
def error(self, pkg, errormsg):
InstallProgress.error(self, pkg, errormsg)
logging.error("got an error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
# we do not report followup errors from earlier failures
if gettext.dgettext('dpkg', "dependency problems - leaving unconfigured") in errormsg:
return False
#self.expander_terminal.set_expanded(True)
self.parent.dialog_error.set_transient_for(self.parent.window_main)
summary = _("Could not install '%s'") % pkg
msg = _("The upgrade will continue but the '%s' package may not "
"be in a working state. Please consider submitting a "
"bug report about it.") % pkg
markup="%s\n\n%s" % (summary, msg)
self.parent.dialog_error.realize()
self.parent.dialog_error.set_title("")
self.parent.dialog_error.get_window().set_functions(Gdk.WMFunction.MOVE)
self.parent.label_error.set_markup(markup)
self.parent.textview_error.get_buffer().set_text(errormsg)
self.parent.scroll_error.show()
self.parent.dialog_error.run()
self.parent.dialog_error.hide()
def conffile(self, current, new):
logging.debug("got a conffile-prompt from dpkg for file: '%s'" % current)
start = time.time()
#self.expander.set_expanded(True)
prim = _("Replace the customized configuration file\n'%s'?") % current
sec = _("You will lose any changes you have made to this "
"configuration file if you choose to replace it with "
"a newer version.")
markup = "%s \n\n%s" % (prim, sec)
self.parent.label_conffile.set_markup(markup)
self.parent.dialog_conffile.set_title("")
self.parent.dialog_conffile.set_transient_for(self.parent.window_main)
# workaround silly dpkg
if not os.path.exists(current):
current = current+".dpkg-dist"
# now get the diff
if os.path.exists("/usr/bin/diff"):
cmd = ["/usr/bin/diff", "-u", current, new]
diff = subprocess.Popen(
cmd, stdout=subprocess.PIPE).communicate()[0]
diff = diff.decode("UTF-8", "replace")
self.parent.textview_conffile.get_buffer().set_text(diff)
else:
self.parent.textview_conffile.get_buffer().set_text(_("The 'diff' command was not found"))
res = self.parent.dialog_conffile.run()
self.parent.dialog_conffile.hide()
self.time_ui += time.time() - start
# if replace, send this to the terminal
if res == Gtk.ResponseType.YES:
self.term.feed_child("y\n", -1)
else:
self.term.feed_child("n\n", -1)
def fork(self):
pty = Vte.Pty.new_sync(Vte.PtyFlags.DEFAULT)
pid = os.fork()
if pid == 0:
# WORKAROUND for broken feisty vte where envv does not work)
for env in self.env:
(key, value) = env.split("=")
os.environ[key] = value
# MUST be called
pty.child_setup()
# force dpkg terminal messages untranslated for better bug
# duplication detection
os.environ["DPKG_UNTRANSLATED_MESSAGES"] = "1"
else:
self.term.set_pty(pty)
self.term.watch_child(pid)
return pid
def _on_term_content_changed(self, term):
""" helper function that is called when the terminal changed
to ensure that we have a accurate idea when something hangs
"""
self.last_activity = time.time()
self.activity_timeout_reported = False
def status_change(self, pkg, percent, status):
# start the timer when the first package changes its status
if self.start_time == 0.0:
#print("setting start time to %s" % self.start_time)
self.start_time = time.time()
# only update if there is a noticable change
if abs(percent-self.progress.get_fraction()*100.0) > 0.1:
self.progress.set_fraction(float(percent)/100.0)
self.label_status.set_text(status.strip())
# start showing when we gathered some data
if percent > 1.0:
delta = self.last_activity - self.start_time
# time wasted in conffile questions (or other ui activity)
delta -= self.time_ui
time_per_percent = (float(delta)/percent)
eta = (100.0 - percent) * time_per_percent
# only show if we have some sensible data (60sec < eta < 2days)
if eta > 61.0 and eta < (60*60*24*2):
self.progress.set_text(_("About %s remaining") % FuzzyTimeToStr(eta))
else:
self.progress.set_text(" ")
# 2 == WEBKIT_LOAD_FINISHED - the enums is not exposed via python
if (self.parent._webkit_view and
self.parent._webkit_view.get_property("load-status") == 2):
self.parent._webkit_view.execute_script('progress("%s")' % percent)
def child_exited(self, term, status=None):
# we need to capture the full status here (not only the WEXITSTATUS)
if status is None:
# COMPAT we must keep until 16.04
self.apt_status = term.get_child_exit_status()
else:
self.apt_status = status
self.finished = True
def wait_child(self):
while not self.finished:
self.update_interface()
return self.apt_status
def finish_update(self):
self.label_status.set_text("")
def update_interface(self):
InstallProgress.update_interface(self)
# check if we haven't started yet with packages, pulse then
if self.start_time == 0.0:
self.progress.pulse()
time.sleep(0.2)
# check about terminal activity
if self.last_activity > 0 and \
(self.last_activity + self.TIMEOUT_TERMINAL_ACTIVITY) < time.time():
if not self.activity_timeout_reported:
logging.warning("no activity on terminal for %s seconds (%s)" % (self.TIMEOUT_TERMINAL_ACTIVITY, self.label_status.get_text()))
self.activity_timeout_reported = True
self.parent.expander_terminal.set_expanded(True)
# process events
while Gtk.events_pending():
Gtk.main_iteration()
time.sleep(0.01)
class DistUpgradeVteTerminal(object):
def __init__(self, parent, term):
self.term = term
self.parent = parent
def call(self, cmd, hidden=False):
if vte291:
def wait_for_child(terminal, status):
#print("wait for child finished")
self.finished=True
else:
def wait_for_child(widget):
#print("wait for child finished")
self.finished=True
self.term.show()
self.term.connect("child-exited", wait_for_child)
self.parent.expander_terminal.set_sensitive(True)
if hidden==False:
self.parent.expander_terminal.set_expanded(True)
self.finished = False
if vte291:
(success, pid) = self.term.spawn_sync(
Vte.PtyFlags.DEFAULT,
"/",
cmd,
None,
0, # GLib.SpawnFlags
None, # child_setup
None, # child_setup_data
None, # GCancellable
)
else:
(success, pid) = self.term.fork_command_full(
Vte.PtyFlags.DEFAULT,
"/",
cmd,
None,
0, # GLib.SpawnFlags
None, # child_setup
None, # child_setup_data
)
if not success or pid < 0:
# error
return
while not self.finished:
while Gtk.events_pending():
Gtk.main_iteration()
time.sleep(0.1)
del self.finished
class HtmlView(object):
def __init__(self, webkit_view):
self._webkit_view = webkit_view
def open(self, url):
if not self._webkit_view:
return
try:
from gi.repository import WebKit2
assert WebKit2 # silence pep8
self._webkit_view.load_uri(url)
self._webkit_view.connect("load-changed", self._on_load_changed)
except ImportError:
self._webkit_view.open(url)
self._webkit_view.connect("load-finished", self._on_load_finished)
def show(self):
self._webkit_view.show()
def hide(self):
self._webkit_view.hide()
def _on_load_finished(self, view, frame):
view.show()
def _on_load_changed(self, view, event, data):
from gi.repository import WebKit2
if event == WebKit2.LoadEvent.LOAD_FINISHED:
view.show()
class DistUpgradeViewGtk3(DistUpgradeView,SimpleGtkbuilderApp):
" gtk frontend of the distUpgrade tool "
def __init__(self, datadir=None, logdir=None):
DistUpgradeView.__init__(self)
self.logdir = logdir
if not datadir or datadir == '.':
localedir=os.path.join(os.getcwd(),"mo")
gladedir=os.getcwd()
else:
localedir="/usr/share/locale/"
gladedir=os.path.join(datadir, "gtkbuilder")
# check if we have a display etc
Gtk.init_check(sys.argv)
get_telemetry().set_updater_type('GTK')
try:
locale.bindtextdomain("ubuntu-release-upgrader",localedir)
gettext.textdomain("ubuntu-release-upgrader")
except Exception as e:
logging.warning("Error setting locales (%s)" % e)
SimpleGtkbuilderApp.__init__(self,
gladedir+"/DistUpgrade.ui",
"ubuntu-release-upgrader")
icons = Gtk.IconTheme.get_default()
try:
self.window_main.set_default_icon(icons.load_icon("system-software-update", 32, 0))
except GObject.GError as e:
logging.debug("error setting default icon, ignoring (%s)" % e)
pass
# terminal stuff
self.create_terminal()
self.prev_step = None # keep a record of the latest step
# we don't use this currently
#self.window_main.set_keep_above(True)
self.icontheme = Gtk.IconTheme.get_default()
self._webkit_view = None
self.window_main.realize()
self.window_main.get_window().set_functions(Gdk.WMFunction.MOVE)
self._opCacheProgress = GtkOpProgress(self.progressbar_cache)
self._acquireProgress = GtkAcquireProgressAdapter(self)
self._cdromProgress = GtkCdromProgressAdapter(self)
self._installProgress = GtkInstallProgressAdapter(self)
# details dialog
self.details_list = Gtk.TreeStore(GObject.TYPE_STRING)
column = Gtk.TreeViewColumn("")
render = Gtk.CellRendererText()
column.pack_start(render, True)
column.add_attribute(render, "markup", 0)
self.treeview_details.append_column(column)
self.details_list.set_sort_column_id(0, Gtk.SortType.ASCENDING)
self.treeview_details.set_model(self.details_list)
# lp: #1072460
self.dialog_changes.set_resizable(False)
def _activated(w):
# the *current* expanded state which will change after the signal
expanded = self.expander_details.get_expanded()
self.dialog_changes.set_resizable(not expanded)
self.expander_details.connect("activate", _activated)
# FIXME: portme
# Use italic style in the status labels
#attrlist=Pango.AttrList()
#attr = Pango.AttrStyle(Pango.Style.ITALIC, 0, -1)
#attr = Pango.AttrScale(Pango.SCALE_SMALL, 0, -1)
#attrlist.insert(attr)
#self.label_status.set_property("attributes", attrlist)
# reasonable fault handler
sys.excepthook = self._handleException
def _handleException(self, type, value, tb):
# we handle the exception here, hand it to apport and run the
# apport gui manually after it because we kill u-n during the upgrade
# to prevent it from poping up for reboot notifications or FF restart
# notifications or somesuch
import traceback
lines = traceback.format_exception(type, value, tb)
logging.error("not handled exception:\n%s" % "\n".join(lines))
# we can't be sure that apport will run in the middle of a upgrade
# so we still show a error message here
apport_crash(type, value, tb)
if not run_apport():
self.error(_("A fatal error occurred"),
_("Please report this as a bug (if you haven't already) and include the "
"files /var/log/dist-upgrade/main.log and "
"/var/log/dist-upgrade/apt.log "
"in your report. The upgrade has aborted.\n"
"Your original sources.list was saved in "
"/etc/apt/sources.list.distUpgrade."),
"\n".join(lines))
sys.exit(1)
def getTerminal(self):
return DistUpgradeVteTerminal(self, self._term)
def getHtmlView(self):
if self._webkit_view is None:
try:
try:
from gi.repository import WebKit2 as WebKit
except ImportError:
from gi.repository import WebKit
self._webkit_view = WebKit.WebView()
settings = self._webkit_view.get_settings()
settings.set_property("enable-plugins", False)
self.vbox_main.pack_end(self._webkit_view, True, True, 0)
except:
logging.exception("html widget")
return DistUpgradeView.DummyHtmlView()
return HtmlView(self._webkit_view)
def _key_press_handler(self, widget, keyev):
# user pressed ctrl-c
if len(keyev.string) == 1 and ord(keyev.string) == 3:
summary = _("Ctrl-c pressed")
msg = _("This will abort the operation and may leave the system "
"in a broken state. Are you sure you want to do that?")
res = self.askYesNoQuestion(summary, msg)
logging.warning("ctrl-c press detected, user decided to pass it "
"on: %s", res)
return not res
return False
def create_terminal(self):
" helper to create a vte terminal "
self._term = Vte.Terminal.new()
# COMPAT that must be kept until 16.04
if not hasattr(self._term, "set_pty"):
self._term.set_pty = self._term.set_pty_object
self._term.connect("key-press-event", self._key_press_handler)
fontdesc = Pango.font_description_from_string("monospace 10")
self._term.set_font(fontdesc)
self._terminal_lines = []
self.hbox_custom.pack_start(self._term, True, True, 0)
self._term.realize()
self.vscrollbar_terminal = Gtk.VScrollbar()
self.vscrollbar_terminal.show()
self.hbox_custom.pack_start(self.vscrollbar_terminal, True, True, 0)
self.vscrollbar_terminal.set_adjustment(self._term.get_vadjustment())
try:
self._terminal_log = open(os.path.join(self.logdir,"term.log"),"w")
except Exception:
# if something goes wrong (permission denied etc), use stdout
self._terminal_log = sys.stdout
return self._term
def getAcquireProgress(self):
return self._acquireProgress
def getInstallProgress(self, cache):
self._installProgress._cache = cache
return self._installProgress
def getOpCacheProgress(self):
return self._opCacheProgress
def getCdromProgress(self):
return self._cdromProgress
def updateStatus(self, msg):
self.label_status.set_text("%s" % msg)
def hideStep(self, step):
image = getattr(self,"image_step%i" % step.value)
label = getattr(self,"label_step%i" % step.value)
#arrow = getattr(self,"arrow_step%i" % step.value)
image.hide()
label.hide()
def showStep(self, step):
image = getattr(self,"image_step%i" % step.value)
label = getattr(self,"label_step%i" % step.value)
image.show()
label.show()
def abort(self):
size = Gtk.IconSize.MENU
step = self.prev_step
if step:
image = getattr(self,"image_step%i" % step.value)
arrow = getattr(self,"arrow_step%i" % step.value)
image.set_from_stock(Gtk.STOCK_CANCEL, size)
image.show()
arrow.hide()
def setStep(self, step):
super(DistUpgradeViewGtk3, self).setStep(step)
if self.icontheme.rescan_if_needed():
logging.debug("icon theme changed, re-reading")
# first update the "previous" step as completed
size = Gtk.IconSize.MENU
attrlist=Pango.AttrList()
if self.prev_step:
image = getattr(self,"image_step%i" % self.prev_step.value)
label = getattr(self,"label_step%i" % self.prev_step.value)
arrow = getattr(self,"arrow_step%i" % self.prev_step.value)
label.set_property("attributes",attrlist)
image.set_from_stock(Gtk.STOCK_APPLY, size)
image.show()
arrow.hide()
self.prev_step = step
# show the an arrow for the current step and make the label bold
image = getattr(self,"image_step%i" % step.value)
label = getattr(self,"label_step%i" % step.value)
arrow = getattr(self,"arrow_step%i" % step.value)
# check if that step was not hidden with hideStep()
if not label.get_property("visible"):
return
arrow.show()
image.hide()
# FIXME: portme
#attr = Pango.AttrWeight(Pango.Weight.BOLD, 0, -1)
#attrlist.insert(attr)
#label.set_property("attributes",attrlist)
def information(self, summary, msg, extended_msg=None):
self.dialog_information.set_title("")
self.dialog_information.set_transient_for(self.window_main)
msg = "%s\n\n%s" % (summary,msg)
self.label_information.set_markup(msg)
if extended_msg != None:
buffer = self.textview_information.get_buffer()
buffer.set_text(extended_msg)
self.scroll_information.show()
else:
self.scroll_information.hide()
self.dialog_information.realize()
self.dialog_information.get_window().set_functions(Gdk.WMFunction.MOVE)
self.dialog_information.run()
self.dialog_information.hide()
while Gtk.events_pending():
Gtk.main_iteration()
def error(self, summary, msg, extended_msg=None):
self.dialog_error.set_title("")
self.dialog_error.set_transient_for(self.window_main)
#self.expander_terminal.set_expanded(True)
msg="%s\n\n%s" % (summary, msg)
self.label_error.set_markup(msg)
if extended_msg != None:
buffer = self.textview_error.get_buffer()
buffer.set_text(extended_msg)
self.scroll_error.show()
else:
self.scroll_error.hide()
self.dialog_error.realize()
self.dialog_error.get_window().set_functions(Gdk.WMFunction.MOVE)
self.dialog_error.run()
self.dialog_error.hide()
return False
def confirmChanges(self, summary, changes, demotions, downloadSize,
actions=None, removal_bold=True):
# FIXME: add a whitelist here for packages that we expect to be
# removed (how to calc this automatically?)
if not DistUpgradeView.confirmChanges(self, summary, changes,
demotions, downloadSize):
return False
# append warning
self.confirmChangesMessage += "\n\n%s" % \
_("To prevent data loss close all open "
"applications and documents.")
if actions != None:
self.button_cancel_changes.set_use_stock(False)
self.button_cancel_changes.set_use_underline(True)
self.button_cancel_changes.set_label(actions[0])
self.button_confirm_changes.set_label(actions[1])
self.label_summary.set_markup("%s" % summary)
self.label_changes.set_markup(self.confirmChangesMessage)
# fill in the details
self.details_list.clear()
for (parent_text, details_list) in (
( _("No longer supported by Canonical (%s)"), self.demotions),
( _("Downgrade (%s)"), self.toDowngrade),
( _("Remove (%s)"), self.toRemove),
( _("No longer needed (%s)"), self.toRemoveAuto),
( _("Install (%s)"), self.toInstall),
( _("Upgrade (%s)"), self.toUpgrade),
):
if details_list:
node = self.details_list.append(None,
[parent_text % len(details_list)])
for pkg in details_list:
self.details_list.append(node, ["%s - %s" % (
pkg.name, GLib.markup_escape_text(getattr(pkg.candidate, "summary", None)))])
# prepare dialog
self.dialog_changes.realize()
self.dialog_changes.set_transient_for(self.window_main)
self.dialog_changes.set_title("")
self.dialog_changes.get_window().set_functions(Gdk.WMFunction.MOVE|
Gdk.WMFunction.RESIZE)
res = self.dialog_changes.run()
self.dialog_changes.hide()
if res == Gtk.ResponseType.YES:
return True
return False
def askYesNoQuestion(self, summary, msg, default='No'):
msg = "%s\n\n%s" % (summary,msg)
dialog = Gtk.MessageDialog(parent=self.window_main,
flags=Gtk.DialogFlags.MODAL,
type=Gtk.MessageType.QUESTION,
buttons=Gtk.ButtonsType.YES_NO)
dialog.set_title("")
if default == 'No':
dialog.set_default_response(Gtk.ResponseType.NO)
else:
dialog.set_default_response(Gtk.ResponseType.YES)
dialog.set_markup(msg)
res = dialog.run()
dialog.destroy()
if res == Gtk.ResponseType.YES:
return True
return False
def confirmRestart(self):
self.dialog_restart.set_transient_for(self.window_main)
self.dialog_restart.set_title("")
self.dialog_restart.realize()
self.dialog_restart.get_window().set_functions(Gdk.WMFunction.MOVE)
res = self.dialog_restart.run()
self.dialog_restart.hide()
if res == Gtk.ResponseType.YES:
return True
return False
def processEvents(self):
while Gtk.events_pending():
Gtk.main_iteration()
def pulseProgress(self, finished=False):
self.progressbar_cache.pulse()
if finished:
self.progressbar_cache.set_fraction(1.0)
def on_window_main_delete_event(self, widget, event):
self.dialog_cancel.set_transient_for(self.window_main)
self.dialog_cancel.set_title("")
self.dialog_cancel.realize()
self.dialog_cancel.get_window().set_functions(Gdk.WMFunction.MOVE)
res = self.dialog_cancel.run()
self.dialog_cancel.hide()
if res == Gtk.ResponseType.CANCEL:
sys.exit(1)
return True
if __name__ == "__main__":
view = DistUpgradeViewGtk3()
fp = GtkAcquireProgressAdapter(view)
ip = GtkInstallProgressAdapter(view)
view.getTerminal().call(["/usr/bin/dpkg","--configure","-a"])
Gtk.main()
sys.exit(0)
cache = apt.Cache()
for pkg in sys.argv[1:]:
if cache[pkg].is_installed:
cache[pkg].mark_delete()
else:
cache[pkg].mark_install()
cache.commit(fp,ip)
Gtk.main()
#sys.exit(0)
ip.conffile("TODO","TODO~")
view.getTerminal().call(["/usr/bin/dpkg","--configure","-a"])
#view.getTerminal().call(["ls","-R","/usr"])
view.error("short","long",
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
)
view.confirmChanges("xx",[], 100)
./DistUpgradeViewKDE.py 0000644 0000000 0000000 00000117572 13502463664 013703 0 ustar root root # DistUpgradeViewKDE.py
#
# Copyright (c) 2007 Canonical Ltd
# Copyright (c) 2014-2018 Harald Sitter
#
# Author: Jonathan Riddell
#
# 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
try:
# 14.04 has a broken pyqt5, so don't even try to import it and require
# pyqt4.
# In 14.04 various signals in pyqt5 can not be connected because it thinks
# the signal does not exist or has an incompatible signature. Since this
# potentially renders the GUI entirely broken and pyqt5 was not actively
# used back then it is fair to simply require qt4 on trusty systems.
from .utils import get_dist
if get_dist() == 'trusty':
raise ImportError
from PyQt5 import uic
from PyQt5.QtCore import Qt, QLocale, QTranslator, PYQT_VERSION, QTimer
from PyQt5.QtWidgets import QTextEdit, QApplication, QDialog,\
QMessageBox, QDialogButtonBox, QTreeWidgetItem, QPushButton, QWidget,\
QHBoxLayout, QLabel
from PyQt5.QtGui import QTextOption, QPixmap, QIcon, QTextCursor
except ImportError:
from PyQt4 import uic
from PyQt4.QtCore import Qt, QLocale, QTranslator, PYQT_VERSION, QTimer
from PyQt4.QtGui import QTextEdit, QDialog, QTextOption, QApplication,\
QMessageBox, QDialogButtonBox, QTreeWidgetItem, QPixmap, QIcon,\
QPushButton, QWidget, QTextCursor, QHBoxLayout, QLabel
# If we still throw an exception, bounce back to Main to try another UI.
import sys
import locale
import logging
import time
import subprocess
import traceback
import apt
import apt_pkg
import shlex # for osrelease
import os
import pty
from .DistUpgradeApport import run_apport, apport_crash
from .DistUpgradeView import DistUpgradeView, FuzzyTimeToStr, InstallProgress, AcquireProgress
from .telemetry import get as get_telemetry
import select
import gettext
from .DistUpgradeGettext import gettext as _
from .DistUpgradeGettext import unicode_gettext
from .QUrlOpener import QUrlOpener
# FIXME: what's the purpose?
def utf8(s, errors="strict"):
if isinstance(s, bytes):
return s.decode("UTF-8", errors)
else:
return s
# FIXME: what's the purpose?
def loadUi(file, parent):
if os.path.exists(file):
uic.loadUi(file, parent)
else:
#FIXME find file
print("error, can't find file: " + file)
def _find_pixmap(path):
if os.path.exists(path):
return QPixmap(path)
return None
def _icon(name, fallbacks = []):
if type(PYQT_VERSION) == int:
return QIcon.fromTheme(name)
else:
for path in fallbacks:
pixmap = _find_pixmap(path)
if pixmap:
return QIcon(pixmap)
return None
# QWidget adjustSize when run on a maximized window will make Qt 5.9, earlier,
# and probably also later, lose its state. Qt will think the window is no longer
# maximized, while in fact it is. This results in parts of the window no longer
# getting redrawn as the window manager will think it maximized but Qt thinks it
# is not and thus not send repaints for the regions it thinks do not exist.
# To prevent this from happening monkey patch adjustSize to not ever run on
# maximized windows.
def adjustSize(self):
if not self.isMaximized():
self.origAdjustSize(self)
QWidget.origAdjustSize = QWidget.adjustSize
QWidget.adjustSize = adjustSize
class _OSRelease:
DEFAULT_OS_RELEASE_FILE = '/etc/os-release'
OS_RELEASE_FILE = '/etc/os-release'
def __init__(self, lsb_compat=True):
self.result = {}
self.valid = False
self.file = _OSRelease.OS_RELEASE_FILE
if not os.path.isfile(self.file):
return
self.parse()
self.valid = True
if lsb_compat:
self.inject_lsb_compat()
def inject_lsb_compat(self):
self.result['Distributor ID'] = self.result['ID']
self.result['Description'] = self.result['PRETTY_NAME']
# Optionals as per os-release spec.
self.result['Codename'] = self.result.get('VERSION_CODENAME')
if not self.result['Codename']:
# Transient Ubuntu 16.04 field (LP: #1598212)
self.result['Codename'] = self.result.get('UBUNTU_CODENAME')
self.result['Release'] = self.result.get('VERSION_ID')
def parse(self):
f = open(self.file, 'r')
for line in f:
line = line.strip()
if not line:
continue
self.parse_entry(*line.split('=', 1))
f.close()
def parse_entry(self, key, value):
value = self.parse_value(value) # Values can be shell strings...
if key == "ID_LIKE" and isinstance(value, str):
# ID_LIKE is specified as quoted space-separated list. This will
# be parsed as string that we need to split manually.
value = value.split(' ')
self.result[key] = value
def parse_value(self, value):
values = shlex.split(value)
if len(values) == 1:
return values[0]
return values
class DumbTerminal(QTextEdit):
""" A very dumb terminal """
def __init__(self, installProgress, parent_frame):
" really dumb terminal with simple editing support "
QTextEdit.__init__(self, "", parent_frame)
self.installProgress = installProgress
self.setFontFamily("Monospace")
# FIXME: fixed font size set!!!
self.setFontPointSize(8)
self.setWordWrapMode(QTextOption.NoWrap)
self.setUndoRedoEnabled(False)
self.setOverwriteMode(True)
self._block = False
#self.connect(self, SIGNAL("cursorPositionChanged()"),
# self.onCursorPositionChanged)
def fork(self):
"""pty voodoo"""
(self.child_pid, self.installProgress.master_fd) = pty.fork()
if self.child_pid == 0:
os.environ["TERM"] = "dumb"
return self.child_pid
def update_interface(self):
(rlist, wlist, xlist) = select.select([self.installProgress.master_fd],[],[], 0)
if len(rlist) > 0:
line = os.read(self.installProgress.master_fd, 255)
self.insertWithTermCodes(utf8(line))
QApplication.processEvents()
def insertWithTermCodes(self, text):
""" support basic terminal codes """
display_text = ""
for c in text:
# \b - backspace - this seems to comes as "^H" now ??!
if ord(c) == 8:
self.insertPlainText(display_text)
self.textCursor().deletePreviousChar()
display_text=""
# \r - is filtered out
elif c == chr(13):
pass
# \a - bell - ignore for now
elif c == chr(7):
pass
else:
display_text += c
self.insertPlainText(display_text)
def keyPressEvent(self, ev):
""" send (ascii) key events to the pty """
# no master_fd yet
if not hasattr(self.installProgress, "master_fd"):
return
# special handling for backspace
if ev.key() == Qt.Key_Backspace:
#print("sent backspace")
os.write(self.installProgress.master_fd, chr(8))
return
# do nothing for events like "shift"
if not ev.text():
return
# now sent the key event to the termianl as utf-8
os.write(self.installProgress.master_fd, ev.text().toUtf8())
def onCursorPositionChanged(self):
""" helper that ensures that the cursor is always at the end """
if self._block:
return
# block signals so that we do not run into a recursion
self._block = True
self.moveCursor(QTextCursor.End)
self._block = False
class KDECdromProgressAdapter(apt.progress.base.CdromProgress):
""" Report the cdrom add progress """
def __init__(self, parent):
self.status = parent.window_main.label_status
self.progressbar = parent.window_main.progressbar_cache
self.parent = parent
def update(self, text, step):
""" update is called regularly so that the gui can be redrawn """
if text:
self.status.setText(text)
self.progressbar.setValue(step.value/float(self.totalSteps))
QApplication.processEvents()
def ask_cdrom_name(self):
return (False, "")
def change_cdrom(self):
return False
class KDEOpProgress(apt.progress.base.OpProgress):
""" methods on the progress bar """
def __init__(self, progressbar, progressbar_label):
self.progressbar = progressbar
self.progressbar_label = progressbar_label
#self.progressbar.set_pulse_step(0.01)
#self.progressbar.pulse()
def update(self, percent=None):
super(KDEOpProgress, self).update(percent)
#if self.percent > 99:
# self.progressbar.set_fraction(1)
#else:
# self.progressbar.pulse()
#self.progressbar.set_fraction(self.percent/100.0)
self.progressbar.setValue(self.percent)
QApplication.processEvents()
def done(self):
self.progressbar_label.setText("")
class KDEAcquireProgressAdapter(AcquireProgress):
""" methods for updating the progress bar while fetching packages """
# FIXME: we really should have some sort of "we are at step"
# xy in the gui
# FIXME2: we need to thing about mediaCheck here too
def __init__(self, parent):
AcquireProgress.__init__(self)
# if this is set to false the download will cancel
self.status = parent.window_main.label_status
self.progress = parent.window_main.progressbar_cache
self.parent = parent
def media_change(self, medium, drive):
msg = _("Please insert '%s' into the drive '%s'") % (medium,drive)
change = QMessageBox.question(self.parent.window_main, _("Media Change"), msg, QMessageBox.Ok, QMessageBox.Cancel)
if change == QMessageBox.Ok:
return True
return False
def start(self):
AcquireProgress.start(self)
#self.progress.show()
self.progress.setValue(0)
self.status.show()
def stop(self):
self.parent.window_main.progress_text.setText(" ")
self.status.setText(_("Fetching is complete"))
def pulse(self, owner):
""" we don't have a mainloop in this application, we just call processEvents here and elsewhere"""
# FIXME: move the status_str and progress_str into python-apt
# (python-apt need i18n first for this)
AcquireProgress.pulse(self, owner)
self.progress.setValue(self.percent)
current_item = self.current_items + 1
if current_item > self.total_items:
current_item = self.total_items
if self.current_cps > 0:
current_cps = apt_pkg.size_to_str(self.current_cps)
if isinstance(current_cps, bytes):
current_cps = current_cps.decode(locale.getpreferredencoding())
self.status.setText(_("Fetching file %li of %li at %sB/s") % (current_item, self.total_items, current_cps))
self.parent.window_main.progress_text.setText("" + _("About %s remaining") % FuzzyTimeToStr(self.eta) + "")
else:
self.status.setText(_("Fetching file %li of %li") % (current_item, self.total_items))
self.parent.window_main.progress_text.setText(" ")
QApplication.processEvents()
return True
class KDEInstallProgressAdapter(InstallProgress):
"""methods for updating the progress bar while installing packages"""
# timeout with no status change when the terminal is expanded
# automatically
TIMEOUT_TERMINAL_ACTIVITY = 240
def __init__(self,parent):
InstallProgress.__init__(self)
self._cache = None
self.label_status = parent.window_main.label_status
self.progress = parent.window_main.progressbar_cache
self.progress_text = parent.window_main.progress_text
self.parent = parent
try:
self._terminal_log = open("/var/log/dist-upgrade/term.log","wb")
except Exception as e:
# if something goes wrong (permission denied etc), use stdout
logging.error("Can not open terminal log: '%s'" % e)
if sys.version >= '3':
self._terminal_log = sys.stdout.buffer
else:
self._terminal_log = sys.stdout
# some options for dpkg to make it die less easily
apt_pkg.config.set("DPkg::StopOnError","False")
def start_update(self):
InstallProgress.start_update(self)
self.finished = False
# FIXME: add support for the timeout
# of the terminal (to display something useful then)
# -> longer term, move this code into python-apt
self.label_status.setText(_("Applying changes"))
self.progress.setValue(0)
self.progress_text.setText(" ")
# do a bit of time-keeping
self.start_time = 0.0
self.time_ui = 0.0
self.last_activity = 0.0
self.parent.window_main.showTerminalButton.setEnabled(True)
def error(self, pkg, errormsg):
InstallProgress.error(self, pkg, errormsg)
logging.error("got an error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
# we do not report followup errors from earlier failures
if gettext.dgettext('dpkg', "dependency problems - leaving unconfigured") in errormsg:
return False
summary = _("Could not install '%s'") % pkg
msg = _("The upgrade will continue but the '%s' package may not "
"be in a working state. Please consider submitting a "
"bug report about it.") % pkg
msg = "%s
%s" % (summary, msg)
dialogue = QDialog(self.parent.window_main)
loadUi("dialog_error.ui", dialogue)
self.parent.translate_widget_children(dialogue)
dialogue.label_error.setText(msg)
if errormsg != None:
dialogue.textview_error.setText(errormsg)
dialogue.textview_error.show()
else:
dialogue.textview_error.hide()
# Make sure we have a suitable size depending on whether or not the view is shown
dialogue.adjustSize()
dialogue.exec_()
def conffile(self, current, new):
"""ask question in case conffile has been changed by user"""
logging.debug("got a conffile-prompt from dpkg for file: '%s'" % current)
start = time.time()
prim = _("Replace the customized configuration file\n'%s'?") % current
sec = _("You will lose any changes you have made to this "
"configuration file if you choose to replace it with "
"a newer version.")
markup = "%s \n\n%s" % (prim, sec)
self.confDialogue = QDialog(self.parent.window_main)
loadUi("dialog_conffile.ui", self.confDialogue)
self.confDialogue.label_conffile.setText(markup)
self.confDialogue.textview_conffile.hide()
#FIXME, below to be tested
#self.confDialogue.resize(self.confDialogue.minimumSizeHint())
self.confDialogue.show_difference_button.clicked.connect(self.showConffile)
# workaround silly dpkg
if not os.path.exists(current):
current = current+".dpkg-dist"
# now get the diff
if os.path.exists("/usr/bin/diff"):
cmd = ["/usr/bin/diff", "-u", current, new]
diff = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
diff = diff.decode("UTF-8", "replace")
self.confDialogue.textview_conffile.setText(diff)
else:
self.confDialogue.textview_conffile.setText(_("The 'diff' command was not found"))
result = self.confDialogue.exec_()
self.time_ui += time.time() - start
# if replace, send this to the terminal
if result == QDialog.Accepted:
os.write(self.master_fd, b"y\n")
else:
os.write(self.master_fd, b"n\n")
def showConffile(self):
if self.confDialogue.textview_conffile.isVisible():
self.confDialogue.textview_conffile.hide()
self.confDialogue.show_difference_button.setText(_("Show Difference >>>"))
else:
self.confDialogue.textview_conffile.show()
self.confDialogue.show_difference_button.setText(_("<<< Hide Difference"))
def fork(self):
"""pty voodoo"""
(self.child_pid, self.master_fd) = pty.fork()
if self.child_pid == 0:
os.environ["TERM"] = "dumb"
if ("DEBIAN_FRONTEND" not in os.environ or
os.environ["DEBIAN_FRONTEND"] == "kde"):
os.environ["DEBIAN_FRONTEND"] = "noninteractive"
os.environ["APT_LISTCHANGES_FRONTEND"] = "none"
logging.debug(" fork pid is: %s" % self.child_pid)
return self.child_pid
def status_change(self, pkg, percent, status):
"""update progress bar and label"""
# start the timer when the first package changes its status
if self.start_time == 0.0:
#print("setting start time to %s" % self.start_time)
self.start_time = time.time()
self.progress.setValue(self.percent)
self.label_status.setText(utf8(status.strip()))
# start showing when we gathered some data
if percent > 1.0:
self.last_activity = time.time()
self.activity_timeout_reported = False
delta = self.last_activity - self.start_time
# time wasted in conffile questions (or other ui activity)
delta -= self.time_ui
time_per_percent = (float(delta)/percent)
eta = (100.0 - self.percent) * time_per_percent
# only show if we have some sensible data (60sec < eta < 2days)
if eta > 61.0 and eta < (60*60*24*2):
self.progress_text.setText(_("About %s remaining") % FuzzyTimeToStr(eta))
else:
self.progress_text.setText(" ")
def finish_update(self):
self.label_status.setText("")
def update_interface(self):
"""
no mainloop in this application, just call processEvents lots here
it's also important to sleep for a minimum amount of time
"""
# log the output of dpkg (on the master_fd) to the terminal log
while True:
try:
(rlist, wlist, xlist) = select.select([self.master_fd],[],[], 0)
if len(rlist) > 0:
line = os.read(self.master_fd, 255)
self._terminal_log.write(line)
self.parent.terminal_text.insertWithTermCodes(
utf8(line, errors="replace"))
else:
break
except Exception as e:
print(e)
logging.debug("error reading from self.master_fd '%s'" % e)
break
# now update the GUI
try:
InstallProgress.update_interface(self)
except ValueError as e:
logging.error("got ValueError from InstallProgress.update_interface. Line was '%s' (%s)" % (self.read, e))
# reset self.read so that it can continue reading and does not loop
self.read = ""
# check about terminal activity
if self.last_activity > 0 and \
(self.last_activity + self.TIMEOUT_TERMINAL_ACTIVITY) < time.time():
if not self.activity_timeout_reported:
#FIXME bug 95465, I can't recreate this, so here's a hacky fix
try:
logging.warning("no activity on terminal for %s seconds (%s)" % (self.TIMEOUT_TERMINAL_ACTIVITY, self.label_status.text()))
except UnicodeEncodeError:
logging.warning("no activity on terminal for %s seconds" % (self.TIMEOUT_TERMINAL_ACTIVITY))
self.activity_timeout_reported = True
self.parent.window_main.konsole_frame.show()
QApplication.processEvents()
time.sleep(0.02)
def wait_child(self):
while True:
self.update_interface()
(pid, res) = os.waitpid(self.child_pid,os.WNOHANG)
if pid == self.child_pid:
break
# we need the full status here (not just WEXITSTATUS)
return res
# inherit from the class created in window_main.ui
# to add the handler for closing the window
class UpgraderMainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
#uic.loadUi("window_main.ui", self)
loadUi("window_main.ui", self)
def setParent(self, parentRef):
self.parent = parentRef
def closeEvent(self, event):
close = self.parent.on_window_main_delete_event()
if close:
event.accept()
else:
event.ignore()
class DistUpgradeViewKDE(DistUpgradeView):
"""KDE frontend of the distUpgrade tool"""
def __init__(self, datadir=None, logdir=None):
DistUpgradeView.__init__(self)
get_telemetry().set_updater_type('KDE')
# silence the PyQt4 logger
logger = logging.getLogger("PyQt4")
logger.setLevel(logging.INFO)
if not datadir or datadir == '.':
localedir=os.path.join(os.getcwd(),"mo")
else:
localedir="/usr/share/locale/ubuntu-release-upgrader"
# FIXME: i18n must be somewhere relative do this dir
try:
gettext.bindtextdomain("ubuntu-release-upgrader", localedir)
gettext.textdomain("ubuntu-release-upgrader")
except Exception as e:
logging.warning("Error setting locales (%s)" % e)
# we test for DISPLAY here, QApplication does not throw a
# exception when run without DISPLAY but dies instead
if not "DISPLAY" in os.environ:
raise Exception("No DISPLAY in os.environ found")
# Force environment to make sure Qt uses suitable theming and UX.
os.environ["QT_PLATFORM_PLUGIN"] = "kde"
# For above settings to apply automatically we need to indicate that we
# are inside a full KDE session.
os.environ["KDE_FULL_SESSION"] = "TRUE"
# We also need to indicate version as otherwise KDElibs3 compatibility
# might kick in such as in QIconLoader.cpp:QString fallbackTheme.
os.environ["KDE_SESSION_VERSION"] = "5"
# Pretty much all of the above but for Qt5
os.environ["QT_QPA_PLATFORMTHEME"] = "kde"
self.app = QApplication(["ubuntu-release-upgrader"])
# Try to load default Qt translations so we don't have to worry about
# QStandardButton translations.
translator = QTranslator(self.app)
if type(PYQT_VERSION) == int:
translator.load(QLocale.system(), 'qt', '_', '/usr/share/qt5/translations')
else:
translator.load(QLocale.system(), 'qt', '_', '/usr/share/qt4/translations')
self.app.installTranslator(translator)
QUrlOpener().setupUrlHandles()
messageIcon = _icon("system-software-update",
fallbacks=["/usr/share/icons/oxygen/48x48/apps/system-software-update.png",
"/usr/share/icons/hicolor/48x48/apps/adept_manager.png"])
self.app.setWindowIcon(messageIcon)
self.window_main = UpgraderMainWindow()
self.window_main.setParent(self)
self.window_main.show()
self.prev_step = None # keep a record of the latest step
self._opCacheProgress = KDEOpProgress(self.window_main.progressbar_cache, self.window_main.progress_text)
self._acquireProgress = KDEAcquireProgressAdapter(self)
self._cdromProgress = KDECdromProgressAdapter(self)
self._installProgress = KDEInstallProgressAdapter(self)
# reasonable fault handler
sys.excepthook = self._handleException
self.window_main.showTerminalButton.setEnabled(False)
self.window_main.showTerminalButton.clicked.connect(self.showTerminal)
# init gettext
gettext.bindtextdomain("ubuntu-release-upgrader",localedir)
gettext.textdomain("ubuntu-release-upgrader")
self.translate_widget_children()
name = _OSRelease().result["PRETTY_NAME"]
if not name or name == "Ubuntu":
name = "Kubuntu"
self.window_main.label_title.setText(self.window_main.label_title.text().replace("Ubuntu", name))
# setup terminal text in hidden by default spot
self.window_main.konsole_frame.hide()
self.konsole_frame_layout = QHBoxLayout(self.window_main.konsole_frame)
self.window_main.konsole_frame.setMinimumSize(600, 400)
self.terminal_text = DumbTerminal(self._installProgress, self.window_main.konsole_frame)
self.konsole_frame_layout.addWidget(self.terminal_text)
self.terminal_text.show()
# for some reason we need to start the main loop to get everything displayed
# this app mostly works with processEvents but run main loop briefly to keep it happily displaying all widgets
QTimer.singleShot(10, self.exitMainLoop)
self.app.exec_()
def exitMainLoop(self):
print("exitMainLoop")
self.app.exit()
def translate_widget_children(self, parentWidget=None):
if parentWidget == None:
parentWidget = self.window_main
if isinstance(parentWidget, QDialog) or isinstance(parentWidget, QWidget):
if str(parentWidget.windowTitle()) == "Error":
parentWidget.setWindowTitle( gettext.dgettext("kdelibs", "Error"))
else:
parentWidget.setWindowTitle(_( str(parentWidget.windowTitle()) ))
if parentWidget.children() != None:
for widget in parentWidget.children():
self.translate_widget(widget)
self.translate_widget_children(widget)
def translate_widget(self, widget):
if isinstance(widget, QLabel) or isinstance(widget, QPushButton):
if str(widget.text()) == "&Cancel":
kdelibs = gettext.translation(
"kdelibs", gettext.textdomain("kdelibs"), fallback=True)
widget.setText(unicode_gettext(kdelibs, "&Cancel"))
elif str(widget.text()) == "&Close":
kdelibs = gettext.translation(
"kdelibs", gettext.textdomain("kdelibs"), fallback=True)
widget.setText(unicode_gettext(kdelibs, "&Close"))
elif str(widget.text()) != "":
widget.setText( _(str(widget.text())).replace("_", "&") )
def _handleException(self, exctype, excvalue, exctb):
"""Crash handler."""
if (issubclass(exctype, KeyboardInterrupt) or
issubclass(exctype, SystemExit)):
return
# we handle the exception here, hand it to apport and run the
# apport gui manually after it because we kill u-m during the upgrade
# to prevent it from popping up for reboot notifications or FF restart
# notifications or somesuch
lines = traceback.format_exception(exctype, excvalue, exctb)
logging.error("not handled exception in KDE frontend:\n%s" % "\n".join(lines))
# we can't be sure that apport will run in the middle of a upgrade
# so we still show a error message here
apport_crash(exctype, excvalue, exctb)
if not run_apport():
tbtext = ''.join(traceback.format_exception(exctype, excvalue, exctb))
dialog = QDialog(self.window_main)
loadUi("dialog_error.ui", dialog)
self.translate_widget_children(self.dialog)
dialog.crash_detail.setText(tbtext)
# Make sure we have a suitable size depending on whether or not the view is shown
dialog.adjustSize()
dialog.exec_()
sys.exit(1)
def showTerminal(self):
if self.window_main.konsole_frame.isVisible():
self.window_main.konsole_frame.hide()
self.window_main.showTerminalButton.setText(_("Show Terminal >>>"))
else:
self.window_main.konsole_frame.show()
self.window_main.showTerminalButton.setText(_("<<< Hide Terminal"))
self.window_main.adjustSize()
def getAcquireProgress(self):
return self._acquireProgress
def getInstallProgress(self, cache):
self._installProgress._cache = cache
return self._installProgress
def getOpCacheProgress(self):
return self._opCacheProgress
def getCdromProgress(self):
return self._cdromProgress
def update_status(self, msg):
self.window_main.label_status.setText(msg)
def hideStep(self, step):
image = getattr(self.window_main,"image_step%i" % step.value)
label = getattr(self.window_main,"label_step%i" % step.value)
image.hide()
label.hide()
def abort(self):
step = self.prev_step
if step:
image = getattr(self.window_main,"image_step%i" % step.value)
cancelIcon = _icon("dialog-cancel",
fallbacks=["/usr/share/icons/oxygen/16x16/actions/dialog-cancel.png",
"/usr/lib/kde4/share/icons/oxygen/16x16/actions/dialog-cancel.png",
"/usr/share/icons/crystalsvg/16x16/actions/cancel.png"])
image.setPixmap(cancelIcon.pixmap(16, 16))
image.show()
def setStep(self, step):
super(DistUpgradeViewKDE , self).setStep(step)
okIcon = _icon("dialog-ok",
fallbacks=["/usr/share/icons/oxygen/16x16/actions/dialog-ok.png",
"/usr/lib/kde4/share/icons/oxygen/16x16/actions/dialog-ok.png",
"/usr/share/icons/crystalsvg/16x16/actions/ok.png"])
arrowIcon = _icon("arrow-right",
fallbacks=["/usr/share/icons/oxygen/16x16/actions/arrow-right.png",
"/usr/lib/kde4/share/icons/oxygen/16x16/actions/arrow-right.png",
"/usr/share/icons/crystalsvg/16x16/actions/1rightarrow.png"])
if self.prev_step:
image = getattr(self.window_main,"image_step%i" % self.prev_step.value)
label = getattr(self.window_main,"label_step%i" % self.prev_step.value)
image.setPixmap(okIcon.pixmap(16, 16))
image.show()
##arrow.hide()
self.prev_step = step
# show the an arrow for the current step and make the label bold
image = getattr(self.window_main,"image_step%i" % step.value)
label = getattr(self.window_main,"label_step%i" % step.value)
image.setPixmap(arrowIcon.pixmap(16, 16))
image.show()
label.setText("" + label.text() + "")
def information(self, summary, msg, extended_msg=None):
msg = "%s
%s" % (summary,msg)
dialogue = QDialog(self.window_main)
loadUi("dialog_error.ui", dialogue)
self.translate_widget_children(dialogue)
dialogue.label_error.setText(msg)
if extended_msg != None:
dialogue.textview_error.setText(extended_msg)
dialogue.textview_error.show()
else:
dialogue.textview_error.hide()
dialogue.setWindowTitle(_("Information"))
messageIcon = _icon("dialog-information",
fallbacks=["/usr/share/icons/oxygen/48x48/status/dialog-information.png",
"/usr/lib/kde4/share/icons/oxygen/48x48/status/dialog-information.png",
"/usr/share/icons/crystalsvg/32x32/actions/messagebox_info.png"])
dialogue.image.setPixmap(messageIcon.pixmap(48, 48))
# Make sure we have a suitable size depending on whether or not the view is shown
dialogue.adjustSize()
dialogue.exec_()
def error(self, summary, msg, extended_msg=None):
msg="%s
%s" % (summary, msg)
dialogue = QDialog(self.window_main)
loadUi("dialog_error.ui", dialogue)
self.translate_widget_children(dialogue)
dialogue.label_error.setText(msg)
if extended_msg != None:
dialogue.textview_error.setText(extended_msg)
dialogue.textview_error.show()
else:
dialogue.textview_error.hide()
messageIcon = _icon("dialog-error",
fallbacks=["/usr/share/icons/oxygen/48x48/status/dialog-error.png",
"/usr/lib/kde4/share/icons/oxygen/48x48/status/dialog-error.png",
"/usr/share/icons/crystalsvg/32x32/actions/messagebox_critical.png"])
dialogue.image.setPixmap(messageIcon.pixmap(48, 48))
# Make sure we have a suitable size depending on whether or not the view is shown
dialogue.adjustSize()
dialogue.exec_()
return False
def confirmChanges(self, summary, changes, demotions, downloadSize,
actions=None, removal_bold=True):
"""show the changes dialogue"""
# FIXME: add a whitelist here for packages that we expect to be
# removed (how to calc this automatically?)
DistUpgradeView.confirmChanges(self, summary, changes, demotions,
downloadSize)
self.changesDialogue = QDialog(self.window_main)
loadUi("dialog_changes.ui", self.changesDialogue)
self.changesDialogue.treeview_details.hide()
self.changesDialogue.buttonBox.helpRequested.connect(self.showChangesDialogueDetails)
self.translate_widget_children(self.changesDialogue)
self.changesDialogue.buttonBox.button(QDialogButtonBox.Ok).setText(_("&Start Upgrade"))
self.changesDialogue.buttonBox.button(QDialogButtonBox.Help).setIcon(QIcon())
self.changesDialogue.buttonBox.button(QDialogButtonBox.Help).setText(_("Details") + " >>>")
messageIcon = _icon("dialog-warning",
fallbacks=["/usr/share/icons/oxygen/48x48/status/dialog-warning.png",
"/usr/lib/kde4/share/icons/oxygen/48x48/status/dialog-warning.png",
"/usr/share/icons/crystalsvg/32x32/actions/messagebox_warning.png"])
self.changesDialogue.question_pixmap.setPixmap(messageIcon.pixmap(48, 48))
if actions != None:
cancel = actions[0].replace("_", "")
self.changesDialogue.buttonBox.button(QDialogButtonBox.Cancel).setText(cancel)
confirm = actions[1].replace("_", "")
self.changesDialogue.buttonBox.button(QDialogButtonBox.Ok).setText(confirm)
summaryText = "%s" % summary
self.changesDialogue.label_summary.setText(summaryText)
self.changesDialogue.label_changes.setText(self.confirmChangesMessage)
# fill in the details
self.changesDialogue.treeview_details.clear()
self.changesDialogue.treeview_details.setHeaderLabels(["Packages"])
self.changesDialogue.treeview_details.header().hide()
for demoted in self.demotions:
self.changesDialogue.treeview_details.insertTopLevelItem(0, QTreeWidgetItem(self.changesDialogue.treeview_details, [_("No longer supported %s") % demoted.name]) )
for rm in self.toRemove:
self.changesDialogue.treeview_details.insertTopLevelItem(0, QTreeWidgetItem(self.changesDialogue.treeview_details, [_("Remove %s") % rm.name]) )
for rm in self.toRemoveAuto:
self.changesDialogue.treeview_details.insertTopLevelItem(0, QTreeWidgetItem(self.changesDialogue.treeview_details, [_("Remove (was auto installed) %s") % rm.name]) )
for inst in self.toInstall:
self.changesDialogue.treeview_details.insertTopLevelItem(0, QTreeWidgetItem(self.changesDialogue.treeview_details, [_("Install %s") % inst.name]) )
for up in self.toUpgrade:
self.changesDialogue.treeview_details.insertTopLevelItem(0, QTreeWidgetItem(self.changesDialogue.treeview_details, [_("Upgrade %s") % up.name]) )
# Use a suitable size for the window given the current content.
self.changesDialogue.adjustSize()
#FIXME resize label, stop it being shrinkable
res = self.changesDialogue.exec_()
if res == QDialog.Accepted:
return True
return False
def showChangesDialogueDetails(self):
if self.changesDialogue.treeview_details.isVisible():
self.changesDialogue.treeview_details.hide()
self.changesDialogue.buttonBox.button(QDialogButtonBox.Help).setText(_("Details") + " >>>")
else:
self.changesDialogue.treeview_details.show()
self.changesDialogue.buttonBox.button(QDialogButtonBox.Help).setText("<<< " + _("Details"))
self.changesDialogue.adjustSize()
def askYesNoQuestion(self, summary, msg, default='No'):
answer = QMessageBox.question(self.window_main, summary, "" + msg, QMessageBox.Yes|QMessageBox.No, QMessageBox.No)
if answer == QMessageBox.Yes:
return True
return False
def confirmRestart(self):
messageBox = QMessageBox(QMessageBox.Question, _("Restart required"), _("Restart the system to complete the upgrade"), QMessageBox.NoButton, self.window_main)
yesButton = messageBox.addButton(QMessageBox.Yes)
noButton = messageBox.addButton(QMessageBox.No)
yesButton.setText(_("_Restart Now").replace("_", "&"))
noButton.setText(gettext.dgettext("kdelibs", "&Close"))
answer = messageBox.exec_()
if answer == QMessageBox.Yes:
return True
return False
def processEvents(self):
QApplication.processEvents()
def pulseProgress(self, finished=False):
# FIXME: currently we do nothing here because this is
# run in a different python thread and QT explodes if the UI is
# touched from a non QThread
pass
def on_window_main_delete_event(self):
#FIXME make this user friendly
text = _("""Cancel the running upgrade?
The system could be in an unusable state if you cancel the upgrade. You are strongly advised to resume the upgrade.""")
text = text.replace("\n", "
")
cancel = QMessageBox.warning(self.window_main, _("Cancel Upgrade?"), text, QMessageBox.Yes, QMessageBox.No)
if cancel == QMessageBox.Yes:
return True
return False
if __name__ == "__main__":
view = DistUpgradeViewKDE()
view.askYesNoQuestion("input box test","bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar ")
if sys.argv[1] == "--test-term":
pid = view.terminal_text.fork()
if pid == 0:
subprocess.call(["bash"])
sys.exit()
while True:
view.terminal_text.update_interface()
QApplication.processEvents()
time.sleep(0.01)
if sys.argv[1] == "--show-in-terminal":
with open(sys.argv[2]) as f:
chars = f.read()
for c in chars:
view.terminal_text.insertWithTermCodes( c )
#print(c, ord(c))
QApplication.processEvents()
time.sleep(0.05)
while True:
QApplication.processEvents()
cache = apt.Cache()
for pkg in sys.argv[1:]:
if cache[pkg].is_installed and not cache[pkg].is_upgradable:
cache[pkg].mark_delete(purge=True)
else:
cache[pkg].mark_install()
cache.commit(view._acquireProgress,view._installProgress)
# keep the window open
while True:
QApplication.processEvents()
./DistUpgradeViewNonInteractive.py 0000644 0000000 0000000 00000032711 13502463664 016217 0 ustar root root # DistUpgradeView.py
#
# Copyright (c) 2004,2005 Canonical
#
# Author: Michael Vogt
#
# 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
import apt
import apt_pkg
import logging
import locale
import time
import sys
import os
import pty
import select
import subprocess
import copy
import apt.progress
from configparser import NoSectionError, NoOptionError
from subprocess import PIPE, Popen
from .DistUpgradeView import DistUpgradeView, InstallProgress, AcquireProgress
from .telemetry import get as get_telemetry
from .DistUpgradeConfigParser import DistUpgradeConfig
class NonInteractiveAcquireProgress(AcquireProgress):
def update_status(self, uri, descr, shortDescr, status):
AcquireProgress.update_status(self, uri, descr, shortDescr, status)
#logging.debug("Fetch: updateStatus %s %s" % (uri, status))
if status == apt_pkg.STAT_DONE:
print("fetched %s (%.2f/100) at %sb/s" % (
uri, self.percent, apt_pkg.size_to_str(int(self.current_cps))))
if sys.stdout.isatty():
sys.stdout.flush()
class NonInteractiveInstallProgress(InstallProgress):
"""
Non-interactive version of the install progress class
This ensures that conffile prompts are handled and that
hanging scripts are killed after a (long) timeout via ctrl-c
"""
def __init__(self, logdir):
InstallProgress.__init__(self)
logging.debug("setting up environ for non-interactive use")
if "DEBIAN_FRONTEND" not in os.environ:
os.environ["DEBIAN_FRONTEND"] = "noninteractive"
os.environ["APT_LISTCHANGES_FRONTEND"] = "none"
os.environ["RELEASE_UPRADER_NO_APPORT"] = "1"
self.config = DistUpgradeConfig(".")
self.logdir = logdir
self.install_run_number = 0
try:
if self.config.getWithDefault("NonInteractive","ForceOverwrite", False):
apt_pkg.config.set("DPkg::Options::","--force-overwrite")
except (NoSectionError, NoOptionError):
pass
# more debug
#apt_pkg.config.set("Debug::pkgOrderList","true")
#apt_pkg.config.set("Debug::pkgDPkgPM","true")
# default to 2400 sec timeout
self.timeout = 2400
try:
self.timeout = self.config.getint("NonInteractive","TerminalTimeout")
except Exception:
pass
def error(self, pkg, errormsg):
logging.error("got a error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
# check if re-run of maintainer script is requested
if not self.config.getWithDefault(
"NonInteractive","DebugBrokenScripts", False):
return
# re-run maintainer script with sh -x/perl debug to get a better
# idea what went wrong
#
# FIXME: this is just a approximation for now, we also need
# to pass:
# - a version after remove (if upgrade to new version)
#
# not everything is a shell or perl script
#
# if the new preinst fails, its not yet in /var/lib/dpkg/info
# so this is inaccurate as well
environ = copy.copy(os.environ)
environ["PYCENTRAL"] = "debug"
cmd = []
# find what maintainer script failed
if "post-installation" in errormsg:
prefix = "/var/lib/dpkg/info/"
name = "postinst"
argument = "configure"
maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
elif "pre-installation" in errormsg:
prefix = "/var/lib/dpkg/tmp.ci/"
#prefix = "/var/lib/dpkg/info/"
name = "preinst"
argument = "install"
maintainer_script = "%s/%s" % (prefix, name)
elif "pre-removal" in errormsg:
prefix = "/var/lib/dpkg/info/"
name = "prerm"
argument = "remove"
maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
elif "post-removal" in errormsg:
prefix = "/var/lib/dpkg/info/"
name = "postrm"
argument = "remove"
maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
else:
print("UNKNOWN (trigger?) dpkg/script failure for %s (%s) " % (pkg, errormsg))
return
# find out about the interpreter
if not os.path.exists(maintainer_script):
logging.error("can not find failed maintainer script '%s' " % maintainer_script)
return
with open(maintainer_script) as f:
interp = f.readline()[2:].strip().split()[0]
if ("bash" in interp) or ("/bin/sh" in interp):
debug_opts = ["-ex"]
elif ("perl" in interp):
debug_opts = ["-d"]
environ["PERLDB_OPTS"] = "AutoTrace NonStop"
else:
logging.warning("unknown interpreter: '%s'" % interp)
# check if debconf is used and fiddle a bit more if it is
with open(maintainer_script) as f:
maintainer_script_text = f.read()
if ". /usr/share/debconf/confmodule" in maintainer_script_text:
environ["DEBCONF_DEBUG"] = "developer"
environ["DEBIAN_HAS_FRONTEND"] = "1"
interp = "/usr/share/debconf/frontend"
debug_opts = ["sh","-ex"]
# build command
cmd.append(interp)
cmd.extend(debug_opts)
cmd.append(maintainer_script)
cmd.append(argument)
# check if we need to pass a version
if name == "postinst":
version = Popen("dpkg-query -s %s|grep ^Config-Version" % pkg,
shell=True, stdout=PIPE,
universal_newlines=True).communicate()[0]
if version:
cmd.append(version.split(":",1)[1].strip())
elif name == "preinst":
pkg = os.path.basename(pkg)
pkg = pkg.split("_")[0]
version = Popen("dpkg-query -s %s|grep ^Version" % pkg,
shell=True, stdout=PIPE,
universal_newlines=True).communicate()[0]
if version:
cmd.append(version.split(":",1)[1].strip())
logging.debug("re-running '%s' (%s)" % (cmd, environ))
ret = subprocess.call(cmd, env=environ)
logging.debug("%s script returned: %s" % (name,ret))
def conffile(self, current, new):
logging.warning("got a conffile-prompt from dpkg for file: '%s'" %
current)
# looks like we have a race here *sometimes*
time.sleep(5)
try:
# don't overwrite
os.write(self.master_fd, b"n\n")
logging.warning("replied no to the conffile-prompt for file: '%s'" %
current)
except Exception as e:
logging.error("error '%s' when trying to write to the conffile"%e)
def start_update(self):
InstallProgress.start_update(self)
self.last_activity = time.time()
progress_log = self.config.getWithDefault("NonInteractive","DpkgProgressLog", False)
if progress_log:
fullpath = os.path.join(self.logdir, "dpkg-progress.%s.log" % self.install_run_number)
logging.debug("writing dpkg progress log to '%s'" % fullpath)
self.dpkg_progress_log = open(fullpath, "w")
else:
self.dpkg_progress_log = open(os.devnull, "w")
self.dpkg_progress_log.write("%s: Start\n" % time.time())
def finish_update(self):
InstallProgress.finish_update(self)
self.dpkg_progress_log.write("%s: Finished\n" % time.time())
self.dpkg_progress_log.close()
self.install_run_number += 1
def status_change(self, pkg, percent, status_str):
self.dpkg_progress_log.write("%s:%s:%s:%s\n" % (time.time(),
percent,
pkg,
status_str))
def update_interface(self):
InstallProgress.update_interface(self)
if self.statusfd == None:
return
if (self.last_activity + self.timeout) < time.time():
logging.warning("no activity %s seconds (%s) - sending ctrl-c" % (
self.timeout, self.status))
# ctrl-c
os.write(self.master_fd,chr(3))
# read master fd and write to stdout so that terminal output
# actualy works
res = select.select([self.master_fd],[],[],0.1)
while len(res[0]) > 0:
self.last_activity = time.time()
try:
s = os.read(self.master_fd, 1)
sys.stdout.write("%s" % s.decode(
locale.getpreferredencoding(), errors='ignore'))
except OSError:
# happens after we are finished because the fd is closed
return
res = select.select([self.master_fd],[],[],0.1)
sys.stdout.flush()
def fork(self):
logging.debug("doing a pty.fork()")
# some maintainer scripts fail without
os.environ["TERM"] = "dumb"
# unset PAGER so that we can do "diff" in the dpkg prompt
os.environ["PAGER"] = "true"
(self.pid, self.master_fd) = pty.fork()
if self.pid != 0:
logging.debug("pid is: %s" % self.pid)
return self.pid
class DistUpgradeViewNonInteractive(DistUpgradeView):
" non-interactive version of the upgrade view "
def __init__(self, datadir=None, logdir=None):
DistUpgradeView.__init__(self)
get_telemetry().set_updater_type('NonInteractive')
self.config = DistUpgradeConfig(".")
self._acquireProgress = NonInteractiveAcquireProgress()
self._installProgress = NonInteractiveInstallProgress(logdir)
self._opProgress = apt.progress.base.OpProgress()
sys.__excepthook__ = self.excepthook
def excepthook(self, type, value, tb):
" on uncaught exceptions -> print error and reboot "
import traceback
logging.exception("got exception '%s': %s " % (type, value))
lines = traceback.format_exception(type, value, tb)
logging.error("not handled exception:\n%s" % "".join(lines))
#sys.excepthook(type, value, tb)
self.confirmRestart()
def getOpCacheProgress(self):
" return a OpProgress() subclass for the given graphic"
return self._opProgress
def getAcquireProgress(self):
" return an acquire progress object "
return self._acquireProgress
def getInstallProgress(self, cache=None):
" return a install progress object "
return self._installProgress
def updateStatus(self, msg):
""" update the current status of the distUpgrade based
on the current view
"""
pass
def setStep(self, step):
""" we have 5 steps current for a upgrade:
1. Analyzing the system
2. Updating repository information
3. Performing the upgrade
4. Post upgrade stuff
5. Complete
"""
super(DistUpgradeViewNonInteractive, self).setStep(step)
pass
def confirmChanges(self, summary, changes, demotions, downloadSize,
actions=None, removal_bold=True):
DistUpgradeView.confirmChanges(self, summary, changes, demotions,
downloadSize, actions)
logging.debug("toinstall: '%s'" % [p.name for p in self.toInstall])
logging.debug("toupgrade: '%s'" % [p.name for p in self.toUpgrade])
logging.debug("toremove: '%s'" % [p.name for p in self.toRemove])
return True
def askYesNoQuestion(self, summary, msg, default='No'):
" ask a Yes/No question and return True on 'Yes' "
# if this gets enabled upgrades over ssh with the non-interactive
# frontend will no longer work
#if default.lower() == "no":
# return False
return True
def confirmRestart(self):
" generic ask about the restart, can be overridden "
logging.debug("confirmRestart() called")
# rebooting here makes sense if we run e.g. in qemu
return self.config.getWithDefault("NonInteractive","RealReboot", False)
def error(self, summary, msg, extended_msg=None):
" display a error "
logging.error("%s %s (%s)" % (summary, msg, extended_msg))
def abort(self):
logging.error("view.abort called")
if __name__ == "__main__":
view = DistUpgradeViewNonInteractive()
ap = NonInteractiveAcquireProgress()
ip = NonInteractiveInstallProgress()
#ip.error("linux-image-2.6.17-10-generic","post-installation script failed")
ip.error("xserver-xorg","pre-installation script failed")
cache = apt.Cache()
for pkg in sys.argv[1:]:
#if cache[pkg].is_installed:
# cache[pkg].mark_delete()
#else:
cache[pkg].mark_install()
cache.commit(ap, ip)
time.sleep(2)
sys.exit(0)
./DistUpgradeViewText.py 0000644 0000000 0000000 00000030526 13502463664 014215 0 ustar root root # DistUpgradeViewText.py
#
# Copyright (c) 2004-2006 Canonical
#
# Author: Michael Vogt
#
# 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
import errno
import sys
import logging
import subprocess
from gettext import dgettext
import apt
import os
from .DistUpgradeApport import run_apport, apport_crash
from .DistUpgradeView import (
AcquireProgress,
DistUpgradeView,
ENCODING,
InstallProgress,
)
from .telemetry import get as get_telemetry
import apt.progress
import gettext
from .DistUpgradeGettext import gettext as _
from .utils import twrap
def readline():
""" py2/py3 compatible readline from stdin """
sys.stdout.flush()
try:
s = input()
except EOFError:
s = ''
if hasattr(s, "decode"):
return s.decode(ENCODING, "backslashreplace")
return s
class TextAcquireProgress(AcquireProgress, apt.progress.text.AcquireProgress):
def __init__(self):
apt.progress.text.AcquireProgress.__init__(self)
AcquireProgress.__init__(self)
def pulse(self, owner):
apt.progress.text.AcquireProgress.pulse(self, owner)
AcquireProgress.pulse(self, owner)
return True
class TextInstallProgress(InstallProgress):
# percent step when progress is reported (to avoid screen spam)
MIN_REPORTING = 5
def __init__(self, *args, **kwargs):
super(TextInstallProgress, self).__init__(*args, **kwargs)
self._prev_percent = 0
def status_change(self, pkg, percent, status):
if self._prev_percent + self.MIN_REPORTING < percent:
# FIXME: move into ubuntu-release-upgrader after trusty
domain = "libapt-pkg4.12"
progress_str = dgettext(domain, "Progress: [%3i%%]") % int(percent)
sys.stdout.write("\r\n%s\r\n" % progress_str)
self._prev_percent = percent
class TextCdromProgressAdapter(apt.progress.base.CdromProgress):
""" Report the cdrom add progress """
def update(self, text, step):
""" update is called regularly so that the gui can be redrawn """
if text:
print("%s (%f)" % (text, step.value/float(self.totalSteps)*100))
def ask_cdrom_name(self):
return (False, "")
def change_cdrom(self):
return False
class DistUpgradeViewText(DistUpgradeView):
""" text frontend of the distUpgrade tool """
def __init__(self, datadir=None, logdir=None):
# indicate that we benefit from using gnu screen
self.needs_screen = True
get_telemetry().set_updater_type('Text')
# its important to have a debconf frontend for
# packages like "quagga"
if "DEBIAN_FRONTEND" not in os.environ:
os.environ["DEBIAN_FRONTEND"] = "dialog"
if not datadir or datadir == '.':
localedir=os.path.join(os.getcwd(),"mo")
else:
localedir="/usr/share/locale/ubuntu-release-upgrader"
try:
gettext.bindtextdomain("ubuntu-release-upgrader", localedir)
gettext.textdomain("ubuntu-release-upgrader")
except Exception as e:
logging.warning("Error setting locales (%s)" % e)
self.last_step = None # keep a record of the latest step
self._opCacheProgress = apt.progress.text.OpProgress()
self._acquireProgress = TextAcquireProgress()
self._cdromProgress = TextCdromProgressAdapter()
self._installProgress = TextInstallProgress()
sys.excepthook = self._handleException
#self._process_events_tick = 0
def _handleException(self, type, value, tb):
# we handle the exception here, hand it to apport and run the
# apport gui manually after it because we kill u-n during the upgrade
# to prevent it from poping up for reboot notifications or FF restart
# notifications or somesuch
import traceback
print()
lines = traceback.format_exception(type, value, tb)
logging.error("not handled exception:\n%s" % "\n".join(lines))
apport_crash(type, value, tb)
if not run_apport():
self.error(_("A fatal error occurred"),
_("Please report this as a bug and include the "
"files /var/log/dist-upgrade/main.log and "
"/var/log/dist-upgrade/apt.log "
"in your report. The upgrade has aborted.\n"
"Your original sources.list was saved in "
"/etc/apt/sources.list.distUpgrade."),
"\n".join(lines))
sys.exit(1)
def getAcquireProgress(self):
return self._acquireProgress
def getInstallProgress(self, cache):
self._installProgress._cache = cache
return self._installProgress
def getOpCacheProgress(self):
return self._opCacheProgress
def getCdromProgress(self):
return self._cdromProgress
def updateStatus(self, msg):
print()
print(msg)
sys.stdout.flush()
def abort(self):
print()
print(_("Aborting"))
def setStep(self, step):
super(DistUpgradeViewText, self).setStep(step)
self.last_step = step
def showDemotions(self, summary, msg, demotions):
self.information(summary, msg,
_("Demoted:\n")+twrap(", ".join(demotions)))
def information(self, summary, msg, extended_msg=None):
print()
print(twrap(summary))
print(twrap(msg))
if extended_msg:
print(twrap(extended_msg))
print(_("To continue please press [ENTER]"))
readline()
def error(self, summary, msg, extended_msg=None):
print()
print(twrap(summary))
print(twrap(msg))
if extended_msg:
print(twrap(extended_msg))
return False
def showInPager(self, output):
""" helper to show output in a pager """
# we need to send a encoded str (bytes in py3) to the pipe
# LP: #1068389
if not isinstance(output, bytes):
output = output.encode(ENCODING)
for pager in ["/usr/bin/sensible-pager", "/bin/more"]:
if os.path.exists(pager):
p = subprocess.Popen([pager,"-"],stdin=subprocess.PIPE)
# if lots of data is shown, we need to catch EPIPE
try:
p.stdin.write(output)
p.stdin.close()
p.wait()
except IOError as e:
if e.errno != errno.EPIPE:
raise
return
# if we don't have a pager, just print
print(output)
def confirmChanges(self, summary, changes, demotions, downloadSize,
actions=None, removal_bold=True):
DistUpgradeView.confirmChanges(self, summary, changes, demotions,
downloadSize, actions)
print()
print(twrap(summary))
print(twrap(self.confirmChangesMessage))
print(" %s %s" % (_("Continue [yN] "), _("Details [d]")), end="")
while True:
res = readline().strip().lower()
# TRANSLATORS: the "y" is "yes"
if res.startswith(_("y")):
return True
# TRANSLATORS: the "n" is "no"
elif not res or res.startswith(_("n")):
return False
# TRANSLATORS: the "d" is "details"
elif res.startswith(_("d")):
output = ""
if len(self.demotions) > 0:
output += "\n"
output += twrap(
_("No longer supported: %s\n") % " ".join([p.name for p in self.demotions]),
subsequent_indent=' ')
if len(self.toRemove) > 0:
output += "\n"
output += twrap(
_("Remove: %s\n") % " ".join([p.name for p in self.toRemove]),
subsequent_indent=' ')
if len(self.toRemoveAuto) > 0:
output += twrap(
_("Remove (was auto installed) %s") % " ".join([p.name for p in self.toRemoveAuto]),
subsequent_indent=' ')
output += "\n"
if len(self.toInstall) > 0:
output += "\n"
output += twrap(
_("Install: %s\n") % " ".join([p.name for p in self.toInstall]),
subsequent_indent=' ')
if len(self.toUpgrade) > 0:
output += "\n"
output += twrap(
_("Upgrade: %s\n") % " ".join([p.name for p in self.toUpgrade]),
subsequent_indent=' ')
self.showInPager(output)
print("%s %s" % (_("Continue [yN] "), _("Details [d]")), end="")
def askYesNoQuestion(self, summary, msg, default='No'):
print()
print(twrap(summary))
print(twrap(msg))
if default == 'No':
print(_("Continue [yN] "), end="")
res = readline()
# TRANSLATORS: first letter of a positive (yes) answer
if res.strip().lower().startswith(_("y")):
return True
return False
else:
print(_("Continue [Yn] "), end="")
res = readline()
# TRANSLATORS: first letter of a negative (no) answer
if res.strip().lower().startswith(_("n")):
return False
return True
# FIXME: when we need this most the resolver is writing debug logs
# and we redirect stdout/stderr
# def processEvents(self):
# #time.sleep(0.2)
# anim = [".","o","O","o"]
# anim = ["\\","|","/","-","\\","|","/","-"]
# self._process_events_tick += 1
# if self._process_events_tick >= len(anim):
# self._process_events_tick = 0
# sys.stdout.write("[%s]" % anim[self._process_events_tick])
# sys.stdout.flush()
def confirmRestart(self):
return self.askYesNoQuestion(_("Restart required"),
_("To finish the upgrade, a restart is "
"required.\n"
"If you select 'y' the system "
"will be restarted."), default='No')
if __name__ == "__main__":
view = DistUpgradeViewText()
#while True:
# view.processEvents()
print(twrap("89 packages are going to be upgraded.\nYou have to download a total of 82.7M.\nThis download will take about 10 minutes with a 1Mbit DSL connection and about 3 hours 12 minutes with a 56k modem.", subsequent_indent=" "))
#sys.exit(1)
view = DistUpgradeViewText()
print(view.askYesNoQuestion("hello", "Icecream?", "No"))
print(view.askYesNoQuestion("hello", "Icecream?", "Yes"))
#view.confirmChangesMessage = "89 packages are going to be upgraded.\n You have to download a total of 82.7M.\n This download will take about 10 minutes with a 1Mbit DSL connection and about 3 hours 12 minutes with a 56k modem."
#view.confirmChanges("xx",[], 100)
sys.exit(0)
view.confirmRestart()
cache = apt.Cache()
fp = view.getAcquireProgress()
ip = view.getInstallProgress(cache)
for pkg in sys.argv[1:]:
cache[pkg].mark_install()
cache.commit(fp,ip)
sys.exit(0)
view.getTerminal().call(["/usr/bin/dpkg","--configure","-a"])
#view.getTerminal().call(["ls","-R","/usr"])
view.error("short","long",
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
"asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
)
view.confirmChanges("xx",[], 100)
print(view.askYesNoQuestion("hello", "Icecream?"))
./EOLReleaseAnnouncement 0000644 0000000 0000000 00000003740 13502463664 014144 0 ustar root root = Ubuntu 18.04 'Bionic Beaver' is no longer supported =
You are about to upgrade to a version of Ubuntu that is no longer
supported.
This release of Ubuntu is '''no longer supported''' by Canonical. The
support timeframe is between 9 months and 5 years after the initial
release. You will not receive security updates or critical
bugfixes. See http://www.ubuntu.com/releaseendoflife for details.
It is still possible to upgrade this version and eventually you will
be able to upgrade to a supported release of Ubuntu.
Alternatively you may want to consider to reinstall the machine to the
latest version, for more information on this, visit:
http://www.ubuntu.com/desktop/get-ubuntu
For pre-installed system you may want to contact the manufacturer
for instructions.
== Feedback and Helping ==
If you would like to help shape Ubuntu, take a look at the list of
ways you can participate at
http://www.ubuntu.com/community/participate/
Your comments, bug reports, patches and suggestions will help ensure
that our next release is the best release of Ubuntu ever. If you feel
that you have found a bug please read:
http://help.ubuntu.com/community/ReportingBugs
Then report bugs using apport in Ubuntu. For example:
ubuntu-bug linux
will open a bug report in Launchpad regarding the linux package.
If you have a question, or if you think you may have found a bug but
aren't sure, first try asking on the #ubuntu or #ubuntu-bugs IRC
channels on Freenode, on the Ubuntu Users mailing list, or on the
Ubuntu forums:
http://help.ubuntu.com/community/InternetRelayChat
http://lists.ubuntu.com/mailman/listinfo/ubuntu-users
http://www.ubuntuforums.org/
== More Information ==
You can find out more about Ubuntu on our website, IRC channel and wiki.
If you're new to Ubuntu, please visit:
http://www.ubuntu.com/
To sign up for future Ubuntu announcements, please subscribe to Ubuntu's
very low volume announcement list at:
http://lists.ubuntu.com/mailman/listinfo/ubuntu-announce
./EOLReleaseAnnouncement.html 0000644 0000000 0000000 00000005660 14114454126 015103 0 ustar root root
Ubuntu 18.04 'Bionic Beaver' is no longer supported
Ubuntu 18.04 'Bionic Beaver' is no longer supported
You are about to upgrade to a version of Ubuntu that is no longer
supported.
This release of Ubuntu is no longer supported by Canonical. The
support timeframe is between 9 months and 5 years after the initial
release. You will not receive security updates or critical
bugfixes. See http://www.ubuntu.com/releaseendoflife for details.
It is still possible to upgrade this version and eventually you will
be able to upgrade to a supported release of Ubuntu.
Alternatively you may want to consider to reinstall the machine to the
latest version, for more information on this, visit:
http://www.ubuntu.com/desktop/get-ubuntu
For pre-installed system you may want to contact the manufacturer
for instructions.
Feedback and Helping
If you would like to help shape Ubuntu, take a look at the list of
ways you can participate at
http://www.ubuntu.com/community/participate/
Your comments, bug reports, patches and suggestions will help ensure
that our next release is the best release of Ubuntu ever. If you feel
that you have found a bug please read:
http://help.ubuntu.com/community/ReportingBugs
Then report bugs using apport in Ubuntu. For example:
ubuntu-bug linux
will open a bug report in Launchpad regarding the linux package.
If you have a question, or if you think you may have found a bug but
aren't sure, first try asking on the #ubuntu or #ubuntu-bugs IRC
channels on Freenode, on the Ubuntu Users mailing list, or on the
Ubuntu forums:
http://help.ubuntu.com/community/InternetRelayChat
http://lists.ubuntu.com/mailman/listinfo/ubuntu-users
http://www.ubuntuforums.org/
More Information
You can find out more about Ubuntu on our website, IRC channel and wiki.
If you're new to Ubuntu, please visit:
http://www.ubuntu.com/
To sign up for future Ubuntu announcements, please subscribe to Ubuntu's
very low volume announcement list at:
http://lists.ubuntu.com/mailman/listinfo/ubuntu-announce
./GtkProgress.py 0000644 0000000 0000000 00000007664 13475213717 012564 0 ustar root root # GtkProgress.py
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
#
# Copyright (c) 2004,2005 Canonical
#
# Author: Michael Vogt
#
# 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 gi.repository import Gtk, Gdk
import apt
import os
from gettext import gettext as _
from .utils import humanize_size
from .SimpleGtk3builderApp import SimpleGtkbuilderApp
class GtkAcquireProgress(apt.progress.base.AcquireProgress):
def __init__(self, parent, datadir, summary="", descr=""):
uifile = os.path.join(datadir, "gtkbuilder", "AcquireProgress.ui")
self.widgets = SimpleGtkbuilderApp(uifile, "ubuntu-release-upgrader")
# if this is set to false the download will cancel
self._continue = True
# init vars here
# FIXME: find a more elegant way, this sucks
self.summary = self.widgets.label_fetch_summary
self.status = self.widgets.label_fetch_status
# we need to connect the signal manual here, it won't work
# from the main window auto-connect
self.widgets.button_fetch_cancel.connect(
"clicked", self.on_button_fetch_cancel_clicked)
self.progress = self.widgets.progressbar_fetch
self.window_fetch = self.widgets.window_fetch
self.window_fetch.set_transient_for(parent)
self.window_fetch.realize()
self.window_fetch.get_window().set_functions(Gdk.WMFunction.MOVE)
# set summary
if summary != "":
self.summary.set_markup("%s \n\n%s" %
(summary, descr))
def start(self):
self.progress.set_fraction(0)
self.window_fetch.show()
def stop(self):
self.window_fetch.hide()
def on_button_fetch_cancel_clicked(self, widget):
self._continue = False
def pulse(self, owner):
apt.progress.base.AcquireProgress.pulse(self, owner)
current_item = self.current_items + 1
if current_item > self.total_items:
current_item = self.total_items
if self.current_cps > 0:
status_text = (_("Downloading file %(current)li of %(total)li "
"with %(speed)s/s") % {
"current": current_item,
"total": self.total_items,
"speed": humanize_size(self.current_cps)})
else:
status_text = (_("Downloading file %(current)li of %(total)li") %
{"current": current_item,
"total": self.total_items})
self.progress.set_fraction(
(self.current_bytes + self.current_items) /
float(self.total_bytes + self.total_items))
self.status.set_markup("%s" % status_text)
# TRANSLATORS: show the remaining time in a progress bar:
#if self.current_cps > 0:
# eta = ((self.total_bytes + self.current_bytes) /
# float(self.current_cps))
#else:
# eta = 0.0
#self.progress.set_text(_("About %s left" % (apt_pkg.TimeToStr(eta))))
# FIXME: show remaining time
self.progress.set_text("")
while Gtk.events_pending():
Gtk.main_iteration()
return self._continue
./MetaRelease.py 0000644 0000000 0000000 00000041234 13417200670 012456 0 ustar root root # MetaRelease.py
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
#
# Copyright (c) 2004,2005 Canonical
#
# Author: Michael Vogt
#
# 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 absolute_import, print_function
import apt
import apt_pkg
try:
import configparser
except ImportError:
import ConfigParser as configparser
try:
from http.client import BadStatusLine
except ImportError:
from httplib import BadStatusLine
import logging
import email.utils
import os
import socket
import sys
import time
import threading
try:
from urllib.parse import quote
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError
except ImportError:
from urllib2 import HTTPError, Request, URLError, urlopen, quote
from .utils import (get_lang, get_dist, get_dist_version, get_ubuntu_flavor,
get_ubuntu_flavor_name)
class MetaReleaseParseError(Exception):
pass
class Dist(object):
def __init__(self, name, version, date, supported):
self.name = name
self.version = version
self.date = date
self.supported = supported
self.releaseNotesURI = None
self.releaseNotesHtmlUri = None
self.upgradeTool = None
self.upgradeToolSig = None
# the server may report that the upgrade is broken currently
self.upgrade_broken = None
class MetaReleaseCore(object):
"""
A MetaReleaseCore object abstracts the list of released
distributions.
"""
DEBUG = "DEBUG_UPDATE_MANAGER" in os.environ
# some constants
CONF = "/etc/update-manager/release-upgrades"
CONF_METARELEASE = "/etc/update-manager/meta-release"
def __init__(self,
useDevelopmentRelease=False,
useProposed=False,
forceLTS=False,
forceDownload=False,
cache=None):
self._debug("MetaRelease.__init__() useDevel=%s useProposed=%s" %
(useDevelopmentRelease, useProposed))
# force download instead of sending if-modified-since
self.forceDownload = forceDownload
self.useDevelopmentRelease = useDevelopmentRelease
# information about the available dists
self.downloaded = threading.Event()
self.upgradable_to = None
self.new_dist = None
if cache is None:
cache = apt.Cache()
self.flavor = get_ubuntu_flavor(cache=cache)
self.flavor_name = get_ubuntu_flavor_name(cache=cache)
self.current_dist_name = get_dist()
self.current_dist_version = get_dist_version()
self.no_longer_supported = None
self.prompt = None
# default (if the conf file is missing)
base_uri = "https://changelogs.ubuntu.com/"
self.METARELEASE_URI = base_uri + "meta-release"
self.METARELEASE_URI_LTS = base_uri + "meta-release-lts"
self.METARELEASE_URI_UNSTABLE_POSTFIX = "-development"
self.METARELEASE_URI_PROPOSED_POSTFIX = "-development"
# check the meta-release config first
parser = configparser.ConfigParser()
if os.path.exists(self.CONF_METARELEASE):
try:
parser.read(self.CONF_METARELEASE)
except configparser.Error as e:
sys.stderr.write("ERROR: failed to read '%s':\n%s" % (
self.CONF_METARELEASE, e))
return
# make changing the metarelease file and the location
# for the files easy
if parser.has_section("METARELEASE"):
sec = "METARELEASE"
for k in ["URI",
"URI_LTS",
"URI_UNSTABLE_POSTFIX",
"URI_PROPOSED_POSTFIX"]:
if parser.has_option(sec, k):
self._debug("%s: %s " % (self.CONF_METARELEASE,
parser.get(sec, k)))
setattr(self, "%s_%s" % (sec, k), parser.get(sec, k))
# check the config file first to figure if we want lts upgrades only
parser = configparser.ConfigParser()
if os.path.exists(self.CONF):
try:
parser.read(self.CONF)
except configparser.Error as e:
sys.stderr.write("ERROR: failed to read '%s':\n%s" % (
self.CONF, e))
return
# now check which specific url to use
if parser.has_option("DEFAULT", "Prompt"):
type = parser.get("DEFAULT", "Prompt").lower()
if (type == "never" or type == "no"):
self.prompt = 'never'
# nothing to do for this object
# FIXME: what about no longer supported?
self.downloaded.set()
return
elif type == "lts":
self.prompt = 'lts'
self.METARELEASE_URI = self.METARELEASE_URI_LTS
else:
self.prompt = 'normal'
# needed for the _tryUpgradeSelf() code in DistUpgradeController
if forceLTS:
self.METARELEASE_URI = self.METARELEASE_URI_LTS
# devel and proposed "just" change the postfix
if useDevelopmentRelease:
self.METARELEASE_URI += self.METARELEASE_URI_UNSTABLE_POSTFIX
elif useProposed:
self.METARELEASE_URI += self.METARELEASE_URI_PROPOSED_POSTFIX
self._debug("metarelease-uri: %s" % self.METARELEASE_URI)
self.metarelease_information = None
if not self._buildMetaReleaseFile():
self._debug("_buildMetaReleaseFile failed")
return
# we start the download thread here and we have a timeout
threading.Thread(target=self.download).start()
#threading.Thread(target=self.check).start()
def _buildMetaReleaseFile(self):
# build the metarelease_file name
self.METARELEASE_FILE = os.path.join(
"/var/lib/update-manager/",
os.path.basename(self.METARELEASE_URI))
# check if we can write to the global location, if not,
# write to homedir
try:
open(self.METARELEASE_FILE, "a").close()
except IOError as e:
cache_dir = os.getenv(
"XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
# Take special care when creating this directory; ~/.cache needs
# to be created with mode 0700, but the other directories do
# not.
cache_parent_dir = os.path.split(cache_dir)[0]
if not os.path.exists(cache_parent_dir):
try:
os.makedirs(cache_parent_dir)
except OSError as e:
sys.stderr.write("mkdir() failed: '%s'" % e)
return False
if not os.path.exists(cache_dir):
try:
os.mkdir(cache_dir, 0o700)
except OSError as e:
sys.stderr.write("mkdir() failed: '%s'" % e)
return False
path = os.path.join(cache_dir, 'update-manager-core')
if not os.path.exists(path):
try:
os.mkdir(path)
except OSError as e:
sys.stderr.write("mkdir() failed: '%s'" % e)
return False
self.METARELEASE_FILE = os.path.join(
path,
os.path.basename(self.METARELEASE_URI))
# if it is empty, remove it to avoid I-M-S hits on empty file
try:
if os.path.getsize(self.METARELEASE_FILE) == 0:
os.unlink(self.METARELEASE_FILE)
except Exception as e:
pass
return True
def dist_no_longer_supported(self, dist):
""" virtual function that is called when the distro is no longer
supported
"""
self.no_longer_supported = dist
def new_dist_available(self, dist):
""" virtual function that is called when a new distro release
is available
"""
self.new_dist = dist
def parse(self):
self._debug("MetaRelease.parse()")
current_dist_name = self.current_dist_name
self._debug("current dist name: '%s'" % current_dist_name)
current_dist = None
dists = []
# parse the metarelease_information file
index_tag = apt_pkg.TagFile(self.metarelease_information)
try:
while index_tag.step():
for required_key in ("Dist", "Version", "Supported", "Date"):
if required_key not in index_tag.section:
raise MetaReleaseParseError(
"Required key '%s' missing" % required_key)
name = index_tag.section["Dist"]
self._debug("found distro name: '%s'" % name)
rawdate = index_tag.section["Date"]
parseddate = list(email.utils.parsedate(rawdate))
parseddate[8] = 0 # assume no DST
date = time.mktime(tuple(parseddate))
supported = int(index_tag.section["Supported"])
version = index_tag.section["Version"]
# add the information to a new date object
dist = Dist(name, version, date, supported)
if "ReleaseNotes" in index_tag.section:
dist.releaseNotesURI = index_tag.section["ReleaseNotes"]
lang = get_lang()
if lang:
dist.releaseNotesURI += "?lang=%s" % lang
if "ReleaseNotesHtml" in index_tag.section:
dist.releaseNotesHtmlUri = index_tag.section[
"ReleaseNotesHtml"]
query = self._get_release_notes_uri_query_string(dist)
if query:
dist.releaseNotesHtmlUri += query
if "UpgradeTool" in index_tag.section:
dist.upgradeTool = index_tag.section["UpgradeTool"]
if "UpgradeToolSignature" in index_tag.section:
dist.upgradeToolSig = index_tag.section[
"UpgradeToolSignature"]
if "UpgradeBroken" in index_tag.section:
dist.upgrade_broken = index_tag.section["UpgradeBroken"]
dists.append(dist)
if name == current_dist_name:
current_dist = dist
except apt_pkg.Error:
raise MetaReleaseParseError("Unable to parse %s" %
self.METARELEASE_URI)
self.metarelease_information.close()
self.metarelease_information = None
# first check if the current runing distro is in the meta-release
# information. if not, we assume that we run on something not
# supported and silently return
if current_dist is None:
self._debug("current dist not found in meta-release file\n")
return False
# then see what we can upgrade to
upgradable_to = ""
for dist in dists:
if dist.date > current_dist.date:
# Only offer to upgrade to an unsupported release if running
# with useDevelopmentRelease, this way one can upgrade from an
# LTS release to the next supported non-LTS release e.g. from
# 14.04 to 15.04.
if not dist.supported and not self.useDevelopmentRelease:
continue
upgradable_to = dist
self._debug("new dist: %s" % upgradable_to)
break
# only warn if unsupported and a new dist is available (because
# the development version is also unsupported)
if upgradable_to != "" and not current_dist.supported:
self.upgradable_to = upgradable_to
self.dist_no_longer_supported(current_dist)
if upgradable_to != "":
self.upgradable_to = upgradable_to
self.new_dist_available(upgradable_to)
# parsing done and sucessfully
return True
# the network thread that tries to fetch the meta-index file
# can't touch the gui, runs as a thread
def download(self):
self._debug("MetaRelease.download()")
lastmodified = 0
req = Request(self.METARELEASE_URI)
# make sure that we always get the latest file (#107716)
req.add_header("Cache-Control", "No-Cache")
req.add_header("Pragma", "no-cache")
if os.access(self.METARELEASE_FILE, os.W_OK):
try:
lastmodified = os.stat(self.METARELEASE_FILE).st_mtime
except OSError as e:
pass
if lastmodified > 0 and not self.forceDownload:
req.add_header("If-Modified-Since",
time.asctime(time.gmtime(lastmodified)))
try:
# open
uri = urlopen(req, timeout=20)
# sometime there is a root owned meta-relase file
# there, try to remove it so that we get it
# with proper permissions
if (os.path.exists(self.METARELEASE_FILE) and
not os.access(self.METARELEASE_FILE, os.W_OK)):
try:
os.unlink(self.METARELEASE_FILE)
except OSError as e:
print("Can't unlink '%s' (%s)" % (self.METARELEASE_FILE,
e))
# we may get exception here on e.g. disk full
try:
f = open(self.METARELEASE_FILE, "w+")
for line in uri.readlines():
f.write(line.decode("UTF-8"))
f.flush()
f.seek(0, 0)
self.metarelease_information = f
except IOError as e:
pass
uri.close()
# http error
except HTTPError as e:
# mvo: only reuse local info on "not-modified"
if e.code == 304 and os.path.exists(self.METARELEASE_FILE):
self._debug("reading file '%s'" % self.METARELEASE_FILE)
self.metarelease_information = open(self.METARELEASE_FILE, "r")
else:
self._debug("result of meta-release download: '%s'" % e)
# generic network error
except (URLError, BadStatusLine, socket.timeout) as e:
self._debug("result of meta-release download: '%s'" % e)
print("Failed to connect to %s. Check your Internet connection "
"or proxy settings" % self.METARELEASE_URI)
# now check the information we have
if self.metarelease_information is not None:
self._debug("have self.metarelease_information")
try:
self.parse()
except Exception as e:
logging.exception("parse failed for '%s'" %
self.METARELEASE_FILE)
# no use keeping a broken file around
os.remove(self.METARELEASE_FILE)
# we don't want to keep a meta-release file around when it
# has a "Broken" flag, this ensures we are not bitten by
# I-M-S/cache issues
if self.new_dist and self.new_dist.upgrade_broken:
os.remove(self.METARELEASE_FILE)
else:
self._debug("NO self.metarelease_information")
self.downloaded.set()
@property
def downloading(self):
return not self.downloaded.is_set()
def _get_release_notes_uri_query_string(self, dist):
q = "?"
# get the lang
lang = get_lang()
if lang:
q += "lang=%s&" % lang
# get the os
q += "os=%s&" % self.flavor
# get the version to upgrade to
q += "ver=%s" % dist.version
# the archive didn't respond well to ? being %3F
return quote(q, '/?')
def _debug(self, msg):
if self.DEBUG:
sys.stderr.write(msg + "\n")
if __name__ == "__main__":
meta = MetaReleaseCore(False, False)
./NvidiaDetector/ 0000755 0000000 0000000 00000000000 14115704656 012626 5 ustar root root ./NvidiaDetector/__init__.py 0000644 0000000 0000000 00000000000 13501460235 014713 0 ustar root root ./NvidiaDetector/alternatives.py 0000644 0000000 0000000 00000013677 13757445774 015735 0 ustar root root #
# alternatives.py
#
# Copyright 2010 Canonical Services Ltd
# Author: Alberto Milone
#
# 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., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
import os
import subprocess
from subprocess import Popen, PIPE, CalledProcessError
class MultiArchUtils(object):
def __init__(self):
# We have 2 alternatives, one for each architecture
self._supported_architectures = {'i386': 'i386', 'amd64': 'x86_64'}
self._main_arch = self._get_architecture()
self._other_arch = list(self._supported_architectures.values())[
int(not list(self._supported_architectures.values()).index(self._main_arch))]
# Make sure that the PATH environment variable is set
if not os.environ.get('PATH'):
os.environ['PATH'] = '/sbin:/usr/sbin:/bin:/usr/bin'
def _get_architecture(self):
dev_null = open('/dev/null', 'w')
p1 = Popen(['dpkg', '--print-architecture'], stdout=PIPE,
stderr=dev_null, universal_newlines=True)
p = p1.communicate()[0]
dev_null.close()
architecture = p.strip()
return self._supported_architectures.get(architecture)
def _get_alternative_name_from_arch(self, architecture):
alternative = '%s-linux-gnu_gl_conf' % architecture
return alternative
def get_main_alternative_name(self):
return self._get_alternative_name_from_arch(self._main_arch)
def get_other_alternative_name(self):
return self._get_alternative_name_from_arch(self._other_arch)
class Alternatives(object):
def __init__(self, master_link):
self._open_drivers_alternative = 'mesa/ld.so.conf'
self._open_egl_drivers_alternative = 'mesa-egl/ld.so.conf'
self._command = 'update-alternatives'
self._master_link = master_link
# Make sure that the PATH environment variable is set
if not os.environ.get('PATH'):
os.environ['PATH'] = '/sbin:/usr/sbin:/bin:/usr/bin'
def list_alternatives(self):
'''Get the list of alternatives for the master link'''
dev_null = open('/dev/null', 'w')
alternatives = []
p1 = Popen([self._command, '--list', self._master_link],
stdout=PIPE, stderr=dev_null, universal_newlines=True)
p = p1.communicate()[0]
dev_null.close()
c = p.split('\n')
for line in c:
line.strip() and alternatives.append(line.strip())
return alternatives
def get_current_alternative(self):
'''Get the alternative in use'''
dev_null = open('/dev/null', 'w')
p1 = Popen([self._command, '--query', self._master_link],
stdout=PIPE, stderr=dev_null, universal_newlines=True)
p = p1.communicate()[0]
dev_null.close()
c = p.split('\n')
for line in c:
if line.strip().startswith('Value:'):
return line.replace('Value:', '').strip()
return None
def get_alternative_by_name(self, name, ignore_pattern=None):
'''Get the alternative link by providing the driver name
ignore_pattern allows ignoring a substring in the name'''
if ignore_pattern:
name = name.replace(ignore_pattern, '')
alternatives = self.list_alternatives()
for alternative in alternatives:
if alternative.split('/')[-2] == name:
return alternative
return None
def get_open_drivers_alternative(self):
'''Get the alternative link for open drivers'''
return self.get_alternative_by_name(self._open_drivers_alternative)
def get_open_egl_drivers_alternative(self):
'''Get the alternative link for open EGL/GLES drivers'''
return self.get_alternative_by_name(self._open_egl_drivers_alternative)
def update_gmenu(self):
'''Trigger gmenu so that the icons will show up in the menu'''
try:
subprocess.check_call(['dpkg-trigger', '--by-package=fakepackage',
'gmenucache'])
subprocess.check_call(['dpkg', '--configure', '-a'])
except (OSError, CalledProcessError):
pass
def set_alternative(self, path):
'''Tries to set an alternative and returns the boolean exit status'''
try:
subprocess.check_call([self._command, '--set',
self._master_link, path])
self.ldconfig()
except CalledProcessError:
return False
self.update_gmenu()
return True
def ldconfig(self):
'''Call ldconfig'''
try:
subprocess.check_call(['ldconfig'])
except CalledProcessError:
return False
return True
def resolve_module_alias(self, alias):
'''Get the 1st kernel module name matching an alias'''
dev_null = open('/dev/null', 'w')
p1 = Popen(['modprobe', '--resolve-alias', alias], stdout=PIPE,
stderr=dev_null, universal_newlines=True)
p = p1.communicate()[0]
dev_null.close()
c = p.split('\n')
for line in c:
if line.strip().startswith('Usage:'):
return None
return line.strip()
return None
./NvidiaDetector/__pycache__/ 0000755 0000000 0000000 00000000000 14115704656 015036 5 ustar root root ./NvidiaDetector/__pycache__/__init__.cpython-36.pyc 0000644 0000000 0000000 00000000214 14115704656 021220 0 ustar root root 3
`] ã @ s d S )N© r r r ú9/usr/lib/python3/dist-packages/NvidiaDetector/__init__.pyÚ s ./NvidiaDetector/__pycache__/alternatives.cpython-36.pyc 0000644 0000000 0000000 00000012347 14115704656 022174 0 ustar root root 3
üK¾_¿ ã @ sH d dl Z d dlZd dlmZmZmZ G dd„ deƒZG dd„ deƒZdS )é N)ÚPopenÚPIPEÚCalledProcessErrorc @ s4 e Zd Zdd„ Zdd„ Zdd„ Zdd„ Zd d
„ ZdS )ÚMultiArchUtilsc C s\ dddœ| _ | jƒ | _t| j jƒ ƒtt| j jƒ ƒj| jƒ ƒ | _tj j
dƒsXdtj d<