blob: fdf3eca873793813c9bba2ea7bb176bb86033511 [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
90
91class FeedbackLog(AbstractLog):
92 """
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040093 Downloads logs and (possibly) screenshot from a feedback id or file name
Dennis Kempin351024d2013-02-06 11:18:21 -080094 """
Dennis Kempin91e96df2013-03-29 14:21:42 -070095 def __init__(self, id_or_filename, options):
WeiNan-Peter, Wene539a182013-05-16 15:31:21 -040096 self.image = None
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -040097 self.try_screenshot = options.screenshot
98
Dennis Kempin91e96df2013-03-29 14:21:42 -070099 if id_or_filename.endswith(".zip") or id_or_filename.endswith(".bz2"):
100 self.report = open(id_or_filename).read()
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400101 screenshot_filename = id_or_filename[:-4] + '.jpg'
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -0700102 try:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400103 self.image = open(screenshot_filename).read()
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -0700104 except IOError:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400105 # No corresponding screenshot available.
106 pass
107 else:
108 def GetAndUpdateCachedFile(cached_filename, downloader):
109 try:
110 return open(cached_filename).read()
111 except IOError:
112 data = downloader(id_or_filename, self._GetOpener())
113 # Cache the data on disk
114 if not os.path.exists(log_cache_dir):
115 os.makedirs(log_cache_dir)
116 file(cached_filename, "w").write(data)
117 return data
118 self.report = GetAndUpdateCachedFile(
119 os.path.join(log_cache_dir, id_or_filename + ".zip"),
120 self._DownloadSystemLog)
121 self._ExtractSystemLog()
122 self._ExtractLogFiles()
123 if self.try_screenshot:
124 self.image = GetAndUpdateCachedFile(
125 os.path.join(log_cache_dir, id_or_filename + ".jpg"),
126 self._DownloadScreenshot)
Dennis Kempin351024d2013-02-06 11:18:21 -0800127
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400128 # Only write to screenshot.jpg if we will be viewing the screenshot
129 if not options.download and self.image:
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -0400130 file(screenshot_filepath, "w").write(self.image)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400131
132 def _GetOpener(self):
133 try:
134 return self.opener
135 except AttributeError:
136 # setup cookies file and url opener
137 self.cookies = cookielib.MozillaCookieJar(cookies_file)
138 if os.path.exists(cookies_file):
139 self.cookies.load()
140 cookie_processor = urllib2.HTTPCookieProcessor(self.cookies)
141 self.opener = urllib2.build_opener(cookie_processor)
142 return self.opener
Dennis Kempin351024d2013-02-06 11:18:21 -0800143
144 def _Login(self, opener):
145 print "Login to corp.google.com:"
146 username = getpass.getuser()
147 password = getpass.getpass()
148 sys.stdout.write("OTP: ")
149 otp = sys.stdin.readline().strip()
150
151 # execute login by posting userdata to login.corp.google.com
152 url = "https://login.corp.google.com/login?ssoformat=CORP_SSO"
153 data = "u=%s&pw=%s&otp=%s" % (username, password, otp)
154 result = opener.open(url, data).read()
155
156 # check if the result displays error
157 if result.find("error") >= 0:
158 print "Login failed"
159 exit(-1)
160
161 # login was successful. save cookies to file to be reused later.
162 self.cookies.save()
163
164 def _GetLatestFile(self, match, tar):
165 # find file name containing match with latest timestamp
166 names = filter(lambda n: n.find(match) >= 0, tar.getnames())
167 names.sort()
Dennis Kempin765ad942013-03-26 13:58:46 -0700168 if not names:
169 print "Cannot find files named %s in tar file" % match
170 print "Tar file contents:", tar.getnames()
171 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800172 return tar.extractfile(names[-1])
173
174 def _DownloadSystemLog(self, id, opener):
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700175 # First download the report.zip file
176 logfile = "report-%s-system_logs.zip" % id
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400177 url = ("https://feedback.corp.googleusercontent.com/binarydata/" +
Dennis Kempin351024d2013-02-06 11:18:21 -0800178 "%s?id=%s&logIndex=0") % (logfile, id)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400179 report = opener.open(url).read()
180 file("test.zip", "w").write(report)
Dennis Kempin351024d2013-02-06 11:18:21 -0800181
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400182 if report[0:2] != "BZ" and report[0:2] != "PK":
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700183 # not a zip/bzip file. Log in and try again.
Dennis Kempin351024d2013-02-06 11:18:21 -0800184 self._Login(opener)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400185 report = opener.open(url).read()
186 return report
Dennis Kempin351024d2013-02-06 11:18:21 -0800187
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400188 def _DownloadScreenshot(self, id, opener):
189 url = "https://feedback.corp.googleusercontent.com/screenshot?id=%s" % id
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400190 image = ""
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400191 try:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400192 image = opener.open(url).read()
193 if imghdr.what("", image) != 'jpeg':
194 # We aren't authenticated; log in and try again.
195 self._Login(opener)
196 image = opener.open(url).read()
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400197 except urllib2.URLError:
198 print "No screenshots available - %s" % url
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400199 return image
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400200
Dennis Kempin91e96df2013-03-29 14:21:42 -0700201 def _ExtractSystemLog(self):
202 if self.report[0:2] == "BZ":
203 self.system = bz2.decompress(self.report)
204 elif self.report[0:2] == "PK":
205 io = StringIO(self.report)
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700206 zip = zipfile.ZipFile(io, "r")
207 self.system = zip.read(zip.namelist()[0])
208 zip.extractall("./")
209 else:
210 print "Cannot download logs file"
211 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800212
213 def _ExtractLogFiles(self):
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400214 # Find embedded and uuencoded activity.tar in system log
215
216 def ExtractByInterface(interface):
217 assert interface == 'pad' or interface == 'screen'
218
219 log_index = [
220 (("hack-33025-touch{0}_activity=\"\"\"\n" +
221 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
222 '"""'),
223 (("touch{0}_activity=\"\"\"\n" +
224 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
225 '"""'),
226 (("hack-33025-touch{0}_activity=<multiline>\n" +
227 "---------- START ----------\n" +
228 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
229 "---------- END ----------"),
230 ]
231
232 start_index = end_index = None
233 for start, end in log_index:
234 if start in self.system:
235 start_index = self.system.index(start) + len(start)
236 end_index = self.system.index(end, start_index)
237 break
238
239 if start_index is None:
240 return []
241
242 activity_tar_enc = self.system[start_index:end_index]
243
244 # base64 decode
245 activity_tar_data = base64.b64decode(activity_tar_enc)
246
247 # untar
248 activity_tar_file = tarfile.open(fileobj=StringIO(activity_tar_data))
249
250 def ExtractPadFiles(name):
251 # find matching evdev file
252 evdev_name = name[0:name.index('touchpad_activity')]
253 evdev_name = evdev_name + "cmt_input_events"
254
255 for file_name in activity_tar_file.getnames():
256 if file_name.startswith(evdev_name):
257 evdev_name = file_name
258 break
259
260 activity_gz = activity_tar_file.extractfile(name)
261 evdev_gz = activity_tar_file.extractfile(evdev_name)
262
263 # decompress log files
264 return (GzipFile(fileobj=activity_gz).read(),
265 GzipFile(fileobj=evdev_gz).read())
266
267 def ExtractScreenFiles(name):
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400268 # Always try for a screenshot with touchscreen view
269 self.try_screenshot = True
270
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400271 evdev = activity_tar_file.extractfile(name)
272 return (evdev.read(), None)
273
274 extract_func = {
275 'pad': ExtractPadFiles,
276 'screen': ExtractScreenFiles,
277 }
278
279 # return latest log files, we don't include cmt files
280 return [(filename, extract_func[interface])
281 for filename in activity_tar_file.getnames()
282 if filename.find('cmt') == -1]
283
284 logs = ExtractByInterface('pad') + ExtractByInterface('screen')
285 if not logs:
286 print ("Cannot find touchpad_activity_log.tar or " +
287 "touchscreen_activity_log.tar in systems log file.")
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700288 sys.exit(-1)
289
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700290 if len(logs) == 1:
291 idx = 0
292 else:
293 while True:
294 print "Which log file would you like to use?"
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400295 for i, (name, extract_func) in enumerate(logs):
296 if name.startswith('evdev'):
297 name = 'touchscreen log - ' + name
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700298 print i, name
299 print ">"
300 selection = sys.stdin.readline()
301 try:
302 idx = int(selection)
303 if idx < 0 or idx >= len(logs):
304 print "Number out of range"
305 else:
306 break
307 except:
308 print "Not a number"
309
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400310 name, extract_func = logs[idx]
311 self.activity, self.evdev = extract_func(name)
Dennis Kempin351024d2013-02-06 11:18:21 -0800312
313
314identity_message = """\
315In order to access devices in TPTool, you need to have password-less
316auth for chromebooks set up.
317Would you like tptool to run the following command for you?
318$ %s
319Yes/No? (Default: No)"""
320
321class DeviceLog(AbstractLog):
322 """
323 Downloads logs from a running chromebook via scp.
324 """
325 def __init__(self, ip, options):
326 self.id_path = os.path.expanduser("~/.ssh/identity")
327 if not os.path.exists(self.id_path):
328 self._SetupIdentity()
329
330 if options.new:
331 self._GenerateLogs(ip)
332
333 self.activity = self._Download(ip, "touchpad_activity_log.txt")
334 self.evdev = self._Download(ip, "cmt_input_events.dat")
335 self.system = ""
336
337 def _GenerateLogs(self, ip):
338 cmd = ("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
339 "-q root@%s /opt/google/touchpad/tpcontrol log") % ip
340 process = subprocess.Popen(cmd.split())
341 process.wait()
342
343 def _Download(self, ip, filename):
344 temp = tempfile.NamedTemporaryFile("r")
345 cmd = ("scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
346 "-q root@%s:/var/log/%s %s")
347 cmd = cmd % (ip, filename, temp.name)
348 process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
349 process.wait()
350 temp.seek(0)
351 return temp.read()
352
353 def _SetupIdentity(self):
354 source = os.path.expanduser("~/trunk/chromite/ssh_keys/testing_rsa")
355 command = "cp %s %s && chmod 600 %s" % (source, self.id_path, self.id_path)
356 print identity_message % command
357 response = sys.stdin.readline().strip()
358 if response in ("Yes", "yes", "Y", "y"):
359 print command
360 os.system(command)