blob: 1fb9daa7c320a9a416e4eb29c5b07baf42b55aaa [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):
185 # find embedded and uuencoded activity.tar in system log
Dennis Kempin91e96df2013-03-29 14:21:42 -0700186 log_start1 = ("hack-33025-touchpad_activity=\"\"\"\n" +
Dennis Kempin351024d2013-02-06 11:18:21 -0800187 "begin-base64 644 touchpad_activity_log.tar\n")
Dennis Kempin91e96df2013-03-29 14:21:42 -0700188 log_start2 = ("touchpad_activity=\"\"\"\n" +
189 "begin-base64 644 touchpad_activity_log.tar\n")
190 log_start3 = ("hack-33025-touchpad_activity=<multiline>\n" +
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700191 "---------- START ----------\n" +
192 "begin-base64 644 touchpad_activity_log.tar\n")
Dennis Kempin91e96df2013-03-29 14:21:42 -0700193 if log_start1 in self.system:
194 log_start_index = self.system.index(log_start1) + len(log_start1)
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700195 log_end_index = self.system.index("\"\"\"", log_start_index)
196 elif log_start2 in self.system:
197 log_start_index = self.system.index(log_start2) + len(log_start2)
Dennis Kempin91e96df2013-03-29 14:21:42 -0700198 log_end_index = self.system.index("\"\"\"", log_start_index)
199 elif log_start3 in self.system:
200 log_start_index = self.system.index(log_start3) + len(log_start3)
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700201 log_end_index = self.system.index("---------- END ----------",
202 log_start_index)
203 else:
204 print "cannot find touchpad_activity_log.tar in systems log file"
205 sys.exit(-1)
206
Dennis Kempin351024d2013-02-06 11:18:21 -0800207 activity_tar_enc = self.system[log_start_index:log_end_index]
208
209 # base64 decode
210 activity_tar_data = base64.b64decode(activity_tar_enc)
211
212 # untar
213 activity_tar_file = tarfile.open(fileobj=StringIO(activity_tar_data))
214
215 # find latest log files
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700216 logs = filter(lambda n: n.find("touchpad_activity") >= 0,
217 activity_tar_file.getnames())
218 if len(logs) == 1:
219 idx = 0
220 else:
221 while True:
222 print "Which log file would you like to use?"
223 for i, name in enumerate(logs):
224 print i, name
225 print ">"
226 selection = sys.stdin.readline()
227 try:
228 idx = int(selection)
229 if idx < 0 or idx >= len(logs):
230 print "Number out of range"
231 else:
232 break
233 except:
234 print "Not a number"
235
236 activity_name = logs[idx]
237
238 # find matching evdev file
239 evdev_name = logs[idx][0:logs[idx].index("touchpad_activity")]
240 evdev_name = evdev_name + "cmt_input_events"
241
242 for name in activity_tar_file.getnames():
243 if name.startswith(evdev_name):
244 evdev_name = name
245 break
246
247 activity_gz = activity_tar_file.extractfile(activity_name)
248 evdev_gz = activity_tar_file.extractfile(evdev_name)
Dennis Kempin351024d2013-02-06 11:18:21 -0800249
250 # decompress log files
251 self.activity = GzipFile(fileobj=activity_gz).read()
252 self.evdev = GzipFile(fileobj=evdev_gz).read()
253
254
255identity_message = """\
256In order to access devices in TPTool, you need to have password-less
257auth for chromebooks set up.
258Would you like tptool to run the following command for you?
259$ %s
260Yes/No? (Default: No)"""
261
262class DeviceLog(AbstractLog):
263 """
264 Downloads logs from a running chromebook via scp.
265 """
266 def __init__(self, ip, options):
267 self.id_path = os.path.expanduser("~/.ssh/identity")
268 if not os.path.exists(self.id_path):
269 self._SetupIdentity()
270
271 if options.new:
272 self._GenerateLogs(ip)
273
274 self.activity = self._Download(ip, "touchpad_activity_log.txt")
275 self.evdev = self._Download(ip, "cmt_input_events.dat")
276 self.system = ""
277
278 def _GenerateLogs(self, ip):
279 cmd = ("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
280 "-q root@%s /opt/google/touchpad/tpcontrol log") % ip
281 process = subprocess.Popen(cmd.split())
282 process.wait()
283
284 def _Download(self, ip, filename):
285 temp = tempfile.NamedTemporaryFile("r")
286 cmd = ("scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
287 "-q root@%s:/var/log/%s %s")
288 cmd = cmd % (ip, filename, temp.name)
289 process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
290 process.wait()
291 temp.seek(0)
292 return temp.read()
293
294 def _SetupIdentity(self):
295 source = os.path.expanduser("~/trunk/chromite/ssh_keys/testing_rsa")
296 command = "cp %s %s && chmod 600 %s" % (source, self.id_path, self.id_path)
297 print identity_message % command
298 response = sys.stdin.readline().strip()
299 if response in ("Yes", "yes", "Y", "y"):
300 print command
301 os.system(command)