Advertisement
Guest User

Untitled

a guest
Sep 7th, 2016
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.35 KB | None | 0 0
  1.  
  2. import redis
  3. from daemon import DaemonContext
  4. import pprint
  5. import mysql.connector
  6. import json
  7. import base64
  8. import zlib
  9. import io
  10. import struct
  11. import logging
  12. import re
  13. import os
  14. import sys
  15.  
  16.  
  17. file_root = '/opt/crash-handler/files'
  18.  
  19. def read_compressed_file(data):
  20. current_file_index = struct.unpack("i", data.read(4))[0]
  21. #print(current_file_index)
  22. filename_len = struct.unpack("i", data.read(4))[0]
  23. #print(filename_len)
  24. fmt = "{}s".format(filename_len)
  25. filename = struct.unpack(fmt, data.read(filename_len))[0].split(b'\0', 1)[0]
  26. #print(filename)
  27. contents_len = struct.unpack("i", data.read(4))[0]
  28. #print(contents_len)
  29. fmt = "{}s".format(contents_len)
  30. contents = struct.unpack(fmt, data.read(contents_len))[0]
  31.  
  32. #if filename[-3:] in ['wer', 'log', 'xml', 'txt']:
  33. # print(">>>> {}".format(filename))
  34. # print(contents)
  35. # print("<<<<")
  36.  
  37. #print("read {}".format(filename))
  38.  
  39. file = {
  40. 'filename' : filename,
  41. 'contents' : contents,
  42. }
  43. return file
  44.  
  45. def read_uploaded_files(compressed_data):
  46. uncompressed_data = zlib.decompress(compressed_data)
  47. print("decompressed {} to {}".format(human_bytes(len(compressed_data)), human_bytes(len(uncompressed_data))))
  48.  
  49. files = []
  50. data = io.BytesIO(uncompressed_data)
  51. while data.tell() < len(uncompressed_data):
  52. files.extend([read_compressed_file(data)])
  53.  
  54. return files
  55.  
  56. def human_bytes(num, suffix='B'):
  57. for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
  58. if abs(num) < 1024.0:
  59. return "%3.1f%s%s" % (num, unit, suffix)
  60. num /= 1024.0
  61. return "%.1f%s%s" % (num, 'Yi', suffix)
  62.  
  63. def on_crash_created(raw_data):
  64. try:
  65. report = json.loads(raw_data)
  66. # make sure id is clean by only keeping hexadecimal characters
  67. report['uuid'] = re.sub(r'[^0-9a-f]', '', report['id'])
  68. report['id'] = None # we call it uuid
  69. decoded_data = base64.b64decode(report['payload'])
  70. files = read_uploaded_files(decoded_data)
  71. if create_report(report):
  72. for file in files:
  73. save_file(file, report['uuid'])
  74. update_derived_data(report['uuid'])
  75. except Exception, e:
  76. print(str(e))
  77. pass
  78.  
  79. def report_fields():
  80. return [
  81. 'uuid',
  82. 'reported_at',
  83. 'reported_by',
  84. 'user',
  85. 'computer',
  86. 'source_file',
  87. 'source_context',
  88. 'callstack',
  89. 'game_version',
  90. ]
  91.  
  92. def create_report(report):
  93. try:
  94. conn = mysql.connector.connect(user='root',
  95. password='supersecret',
  96. host='localhost',
  97. database='crash',
  98. connection_timeout=5)
  99. cursor = conn.cursor()
  100. fields = filter(lambda x : x in report, report_fields())
  101. query = ("INSERT INTO reports "
  102. "({}) "
  103. "VALUES ({})".format(
  104. ", ".join(fields),
  105. ", ".join(map(lambda x: "%({})s".format(x), fields))))
  106. print(query)
  107. cursor.execute(query, report)
  108. conn.commit()
  109. cursor.close()
  110. conn.close()
  111.  
  112. print("report saved")
  113. return True
  114. except Exception, e:
  115. logging.exception('report not saved')
  116. if cursor:
  117. print(cursor.statement or 'no statement')
  118. return False
  119.  
  120. def save_report(report):
  121. try:
  122. conn = mysql.connector.connect(user='crash',
  123. password='supersecret',
  124. host='localhost',
  125. database='crash',
  126. connection_timeout=5)
  127. cursor = conn.cursor()
  128. fields = report_fields()
  129. query = ("UPDATE reports "
  130. "SET {} "
  131. "WHERE uuid = %(uuid)s "
  132. "LIMIT 1".format(
  133. ", ".join(map(lambda x: "{0}=%({0})s".format(x), fields))))
  134. cursor.execute(query, report)
  135. conn.commit()
  136. cursor.close()
  137. conn.close()
  138.  
  139. print("report saved")
  140. return True
  141. except Exception, e:
  142. logging.exception('report not saved')
  143. if cursor:
  144. print(cursor.statement or 'no statement')
  145. return False
  146.  
  147. def save_file_to_storage(file, report_uuid):
  148. root = file_root
  149. d = os.path.join(root, report_uuid)
  150. if not os.path.exists(d):
  151. os.mkdir(d)
  152. print("dir is {}".format(d))
  153. filepath = os.path.join(d, file['filename'])
  154. if not os.path.realpath(filepath).startswith(d):
  155. raise Exception("Path {} is not in directory {}, will not continue".format(filepath, d))
  156. with open(filepath, 'wb') as f:
  157. f.write(file['contents'])
  158. print("wrote {} to {}".format(human_bytes(len(file['filename'])), os.path.realpath(filepath)))
  159.  
  160. def load_file_from_storage(file, report_uuid):
  161. root = file_root
  162. filepath = os.path.join(root, report_uuid, file['filename'])
  163. if not os.path.exists(filepath):
  164. return None
  165. with open(filepath, 'rb') as f:
  166. file['contents'] = f.read()
  167. return file
  168.  
  169. def save_file(file, report_uuid):
  170. try:
  171. save_file_to_storage(file, report_uuid)
  172.  
  173. conn = mysql.connector.connect(user='crash', password='supersecret', host='localhost', database='crash')
  174. cursor = conn.cursor()
  175. existing_id = get_file_id(file, report_uuid)
  176. if not existing_id:
  177. query = ("INSERT INTO files "
  178. "(report_uuid, filename, size) "
  179. "VALUES (%(report_uuid)s, %(filename)s, %(size)s)")
  180. data = {
  181. 'report_uuid': report_uuid,
  182. 'filename': file['filename'],
  183. 'size': len(file['contents']),
  184. }
  185. cursor.execute(query, data)
  186. conn.commit()
  187. else:
  188. # file entry already exists, update it
  189. query = ("UPDATE files "
  190. "SET size = %(size)s "
  191. "WHERE id = %(id)s LIMIT 1")
  192. data = {
  193. 'size': len(file['contents']),
  194. 'id': existing_id,
  195. }
  196. cursor.execute(query, data)
  197. conn.commit()
  198. cursor.close()
  199. conn.close()
  200.  
  201. print("file {} saved".format(file['filename']))
  202. return True
  203. except Exception, e:
  204. logging.exception('file not saved')
  205. if cursor:
  206. print(cursor.statement or 'no statement')
  207. return False
  208.  
  209. def get_file_id(file, report_uuid):
  210. """returns row id if already exists in db"""
  211. try:
  212. conn = mysql.connector.connect(user='crash', password='supersecret', host='localhost', database='crash')
  213. cursor = conn.cursor()
  214. query = ("SELECT id FROM files "
  215. "WHERE report_uuid = %(report_uuid)s AND filename = %(filename)s")
  216. data = {
  217. 'report_uuid': report_uuid,
  218. 'filename': file['filename'],
  219. }
  220. cursor.execute(query, data)
  221. found_id = None
  222. row = cursor.fetchone()
  223. if row:
  224. found_id = row[0]
  225. print("{} for {} exists as id {}".format(file['filename'], report_uuid, found_id))
  226. cursor.close()
  227. conn.close()
  228. return found_id
  229. except Exception, e:
  230. logging.exception('unable to find file in db')
  231. return None
  232.  
  233. def get_all_report_ids():
  234. conn = mysql.connector.connect(user='crash', password='supersecret', host='localhost', database='crash')
  235. cursor = conn.cursor()
  236. ids = []
  237. try:
  238. query = ("SELECT uuid FROM reports "
  239. "ORDER BY reported_at DESC")
  240. cursor.execute(query)
  241. for (uuid,) in cursor:
  242. ids.extend([uuid])
  243. except Exception, e:
  244. logging.exception(e)
  245. finally:
  246. cursor.close()
  247. conn.close()
  248. return ids
  249.  
  250. def get_report_by_id(report_uuid):
  251. conn = mysql.connector.connect(user='crash', password='supersecret', host='localhost', database='crash')
  252. cursor = conn.cursor()
  253. report = None
  254. try:
  255. query = ("SELECT uuid, reported_by, reported_at, user, computer, source_file, source_context, callstack FROM reports "
  256. "WHERE uuid = %(uuid)s LIMIT 1")
  257. data = {'uuid': report_uuid}
  258. cursor.execute(query, data)
  259. row = cursor.fetchone()
  260. if row:
  261. (uuid, reported_by, reported_at, user, computer, source_file, source_context, callstack) = row
  262. report = {
  263. 'uuid': uuid,
  264. 'reported_by': reported_by,
  265. 'reported_at': reported_at,
  266. 'user': user,
  267. 'computer': computer,
  268. 'source_file': source_file,
  269. 'source_context': source_context,
  270. 'callstack': callstack,
  271. }
  272. except Exception, e:
  273. logging.exception(e)
  274. finally:
  275. cursor.close()
  276. conn.close()
  277. return report
  278.  
  279. def get_filenames(report_uuid):
  280. conn = mysql.connector.connect(user='crash', password='supersecret', host='localhost', database='crash')
  281. cursor = conn.cursor()
  282. filenames = []
  283. try:
  284. query = ("SELECT filename FROM files "
  285. "WHERE report_uuid = %(report_uuid)s ")
  286. data = {
  287. 'report_uuid': report_uuid
  288. }
  289. cursor.execute(query, data)
  290. for (filename,) in cursor:
  291. filenames.extend([filename])
  292. except Exception, e:
  293. logging.exception(e)
  294. finally:
  295. cursor.close()
  296. conn.close()
  297. return filenames
  298.  
  299. def get_file_by_name(report_uuid, filename):
  300. filenames = get_filenames(report_uuid)
  301. for name in filenames:
  302. if name == filename:
  303. file = {'filename': name}
  304. file = load_file_from_storage(file, report_uuid)
  305. if file:
  306. return file['contents']
  307. else:
  308. return None
  309. return None
  310.  
  311. def get_first_match_from_file(report_uuid, filename, regex, flags=re.MULTILINE):
  312. file = get_file_by_name(report_uuid, filename)
  313. if file:
  314. m = re.search(regex, file, flags)
  315. if m and m.group(1):
  316. return m.group(1).strip()
  317. return None
  318.  
  319. def get_dd_game_version(report_uuid):
  320. return get_first_match_from_file(report_uuid, 'Diagnostics.txt', r'^Game version (.*)$')
  321.  
  322. def get_dd_user(report_uuid):
  323. return get_first_match_from_file(report_uuid, 'MyGame.log', r'.*LogInit: User: ([\w\\]*)')
  324.  
  325. def get_dd_computer(report_uuid):
  326. return get_first_match_from_file(report_uuid, 'MyGame.log', r'.*LogInit: Computer: ([\w\\]*)')
  327.  
  328. def get_dd_source_file(report_uuid):
  329. return get_first_match_from_file(report_uuid, 'Diagnostics.txt', r'^Source context from "(.*)"')
  330.  
  331. def get_dd_source_context(report_uuid):
  332. return get_first_match_from_file(report_uuid, 'Diagnostics.txt', r'<SOURCE START>(.*)<SOURCE END>', re.MULTILINE|re.DOTALL)
  333.  
  334. def get_dd_callstack(report_uuid):
  335. return get_first_match_from_file(report_uuid, 'Diagnostics.txt', r'<CALLSTACK START>(.*)<CALLSTACK END>', re.MULTILINE|re.DOTALL)
  336.  
  337. def update_derived_data(report_uuid):
  338. """updates data saved in db but read from files, can be run again to refresh"""
  339. report = get_report_by_id(report_uuid)
  340. if not report:
  341. raise Exception("Unable to update derived data for uuid {}, report not found".format(report_uuid))
  342. report['game_version'] = get_dd_game_version(report_uuid)
  343. report['user'] = get_dd_user(report_uuid)
  344. report['computer'] = get_dd_computer(report_uuid)
  345. report['source_file'] = get_dd_source_file(report_uuid)
  346. report['source_context'] = get_dd_source_context(report_uuid)
  347. report['callstack'] = get_dd_callstack(report_uuid)
  348. save_report(report)
  349.  
  350. def update_all_derived_data():
  351. ids = get_all_report_ids()
  352. for id in ids:
  353. update_derived_data(id)
  354.  
  355. def main():
  356. r = redis.Redis(host='redis.example.com')
  357. pubsub = r.pubsub()
  358. try:
  359. pubsub.subscribe(['crash.created'])
  360. for item in pubsub.listen():
  361. if item['type'] == 'message':
  362. print("data size: {}".format(human_bytes(len(item['data']))))
  363. on_crash_created(item['data'])
  364. finally:
  365. pubsub.close()
  366.  
  367. if __name__ == '__main__':
  368. #with DaemonContext():
  369. if len(sys.argv) > 1:
  370. cmd = sys.argv[1]
  371. if cmd == 'update_all_derived_data':
  372. update_all_derived_data()
  373. else:
  374. print('unknown command {}'.format(cmd))
  375. else:
  376. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement