blob: ce03b322cb4b8952f1a784b7a84176ee9b409f7b [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
30def Log(source, options):
31 """
32 Returns a log object containg activity, evdev and system logs.
33 source can be either an ip address to a device from which to
34 download, a url pointing to a feedback report to pull or a filename.
35 """
36 if os.path.exists(source):
Dennis Kempin91e96df2013-03-29 14:21:42 -070037 if source.endswith(".zip") or source.endswith(".bz2"):
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040038 return FeedbackLog(source, options)
Dennis Kempin351024d2013-02-06 11:18:21 -080039 return FileLog(source, options)
40 else:
41 match = re.search("Report/([0-9]+)", source)
42 if match:
43 return FeedbackLog(match.group(1), options)
44 else:
45 # todo add regex and error message
46 return DeviceLog(source, options)
47
48
49class AbstractLog(object):
50 """
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040051 A log file consists of the activity log, the evdev log, a system log, and
52 possibly a screenshot, which can be saved to disk all together.
Dennis Kempin351024d2013-02-06 11:18:21 -080053 """
54 def SaveAs(self, filename):
55 open(filename, "w").write(self.activity)
56 if self.evdev:
57 open(filename + ".evdev", "w").write(self.evdev)
58 if self.system:
59 open(filename + ".system", "w").write(self.system)
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040060 if self.image:
61 open(filename + ".jpg", "w").write(self.image)
62
63 def CleanUp(self):
64 if os.path.exists("screenshot.jpg"):
65 os.remove("screenshot.jpg")
Dennis Kempin351024d2013-02-06 11:18:21 -080066
67
68class FileLog(AbstractLog):
69 """
70 Loads log from file. Does not contain evdev or system logs.
71 """
72 def __init__(self, filename, options):
73 self.activity = open(filename).read()
74 self.evdev = ""
75 self.system = ""
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040076 self.image = None
Dennis Kempin7b783272013-04-01 15:06:35 -070077 if options.evdev:
78 self.evdev = open(options.evdev).read()
79 elif os.path.exists(filename + ".evdev"):
Dennis Kempinfa6597c2013-02-20 14:05:19 -080080 self.evdev = open(filename + ".evdev").read()
81 if os.path.exists(filename + ".system"):
82 self.system = open(filename + ".system").read()
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040083 if os.path.exists(filename + ".jpg"):
84 self.image = open(filename + ".jpg").read()
85 file("screenshot.jpg", "w").write(self.image)
Dennis Kempin351024d2013-02-06 11:18:21 -080086
87
88class FeedbackLog(AbstractLog):
89 """
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040090 Downloads logs and (possibly) screenshot from a feedback id or file name
Dennis Kempin351024d2013-02-06 11:18:21 -080091 """
Dennis Kempin91e96df2013-03-29 14:21:42 -070092 def __init__(self, id_or_filename, options):
WeiNan-Peter, Wene539a182013-05-16 15:31:21 -040093 self.image = None
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -040094 self.try_screenshot = options.screenshot
95
Dennis Kempin91e96df2013-03-29 14:21:42 -070096 if id_or_filename.endswith(".zip") or id_or_filename.endswith(".bz2"):
97 self.report = open(id_or_filename).read()
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -040098 screenshot_filename = id_or_filename[:-4] + '.jpg'
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -070099 try:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400100 self.image = open(screenshot_filename).read()
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -0700101 except IOError:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400102 # No corresponding screenshot available.
103 pass
104 else:
105 def GetAndUpdateCachedFile(cached_filename, downloader):
106 try:
107 return open(cached_filename).read()
108 except IOError:
109 data = downloader(id_or_filename, self._GetOpener())
110 # Cache the data on disk
111 if not os.path.exists(log_cache_dir):
112 os.makedirs(log_cache_dir)
113 file(cached_filename, "w").write(data)
114 return data
115 self.report = GetAndUpdateCachedFile(
116 os.path.join(log_cache_dir, id_or_filename + ".zip"),
117 self._DownloadSystemLog)
118 self._ExtractSystemLog()
119 self._ExtractLogFiles()
120 if self.try_screenshot:
121 self.image = GetAndUpdateCachedFile(
122 os.path.join(log_cache_dir, id_or_filename + ".jpg"),
123 self._DownloadScreenshot)
Dennis Kempin351024d2013-02-06 11:18:21 -0800124
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400125 # Only write to screenshot.jpg if we will be viewing the screenshot
126 if not options.download and self.image:
127 file("screenshot.jpg", "w").write(self.image)
128
129 def _GetOpener(self):
130 try:
131 return self.opener
132 except AttributeError:
133 # setup cookies file and url opener
134 self.cookies = cookielib.MozillaCookieJar(cookies_file)
135 if os.path.exists(cookies_file):
136 self.cookies.load()
137 cookie_processor = urllib2.HTTPCookieProcessor(self.cookies)
138 self.opener = urllib2.build_opener(cookie_processor)
139 return self.opener
Dennis Kempin351024d2013-02-06 11:18:21 -0800140
141 def _Login(self, opener):
142 print "Login to corp.google.com:"
143 username = getpass.getuser()
144 password = getpass.getpass()
145 sys.stdout.write("OTP: ")
146 otp = sys.stdin.readline().strip()
147
148 # execute login by posting userdata to login.corp.google.com
149 url = "https://login.corp.google.com/login?ssoformat=CORP_SSO"
150 data = "u=%s&pw=%s&otp=%s" % (username, password, otp)
151 result = opener.open(url, data).read()
152
153 # check if the result displays error
154 if result.find("error") >= 0:
155 print "Login failed"
156 exit(-1)
157
158 # login was successful. save cookies to file to be reused later.
159 self.cookies.save()
160
161 def _GetLatestFile(self, match, tar):
162 # find file name containing match with latest timestamp
163 names = filter(lambda n: n.find(match) >= 0, tar.getnames())
164 names.sort()
Dennis Kempin765ad942013-03-26 13:58:46 -0700165 if not names:
166 print "Cannot find files named %s in tar file" % match
167 print "Tar file contents:", tar.getnames()
168 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800169 return tar.extractfile(names[-1])
170
171 def _DownloadSystemLog(self, id, opener):
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700172 # First download the report.zip file
173 logfile = "report-%s-system_logs.zip" % id
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400174 url = ("https://feedback.corp.googleusercontent.com/binarydata/" +
Dennis Kempin351024d2013-02-06 11:18:21 -0800175 "%s?id=%s&logIndex=0") % (logfile, id)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400176 report = opener.open(url).read()
177 file("test.zip", "w").write(report)
Dennis Kempin351024d2013-02-06 11:18:21 -0800178
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400179 if report[0:2] != "BZ" and report[0:2] != "PK":
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700180 # not a zip/bzip file. Log in and try again.
Dennis Kempin351024d2013-02-06 11:18:21 -0800181 self._Login(opener)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400182 report = opener.open(url).read()
183 return report
Dennis Kempin351024d2013-02-06 11:18:21 -0800184
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400185 def _DownloadScreenshot(self, id, opener):
186 url = "https://feedback.corp.googleusercontent.com/screenshot?id=%s" % id
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400187 image = ""
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400188 try:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400189 image = opener.open(url).read()
190 if imghdr.what("", image) != 'jpeg':
191 # We aren't authenticated; log in and try again.
192 self._Login(opener)
193 image = opener.open(url).read()
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400194 except urllib2.URLError:
195 print "No screenshots available - %s" % url
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400196 return image
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400197
Dennis Kempin91e96df2013-03-29 14:21:42 -0700198 def _ExtractSystemLog(self):
199 if self.report[0:2] == "BZ":
200 self.system = bz2.decompress(self.report)
201 elif self.report[0:2] == "PK":
202 io = StringIO(self.report)
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700203 zip = zipfile.ZipFile(io, "r")
204 self.system = zip.read(zip.namelist()[0])
205 zip.extractall("./")
206 else:
207 print "Cannot download logs file"
208 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800209
210 def _ExtractLogFiles(self):
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400211 # Find embedded and uuencoded activity.tar in system log
212
213 def ExtractByInterface(interface):
214 assert interface == 'pad' or interface == 'screen'
215
216 log_index = [
217 (("hack-33025-touch{0}_activity=\"\"\"\n" +
218 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
219 '"""'),
220 (("touch{0}_activity=\"\"\"\n" +
221 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
222 '"""'),
223 (("hack-33025-touch{0}_activity=<multiline>\n" +
224 "---------- START ----------\n" +
225 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
226 "---------- END ----------"),
227 ]
228
229 start_index = end_index = None
230 for start, end in log_index:
231 if start in self.system:
232 start_index = self.system.index(start) + len(start)
233 end_index = self.system.index(end, start_index)
234 break
235
236 if start_index is None:
237 return []
238
239 activity_tar_enc = self.system[start_index:end_index]
240
241 # base64 decode
242 activity_tar_data = base64.b64decode(activity_tar_enc)
243
244 # untar
245 activity_tar_file = tarfile.open(fileobj=StringIO(activity_tar_data))
246
247 def ExtractPadFiles(name):
248 # find matching evdev file
249 evdev_name = name[0:name.index('touchpad_activity')]
250 evdev_name = evdev_name + "cmt_input_events"
251
252 for file_name in activity_tar_file.getnames():
253 if file_name.startswith(evdev_name):
254 evdev_name = file_name
255 break
256
257 activity_gz = activity_tar_file.extractfile(name)
258 evdev_gz = activity_tar_file.extractfile(evdev_name)
259
260 # decompress log files
261 return (GzipFile(fileobj=activity_gz).read(),
262 GzipFile(fileobj=evdev_gz).read())
263
264 def ExtractScreenFiles(name):
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400265 # Always try for a screenshot with touchscreen view
266 self.try_screenshot = True
267
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400268 evdev = activity_tar_file.extractfile(name)
269 return (evdev.read(), None)
270
271 extract_func = {
272 'pad': ExtractPadFiles,
273 'screen': ExtractScreenFiles,
274 }
275
276 # return latest log files, we don't include cmt files
277 return [(filename, extract_func[interface])
278 for filename in activity_tar_file.getnames()
279 if filename.find('cmt') == -1]
280
281 logs = ExtractByInterface('pad') + ExtractByInterface('screen')
282 if not logs:
283 print ("Cannot find touchpad_activity_log.tar or " +
284 "touchscreen_activity_log.tar in systems log file.")
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700285 sys.exit(-1)
286
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700287 if len(logs) == 1:
288 idx = 0
289 else:
290 while True:
291 print "Which log file would you like to use?"
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400292 for i, (name, extract_func) in enumerate(logs):
293 if name.startswith('evdev'):
294 name = 'touchscreen log - ' + name
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700295 print i, name
296 print ">"
297 selection = sys.stdin.readline()
298 try:
299 idx = int(selection)
300 if idx < 0 or idx >= len(logs):
301 print "Number out of range"
302 else:
303 break
304 except:
305 print "Not a number"
306
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400307 name, extract_func = logs[idx]
308 self.activity, self.evdev = extract_func(name)
Dennis Kempin351024d2013-02-06 11:18:21 -0800309
310
311identity_message = """\
312In order to access devices in TPTool, you need to have password-less
313auth for chromebooks set up.
314Would you like tptool to run the following command for you?
315$ %s
316Yes/No? (Default: No)"""
317
318class DeviceLog(AbstractLog):
319 """
320 Downloads logs from a running chromebook via scp.
321 """
322 def __init__(self, ip, options):
323 self.id_path = os.path.expanduser("~/.ssh/identity")
324 if not os.path.exists(self.id_path):
325 self._SetupIdentity()
326
327 if options.new:
328 self._GenerateLogs(ip)
329
330 self.activity = self._Download(ip, "touchpad_activity_log.txt")
331 self.evdev = self._Download(ip, "cmt_input_events.dat")
332 self.system = ""
333
334 def _GenerateLogs(self, ip):
335 cmd = ("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
336 "-q root@%s /opt/google/touchpad/tpcontrol log") % ip
337 process = subprocess.Popen(cmd.split())
338 process.wait()
339
340 def _Download(self, ip, filename):
341 temp = tempfile.NamedTemporaryFile("r")
342 cmd = ("scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
343 "-q root@%s:/var/log/%s %s")
344 cmd = cmd % (ip, filename, temp.name)
345 process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
346 process.wait()
347 temp.seek(0)
348 return temp.read()
349
350 def _SetupIdentity(self):
351 source = os.path.expanduser("~/trunk/chromite/ssh_keys/testing_rsa")
352 command = "cp %s %s && chmod 600 %s" % (source, self.id_path, self.id_path)
353 print identity_message % command
354 response = sys.stdin.readline().strip()
355 if response in ("Yes", "yes", "Y", "y"):
356 print command
357 os.system(command)