blob: c02004709a00565b7ae6080a2e3478c18cfe7f1d [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
11import os
12import os.path
13import re
14import subprocess
15import sys
16import tarfile
17import tempfile
18import urllib2
Dennis Kempinecd9a0c2013-03-27 16:03:46 -070019import zipfile
Dennis Kempin351024d2013-02-06 11:18:21 -080020
21# path to current script directory
22script_dir = os.path.dirname(os.path.realpath(__file__))
23
24# path to the cookies file used for storing the login cookies.
Dennis Kempin136b7322013-02-06 13:20:57 -080025cookies_file = os.path.join(script_dir, "mtedit.cookies")
Dennis Kempin351024d2013-02-06 11:18:21 -080026
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -070027log_cache_dir = os.path.join(script_dir, "reports")
Dennis Kempin351024d2013-02-06 11:18:21 -080028
29def Log(source, options):
30 """
31 Returns a log object containg activity, evdev and system logs.
32 source can be either an ip address to a device from which to
33 download, a url pointing to a feedback report to pull or a filename.
34 """
35 if os.path.exists(source):
Dennis Kempin91e96df2013-03-29 14:21:42 -070036 if source.endswith(".zip") or source.endswith(".bz2"):
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040037 return FeedbackLog(source, options)
Dennis Kempin351024d2013-02-06 11:18:21 -080038 return FileLog(source, options)
39 else:
40 match = re.search("Report/([0-9]+)", source)
41 if match:
42 return FeedbackLog(match.group(1), options)
43 else:
44 # todo add regex and error message
45 return DeviceLog(source, options)
46
47
48class AbstractLog(object):
49 """
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040050 A log file consists of the activity log, the evdev log, a system log, and
51 possibly a screenshot, which can be saved to disk all together.
Dennis Kempin351024d2013-02-06 11:18:21 -080052 """
53 def SaveAs(self, filename):
54 open(filename, "w").write(self.activity)
55 if self.evdev:
56 open(filename + ".evdev", "w").write(self.evdev)
57 if self.system:
58 open(filename + ".system", "w").write(self.system)
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040059 if self.image:
60 open(filename + ".jpg", "w").write(self.image)
61
62 def CleanUp(self):
63 if os.path.exists("screenshot.jpg"):
64 os.remove("screenshot.jpg")
Dennis Kempin351024d2013-02-06 11:18:21 -080065
66
67class FileLog(AbstractLog):
68 """
69 Loads log from file. Does not contain evdev or system logs.
70 """
71 def __init__(self, filename, options):
72 self.activity = open(filename).read()
73 self.evdev = ""
74 self.system = ""
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040075 self.image = None
Dennis Kempin7b783272013-04-01 15:06:35 -070076 if options.evdev:
77 self.evdev = open(options.evdev).read()
78 elif os.path.exists(filename + ".evdev"):
Dennis Kempinfa6597c2013-02-20 14:05:19 -080079 self.evdev = open(filename + ".evdev").read()
80 if os.path.exists(filename + ".system"):
81 self.system = open(filename + ".system").read()
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040082 if os.path.exists(filename + ".jpg"):
83 self.image = open(filename + ".jpg").read()
84 file("screenshot.jpg", "w").write(self.image)
Dennis Kempin351024d2013-02-06 11:18:21 -080085
86
87class FeedbackLog(AbstractLog):
88 """
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040089 Downloads logs and (possibly) screenshot from a feedback id or file name
Dennis Kempin351024d2013-02-06 11:18:21 -080090 """
Dennis Kempin91e96df2013-03-29 14:21:42 -070091 def __init__(self, id_or_filename, options):
92 if id_or_filename.endswith(".zip") or id_or_filename.endswith(".bz2"):
93 self.report = open(id_or_filename).read()
94 else:
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -070095 # See if we've already downloaded this
96 cached_filename = os.path.join(log_cache_dir, id_or_filename + ".zip")
97 try:
98 self.report = open(cached_filename).read()
99 except IOError:
100 # setup cookies file and url opener then download the report archive
101 self.cookies = cookielib.MozillaCookieJar(cookies_file)
102 if os.path.exists(cookies_file):
103 self.cookies.load()
104 cookie_processor = urllib2.HTTPCookieProcessor(self.cookies)
105 opener = urllib2.build_opener(cookie_processor)
106 self._DownloadSystemLog(id_or_filename, opener)
107 # Cache the log on disk
108 if not os.path.exists(log_cache_dir):
109 os.makedirs(log_cache_dir)
110 file(cached_filename, "w").write(self.report)
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400111 self.image = None
112 if options.screenshot:
113 self._DownloadScreenshot(id_or_filename, opener)
114 # Only write to screenshot.jpg if we will be viewing the screenshot
115 if not options.download and self.image:
116 file("screenshot.jpg", "w").write(self.image)
Dennis Kempin351024d2013-02-06 11:18:21 -0800117
Dennis Kempin91e96df2013-03-29 14:21:42 -0700118 self._ExtractSystemLog()
Dennis Kempin351024d2013-02-06 11:18:21 -0800119 self._ExtractLogFiles()
120
121 def _Login(self, opener):
122 print "Login to corp.google.com:"
123 username = getpass.getuser()
124 password = getpass.getpass()
125 sys.stdout.write("OTP: ")
126 otp = sys.stdin.readline().strip()
127
128 # execute login by posting userdata to login.corp.google.com
129 url = "https://login.corp.google.com/login?ssoformat=CORP_SSO"
130 data = "u=%s&pw=%s&otp=%s" % (username, password, otp)
131 result = opener.open(url, data).read()
132
133 # check if the result displays error
134 if result.find("error") >= 0:
135 print "Login failed"
136 exit(-1)
137
138 # login was successful. save cookies to file to be reused later.
139 self.cookies.save()
140
141 def _GetLatestFile(self, match, tar):
142 # find file name containing match with latest timestamp
143 names = filter(lambda n: n.find(match) >= 0, tar.getnames())
144 names.sort()
Dennis Kempin765ad942013-03-26 13:58:46 -0700145 if not names:
146 print "Cannot find files named %s in tar file" % match
147 print "Tar file contents:", tar.getnames()
148 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800149 return tar.extractfile(names[-1])
150
151 def _DownloadSystemLog(self, id, opener):
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700152 # First download the report.zip file
153 logfile = "report-%s-system_logs.zip" % id
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400154 url = ("https://feedback.corp.googleusercontent.com/binarydata/" +
Dennis Kempin351024d2013-02-06 11:18:21 -0800155 "%s?id=%s&logIndex=0") % (logfile, id)
Dennis Kempin91e96df2013-03-29 14:21:42 -0700156 self.report = opener.open(url).read()
157 file("test.zip", "w").write(self.report)
Dennis Kempin351024d2013-02-06 11:18:21 -0800158
Dennis Kempin91e96df2013-03-29 14:21:42 -0700159 if self.report[0:2] != "BZ" and self.report[0:2] != "PK":
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700160 # not a zip/bzip file. Log in and try again.
Dennis Kempin351024d2013-02-06 11:18:21 -0800161 self._Login(opener)
Dennis Kempin91e96df2013-03-29 14:21:42 -0700162 self.report = opener.open(url).read()
Dennis Kempin351024d2013-02-06 11:18:21 -0800163
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400164 def _DownloadScreenshot(self, id, opener):
165 url = "https://feedback.corp.googleusercontent.com/screenshot?id=%s" % id
166 self.image = None
167 try:
168 self.image = opener.open(url).read()
169 except urllib2.URLError:
170 print "No screenshots available - %s" % url
171
Dennis Kempin91e96df2013-03-29 14:21:42 -0700172 def _ExtractSystemLog(self):
173 if self.report[0:2] == "BZ":
174 self.system = bz2.decompress(self.report)
175 elif self.report[0:2] == "PK":
176 io = StringIO(self.report)
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700177 zip = zipfile.ZipFile(io, "r")
178 self.system = zip.read(zip.namelist()[0])
179 zip.extractall("./")
180 else:
181 print "Cannot download logs file"
182 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800183
184 def _ExtractLogFiles(self):
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400185 # Find embedded and uuencoded activity.tar in system log
186
187 def ExtractByInterface(interface):
188 assert interface == 'pad' or interface == 'screen'
189
190 log_index = [
191 (("hack-33025-touch{0}_activity=\"\"\"\n" +
192 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
193 '"""'),
194 (("touch{0}_activity=\"\"\"\n" +
195 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
196 '"""'),
197 (("hack-33025-touch{0}_activity=<multiline>\n" +
198 "---------- START ----------\n" +
199 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
200 "---------- END ----------"),
201 ]
202
203 start_index = end_index = None
204 for start, end in log_index:
205 if start in self.system:
206 start_index = self.system.index(start) + len(start)
207 end_index = self.system.index(end, start_index)
208 break
209
210 if start_index is None:
211 return []
212
213 activity_tar_enc = self.system[start_index:end_index]
214
215 # base64 decode
216 activity_tar_data = base64.b64decode(activity_tar_enc)
217
218 # untar
219 activity_tar_file = tarfile.open(fileobj=StringIO(activity_tar_data))
220
221 def ExtractPadFiles(name):
222 # find matching evdev file
223 evdev_name = name[0:name.index('touchpad_activity')]
224 evdev_name = evdev_name + "cmt_input_events"
225
226 for file_name in activity_tar_file.getnames():
227 if file_name.startswith(evdev_name):
228 evdev_name = file_name
229 break
230
231 activity_gz = activity_tar_file.extractfile(name)
232 evdev_gz = activity_tar_file.extractfile(evdev_name)
233
234 # decompress log files
235 return (GzipFile(fileobj=activity_gz).read(),
236 GzipFile(fileobj=evdev_gz).read())
237
238 def ExtractScreenFiles(name):
239 evdev = activity_tar_file.extractfile(name)
240 return (evdev.read(), None)
241
242 extract_func = {
243 'pad': ExtractPadFiles,
244 'screen': ExtractScreenFiles,
245 }
246
247 # return latest log files, we don't include cmt files
248 return [(filename, extract_func[interface])
249 for filename in activity_tar_file.getnames()
250 if filename.find('cmt') == -1]
251
252 logs = ExtractByInterface('pad') + ExtractByInterface('screen')
253 if not logs:
254 print ("Cannot find touchpad_activity_log.tar or " +
255 "touchscreen_activity_log.tar in systems log file.")
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700256 sys.exit(-1)
257
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700258 if len(logs) == 1:
259 idx = 0
260 else:
261 while True:
262 print "Which log file would you like to use?"
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400263 for i, (name, extract_func) in enumerate(logs):
264 if name.startswith('evdev'):
265 name = 'touchscreen log - ' + name
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700266 print i, name
267 print ">"
268 selection = sys.stdin.readline()
269 try:
270 idx = int(selection)
271 if idx < 0 or idx >= len(logs):
272 print "Number out of range"
273 else:
274 break
275 except:
276 print "Not a number"
277
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400278 name, extract_func = logs[idx]
279 self.activity, self.evdev = extract_func(name)
Dennis Kempin351024d2013-02-06 11:18:21 -0800280
281
282identity_message = """\
283In order to access devices in TPTool, you need to have password-less
284auth for chromebooks set up.
285Would you like tptool to run the following command for you?
286$ %s
287Yes/No? (Default: No)"""
288
289class DeviceLog(AbstractLog):
290 """
291 Downloads logs from a running chromebook via scp.
292 """
293 def __init__(self, ip, options):
294 self.id_path = os.path.expanduser("~/.ssh/identity")
295 if not os.path.exists(self.id_path):
296 self._SetupIdentity()
297
298 if options.new:
299 self._GenerateLogs(ip)
300
301 self.activity = self._Download(ip, "touchpad_activity_log.txt")
302 self.evdev = self._Download(ip, "cmt_input_events.dat")
303 self.system = ""
304
305 def _GenerateLogs(self, ip):
306 cmd = ("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
307 "-q root@%s /opt/google/touchpad/tpcontrol log") % ip
308 process = subprocess.Popen(cmd.split())
309 process.wait()
310
311 def _Download(self, ip, filename):
312 temp = tempfile.NamedTemporaryFile("r")
313 cmd = ("scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
314 "-q root@%s:/var/log/%s %s")
315 cmd = cmd % (ip, filename, temp.name)
316 process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
317 process.wait()
318 temp.seek(0)
319 return temp.read()
320
321 def _SetupIdentity(self):
322 source = os.path.expanduser("~/trunk/chromite/ssh_keys/testing_rsa")
323 command = "cp %s %s && chmod 600 %s" % (source, self.id_path, self.id_path)
324 print identity_message % command
325 response = sys.stdin.readline().strip()
326 if response in ("Yes", "yes", "Y", "y"):
327 print command
328 os.system(command)