blob: 29fb5b10543def49f4dacc79812d1ce49d9d0721 [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
Dennis Kempincee37612013-06-14 10:40:41 -07006from mtlib.feedback import FeedbackDownloader
Dennis Kempin351024d2013-02-06 11:18:21 -08007from StringIO import StringIO
8import base64
9import bz2
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -040010import imghdr
Dennis Kempin351024d2013-02-06 11:18:21 -080011import os
12import os.path
13import re
14import subprocess
15import sys
16import tarfile
17import tempfile
Dennis Kempinecd9a0c2013-03-27 16:03:46 -070018import zipfile
Dennis Kempin351024d2013-02-06 11:18:21 -080019
Dennis Kempin351024d2013-02-06 11:18:21 -080020
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -040021# path for temporary screenshot
22screenshot_filepath = "extension/screenshot.jpg"
23
Dennis Kempin351024d2013-02-06 11:18:21 -080024def Log(source, options):
25 """
26 Returns a log object containg activity, evdev and system logs.
27 source can be either an ip address to a device from which to
28 download, a url pointing to a feedback report to pull or a filename.
29 """
30 if os.path.exists(source):
Dennis Kempin91e96df2013-03-29 14:21:42 -070031 if source.endswith(".zip") or source.endswith(".bz2"):
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040032 return FeedbackLog(source, options)
Dennis Kempin351024d2013-02-06 11:18:21 -080033 return FileLog(source, options)
34 else:
35 match = re.search("Report/([0-9]+)", source)
36 if match:
37 return FeedbackLog(match.group(1), options)
38 else:
39 # todo add regex and error message
40 return DeviceLog(source, options)
41
42
43class AbstractLog(object):
44 """
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040045 A log file consists of the activity log, the evdev log, a system log, and
46 possibly a screenshot, which can be saved to disk all together.
Dennis Kempin351024d2013-02-06 11:18:21 -080047 """
48 def SaveAs(self, filename):
49 open(filename, "w").write(self.activity)
50 if self.evdev:
51 open(filename + ".evdev", "w").write(self.evdev)
52 if self.system:
53 open(filename + ".system", "w").write(self.system)
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040054 if self.image:
55 open(filename + ".jpg", "w").write(self.image)
56
57 def CleanUp(self):
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -040058 if os.path.exists(screenshot_filepath):
59 os.remove(screenshot_filepath)
Dennis Kempin351024d2013-02-06 11:18:21 -080060
61
62class FileLog(AbstractLog):
63 """
64 Loads log from file. Does not contain evdev or system logs.
65 """
66 def __init__(self, filename, options):
67 self.activity = open(filename).read()
68 self.evdev = ""
69 self.system = ""
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040070 self.image = None
Dennis Kempin7b783272013-04-01 15:06:35 -070071 if options.evdev:
72 self.evdev = open(options.evdev).read()
73 elif os.path.exists(filename + ".evdev"):
Dennis Kempinfa6597c2013-02-20 14:05:19 -080074 self.evdev = open(filename + ".evdev").read()
75 if os.path.exists(filename + ".system"):
76 self.system = open(filename + ".system").read()
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040077 if os.path.exists(filename + ".jpg"):
78 self.image = open(filename + ".jpg").read()
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -040079 file(screenshot_filepath, "w").write(self.image)
Dennis Kempin351024d2013-02-06 11:18:21 -080080
81
82class FeedbackLog(AbstractLog):
83 """
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -040084 Downloads logs and (possibly) screenshot from a feedback id or file name
Dennis Kempin351024d2013-02-06 11:18:21 -080085 """
Dennis Kempinb049d542013-06-13 13:55:18 -070086 def __init__(self, id_or_filename, options=None, force_latest=None):
WeiNan-Peter, Wene539a182013-05-16 15:31:21 -040087 self.image = None
Dennis Kempinb049d542013-06-13 13:55:18 -070088 self.force_latest = force_latest
89 self.try_screenshot = options and options.screenshot
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -040090
Dennis Kempin91e96df2013-03-29 14:21:42 -070091 if id_or_filename.endswith(".zip") or id_or_filename.endswith(".bz2"):
92 self.report = open(id_or_filename).read()
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -040093 screenshot_filename = id_or_filename[:-4] + '.jpg'
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -070094 try:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -040095 self.image = open(screenshot_filename).read()
Andrew de los Reyes1de4dcd2013-05-14 11:45:32 -070096 except IOError:
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -040097 # No corresponding screenshot available.
98 pass
99 else:
Dennis Kempinb049d542013-06-13 13:55:18 -0700100 self.downloader = FeedbackDownloader()
Dennis Kempincee37612013-06-14 10:40:41 -0700101 self.report = self.downloader.DownloadSystemLog(id_or_filename)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400102 self._ExtractSystemLog()
103 self._ExtractLogFiles()
104 if self.try_screenshot:
Dennis Kempincee37612013-06-14 10:40:41 -0700105 self.image = self.downloader.DownloadScreenshot(id_or_filename)
Dennis Kempin351024d2013-02-06 11:18:21 -0800106
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400107 # Only write to screenshot.jpg if we will be viewing the screenshot
Dennis Kempinb049d542013-06-13 13:55:18 -0700108 if options and not options.download and self.image:
WeiNan-Peter, Wen294408a2013-06-13 10:48:39 -0400109 file(screenshot_filepath, "w").write(self.image)
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400110
Dennis Kempin351024d2013-02-06 11:18:21 -0800111
112 def _GetLatestFile(self, match, tar):
113 # find file name containing match with latest timestamp
114 names = filter(lambda n: n.find(match) >= 0, tar.getnames())
115 names.sort()
Dennis Kempin765ad942013-03-26 13:58:46 -0700116 if not names:
117 print "Cannot find files named %s in tar file" % match
118 print "Tar file contents:", tar.getnames()
119 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800120 return tar.extractfile(names[-1])
121
WeiNan-Peter, Wen9aab26a2013-05-13 09:32:29 -0400122
Dennis Kempin91e96df2013-03-29 14:21:42 -0700123 def _ExtractSystemLog(self):
124 if self.report[0:2] == "BZ":
125 self.system = bz2.decompress(self.report)
126 elif self.report[0:2] == "PK":
127 io = StringIO(self.report)
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700128 zip = zipfile.ZipFile(io, "r")
129 self.system = zip.read(zip.namelist()[0])
130 zip.extractall("./")
131 else:
132 print "Cannot download logs file"
133 sys.exit(-1)
Dennis Kempin351024d2013-02-06 11:18:21 -0800134
135 def _ExtractLogFiles(self):
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400136 # Find embedded and uuencoded activity.tar in system log
137
138 def ExtractByInterface(interface):
139 assert interface == 'pad' or interface == 'screen'
140
141 log_index = [
142 (("hack-33025-touch{0}_activity=\"\"\"\n" +
143 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
144 '"""'),
145 (("touch{0}_activity=\"\"\"\n" +
146 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
147 '"""'),
148 (("hack-33025-touch{0}_activity=<multiline>\n" +
149 "---------- START ----------\n" +
150 "begin-base64 644 touch{0}_activity_log.tar\n").format(interface),
151 "---------- END ----------"),
152 ]
153
154 start_index = end_index = None
155 for start, end in log_index:
156 if start in self.system:
157 start_index = self.system.index(start) + len(start)
158 end_index = self.system.index(end, start_index)
159 break
160
161 if start_index is None:
162 return []
163
164 activity_tar_enc = self.system[start_index:end_index]
165
166 # base64 decode
167 activity_tar_data = base64.b64decode(activity_tar_enc)
168
169 # untar
170 activity_tar_file = tarfile.open(fileobj=StringIO(activity_tar_data))
171
172 def ExtractPadFiles(name):
173 # find matching evdev file
174 evdev_name = name[0:name.index('touchpad_activity')]
175 evdev_name = evdev_name + "cmt_input_events"
176
177 for file_name in activity_tar_file.getnames():
178 if file_name.startswith(evdev_name):
179 evdev_name = file_name
180 break
181
182 activity_gz = activity_tar_file.extractfile(name)
183 evdev_gz = activity_tar_file.extractfile(evdev_name)
184
185 # decompress log files
186 return (GzipFile(fileobj=activity_gz).read(),
187 GzipFile(fileobj=evdev_gz).read())
188
189 def ExtractScreenFiles(name):
WeiNan-Peter, Wen46258862013-05-21 11:17:54 -0400190 # Always try for a screenshot with touchscreen view
191 self.try_screenshot = True
192
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400193 evdev = activity_tar_file.extractfile(name)
194 return (evdev.read(), None)
195
196 extract_func = {
197 'pad': ExtractPadFiles,
198 'screen': ExtractScreenFiles,
199 }
200
201 # return latest log files, we don't include cmt files
202 return [(filename, extract_func[interface])
203 for filename in activity_tar_file.getnames()
204 if filename.find('cmt') == -1]
205
Dennis Kempinb049d542013-06-13 13:55:18 -0700206 if self.force_latest == "pad":
207 logs = ExtractByInterface('pad')
208 idx = 0
209 elif self.force_latest == "screen":
210 logs = ExtractByInterface('screen')
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700211 idx = 0
212 else:
Dennis Kempinb049d542013-06-13 13:55:18 -0700213 logs = ExtractByInterface('pad') + ExtractByInterface('screen')
214 if not logs:
215 print ("Cannot find touchpad_activity_log.tar or " +
216 "touchscreen_activity_log.tar in systems log file.")
217 sys.exit(-1)
218
219 if len(logs) == 1:
220 idx = 0
221 else:
222 while True:
223 print "Which log file would you like to use?"
224 for i, (name, extract_func) in enumerate(logs):
225 if name.startswith('evdev'):
226 name = 'touchscreen log - ' + name
227 print i, name
228 print ">"
229 selection = sys.stdin.readline()
230 try:
231 idx = int(selection)
232 if idx < 0 or idx >= len(logs):
233 print "Number out of range"
234 else:
235 break
236 except:
237 print "Not a number"
Dennis Kempinecd9a0c2013-03-27 16:03:46 -0700238
WeiNan-Peter, Wenf3f40342013-05-15 11:26:09 -0400239 name, extract_func = logs[idx]
240 self.activity, self.evdev = extract_func(name)
Dennis Kempin351024d2013-02-06 11:18:21 -0800241
242
243identity_message = """\
244In order to access devices in TPTool, you need to have password-less
245auth for chromebooks set up.
246Would you like tptool to run the following command for you?
247$ %s
248Yes/No? (Default: No)"""
249
250class DeviceLog(AbstractLog):
251 """
252 Downloads logs from a running chromebook via scp.
253 """
254 def __init__(self, ip, options):
255 self.id_path = os.path.expanduser("~/.ssh/identity")
256 if not os.path.exists(self.id_path):
257 self._SetupIdentity()
258
259 if options.new:
260 self._GenerateLogs(ip)
261
262 self.activity = self._Download(ip, "touchpad_activity_log.txt")
263 self.evdev = self._Download(ip, "cmt_input_events.dat")
264 self.system = ""
265
266 def _GenerateLogs(self, ip):
267 cmd = ("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
268 "-q root@%s /opt/google/touchpad/tpcontrol log") % ip
269 process = subprocess.Popen(cmd.split())
270 process.wait()
271
272 def _Download(self, ip, filename):
273 temp = tempfile.NamedTemporaryFile("r")
274 cmd = ("scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " +
275 "-q root@%s:/var/log/%s %s")
276 cmd = cmd % (ip, filename, temp.name)
277 process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
278 process.wait()
279 temp.seek(0)
280 return temp.read()
281
282 def _SetupIdentity(self):
283 source = os.path.expanduser("~/trunk/chromite/ssh_keys/testing_rsa")
284 command = "cp %s %s && chmod 600 %s" % (source, self.id_path, self.id_path)
285 print identity_message % command
286 response = sys.stdin.readline().strip()
287 if response in ("Yes", "yes", "Y", "y"):
288 print command
289 os.system(command)