File manager - Edit - /var/www/payraty/helpdesk/public/storage/branding_media/images/apport.tar
Back
blacklist.d/README.blacklist 0000644 00000000331 00000000000 0011523 0 ustar 00 # Blacklist for apport # If an executable path appears on any line in any file in # /etc/apport/blacklist.d/, apport will not generate a crash report # for it. Matches are exact only at the moment (no globbing etc.). blacklist.d/apport 0000644 00000000030 00000000000 0010120 0 ustar 00 /usr/bin/wine-preloader crashdb.conf 0000644 00000002300 00000000000 0006755 0 ustar 00 # map crash database names to CrashDatabase implementations and URLs default = 'ubuntu' def get_oem_project(): '''Determine OEM project name from Distribution Channel Descriptor Return None if it cannot be determined or does not exist. ''' try: dcd = open('/var/lib/ubuntu_dist_channel').read() if dcd.startswith('canonical-oem-'): return dcd.split('-')[2] except IOError: return None databases = { 'ubuntu': { 'impl': 'launchpad', 'bug_pattern_url': 'http://people.canonical.com/~ubuntu-archive/bugpatterns/bugpatterns.xml', 'dupdb_url': 'http://people.canonical.com/~ubuntu-archive/apport-duplicates', 'distro': 'ubuntu', 'problem_types': ['Bug', 'Package'], 'escalation_tag': 'bugpattern-needed', 'escalated_tag': 'bugpattern-written', }, 'canonical-oem': { 'impl': 'launchpad', 'bug_pattern_url': 'http://people.canonical.com/~ubuntu-archive/bugpatterns/bugpatterns.xml', 'project': get_oem_project(), }, 'debug': { # for debugging 'impl': 'memory', 'bug_pattern_url': '/tmp/bugpatterns.xml', 'distro': 'debug' }, } etc/init.d/apport 0000755 00000005550 00000000000 0007705 0 ustar 00 #! /bin/sh ### BEGIN INIT INFO # Provides: apport # Required-Start: $local_fs $remote_fs # Required-Stop: $local_fs $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: # Short-Description: automatic crash report generation ### END INIT INFO DESC="automatic crash report generation" NAME=apport AGENT=/usr/share/apport/apport SCRIPTNAME=/etc/init.d/$NAME # Exit if the package is not installed [ -x "$AGENT" ] || exit 0 # read default file enabled=1 [ -e /etc/default/$NAME ] && . /etc/default/$NAME || true # Define LSB log_* functions. # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started [ -e /var/crash ] || mkdir -p /var/crash chmod 1777 /var/crash # check for kernel crash dump, convert it to apport report if [ -e /var/crash/vmcore ] || [ -n "`ls /var/crash | egrep ^[0-9]{12}$`" ];then /usr/share/apport/kernel_crashdump || true fi # check for incomplete suspend/resume or hibernate if [ -e /var/lib/pm-utils/status ]; then /usr/share/apport/apportcheckresume || true rm -f /var/lib/pm-utils/status rm -f /var/lib/pm-utils/resume-hang.log fi echo "|$AGENT -p%p -s%s -c%c -d%d -P%P -u%u -g%g -F%F -- %E" > /proc/sys/kernel/core_pattern echo 2 > /proc/sys/fs/suid_dumpable echo 10 > /proc/sys/kernel/core_pipe_limit } # # Function that stops the daemon/service # do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred echo 0 > /proc/sys/kernel/core_pipe_limit echo 0 > /proc/sys/fs/suid_dumpable # Check for a hung resume. If we find one try and grab everything # we can to aid in its discovery. if [ -e /var/lib/pm-utils/status ]; then ps -wwef >/var/lib/pm-utils/resume-hang.log fi if [ "`dd if=/proc/sys/kernel/core_pattern count=1 bs=1 2>/dev/null`" != "|" ]; then return 1 else echo "core" > /proc/sys/kernel/core_pattern fi } case "$1" in start) # don't start in containers grep -zqs '^container=' /proc/1/environ && exit 0 [ "$enabled" = "1" ] || [ "$force_start" = "1" ] || exit 0 [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC:" "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) # don't stop in containers grep -zqs '^container=' /proc/1/environ && exit 0 [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC:" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; restart|force-reload) $0 stop || true $0 start ;; *) echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 exit 3 ;; esac : testsuite/crash.class 0000644 00000000661 00000000000 0010670 0 ustar 00 ��� 2 <init> ()V Code LineNumberTable main ([Ljava/lang/String;)V SourceFile crash.java java/lang/RuntimeException Can't catch this crash java/lang/Object 0com/ubuntu/apport/ApportUncaughtExceptionHandler install (Ljava/lang/String;)V *� � ) � � Y� � testsuite/crash.jar 0000644 00000001337 00000000000 0010340 0 ustar 00 PK ({�Z META-INF/�� PK ({�Z META-INF/MANIFEST.MF�M��LK-.� K-*��ϳR0�3��r.JM,IM�u� E�,4B�J�JJ5y�x� PK��߱6 7 PK ({�Z crash.classmPAK�0}Y�u����M���y��UDC�� ��,�m:�T�[�& z�G�_�l����K�|��8 kh:X/cÅ�����M�҉�ʜ2X��C�<I��@iy�F�L�S������`�r=��&Qz|��a�&B^�L슄ς�LXE[Ul���Y�oSmT$/^��Sx��=�܈�k5��;���ƟHaEy�OQ�ǧ�81�Y�Z�t�E�ףP&��3�Ð���tQ��F�&=�U����ޟ��R��Bk)'-�UQ���i���7>`=6�s�_��'�$�̫�M�_PKB3� 7 � PK ({�Z META-INF/�� PK ({�Z��߱6 7 + META-INF/MANIFEST.MFPK ({�ZB3� 7 � � crash.classPK � root_info_wrapper 0000755 00000000140 00000000000 0010164 0 ustar 00 #!/bin/sh # this wrapper just exists so that we can put a polkit .policy around it exec sh "$@" kernel_oops 0000755 00000002321 00000000000 0006751 0 ustar 00 #!/usr/bin/python3 # # Collect information about a kernel oops. # # Copyright (c) 2008 Canonical Ltd. # Author: Matt Zimmerman <mdz@canonical.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import os import sys import apport.fileutils from apport import unicode_gettext as _ checksum = None if len(sys.argv) > 1: checksum = sys.argv[1] oops = sys.stdin.read() pr = apport.Report('KernelOops') pr['Failure'] = 'oops' pr['Tags'] = 'kernel-oops' pr['Annotation'] = _('Your system might become unstable ' 'now and might need to be restarted.') package = apport.packaging.get_kernel_package() pr.add_package(package) pr['SourcePackage'] = 'linux' pr['OopsText'] = oops u = os.uname() pr['Uname'] = '%s %s %s' % (u[0], u[2], u[4]) # write report try: with apport.fileutils.make_report_file(pr, uid=checksum) as f: pr.write(f) except (IOError, OSError) as e: apport.fatal('Cannot create report: ' + str(e)) package-hooks/source_shadow.py 0000644 00000001320 00000000000 0012434 0 ustar 00 #!/usr/bin/python '''Apport package hook for shadow (c) 2010 Canonical Ltd. Contributors: Marc Deslauriers <marc.deslauriers@canonical.com> 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. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' from apport.hookutils import * def add_info(report): attach_file_if_exists(report, '/etc/login.defs', 'LoginDefs') if __name__ == '__main__': report = {} add_info(report) for key in report: print('[%s]\n%s' % (key, report[key])) package-hooks/vsftpd.py 0000644 00000002043 00000000000 0011100 0 ustar 00 #!/usr/bin/python '''apport hook for vsftpd (c) 2010 Andres Rodriguez. Author: Andres Rodriguez <andreserl@ubuntu.com> 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. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' from apport.hookutils import * def add_info(report, ui): response = ui.yesno("The contents of your /etc/vsftpd.conf file " "may help developers diagnose your bug more " "quickly. However, it may contain sensitive " "information. Do you want to include it in your " "bug report?") if response == None: # user cancelled raise StopIteration elif response == True: attach_conffiles(report,'vsftpd') attach_file_if_exists(report, os.path.expanduser('/var/log/vsftpd.log'), 'vsftpd.log') package-hooks/source_linux.py 0000644 00000014236 00000000000 0012320 0 ustar 00 '''Apport package hook for the Linux kernel. (c) 2008 Canonical Ltd. Contributors: Matt Zimmerman <mdz@canonical.com> Martin Pitt <martin.pitt@canonical.com> Brian Murray <brian@canonical.com> 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. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' import os.path, re import apport import apport.hookutils SUBMIT_SCRIPT = "/usr/bin/kerneloops-submit" def add_info(report, ui): # If running an upstream kernel, instruct reporter to file bug upstream abi = re.search("-(.*?)-", report['Uname']) if abi and (abi.group(1) == '999' or re.search("^0\\d", abi.group(1))): ui.information("It appears you are currently running a mainline kernel. It would be better to report this bug upstream at http://bugzilla.kernel.org/ so that the upstream kernel developers are aware of the issue. If you'd still like to file a bug against the Ubuntu kernel, please boot with an official Ubuntu kernel and re-file.") report['UnreportableReason'] = 'The running kernel is not an Ubuntu kernel' return version_signature = report.get('ProcVersionSignature', '') if not version_signature.startswith('Ubuntu ') and 'CrashDB' not in report: report['UnreportableReason'] = 'The running kernel is not an Ubuntu kernel' return # Prevent reports against the linux-meta and linux-signed families, redirect to the main package. for src_pkg in ['linux-meta', 'linux-signed']: if report['SourcePackage'].startswith(src_pkg): report['SourcePackage'] = report['SourcePackage'].replace(src_pkg, 'linux', 1) report.setdefault('Tags', '') # Tag up back ported kernel reports for easy identification if report['SourcePackage'].startswith('linux-lts-'): report['Tags'] += ' qa-kernel-lts-testing' apport.hookutils.attach_hardware(report) apport.hookutils.attach_alsa(report) apport.hookutils.attach_wifi(report) apport.hookutils.attach_file(report, '/proc/fb', 'ProcFB') staging_drivers = re.findall("(\\w+): module is from the staging directory", report['CurrentDmesg']) if staging_drivers: staging_drivers = list(set(staging_drivers)) report['StagingDrivers'] = ' '.join(staging_drivers) report['Tags'] += ' staging' # Only if there is an existing title prepend '[STAGING]'. # Changed to prevent bug titles with just '[STAGING] '. if report.get('Title'): report['Title'] = '[STAGING] ' + report.get('Title') apport.hookutils.attach_file_if_exists(report, "/etc/initramfs-tools/conf.d/resume", key="HibernationDevice") uname_release = os.uname()[2] lrm_package_name = 'linux-restricted-modules-%s' % uname_release lbm_package_name = 'linux-backports-modules-%s' % uname_release apport.hookutils.attach_related_packages(report, [lrm_package_name, lbm_package_name, 'linux-firmware']) if ('Failure' in report and report['Failure'] == 'oops' and 'OopsText' in report and os.path.exists(SUBMIT_SCRIPT)): # tag kerneloopses with the version of the kerneloops package apport.hookutils.attach_related_packages(report, ['kerneloops-daemon']) oopstext = report['OopsText'] dupe_sig1 = None dupe_sig2 = None for line in oopstext.splitlines(): if line.startswith('BUG:'): bug = re.compile('at [0-9a-f]+$') dupe_sig1 = bug.sub('at location', line) rip = re.compile('^[RE]?IP:') if re.search(rip, line): loc = re.compile('\\[<[0-9a-f]+>\\]') dupe_sig2 = loc.sub('location', line) if dupe_sig1 and dupe_sig2: report['DuplicateSignature'] = '%s %s' % (dupe_sig1, dupe_sig2) # it's from kerneloops, ask the user whether to submit there as well if ui: # Some OopsText begin with "--- [ cut here ] ---", so remove it oopstext = re.sub("---.*\n", "", oopstext) first_line = re.match(".*\n", oopstext) ip = re.search("(R|E)?IP\\:.*\n", oopstext) kernel_driver = re.search("(R|E)?IP(:| is at) .*\\[(.*)\\]\n", oopstext) call_trace = re.search("Call Trace(.*\n){,10}", oopstext) oops = '' if first_line: oops += first_line.group(0) if ip: oops += ip.group(0) if call_trace: oops += call_trace.group(0) if kernel_driver: report['Tags'] += ' kernel-driver-%s' % kernel_driver.group(3) # 2012-01-13 - disable submission question as kerneloops.org is # down # if ui.yesno("This report may also be submitted to " # "http://kerneloops.org/ in order to help collect aggregate " # "information about kernel problems. This aids in identifying " # "widespread issues and problematic areas. A condensed " # "summary of the Oops is shown below. Would you like to submit " # "information about this crash to kerneloops.org?" # "\n\n%s" % oops): # text = report['OopsText'] # proc = subprocess.Popen(SUBMIT_SCRIPT, stdin=subprocess.PIPE) # proc.communicate(text) elif 'Failure' in report and ('resume' in report['Failure'] or 'suspend' in report['Failure']): crash_signature = report.crash_signature() if crash_signature: report['DuplicateSignature'] = crash_signature if report.get('ProblemType') == 'Package': # in case there is a failure with a grub script apport.hookutils.attach_related_packages(report, ['grub-pc']) if __name__ == '__main__': r = apport.Report() r.add_proc_info() r.add_os_info() r['ProcVersionSignature'] = 'Ubuntu 3.4.0' add_info(r, None) for k, v in r.items(): print('%s: %s' % (k, v)) package-hooks/source_unattended-upgrades.py 0000644 00000001301 00000000000 0015111 0 ustar 00 """ unattended-upgrades apport hook Collects the following log file: - /var/log/unattended-upgrades/unattended-upgrades.log Check to see if either of these conffiles has been modified: - /etc/apt/apt.conf.d/50unattended-upgrades - /etc/apt/apt.conf.d/10periodic """ from apport.hookutils import ( attach_conffiles, attach_file_if_exists) def add_info(report, ui): # always attach these files attach_conffiles(report, 'unattended-upgrades', ui=ui) attach_conffiles(report, 'update-notifier-common', ui=ui) attach_file_if_exists( report, '/var/log/unattended-upgrades/unattended-upgrades.log') attach_file_if_exists( report, '/var/log/apt/history.log') package-hooks/source_shim-signed.py 0000644 00000003720 00000000000 0013364 0 ustar 00 '''apport package hook for shim and shim-signed (c) 2015 Canonical Ltd. Author: Brian Murray <brian@ubuntu.com> ''' import errno import os import re from apport.hookutils import ( command_available, command_output, recent_syslog, attach_file, attach_root_command_outputs) efiarch = {'amd64': 'x64', 'i386': 'ia32', 'arm64': 'aa64' } grubarch = {'amd64': 'x86_64', 'i386': 'i386', 'arm64': 'arm64' } def add_info(report, ui): efiboot = '/boot/efi/EFI/ubuntu' if command_available('efibootmgr'): report['EFIBootMgr'] = command_output(['efibootmgr', '-v']) else: report['EFIBootMgr'] = 'efibootmgr not available' commands = {} try: directory = os.stat(efiboot) except OSError as e: if e.errno == errno.ENOENT: report['Missing'] = '/boot/efi/EFI/ubuntu directory is missing' return if e.errno == errno.EACCES: directory= True if directory: arch = report['Architecture'] commands['BootEFIContents'] = 'ls %s' % efiboot commands['ShimDiff'] = 'diff %s/shim%s.efi /usr/lib/shim/shim%s.efi.signed' % (efiboot, efiarch[arch], efiarch[arch]) commands['GrubDiff'] = 'diff %s/grub%s.efi /usr/lib/grub/%s-efi-signed/grub%s.efi.signed' %(efiboot, efiarch[arch], grubarch[arch], efiarch[arch]) efivars_dir = '/sys/firmware/efi/efivars' sb_var = os.path.join(efivars_dir, 'SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c') mok_var = os.path.join(efivars_dir, 'MokSBStateRT-605dab50-e046-4300-abb6-3dd810dd8b23') attach_file(report, '/proc/sys/kernel/moksbstate_disabled') commands['SecureBoot'] = 'od -An -t u1 %s' % sb_var commands['MokSBStateRT'] = 'od -An -t u1 %s' % mok_var attach_root_command_outputs(report, commands) report['EFITables'] = recent_syslog(re.compile(r'(efi|esrt):|Secure boot')) package-hooks/source_sudo.py 0000644 00000002210 00000000000 0012120 0 ustar 00 #!/usr/bin/python3 '''Apport package hook for sudo (c) 2010 Canonical Ltd. Contributors: Marc Deslauriers <marc.deslauriers@canonical.com> 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. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' from apport.hookutils import * def add_info(report, ui): response = ui.yesno("The contents of your /etc/sudoers file may help developers diagnose your bug more quickly, however, it may contain sensitive information. Do you want to include it in your bug report?") if response == None: #user cancelled raise StopIteration elif response == True: # This needs to be run as root report['Sudoers'] = root_command_output(['/bin/cat', '/etc/sudoers']) report['VisudoCheck'] = root_command_output(['/usr/sbin/visudo', '-c']) elif response == False: ui.information("The contents of your /etc/sudoers will NOT be included in the bug report.") package-hooks/libatasmart4.py 0000644 00000001657 00000000000 0012173 0 ustar 00 '''apport package hook for libatasmart (c) 2009 Canonical Ltd. Author: Martin Pitt <martin.pitt@ubuntu.com> ''' import os import os.path import apport.hookutils import dbus UD = 'org.freedesktop.UDisks' def add_info(report): report['UdisksDump'] = apport.hookutils.command_output(['udisks', '--dump']) report['Udisks2Dump'] = apport.hookutils.command_output(['udisksctl', 'dump']) # grab SMART blobs dkd = dbus.Interface(dbus.SystemBus().get_object(UD, '/org/freedesktop/UDisks'), UD) for d in dkd.EnumerateDevices(): dev_props = dbus.Interface(dbus.SystemBus().get_object(UD, d), dbus.PROPERTIES_IFACE) blob = dev_props.Get(UD, 'DriveAtaSmartBlob') if len(blob) > 0: report['AtaSmartBlob_' + os.path.basename(d)] = ''.join(map(chr, blob)) if __name__ == '__main__': r = {} add_info(r) for k, v in r.items(): print('%s: "%s"' % (k, v)) package-hooks/ubuntu-desktop-installer.py 0000644 00000002374 00000000000 0014565 0 ustar 00 """ Send reports about ubuntu-desktop-installer to the correct Launchpad project. """ import os from apport import hookutils def add_info(report, ui): udilog = os.path.realpath("/var/log/installer/ubuntu_desktop_installer.log") hookutils.attach_file_if_exists(report, udilog, "UdiLog") report['SourcePackage'] = 'ubuntu-desktop-installer' # rewrite this section so the report goes to the project in Launchpad report[ "CrashDB" ] = """{ "impl": "launchpad", "project": "ubuntu-desktop-installer", "bug_pattern_url": "http://people.canonical.com/~ubuntu-archive/bugpatterns/bugpatterns.xml" }""" subiquitylog = os.path.realpath("/var/log/installer/subiquity-server-debug.log") hookutils.attach_file_if_exists(report, subiquitylog, "SubiquityLog") hookutils.attach_file_if_exists( report, "/var/log/installer/subiquity-curtin-install.conf", "CurtinConfig" ) hookutils.attach_file_if_exists( report, "/var/log/curtin/install.log", "CurtinLog" ) hookutils.attach_file_if_exists( report, "/var/log/curtin/curtin-error-logs.tar", "CurtinError" ) hookutils.attach_file_if_exists( report, "/var/log/installer/block/probe-data.json", "ProbeData" ) package-hooks/cryptsetup.py 0000644 00000002072 00000000000 0012016 0 ustar 00 '''apport package hook for cryptsetup (c) 2009 Author: Reinhard Tartler <siretart@tauware.de> (c) 2015 Author: Jonas Meurer <jonas@freesources.org> ''' from apport.hookutils import * msg = \ """ Providing additional information can help diagnose problems with cryptsetup. Specifically, this would include: - kernel cmdline (copy of /proc/cmdline). - crypttab configuration (copy of /etc/crypttab). - fstab configuration (copy of /etc/fstab). If this information is not relevant for your bug report or you have privacy concerns, please choose no. Do you want to provide additional information? (you will be able to review the data before it is sent) """ def add_info(report, ui): attach_files = False if ui: if ui.yesno(msg) == None: # user decided to cancel raise StopIteration # user is allowing files to be attached. attach_files = True if attach_files == False: # do not attach any files return attach_file(report, '/proc/cmdline', 'cmdline') attach_file(report, '/etc/fstab', 'fstab') attach_file_if_exists(report, '/etc/crypttab', 'crypttab') package-hooks/cloud-init.py 0000644 00000000260 00000000000 0011640 0 ustar 00 """Wrapper for cloudinit apport interface""" from cloudinit.apport import add_info as cloudinit_add_info def add_info(report, ui): return cloudinit_add_info(report, ui) package-hooks/source_ubiquity.py 0000644 00000017130 00000000000 0013030 0 ustar 00 '''Apport package hook for the ubiquity live CD installer. Copyright (C) 2009 Canonical Ltd. Authors: Colin Watson <cjwatson@ubuntu.com>, Brian Murray <brian@ubuntu.com>''' import apport.hookutils import os.path import re def add_installation_log(report, ident, name): f = False for try_location in ('/var/log/installer/%s', '/var/log/%s', '/var/log/upstart/%s'): if os.path.exists(try_location % name): f = try_location % name break if not f: return if os.access(f, os.R_OK): with open(f, 'rb') as f: report[ident] = f.read().decode('UTF-8', 'replace') elif os.path.exists(f): apport.hookutils.attach_root_command_outputs(report, {ident: "cat '%s'" % f}) if ident in report and isinstance(report[ident], bytes): try: report[ident] = report[ident].decode('UTF-8', 'replace') except (UnicodeDecodeError, KeyError): pass def prepare_duplicate_signature(syslog, collect_grub, collect_trace): collect = '' for line in syslog.split('\n'): if collect_grub: if 'grub-installer:' in line and collect == "": collect = ' '.join(line.split(' ')[4:]) + '\n' continue elif 'grub-installer:' in line and collect != "": collect += ' '.join(line.split(' ')[4:]) + '\n' continue if not collect_trace and collect != '': return collect if 'Traceback (most recent call last):' in line and \ collect_grub: collect += ' '.join(line.split(' ')[5:]) + '\n' continue if 'Traceback (most recent call last):' in line and \ not collect_grub: collect = ' '.join(line.split(' ')[5:]) + '\n' continue if len(line.split(' ')[5:]) == 1 and 'Traceback' in collect: if collect != '': return collect if 'Traceback' not in collect: continue collect += ' '.join(line.split(' ')[5:]) + '\n' def add_info(report, ui): add_installation_log(report, 'UbiquitySyslog', 'syslog') syslog = report['UbiquitySyslog'] if 'Buffer I/O error on device' in syslog: if re.search('Attached .* CD-ROM (\\w+)', syslog): cd_drive = re.search('Attached .* CD-ROM (\\w+)', syslog).group(1) cd_error = re.search('Buffer I/O error on device %s' % cd_drive, syslog) else: cd_error = None if cd_error: ui.information("The system log from your installation contains an error. The specific error commonly occurs when there is an issue with the media from which you were installing. This can happen when your media is dirty or damaged or when you've burned the media at a high speed. Please try cleaning the media and or burning new media at a lower speed. In the event that you continue to encounter these errors it may be an issue with your CD / DVD drive.") raise StopIteration if 'I/O error, dev' in syslog: # check for either usb stick (install media) or hard disk I/O errors if re.search('I/O error, dev (\\w+)', syslog): error_disk = re.search('I/O error, dev (\\w+)', syslog).group(1) mount = apport.hookutils.command_output(['grep', '%s' % error_disk, '/proc/mounts']) if 'target' in mount: ui.information("The system log from your installation contains an error. The specific error commonly occurs when there is an issue with the disk to which you are trying to install Ubuntu. It is recommended that you back up important data on your disk and investigate the situation. Measures you might take include checking cable connections for your disks and using software tools to investigate the health of your hardware.") raise StopIteration if 'cdrom' in mount: ui.information("The system log from your installation contains an error. The specific error commonly occurs when there is an issue with the media from which you were installing. Please try creating the USB stick you were installing from again or try installing from a different USB stick.") raise StopIteration if 'SQUASHFS error: Unable to read' in syslog: ui.information("The system log from your installation contains an error. The specific error commonly occurs when there is an issue with the media from which you were installing. This can happen when your media is dirty or damaged or when you've burned the media at a high speed. Please try cleaning the media and or burning new media at a lower speed. In the event that you continue to encounter these errors it may be an issue with your CD / DVD drive.") raise StopIteration if 'Kernel command line' in syslog: install_cmdline = re.search('Kernel command line: (.*)', syslog).group(1) else: install_cmdline = None if install_cmdline: report['InstallCmdLine'] = install_cmdline if 'Traceback' not in report: collect_grub = False collect_trace = False if 'grub-install ran successfully' not in syslog and 'grub-installer:' in syslog: collect_grub = True if 'Traceback' in syslog: collect_trace = True if report['ProblemType'] != 'Bug' and collect_grub or \ report['ProblemType'] != 'Bug' and collect_trace: duplicate_signature = prepare_duplicate_signature(syslog, collect_grub, collect_trace) if duplicate_signature: report['DuplicateSignature'] = duplicate_signature if collect_grub: report['SourcePackage'] = 'grub-installer' match = re.search('ubiquity.*Ubiquity (.*)\n', syslog) if match: match = match.group(1) report.setdefault('Tags', '') if match: report['Tags'] += ' ubiquity-%s' % match.split()[0] # tag bug reports where people choose to "upgrade" their install of Ubuntu if re.search('UpgradeSystem\\(\\) was called with safe mode', syslog): report['Tags'] += ' ubiquity-upgrade' add_installation_log(report, 'UbiquityPartman', 'partman') debug_log = '/var/log/installer/debug' debug_mode = False if os.path.exists(debug_log): try: fp = open(debug_log, 'r') except (OSError, IOError): pass else: with fp: for line in fp: if line.startswith('debconf (developer)'): debug_mode = True break if debug_mode: response = ui.yesno("The debug log file from your installation would help us a lot but includes the password you used for your user when installing Ubuntu. Do you want to include this log file?") if response is None: raise StopIteration if response: add_installation_log(report, 'UbiquityDebug', 'debug') else: add_installation_log(report, 'UbiquityDebug', 'debug') add_installation_log(report, 'UbiquityDm', 'dm') add_installation_log(report, 'UpstartUbiquity', 'ubiquity.log') # add seed name as Tag so we know which image was used with open('/proc/cmdline', 'r') as f: cmdline = f.read() match = re.search('([^/]+)\\.seed', cmdline) if match: report['Tags'] += ' ' + match.group(1) add_installation_log(report, 'Casper', 'casper.log') add_installation_log(report, 'OemConfigLog', 'oem-config.log') if 'OemConfigLog' in report: report['Tags'] += ' oem-config' package-hooks/systemd.py 0000644 00000001547 00000000000 0011272 0 ustar 00 '''apport package hook for systemd (c) 2014 Canonical Ltd. Author: Martin Pitt <martin.pitt@ubuntu.com> ''' import os.path import apport.hookutils def add_info(report): apport.hookutils.attach_hardware(report) report['SystemdDelta'] = apport.hookutils.command_output(['systemd-delta']) if not os.path.exists('/run/systemd/system'): return # Add details about all failed units, if any out = apport.hookutils.command_output(['systemctl', '--state=failed', '--full', '--no-legend']).strip() if out: failed = '' for line in out.splitlines(): unit = line.split()[0] if failed: failed += '------\n' failed += apport.hookutils.command_output(['systemctl', 'status', '--full', unit]) report['SystemdFailedUnits'] = failed package-hooks/source_ubuntu-release-upgrader.py 0000644 00000005174 00000000000 0015731 0 ustar 00 '''apport package hook for ubuntu-release-upgrader (c) 2011-2022 Canonical Ltd. Author: Brian Murray <brian@ubuntu.com> ''' import os import re from glob import glob from apport.hookutils import ( attach_gsettings_package, attach_file_if_exists, attach_root_command_outputs, command_output, root_command_output) def add_info(report, ui): try: attach_gsettings_package(report, 'ubuntu-release-upgrader') except: pass report['CrashDB'] = 'ubuntu' report.setdefault('Tags', 'dist-upgrade') report['Tags'] += ' dist-upgrade' clone_file = '/var/log/dist-upgrade/apt-clone_system_state.tar.gz' if os.path.exists(clone_file): report['VarLogDistupgradeAptclonesystemstate.tar.gz'] = \ root_command_output(["cat", clone_file], decode_utf8=False) attach_file_if_exists(report, '/var/log/dist-upgrade/apt.log', 'VarLogDistupgradeAptlog') attach_file_if_exists(report, '/var/log/dist-upgrade/apt-term.log', 'VarLogDistupgradeApttermlog') attach_file_if_exists(report, '/var/log/dist-upgrade/history.log', 'VarLogDistupgradeAptHistorylog') attach_file_if_exists(report, '/var/log/dist-upgrade/lspci.txt', 'VarLogDistupgradeLspcitxt') attach_file_if_exists(report, '/var/log/dist-upgrade/main.log', 'VarLogDistupgradeMainlog') attach_file_if_exists(report, '/var/log/dist-upgrade/term.log', 'VarLogDistupgradeTermlog') attach_file_if_exists(report, '/var/log/dist-upgrade/xorg_fixup.log', 'VarLogDistupgradeXorgFixuplog') attach_file_if_exists(report, '/var/log/dist-upgrade/screenlog.0', 'VarLogDistupgradeScreenlog') attach_root_command_outputs( report, {'CurrentDmesg.txt': 'dmesg | comm -13 --nocheck-order /var/log/dmesg -'}) if os.path.exists('/run/systemd/system'): report['JournalErrors'] = command_output( ['journalctl', '-b', '--priority=warning', '--lines=1000']) # the release upgrade may have crashed due to something else crashing reports = glob('/var/crash/*') if reports: report['CrashReports'] = command_output( ['stat', '-c', '%a:%u:%g:%s:%y:%x:%n'] + reports) problem_type = report.get("ProblemType", None) if problem_type == 'Crash': tmpdir = re.compile('ubuntu-release-upgrader-\w+') tb = report.get("Traceback", None) if tb: dupe_sig = '' for line in tb.splitlines(): scrub_line = tmpdir.sub('ubuntu-release-upgrader-tmpdir', line) dupe_sig += scrub_line + '\n' report["DuplicateSignature"] = dupe_sig package-hooks/apache2.py 0000644 00000003430 00000000000 0011076 0 ustar 00 #!/usr/bin/python '''apport hook for apache2 (c) 2010 Adam Sommer. Author: Adam Sommer <asommer@ubuntu.com> 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. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' from apport.hookutils import * import os SITES_ENABLED_DIR = '/etc/apache2/sites-enabled/' def add_info(report, ui): if os.path.isdir(SITES_ENABLED_DIR): response = ui.yesno("The contents of your " + SITES_ENABLED_DIR + " directory " "may help developers diagnose your bug more " "quickly. However, it may contain sensitive " "information. Do you want to include it in your " "bug report?") if response == None: # user cancelled raise StopIteration elif response == True: # Attache config files in /etc/apache2/sites-enabled and listing of files in /etc/apache2/conf.d for conf_file in os.listdir(SITES_ENABLED_DIR): attach_file_if_exists(report, SITES_ENABLED_DIR + conf_file, conf_file) try: report['Apache2ConfdDirListing'] = str(os.listdir('/etc/apache2/conf.d')) except OSError: report['Apache2ConfdDirListing'] = str(False) # Attach default config files if changed. attach_conffiles(report, 'apache2', conffiles=None) # Attach the error.log file. attach_file(report, '/var/log/apache2/error.log', key='error.log') # Get loaded modules. report['Apache2Modules'] = root_command_output(['/usr/sbin/apachectl', '-D DUMP_MODULES']) package-hooks/openssh-server.py 0000644 00000001754 00000000000 0012565 0 ustar 00 '''apport hook for openssh-server (c) 2010 Canonical Ltd. Author: Chuck Short <chuck.short@canonical.com> 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. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' from apport.hookutils import root_command_output def add_info(report, ui): response = ui.yesno("The contents of your /etc/ssh/sshd_config file " "may help developers diagnose your bug more " "quickly. However, it may contain sensitive " "information. Do you want to include it in your " "bug report?") if response == None: # user cancelled raise StopIteration elif response: report['SSHDConfig'] = root_command_output(['/usr/sbin/sshd', '-T']) package-hooks/isc-dhcp-client.py 0000644 00000003404 00000000000 0012542 0 ustar 00 #!/usr/bin/python '''apport hook for dhclient (c) 2010 Canonical Ltd. Author: Chuck Short <chuck.short@canonical.com> 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. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' from apport.hookutils import * from os import path import re def dhcp_leases(pattern): ''' Extract options from '/var/lib/dhcp/dhclient.leases' which match a regex. pattern should be a "re" object. ''' lines = "" if os.path.exists('/var/lib/dhcp/dhclient.leases'): file = '/var/lib/dhcp/dhclient.leases' else: return lines for line in open(file): if pattern.search(line): lines += line return lines def add_info(report, ui): response = ui.yesno("The contents of your /etc/dhcp/dhclient.conf file may help developers diagnose your bug more quickly, however, it may contain sensitive information. Do you want to include it in your bug report?") if response == None: #user cancelled raise StopIteration elif response == True: attach_file_if_exists(report, '/etc/dhcp/dhclient.conf', 'Dhclient') attach_mac_events(report, ['/sbin/dhclient', '/usr/lib/NetworkManager/nm-dhcp-client.action', '/usr/lib/connman/scripts/dhclient-script']) attach_related_packages(report, ['apparmor', 'libapparmor1', 'libapparmor-perl', 'apparmor-utils', 'auditd', 'libaudit0']) attach_file(report, '/etc/apparmor.d/sbin.dhclient') leases = re.compile('option|renew|rebind|expire', re.IGNORECASE) report['DhclientLeases'] = dhcp_leases(leases) package-hooks/source_console-setup.py 0000644 00000000566 00000000000 0013762 0 ustar 00 '''apport package hook for console-setup Copyright (C) 2009 Canonical Ltd. Author: Colin Watson <cjwatson@ubuntu.com> ''' import apport.hookutils def add_info(report): apport.hookutils.attach_file_if_exists( report, '/etc/default/keyboard', 'Keyboard') apport.hookutils.attach_file_if_exists( report, '/etc/default/console-setup', 'ConsoleSetup') package-hooks/source_apport.py 0000644 00000001071 00000000000 0012457 0 ustar 00 '''Apport package hook for apport itself. This adds /var/log/apport.log and the file listing in /var/crash to the report. ''' # Copyright 2007 Canonical Ltd. # Author: Martin Pitt <martin.pitt@ubuntu.com> from glob import glob import apport.hookutils APPORT_LOG = '/var/log/apport.log' def add_info(report): apport.hookutils.attach_file_if_exists(report, APPORT_LOG, 'ApportLog') reports = glob('/var/crash/*') if reports: report['CrashReports'] = apport.hookutils.command_output( ['stat', '-c', '%a:%u:%g:%s:%y:%x:%n'] + reports) package-hooks/subiquity.py 0000644 00000012156 00000000000 0011636 0 ustar 00 """Send reports about subiquity to the correct Launchpad project.""" import os import re from apport import Report, hookutils from apport.ui import HookUI def get_journal(report: Report, log_map: dict[str, str]) -> None: """Get installer environment journal log. Appends the installer specific journal to the log map if it exists, otherwise appends the system journal to the report directly. """ installer_journal = "/var/log/installer/installer-journal.txt" if os.path.exists(installer_journal): log_map["InstallerJournal"] = installer_journal else: report["SystemJournal"] = hookutils.recent_syslog(re.compile(".")) def get_revision(debug_log_contents: str) -> str: """Get subiquity revision from debug log contents. Returns revision found (e.g. "1234") or "unknown" if not found. """ # Revision information is always in the first line of the log file first_line = debug_log_contents.splitlines()[0] marker = "Starting Subiquity server revision" if marker in first_line: revision = first_line.split(marker)[1].strip() else: revision = "unknown" return revision def add_info(report: Report, unused_ui: HookUI) -> None: """Package hook entry point.""" # Check if the snap was updated # TODO: Add logic to support this outside of the live environment. # It may be possible someone wants to report a bug against the # installer after first boot. report["SnapUpdated"] = str(os.path.exists("/run/subiquity/updating")) # rewrite this section so the report goes to the project in Launchpad report[ "CrashDB" ] = """\ { "impl": "launchpad", "project": "subiquity", "bug_pattern_url": "http://people.canonical.com/" "~ubuntu-archive/bugpatterns/bugpatterns.xml", } """ # add in hardware information hookutils.attach_hardware(report) # This key is important for checking revision information later debug_log_key = "InstallerServerLog" # static subiquity generated logs log_map = { debug_log_key: "/var/log/installer/subiquity-server-debug.log", "InstallerServerLogInfo": "/var/log/installer/subiquity-server-info.log", "InstallerClientLog": "/var/log/installer/subiquity-client-debug.log", "InstallerClientLogInfo": "/var/log/installer/subiquity-client-info.log", "CurtinLog": "/var/log/installer/curtin-install.log", "CurtinAptConfig": "var/log/installer/curtin-install/subiquity-curtin-apt.conf", "CurtinCurthooksConfig": ( "/var/log/installer/curtin-install/subiquity-curthooks.conf" ), "CurtinExtractConfig": ( "/var/log/installer/curtin-install/subiquity-extract.conf" ), "CurtinInitialConfig": ( "/var/log/installer/curtin-install/subiquity-initial.conf" ), "CurtinPartitioningConfig": ( "/var/log/installer/curtin-install/subiquity-partitioning.conf" ), "ProbeData": "/var/log/installer/block/probe-data.json", "Traceback": "/var/log/installer/subiquity-traceback.txt", "NetplanInstallerConfig": "/etc/netplan/00-installer-config.yaml", "NetplanSnapdConfig": "/etc/netplan/00-snapd-config.yaml", } # Add journal log get_journal(report, log_map) # Add ubuntu-desktop-bootstrap information if available udb_log = os.path.realpath("/var/log/installer/ubuntu_bootstrap.log") if os.path.exists(udb_log): log_map["BootstrapLog"] = udb_log report.add_tags(["ubuntu-desktop-bootstrap"]) # check for udb snap version udb_snap = os.path.realpath("/snap/ubuntu-desktop-bootstrap/current") if os.path.exists(udb_snap): # "current" is a symlink to the current revision and # os.path.realpath should have resolved it report["DesktopInstallerRev"] = os.path.basename(udb_snap) # Attach logs if they exist # The conventional way to attach logs would be to use the # hookutils.attach_file_if_exists method, but since subiquity logs # are mostly locked down with root r/w only then we will get a permission # error if the caller does not have permissions. Ask for elevated # permissions instead of requiring users know to run with sudo or similar command_mapping = {} for name, path in log_map.items(): if os.path.exists(path): real_path = os.path.realpath(path) command_mapping[name] = f"cat {real_path}" hookutils.attach_root_command_outputs(report, command_mapping) # Get revision to display in the UI prompt # If attach_root_command_outputs fails the log won't be in the report revision = "unknown" if debug_log_key in report: revision = get_revision(report[debug_log_key]) report["Package"] = f"subiquity ({revision})" report["SourcePackage"] = "subiquity" # Package and SourcePackage keys are deleted when the Snap is chosen, # so make a separate key to keep track of the revision report["SnapVersion"] = revision # Always set reports to private since we might collect sensitive data report["LaunchpadPrivate"] = "1" package-hooks/source_apparmor.py 0000644 00000005346 00000000000 0013004 0 ustar 00 '''apport package hook for apparmor (c) 2009-2014 Canonical Ltd. Author: Steve Beattie <sbeattie@ubuntu.com> Jamie Strandboge <jamie@canonical.com> License: GPLv2 ''' from apport.hookutils import (attach_file, attach_file_if_exists, packaging, command_output, root_command_output) import os import re import codecs def stringify(s): '''Converts a byte array into a unicode string''' return codecs.latin_1_decode(s)[0] def recent_kernlog(pattern): '''Extract recent messages from kern.log or message which match a regex. pattern should be a "re" object. ''' lines = '' if os.path.exists('/var/log/kern.log'): file = '/var/log/kern.log' elif os.path.exists('/var/log/messages'): file = '/var/log/messages' else: return lines with open(file, 'rb') as f: for l in f.readlines(): line = stringify(l) if pattern.search(line): lines += line return lines def recent_syslog(pattern): '''Extract recent messages from syslog which match a regex. pattern should be a "re" object. ''' lines = '' if os.path.exists('/var/log/syslog'): file = '/var/log/syslog' else: return lines with open(file, 'rb') as f: for l in f.readlines(): line = stringify(l) if pattern.search(line): lines += line return lines def add_info(report, ui): attach_file(report, '/proc/version_signature', 'ProcVersionSignature') attach_file(report, '/proc/cmdline', 'ProcKernelCmdline') sec_re = re.compile('audit\(|apparmor|selinux|security', re.IGNORECASE) report['KernLog'] = recent_kernlog(sec_re) # DBus messages are reported to syslog dbus_sec_re = re.compile('dbus.* apparmor', re.IGNORECASE) report['Syslog'] = recent_syslog(dbus_sec_re) packages = ['apparmor', 'apparmor-utils', 'libapparmor1', 'libapparmor-dev', 'apparmor-utils', 'apparmor-profiles', 'python3-apparmor', 'libpam-apparmor', 'libapache2-mod-apparmor', 'python3-libapparmor', 'auditd', 'libaudit0'] versions = '' for package in packages: try: version = packaging.get_version(package) except ValueError: version = 'N/A' if version is None: version = 'N/A' versions += '%s %s\n' % (package, version) report['ApparmorPackages'] = versions # These need to be run as root report['ApparmorStatusOutput'] = root_command_output(['/usr/sbin/apparmor_status']) report['PstreeP'] = command_output(['/usr/bin/pstree', '-p']) attach_file_if_exists(report, '/var/log/audit/audit.log', 'audit.log') package-hooks/source_ubuntu-advantage-tools.py 0000644 00000002241 00000000000 0015562 0 ustar 00 import os import tempfile from apport.hookutils import attach_file_if_exists from uaclient import defaults from uaclient.actions import collect_logs, APPARMOR_PROFILES from uaclient.config import UAConfig def add_info(report, ui=None): report["LaunchpadPrivate"] = "1" report["LaunchpadSubscribe"] = "ua-client" cfg = UAConfig() apparmor_files = [os.path.basename(f) for f in APPARMOR_PROFILES] with tempfile.TemporaryDirectory() as output_dir: collect_logs(cfg, output_dir) auto_include_log_files = { "cloud-id.txt", "cloud-id.txt-error", "ua-status.json", "ua-status.json-error", "livepatch-status.txt", "livepatch-status.txt-error", "pro-journal.txt", "apparmor_logs.txt", *apparmor_files, os.path.basename(cfg.cfg_path), os.path.basename(cfg.log_file), os.path.basename(cfg.data_path("jobs-status")), os.path.basename(defaults.CONFIG_DEFAULTS["log_file"]), } for f in auto_include_log_files: attach_file_if_exists(report, os.path.join(output_dir, f), key=f) package-hooks/source_debian-installer.py 0000644 00000003562 00000000000 0014376 0 ustar 00 '''Apport package hook for the Debian installer. Copyright (C) 2011 Canonical Ltd. Authors: Colin Watson <cjwatson@ubuntu.com>, Brian Murray <brian@ubuntu.com>''' import os from apport.hookutils import attach_hardware, command_available, command_output, attach_root_command_outputs def add_installation_log(report, ident, name): if os.path.exists('/var/log/installer/%s' % name): f = '/var/log/installer/%s' % name elif os.path.exists('/var/log/%s' % name): f = '/var/log/%s' % name else: return if os.access(f, os.R_OK): report[ident] = (f,) else: attach_root_command_outputs(report, {ident: "cat '%s'" % f}) def add_info(report): attach_hardware(report) report['DiskUsage'] = command_output(['df']) report['MemoryUsage'] = command_output(['free']) if command_available('dmraid'): attach_root_command_outputs(report, {'DmraidSets': 'dmraid -s', 'DmraidDevices': 'dmraid -r'}) if command_available('dmsetup'): attach_root_command_outputs(report, {'DeviceMapperTables': 'dmsetup table'}) try: installer_version = open('/var/log/installer/version') for line in installer_version: if line.startswith('ubiquity '): # File these reports on the ubiquity package instead report['SourcePackage'] = 'ubiquity' break installer_version.close() except IOError: pass add_installation_log(report, 'DIPartman', 'partman') add_installation_log(report, 'DISyslog', 'syslog') if __name__ == '__main__': report = {} add_info(report) for key in report: if isinstance(report[key], type('')): print('%s: %s' % (key, report[key].split('\n', 1)[0])) else: print('%s: %s' % (key, type(report[key]))) package-hooks/source_mdadm.py 0000644 00000004314 00000000000 0012237 0 ustar 00 '''apport package hook for mdadm (c) 2009-2016 Canonical Ltd. Author: Steve Beattie <sbeattie@ubuntu.com> Based on the ideas in debian's /usr/share/bug/mdadm/script ''' from apport.hookutils import attach_file, attach_file_if_exists, attach_hardware, path_to_key, command_output import os import re import glob import gzip import subprocess import sys def get_initrd_files(pattern): '''Extract listing of files from the current initrd which match a regex. pattern should be a "re" object. ''' (_, _, release, _, _) = os.uname() try: fd = gzip.GzipFile('/boot/initrd.img-' + release, 'rb') # universal_newlines needs to be False here as we're passing # binary data from gzip into cpio, which means we'll need to # decode the bytes into strings later when reading the output cpio = subprocess.Popen(['cpio', '-t'], close_fds=True, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=False) except OSError as e: return 'Error: ' + str(e) out = cpio.communicate(fd.read())[0].decode(sys.stdout.encoding, errors='replace') if cpio.returncode != 0: return 'Error: command %s failed with exit code %i %' % ( 'cpio', cpio.returncode, out) lines = ''.join([l for l in out.splitlines(True) if pattern.search(l)]) return lines def add_info(report): attach_hardware(report) attach_file(report, '/proc/mounts', 'ProcMounts') attach_file_if_exists(report, '/etc/mdadm/mdadm.conf', 'mdadm.conf') attach_file(report, '/proc/mdstat', 'ProcMDstat') attach_file(report, '/proc/partitions', 'ProcPartitions') attach_file(report, '/etc/blkid.tab', 'etc.blkid.tab') attach_file_if_exists(report, '/boot/grub/menu.lst', 'GrubMenu.lst') attach_file_if_exists(report, '/boot/grub/grub.cfg', 'Grub.cfg') attach_file_if_exists(report, '/etc/lilo.conf', 'lilo.conf') devices = glob.glob("/dev/[hs]d*") for dev in devices: report['MDadmExamine' + path_to_key(dev)] = command_output(['/sbin/mdadm', '-E', dev]) initrd_re = re.compile('md[a/]') report['initrd.files'] = get_initrd_files(initrd_re) package-hooks/source_plymouth.py 0000644 00000002702 00000000000 0013035 0 ustar 00 '''apport package hook for plymouth Copyright 2010 Canonical Ltd. Authors: Steve Langasek <steve.langasek@ubuntu.com>, Brian Murray <brian@ubuntu.com> ''' from apport.hookutils import * import os.path def _attach_file_filtered(report, path, key=None): '''filter out password from grub configuration''' if not key: key = path_to_key(path) if os.path.exists(path): with open(path,'r') as f: filtered = [l if not l.startswith('password') else '### PASSWORD LINE REMOVED ###' for l in f.readlines()] report[key] = ''.join(filtered) def add_info(report): attach_hardware(report) attach_file(report, '/proc/fb', 'ProcFB') attach_file(report, '/proc/cmdline', 'ProcCmdLine') attach_file(report, '/var/log/boot.log', 'BootLog') debug_log = '/var/log/plymouth-debug.log' if os.path.exists(debug_log): attach_root_command_outputs(report, {'PlymouthDebug': 'cat %s' % debug_log}) _attach_file_filtered(report, '/etc/default/grub', 'EtcDefaultGrub') default_alternative = '/etc/alternatives/default.plymouth' if os.path.exists(default_alternative): report['DefaultPlymouth'] = command_output(['readlink', default_alternative]) text_alternative = '/etc/alternatives/text.plymouth' if os.path.exists(text_alternative): report['TextPlymouth'] = command_output(['readlink', text_alternative]) package-hooks/source_grub2.py 0000644 00000007447 00000000000 0012210 0 ustar 00 # vim: set fileencoding=UTF-8 : '''apport package hook for grub2 Author: Jean-Baptiste Lallement <jeanbaptiste.lallement@gmail.com> 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 3 of the License, or (at your option) any later version. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' from __future__ import print_function import os import re import subprocess from apport.hookutils import ( attach_default_grub, attach_file, attach_file_if_exists, path_to_key, ) def check_shell_syntax(path): ''' Check the syntax of a shell script ''' try: with open(os.devnull, 'w') as devnull: subprocess.check_call(['/bin/sh', '-n', path], stderr=devnull) except subprocess.CalledProcessError: return False return True def check_shell_syntax_harder(path): ''' Check the syntax of a shell script ''' try: # sh -n is tempting, but not good enough. Consider this case: # # GRUB_CMDLINE_LINUX_DEFAULT=”quiet splash nomodeset” # # The quotes are Unicode quotes, not valid in the shell and probably # caused by copying a line out of a web page. This is parsed as an # instruction to run the 'splash' command with argument 'nomodeset”' # and with the GRUB_CMDLINE_LINUX_DEFAULT environment variable set # to '”quiet'. 'sh -n' allows this because this is a valid parse # and it's possible that the command 'splash' might exist, but what # we need to know is whether sourcing the file will fail. # # Unfortunately this test may involve executing code. However, this # file is already sourced as root when running update-grub, so it # seems unlikely that this could do any further harm. with open(os.devnull, 'w') as devnull: subprocess.check_call( ['/bin/sh', '-ec', '. %s' % re.escape(path)], stderr=devnull) except subprocess.CalledProcessError: return False return True def add_info(report): if report['ProblemType'] == 'Package': # To detect if root fs is a loop device attach_file(report, '/proc/cmdline', 'ProcCmdLine') attach_default_grub(report, 'EtcDefaultGrub') attach_file_if_exists(report, '/boot/grub/device.map', 'DeviceMap') try: grub_d = '/etc/default/grub.d' for name in sorted(os.listdir(grub_d)): if name.endswith('.cfg'): key = 'EtcDefaultGrubD.' + path_to_key(name) attach_file_if_exists( report, os.path.join(grub_d, name), key) except OSError: pass invalid_grub_script = [] if not check_shell_syntax_harder('/etc/default/grub'): invalid_grub_script.append('/etc/default/grub') # Check scripts in /etc/grub.d since some users directly change # configuration there grubdir = '/etc/grub.d' for f in os.listdir(grubdir): fullpath = os.path.join(grubdir, f) if f != 'README' and os.access(fullpath, os.X_OK) \ and not check_shell_syntax(fullpath): invalid_grub_script.append(fullpath) attach_file(report, fullpath) # TODO: Add some UI to ask if the user modified the invalid script # and if he still wants to report it if invalid_grub_script: report['InvalidGrubScript'] = ' '.join(invalid_grub_script) if __name__ == '__main__': r = {} r['ProblemType'] = 'Package' add_info(r) for k, v in r.items(): print('%s: "%s"' % (k, v)) print("========================================") package-hooks/source_update-manager.py 0000644 00000004614 00000000000 0014052 0 ustar 00 # -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- '''apport package hook for update-manager (c) 2011 Canonical Ltd. Author: Brian Murray <brian@ubuntu.com> ''' import os import re import subprocess from apport.hookutils import ( attach_gsettings_package, attach_root_command_outputs, attach_file_if_exists, command_available, recent_syslog) def run_hwe_command(option): command = ['hwe-support-status', option] sp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=None) out = sp.communicate()[0] if sp.returncode == 0: res = out.strip() # exit code is 10 on unsupported HWE elif sp.returncode == 10: res = out.strip() else: res = (b'Error: command ' + str(command).encode() + b' failed with exit code ' + str(sp.returncode).encode() + b': ' + out) return res def add_info(report, ui): problem_type = report.get("ProblemType", None) if problem_type == "Bug": response = ui.yesno("Is the issue you are reporting one you \ encountered when upgrading Ubuntu from one release to another?") else: response = None if response: os.execlp('apport-bug', 'apport-bug', 'ubuntu-release-upgrader') else: package = report.get("Package", None) if package: package = package.split(' ')[0] if package == 'update-manager': attach_gsettings_package(report, 'update-manager') attach_file_if_exists(report, '/var/log/apt/history.log', 'DpkgHistoryLog.txt') attach_file_if_exists(report, '/var/log/apt/term.log', 'DpkgTerminalLog.txt') attach_root_command_outputs( report, {'CurrentDmesg.txt': 'dmesg | comm -13 --nocheck-order /var/log/dmesg -'}) if command_available('hwe-support-status'): # not using apport's command_output because it doesn't expect a # return code of 10 unsupported = run_hwe_command('--show-all-unsupported') if unsupported: report['HWEunsupported'] = unsupported report['HWEreplacements'] = \ run_hwe_command('--show-replacements') report["Aptdaemon"] = recent_syslog(re.compile("AptDaemon")) package-hooks/source_mysql-8.0.py 0000644 00000007336 00000000000 0012634 0 ustar 00 '''apport package hook for mysql-8.0 (c) 2009 Canonical Ltd. Author: Mathias Gug <mathias.gug@canonical.com> ''' from __future__ import print_function, unicode_literals import os import os.path from apport.hookutils import ( attach_conffiles, attach_file, attach_mac_events, path_to_key, read_file ) def _add_my_conf_files(report, filename): key = 'MySQLConf' + path_to_key(filename) report[key] = "" for line in read_file(filename).split('\n'): try: if 'password' in line.split('=')[0]: line = "%s = @@APPORTREPLACED@@" % (line.split('=')[0]) report[key] += line + '\n' except IndexError: continue def strip_protected(line): ''' Mitigation for upstream bug that can lead to statements containing passwords being written to error log We strip out any lines containing terms listed on http://dev.mysql.com/doc/refman/8.0/en/password-logging.html (LP: #1574458) ''' protected_terms = [ 'grant', 'alter user', 'create user', 'set password', 'create server', 'alter server'] for term in protected_terms: if term in line: return '--- Line containing protected term %s stripped from log ' \ 'by apport hook. Ref. Launchpad bug #1574458' % term return line def add_info(report, ui=None): ''' Collect system information relevant to mysql. ''' attach_conffiles(report, 'mysql-server-8.0', conffiles=None) key = 'Logs' + path_to_key('/var/log/daemon.log') report[key] = "" for line in read_file('/var/log/daemon.log').split('\n'): try: if 'mysqld' in line.split()[4]: report[key] += line + '\n' except IndexError: continue if os.path.exists('/var/log/mysql/error.log'): key = 'Logs' + path_to_key('/var/log/mysql/error.log') report[key] = "" for line in read_file('/var/log/mysql/error.log').split('\n'): line = strip_protected(line) report[key] += line + '\n' attach_mac_events(report, '/usr/sbin/mysqld') attach_file(report, '/etc/apparmor.d/usr.sbin.mysqld') if not os.path.isdir('/etc/mysql'): report['EtcMysqlDirListing'] = str(False) response = ui.yesno("The /etc/mysql directory is missing, which " "suggests a local configuration problem rather " "than a bug in Ubuntu. Do you still wish to " "report this bug?") if not response: # user cancelled or answered No report['UnreportableReason'] = "Missing /etc/mysql directory" return False else: # By default my.cnf is a symlink, and _add_my_conf_files calls apport.hookutils.read_file() # doesn't support them, so send the link target separately instead. LP: #1969369 if os.path.islink('/etc/mysql/my.cnf'): my_cnf_link = os.path.realpath('/etc/mysql/my.cnf') report['MySQLConf.etc.mysql.my.cnf'] = f'my.cnf links to {my_cnf_link}' else: _add_my_conf_files(report, '/etc/mysql/my.cnf') _add_my_conf_files(report, '/etc/mysql/mysql.cnf') for d in ['/etc/mysql/conf.d', '/etc/mysql/mysql.conf.d']: if os.path.isdir(d): for f in os.listdir(d): _add_my_conf_files(report, os.path.join(d, f)) try: report['MySQLVarLibDirListing'] = str(os.listdir('/var/lib/mysql')) except OSError: report['MySQLVarLibDirListing'] = str(False) return True if __name__ == '__main__': report = {} add_info(report) for key in report: print('%s: %s' % (key, report[key].split('\n', 1)[0])) package-hooks/udev.py 0000644 00000000707 00000000000 0010542 0 ustar 00 '''apport package hook for udev (c) 2009 Canonical Ltd. Author: Martin Pitt <martin.pitt@ubuntu.com> ''' import os import apport.hookutils def add_info(report): apport.hookutils.attach_hardware(report) user_rules = [] for f in os.listdir('/etc/udev/rules.d'): if not f.startswith('70-persistent-') and f != 'README': user_rules.append(f) if user_rules: report['CustomUdevRuleFiles'] = ' '.join(user_rules) package-hooks/openssh-client.py 0000644 00000002275 00000000000 0012534 0 ustar 00 '''apport hook for openssh-client (c) 2010 Canonical Ltd. Author: Chuck Short <chuck.short@canonical.com> 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. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' from apport.hookutils import ( attach_conffiles, attach_related_packages, command_output, ) def add_info(report, ui): response = ui.yesno("The contents of your /etc/ssh/ssh_config file " "may help developers diagnose your bug more " "quickly. However, it may contain sensitive " "information. Do you want to include it in your " "bug report?") if response == None: # user cancelled raise StopIteration elif response: attach_conffiles(report, 'openssh-client') attach_related_packages(report, ['ssh-askpass', 'libpam-ssh', 'keychain', 'ssh-askpass-gnome']) report['SSHClientVersion'] = command_output(['/usr/bin/ssh', '-V']) package-hooks/source_byobu.py 0000644 00000000523 00000000000 0012273 0 ustar 00 '''apport package hook for byobu (c) 2009 Canonical Ltd. Author: Dustin Kirkland <kirkland@byobu.org> ''' from apport.hookutils import * from os import path def add_info(report): attach_related_packages(report, ['byobu*', 'screen*']) report['Binaries'] = command_output(['sh', '-c', 'ls -alF /usr/bin/screen* /usr/bin/byobu*']) unkillable_shutdown 0000755 00000006646 00000000000 0010524 0 ustar 00 #!/usr/bin/python3 # # Collect information about processes which are still running after sending # SIGTERM to them (which happens during computer shutdown in # /etc/init.d/sendsigs in Debian/Ubuntu) # # Copyright (c) 2010 Canonical Ltd. # Author: Martin Pitt <martin.pitt@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import os, os.path, sys, errno, optparse import apport, apport.fileutils, apport.hookutils def parse_argv(): '''Parse command line and return (options, args).''' optparser = optparse.OptionParser('%prog [options]') optparser.add_option('-o', '--omit', metavar='PID', action='append', default=[], dest='blacklist', help='Ignore a particular process ID (can be specified multiple times)') (opts, args) = optparser.parse_args() if len(args) != 0: optparser.error('This program does not take any non-option arguments. Please see --help.') sys.exit(1) return (opts, args) def orphaned_processes(blacklist): '''Yield an iterator of running process IDs. This excludes PIDs which do not have a valid /proc/pid/exe symlink (e. g. kernel processes), the PID of our own process, and everything that is contained in the blacklist argument. ''' my_pid = os.getpid() my_sid = os.getsid(0) for d in os.listdir('/proc'): try: pid = int(d) except ValueError: continue if pid == 1 or pid == my_pid or d in blacklist: apport.warning('ignoring: %s', d) continue try: sid = os.getsid(pid) except OSError: # os.getsid() can fail with "No such process" if the process died # in the meantime continue if sid == my_sid: apport.warning('ignoring same sid: %s', d) continue try: os.readlink(os.path.join('/proc', d, 'exe')) except OSError as e: if e.errno == errno.ENOENT: # kernel thread or similar, silently ignore continue apport.warning('Could not read information about pid %s: %s', d, str(e)) continue yield d def do_report(pid, blacklist): '''Create a report for a particular PID.''' r = apport.Report('Bug') try: r.add_proc_info(pid) except (ValueError, AssertionError): # happens if ExecutablePath doesn't exist (any more?), ignore return r['Tags'] = 'shutdown-hang' r['Title'] = 'does not terminate at computer shutdown' if 'ExecutablePath' in r: r['Title'] = os.path.basename(r['ExecutablePath']) + ' ' + r['Title'] r['Processes'] = apport.hookutils.command_output(['ps', 'aux']) r['InitctlList'] = apport.hookutils.command_output(['initctl', 'list']) if blacklist: r['OmitPids'] = ' '.join(blacklist) try: with apport.fileutils.make_report_file(r) as f: r.write(f) except (IOError, OSError) as e: apport.fatal('Cannot create report: ' + str(e)) # # main # (opts, args) = parse_argv() for p in orphaned_processes(opts.blacklist): do_report(p, opts.blacklist) whoopsie-upload-all 0000755 00000017625 00000000000 0010333 0 ustar 00 #!/usr/bin/python3 # Process all pending crashes and mark them for whoopsie upload, but do not # upload them to any other crash database. Wait until whoopsie is done # uploading. # # Copyright (c) 2013 Canonical Ltd. # Author: Martin Pitt <martin.pitt@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import os import stat import sys import time import subprocess import argparse import fcntl import errno import logging import zlib import apport.fileutils import apport def process_report(report): '''Collect information for a report and mark for whoopsie upload errors.ubuntu.com does not collect any hook data anyway, so we do not need to bother collecting it. Return path of upload stamp if report was successfully processed, or None otherwise. ''' upload_stamp = '%s.upload' % report.rsplit('.', 1)[0] # whoopsie-upload-all is only called if autoreporting of crashes is set, # so its unlikely that systems with that set are interested in having the # .crash file around. Additionally, having .crash files left over causes # systemd's PathExistsGlob to be constantly triggered. See systemd issue # #16669 and LP: #1891657 for some details. uploaded_stamp = upload_stamp + 'ed' if os.path.exists(uploaded_stamp) and os.path.exists(report): report_st = os.stat(report) uploaded_st = os.stat(uploaded_stamp) # if the crash file is new delete the uploaded file if uploaded_st.st_mtime < report_st.st_mtime: os.unlink(uploaded_stamp) if os.path.exists(upload_stamp): upload_st = os.stat(upload_stamp) # also delete the upload file if upload_st.st_mtime < report_st.st_mtime: os.unlink(upload_stamp) if os.path.exists(upload_stamp): logging.info('%s already marked for upload, skipping', report) return upload_stamp report_stat = None r = apport.Report() # make sure we're actually on the hook to write this updated report # before we start doing expensive collection operations try: with open(report, 'rb') as f: try: fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: logging.info('%s already being processed, skipping', report) return None r.load(f, binary='compressed') report_stat = os.stat(report) except Exception as e: sys.stderr.write('ERROR: cannot load %s: %s\n' % (report, str(e))) return None if r.get('ProblemType', '') != 'Crash' and 'ExecutablePath' not in r: logging.info(' skipping, not a crash') return None if 'Dependencies' in r: logging.info('%s already has info collected', report) else: logging.info('Collecting info for %s...', report) r.add_os_info() try: r.add_package_info() except (SystemError, ValueError) as e: sys.stderr.write('ERROR: cannot add package info on %s: %s\n' % (report, str(e))) return None # add information from package specific hooks try: r.add_hooks_info(None) except Exception as e: sys.stderr.write('WARNING: hook failed for processing %s: %s\n' % (report, str(e))) try: r.add_gdb_info() except (IOError, EOFError, OSError, zlib.error) as e: # gzip.GzipFile.read can raise zlib.error. See LP bug #1947800 if hasattr(e, 'errno'): # calling add_gdb_info raises ENOENT if the crash's executable # is missing or gdb is not available but apport-retrace could # still process it if e.errno != errno.ENOENT: sys.stderr.write('ERROR: processing %s: %s\n' % (report, str(e))) if os.path.exists(report): os.unlink(report) return None # write updated report, we use os.open and os.fdopen as # /proc/sys/fs/protected_regular is set to 1 (LP: #1848064) # make sure the file isn't a FIFO or symlink try: fd = os.open(report, os.O_NOFOLLOW | os.O_WRONLY | os.O_APPEND | os.O_NONBLOCK) except FileNotFoundError: # The crash report was deleted. Nothing left to do. return None st = os.fstat(fd) if stat.S_ISREG(st.st_mode): with os.fdopen(fd, 'wb') as f: os.fchmod(fd, 0) r.write(f, only_new=True) os.fchmod(fd, 0o640) # now tell whoopsie to upload the report logging.info('Marking %s for whoopsie upload', report) apport.fileutils.mark_report_upload(report) assert os.path.exists(upload_stamp) os.chown(upload_stamp, report_stat.st_uid, report_stat.st_gid) return upload_stamp def collect_info(): '''Collect information for all reports Return set of all generated upload stamps. ''' if os.geteuid() != 0: sys.stderr.write('WARNING: Not running as root, cannot process reports' ' which are not owned by uid %i\n' % os.getuid()) stamps = set() reports = apport.fileutils.get_all_reports() for r in reports: res = process_report(r) if res: stamps.add(res) return stamps def wait_uploaded(stamps, timeout): '''Wait until all reports were uploaded. Times out after a given number of seconds. Return True if all reports were uploaded, False if there are some missing. ''' logging.info('Waiting for whoopsie to upload reports (timeout: %d s)', timeout) while timeout >= 0: # determine missing stamps missing = '' for stamp in stamps: uploaded = stamp + 'ed' if os.path.exists(stamp) and not os.path.exists(uploaded): missing += uploaded + ' ' if not missing: return True logging.info(' missing (remaining: %d s): %s', timeout, missing) time.sleep(10) timeout -= 10 return False def main() -> None: parser = argparse.ArgumentParser(description='Noninteractively upload all ' 'Apport crash reports to errors.ubuntu.com') parser.add_argument('-t', '--timeout', default=0, type=int, help='seconds to wait for whoopsie to upload the reports (default: do not wait)') parser.add_argument("--loglevel", default="info", help="log level (default: info)") opts = parser.parse_args() numeric_level = getattr(logging, opts.loglevel.upper(), None) if not isinstance(numeric_level, int): raise ValueError(f"Invalid log level: {opts.loglevel}") logging.basicConfig(level=numeric_level) # verify that whoopsie.path is enabled if subprocess.call(['/bin/systemctl', '--quiet', 'is-enabled', 'whoopsie.path'], stdout=subprocess.PIPE) != 0: sys.stderr.write('ERROR: whoopsie.path is not enabled\n') sys.exit(1) stamps = collect_info() # print('stamps:', stamps) if stamps: # Touch the directory that is monitored by whoopsie.path so that # whoopsie.service can activate. Ideally, this directory and the one # that apport-autoreport.path monitors would be different but they are # the same currently. os.utime(apport.fileutils.report_dir) if opts.timeout > 0: if not wait_uploaded(stamps, opts.timeout): sys.exit(2) logging.info('All reports uploaded successfully') else: logging.info('All reports processed') if __name__ == "__main__": main() recoverable_problem 0000755 00000004375 00000000000 0010455 0 ustar 00 #!/usr/bin/python3 '''Report an error that can be recovered from. This application should be called with its standard input pipe fed a nul-separated list of key-value pairs. ''' # Copyright (C) 2012 Canonical Ltd. # Author: Evan Dandrea <ev@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import apport.report import sys import os import argparse def main(): # Check parameters argparser = argparse.ArgumentParser('%(prog) [options]') argparser.add_argument('-p', '--pid', action='store', type=int, dest='optpid') args = argparser.parse_args() # Build the base report report = apport.report.Report('RecoverableProblem') # If we have a parameter pid, use that, otherwise look to our parent if args.optpid: report.pid = args.optpid else: report.pid = os.getppid() # Grab PID info right away, as we don't know how long it'll stick around try: report.add_proc_info(report.pid) except ValueError as e: # The process may have gone away before we could get to it. if str(e) == 'invalid process': return # Get the info on the bug items = sys.stdin.read().split('\0') if len(items) % 2 != 0: sys.stderr.write('Expect even number of fields in stdin, needs to have pairs of key and value.\n') sys.exit(1) while items: key = items.pop(0) if not items: break value = items.pop(0) report[key] = value # Put in the more general stuff report.add_os_info() report.add_user_info() ds = report.get('DuplicateSignature', '') exec_path = report.get('ExecutablePath', '') if exec_path and ds: report['DuplicateSignature'] = '%s:%s' % (exec_path, ds) # Write the final report try: with apport.fileutils.make_report_file(report) as f: report.write(f) except (IOError, OSError) as e: apport.fatal('Cannot create report: ' + str(e)) if __name__ == '__main__': main() iwlwifi_error_dump 0000755 00000003657 00000000000 0010356 0 ustar 00 #!/usr/bin/python3 # # Collect information about an iwlwifi firmware error dump. # # Copyright (c) 2014 Canonical Ltd. # Author: Seth Forshee <seth.forshee@canonical.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import os, sys, re import apport, apport.fileutils from apport.hookutils import command_output if len(sys.argv) != 2: sys.exit(1) phy = os.path.basename(sys.argv[1]) sysfs_path = '/sys/kernel/debug/ieee80211/' + phy + '/iwlwifi/iwlmvm/fw_error_dump' if not os.path.exists(sysfs_path): sys.exit(1) pr = apport.Report('KernelCrash') pr.add_package(apport.packaging.get_kernel_package()) pr['Title'] = 'iwlwifi firmware error' pr.add_os_info() # Get iwl firmware version and error code from dmesg dmesg = command_output(['dmesg']) regex = re.compile('^.*iwlwifi [0-9a-fA-F:]{10}\\.[0-9a-fA-F]: Loaded firmware version: ([0-9\\.]+).*\\n.*iwlwifi [0-9a-fA-F:]{10}\\.[0-9a-fA-F]: (0x[0-9A-F]{8} \\| [A-Z_]+)', re.MULTILINE) m = regex.findall(dmesg) if m: v = m[len(m) - 1] fw_version = v[0] error_code = v[1] pr['IwlFwVersion'] = fw_version pr['IwlErrorCode'] = error_code pr['DuplicateSignature'] = 'iwlwifi:' + fw_version + ':' + error_code pr['Title'] += ': ' + error_code # Get iwl firmware dump file from debugfs try: with open(sysfs_path, 'rb') as f: pr['IwlFwDump'] = f.read() # Firmware dump could contain sensitive information pr['LaunchpadPrivate'] = 'yes' pr['LaunchpadSubscribe'] = 'canonical-kernel-team' except IOError: pass try: with apport.fileutils.make_report_file(pr) as f: pr.write(f) except IOError as e: apport.fatal('Cannot create report: ' + str(e)) general-hooks/wayland_session.py 0000644 00000000341 00000000000 0013015 0 ustar 00 '''Detect if the current session is running under wayland''' import os def add_info(report, ui): if os.environ.get('WAYLAND_DISPLAY'): report.setdefault('Tags', '') report['Tags'] += ' wayland-session' general-hooks/powerpc.py 0000644 00000010763 00000000000 0011303 0 ustar 00 # This hook collects logs for Power systems and more specific logs for Pseries, # PowerNV platforms. # # Author: Thierry FAUCK <thierry@linux.vnet.ibm.com> # # 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., 675 Mass Ave, Cambridge, MA 02139, USA. import os, os.path, platform, tempfile, subprocess from apport.hookutils import command_output, attach_root_command_outputs, attach_file, attach_file_if_exists, command_available '''IBM Power System related information''' def add_tar(report, dir, key): (fd, f) = tempfile.mkstemp(prefix='apport.', suffix='.tar') os.close(fd) subprocess.call(['tar', 'chf', f, dir]) if os.path.getsize(f) > 0: report[key] = (f, ) # NB, don't cleanup the temp file, it'll get read later by the apport main # code def add_info(report, ui): arch = platform.machine() if arch not in ['ppc64', 'ppc64le']: return is_kernel = report['ProblemType'].startswith('Kernel') or 'linux' in report.get('Package', '') try: with open('/proc/cpuinfo', 'r') as fp: contents = fp.read() ispSeries = 'pSeries' in contents isPowerNV = 'PowerNV' in contents isPowerKVM = 'emulated by qemu' in contents except IOError: ispSeries = False isPowerNV = False isPowerKVM = False if ispSeries or isPowerNV: if is_kernel: add_tar(report, '/proc/device-tree/', 'DeviceTree.tar') attach_file(report, '/proc/misc', 'ProcMisc') attach_file(report, '/proc/locks', 'ProcLocks') attach_file(report, '/proc/loadavg', 'ProcLoadAvg') attach_file(report, '/proc/swaps', 'ProcSwaps') attach_file(report, '/proc/version', 'ProcVersion') report['cpu_smt'] = command_output(['ppc64_cpu', '--smt']) report['cpu_cores'] = command_output(['ppc64_cpu', '--cores-present']) report['cpu_coreson'] = command_output(['ppc64_cpu', '--cores-on']) # To be executed as root if is_kernel: attach_root_command_outputs(report, { 'cpu_runmode': 'ppc64_cpu --run-mode', 'cpu_freq': 'ppc64_cpu --frequency', 'cpu_dscr': 'ppc64_cpu --dscr', 'nvram': 'cat /dev/nvram', }) attach_file_if_exists(report, '/var/log/platform') if ispSeries and not isPowerKVM: attach_file(report, '/proc/ppc64/lparcfg', 'ProcLparCfg') attach_file(report, '/proc/ppc64/eeh', 'ProcEeh') attach_file(report, '/proc/ppc64/systemcfg', 'ProcSystemCfg') report['lscfg_vp'] = command_output(['lscfg', '-vp']) report['lsmcode'] = command_output(['lsmcode', '-A']) report['bootlist'] = command_output(['bootlist', '-m', 'both', '-r']) report['lparstat'] = command_output(['lparstat', '-i']) if command_available('lsvpd'): report['lsvpd'] = command_output(['lsvpd', '--debug']) if command_available('lsvio'): report['lsvio'] = command_output(['lsvio', '-des']) if command_available('servicelog'): report['servicelog_dump'] = command_output(['servicelog', '--dump']) if command_available('servicelog_notify'): report['servicelog_list'] = command_output(['servicelog_notify', '--list']) if command_available('usysattn'): report['usysattn'] = command_output(['usysattn']) if command_available('usysident'): report['usysident'] = command_output(['usysident']) if command_available('serv_config'): report['serv_config'] = command_output(['serv_config', '-l']) if isPowerNV: add_tar(report, '/proc/ppc64/', 'ProcPpc64.tar') attach_file_if_exists(report, '/sys/firmware/opal/msglog') if os.path.exists('/var/log/dump'): report['VarLogDump_list'] = command_output(['ls', '-l', '/var/log/dump']) if is_kernel: add_tar(report, '/var/log/opal-elog', 'OpalElog.tar') general-hooks/cloud-init.py 0000644 00000000536 00000000000 0011670 0 ustar 00 """General Apport hook for all reports that are using cloud-init.""" from cloudinit.apport import general_add_info def add_info(report, ui) -> None: """Entry point for Apport. Add a subset of non-sensitive cloud-init data from /run/cloud/instance-data.json that will be helpful for debugging. """ general_add_info(report, ui) general-hooks/generic.py 0000644 00000007720 00000000000 0011237 0 ustar 00 '''Attach generally useful information, not specific to any package.''' # Copyright (C) 2009 Canonical Ltd. # Authors: Matt Zimmerman <mdz@canonical.com> # Martin Pitt <martin.pitt@ubuntu.com> # Brian Murray <brian@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import os, re import apport.hookutils, apport.fileutils def add_info(report, ui): nm = apport.hookutils.nonfree_kernel_modules() if nm: report['NonfreeKernelModules'] = ' '.join(nm) # check for low space mounts = {'/': 'system', '/var': '/var', '/tmp': '/tmp'} home = os.getenv('HOME') if home: mounts[home] = 'home' treshold = 50 for mount in mounts: st = os.statvfs(mount) free_mb = st.f_bavail * st.f_frsize / 1000000 if free_mb < treshold: report['UnreportableReason'] = 'Your %s partition has less than \ %s MB of free space available, which leads to problems using applications \ and installing updates. Please free some space.' % (mounts[mount], free_mb) # important glib errors/assertions (which should not have private data) if 'ExecutablePath' in report: path = report['ExecutablePath'] gtk_like = (apport.fileutils.links_with_shared_library(path, 'libgtk') or apport.fileutils.links_with_shared_library(path, 'libgtk-3') or apport.fileutils.links_with_shared_library(path, 'libX11')) if gtk_like and apport.hookutils.in_session_of_problem(report): xsession_errors = apport.hookutils.xsession_errors() if xsession_errors: report['XsessionErrors'] = xsession_errors # using local libraries? if 'ProcMaps' in report: local_libs = set() for lib in re.finditer(r'\s(/[^ ]+\.so[.0-9]*)$', report['ProcMaps'], re.M): if not apport.fileutils.likely_packaged(lib.group(1)): local_libs.add(lib.group(1)) if ui and local_libs: if not ui.yesno('''The crashed program seems to use third-party or local libraries: %s It is highly recommended to check if the problem persists without those first. Do you want to continue the report process anyway? ''' % '\n'.join(local_libs)): raise StopIteration report['LocalLibraries'] = ' '.join(local_libs) report['Tags'] = (report.get('Tags', '') + ' local-libs').strip() # using third-party packages? if '[origin:' in report.get('Package', '') or '[origin:' in report.get('Dependencies', ''): report['Tags'] = (report.get('Tags', '') + ' third-party-packages').strip() # using ecryptfs? if os.path.exists(os.path.expanduser('~/.ecryptfs/wrapped-passphrase')): report['EcryptfsInUse'] = 'Yes' # filter out crashes on missing GLX (LP#327673) in_gl = '/usr/lib/libGL.so' in (report.get('StacktraceTop') or '\n').splitlines()[0] if in_gl and 'Loading extension GLX' not in apport.hookutils.read_file('/var/log/Xorg.0.log'): report['UnreportableReason'] = 'The X.org server does not support the GLX extension, which the crashed program expected to use.' # filter out package install failures due to a segfault if 'Segmentation fault' in report.get('ErrorMessage', '') \ and report['ProblemType'] == 'Package': report['UnreportableReason'] = 'The package installation resulted in a segmentation fault which is better reported as a crash report rather than a package install failure.' # log errors if report['ProblemType'] == 'Crash': apport.hookutils.attach_journal_errors(report) if __name__ == '__main__': r = {} add_info(r, None) for k in r: print('%s: %s' % (k, r[k])) general-hooks/ubuntu.py 0000644 00000062336 00000000000 0011151 0 ustar 00 '''Attach generally useful information, not specific to any package. Copyright (C) 2009 Canonical Ltd. Authors: Matt Zimmerman <mdz@canonical.com>, Brian Murray <brian@ubuntu.com> 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. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' import re, os, os.path, time, sys, subprocess import apport.packaging import apport.hookutils import problem_report from apport import unicode_gettext as _ from glob import glob if sys.version < '3': from urlparse import urljoin from urllib2 import urlopen (urljoin, urlopen) # pyflakes else: from urllib.parse import urljoin from urllib.request import urlopen def add_info(report, ui): add_release_info(report) add_kernel_info(report) add_cloud_info(report) add_proposed_info(report) # collect a condensed version of /proc/cpuinfo apport.hookutils.attach_file(report, '/proc/cpuinfo', 'ProcCpuinfo') short_cpuinfo = [] for item in reversed(report.get('ProcCpuinfo', '').split('\n')): short_cpuinfo.append(item) if item.startswith('processor\t:'): break short_cpuinfo = reversed(short_cpuinfo) report['ProcCpuinfoMinimal'] = '\n'.join(short_cpuinfo) report.pop('ProcCpuinfo') hook_errors = [k for k in report.keys() if k.startswith('HookError_')] if hook_errors: add_tag(report, 'apport-hook-error') # locally installed python versions can cause a multitude of errors if report.get('ProblemType') == 'Package' or \ 'python' in report.get('InterpreterPath', '') or \ 'python' in report.get('ExecutablePath', ''): for python in ('python', 'python3'): add_python_details('%sDetails' % python.title(), python, report) try: report['ApportVersion'] = apport.packaging.get_version('apport') except ValueError: # might happen on local installs pass # We want to know if people have modified apport's crashdb.conf in case # crashes are reported to Launchpad when they shouldn't be e.g. for a # non-development release. apport.hookutils.attach_conffiles(report, 'apport', ui=ui) # Should the system have been rebooted? apport.hookutils.attach_file_if_exists(report, '/var/run/reboot-required.pkgs', 'RebootRequiredPkgs') casper_md5check = 'casper-md5check.json' if 'LiveMediaBuild' in report: apport.hookutils.attach_casper_md5check(report, '/run/%s' % casper_md5check) else: apport.hookutils.attach_casper_md5check(report, '/var/log/installer/%s' % casper_md5check) if report.get('ProblemType') == 'Package': # every error report regarding a package should have package manager # version information apport.hookutils.attach_related_packages(report, ['dpkg', 'apt']) check_for_disk_error(report) # check to see if the real root on a persistent media is full if 'LiveMediaBuild' in report: st = os.statvfs('/cdrom') free_mb = st.f_bavail * st.f_frsize / 1000000 if free_mb < 10: report['UnreportableReason'] = 'Your system partition has less than \ %s MB of free space available, which leads to problems using applications \ and installing updates. Please free some space.' % (free_mb) match_error_messages(report) # these attachments will not exist if ProblemType is Bug as the package # hook runs after the general hook for attachment in ['DpkgTerminalLog', 'VarLogDistupgradeApttermlog']: if attachment in report: log_file = get_attachment_contents(report, attachment) untrimmed_dpkg_log = log_file check_attachment_for_errors(report, attachment) trimmed_log = get_attachment_contents(report, attachment) trimmed_log = trimmed_log.split('\n') lines = [] for line in untrimmed_dpkg_log.splitlines(): if line not in trimmed_log: lines.append(str(line)) elif line in trimmed_log: trimmed_log.remove(line) dpkg_log_without_error = '\n'.join(lines) # crash reports from live system installer often expose target mount for f in ('ExecutablePath', 'InterpreterPath'): if f in report and report[f].startswith('/target/'): report[f] = report[f][7:] # Allow filing update-manager bugs with obsolete packages if report.get('Package', '').startswith('update-manager'): os.environ['APPORT_IGNORE_OBSOLETE_PACKAGES'] = '1' # file bugs against OEM project for modified packages if 'Package' in report: v = report['Package'].split()[1] oem_project = get_oem_project(report) if oem_project and ('common' in v or oem_project in v): report['CrashDB'] = 'canonical-oem' if 'Package' in report: package = report['Package'].split()[0] if package: apport.hookutils.attach_conffiles(report, package, ui=ui) # do not file bugs against "upgrade-system" if it is not installed (LP#404727) if package == 'upgrade-system' and 'not installed' in report['Package']: report['UnreportableReason'] = 'You do not have the upgrade-system package installed. Please report package upgrade failures against the package that failed to install, or against upgrade-manager.' if 'Package' in report: package = report['Package'].split()[0] if package: apport.hookutils.attach_upstart_overrides(report, package) apport.hookutils.attach_upstart_logs(report, package) # build a duplicate signature tag for package reports if report.get('ProblemType') == 'Package': if 'DpkgTerminalLog' in report: # this was previously trimmed in check_attachment_for_errors termlog = report['DpkgTerminalLog'] elif 'VarLogDistupgradeApttermlog' in report: termlog = get_attachment_contents(report, 'VarLogDistupgradeApttermlog') else: termlog = None if termlog: (package, version) = report['Package'].split(None, 1) # for packages that run update-grub include /etc/default/grub UPDATE_BOOT = ['friendly-recovery', 'linux', 'memtest86+', 'plymouth', 'ubuntu-meta', 'virtualbox-ose'] ug_failure = r'/etc/kernel/post(inst|rm)\.d/zz-update-grub exited with return code [1-9]+' mkconfig_failure = r'/usr/sbin/grub-mkconfig.*/etc/default/grub: Syntax error' if re.search(ug_failure, termlog) or re.search(mkconfig_failure, termlog): if report['SourcePackage'] in UPDATE_BOOT: apport.hookutils.attach_default_grub(report, 'EtcDefaultGrub') dupe_sig = '' dupe_sig_created = False # messages we expect to see from a package manager (LP: #1692127) pkg_mngr_msgs = re.compile(r"""^(Authenticating| De-configuring| Examining| Installing| Preparing| Processing\ triggers| Purging| Removing| Replaced| Replacing| Setting\ up| Unpacking| Would remove).* \.\.\.\s*$""", re.X) for line in termlog.split('\n'): if pkg_mngr_msgs.search(line): dupe_sig = '%s\n' % line dupe_sig_created = True continue dupe_sig += '%s\n' % line # this doesn't catch 'dpkg-divert: error' LP: #1581399 if 'dpkg: error' in dupe_sig and line.startswith(' '): if 'trying to overwrite' in line: conflict_pkg = re.search('in package (.*) ', line) if conflict_pkg and not apport.packaging.is_distro_package(conflict_pkg.group(1)): report['UnreportableReason'] = _('An Ubuntu package has a file conflict with a package that is not a genuine Ubuntu package.') add_tag(report, 'package-conflict') if dupe_sig_created: # the duplicate signature should be the first failure report['DuplicateSignature'] = 'package:%s:%s\n%s' % (package, version, dupe_sig) break if dupe_sig: if dpkg_log_without_error.find(dupe_sig) != -1: report['UnreportableReason'] = _('You have already encountered this package installation failure.') def match_error_messages(report): # There are enough of these now that it is probably worth refactoring... # -mdz if report.get('ProblemType') == 'Package': if 'failed to install/upgrade: corrupted filesystem tarfile' in report.get('Title', ''): report['UnreportableReason'] = 'This failure was caused by a corrupted package download or file system corruption.' if 'is already installed and configured' in report.get('ErrorMessage', ''): report['SourcePackage'] = 'dpkg' def check_attachment_for_errors(report, attachment): if report.get('ProblemType') == 'Package': wrong_grub_msg = _('''Your system was initially configured with grub version 2, but you have removed it from your system in favor of grub 1 without configuring it. To ensure your bootloader configuration is updated whenever a new kernel is available, open a terminal and run: sudo apt-get install grub-pc ''') trim_dpkg_log(report) log_file = get_attachment_contents(report, attachment) if 'DpkgTerminalLog' in report \ and re.search(r'^Not creating /boot/grub/menu.lst as you wish', report['DpkgTerminalLog'], re.MULTILINE): grub_hook_failure = True else: grub_hook_failure = False if report['Package'] not in ['grub', 'grub2']: # linux-image postinst emits this when update-grub fails # https://wiki.ubuntu.com/KernelTeam/DebuggingUpdateErrors grub_errors = [r'^User postinst hook script \[.*update-grub\] exited with value', r'^run-parts: /etc/kernel/post(inst|rm).d/zz-update-grub exited with return code [1-9]+', r'^/usr/sbin/grub-probe: error'] for grub_error in grub_errors: if attachment in report and re.search(grub_error, log_file, re.MULTILINE): # File these reports on the grub package instead grub_package = apport.packaging.get_file_package('/usr/sbin/update-grub') if grub_package is None or grub_package == 'grub' and 'grub-probe' not in log_file: report['SourcePackage'] = 'grub' if os.path.exists('/boot/grub/grub.cfg') and grub_hook_failure: report['UnreportableReason'] = wrong_grub_msg else: report['SourcePackage'] = 'grub2' if report['Package'] != 'initramfs-tools': # update-initramfs emits this when it fails, usually invoked from the linux-image postinst # https://wiki.ubuntu.com/KernelTeam/DebuggingUpdateErrors if attachment in report and re.search(r'^update-initramfs: failed for ', log_file, re.MULTILINE): # File these reports on the initramfs-tools package instead report['SourcePackage'] = 'initramfs-tools' if report['Package'].startswith('linux-image-') and attachment in report: # /etc/kernel/*.d failures from kernel package postinst m = re.search(r'^run-parts: (/etc/kernel/\S+\.d/\S+) exited with return code \d+', log_file, re.MULTILINE) if m: path = m.group(1) package = apport.packaging.get_file_package(path) if package: report['SourcePackage'] = package report['ErrorMessage'] = m.group(0) if package == 'grub-pc' and grub_hook_failure: report['UnreportableReason'] = wrong_grub_msg else: report['UnreportableReason'] = 'This failure was caused by a program which did not originate from Ubuntu' error_message = report.get('ErrorMessage') corrupt_package = 'This failure was caused by a corrupted package download or file system corruption.' out_of_memory = 'This failure was caused by the system running out of memory.' if 'failed to install/upgrade: corrupted filesystem tarfile' in report.get('Title', ''): report['UnreportableReason'] = corrupt_package if 'dependency problems - leaving unconfigured' in error_message: report['UnreportableReason'] = 'This failure is a followup error from a previous package install failure.' if 'failed to allocate memory' in error_message: report['UnreportableReason'] = out_of_memory if 'cannot access archive' in error_message: report['UnreportableReason'] = corrupt_package if re.search(r'(failed to read|failed in write|short read) on buffer copy', error_message): report['UnreportableReason'] = corrupt_package if re.search(r'(failed to read|failed to write|failed to seek|unexpected end of file or stream)', error_message): report['UnreportableReason'] = corrupt_package if re.search(r'(--fsys-tarfile|dpkg-deb --control) returned error exit status 2', error_message): report['UnreportableReason'] = corrupt_package if attachment in report and re.search(r'dpkg-deb: error.*is not a debian format archive', log_file, re.MULTILINE): report['UnreportableReason'] = corrupt_package if 'is already installed and configured' in report.get('ErrorMessage', ''): # there is insufficient information in the data currently gathered # so gather more data report['SourcePackage'] = 'dpkg' report['AptdaemonVersion'] = apport.packaging.get_version('aptdaemon') apport.hookutils.attach_file_if_exists(report, '/var/log/dpkg.log', 'DpkgLog') apport.hookutils.attach_file_if_exists(report, '/var/log/apt/term.log', 'AptTermLog') # gather filenames in /var/crash to see if there is one for dpkg reports = glob('/var/crash/*') if reports: report['CrashReports'] = apport.hookutils.command_output( ['stat', '-c', '%a:%u:%g:%s:%y:%x:%n'] + reports) add_tag(report, 'already-installed') def check_for_disk_error(report): devs_to_check = [] if 'Dmesg.txt' not in report and 'CurrentDmesg.txt' not in report: return if 'Df.txt' not in report: return df = report['Df.txt'] device_error = False for line in df: line = line.strip('\n') if line.endswith('/') or line.endswith('/usr') or line.endswith('/var'): # without manipulation it'd look like /dev/sda1 device = line.split(' ')[0].strip('0123456789') device = device.replace('/dev/', '') devs_to_check.append(device) dmesg = report.get('CurrentDmesg.txt', report['Dmesg.txt']) for line in dmesg: line = line.strip('\n') if 'I/O error' in line: # no device in this line if 'journal commit I/O error' in line: continue for dev in devs_to_check: if re.search(dev, line): error_device = dev device_error = True break if device_error: report['UnreportableReason'] = 'This failure was caused by a hardware error on /dev/%s' % error_device def add_kernel_info(report): # This includes the Ubuntu packaged kernel version apport.hookutils.attach_file_if_exists(report, '/proc/version_signature', 'ProcVersionSignature') def add_release_info(report): # https://bugs.launchpad.net/bugs/364649 media = '/var/log/installer/media-info' apport.hookutils.attach_file_if_exists(report, media, 'InstallationMedia') # Preinstalled Raspberry Pi images include a build date breadcrumb apport.hookutils.attach_file_if_exists(report, '/.disk/info', 'ImageMediaBuild') if 'ImageMediaBuild' in report: add_tag(report, '%s-image' % report['Architecture']) try: with open('/proc/device-tree/compatible', 'rb') as f: is_a_pi = any(vendor == 'raspberrypi' for s in f.read().split(b'\0') if s for vendor, model in (s.decode('ascii').split(',', 1),)) except FileNotFoundError: is_a_pi = False if is_a_pi: add_tag(report, 'raspi-image') # if we are running from a live system, add the build timestamp apport.hookutils.attach_file_if_exists( report, '/cdrom/.disk/info', 'LiveMediaBuild') if os.path.exists('/cdrom/.disk/info'): report['CasperVersion'] = apport.packaging.get_version('casper') # https://wiki.ubuntu.com/FoundationsTeam/Specs/OemTrackingId apport.hookutils.attach_file_if_exists( report, '/var/lib/ubuntu_dist_channel', 'DistributionChannelDescriptor') release_codename = apport.hookutils.command_output(['lsb_release', '-sc'], stderr=None) if release_codename.startswith('Error'): release_codename = None else: add_tag(report, release_codename) if os.path.exists(media): mtime = os.stat(media).st_mtime human_mtime = time.strftime('%Y-%m-%d', time.gmtime(mtime)) delta = time.time() - mtime report['InstallationDate'] = 'Installed on %s (%d days ago)' % (human_mtime, delta / 86400) log = '/var/log/dist-upgrade/main.log' if os.path.exists(log): mtime = os.stat(log).st_mtime human_mtime = time.strftime('%Y-%m-%d', time.gmtime(mtime)) delta = time.time() - mtime # Would be nice if this also showed which release was originally installed report['UpgradeStatus'] = 'Upgraded to %s on %s (%d days ago)' % (release_codename, human_mtime, delta / 86400) else: report['UpgradeStatus'] = 'No upgrade log present (probably fresh install)' def add_proposed_info(report): '''Tag if package comes from -proposed''' if 'Package' not in report: return try: (package, version) = report['Package'].split()[:2] except ValueError: print('WARNING: malformed Package field: ' + report['Package']) return apt_cache = subprocess.Popen(['apt-cache', 'showpkg', package], stdout=subprocess.PIPE, universal_newlines=True) out = apt_cache.communicate()[0] if apt_cache.returncode != 0: print('WARNING: apt-cache showpkg %s failed' % package) return found_proposed = False found_updates = False found_security = False for line in out.splitlines(): if line.startswith(version + ' ('): if '-proposed_' in line: found_proposed = True if '-updates_' in line: found_updates = True if '-security' in line: found_security = True if found_proposed and not found_updates and not found_security: add_tag(report, 'package-from-proposed') def add_cloud_info(report): # EC2 and Ubuntu Enterprise Cloud instances ec2_instance = False for pkg in ('ec2-init', 'cloud-init'): try: if apport.packaging.get_version(pkg): ec2_instance = True break except ValueError: pass if ec2_instance: metadata_url = 'http://169.254.169.254/latest/meta-data/' ami_id_url = urljoin(metadata_url, 'ami-id') try: ami = urlopen(ami_id_url, timeout=5).read() except Exception: ami = None if ami and ami.startswith(b'ami'): add_tag(report, 'ec2-images') fields = {'Ec2AMIManifest': 'ami-manifest-path', 'Ec2Kernel': 'kernel-id', 'Ec2Ramdisk': 'ramdisk-id', 'Ec2InstanceType': 'instance-type', 'Ec2AvailabilityZone': 'placement/availability-zone'} report['Ec2AMI'] = ami for key, value in fields.items(): try: report[key] = urlopen(urljoin(metadata_url, value), timeout=5).read() except Exception: report[key] = 'unavailable' else: add_tag(report, 'uec-images') def add_tag(report, tag): report.setdefault('Tags', '') if tag in report['Tags'].split(): return report['Tags'] += ' ' + tag def get_oem_project(report): '''Determine OEM project name from Distribution Channel Descriptor Return None if it cannot be determined or does not exist. ''' dcd = report.get('DistributionChannelDescriptor', None) if dcd and dcd.startswith('canonical-oem-'): return dcd.split('-')[2] return None def trim_dpkg_log(report): '''Trim DpkgTerminalLog to the most recent installation session.''' if 'DpkgTerminalLog' not in report: return if not report['DpkgTerminalLog'].strip(): report['UnreportableReason'] = '/var/log/apt/term.log does not contain any data' return lines = [] dpkg_log = report['DpkgTerminalLog'] if isinstance(dpkg_log, bytes): trim_re = re.compile(b'^\\(.* ... \\d+ .*\\)$') start_re = re.compile(b'^Log started:') else: trim_re = re.compile('^\\(.* ... \\d+ .*\\)$') start_re = re.compile('^Log started:') for line in dpkg_log.splitlines(): if start_re.match(line) or trim_re.match(line): lines = [] continue lines.append(line) # If trimming the log file fails, return the whole log file. if not lines: return if isinstance(lines[0], str): report['DpkgTerminalLog'] = '\n'.join(lines) else: report['DpkgTerminalLog'] = '\n'.join([str(line.decode('UTF-8', 'replace')) for line in lines]) def get_attachment_contents(report, attachment): if isinstance(report[attachment], problem_report.CompressedValue): contents = report[attachment].get_value().decode('UTF-8') else: contents = report[attachment] return contents def add_python_details(key, python, report): '''Add comma separated details about which python is being used''' python_path = apport.hookutils.command_output(['which', python]) if python_path.startswith('Error: '): report[key] = 'N/A' return python_link = apport.hookutils.command_output(['readlink', '-f', python_path]) python_pkg = apport.fileutils.find_file_package(python_path) if python_pkg: python_pkg_version = apport.packaging.get_version(python_pkg) python_version = apport.hookutils.command_output([python_link, '--version']) data = '%s, %s' % (python_link, python_version) if python_pkg: data += ', %s, %s' % (python_pkg, python_pkg_version) else: data += ', unpackaged' report[key] = data if __name__ == '__main__': import sys # for testing: update report file given on command line if len(sys.argv) != 2: sys.stderr.write('Usage for testing this hook: %s <report file>\n' % sys.argv[0]) sys.exit(1) report_file = sys.argv[1] report = apport.Report() with open(report_file, 'rb') as f: report.load(f) report_keys = set(report.keys()) new_report = report.copy() add_info(new_report, None) new_report_keys = set(new_report.keys()) # Show differences # N.B. Some differences will exist if the report file is not from your # system because the hook runs against your local system. changed = 0 for key in sorted(report_keys | new_report_keys): if key in new_report_keys and key not in report_keys: print('+%s: %s' % (key, new_report[key])) changed += 1 elif key in report_keys and key not in new_report_keys: print('-%s: (deleted)' % key) changed += 1 elif key in report_keys and key in new_report_keys: if report[key] != new_report[key]: print('~%s: (changed)' % key) changed += 1 print('%d items changed' % changed) general-hooks/ubuntu-gnome.py 0000644 00000004374 00000000000 0012252 0 ustar 00 '''Bugs and crashes for the Ubuntu GNOME flavour. Copyright (C) 2013 Canonical Ltd. Author: Martin Pitt <martin.pitt@ubuntu.com> 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. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' def add_info(report, ui): release = report.get('DistroRelease', '') msg = 'The GNOME3 PPA you are using is no longer supported for this Ubuntu release. Please ' # redirect reports against PPA packages to ubuntu-gnome project if '[origin: LP-PPA-gnome3-team-gnome3' in report.get('Package', ''): report['CrashDB'] = '''{ "impl": "launchpad", "project": "ubuntu-gnome", "bug_pattern_url": "http://people.canonical.com/~ubuntu-archive/bugpatterns/bugpatterns.xml", "dupdb_url": "http://phillw.net/ubuntu-gnome/apport_duplicates/", }''' # using the staging PPA? if 'LP-PPA-gnome3-team-gnome3-staging' in report.get('Package', ''): report.setdefault('Tags', '') report['Tags'] += ' gnome3-staging' if release in ('Ubuntu 14.04', 'Ubuntu 16.04'): report['UnreportableReason'] = '%s run "ppa-purge ppa:gnome3-team/gnome3-staging".' % msg # using the next PPA? elif 'LP-PPA-gnome3-team-gnome3-next' in report.get('Package', ''): report.setdefault('Tags', '') report['Tags'] += ' gnome3-next' if release in ('Ubuntu 14.04', 'Ubuntu 16.04'): report['UnreportableReason'] = '%s run "ppa-purge ppa:gnome3-team/gnome3-next".' % msg else: if release in ('Ubuntu 14.04', 'Ubuntu 16.04'): report['UnreportableReason'] = '%s run "ppa-purge ppa:gnome3-team/gnome3".' % msg if '[origin: LP-PPA-gnome3-team-gnome3' in report.get('Dependencies', ''): report.setdefault('Tags', '') report['Tags'] += ' gnome3-ppa' if release in ('Ubuntu 14.04', 'Ubuntu 16.04') and 'UnreportableReason' not in report: report['UnreportableReason'] = '%s use ppa-purge to remove the PPA.' % msg general-hooks/cloud_archive.py 0000644 00000002207 00000000000 0012425 0 ustar 00 ''' Redirect reports on packages from the Ubuntu Cloud Archive to the launchpad cloud-archive project. Copyright (C) 2013 Canonical Ltd. Author: James Page <james.page@ubuntu.com> 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. See http://www.gnu.org/copyleft/gpl.html for the full text of the license. ''' from apport import packaging def add_info(report, ui): package = report.get('Package') if not package: return package = package.split()[0] try: if '~cloud' in packaging.get_version(package) and \ packaging.get_package_origin(package) == 'Canonical': report['CrashDB'] = '''{ "impl": "launchpad", "project": "cloud-archive", "bug_pattern_url": "http://people.canonical.com/~ubuntu-archive/bugpatterns/bugpatterns.xml", }''' except ValueError as e: if 'does not exist' in str(e): return else: raise e general-hooks/parse_segv.py 0000644 00000034502 00000000000 0011757 0 ustar 00 #!/usr/bin/python3 # Examine the crash files saved by apport to attempt to determine the cause # of a segfault. Currently very very simplistic, and only finds commonly # understood situations for x86/x86_64. # # Copyright 2009-2010 Canonical, Ltd. # Author: Kees Cook <kees@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import sys, re, logging, io class ParseSegv(object): def __init__(self, registers, disassembly, maps, debug=False): if debug: if sys.version > '3': logging.basicConfig(level=logging.DEBUG, stream=io.TextIOWrapper(sys.stderr, encoding='UTF-8')) else: logging.basicConfig(level=logging.DEBUG, stream=sys.stderr) self.regs = self.parse_regs(registers) self.sp = None for reg in ['rsp', 'esp']: if reg in self.regs: self.sp = self.regs[reg] self.line, self.pc, self.insn, self.src, self.dest = \ self.parse_disassembly(disassembly) self.stack_vma = None self.maps = self.parse_maps(maps) def find_vma(self, addr): for vma in self.maps: if addr >= vma['start'] and addr < vma['end']: return vma return None def parse_maps(self, maps_str): maps = [] for line in maps_str.splitlines(): items = line.strip().split() try: span, perms, bits, dev = items[0:4] except Exception: raise ValueError('Cannot parse maps line: %s' % (line.strip())) if len(items) == 5: name = None else: name = items[5] start, end = [int(x, 16) for x in span.split('-')] if name == '[stack]': self.stack_vma = len(maps) maps.append({'start': start, 'end': end, 'perms': perms, 'name': name}) logging.debug('start: %s, end: %s, perms: %s, name: %s', start, end, perms, name) return maps def parse_regs(self, reg_str): regs = dict() for line in reg_str.splitlines(): reg, hexvalue = line.split()[0:2] regs[reg] = int(hexvalue, 16) logging.debug('%s:0x%08x', reg, regs[reg]) return regs def parse_disassembly(self, disassembly): if not self.regs: raise ValueError('Registers not loaded yet!?') lines = disassembly.splitlines() # Throw away possible 'Dump' gdb report line if len(lines) > 0 and lines[0].startswith('Dump'): lines.pop(0) if len(lines) < 1: raise ValueError('Failed to load empty disassembly') line = lines[0].strip() # Drop GDB 7.1's leading $pc mark if line.startswith('=>'): line = line[2:].strip() logging.debug(line) pc_str = line.split()[0] if pc_str.startswith('0x'): pc = int(pc_str.split(':')[0], 16) else: # Could not identify this instruction line raise ValueError('Could not parse PC "%s" from disassembly line: %s' % (pc_str, line)) logging.debug('pc: 0x%08x', pc) full_insn_str = line.split(':', 1)[1].strip() # Handle invalid memory if 'Cannot access memory at address' in full_insn_str or (full_insn_str == '' and len(lines) == 1): return line, pc, None, None, None # Handle wrapped lines if full_insn_str == '' and lines[1].startswith(' '): line = line + ' ' + lines[1].strip() full_insn_str = line.split(':', 1)[1].strip() insn_parts = full_insn_str.split() # Drop call target names "call 0xb7a805af <_Unwind_Find_FDE@plt+111>" if insn_parts[-1].endswith('>') and insn_parts[-1].startswith('<'): insn_parts.pop(-1) # Attempt to find arguments args_str = '' if len(insn_parts) > 1: args_str = insn_parts.pop(-1) # Assume remainder is the insn itself insn = ' '.join(insn_parts) logging.debug('insn: %s', insn) args = [] src = None dest = None if args_str == '': # Could not find insn args args = None else: logging.debug('args: "%s"', args_str) for m in re.finditer(r'([^,\(]*(\(:?[^\)]+\))*)', args_str): if len(m.group(0)): args.append(m.group(0)) if len(args) > 0: src = args[0] logging.debug('src: %s', src) if len(args) > 1: dest = args[1] logging.debug('dest: %s', dest) # Set up possible implicit memory destinations (stack actions) if insn in ['push', 'pop', 'pushl', 'popl', 'call', 'callq', 'ret', 'retq']: for reg in ['rsp', 'esp']: if reg in self.regs: dest = '(%%%s)' % (reg) break return line, pc, insn, src, dest def validate_vma(self, perm, addr, name): perm_name = {'x': ['executable', 'executing'], 'r': ['readable', 'reading'], 'w': ['writable', 'writing']} vma = self.find_vma(addr) if vma is None: alarmist = 'unknown' if addr < 65536: alarmist = 'NULL' return False, '%s (0x%08x) not located in a known VMA region (needed %s region)!' % (name, addr, perm_name[perm][0]), '%s %s VMA' % (perm_name[perm][1], alarmist) elif perm not in vma['perms']: alarmist = '' if perm == 'x': if 'w' in vma['perms']: alarmist = 'writable ' else: alarmist = 'non-writable ' short = '%s %sVMA %s' % (perm_name[perm][1], alarmist, vma['name']) return False, '%s (0x%08x) in non-%s VMA region: 0x%08x-0x%08x %s %s' % (name, addr, perm_name[perm][0], vma['start'], vma['end'], vma['perms'], vma['name']), short else: return True, '%s (0x%08x) ok' % (name, addr), '%s ok' % (perm_name[perm][1]) def register_value(self, reg): reg_orig = reg # print reg mask = 0 if reg.startswith('%'): # print('%s -> %s' % (reg, reg[1:])) reg = reg[1:] if reg in self.regs: # print('got %s (%d & %d == %d)' % (reg, self.regs[reg], mask, self.regs[reg] & ~mask)) return self.regs[reg] if len(reg) == 2 and reg.endswith('l'): mask |= 0xff00 # print('%s -> %sx' % (reg, reg[0])) reg = '%sx' % reg[0] if reg in self.regs: # print('got %s (%d & %d == %d)' % (reg, self.regs[reg], mask, self.regs[reg] & ~mask)) return self.regs[reg] & ~mask if len(reg) == 2 and reg.endswith('x'): mask |= 0xffff0000 # print('%s -> e%s' % (reg, reg)) reg = 'e%s' % reg if reg in self.regs: # print('got %s (%d & %d == %d)' % (reg, self.regs[reg], mask, self.regs[reg] & ~mask)) return self.regs[reg] & ~mask if len(reg) == 3 and reg.startswith('e'): mask |= 0xffffffff00000000 # print('%s -> r%s' % (reg, reg[1:])) reg = 'r%s' % reg[1:] if reg in self.regs: # print('got %s (%d & %d == %d)' % (reg, self.regs[reg], mask, self.regs[reg] & ~mask)) return self.regs[reg] & ~mask raise ValueError("Could not resolve register '%s'" % (reg_orig)) def calculate_arg(self, arg): # Check for and pre-remove segment offset segment = 0 if arg.startswith('%') and ':' in arg: parts = arg.split(':', 1) segment = self.regs[parts[0][1:]] arg = parts[1] # Handle standard offsets parts = arg.split('(') offset = parts[0] # Handle negative signs sign = 1 if offset.startswith('-'): sign = -1 offset = offset[1:] # Skip call target dereferences if offset.startswith('*'): offset = offset[1:] if len(offset) > 0: if offset.startswith('%'): # Handle the *%REG case add = self.regs[offset[1:]] else: if not offset.startswith('0x'): raise ValueError('Unknown offset literal: %s' % (parts[0])) add = int(offset[2:], 16) * sign else: add = 0 def _reg_val(self, text, val=0): if text.startswith('%'): val = self.regs[text[1:]] elif text == "": val = 0 else: val = int(text) return val # (%ebx, %ecx, 4) style value = 0 if len(parts) > 1: parens = parts[1][0:-1] reg_list = parens.split(',') base = 0 if len(reg_list) > 0: base = _reg_val(self, reg_list[0], base) index = 0 if len(reg_list) > 1: index = _reg_val(self, reg_list[1], index) scale = 1 if len(reg_list) > 2: scale = _reg_val(self, reg_list[2], scale) value = base + index * scale value = segment + value + add if 'esp' in self.regs: # 32bit return value % 0x100000000 else: # 64bit return value % 0x10000000000000000 def report(self): understood = False reason = [] details = ['Segfault happened at: %s' % (self.line)] # Verify PC is in an executable region valid, out, short = self.validate_vma('x', self.pc, 'PC') details.append(out) if not valid: reason.append(short) understood = True if self.insn in ['lea', 'leal']: # Short-circuit for instructions that do not cause vma access details.append('insn (%s) does not access VMA' % (self.insn)) else: # Verify source is readable if self.src: if ':' not in self.src and (self.src[0] in ['%', '$', '*']) and not self.src.startswith('*%'): details.append('source "%s" ok' % (self.src)) else: addr = self.calculate_arg(self.src) valid, out, short = self.validate_vma('r', addr, 'source "%s"' % (self.src)) details.append(out) if not valid: reason.append(short) understood = True # Verify destination is writable if self.dest: if ':' not in self.dest and (self.dest[0] in ['%', '$', '*']): details.append('destination "%s" ok' % (self.dest)) else: addr = self.calculate_arg(self.dest) valid, out, short = self.validate_vma('w', addr, 'destination "%s"' % (self.dest)) details.append(out) if not valid: reason.append(short) understood = True # Handle I/O port operations if self.insn in ['out', 'in'] and not understood: reason.append('disallowed I/O port operation on port %d' % (self.register_value(self.src))) details.append('disallowed I/O port operation on port %d' % (self.register_value(self.src))) understood = True # Note position of SP with regard to "[stack]" VMA if self.sp is not None: if self.stack_vma is not None: if self.sp < self.maps[self.stack_vma]['start']: details.append("Stack memory exhausted (SP below stack segment)") if self.sp >= self.maps[self.stack_vma]['end']: details.append("Stack pointer not within stack segment") if not understood: valid, out, short = self.validate_vma('r', self.sp, 'SP') details.append(out) if not valid: reason.append(short) understood = True if not understood: vma = self.find_vma(self.pc) if vma and (vma['name'] == '[vdso]' or vma['name'] == '[vsyscall]'): reason.append('Reason could not be automatically determined. (Unhandled exception in kernel code?)') details.append('Reason could not be automatically determined. (Unhandled exception in kernel code?)') else: reason.append('Reason could not be automatically determined.') details.append('Reason could not be automatically determined.') return understood, '\n'.join(reason), '\n'.join(details) def add_info(report): # Only interested in segmentation faults... if report.get('Signal', '0') != '11': return needed = ['Signal', 'Architecture', 'Disassembly', 'ProcMaps', 'Registers'] for field in needed: if field not in report: report['SegvAnalysis'] = 'Skipped: missing required field "%s"' % (field) return # Only run on segv for x86 and x86_64... if not report['Architecture'] in ['i386', 'amd64']: return try: segv = ParseSegv(report['Registers'], report['Disassembly'], report['ProcMaps']) understood, reason, details = segv.report() if understood: report['SegvReason'] = reason report['SegvAnalysis'] = details except BaseException as e: report['SegvAnalysis'] = 'Failure: %s' % (str(e)) if __name__ == '__main__': if len(sys.argv) != 4 or sys.argv[1] in ['-h', '--help']: print('To run self-test, run without any arguments (or with -v)') print('To do stand-alone crash parsing:') print(' Usage: %s Registers.txt Disassembly.txt ProcMaps.txt' % (sys.argv[0])) sys.exit(0) segv = ParseSegv(open(sys.argv[1]).read(), open(sys.argv[2]).read(), open(sys.argv[3]).read()) understood, reason, details = segv.report() print('%s\n\n%s' % (reason, details)) rc = 0 if not understood: rc = 1 sys.exit(rc) package_hook 0000755 00000004255 00000000000 0007054 0 ustar 00 #!/usr/bin/python3 # # Collect information about a package installation/upgrade failure. # # Copyright (c) 2007 - 2009 Canonical Ltd. # Author: Martin Pitt <martin.pitt@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import sys, optparse, os.path, os import apport, apport.fileutils def mkattrname(path): '''Convert a file path to a problem report attribute name.''' name = '' for dir in path.split(os.sep): if dir: name += ''.join([c for c in dir[0].upper() + dir[1:] if c.isalnum()]) return name # # main # # parse command line arguments optparser = optparse.OptionParser('%prog [options]') optparser.add_option('-p', '--package', help='Specify the package name which failed to upgrade (mandatory)') optparser.add_option('-l', '--log', action='append', dest='logs', help='Append given log file, or, if it is a directory, all files in it (can be specified multiple times)') optparser.add_option('-t', '--tags', help='Add the following tags to the bug report (comma separated)') options = optparser.parse_args()[0] if not options.package: apport.fatal('You need to specify a package name with --package') sys.exit(1) # create report pr = apport.Report('Package') pr.add_package(options.package) pr['SourcePackage'] = apport.packaging.get_source(options.package) pr['ErrorMessage'] = (sys.stdin, False) if options.tags: tags = options.tags.replace(',', '') pr['Tags'] = tags for line in (options.logs or []): if os.path.isfile(line): pr[mkattrname(line)] = (line,) elif os.path.isdir(line): for f in os.listdir(line): path = os.path.join(line, f) if os.path.isfile(path): pr[mkattrname(path)] = (path,) # write report try: with apport.fileutils.make_report_file(pr) as f: pr.write(f) except (IOError, OSError) as e: apport.fatal('Cannot create report: ' + str(e)) java_uncaught_exception 0000755 00000005126 00000000000 0011334 0 ustar 00 #!/usr/bin/python3 '''Receive details from ApportUncaughtExceptionHandler. This generates and saves a problem report. ''' # Copyright 2010 Canonical Ltd. # Author: Matt Zimmerman <mdz@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import sys if sys.version_info.major < 3: from urlparse import urlparse urlparse # pyflakes else: from urllib.parse import urlparse def make_title(report): lines = report['StackTrace'].split('\n') message = lines[0].strip() stackframe = lines[1].strip() return '%s in %s' % (message, stackframe) def main(): from apport.packaging_impl import impl as packaging if not packaging.enabled(): return -1 # read from the JVM process a sequence of key, value delimited by null # bytes items = sys.stdin.read().split('\0') d = dict() while items: key = items.pop(0) if not items: break value = items.pop(0) d[key] = value # create report import apport.report import os report = apport.report.Report(type='Crash') # assume our parent is the JVM process report.pid = os.getppid() report.add_os_info() report.add_proc_info() # these aren't relevant because the crash was in bytecode del report['ProcMaps'] del report['ProcStatus'] report.add_user_info() # add in data which was fed to us from the JVM process for key, value in d.items(): report[key] = value # Add an ExecutablePath pointing to the file where the main class resides if 'MainClassUrl' in report: url = report['MainClassUrl'] scheme, netloc, path, params, query, fragment = urlparse(url) if scheme == 'jar': # path is then a URL to the jar file scheme, netloc, path, params, query, fragment = urlparse(path) if '!/' in path: path = path.split('!/', 1)[0] if scheme == 'file': report['ExecutablePath'] = path else: # Program at some non-file URL crashed. Give up. return report['Title'] = make_title(report) try: with apport.fileutils.make_report_file(report) as f: report.write(f) except (IOError, OSError) as e: apport.fatal('Cannot create report: ' + str(e)) if __name__ == '__main__': main() dump_acpi_tables.py 0000755 00000003130 00000000000 0010352 0 ustar 00 #!/usr/bin/python3 import os, sys, stat def dump_acpi_table(filename, tablename, out): '''Dump a single ACPI table''' if not os.access(filename, os.R_OK): return out.write('%s @ 0x0000000000000000\n' % tablename[0:4]) n = 0 f = open(filename, 'rb') hex_str = '' try: byte = f.read(1) while byte != b'': val = ord(byte) if (n & 15) == 0: if (n > 65535): hex_str = ' %4.4X: ' % n else: hex_str = ' %4.4X: ' % n ascii_str = '' hex_str = hex_str + '%2.2X ' % val if (val < 32) or (val > 126): ascii_str = ascii_str + '.' else: ascii_str = ascii_str + chr(val) n = n + 1 if (n & 15) == 0: out.write('%s %s\n' % (hex_str, ascii_str)) byte = f.read(1) finally: if (n % 16) != 0: for i in range(n & 15, 16): hex_str = hex_str + ' ' out.write('%s %s\n' % (hex_str, ascii_str)) f.close() out.write('\n') def dump_acpi_tables(path, out): '''Dump ACPI tables''' tables = os.listdir(path) for tablename in tables: pathname = os.path.join(path, tablename) mode = os.stat(pathname).st_mode if stat.S_ISDIR(mode): dump_acpi_tables(pathname, out) else: dump_acpi_table(pathname, tablename, out) if os.path.isdir('/sys/firmware/acpi/tables'): dump_acpi_tables('/sys/firmware/acpi/tables', sys.stdout) apport-checkreports 0000755 00000002365 00000000000 0010440 0 ustar 00 #!/usr/bin/python3 # Check if there are new reports for the invoking user. Exit with 0 if new # reports are available, or with 1 if not. # # Copyright (c) 2006 Canonical Ltd. # Author: Martin Pitt <martin.pitt@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import sys, optparse from apport.fileutils import get_new_reports, get_new_system_reports import apport # parse command line options optparser = optparse.OptionParser('%prog [options]') optparser.add_option('-s', '--system', default=False, action='store_true', help='Check for crash reports from system users.') options, args = optparser.parse_args() if options.system: reports = get_new_system_reports() else: reports = get_new_reports() if len(reports) > 0: for r in reports: print(r.split('.')[0].split('_')[-1]) if apport.packaging.enabled(): sys.exit(0) else: print('new reports but apport disabled') sys.exit(2) else: sys.exit(1) is-enabled 0000755 00000001240 00000000000 0006433 0 ustar 00 #!/bin/sh # Check if apport reports are enabled. Exit with 0 if so, otherwise with 1. # # Copyright (c) 2011 Canonical Ltd. # Author: Martin Pitt <martin.pitt@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. set -e CONF=/etc/default/apport # defaults to enabled if not present [ -f $CONF ] || exit 0 ! grep -q '^[[:space:]]*enabled[[:space:]]*=[[:space:]]*0[[:space:]]*$' $CONF gcc_ice_hook 0000755 00000002265 00000000000 0007034 0 ustar 00 #!/usr/bin/python3 # # Collect information about a gcc internal compiler exception (ICE). # # Copyright (c) 2007 Canonical Ltd. # Author: Martin Pitt <martin.pitt@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import sys import apport, apport.fileutils # parse command line arguments if len(sys.argv) != 3: print('Usage: %s <executable name> <gcc -E output file>' % sys.argv[0]) print('If "-" is specified as second argument, the preprocessed source is read from stdin.') sys.exit(1) (exename, sourcefile) = sys.argv[1:] # create report pr = apport.Report() pr['ExecutablePath'] = exename if sourcefile == '-': pr['PreprocessedSource'] = (sys.stdin, False) else: pr['PreprocessedSource'] = (sourcefile, False) # write report try: with apport.fileutils.make_report_file(pr) as f: pr.write(f) except (IOError, OSError) as e: apport.fatal('Cannot create report: ' + str(e)) kernel_crashdump 0000755 00000005511 00000000000 0007763 0 ustar 00 #!/usr/bin/python3 # # Collect information about a kernel oops. # # Copyright (c) 2007 Canonical Ltd. # Author: Martin Pitt <martin.pitt@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import os, re, glob import apport, apport.fileutils pr = apport.Report('KernelCrash') package = apport.packaging.get_kernel_package() pr.add_package(package) pr.add_os_info() vmcore_path = os.path.join(apport.fileutils.report_dir, 'vmcore') # only accept plain files here, not symlinks; otherwise we might recursively # include the report, or similar DoS attacks if os.path.exists(vmcore_path + '.log'): try: log_fd = os.open(vmcore_path + '.log', os.O_RDONLY | os.O_NOFOLLOW) pr['VmCoreLog'] = (os.fdopen(log_fd, 'rb'),) os.unlink(vmcore_path + '.log') except OSError as e: apport.fatal('Cannot open vmcore log: ' + str(e)) if os.path.exists(vmcore_path): try: core_fd = os.open(vmcore_path, os.O_RDONLY | os.O_NOFOLLOW) pr['VmCore'] = (os.fdopen(core_fd, 'rb'),) with apport.fileutils.make_report_file(pr) as f: pr.write(f) except (IOError, OSError) as e: apport.fatal('Cannot create report: ' + str(e)) try: os.unlink(vmcore_path) except OSError: pass # huh, already gone? else: # check for kdump-tools generated dmesg in timestamped dir for dmesg_file in glob.glob(os.path.join(apport.fileutils.report_dir, '*', 'dmesg.*')): timedir = os.path.dirname(dmesg_file) timestamp = os.path.basename(timedir) if re.match('^[0-9]{12}$', timestamp): # we require the containing dir to be owned by root, to avoid users # creating a symlink to someplace else and disclosing data; we just # compare against euid here so that we can test this as non-root if os.lstat(timedir).st_uid != os.geteuid(): apport.fatal('%s has unsafe permissions, ignoring' % timedir) report_name = package + '-' + timestamp + '.crash' try: crash_report = os.path.join(apport.fileutils.report_dir, report_name) dmesg_fd = os.open(dmesg_file, os.O_RDONLY | os.O_NOFOLLOW) pr['VmCoreDmesg'] = (os.fdopen(dmesg_fd, 'rb'),) # TODO: Replace with open(..., 'xb') once we drop Python 2 support with os.fdopen(os.open(crash_report, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o640), 'wb') as f: pr.write(f) except (IOError, OSError) as e: apport.fatal('Cannot create report: ' + str(e)) apportcheckresume 0000755 00000006221 00000000000 0010160 0 ustar 00 #!/usr/bin/python3 # Copyright (C) 2009 Canonical Ltd. # Author: Andy Whitcroft <apw@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import os import sys import datetime from apport import unicode_gettext as _ from apport.hookutils import attach_file_if_exists def main(argv=None): if argv is None: argv = sys.argv try: from apport.packaging_impl import impl as packaging if not packaging.enabled(): return -1 import apport.report pr = apport.report.Report(type='KernelOops') libdir = '/var/lib/pm-utils' flagfile = libdir + '/status' stresslog = libdir + '/stress.log' hanglog = libdir + '/resume-hang.log' pr.add_os_info() pr.add_proc_info() pr.add_user_info() pr.add_package(apport.packaging.get_kernel_package()) # grab the contents of the suspend/resume flag file attach_file_if_exists(pr, flagfile, 'Failure') # grab the contents of the suspend/hibernate log file attach_file_if_exists(pr, '/var/log/pm-suspend.log', 'SleepLog') # grab the contents of the suspend/resume stress test log if present. attach_file_if_exists(pr, stresslog, 'StressLog') # Ensure we are appropriately tagged. if 'Failure' in pr: pr['Tags'] = 'resume ' + pr['Failure'] # Record the failure mode. pr['Failure'] += '/resume' # If we had a late hang pull in the resume-hang logfile. Also # add an additional tag so we can pick these out. if os.path.exists(hanglog): attach_file_if_exists(pr, hanglog, 'ResumeHangLog') pr['Tags'] += ' resume-late-hang' # Generate a sensible report message. if pr.get('Failure') == 'suspend/resume': pr['Annotation'] = _('This occurred during a previous suspend, and prevented the system from resuming properly.') else: pr['Annotation'] = _('This occurred during a previous hibernation, and prevented the system from resuming properly.') # If we had a late hang make sure the dialog is clear that they may # not have noticed. Also update the bug title so we notice. if os.path.exists(hanglog): pr['Annotation'] += ' ' + _('The resume processing hung very near the end and will have appeared to have completed normally.') pr['Failure'] = 'late resume' if pr.check_ignored(): return 0 nowtime = datetime.datetime.now() pr_filename = '/var/crash/susres.%s.crash' % (str(nowtime).replace(' ', '_')) with os.fdopen(os.open(pr_filename, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o640), 'wb') as report_file: pr.write(report_file) return 0 except Exception: print('apportcheckresume failed') raise if __name__ == '__main__': sys.exit(main()) apport.jar 0000644 00000004602 00000000000 0006512 0 ustar 00 PK ({�Z META-INF/�� PK ({�Z META-INF/MANIFEST.MF�M��LK-.� K-*��ϳR0�3��r.JM,IM�u� E�,4B�J�JJ5y�x� PK��߱6 7 PK ({�Z 6 com/ubuntu/apport/ApportUncaughtExceptionHandler.class�W�[��-�X�1�8&2v� 9N�ɱ\�À�C��b��|�M�^9z�m��{�v����4mӦ�}�!��}n���@� |��Λ��{�ޛ����؇�8�i��8��p҃��!�r3/��hA�[���a�E��Zڑ���H�`Y@�шiRܧe�<0y�%c�32ZX�Y~���h�y�|�og��+��#���<x��:���ܼ�e�����1<�>� O�x?>�Y�!n>�G>�`���d|���IV4�O |Z�3�z�:$�:�~\��/��I�<�еXfy^3&�$I�d�Y\���ŵ��H�:C��3j8���%CS����Y�e�M�?=��@��>'�y�/H�_ԬQ#E��1-�2, _7vV����j.����Y#aie��f"�X�5n�r�B�q�/J�ޚ�e5��%UӜ4����Ǒ~B_� |I�H覥&i�<��q�h�y�Փf�&��P���ft1���Jh,'䈦ZK �0,aG������ܱ��=s��}���#����ؑ��1�/+� �JV�3�6�TC���0����;��ŷyM!1�L�7�H.hE��I��k ��o(x�0���U�-|[B�چA��#�����djȇfp� U�|WB[<�I.������ �����Y�xr�7���r��*��+ ����Z� ~��،���T�D�ּ�Y S�tF�/�$��R&�C����0a�qM�*~H�5\�[ �0��E\���xI��� ~�W�ϒ�7.(�^��/� ����߲RX���{��ʻ�-ao<���gt+���P���g��(�+�&�w��?K�?2J�Su�����0)�6�Ru]3*4�DQ5�ni���S��E�).w:�"9H�Q��m�-)�||���t+Z���MK# 7�[�~�24���+��V��XC��Q���}9Oh�fRU�Es~e�qF`�d�$N�Y5ae��t�6�aC%�Y��PALi%u6����T�����SxB���\1�ZA���e2wk�"g+��&�c��t��|����^�} kS�Rt������LӁb?�I]/i_/".Nk��5��(��HD��J�^���M%���-�fL;g9�<A״�J +��^dܶ �W\^�ܕdm�=oqչ�t��f{���f������"'@�ij����7���iF>CK'�M���ʙ����p�\Ee" u�eL3�YB�ƹ�kVxrl�@��1�15���Inx�J�Z����x��Nz���7���M== ����%�k:V ]� Gr��j�q4�T�&���O_B�Sp]��ڨ�y]WQq�\�@x��*Č�Exf�}���ڐ#5�uw������66�趩��W\Ħ�� �x�6�`�*gV���f��ӆ�:�����n� ����2��f�[v3��a�$a��{�������4�M�Bގ]��q� � ��I]�q�� c?�܍)Z1�{��<B���qDh-h��&��k�!�-0 0�� �6�R�@}-H���QMD�U���w�V�~'�:�s�`W�&���y��s�h��g*� ����T�S��G^� ��V�f����ÉXa��Q���)�Q�:��S���B7�HD�O��X�ş"�a��{������^�9f�ec'Q6]��v���� �)��C������8�&��ă4��6T�D��_`�?��$s����gL��g�C�';l�=��yݵ���=�A�~o|�� D��`6q���]�a��&Y�=��\E7�"6�$>#w�Tp���3�� ʥЌ�R���C�ki���c��@�� LP*ж�B��!0��˩��Ck�&;���?� �<E��.�|�]�?PK,"��� } PK ({�Z META-INF/�� PK ({�Z��߱6 7 + META-INF/MANIFEST.MFPK ({�Z,"��� } 6 � com/ubuntu/apport/ApportUncaughtExceptionHandler.classPK � � apport 0000755 00000103736 00000000000 0005752 0 ustar 00 #!/usr/bin/python3 # Collect information about a crash and create a report in the directory # specified by apport.fileutils.report_dir. # See https://wiki.ubuntu.com/Apport for details. # # Copyright (c) 2006 - 2016 Canonical Ltd. # Author: Martin Pitt <martin.pitt@ubuntu.com> # # 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. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import sys, os, os.path, subprocess, time, traceback, pwd, io import signal, inspect, grp, fcntl, socket, atexit, array, struct import errno, argparse, stat import re import apport, apport.fileutils ################################################################# # # functions # ################################################################# class ProcPidNotFoundError(FileNotFoundError): """FileNotFoundError specific for /proc/<pid>.""" def pidfd_getpid(pidfd: int) -> int: """Get the associated pid from the pid file descriptor. This function is equivalent to the identical named function in glibc. """ pid_re = re.compile(rb"^Pid:\s*(-?[0-9]+)$") with open(f"/proc/self/fdinfo/{pidfd}", "rb") as fdinfo_file: for line in fdinfo_file: match = pid_re.match(line) if match: return int(match.group(1)) raise OSError(errno.EBADF, f"{os.strerror(errno.EBADF)}: {pidfd}") def check_lock(): '''Abort if another instance of apport is already running. This avoids bringing down the system to its knees if there is a series of crashes.''' # create a lock file try: fd = os.open("/var/run/apport.lock", os.O_WRONLY | os.O_CREAT | os.O_NOFOLLOW, mode=0o600) except OSError as e: error_log('cannot create lock file (uid %i): %s' % (os.getuid(), str(e))) sys.exit(1) def error_running(*args): error_log('another apport instance is already running, aborting') sys.exit(1) original_handler = signal.signal(signal.SIGALRM, error_running) signal.alarm(30) # Timeout after that many seconds try: fcntl.lockf(fd, fcntl.LOCK_EX) except IOError: error_running() finally: signal.alarm(0) signal.signal(signal.SIGALRM, original_handler) (pidstat, crash_uid, crash_gid, cwd, proc_pid_fd) = (None, None, None, None, None) def proc_pid_exists(path: str | os.PathLike[str]) -> bool: """Test whether a path relative to /proc/<pid> exists. Returns False for broken symbolic links.""" assert proc_pid_fd is not None try: os.stat(path, dir_fd=proc_pid_fd) except (OSError, ValueError): return False return True def proc_pid_opener(path, flags): return os.open(path, flags, dir_fd=proc_pid_fd) def get_pid_info(pid): '''Read /proc information about pid''' global pidstat, crash_uid, crash_gid, cwd, proc_pid_fd try: proc_pid_fd = os.open('/proc/%s' % pid, os.O_RDONLY | os.O_PATH | os.O_DIRECTORY) except FileNotFoundError as error: raise ProcPidNotFoundError(*error.args, error.filename) from error # unhandled exceptions on missing or invalidly formatted files are okay # here -- we want to know in the log file pidstat = os.stat('stat', dir_fd=proc_pid_fd) # determine UID and GID of the target process; do *not* use the owner of # /proc/pid/stat, as that will be root for setuid or unreadable programs! # (this matters when suid_dumpable is enabled) with open('status', opener=proc_pid_opener) as status_file: contents = status_file.read() (crash_uid, crash_gid) = apport.fileutils.get_uid_and_gid(contents) assert crash_uid is not None, 'failed to parse Uid' assert crash_gid is not None, 'failed to parse Gid' cwd = os.open('cwd', os.O_RDONLY | os.O_PATH | os.O_DIRECTORY, dir_fd=proc_pid_fd) def get_process_starttime(): '''Get the starttime of the process using proc_pid_fd''' with open("stat", opener=proc_pid_opener) as stat_file: contents = stat_file.read() return apport.fileutils.get_starttime(contents) def get_apport_starttime(): '''Get the Apport process starttime''' with open("/proc/%s/stat" % os.getpid()) as stat_file: contents = stat_file.read() return apport.fileutils.get_starttime(contents) def drop_privileges(): '''Change effective user and group to crash_[ug]id ''' # Drop any supplemental groups, or we'll still be in the root group if os.getuid() == 0: os.setgroups([]) assert os.getgroups() == [] os.setregid(-1, crash_gid) os.setreuid(-1, crash_uid) assert os.getegid() == crash_gid assert os.geteuid() == crash_uid def recover_privileges(): '''Change effective user and group back to real uid and gid ''' os.setregid(-1, os.getgid()) os.setreuid(-1, os.getuid()) assert os.getegid() == os.getgid() assert os.geteuid() == os.getuid() def init_error_log(): '''Open a suitable error log if sys.stderr is not a tty.''' if not os.isatty(2): log = os.environ.get('APPORT_LOG_FILE', '/var/log/apport.log') try: f = os.open(log, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600) try: admgid = grp.getgrnam('adm')[2] os.chown(log, -1, admgid) os.chmod(log, 0o640) except KeyError: pass # if group adm doesn't exist, just leave it as root except OSError: # on a permission error, don't touch stderr return os.dup2(f, 1) os.dup2(f, 2) sys.stderr = os.fdopen(2, 'wb') if sys.version_info.major >= 3: sys.stderr = io.TextIOWrapper(sys.stderr) sys.stdout = sys.stderr def error_log(msg): '''Output something to the error log.''' apport.error('apport (pid %s) %s: %s', os.getpid(), time.asctime(), msg) def _log_signal_handler(sgn, frame): '''Internal apport signal handler. Just log the signal handler and exit.''' # reset handler so that we do not get stuck in loops signal.signal(sgn, signal.SIG_IGN) try: error_log('Got signal %i, aborting; frame:' % sgn) for s in inspect.stack(): error_log(str(s)) except Exception: pass sys.exit(1) def setup_signals(): '''Install a signal handler for all crash-like signals, so that apport is not called on itself when apport crashed.''' signal.signal(signal.SIGILL, _log_signal_handler) signal.signal(signal.SIGABRT, _log_signal_handler) signal.signal(signal.SIGFPE, _log_signal_handler) signal.signal(signal.SIGSEGV, _log_signal_handler) signal.signal(signal.SIGPIPE, _log_signal_handler) signal.signal(signal.SIGBUS, _log_signal_handler) def write_user_coredump( pid, timestamp, limit, report_owner_uid, coredump_fd=None, from_report=None ): '''Write the core into a directory if ulimit requests it.''' # three cases: # limit == 0: do not write anything # limit < 0: unlimited, write out everything # limit nonzero: crashed process' core size ulimit in bytes if limit == 0: return (core_name, core_path) = apport.fileutils.get_core_path(pid, options.executable_path, crash_uid, timestamp, proc_pid_fd) try: # Limit number of core files to prevent DoS apport.fileutils.clean_core_directory(report_owner_uid) core_file = os.open(core_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, mode=0o400, dir_fd=cwd) except (OSError, IOError): return error_log('writing core dump to %s (limit: %s)' % (core_name, str(limit))) written = 0 # Priming read if from_report: r = apport.Report() r.load(from_report) core_size = len(r['CoreDump']) if limit > 0 and core_size > limit: error_log('aborting core dump writing, size %i exceeds current limit' % core_size) os.close(core_file) os.unlink(core_path, dir_fd=cwd) return error_log('writing core dump %s of size %i' % (core_name, core_size)) os.write(core_file, r['CoreDump']) else: block = os.read(coredump_fd, 1048576) while True: size = len(block) if size == 0: break written += size if limit > 0 and written > limit: error_log('aborting core dump writing, size exceeds current limit %i' % limit) os.close(core_file) os.unlink(core_path, dir_fd=cwd) return if os.write(core_file, block) != size: error_log('aborting core dump writing, could not write') os.close(core_file) os.unlink(core_path, dir_fd=cwd) return block = os.read(coredump_fd, 1048576) # Make sure the user can read it os.fchown(core_file, report_owner_uid, -1) os.close(core_file) def usable_ram(): '''Return how many bytes of RAM is currently available that can be allocated without causing major thrashing.''' # abuse our excellent RFC822 parser to parse /proc/meminfo r = apport.Report() with open('/proc/meminfo', 'rb') as f: r.load(f) memfree = int(r['MemFree'].split()[0]) cached = int(r['Cached'].split()[0]) writeback = int(r['Writeback'].split()[0]) return (memfree + cached - writeback) * 1024 def _run_with_output_limit_and_timeout(args, output_limit, timeout, close_fds=True, env=None): '''Run command like subprocess.run() but with output limit and timeout. Return (stdout, stderr).''' stdout = b"" stderr = b"" process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=close_fds, env=env) try: # Don't block so we don't deadlock os.set_blocking(process.stdout.fileno(), False) os.set_blocking(process.stderr.fileno(), False) for _ in range(timeout): alive = process.poll() is None while len(stdout) < output_limit and len(stderr) < output_limit: tempout = process.stdout.read(100) if tempout: stdout += tempout temperr = process.stderr.read(100) if temperr: stderr += temperr if not tempout and not temperr: break if not alive or len(stdout) >= output_limit or len(stderr) >= output_limit: break time.sleep(1) finally: process.kill() return stdout, stderr def is_closing_session(): '''Check if pid is in a closing user session. During that, crashes are common as the session D-BUS and X.org are going away, etc. These crash reports are mostly noise, so should be ignored. ''' # Sanity check, don't do anything for root processes if crash_uid == 0 or crash_gid == 0: return False with open('environ', 'rb', opener=proc_pid_opener) as e: env = e.read().split(b'\0') for e in env: if e.startswith(b'DBUS_SESSION_BUS_ADDRESS='): dbus_addr = e.split(b'=', 1)[1].decode() break else: error_log('is_closing_session(): no DBUS_SESSION_BUS_ADDRESS in environment') return False dbus_socket = apport.fileutils.get_dbus_socket(dbus_addr) if not dbus_socket: error_log('is_closing_session(): Could not determine DBUS socket.') return False if not os.path.exists(dbus_socket): error_log("is_closing_session(): DBUS socket doesn't exist.") return False # We need to drop both the real and effective uid/gid before calling # gdbus because DBUS_SESSION_BUS_ADDRESS is untrusted and may allow # reading arbitrary files as a noncefile. We can't just drop effective # uid/gid as gdbus has a check to make sure it's not running in a # setuid environment and it does so by comparing the real and effective # ids. We don't need to drop supplemental groups here, as the privilege # dropping code elsewhere has already done so. real_uid = os.getuid() real_gid = os.getgid() try: os.setresgid(crash_gid, crash_gid, real_gid) os.setresuid(crash_uid, crash_uid, real_uid) out, err = _run_with_output_limit_and_timeout(['/usr/bin/gdbus', 'call', '-e', '-d', 'org.gnome.SessionManager', '-o', '/org/gnome/SessionManager', '-m', 'org.gnome.SessionManager.IsSessionRunning', '-t', '5'], 1000, 5, env={'DBUS_SESSION_BUS_ADDRESS': dbus_addr}) if err: error_log('gdbus call error: ' + err.decode('UTF-8')) except OSError as e: error_log('gdbus call failed, cannot determine running session: ' + str(e)) return False finally: os.setresuid(real_uid, real_uid, -1) os.setresgid(real_gid, real_gid, -1) error_log('debug: session gdbus call: ' + out.decode('UTF-8')) if out.startswith(b'(false,'): return True return False def is_systemd_watchdog_restart(signum): '''Check if this is a restart by systemd's watchdog''' if signum != str(signal.SIGABRT) or not os.path.isdir('/run/systemd/system'): return False try: with open('cgroup', opener=proc_pid_opener) as f: for line in f: if 'name=systemd:' in line: unit = line.split('/')[-1].strip() break else: return False journalctl = subprocess.Popen(['/bin/journalctl', '--output=cat', '--since=-5min', '--priority=warning', '--unit', unit], stdout=subprocess.PIPE) out = journalctl.communicate()[0] return b'Watchdog timeout' in out except (IOError, OSError) as e: error_log('cannot determine if this crash is from systemd watchdog: %s' % e) return False def is_same_ns(ns: str) -> bool: if not os.path.exists('/proc/self/ns/%s' % ns) or not proc_pid_exists('ns/%s' % ns): # If the namespace doesn't exist, then it's obviously shared return True try: if os.readlink('ns/%s' % ns, dir_fd=proc_pid_fd) == os.readlink('/proc/self/ns/%s' % ns): # Check that the inode for both namespaces is the same return True except OSError as e: if e.errno == errno.ENOENT: return True else: raise # check to see if the process is part of the system.slice (LP: #1870060) if proc_pid_exists('cgroup'): with open('cgroup', opener=proc_pid_opener) as cgroup: for line in cgroup: fields = line.split(':') if fields[-1].startswith('/system.slice'): return True return False def parse_arguments(): parser = argparse.ArgumentParser(epilog=""" Alternatively, the following command line is understood for legacy hosts: <pid> <signal number> <core file ulimit> <dump mode> [global pid] [exe path] """) # TODO: Use type=int parser.add_argument("-p", "--pid", help="process id (%%p)") parser.add_argument("-s", "--signal-number", help="signal number (%%s)") parser.add_argument("-c", "--core-ulimit", help="core ulimit (%%c)") parser.add_argument("-d", "--dump-mode", help="dump mode (%%d)") parser.add_argument("-P", "--global-pid", help="pid in root namespace (%%P)") parser.add_argument( "-F", "--pidfd", nargs="?", type=int, help="pidfd for the crashed process (%%F)" ) parser.add_argument("-u", "--uid", type=int, help="real UID (%%u)") parser.add_argument("-g", "--gid", type=int, help="real GID (%%g)") parser.add_argument("executable_path", nargs='*', help="path of executable (%%E)") options, rest = parser.parse_known_args() # Legacy command line needs to remain for the scenario where a more # recent apport is running inside a container with an older apport # running on the host. if options.pid is None and len(sys.argv) == 5: # Translate legacy command line return argparse.Namespace( pid=sys.argv[1], signal_number=sys.argv[2], core_ulimit=sys.argv[3], dump_mode=sys.argv[4], global_pid=None, pidfd=None, uid=None, gid=None, executable_path=None, ) if options.pid is None: parser.print_usage() sys.exit(1) if options.dump_mode is None: parser.error("the following arguments are required: -d/--dump-mode") for arg in rest: error_log("Unknown argument: %s" % arg) # In kernels before 5.3.0, an executable path with spaces may be split # into separate arguments. If options.executable_path is a list, join # it back into a string. Also restore directory separators. if isinstance(options.executable_path, list): options.executable_path = " ".join(options.executable_path) options.executable_path = options.executable_path.replace('!', '/') # Sanity check to prevent trickery later on if '../' in options.executable_path: options.executable_path = None return options def consistency_checks(options: argparse.Namespace, process_start: int) -> bool: # Sanity check to make sure the process wasn't replaced after the crash # happened. The start time isn't fine-grained enough to be an adequate # security check. apport_start = get_apport_starttime() if process_start > apport_start: error_log('process was replaced after Apport started, ignoring') return False # Make sure the process uid/gid match the ones provided by the kernel # if available, if not, it may have been replaced if (options.uid is not None) and (options.gid is not None): if (options.uid != crash_uid) or (options.gid != crash_gid): error_log("process uid/gid doesn't match expected, ignoring") return False # check if the executable was modified after the process started (e. g. # package got upgraded in between). exe_path = "root%s" % (os.readlink('exe', dir_fd=proc_pid_fd)) exe_stat = None try: exe_stat = os.stat(exe_path, dir_fd=proc_pid_fd) except (OSError, ValueError): pass process_mtime = os.lstat('cmdline', dir_fd=proc_pid_fd).st_mtime if exe_stat is None or exe_stat.st_mtime > process_mtime: error_log('executable was modified after program start, ignoring') return False return True def determine_report_owner(dump_mode: str, real_user: int) -> int: """Determine who should be the owner of the crash report file. For dump_mode 1 ("debug") the owner of the process can become the crash report owner. For dump_mode 2 ("suidsafe") the crashed process is a suid process and the report should be owned by root instead. See proc_sys_fs(5) man page and https://kernel.org/doc/html/latest/admin-guide/sysctl/fs.html#suid-dumpable """ if dump_mode == "1": return real_user return 0 ################################################################# # # main # ################################################################# init_error_log() # systemd socket activation if 'LISTEN_FDS' in os.environ: try: from systemd.daemon import listen_fds except ImportError: error_log('Received a crash via apport-forward.socket, but systemd python module is not installed') sys.exit(0) # Extract and validate the fd fds = listen_fds() if len(fds) < 1: error_log('Invalid socket activation, no fd provided') sys.exit(1) # Open the socket sock = socket.fromfd(int(fds[0]), socket.AF_UNIX, socket.SOCK_STREAM) atexit.register(sock.shutdown, socket.SHUT_RDWR) # Replace stdin by the socket activation fd sys.stdin.close() fds = array.array('i') ucreds = array.array('i') msg, ancdata, flags, addr = sock.recvmsg(4096, 4096) for cmsg_level, cmsg_type, cmsg_data in ancdata: if (cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS): fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) elif (cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_CREDENTIALS): ucreds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % ucreds.itemsize)]) sys.stdin = os.fdopen(int(fds[0]), 'r') # Replace argv by the arguments received over the socket sys.argv = [sys.argv[0]] sys.argv += msg.decode().split() if len(ucreds) >= 3: sys.argv[1] = "%d" % ucreds[0] if len(sys.argv) != 5: error_log('Received a bad number of arguments from forwarder, received %d, expected 5, aborting.' % len(sys.argv)) sys.exit(1) options = parse_arguments() if options.global_pid is None: pid = options.pid else: pid = options.global_pid try: get_pid_info(pid) except ProcPidNotFoundError as error: error_log( "%s not found. Cannot collect crash information for process %s any more." % (error.filename, pid) ) sys.exit(1) if options.pidfd is not None and int(pid) != pidfd_getpid(options.pidfd): error_log( "The process %s has already been replaced by a new process" " with the same ID. Ignoring crash." % pid ) sys.exit(1) process_start = get_process_starttime() if not consistency_checks(options, process_start): sys.exit(0) # Check if we received a valid global PID (kernel >= 3.12). If we do, # then compare it with the local PID. If they don't match, it's an # indication that the crash originated from another PID namespace. # Simply log an entry in the host error log and exit 0. if options.global_pid is not None: host_pid = int(options.global_pid) if not is_same_ns("pid") and not is_same_ns("mnt"): # If the crash came from a container, don't attempt to handle # locally as that would just result in wrong system information. # Instead, attempt to find apport inside the container and # forward the process information there. if options.pidfd is None and options.dump_mode != "1": error_log( "Not forwarding crash with dump mode of %s to container" " due to security concerns. Please provide --pidfd." % options.dump_mode ) sys.exit(0) # Validate that the target socket is owned by the user namespace of the process try: sock_fd = os.open("root/run/apport.socket", os.O_RDONLY | os.O_PATH, dir_fd=proc_pid_fd) socket_uid = os.fstat(sock_fd).st_uid except FileNotFoundError: error_log('host pid %s crashed in a container without apport support' % options.global_pid) sys.exit(0) try: with open("uid_map", "r", opener=proc_pid_opener) as fd: if not apport.fileutils.search_map(fd, socket_uid): error_log("user is trying to trick apport into accessing a socket that doesn't belong to the container") sys.exit(0) except FileNotFoundError: pass # Validate that the crashed binary is owned by the user namespace of the process exe_path = "root%s" % (os.readlink('exe', dir_fd=proc_pid_fd)) exe_stat = os.stat(exe_path, dir_fd=proc_pid_fd) try: with open("uid_map", "r", opener=proc_pid_opener) as fd: if not apport.fileutils.search_map(fd, exe_stat.st_uid): error_log("host pid %s crashed in a container with no access to the binary" % options.global_pid) sys.exit(0) except FileNotFoundError: pass try: with open("gid_map", "r", opener=proc_pid_opener) as fd: if not apport.fileutils.search_map(fd, exe_stat.st_gid): error_log("host pid %s crashed in a container with no access to the binary" % options.global_pid) sys.exit(0) except FileNotFoundError: pass # Now open the socket sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: sock.connect('/proc/self/fd/%s' % sock_fd) except Exception: error_log('host pid %s crashed in a container with a broken apport' % options.global_pid) sys.exit(0) # Send main arguments only # Older apport in containers doesn't support positional arguments args = "%s %s %s %s" % (options.pid, options.signal_number, options.core_ulimit, options.dump_mode) try: sock.sendmsg([args.encode()], [ # Send a ucred containing the global pid (socket.SOL_SOCKET, socket.SCM_CREDENTIALS, struct.pack("3i", host_pid, 0, 0)), # Send fd 0 (the coredump) (socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array('i', [0]))]) sock.shutdown(socket.SHUT_RDWR) except Exception: error_log('Container apport failed to process crash within 30s') sys.exit(0) elif not is_same_ns("mnt"): error_log('host pid %s crashed in a separate mount namespace, ignoring' % host_pid) sys.exit(0) # If it doesn't look like the crash originated from within a # full container or if the is_same_ns() function fails open (returning # True), then take the global pid and move on to normal handling. # This bit is needed because some software like the chrome # sandbox will use container namespaces as a security measure but are # still otherwise host processes. When that's the case, we need to keep # handling those crashes locally using the global pid. check_lock() try: setup_signals() signum = options.signal_number core_ulimit = options.core_ulimit dump_mode = options.dump_mode coredump_fd = sys.stdin.fileno() error_log('called for%s pid %s, signal %s, core limit %s, dump mode %s' % (" global" if options.global_pid else "", pid, signum, core_ulimit, dump_mode)) try: core_ulimit = int(core_ulimit) except ValueError: error_log('core limit is invalid, disabling core files') core_ulimit = 0 # clamp core_ulimit to a sensible size, for -1 the kernel reports something # absurdly big if core_ulimit > 9223372036854775807: error_log('ignoring implausibly big core limit, treating as unlimited') core_ulimit = -1 assert crash_uid is not None report_owner_uid = determine_report_owner(options.dump_mode, crash_uid) # ignore SIGQUIT (it's usually deliberately generated by users) if signum == str(int(signal.SIGQUIT)): write_user_coredump(pid, process_start, core_ulimit, report_owner_uid, coredump_fd) sys.exit(0) info = apport.Report('Crash') info['Signal'] = signum core_size_limit = usable_ram() * 3 / 4 if sys.version_info.major < 3: info['CoreDump'] = (sys.stdin, True, core_size_limit, True) else: # read binary data from stdio info['CoreDump'] = (sys.stdin.detach(), True, core_size_limit, True) # We already need this here to figure out the ExecutableName (for scripts, # etc). if options.executable_path is not None and os.path.exists(options.executable_path): info['ExecutablePath'] = options.executable_path else: info['ExecutablePath'] = os.readlink('exe', dir_fd=proc_pid_fd) # Drop privileges temporarily to make sure that we don't # include information in the crash report that the user should # not be allowed to access. drop_privileges() info.add_proc_info(proc_pid_fd=proc_pid_fd) if 'ExecutablePath' not in info: error_log('could not determine ExecutablePath, aborting') sys.exit(1) subject = info['ExecutablePath'].replace('/', '_') base = '%s.%s.%s.hanging' % (subject, str(pidstat.st_uid), pid) hanging = os.path.join(apport.fileutils.report_dir, base) if os.path.exists(hanging): if (os.stat('/proc/uptime').st_ctime < os.stat(hanging).st_mtime): info['ProblemType'] = 'Hang' os.unlink(hanging) if 'InterpreterPath' in info: error_log('script: %s, interpreted by %s (command line "%s")' % (info['ExecutablePath'], info['InterpreterPath'], info['ProcCmdline'])) else: error_log('executable: %s (command line "%s")' % (info['ExecutablePath'], info['ProcCmdline'])) # ignore non-package binaries (unless configured otherwise) if not apport.fileutils.likely_packaged(info['ExecutablePath']): if not apport.fileutils.get_config('main', 'unpackaged', False, bool=True): error_log('executable does not belong to a package, ignoring') # check if the user wants a core dump recover_privileges() write_user_coredump(pid, process_start, core_ulimit, report_owner_uid, coredump_fd) sys.exit(0) # ignore SIGXCPU and SIGXFSZ since this indicates some external # influence changing soft RLIMIT values when running programs. if signum in [str(signal.SIGXCPU), str(signal.SIGXFSZ)]: error_log('Ignoring signal %s (caused by exceeding soft RLIMIT)' % signum) recover_privileges() write_user_coredump(pid, process_start, core_ulimit, report_owner_uid, coredump_fd) sys.exit(0) # ignore blacklisted binaries if info.check_ignored(): error_log('executable version is blacklisted, ignoring') sys.exit(0) # We can now recover privileges to create the crash report file and # write out the user coredumps recover_privileges() if is_closing_session(): error_log('happens for shutting down session, ignoring') sys.exit(0) # ignore systemd watchdog kills; most often they don't tell us the actual # reason (kernel hang, etc.), LP #1433320 if is_systemd_watchdog_restart(signum): error_log('Ignoring systemd watchdog restart') sys.exit(0) crash_counter = 0 # Create crash report file descriptor for writing the report into # report_dir try: report = '%s/%s.%i.crash' % (apport.fileutils.report_dir, info['ExecutablePath'].replace('/', '_'), pidstat.st_uid) if os.path.exists(report): if apport.fileutils.seen_report(report): # do not flood the logs and the user with repeated crashes # and make sure the file isn't a FIFO or symlink fd = os.open(report, os.O_NOFOLLOW | os.O_RDONLY | os.O_NONBLOCK) st = os.fstat(fd) if stat.S_ISREG(st.st_mode): with os.fdopen(fd, 'rb') as f: crash_counter = apport.fileutils.get_recent_crashes(f) crash_counter += 1 if crash_counter > 1: write_user_coredump( pid, process_start, core_ulimit, report_owner_uid, coredump_fd ) error_log('this executable already crashed %i times, ignoring' % crash_counter) sys.exit(0) # remove the old file, so that we can create the new one with # os.O_CREAT|os.O_EXCL os.unlink(report) else: error_log('apport: report %s already exists and unseen, skipping to avoid disk usage DoS' % report) write_user_coredump( pid, process_start, core_ulimit, report_owner_uid, coredump_fd ) sys.exit(0) # we prefer having a file mode of 0 while writing; fd = os.open(report, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0) reportfile = os.fdopen(fd, 'w+b') assert reportfile.fileno() > sys.stderr.fileno() # Make sure the crash reporting daemon can read this report try: gid = pwd.getpwnam('whoopsie').pw_gid os.fchown(fd, report_owner_uid, gid) except (OSError, KeyError): os.fchown(fd, report_owner_uid, -1) except (OSError, IOError) as e: error_log('Could not create report file: %s' % str(e)) sys.exit(1) # Drop privileges before writing out the reportfile. drop_privileges() info.add_user_info() info.add_os_info() if crash_counter > 0: info['CrashCounter'] = '%i' % crash_counter try: info.write(reportfile) except IOError: os.unlink(report) raise if 'CoreDump' not in info: error_log('core dump exceeded %i MiB, dropped from %s to avoid memory overflow' % (core_size_limit / 1048576, report)) # Get privileges back so the core file can be written to root-owned # corefile directory recover_privileges() # make the report writable now, when it's completely written os.fchmod(fd, 0o640) error_log('wrote report %s' % report) # Check if the user wants a core file. We need to create that from the # written report, as we can only read stdin once and write_user_coredump() # might abort reading from stdin and remove the written core file when # core_ulimit is > 0 and smaller than the core size. reportfile.seek(0) write_user_coredump(pid, process_start, core_ulimit, report_owner_uid, from_report=reportfile) except (SystemExit, KeyboardInterrupt): raise except Exception: error_log('Unhandled exception:') traceback.print_exc() error_log('pid: %i, uid: %i, gid: %i, euid: %i, egid: %i' % ( os.getpid(), os.getuid(), os.getgid(), os.geteuid(), os.getegid())) error_log('environment: %s' % str(os.environ)) symptoms/ubuntu-release-upgrader.py 0000644 00000000326 00000000000 0013522 0 ustar 00 # ubuntu-release-upgrader related problems # Author: Brian Murray <brian.murray@canonical.com> # (C) 2013 Canonical Ltd. # License: GPL v2 or later. def run(report, ui): return 'ubuntu-release-upgrader-core' symptoms/_audio_mixercontrol.py 0000644 00000007533 00000000000 0013027 0 ustar 00 # Written by David Henningsson <david.henningsson@canonical.com> # Copyright Canonical Ltd 2010. # Licensed under GPLv3. from _audio_data import run_subprocess import re class MixerControl: ''' A simple mixer control ''' def parse_amixer(self, amixer_data): self.name = amixer_data[0][len("Simple mixer control "):] self.amixer_dict = {} for line in amixer_data: s = line.split(":") if (len(s) != 2): continue self.amixer_dict[s[0].strip()] = s[1].strip() self.caps = set(self.amixer_dict['Capabilities'].split(" ")) if not 'Playback channels' in self.amixer_dict: pchan_set = set() else: pchan_set = set(self.amixer_dict['Playback channels'].split(" - ")) self.has_dB = True self.pchans = {} for c in pchan_set: self.pchans[c] = {} s = self.amixer_dict[c] m = re.search("\[([\-0-9.]+)dB\]", s) if m is None: self.has_dB = False else: self.pchans[c]['dB'] = float(m.group(1)) if re.search("\[on\]", s): self.pchans[c]['muted'] = False if re.search("\[off\]", s): self.pchans[c]['muted'] = True m = re.search(" ?(\d+) ", s) if m is not None: self.pchans[c]['value'] = int(m.group(1)) m = re.search("\[([\-0-9.]+)%\]", s) if m is not None: self.pchans[c]['percent'] = float(m.group(1)) def __init__(self, amixer_data, device_name): self.device_name = device_name self.parse_amixer(amixer_data) def get_pretty_name(self): s = self.name.split("'") t = int(s[2].split(",")[1]) s = s[1] if (t > 0): return s + " " + str(t) return s def get_caps(self): return self.caps def do_get(self): s = run_subprocess(("amixer", "-D", self.device_name, "sget", self.name)) self.parse_amixer(s.splitlines()) def do_set(self, *args): run_subprocess(("amixer", "-D", self.device_name, "--", "sset", self.name) + args) def get_pstate(self): self.do_get() return self.pchans def set_dB(self, level): s = '{0:.4f}dB'.format(level) if ("pswitch" in self.caps) or ("pswitch-joined" in self.caps): self.do_set(s, "unmute") else: self.do_set(s) def set_mute(self, is_muted): if is_muted: self.do_set("mute") else: self.do_set("unmute") def set_original(self): for (k,v) in self.pchans.iteritems(): t = [] # t = [k] didn't work if 'value' in v: t.append(str(v['value'])) if 'muted' in v: t.append("mute" if v['muted'] else "unmute") if len(t) <= 0: return self.do_set(*t) class MixerControlList: def __init__(self, device_name, report): self.device_name = device_name self.parse_amixer(report) def parse_amixer(self, report): r = run_subprocess(report, "Symptom_amixer", ["amixer", "-D", self.device_name, "scontents"]) self.controls = [] s = [] for line in r.splitlines(): if (s != []) and (not line.startswith(" ")): self.controls.append(MixerControl(s, self.device_name)) s = [] s.append(line) if (s != []): self.controls.append(MixerControl(s, self.device_name)) def get_control_cap(self, cap_list): result = [] for c in self.controls: if len(c.get_caps().intersection(set(cap_list))) > 0: result.append(c) return result def restore_all(self): for c in self.controls: c.set_original() symptoms/installer.py 0000644 00000001254 00000000000 0010751 0 ustar 00 # ubuntu-release-upgrader related problems # Author: Brian Murray <brian.murray@canonical.com> # (C) 2020 Canonical Ltd. # License: GPL v3 or later. import os def run(report, ui): if not os.path.exists('/var/log/installer'): return with os.scandir('/var/log/installer') as entries: for entry in entries: if entry.name.startswith('subiquity'): return 'subiquity' elif entry.name == 'telemetry': return 'ubiquity' # d-i leaves a few unique things including # lsb-release, hardware-summary, status elif entry.name == 'status': return 'debian-installer' symptoms/_audio_checks.py 0000644 00000020001 00000000000 0011523 0 ustar 00 # Sound/audio related problem troubleshooter/triager # Written by David Henningsson 2011, david.henningsson@canonical.com # Copyright Canonical Ltd 2011 # License: BSD (see /usr/share/common-licenses/BSD ) import apport from apport.hookutils import * from _audio_mixercontrol import * from _audio_data import * def check_audio_users(report, ui): a = get_users_in_group('audio') report['Symptom_AudioUsers'] = ', '.join(a) try: a.remove('pulse') except: pass if len(a) > 0: yn = ui.yesno('The following users or programs can access the sound card ' 'exclusively, and even when not logged in:\n\n%s\n\n' 'This can cause problems even for other users.\n' 'You can fix this by uninstalling the relevant programs or by ' 'unchecking the "Use Audio Devices" checkbox for the user ' 'in the "users and groups" dialog. Do you want to continue troubleshooting?' % ', '.join(a)) if not yn: raise StopIteration def check_volumes(report, ui, card, isOutput, jack, maxdb=None): try: aname = 'hw:'+card.alsa_shortname except: return # this is a PA card only, can't check alsa stuff if jack is None: controlnames = card.get_controlnames(isOutput) else: controlnames = jack.get_controlnames() levels = set() mcl = MixerControlList(aname, report) for c in mcl.controls: cname = c.get_pretty_name() if not cname in controlnames: continue for p in c.pchans: if ('muted' in c.pchans[p]) and c.pchans[p]['muted']: levels.add(cname + ' is muted\n') if ('percent' in c.pchans[p]) and c.pchans[p]['percent'] < 70: levels.add(cname + ' is at {0}%\n'.format(c.pchans[p]['percent'])) if (maxdb is not None) and ('dB' in c.pchans[p]) and (c.pchans[p]['dB'] > maxdb): levels.add(cname + ' is at {0} dB\n'.format(c.pchans[p]['dB'])) if len(levels) > 0: if not ui.yesno('The following mixer control(s) might be incorrectly set: \n' + '\n'.join(levels) + 'Please try to fix that (e g by running \n"alsamixer -D ' + aname + '" in a terminal) and see if that solves the problem.\n' 'Would you like to continue troubleshooting anyway?\n'): raise StopIteration return def check_pulseaudio_running(report, ui): ''' Possible outcomes: None if pulseaudio is running, pulseaudio if installed but not running, or alsa-base if pulseaudio is not installed. ''' if subprocess.call(['pgrep', '-u', str(os.getuid()), '-x', 'pulseaudio']) == 0: return None if subprocess.call(['pgrep', '-u', 'pulse', '-x', 'pulseaudio']) == 0: ui.information('PulseAudio is running as a system-wide daemon.\n' 'This mode of operation is not supported by Ubuntu.\n' 'If this is not intentional, ask for help on answers.launchpad.net.') raise StopIteration try: if apport.packaging.get_version('pulseaudio'): if ui.yesno('PulseAudio seems to have crashed. ' 'Do you wish to report a bug?'): return 'pulseaudio' raise StopIteration except ValueError: pass # PulseAudio is not installed, so fall back to ALSA return 'alsa-base' def check_pulseaudio_profile(report, ui, card, isOutput, jack): ''' Returns package, channelmap ''' # ensure card was detected by PA if not 'pa_card' in card.__dict__: report['Title'] = get_hw_title(card, isOutput, jack, "Pulseaudio fails to detect card") return 'pulseaudio', None ssname, ssprofile, channelcount = get_pa_default_profile(isOutput) if not card.has_sink_or_source(ssname): if not ui.yesno("You don't seem to have configured PulseAudio to use " 'the card you want %s from (%s).\n You can fix ' 'that using pavucontrol or the GNOME volume control. ' 'Continue anyway?' % ("output" if isOutput else "input", card.pretty_name())): raise StopIteration if isOutput and (jack is not None) and (channelcount < jack.needed_channelcount()): if not ui.yesno("You don't seem to have configured PulseAudio " 'for surround output (%s).\n You can fix that using pavucontrol ' 'or the GNOME volume control. Continue anyway?' % ssprofile): raise StopIteration return None, channelcount def check_playback(report, ui, device_name, channelcount): ''' Returns package if it successfully finds one. ''' ui.information('Next, a speaker test will be performed. For your safety,\n' 'if you have headphones on, take them off to avoid damaging your ears.\n' 'Close this dialog to hear the test tone. It should alternate between %d channels.' % channelcount) run_subprocess(report, 'Symptom_AlsaPlaybackTest', ['pasuspender', '--', 'speaker-test', '-l', '3', '-c', str(channelcount), '-b', '100000', '-D', device_name, '-t', 'sine']) result = ui.yesno('Were the test tones played back correctly?') report['Symptom_AlsaPlaybackTest'] = "ALSA playback test through %s %s" % (device_name, 'successful' if result else 'failed') if not result: return 'alsa-base' ui.information('Close this dialog to hear the second test tone. It should alternate between channels.') run_subprocess(report, 'Symptom_SpeakerTestPulse', ['speaker-test', '-l', '3', '-c', str(channelcount), '-b', '100000', '-D', 'pulse', '-t', 'sine']) result = ui.yesno('Were the test tones played back correctly?') report['Symptom_PulsePlaybackTest'] = 'PulseAudio playback test %s' % ('successful' if result else 'failed') if not result: return 'pulseaudio' return None def check_recording(report, ui, device_name, channelcount, tmpfile): ui.information('Next, up to two recording tests will be performed. Close this dialog to \n' 'start recording some sounds. After some seconds, the recorded sound should be \n' 'played back to you through the default sound output.') if os.path.exists(tmpfile): run_subprocess(report, 'Symptom_RemoveRecording', ['rm', tmpfile]) run_subprocess(report, 'Symptom_AlsaRecordingTest', ['pasuspender', '--', 'arecord', '-q', '-f', 'cd', '-d', '7', '-D', device_name, tmpfile]) run_subprocess(report, 'Symptom_AlsaRecordingPlayback', ['paplay', tmpfile]) result = ui.yesno('Was the sound recorded correctly?') report['Symptom_AlsaRecordingTest'] = "ALSA recording test through %s %s" % (device_name, 'successful' if result else 'failed') if not result: return 'alsa-base' ui.information('Close this dialog to record some sounds for the second test.\n' 'After some seconds, the recorded sound should be \n' 'played back to you through the default sound output.') run_subprocess(report, 'Symptom_RemoveRecording', ['rm', tmpfile]) run_subprocess(report, 'Symptom_PulseAudioRecordingTest', [ 'arecord', '-q', '-f', 'cd', '-d', '7', '-D', 'pulse', tmpfile]) run_subprocess(report, 'Symptom_PulseAudioRecordingPlayback', ['paplay', tmpfile]) result = ui.yesno('Was the sound recorded correctly?') report['Symptom_PulseAudioRecordingTest'] = "PulseAudio recording test through %s %s" % (device_name, 'successful' if result else 'failed') if not result: return 'pulseaudio' return None def check_devices_in_use(report, ui): report['Symptom_DevicesInUse'] = root_command_output(['fuser','-v'] + glob.glob('/dev/snd/*')) def check_test_tones(report, ui, card, isOutput, jack): channelcount = 2 if jack is not None: channelcount = jack.needed_channelcount()+1 try: aname = 'plughw:' + card.alsa_shortname except: return 'pulseaudio' if isOutput: return check_playback(report, ui, aname, channelcount) else: return check_recording(report, ui, aname, channelcount, '/tmp/audio_symptom_test.wav') symptoms/storage.py 0000644 00000012174 00000000000 0010423 0 ustar 00 # Storage device related problems # Author: Martin Pitt <martin.pitt@ubuntu.com> # (C) 2009 Canonical Ltd. # License: GPL v2 or later. from glob import glob import subprocess import apport.hookutils import time import os description = 'External or internal storage devices (e. g. USB sticks)' def run(report, ui): problem = \ ui.choice('What particular problem do you observe?', ['Removable storage device is not mounted automatically', 'Internal hard disk partition cannot be mounted manually', 'Internal hard disk partition is not displayed in Places menu', 'No permission to access files on storage device', 'Documents cannot be opened in desktop UI on storage device', 'Other problem'] ) if problem is None: raise StopIteration problem = problem[0] if problem == 0: return problem_removable(report, ui) if problem == 1: report['Title'] = 'Internal hard disk partition cannot be mounted manually' return 'udisks2' if problem == 2: report['Title'] = 'Internal hard disk partition is not displayed in Places menu' return get_desktop_vfs(ui) if problem == 3: return problem_permission(report, ui) if problem == 4: report['Title'] = 'Documents cannot be opened in desktop UI on storage device' return get_desktop_vfs(ui) if problem == 5: ui.information('Please use "ubuntu-bug <packagename>" to report a bug against the particular package') raise StopIteration assert False, 'not reached' def problem_removable(report, ui): ui.information('Please disconnect the problematic device now if it is still plugged in.') try: ud_mon = subprocess.Popen(['udisksctl', 'monitor'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) except OSError: ud_mon = None try: udev_mon = subprocess.Popen(['udevadm', 'monitor', '--udev', '-e'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) except OSError: udev_mon = None try: gvfs_mon = subprocess.Popen(['gvfs-mount', '-oi'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) except OSError: gvfs_mon = None old_mounts = set(open('/proc/mounts').readlines()) old_devices = set(glob('/dev/sd*')) ui.information('Please connect the problematic device now.') time.sleep(10) new_mounts = set(open('/proc/mounts').readlines()) new_devices = set(glob('/dev/sd*')) if ud_mon: ud_mon.terminate() out, err = ud_mon.communicate() report['UdisksMonitorLog'] = out if err: report['UdisksMonitorError'] = err if udev_mon: udev_mon.terminate() out, err = udev_mon.communicate() report['UdevMonitorLog'] = out if err: report['UdevMonitorError'] = err if gvfs_mon: gvfs_mon.terminate() out, err = gvfs_mon.communicate() report['GvfsMonitorLog'] = out if err: report['GvfsMonitorError'] = err new_devices = new_devices - old_devices new_mounts = new_mounts - old_mounts report['HotplugNewDevices'] = ' '.join(new_devices) report['HotplugNewMounts'] = '\n'.join(new_mounts) apport.hookutils.attach_dmesg(report) if not new_devices: return apport.packaging.get_kernel_package() if new_mounts: if ui.yesno('The plugged in device was automounted:\n\n%s\n' 'Do you still need to report a problem about this?' % report['HotplugNewMounts']): return 'udisks2' else: raise StopIteration if 'SUBSYSTEM=block' not in report['UdevMonitorLog']: report['Title'] = \ 'Removable storage device not detected as block device' return 'udev' report['Title'] = 'Does not detect hotplugged storage device' for d in new_devices: if 'DEVNAME=' + d not in report['UdevMonitorLog']: return 'udev' if ' %s\n' % d not in report['UdisksMonitorLog']: return 'udisks2' return get_desktop_vfs(ui) def problem_permission(report, ui): '''No permission to access files on storage device''' report['Title'] = 'No permission to access files on storage device' return 'udisks2' def get_desktop_vfs(ui): if subprocess.call(['pgrep', '-u', str(os.getuid()), '-f', 'gnome-session-binary']) == 0: return 'gvfs' if subprocess.call(['pgrep', '-u', str(os.getuid()), '-x', 'ksmserver']) == 0: return 'kdelibs5' ui.information('Sorry, you are not running GNOME or KDE. Automounting needs to be provided by your desktop environment.') raise StopIteration symptoms/_audio_data.py 0000644 00000023047 00000000000 0011211 0 ustar 00 # Written by David Henningsson <david.henningsson@canonical.com> # Copyright Canonical Ltd 2011. # Licensed under GPLv3. import re import os import subprocess from apport.hookutils import * class SoundCard: def init_alsa(self, index, name, longname): self.alsa_index = index self.alsa_shortname = name self.alsa_longname = longname self.jacks = parse_jacks(index) def init_pa(self, pactl_card): self.pa_card = pactl_card self.pa_properties = pactl_card['Properties'] self.pa_longname = self.pa_properties['device.description'] if not 'jacks' in self.__dict__: self.jacks = [] def pretty_name(self): try: return '%s - %s' % (self.pa_longname, self.pa_properties['alsa.card_name']) except: if not 'pa_longname' in self.__dict__: return self.alsa_longname return self.pa_longname def has_sink_or_source(self, ssname): # TODO: Check that this works for bluetooth as well, # and/or implement something more sophisticated try: a, b, ssname = ssname.partition(".") return self.pa_card['Name'].find(ssname) != -1 except: return False def get_controlnames(self, isOutput): if isOutput: return set(["PCM", "Hardware Master", "Master", "Master Front", "Front"]) else: return set(["Capture"]) def get_codecinfo(self): try: s = "" for codecpath in glob.glob('/proc/asound/card%s/codec*' % self.alsa_index): if os.path.isfile(codecpath): s = s + read_file(codecpath) return s except: return "" class Jack: def isOutput(self): if self.jack_type.find("Out") != -1: return True if self.jack_type == "Speaker": return True return False def needed_channelcount(self): if self.jack_type == "Line Out": try: colormap = {'Orange': 3, 'Black': 5, 'Grey': 7} return colormap[self.color] except: pass return 1 def get_controlnames(self): if self.isOutput(): # Do not localize c = set(["PCM", "Hardware Master", "Master", "Master Front", "Front"]) if self.jack_type == "Speaker": c |= set(["Speaker", "Desktop Speaker"]) nc = self.needed_channelcount() if nc >= 3: c |= set(["Center", "LFE", "Surround"]) if nc >= 7: c.add("Side") else: c = set(["Capture", self.jack_type]) if self.jack_type.find("Mic"): c.add(self.jack_type+" Boost") return c def pretty_name(self): # Hmm, this is not going to be easy to localize. jack_type = self.jack_type if jack_type == 'HP Out': jack_type = 'Headphone Out' color = self.color if (color != 'Unknown'): jack_type = '%s %s' % (color, jack_type) if self.jack_conns == 'Fixed': return '%s, Internal' % jack_type if self.connectivity == 'Sep': return '%s, %s, Docking station' % (jack_type, self.location) return '%s, %s' % (jack_type, self.location) def parse_jacks(alsa_card_index): ''' Returns list of jacks on a specific card. ''' result = [] dirname = '/proc/asound/card%d' % int(alsa_card_index) for fname in os.listdir(dirname): if not fname.startswith('codec#'): continue codecname = '' for line in open(os.path.join(dirname,fname)): m = re.search('Codec: (.*)', line) if m: codecname = m.groups(1)[0] m = re.search('Pin Default 0x(.*?): \[(.*?)\] (.*?) at (.*?) (.*)', line) if m: item = Jack() item.codecname = codecname item.hex_value = m.groups(1)[0] item.jack_conns = m.groups(1)[1] item.jack_type = m.groups(1)[2] item.connectivity = m.groups(1)[3] item.location = m.groups(1)[4] if not item.jack_conns == 'N/A': result.append(item) continue m = re.search('Conn = (.*?), Color = (.*)', line) if m: item.connection = m.groups(1)[0] item.color = m.groups(1)[1] return result def parse_alsa_cards(): ''' Returns list of SoundCard as seen by alsa ''' # Parse /proc/asound/cards alsacards = [] try: for card in open('/proc/asound/cards'): m = re.search(' (\d+) \[(\w+)\s*\]: (.+)', card) if not m is None: s = SoundCard() s.init_alsa(*tuple(m.groups(1))) alsacards.append(s) except: pass return alsacards def parse_pactl_list(pactlvalues): ''' Returns a structured version of pactl list ''' # Not perfect, but good enough for its purpose result = dict() for line in pactlvalues.splitlines(): m = re.match('^(\w+) #(\d+)', line) if m: category = m.groups(1)[0] index = int(m.groups(1)[1]) if not category in result: result[category] = dict() curitem = dict() result[category][index] = curitem continue m = re.match('^\t(\w+.*?): (.*)', line) if m: curname = m.groups(1)[0] curvalue = m.groups(1)[1] curitem[curname] = curvalue continue m = re.match('^\t(\w+.*?):', line) if m: curname = m.groups(1)[0] curitem[curname] = dict() continue m = re.match('^\t\t(\w+.*?) = "(.*)"', line) if m: curpropname = m.groups(1)[0] curpropvalue = m.groups(1)[1] curitem[curname][curpropname] = curpropvalue return result def add_pa_cards(cards, pactlvalues): if not 'Card' in pactlvalues: return cards for pa_card in pactlvalues['Card'].values(): s = None try: index = pa_card['Properties']['alsa.card'] for c in cards: if index == c.alsa_index: s = c except: pass if not s: s = SoundCard() cards.append(s) s.init_pa(pa_card) return cards def get_pa_default_profile(isOutput): ''' Returns sinkname,sinkprofile,channelcount ''' pactlstat = command_output(['pactl', 'info']) ss = "Default %s: (.*)" % ("Sink" if isOutput else "Source") for line in pactlstat.splitlines(): m = re.match(ss, line) if m: sink = m.groups(1)[0] sinkname, dummy, sinkprofile = sink.rpartition(".") if dummy == '': sinkname = sinkprofile sinkprofile = "analog-stereo" # easy processing later continue # TODO: handle missing sinkname/sinkprofile match? # calculate channel count a = sinkprofile.split('-') cc = 2 if 'mono' in a: cc = 1 elif 'surround' in a: try: # e g surround-51 => 51 => 5+1 => 6 cc = int(a[len(a)-1]) cc = cc/10 + cc % 10 except: cc = 2 return sinkname, sinkprofile, cc def get_hw_title(card, isOutput, jack, text): a = [] if jack is not None: # Get motherboard/system name try: f = open("/sys/class/dmi/id/product_name") a.append(''.join(f.readlines()).strip()) except: pass a.append(jack.codecname) a.append(jack.pretty_name()) else: try: a.append(card.alsa_longname) except: a.append(card.pretty_name()) a.append("playback" if isOutput else "recording") return "[%s] %s" % (', '.join(a), text) def get_users_in_group(groupname): match = "%s:(.*?):(.*?):(.*)" % groupname for line in open("/etc/group"): m = re.match(match, line) if not m: continue s = m.groups(1)[2] return s.split(',') # group does not exist return [] def pa_start_logging(): command_output(['pacmd','set-log-level','4']) command_output(['pacmd','set-log-time','1']) def pa_finish_logging(report): command_output(['pacmd','set-log-level','1']) command_output(['pacmd','set-log-time','0']) s = recent_syslog(re.compile("pulseaudio")) report['Symptom_PulseAudioLog'] = s return s def run_subprocess(report, reportkey, args): ''' Helper function to run a subprocess. Returns stdout and writes stderr, if any, to the report. ''' # avoid localized strings env = os.environ env['LC_MESSAGES'] = 'C' sub_mon = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, universal_newlines=True) sub_out, sub_err = sub_mon.communicate() if sub_err is not None and (len(str(sub_err).strip()) > 0): report[reportkey+'Stderr'] = ' '.join(sub_err) return sub_out def parse_cards(): cards = parse_alsa_cards() pactlvalues = run_subprocess(dict(), 'PactlList', ['pactl', 'list']) pactl_parsed = parse_pactl_list(pactlvalues) cards = add_pa_cards(cards, pactl_parsed) return cards '''test Main''' if __name__ == '__main__': cards = parse_cards() for c in cards: print(c.pretty_name()) for j in c.jacks: print(j.pretty_name()) symptoms/audio.py 0000644 00000017703 00000000000 0010063 0 ustar 00 # Sound/audio related problem troubleshooter/triager # Written by David Henningsson 2010, david.henningsson@canonical.com # Copyright Canonical Ltd 2010 # License: BSD (see /usr/share/common-licenses/BSD ) description = 'Sound/audio related problems' import apport from apport.hookutils import * import re import os import sys import subprocess sys.path.append('/usr/share/apport/symptoms/') from _audio_data import * from _audio_checks import * def ask_jack_and_card(report, ui): ''' Reports what jack and/or card the user has a problem with Returns (package, card, isOutput, jack) tuple ''' cards = parse_cards() jacks = [] for c in cards: for j in c.jacks: jacks.append((c,j)) # Ask for a specific jack if len(jacks) > 0: choices = ["%s (%s)" % (j.pretty_name(), c.pretty_name()) for c,j in jacks] choices.append("It's not listed here") nr = ui.choice('What hardware are you having a problem with?\n', choices) if nr is None: raise StopIteration nr = nr[0] if (nr < len(jacks)): c,j = jacks[nr] report['Symptom_Card'] = c.pretty_name() report['Symptom_Jack'] = j.pretty_name() return (None, c, j.isOutput(), j) # Okay, not a specific jack, let's ask for sound cards choices = [] interfaces = ['PCI/internal', 'USB', 'Firewire', 'Bluetooth'] for c in cards: choices.append("Playback from %s" % c.pretty_name()) choices.append("Recording from %s" % c.pretty_name()) choices.extend(["%s device not listed here" % i for i in interfaces]) nr = ui.choice("What audio device are you having a problem with?", choices) if nr is None: raise StopIteration nr = nr[0] if int(nr / 2) < len(cards): report['Symptom_Card'] = c.pretty_name() return (None, cards[int(nr/2)], nr % 2 == 0, None) # card not detected by alsa, nor pulse nr = nr - 2 * len(cards) report['Title'] = "%s sound card not detected" % interfaces[nr] if interfaces[nr] == 'Firewire': if not ui.yesno('External firewire cards require manual setup.\n' 'Documentation is here: https://help.ubuntu.com/community/HowToJACKConfiguration\n' 'Would you like to continue reporting a bug anyway?'): raise StopIteration return ('libffado1', None, None, None) return ('alsa-base', None, None, None) def symptom_fails_after_a_while(report, ui, card, isOutput, jack): pa_start_logging() ui.information("Please try to reproduce the problem now. Close this dialog\n" "when the problem has appeared.") pa_finish_logging(report) report['Title'] = get_hw_title(card, isOutput, jack, "fails after a while") return 'alsa-base' def symptom_distortion(report, ui, card, isOutput, jack): check_volumes(report, ui, card, isOutput, jack, 0) p = check_test_tones(report, ui, card, isOutput, jack) report['Title'] = get_hw_title(card, isOutput, jack, "Sound is distorted") return p def symptom_background_noise(report, ui, card, isOutput, jack): check_volumes(report, ui, card, isOutput, jack) p = check_test_tones(report, ui, card, isOutput, jack) report['Title'] = get_hw_title(card, isOutput, jack, "Background noise or low volume") return p def symptom_underrun(report, ui, card, isOutput, jack): pa_start_logging() p = check_test_tones(report, ui, card, isOutput, jack) if p is None: ui.information("Please try to reproduce the problem now. Close this dialog\n" "when the problem has appeared.") pa_finish_logging(report) report['Title'] = get_hw_title(card, isOutput, jack, "Underruns, dropouts or crackling sound") return p def symptom_mixer(report, ui, card, isOutput, jack): pa_start_logging() ui.information("If there is a range of the mixer slider that's particularly\n" "problematic, please place the slider in that range before continuing.\n" "Also describe the problem in the bug report. Thank you!") pa_finish_logging(report) report['Title'] = get_hw_title(card, isOutput, jack, "volume slider problem") return 'alsa-base' def symptom_user(report, ui, card, isOutput, jack): check_audio_users(report, ui) check_devices_in_use(report, ui) report['Title'] = get_hw_title(card, isOutput, None, "sound not working for all users") return 'alsa-base' def symptom_no_sound(report, ui, card, isOutput, jack): check_volumes(report, ui, card, isOutput, jack) check_devices_in_use(report, ui) p = check_test_tones(report, ui, card, isOutput, jack) report['Title'] = get_hw_title(card, isOutput, jack, "No sound at all") return p def symptom_fallback(report, ui, card, isOutput, jack): check_volumes(report, ui, card, isOutput, jack) p = check_test_tones(report, ui, card, isOutput, jack) report['Title'] = get_hw_title(card, isOutput, jack, "Playback problem" if isOutput else "Recording problem") return 'alsa-base' def symptom_noautomute(report, ui, card, isOutput, jack): check_volumes(report, ui, card, isOutput, jack) if jack is not None: ui.information("Now, please make sure the jack is NOT plugged in.\n" "After having done that, close this dialog.\n") report['Symptom_JackUnplugged'] = card.get_codecinfo() ui.information("Now, please plug the jack in.\n" "After having done that, close this dialog.\n") report['Symptom_JackPlugged'] = card.get_codecinfo() report['Title'] = get_hw_title(card, isOutput, jack, "No automute" if isOutput else "No autoswitch") return 'alsa-base' def ask_symptom(report, ui, isOutput): ''' returns a function to call next, or StopIteration ''' dirstr = "output" if isOutput else "input" symptom_map = [ ('No sound at all', symptom_no_sound), ('Only some of %ss are working' % dirstr, symptom_fallback), ('No auto-%s between %ss' % ("mute" if isOutput else "switch", dirstr), symptom_noautomute), ('Volume slider, or mixer problems', symptom_mixer), ('Sound has bad quality (e g crackles, distortion, high noise levels etc)', None), ('Sound works for a while, then breaks', symptom_fails_after_a_while), ('Sound works for some users but not for others', symptom_user), ('None of the above', symptom_fallback)] symptom_badquality_map = [ ('Digital clip or distortion, or "overdriven" sound', symptom_distortion), ('Underruns, dropouts, or "crackling" sound', symptom_underrun), ('High background noise, or volume is too low', symptom_background_noise)] problem = ui.choice('What particular problem do you observe?', [a for a,b in symptom_map]) if problem is None: raise StopIteration desc, func = symptom_map[problem[0]] # subquestion for bad quality sound if func is None: problem = ui.choice('In what way is the sound quality bad?', [a for a,b in symptom_badquality_map]) if problem is None: raise StopIteration desc, func = symptom_badquality_map[problem[0]] report['Symptom_Type'] = desc return func def run(report, ui): # is pulseaudio installed and running? package = check_pulseaudio_running(report, ui) if package is not None: return package # Hardware query (package, card, isOutput, jack) = ask_jack_and_card(report, ui) if package is not None: return package # Check that the pulseaudio profile is correctly set package, channelcount = check_pulseaudio_profile(report, ui, card, isOutput, jack) if package is not None: return package # Symptom query problem_func = ask_symptom(report, ui, isOutput) package = problem_func(report, ui, card, isOutput, jack) if package is not None: return package # Hopefully we don't come here, but if we do, use ALSA as fallback. return 'alsa-base' symptoms/display.py 0000644 00000006654 00000000000 0010432 0 ustar 00 # Display (X.org) related problems # Author: Bryce Harrington <bryce@canonical.com> # (C) 2009 Canonical Ltd. # License: GPL v2 or later. from glob import glob import subprocess import apport.hookutils import time import os description = 'Display (X.org)' def problem_boot_fail(report, ui): prompt = "What type of boot failure do you experience?" problems = [ {'desc':"Go back", 'package':None}, {'desc':"Black or blank screen", 'tags':'blank', 'info':"See http://wiki.ubuntu.com/X/Troubleshooting/BlankScreen for help working around and debugging blank screen on boot issues.", 'package':report['VideoDriver']}, {'desc':"White screen after logging in", 'info':"White screens generally indicate a failure initializing Compiz or kwin; thus, as a workaround try disabiling Desktop Effects", 'package':'mesa'}, {'desc':"Solid brown or other color", 'tags':'freeze', 'package':report['VideoDriver']}, {'desc':"Garbage or corruption on screen", 'tags':'freeze corruption', 'info':"If possible, please also take a photo of the screen and attach to the bug report. Sometimes the style of corruption can give clues as to where in memory the error has occurred.", 'package':report['VideoDriver']}, ] choice = [] for item in problems: choices.append(item['desc']) choice = ui.choice(prompt, choices)[0] if choice is None: raise StopIteration return problem[choice]['package'] def run(report, ui): prompt = 'What display problem do you observe?' choice = ui.choice(prompt, ['I don\'t know', 'Freezes or hangs during boot or usage', 'Crashes or restarts back to login screen', 'Resolution is incorrect', 'Shows screen corruption', 'Performance is worse than expected', 'Fonts are the wrong size', 'Other display-related problem', ]) if choice is None or choice == [0]: raise StopIteration choice = choice[0] report.setdefault('Tags', '') # Tag kubuntu bugs if subprocess.call(['pgrep', '-u', str(os.getuid()), '-x', 'ksmserver']) == 0: report['Tags'] += ' kubuntu' # Process problems if choice == 1: ui.information('To debug X freezes, please see https://wiki.ubuntu.com/X/Troubleshooting/Freeze') report['Tags'] += ' freeze' report['Title'] = 'Xorg freeze' return 'xorg' if choice == 2: ui.information('Please reproduce the crash and collect a backtrace. See https://wiki.ubuntu.com/X/Backtracing for directions.') report['Tags'] += ' crash' report['Title'] = 'Xorg crash' # TODO: Look in /var/crash and let user select a crash file, and then parse it return 'xorg' if choice == 3: report['Tags'] += ' resolution' return 'xorg' if choice == 4: ui.information('Please take a photo of the screen showing the corruption and attach to the bug report') report['Tags'] += ' corruption' # TODO: Let user upload the file return 'xorg' if choice == 5: report['Tags'] += ' performance' return 'xorg' if choice == 6: report['Tags'] += ' fonts' return 'xorg' if choice == 7: return 'xorg' ui.information('Please run "ubuntu-bug <packagename>" to report this bug') return None symptoms/security.py 0000644 00000006142 00000000000 0010624 0 ustar 00 # Security related problems # Author: Marc Deslauriers <marc.deslauriers@ubuntu.com> # (C) 2010 Canonical Ltd. # License: GPL v3 or later. from glob import glob import subprocess import apport.hookutils import time import os description = 'Security related problems' def run(report, ui): problem = ui.choice('What particular problem do you observe?', ['I can see my password as I type when using ssh and sudo', 'The root account is disabled by default', 'I am not prompted for my password when I run sudo a second time', 'I am not prompted for a password when booting to rescue mode', 'Other users can access files in my home directory', 'My screen isn\'t locked when I come out of suspend or hibernate', 'My screen doesn\'t lock automatically after being idle', 'Other screen locking issue', 'Other problem', ]) if problem is None: raise StopIteration problem = problem[0] if problem == 0: ui.information('This is expected as there is no "tty" allocated when running commands directly via ssh. Adding the "-t" flag will allocate a tty and prevent sudo from echoing the password.\n\nFor more information, please see:\nhttps://wiki.ubuntu.com/SecurityTeam/FAQ#SSH') raise StopIteration if problem == 1: ui.information('By default, the root account is disabled in Ubuntu and use of the "sudo" command is recommended.\n\nFor more information, please see:\nhttps://help.ubuntu.com/community/RootSudo') raise StopIteration if problem == 2: ui.information('Sudo is designed to keep a "ticket" valid for 15 minutes after you use your password for the first time. This is configurable.\n\nFor more information, please see:\nhttps://wiki.ubuntu.com/SecurityTeam/FAQ#Sudo') raise StopIteration if problem == 3: ui.information('By default, the root account is disabled in Ubuntu and use of the "sudo" command is recommended. Since the root account is disabled, it\'s not possible to prompt for the root password when entering single user mode.\n\nFor more information, please see:\nhttps://wiki.ubuntu.com/SecurityTeam/FAQ#Rescue%20Mode') raise StopIteration if problem == 4: ui.information('By default, Ubuntu is designed to allow users to easily share files and help each other. To support this, each user\'s default home directory is readable by all other users.\n\nFor more information, please see:\nhttps://wiki.ubuntu.com/SecurityTeam/Policies#Permissive%20Home%20Directory%20Access') raise StopIteration if problem == 5: report['Title'] = 'Screen not locked when coming out of suspend/hibernate' return 'gnome-screensaver' if problem == 6: report['Title'] = 'Screen not locked after inactivity' return 'gnome-screensaver' if problem == 7: report['Title'] = 'Screen locking issue' return 'gnome-screensaver' if problem == 8: ui.information('Please use "ubuntu-bug <packagename>" to report a bug against the particular package') raise StopIteration assert False, 'not reached' etc/default/apport 0000644 00000000225 00000000000 0010133 0 ustar 00 # set this to 0 to disable apport, or to 1 to enable it # you can temporarily override this with # sudo service apport start force_start=1 enabled=1 etc/logrotate.d/apport 0000644 00000000176 00000000000 0010736 0 ustar 00 /var/log/apport.log { daily rotate 7 delaycompress compress notifempty missingok } usr/lib/byobu/apport 0000755 00000003007 00000000000 0010437 0 ustar 00 #!/bin/sh -e # # apport: note if there are crash dumps available for apporting # # Copyright (C) 2009 Canonical Ltd. # Copyright (C) 2011-2014 Dustin Kirkland # # Authors: Dustin Kirkland <kirkland@byobu.org> # # 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, version 3 of the License. # # 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 <http://www.gnu.org/licenses/>. __apport_detail() { for i in /var/crash/*.crash; do if [ -f "$i" ]; then printf "\nTo file bugs on the existing crash reports, run:\n" which apport-cli >/dev/null || printf " sudo apt-get install apport\n" for i in /var/crash/*.crash; do printf " apport-cli $i\n" done printf "\nTo clear the pending reports:\n" printf " rm -f /var/crash/*.crash\n\n" return fi done printf "No pending crash reports\n" } __apport() { # Print {!} if a /var/crash/*.crash file exists for i in /var/crash/*.crash; do if [ -f "$i" ]; then color y k; printf "{!}"; color -- return fi done rm -f "$BYOBU_RUN_DIR/status.$BYOBU_BACKEND/apport"* } # vi: syntax=sh ts=4 noexpandtab
| ver. 1.4 |
Github
|
.
| PHP 8.3.30 | Generation time: 2.39 |
proxy
|
phpinfo
|
Settings