# (c) Copyright 2014-2015. CodeWeavers, Inc.
import os.path
import cxlog
import cxutils
# for localization
from cxutils import cxgettext as _
FIXES_URL = "http://ftp.codeweavers.com/pub/crossover/cxfixes/cxfixes.xml.gz"
WIKI_URL = "https://www.codeweavers.com/support/wiki/Diag/"
#####
#
# Methods for displaying error messages
#
#####
def display_error_internal(title, text):
try:
import gtk
gtk_level = gtk.main_level()
import threading
lock = threading.Lock()
lock.acquire()
def done(_response):
lock.release()
if gtk_level == 0:
gtk.main_quit()
import cxguitools
cxguitools.CXMessageDlg(
primary=title,
secondary=text,
button_array=((gtk.STOCK_OK, 0),),
response_function=done,
message_type=gtk.MESSAGE_ERROR)
if gtk.main_level() == 0:
gtk.main()
lock.acquire()
return 0
except:
return -1
def display_error_zenity(title, text):
if isinstance(title, cxutils.unicode_type):
title = title.encode('utf8')
if isinstance(text, cxutils.unicode_type):
text = text.encode('utf8')
return cxutils.run(['zenity', '--error', '--no-markup', '--title', title,
'--text', text])
def display_error_kdialog(title, text):
if isinstance(title, cxutils.unicode_type):
title = title.encode('utf8')
if isinstance(text, cxutils.unicode_type):
text = text.encode('utf8')
return cxutils.run(['kdialog', '--title', title, '--error', text])
def display_error_xmessage(title, text):
if isinstance(title, cxutils.unicode_type):
try:
title = title.encode('iso-8859-1')
except UnicodeEncodeError:
# Hope xmessage will display it right anyway
title = title.encode('utf8')
if isinstance(text, cxutils.unicode_type):
try:
text = text.encode('iso-8859-1')
except UnicodeEncodeError:
# Hope xmessage will display it right anyway
text = text.encode('utf8')
return cxutils.run(['xmessage', title + '\n' + text])
def display_error(gui, title, text):
if not gui:
# pylint: disable=C0325
print(text)
return
# kdialog and xmessage return 1 either when an error occurs or when the user
# closes the window instead of clicking on a button. So we cannot know if
# the user really got notified through the GUI. So always print the error
# on the console too. Do it first too in case the GUI garbles the message
# (xmessage I'm looking at you).
import sys
sys.stderr.write(title + "\n" + text)
# retcode is negative if the tool is not present, except for zenity which
# also returns -5 if it cannot open the display. Either way it means the
# user did not get the warning so try the next one when that happens.
retcode = display_error_internal(title, text)
if retcode < 0:
retcode, _out, _err = display_error_zenity(title, text)
if retcode < 0:
retcode, _out, _err = display_error_kdialog(title, text)
if retcode < 0:
# xmessage should only be used as a last resort
display_error_xmessage(title, text)
#####
#
# Loading the distributions and fixes information
#
#####
DISTROS = None
FIXES = None
def _load_fixes():
# pylint: disable=W0603
global DISTROS
global FIXES
if DISTROS is not None:
return
# Try to grab the latest information on distributions and fixes. Use a
# short timeout to not risk delaying the application startup too much.
import cxproduct
latestfixes = os.path.join(cxproduct.get_user_dir(), "cxfixes.xml")
try:
import fileupdate
fileupdate.update(latestfixes, FIXES_URL, True, 5)
except:
# We cannot download updates with Python 3 due to urllib2
# among other issues.
pass
import cxfixesparser
l_release, l_distros, l_fixes, l_errors = cxfixesparser.read_file(latestfixes)
for error in l_errors:
cxlog.warn(error)
# Load the builtin information
b_release, b_distros, b_fixes, b_errors = cxfixesparser.read_file(os.path.join(cxutils.CX_ROOT, "share/crossover/data/cxfixes.xml"))
for error in b_errors:
cxlog.warn(error)
# Use the most recent data
if b_distros is None and l_distros is None:
pass
elif b_distros is None or (l_release is not None and b_release < l_release):
cxlog.log("using the downloaded fixes information (release %s)" % l_release)
DISTROS = l_distros
FIXES = l_fixes
else:
cxlog.log("using the builtin fixes information (release %s)" % b_release)
DISTROS = b_distros
FIXES = b_fixes
if DISTROS is None:
cxlog.warn("Could not load any fixes information")
# Consider that there are no known distributions and fixes
DISTROS = {}
FIXES = {}
#####
#
# Platform detection
#
#####
_BITNESS = None
def detect_bitness():
# pylint: disable=W0603
global _BITNESS
if _BITNESS is None:
_BITNESS = '64'
# We don't detect bitness through uname to correctly deal with chroots.
_retcode, out, _err = cxutils.run(('env', 'POSIXLY_CORRECT=1', 'file', '/bin/ls'), stdout=cxutils.GRAB, stderr=cxutils.NULL)
if out.find('ELF 32-bit') >= 0:
_BITNESS = '32'
return _BITNESS
_DISTROID = None
def detect_distribution():
# pylint: disable=W0603
global _DISTROID
if _DISTROID is None:
_load_fixes()
import globtree
glob_tree = globtree.FileContentGlobTree()
import re
for (distroid, distro) in DISTROS.items():
for distglob in distro.globs:
try:
glob_tree.add_content_glob(distglob.file_glob, distglob.patterns, distroid)
except re.error:
cxlog.warn("the %s fileglob of %s contains an invalid regular expression" % (distglob.file_glob, distroid))
best_priority = -1
for _filename, distroid in glob_tree.matches(cxutils.b('/')):
if _DISTROID is None or \
best_priority < DISTROS[distroid].priority:
_DISTROID = distroid
best_priority = DISTROS[distroid].priority
cxlog.log("found a match for distro %s (%s)" % (_DISTROID, best_priority))
if _DISTROID is None:
cxlog.err("Could not identify the distribution")
return _DISTROID
#####
#
# Error management
#
#####
_ERRORS = {}
_BUILTIN_DESCRIPTIONS = \
{'dbus': _('Could not load the D-Bus Python modules.'),
'gtk2': _('Could not load the GTK+ Python modules.'),
'missinglibc': _('Could not find the 32-bit C library.'),
}
def add_error(errid, description=None):
if description is None:
if errid in _BUILTIN_DESCRIPTIONS:
description = _BUILTIN_DESCRIPTIONS[errid]
else:
description = _('Unknown error %s' % errid)
_ERRORS[errid] = description
def remove_error(errid):
del _ERRORS[errid]
def has_error(errid):
return errid in _ERRORS
def has_errors():
if _ERRORS:
return True
return False
def get_errors():
return _ERRORS
def clear_errors():
# pylint: disable=W0603
global _ERRORS
_ERRORS = {}
#####
#
# Fixes management
#
#####
def has_fix(errid):
if get_packages(errid):
return True
return False
def get_distribution_property(distroid, name):
"""Returns the distribution's property, using the fallbacks if necessary.
If there is no such property, then None is returned.
"""
dist = DISTROS[distroid]
while name not in dist.__dict__:
if dist.fallback is None:
return None
dist = DISTROS[dist.fallback]
return dist.__dict__[name]
def get_packages(seed_errid, seed_distroid=None, bitness=None):
_load_fixes()
if seed_errid not in FIXES:
return None
if seed_distroid is None:
seed_distroid = detect_distribution()
if seed_distroid is None:
return None
if bitness is None:
bitness = detect_bitness()
distroids = [seed_distroid]
dist = DISTROS[seed_distroid]
while dist.fallback:
distroids.append(dist.fallback)
dist = DISTROS[dist.fallback]
# Check if there are any other errors that this one may have masked and
# which should be fixed at the same time
errids = [seed_errid]
for fixid, fix in FIXES.items():
if seed_errid in fix.tiedto:
errids.append(fixid)
# Return the packages for all the relevant errors
packages = []
for errid in errids:
for distroid in distroids:
distfixes = FIXES[errid].distfixes
if (distroid, bitness) in distfixes:
packages.extend(distfixes[(distroid, bitness)].packages)
break
if (distroid, None) in distfixes:
packages.extend(distfixes[(distroid, None)].packages)
break
return packages
def get_fix_command(update=True):
if not has_errors():
return ([], None)
distroid = detect_distribution()
if distroid is None:
return ([], None)
bitness = detect_bitness()
# First make sure the method for fixing errors in distribution is one
# we know about. Currently that's the packagecmd method.
cmd = get_distribution_property(distroid, 'packagecmd')
if cmd is None:
cxlog.warn("No command has been set for installing the packages on %s" % DISTROS[distroid].name)
return ([], None)
fixable_errors = set()
packages = set()
for errid in _ERRORS:
fix_packages = get_packages(errid, distroid, bitness)
if fix_packages:
fixable_errors.add(errid)
packages.update(fix_packages)
if not fixable_errors:
return ([], None)
cmd += ' ' + cxutils.argvtocmdline(sorted(packages))
updatecmd = get_distribution_property(distroid, 'updatecmd')
if update and updatecmd:
# Ignore update errors, hoping the package list was not too outdated
cmd = updatecmd + "; " + cmd
# If we need to install a Debian multiarch package, make sure we have
# the required architecture
for package in packages:
if ':' in package:
retcode, out, _err = cxutils.run(('dpkg', '--print-architecture'), stdout=cxutils.GRAB, stderr=cxutils.NULL)
if retcode == 0 and out != 'i386\n':
retcode, out, _err = cxutils.run(('dpkg', '--print-foreign-architectures'), stdout=cxutils.GRAB, stderr=cxutils.NULL)
if retcode == 0 and out.find('i386\n') < 0:
cmd = 'dpkg --add-architecture i386 && (' + cmd + ')'
break
return (fixable_errors, cmd)
def fix_errors(new_console=True, update=True):
fixable_errors, cmd = get_fix_command(update)
if cmd is None:
return
# The packaging tools are likely to ask for confirmation on the console
if new_console:
stdin = cxutils.NULL
else:
stdin = None
cxsu_cmd = [os.path.join(cxutils.CX_ROOT, "bin", "cxsu"),
'--description', _('CrossOver needs your permission to install missing packages.'),
'--console', '--ignore-home', 'sh', '-c', cmd]
retcode, _out, _err = cxutils.run(cxsu_cmd, stdin=stdin)
if retcode == 0:
for errid in fixable_errors:
remove_error(errid)
def report_errors(prefix=True, gui=True):
if not has_errors():
return
message = ""
if prefix:
import distversion
message = _("Some errors may prevent %s from working correctly:\n") % distversion.PRODUCT_NAME
for description in sorted(_ERRORS.values()):
message += "* " + description + "\n"
# Get a list of packages the user could try to install.
_load_fixes()
packages = {}
missing_fixes = set()
for errid in _ERRORS:
if errid not in FIXES:
missing_fixes.add(errid)
continue
distfixes = FIXES[errid].distfixes
for key in distfixes:
if key in packages:
packages[key].update(distfixes[key].packages)
else:
packages[key] = set(distfixes[key].packages)
# Avoid printing separate instructions for bitness-specific and
# bitness-independent cases.
# pylint: disable=C0201
for (distroid, bitness) in packages.keys():
if bitness is not None:
continue
key32 = (distroid, '32')
key64 = (distroid, '64')
if key32 in packages or key64 in packages:
dist_packages = packages[(distroid, None)]
packages[(distroid, None)] = None
if key32 in packages:
packages[key32].update(dist_packages)
elif 'missinglibc' not in _ERRORS:
packages[key32] = set(dist_packages)
if key64 in packages:
packages[key64].update(dist_packages)
else:
packages[key64] = set(dist_packages)
if packages:
message += _("\nYou may be able to fix some issues by running one of the following commands as root:\n")
lines = []
for (distroid, bitness) in packages:
if not packages[(distroid, bitness)]:
continue
name = DISTROS[distroid].name
if bitness is not None:
name = _("%(name)s (%(bitness)s bits)") % {'name': name, 'bitness': bitness}
lines.append("%-23s\t%s %s\n" % (name, get_distribution_property(distroid, 'packagecmd'),
' '.join(sorted(packages[(distroid, bitness)]))))
message += ''.join(sorted(lines))
if missing_fixes:
message += _("\nThere is no automated fix for the remaining issues but you may find help on these pages:\n")
for errid in missing_fixes:
message += WIKI_URL + errid + "\n"
display_error(gui, _("Could not install some Unix packages"), message)