blob: 88368d4885a18c8534d9cddeda2333ee129d35a9 [file] [log] [blame]
Dennis Kempin351024d2013-02-06 11:18:21 -08001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from gzip import GzipFile
6from StringIO import StringIO
7import base64
8import bz2
9import cookielib
10import getpass
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -040011import imghdr
Dennis Kempin351024d2013-02-06 11:18:21 -080012import os
13import os.path
14import re
15import subprocess
16import sys
17import tarfile
18import tempfile
19import urllib2
Dennis Kempinecd9a0c2013-03-27 16:03:46 -070020import zipfile
Dennis Kempin351024d2013-02-06 11:18:21 -080021
22# path to current script directory
23script_dir = os.path.dirname(os.path.realpath(__file__))
24
25# path to the cookies file used for storing the login cookies.
Dennis Kempin136b7322013-02-06 13:20:57 -080026cookies_file = os.path.join(script_dir, "mtedit.cookies")
Dennis Kempin351024d2013-02-06 11:18:21 -080027
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -070028log_cache_dir = os.path.join(script_dir, "reports")
Dennis Kempin351024d2013-02-06 11:18:21 -080029
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -040030# path for temporary screenshot
31screenshot_filepath = "extension/screenshot.jpg"
32
Dennis Kempin351024d2013-02-06 11:18:21 -080033def Log(source, options):
34 """
35 Returns a log object containg activity, evdev and system logs.
36 source can be either an ip address to a device from which to
37 download, a url pointing to a feedback report to pull or a filename.
38 """
39 if os.path.exists(source):
Dennis Kempin91e96df2013-03-29 14:21:42 -070040 if source.endswith(".zip") or source.endswith(".bz2"):
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040041 return FeedbackLog(source, options)
Dennis Kempin351024d2013-02-06 11:18:21 -080042 return FileLog(source, options)
43 else:
44 match = re.search("Report/([0-9]+)", source)
45 if match:
46 return FeedbackLog(match.group(1), options)
47 else:
48 # todo add regex and error message
49 return DeviceLog(source, options)
50
51
52class AbstractLog(object):
53 """
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040054 A log file consists of the activity log, the evdev log, a system log, and
55 possibly a screenshot, which can be saved to disk all together.
Dennis Kempin351024d2013-02-06 11:18:21 -080056 """
57 def SaveAs(self, filename):
58 open(filename, "w").write(self.activity)
59 if self.evdev:
60 open(filename + ".evdev", "w").write(self.evdev)
61 if self.system:
62 open(filename + ".system", "w").write(self.system)
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040063 if self.image:
64 open(filename + ".jpg", "w").write(self.image)
65
66 def CleanUp(self):
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -040067 if os.path.exists(screenshot_filepath):
68 os.remove(screenshot_filepath)
Dennis Kempin351024d2013-02-06 11:18:21 -080069
70
71class FileLog(AbstractLog):
72 """
73 Loads log from file. Does not contain evdev or system logs.
74 """
75 def __init__(self, filename, options):
76 self.activity = open(filename).read()
77 self.evdev = ""
78 self.system = ""
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040079 self.image = None
Dennis Kempin7b783272013-04-01 15:06:35 -070080 if options.evdev:
81 self.evdev = open(options.evdev).read()
82 elif os.path.exists(filename + ".evdev"):
Dennis Kempinfa6597c2013-02-20 14:05:19 -080083 self.evdev = open(filename + ".evdev").read()
84 if os.path.exists(filename + ".system"):
85 self.system = open(filename + ".system").read()
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040086 if os.path.exists(filename + ".jpg"):
87 self.image = open(filename + ".jpg").read()
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -040088 file(screenshot_filepath, "w").write(self.image)
Dennis Kempin351024d2013-02-06 11:18:21 -080089
Dennis Kempinb049d542013-06-13 13:55:18 -070090class FeedbackDownloader():
91 def __init__(self):
92 # setup cookies file and url opener
93 self.cookies = cookielib.MozillaCookieJar(cookies_file)
94 if os.path.exists(cookies_file):
95 self.cookies.load()
96 cookie_processor = urllib2.HTTPCookieProcessor(self.cookies)
97 self.opener = urllib2.build_opener(cookie_processor)
98 self._Login()
99
100 def _Login(self):
101 username = getpass.getuser()
102
103 # check if already logged in
104 url = "https://login.corp.google.com/"
105 data = self.opener.open(url).read()
106 if username in data:
107 return
108
109 # ask for credentials
110 print "Login to corp.google.com:"
111 password = getpass.getpass()
112 sys.stdout.write("OTP: ")
113 otp = sys.stdin.readline().strip()
114
115 # execute login by posting userdata to login.corp.google.com
116 url = "https://login.corp.google.com/login?ssoformat=CORP_SSO"
117 data = "u=%s&pw=%s&otp=%s" % (username, password, otp)
118 result = self.opener.open(url, data).read()
119
120 # check if the result displays error
121 if result.find("error") >= 0:
122 print "Login failed"
123 exit(-1)
124
125 # login was successful. save cookies to file to be reused later.
126 self.cookies.save()
127
128 def DownloadFile(self, url):
129 try:
130 return self.opener.open(url).read()
131 except urllib2.URLError:
132 return None
133
134 def DownloadSystemLog(self, id):
135 # First download the report.zip file
136 logfile = "report-%s-system_logs.zip" % id
137 url = ("https://feedback.corp.googleusercontent.com/binarydata/" +
138 "%s?id=%s&logIndex=0") % (logfile, id)
139 report = self.DownloadFile(url)
140 if not report or (report[0:2] != "BZ" and report[0:2] != "PK"):
141 print "Report does not include log files at %s" % url
142 return None
143 return report
144
145 def DownloadScreenshot(self, id):
146 url = "https://feedback.corp.googleusercontent.com/screenshot?id=%s" % id
147 image = self.DownloadFile(url)
148 if not image or imghdr.what("", image) != 'jpeg':
149 print "No screenshots available at %s" % url
150 return None
151 return image
Dennis Kempin351024d2013-02-06 11:18:21 -0800152
153class FeedbackLog(AbstractLog):
154 """
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400155 Downloads logs and (possibly) screenshot from a feedback id or file name
Dennis Kempin351024d2013-02-06 11:18:21 -0800156 """
Dennis Kempinb049d542013-06-13 13:55:18 -0700157 def __init__(self, id_or_filename, options=None, force_latest=None):
WeiNan-Peter, Wene539a182013-05-16 15:31:21 -0400158 self.image = None
Dennis Kempinb049d542013-06-13 13:55:18 -0700159 self.force_latest = force_latest
160 self.try_screenshot = options and options.screenshot
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400161
Dennis Kempin91e96df2013-03-29 14:21:42 -0700162 if id_or_filename.endswith(".zip") or id_or_filename.endswith(".bz2"):
163 self.report = open(id_or_filename).read()
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400164 screenshot_filename = id_or_filename[:-4] + '.jpg'
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -0700165 try:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400166 self.image = open(screenshot_filename).read()
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -0700167 except IOError:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400168 # No corresponding screenshot available.
169 pass
170 else:
Dennis Kempinb049d542013-06-13 13:55:18 -0700171 self.downloader = FeedbackDownloader()
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400172 def GetAndUpdateCachedFile(cached_filename, downloader):
173 try:
174 return open(cached_filename).read()
175 except IOError:
Dennis Kempinb049d542013-06-13 13:55:18 -0700176 data = downloader(id_or_filename)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400177 # Cache the data on disk
178 if not os.path.exists(log_cache_dir):
179 os.makedirs(log_cache_dir)
180 file(cached_filename, "w").write(data)
181 return data
182 self.report = GetAndUpdateCachedFile(
183 os.path.join(log_cache_dir, id_or_filename + ".zip"),
Dennis Kempinb049d542013-06-13 13:55:18 -0700184 self.downloader.DownloadSystemLog)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400185 self._ExtractSystemLog()
186 self._ExtractLogFiles()
187 if self.try_screenshot:
188 self.image = GetAndUpdateCachedFile(
189 os.path.join(log_cache_dir, id_or_filename + ".jpg"),
Dennis Kempinb049d542013-06-13 13:55:18 -0700190 self.downloader.DownloadScreenshot)
Dennis Kempin351024d2013-02-06 11:18:21 -0800191
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400192 # Only write to screenshot.jpg if we will be viewing the screenshot
Dennis Kempinb049d542013-06-13 13:55:18 -0700193 if options and not options.download and self.image:
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -0400194 file(screenshot_filepath, "w").write(self.image)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400195
Dennis Kempin351024d2013-02-06 11:18:21 -0800196
197 def _GetLatestFile(self, match, tar):
198 # find file name containing match with latest timestamp
199 names = filter(lambda n: n.find(match) >= 0, tar.getnames())
200 names.sort()
Dennis Kempin765ad942013-03-26 13:58:46 -0700201 if not names:
202 print "Cannot find files named %s in tar file" % match
203 print "Tar file contents:", tar.getnames()
204 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800205 return tar.extractfile(names[-1])
206
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400207
Dennis Kempin91e96df2013-03-29 14:21:42 -0700208 def _ExtractSystemLog(self):
209 if self.report[0:2] == "BZ":
210 self.system = bz2.decompress(self.report)
211 elif self.report[0:2] == "PK":
212 io = StringIO(self.report)
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700213 zip = zipfile.ZipFile(io, "r")
214 self.system = zip.read(zip.namelist()[0])
215 zip.extractall("./")
216 else:
217 print "Cannot download logs file"
218 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800219
220 def _ExtractLogFiles(self):
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400221 # Find embedded and uuencoded activity.tar in system log
222
223 def ExtractByInterface(interface):
224 assert interface == 'pad' or interface == 'screen'
225
226 log_index = [
227 (("hack-33025-touch{0}_activity=\"\"\"\n" +
228 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
229 '"""'),
230 (("touch{0}_activity=\"\"\"\n" +
231 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
232 '"""'),
233 (("hack-33025-touch{0}_activity=<multiline>\n" +
234 "---------- START ----------\n" +
235 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
236 "---------- END ----------"),
237 ]
238
239 start_index = end_index = None
240 for start, end in log_index:
241 if start in self.system:
242 start_index = self.system.index(start) + len(start)
243 end_index = self.system.index(end, start_index)
244 break
245
246 if start_index is None:
247 return []
248
249 activity_tar_enc = self.system[start_index:end_index]
250
251 # base64 decode
252 activity_tar_data = base64.b64decode(activity_tar_enc)
253
254 # untar
255 activity_tar_file = tarfile.open(fileobj=StringIO(activity_tar_data))
256
257 def ExtractPadFiles(name):
258 # find matching evdev file
259 evdev_name = name[0:name.index('touchpad_activity')]
260 evdev_name = evdev_name + "cmt_input_events"
261
262 for file_name in activity_tar_file.getnames():
263 if file_name.startswith(evdev_name):
264 evdev_name = file_name
265 break
266
267 activity_gz = activity_tar_file.extractfile(name)
268 evdev_gz = activity_tar_file.extractfile(evdev_name)
269
270 # decompress log files
271 return (GzipFile(fileobj=activity_gz).read(),
272 GzipFile(fileobj=evdev_gz).read())
273
274 def ExtractScreenFiles(name):
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400275 # Always try for a screenshot with touchscreen view
276 self.try_screenshot = True
277
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400278 evdev = activity_tar_file.extractfile(name)
279 return (evdev.read(), None)
280
281 extract_func = {
282 'pad': ExtractPadFiles,
283 'screen': ExtractScreenFiles,
284 }
285
286 # return latest log files, we don't include cmt files
287 return [(filename, extract_func[interface])
288 for filename in activity_tar_file.getnames()
289 if filename.find('cmt') == -1]
290
Dennis Kempinb049d542013-06-13 13:55:18 -0700291 if self.force_latest == "pad":
292 logs = ExtractByInterface('pad')
293 idx = 0
294 elif self.force_latest == "screen":
295 logs = ExtractByInterface('screen')
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700296 idx = 0
297 else:
Dennis Kempinb049d542013-06-13 13:55:18 -0700298 logs = ExtractByInterface('pad') + ExtractByInterface('screen')
299 if not logs:
300 print ("Cannot find touchpad_activity_log.tar or " +
301 "touchscreen_activity_log.tar in systems log file.")
302 sys.exit(-1)
303
304 if len(logs) == 1:
305 idx = 0
306 else:
307 while True:
308 print "Which log file would you like to use?"
309 for i, (name, extract_func) in enumerate(logs):
310 if name.startswith('evdev'):
311 name = 'touchscreen log - ' + name
312 print i, name
313 print ">"
314 selection = sys.stdin.readline()
315 try:
316 idx = int(selection)
317 if idx < 0 or idx >= len(logs):
318 print "Number out of range"
319 else:
320 break
321 except:
322 print "Not a number"
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700323
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400324 name, extract_func = logs[idx]
325 self.activity, self.evdev = extract_func(name)
Dennis Kempin351024d2013-02-06 11:18:21 -0800326
327
328identity_message = """\
329In order to access devices in TPTool, you need to have password-less
330auth for chromebooks set up.
331Would you like tptool to run the following command for you?
332$ %s
333Yes/No? (Default: No)"""
334
335class DeviceLog(AbstractLog):
336 """
337 Downloads logs from a running chromebook via scp.
338 """
339 def __init__(self, ip, options):
340 self.id_path = os.path.expanduser("~/.ssh/identity")
341 if not os.path.exists(self.id_path):
342 self._SetupIdentity()
343
344 if options.new:
345 self._GenerateLogs(ip)
346
347 self.activity = self._Download(ip, "touchpad_activity_log.txt")
348 self.evdev = self._Download(ip, "cmt_input_events.dat")
349 self.system = ""
350
351 def _GenerateLogs(self, ip):
352 cmd = ("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
353 "-q root@%s /opt/google/touchpad/tpcontrol log") % ip
354 process = subprocess.Popen(cmd.split())
355 process.wait()
356
357 def _Download(self, ip, filename):
358 temp = tempfile.NamedTemporaryFile("r")
359 cmd = ("scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
360 "-q root@%s:/var/log/%s %s")
361 cmd = cmd % (ip, filename, temp.name)
362 process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
363 process.wait()
364 temp.seek(0)
365 return temp.read()
366
367 def _SetupIdentity(self):
368 source = os.path.expanduser("~/trunk/chromite/ssh_keys/testing_rsa")
369 command = "cp %s %s && chmod 600 %s" % (source, self.id_path, self.id_path)
370 print identity_message % command
371 response = sys.stdin.readline().strip()
372 if response in ("Yes", "yes", "Y", "y"):
373 print command
374 os.system(command)