qubes-bug-report 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #!/usr/bin/env python3
  2. import subprocess
  3. import argparse
  4. import time
  5. import sys
  6. import os
  7. from os.path import expanduser
  8. #the term qube refers to a qubes vm
  9. def is_program_installed_in_qube( program, qube_name ):
  10. is_installed = True
  11. try:
  12. command = 'command -v ' + program
  13. subprocess.check_call([ 'qvm-run', '--no-color-output', qube_name, command ], stdout = open( os.devnull, 'w' ) )
  14. except subprocess.CalledProcessError:
  15. is_installed = False
  16. return is_installed
  17. #this function requires virsh
  18. #domstate only works for Xen domU (guests)
  19. def is_qube_running( qube_name ):
  20. runs = False
  21. out = subprocess.check_output([ "virsh", "-c", "xen:///", "domstate", qube_name ])
  22. out = out.decode('utf-8').replace('\n', '')
  23. if 'running' == out:
  24. runs = True
  25. return runs
  26. def get_qube_packages( qube_name ):
  27. content = "## Qubes Packages\n\n"
  28. #a qube can have more than one package manager installed (only one is functional)
  29. pkg_cmd = { 'dpkg' : 'dpkg -l qubes-*', 'pacman' : 'pacman -Qs qubes', 'rpm' : 'rpm -qa qubes-*' }
  30. if is_qube_running( qube_name ):
  31. for package_manager in pkg_cmd.keys():
  32. if is_program_installed_in_qube( package_manager, qube_name ):
  33. pkg_list_cmd = pkg_cmd[package_manager]
  34. try:
  35. out = subprocess.check_output([ 'qvm-run', qube_name, '--pass-io', '--no-color-output', pkg_list_cmd ], stderr = open( os.devnull, 'w' ) )
  36. out = out.decode('utf-8')
  37. content += create_heading( ( "Package Manager: " + package_manager ), 3 )
  38. content += wrap_code( out )
  39. except subprocess.CalledProcessError:
  40. pass #do nothing
  41. else:
  42. content += "**No packages listed, because Qube " + qube_name + " was not running**\n\n"
  43. return content
  44. def get_dom0_packages():
  45. content = create_heading( "Dom0 Packages", 2 )
  46. out = subprocess.check_output([ "rpm", "-qa", "qubes-*" ])
  47. out = out.decode('utf-8')
  48. content += wrap_code( out )
  49. return content
  50. def wrap_code( text ):
  51. code = "~~~\n" + text + "~~~\n\n"
  52. return code
  53. def create_heading( heading, level ):
  54. heading = heading + "\n\n"
  55. if 1 == level:
  56. heading = "# " + heading
  57. elif 2 == level:
  58. heading = "## " + heading
  59. else:
  60. heading = "### " + heading
  61. return heading
  62. def get_log_file_content( qube_name ):
  63. content = "## Log Files\n\n"
  64. qubes_os_log = "/var/log/qubes/"
  65. ext = ".log"
  66. log_prefix = [ "guid", "pacat", "qubesdb", "qrexec" ]
  67. #constructs for each log file prefix the full path and reads the log file
  68. for prefix in log_prefix:
  69. log_file = prefix + "." + qube_name + ext
  70. content += create_heading( ( "Log File: " + log_file ), 3 )
  71. content += wrap_code( get_log_file( qubes_os_log + log_file ) )
  72. return content
  73. def get_qube_prefs( qube_name ):
  74. qube_prefs = subprocess.check_output([ "qvm-prefs", qube_name ])
  75. qube_prefs = qube_prefs.decode('utf-8')
  76. content = create_heading( "Qube Prefs", 2 )
  77. content += wrap_code( qube_prefs )
  78. return content
  79. def report( qube_name ):
  80. template = '''{title}
  81. {content}
  82. '''
  83. title_text = create_heading( "Bug report: " + qube_name, 1 )
  84. content_text = get_qube_prefs( qube_name )
  85. content_text += get_dom0_packages()
  86. content_text += get_log_file_content( qube_name )
  87. content_text += get_qube_packages( qube_name )
  88. report = template.format( title=title_text, content=content_text )
  89. return report
  90. def write_report( report_content, file_path ):
  91. with open( file_path, 'w' ) as report_file:
  92. report_file.write( report_content )
  93. def send_report( dest_qube, file_path):
  94. #if dest_qube is not running -> start dest_qube
  95. if not is_qube_running( dest_qube ):
  96. try:
  97. subprocess.check_call([ "qvm-start", dest_qube ])
  98. except subprocess.CalledProcessError:
  99. print( "Error while starting: " + dest_qube, file = sys.stderr )
  100. try:
  101. subprocess.check_call([ "qvm-move-to-vm", dest_qube, file_path ])
  102. except subprocess.calledProcessError:
  103. print( "Moving file bug-report failed", file = sys.stderr )
  104. def get_log_file( log_file ):
  105. data = ""
  106. #open and close the file
  107. with open( log_file ) as log:
  108. data = log.read()
  109. return data
  110. def qube_exist( qube_name ):
  111. exists = True
  112. try:
  113. #calls: qvm-check --quiet vmanme
  114. subprocess.check_call([ "qvm-check", "--quiet", qube_name ])
  115. except subprocess.CalledProcessError:
  116. exists = False
  117. return exists
  118. def get_report_file_path( qube_name ):
  119. #exapanduser -> works corss platform
  120. home_dir = expanduser("~")
  121. date = time.strftime("%H%M%S")
  122. file_path = home_dir + "/" + qube_name + "_bug-report_" + date + ".md"
  123. return file_path
  124. def main():
  125. parser = argparse.ArgumentParser( description = 'Generates a bug report for a specific qube (Qubes VM)' )
  126. parser.add_argument( 'vmname', metavar = '<vmanme>', type = str )
  127. parser.add_argument( '-d', '--dest-vm', metavar = '<dest-vm>', dest = "destvm", type = str, default = 'dom0', help = "send the report to the destination VM" )
  128. parser.add_argument( '-p', '--print-report', action = 'store_const', const = "print_report", required = False, help = "prints the report without writing it or sending it to a destination VM" )
  129. args = parser.parse_args()
  130. if qube_exist( args.vmname ):
  131. if qube_exist( args.destvm ):
  132. #get the report
  133. report_content = report( args.vmname )
  134. #if -p or --print-report is an argument print the report
  135. if args.print_report:
  136. print( report_content )
  137. #write and send the report
  138. else:
  139. file_path = get_report_file_path( args.vmname )
  140. write_report( report_content, file_path )
  141. print( "Report written to: " + file_path )
  142. if 'dom0' != args.destvm:
  143. send_report( args.destvm, file_path )
  144. print( "Report send to VM: " + args.destvm )
  145. exit(0)
  146. else:
  147. print ( "Destination VM does not exist" )
  148. exit(1)
  149. else:
  150. print( "VM does not exist" )
  151. exit(1)
  152. #calls the main function -> program start point
  153. main()