blob: 1618760cdd34406d9bdaf711b73514da6ab2ea19 [file] [log] [blame]
Chris Sosa0356d3b2010-09-16 15:46:22 -07001# Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved.
rtc@google.comded22402009-10-26 22:36:21 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
rtc@google.com64244662009-11-12 00:52:08 +00005from buildutil import BuildObject
rtc@google.comded22402009-10-26 22:36:21 +00006from xml.dom import minidom
7
Chris Sosa7c931362010-10-11 19:49:01 -07008import cherrypy
rtc@google.comded22402009-10-26 22:36:21 +00009import os
Darin Petkov798fe7d2010-03-22 15:18:13 -070010import shutil
Chris Sosa7c931362010-10-11 19:49:01 -070011import socket
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070012import time
Chris Sosa7c931362010-10-11 19:49:01 -070013
14def _LogMessage(message):
15 cherrypy.log(message, 'UPDATE')
rtc@google.comded22402009-10-26 22:36:21 +000016
Chris Sosa0356d3b2010-09-16 15:46:22 -070017
rtc@google.com64244662009-11-12 00:52:08 +000018class Autoupdate(BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -070019 """Class that contains functionality that handles Chrome OS update pings.
20
21 Members:
22 serve_only: Serve images from a pre-built image.zip file. static_dir
23 must be set to the location of the image.zip.
24 factory_config: Path to the factory config file if handling factory
25 requests.
26 use_test_image: Use chromiumos_test_image.bin rather than the standard.
27 static_url_base: base URL, other than devserver, for update images.
28 client_prefix: The prefix for the update engine client.
29 forced_image: Path to an image to use for all updates.
30 """
rtc@google.comded22402009-10-26 22:36:21 +000031
Sean O'Connor1f7fd362010-04-07 16:34:52 -070032 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Chris Sosa0356d3b2010-09-16 15:46:22 -070033 factory_config_path=None, client_prefix=None, forced_image=None,
Chris Sosa4136e692010-10-28 23:42:37 -070034 use_cached=False, port=8080, src_image='', vm=False, *args,
35 **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070036 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -070037 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -070038 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 15:46:22 -070039 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 16:54:41 -070040 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -070041 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -070042 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -070043 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -070044
Chris Sosab63a9282010-09-02 10:43:23 -070045 self.client_prefix = client_prefix
Chris Sosa0356d3b2010-09-16 15:46:22 -070046 self.forced_image = forced_image
Chris Sosa5d342a22010-09-28 16:54:41 -070047 self.use_cached = use_cached
Chris Sosa62f720b2010-10-26 21:39:48 -070048 self.src_image = src_image
Chris Sosa4136e692010-10-28 23:42:37 -070049 self.vm = vm
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070050
Chris Sosa0356d3b2010-09-16 15:46:22 -070051 def _GetSecondsSinceMidnight(self):
52 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070053 now = time.localtime()
54 return now[3] * 3600 + now[4] * 60 + now[5]
55
Chris Sosa0356d3b2010-09-16 15:46:22 -070056 def _GetDefaultBoardID(self):
57 """Returns the default board id stored in .default_board."""
58 board_file = '%s/.default_board' % (self.scripts_dir)
59 try:
60 return open(board_file).read()
61 except IOError:
62 return 'x86-generic'
63
64 def _GetLatestImageDir(self, board_id):
65 """Returns the latest image dir based on shell script."""
66 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
67 return os.popen(cmd).read().strip()
68
69 def _GetVersionFromDir(self, image_dir):
70 """Returns the version of the image based on the name of the directory."""
71 latest_version = os.path.basename(image_dir)
72 return latest_version.split('-')[0]
73
74 def _CanUpdate(self, client_version, latest_version):
75 """Returns true if the latest_version is greater than the client_version."""
76 client_tokens = client_version.replace('_', '').split('.')
77 latest_tokens = latest_version.replace('_', '').split('.')
Chris Sosa7c931362010-10-11 19:49:01 -070078 _LogMessage('client version %s latest version %s'
Chris Sosa0356d3b2010-09-16 15:46:22 -070079 % (client_version, latest_version))
80 for i in range(4):
81 if int(latest_tokens[i]) == int(client_tokens[i]):
82 continue
83 return int(latest_tokens[i]) > int(client_tokens[i])
84 return False
85
86 def _UnpackStatefulPartition(self, image_path, stateful_file):
87 """Given an image, unpacks its stateful partition to stateful_file."""
88 image_dir = os.path.dirname(image_path)
89 image_file = os.path.basename(image_path)
90
91 get_offset = '$(cgpt show -b -i 1 %s)' % image_file
92 get_size = '$(cgpt show -s -i 1 %s)' % image_file
93 unpack_command = (
94 'cd %s && '
95 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file,
96 stateful_file, get_offset,
97 get_size))
Chris Sosa7c931362010-10-11 19:49:01 -070098 _LogMessage(unpack_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -070099 return os.system(unpack_command) == 0
100
101 def _UnpackZip(self, image_dir):
102 """Unpacks an image.zip into a given directory."""
103 image = os.path.join(image_dir, self._GetImageName())
104 if os.path.exists(image):
105 return True
106 else:
107 # -n, never clobber an existing file, in case we get invoked
108 # simultaneously by multiple request handlers. This means that
109 # we're assuming each image.zip file lives in a versioned
110 # directory (a la Buildbot).
111 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
112
113 def _GetImageName(self):
114 """Returns the name of the image that should be used."""
115 if self.use_test_image:
116 image_name = 'chromiumos_test_image.bin'
117 else:
118 image_name = 'chromiumos_image.bin'
119 return image_name
120
121 def _IsImageNewerThanCached(self, image_path, cached_file_path):
122 """Returns true if the image is newer than the cached image."""
123 if os.path.exists(cached_file_path) and os.path.exists(image_path):
Chris Sosa7c931362010-10-11 19:49:01 -0700124 _LogMessage('Usable cached image found at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700125 return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path)
126 elif not os.path.exists(cached_file_path) and not os.path.exists(image_path):
127 raise Exception('Image does not exist and cached image missing')
128 else:
129 # Only one is missing, figure out which one.
130 if os.path.exists(image_path):
Chris Sosa7c931362010-10-11 19:49:01 -0700131 _LogMessage('No cached image found - image generation required.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700132 return True
133 else:
Chris Sosa7c931362010-10-11 19:49:01 -0700134 _LogMessage('Cached image found to serve at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700135 return False
136
137 def _GetSize(self, update_path):
138 """Returns the size of the file given."""
139 return os.path.getsize(update_path)
140
141 def _GetHash(self, update_path):
142 """Returns the sha1 of the file given."""
143 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
144 % update_path)
145 return os.popen(cmd).read().rstrip()
146
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700147 def _IsDeltaFormatFile(self, filename):
148 try:
149 file_handle = open(filename, 'r')
150 delta_magic = 'CrAU'
151 magic = file_handle.read(len(delta_magic))
152 return magic == delta_magic
153 except Exception:
154 return False
155
Darin Petkov91436cb2010-09-28 08:52:17 -0700156 # TODO(petkov): Consider optimizing getting both SHA-1 and SHA-256 so that
157 # it takes advantage of reduced I/O and multiple processors. Something like:
158 # % tee < FILE > /dev/null \
159 # >( openssl dgst -sha256 -binary | openssl base64 ) \
160 # >( openssl sha1 -binary | openssl base64 )
161 def _GetSHA256(self, update_path):
162 """Returns the sha256 of the file given."""
163 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
164 update_path)
165 return os.popen(cmd).read().rstrip()
166
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700167 def GetUpdatePayload(self, hash, sha256, size, url, is_delta_format):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700168 """Returns a payload to the client corresponding to a new update.
169
170 Args:
171 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700172 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700173 size: size of update blob
174 url: where to find update blob
175 Returns:
176 Xml string to be passed back to client.
177 """
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700178 delta = 'false'
179 if is_delta_format:
180 delta = 'true'
rtc@google.com21a5ca32009-11-04 18:23:23 +0000181 payload = """<?xml version="1.0" encoding="UTF-8"?>
182 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700183 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000184 <app appid="{%s}" status="ok">
185 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700186 <updatecheck
187 codebase="%s"
188 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700189 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700190 needsadmin="false"
191 size="%s"
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700192 IsDelta="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +0000193 status="ok"/>
194 </app>
195 </gupdate>
196 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700197 return payload % (self._GetSecondsSinceMidnight(),
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700198 self.app_id, url, hash, sha256, size, delta)
rtc@google.comded22402009-10-26 22:36:21 +0000199
rtc@google.com21a5ca32009-11-04 18:23:23 +0000200 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700201 """Returns a payload to the client corresponding to no update."""
202 payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
203 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0" >
204 < daystart elapsed_seconds = "%s" />
205 < app appid = "{%s}" status = "ok" >
206 < ping status = "ok" />
207 < updatecheck status = "noupdate" />
208 </ app >
209 </ gupdate >
rtc@google.com21a5ca32009-11-04 18:23:23 +0000210 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700211 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000212
Chris Sosa0356d3b2010-09-16 15:46:22 -0700213 def GenerateUpdateFile(self, image_path):
214 """Generates an update gz given a full path to an image.
215
216 Args:
217 image_path: Full path to image.
218 Returns:
219 Path to created update_payload or None on error.
220 """
221 image_dir = os.path.dirname(image_path)
222 update_path = os.path.join(image_dir, 'update.gz')
Chris Sosa4136e692010-10-28 23:42:37 -0700223 patch_kernel_flag = '--patch_kernel'
Chris Sosa7c931362010-10-11 19:49:01 -0700224 _LogMessage('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700225
Chris Sosa4136e692010-10-28 23:42:37 -0700226 # Don't patch the kernel for vm images as they don't need the patch.
227 if self.vm:
228 patch_kernel_flag = ''
229
Chris Sosa0356d3b2010-09-16 15:46:22 -0700230 mkupdate_command = (
Chris Sosa62f720b2010-10-26 21:39:48 -0700231 '%s/cros_generate_update_payload --image="%s" --output="%s" '
Chris Sosa4136e692010-10-28 23:42:37 -0700232 '%s --noold_style --src_image="%s"' % (
233 self.scripts_dir, image_path, update_path, patch_kernel_flag,
234 self.src_image))
Chris Sosa62f720b2010-10-26 21:39:48 -0700235 _LogMessage(mkupdate_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700236 if os.system(mkupdate_command) != 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700237 _LogMessage('Failed to create base update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700238 return None
239
240 return update_path
241
242 def GenerateStatefulFile(self, image_path):
243 """Generates a stateful update gz given a full path to an image.
244
245 Args:
246 image_path: Full path to image.
247 Returns:
248 Path to created stateful update_payload or None on error.
249 """
250 stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path)
251
252 # Unpack to get stateful partition.
253 if self._UnpackStatefulPartition(image_path, stateful_partition_path):
254 mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path
255 if os.system(mkstatefulupdate_command) == 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700256 _LogMessage('Successfully generated %s.gz' % stateful_partition_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700257 return '%s.gz' % stateful_partition_path
258
Chris Sosa7c931362010-10-11 19:49:01 -0700259 _LogMessage('Failed to create stateful update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700260 return None
261
262 def MoveImagesToStaticDir(self, update_path, stateful_update_path,
263 static_image_dir):
264 """Moves gz files from their directories to serving directories.
265
266 Args:
267 update_path: full path to main update gz.
268 stateful_update_path: full path to stateful partition gz.
269 static_image_dir: where to put files.
270 Returns:
271 Returns True if the files were moved over successfully.
272 """
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700273 try:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700274 shutil.copy(update_path, static_image_dir)
275 shutil.copy(stateful_update_path, static_image_dir)
276 os.remove(update_path)
277 os.remove(stateful_update_path)
278 except Exception:
Chris Sosa7c931362010-10-11 19:49:01 -0700279 _LogMessage('Failed to move %s and %s to %s' % (update_path,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700280 stateful_update_path,
281 static_image_dir))
282 return False
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700283
rtc@google.com21a5ca32009-11-04 18:23:23 +0000284 return True
rtc@google.comded22402009-10-26 22:36:21 +0000285
Chris Sosa0356d3b2010-09-16 15:46:22 -0700286 def GenerateUpdateImage(self, image_path, move_to_static_dir=False,
287 static_image_dir=None):
288 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000289
Chris Sosa0356d3b2010-09-16 15:46:22 -0700290 Args:
291 image_path: full path to the image.
292 move_to_static_dir: Moves the files from their dir to the static dir.
293 static_image_dir: the directory to move images to after generating.
294 Returns:
295 True if the update payload was created successfully.
296 """
Chris Sosa7c931362010-10-11 19:49:01 -0700297 _LogMessage('Generating update for image %s' % image_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700298 update_path = self.GenerateUpdateFile(image_path)
299 stateful_update_path = self.GenerateStatefulFile(image_path)
300 if not update_path or not stateful_update_path:
Chris Sosa7c931362010-10-11 19:49:01 -0700301 _LogMessage('Failed to generate update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700302 return False
303
304 if move_to_static_dir:
305 return self.MoveImagesToStaticDir(update_path, stateful_update_path,
306 static_image_dir)
307 else:
308 return True
309
310 def GenerateLatestUpdateImage(self, board_id, client_version,
311 static_image_dir=None):
312 """Generates an update using the latest image that has been built.
313
314 This will only generate an update if the newest update is newer than that
315 on the client or client_version is 'ForcedUpdate'.
316
317 Args:
318 board_id: Name of the board.
319 client_version: Current version of the client or 'ForcedUpdate'
320 static_image_dir: the directory to move images to after generating.
321 Returns:
322 True if the update payload was created successfully.
323 """
324 latest_image_dir = self._GetLatestImageDir(board_id)
325 latest_version = self._GetVersionFromDir(latest_image_dir)
326 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
327
Chris Sosa7c931362010-10-11 19:49:01 -0700328 _LogMessage('Preparing to generate update from latest built image %s.' %
Chris Sosa0356d3b2010-09-16 15:46:22 -0700329 latest_image_path)
330
331 # Check to see whether or not we should update.
332 if client_version != 'ForcedUpdate' and not self._CanUpdate(
333 client_version, latest_version):
Chris Sosa7c931362010-10-11 19:49:01 -0700334 _LogMessage('no update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700335 return False
336
337 cached_file_path = os.path.join(static_image_dir, 'update.gz')
338 if (os.path.exists(cached_file_path) and
339 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
340 return True
341
342 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
343 static_image_dir=static_image_dir)
344
345 def GenerateImageFromZip(self, static_image_dir):
346 """Generates an update from an image zip file.
347
348 This method assumes you have an image.zip in directory you are serving
349 from. If this file is newer than a previously cached file, it will unzip
350 this file, create a payload and serve it.
351
352 Args:
353 static_image_dir: Directory where the zip file exists.
354 Returns:
355 True if the update payload was created successfully.
356 """
Chris Sosa7c931362010-10-11 19:49:01 -0700357 _LogMessage('Preparing to generate update from zip in %s.' % static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700358 image_path = os.path.join(static_image_dir, self._GetImageName())
359 cached_file_path = os.path.join(static_image_dir, 'update.gz')
360 zip_file_path = os.path.join(static_image_dir, 'image.zip')
361 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path):
362 return True
363
364 if not self._UnpackZip(static_image_dir):
Chris Sosa7c931362010-10-11 19:49:01 -0700365 _LogMessage('unzip image.zip failed.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700366 return False
367
368 return self.GenerateUpdateImage(image_path, move_to_static_dir=False,
369 static_image_dir=None)
Darin Petkov8ef83452010-03-23 16:52:29 -0700370
Andrew de los Reyes52620802010-04-12 13:40:07 -0700371 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
372 """Imports a factory-floor server configuration file. The file should
373 be in this format:
374 config = [
375 {
376 'qual_ids': set([1, 2, 3, "x86-generic"]),
377 'factory_image': 'generic-factory.gz',
378 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
379 'release_image': 'generic-release.gz',
380 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
381 'oempartitionimg_image': 'generic-oem.gz',
382 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700383 'efipartitionimg_image': 'generic-efi.gz',
384 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700385 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800386 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800387 'firmware_image': 'generic-firmware.gz',
388 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700389 },
390 {
391 'qual_ids': set([6]),
392 'factory_image': '6-factory.gz',
393 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
394 'release_image': '6-release.gz',
395 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
396 'oempartitionimg_image': '6-oem.gz',
397 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700398 'efipartitionimg_image': '6-efi.gz',
399 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700400 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800401 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800402 'firmware_image': '6-firmware.gz',
403 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700404 },
405 ]
406 The server will look for the files by name in the static files
407 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700408
Andrew de los Reyes52620802010-04-12 13:40:07 -0700409 If validate_checksums is True, validates checksums and exits. If
410 a checksum mismatch is found, it's printed to the screen.
411 """
412 f = open(filename, 'r')
413 output = {}
414 exec(f.read(), output)
415 self.factory_config = output['config']
416 success = True
417 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800418 for key in stanza.copy().iterkeys():
419 suffix = '_image'
420 if key.endswith(suffix):
421 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700422 stanza[kind + '_size'] = self._GetSize(os.path.join(
423 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800424 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700425 factory_checksum = self._GetHash(os.path.join(self.static_dir,
426 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800427 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700428 print ('Error: checksum mismatch for %s. Expected "%s" but file '
429 'has checksum "%s".' % (stanza[kind + '_image'],
430 stanza[kind + '_checksum'],
431 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800432 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700433
Andrew de los Reyes52620802010-04-12 13:40:07 -0700434 if validate_checksums:
435 if success is False:
436 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700437
Andrew de los Reyes52620802010-04-12 13:40:07 -0700438 print 'Config file looks good.'
439
440 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700441 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700442 for stanza in self.factory_config:
443 if board_id not in stanza['qual_ids']:
444 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700445 if kind + '_image' not in stanza:
446 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700447 return (stanza[kind + '_image'],
448 stanza[kind + '_checksum'],
449 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700450 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000451
Chris Sosa7c931362010-10-11 19:49:01 -0700452 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700453 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
454 if filename is None:
Chris Sosa7c931362010-10-11 19:49:01 -0700455 _LogMessage('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700456 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700457 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700458 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa7c931362010-10-11 19:49:01 -0700459 _LogMessage('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700460 # Factory install is using memento updater which is using the sha-1 hash so
461 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700462 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700463
Chris Sosa151643e2010-10-28 14:40:57 -0700464 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
465 static_image_dir):
Chris Sosa2c048f12010-10-27 16:05:27 -0700466 """Generates an update for non-factory and returns True on success."""
467 if self.use_cached and os.path.exists(os.path.join(static_image_dir,
468 'update.gz')):
469 _LogMessage('Using cached image regardless of timestamps.')
470 return True
471 else:
472 if self.forced_image:
473 has_built_image = self.GenerateUpdateImage(
474 self.forced_image, move_to_static_dir=True,
475 static_image_dir=static_image_dir)
476 # Now that we've generated it, force devserver to use it.
477 self.use_cached = True
478 elif self.serve_only:
479 return self.GenerateImageFromZip(static_image_dir)
Chris Sosa151643e2010-10-28 14:40:57 -0700480 elif board_id and client_version:
Chris Sosa2c048f12010-10-27 16:05:27 -0700481 return self.GenerateLatestUpdateImage(board_id,
482 client_version,
483 static_image_dir)
Chris Sosa151643e2010-10-28 14:40:57 -0700484 else:
485 return False
Chris Sosa2c048f12010-10-27 16:05:27 -0700486
487 def PreGenerateUpdate(self):
488 """Pre-generates an update. Does not work for factory or label updates."""
489 # Does not work with factory config.
490 assert(not self.factory_config)
491 _LogMessage('Pre-generating the update payload.')
492 # Does not work with labels so just use static dir.
Chris Sosa151643e2010-10-28 14:40:57 -0700493 if self.GenerateUpdatePayloadForNonFactory(None, None, self.static_dir):
Chris Sosa2c048f12010-10-27 16:05:27 -0700494 # Force the devserver to use the pre-generated payload.
495 self.use_cached = True
496 else:
497 _LogMessage('Failed to pre-generate update.')
498
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700499 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700500 """Handles an update ping from an update client.
501
502 Args:
503 data: xml blob from client.
504 label: optional label for the update.
505 Returns:
506 Update payload message for client.
507 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700508 # Set hostname as the hostname that the client is calling to and set up
509 # the url base.
510 self.hostname = cherrypy.request.base
511 if self.urlbase:
512 static_urlbase = self.urlbase
513 elif self.serve_only:
514 static_urlbase = '%s/static/archive' % self.hostname
515 else:
516 static_urlbase = '%s/static' % self.hostname
517
518 _LogMessage('Using static url base %s' % static_urlbase)
519 _LogMessage('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700520
521 # Check the client prefix to make sure you can support this type of update.
Chris Sosa9841e1c2010-10-14 10:51:45 -0700522 update_dom = minidom.parseString(data)
523 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700524 if (root.hasAttribute('updaterversion') and
525 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
Chris Sosa7c931362010-10-11 19:49:01 -0700526 _LogMessage('Got update from unsupported updater:' +
Chris Sosa0356d3b2010-09-16 15:46:22 -0700527 root.getAttribute('updaterversion'))
Andrew de los Reyes9223f132010-05-07 17:08:17 -0700528 return self.GetNoUpdatePayload()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700529
530 # We only generate update payloads for updatecheck requests.
531 update_check = root.getElementsByTagName('o:updatecheck')
532 if not update_check:
Chris Sosa7c931362010-10-11 19:49:01 -0700533 _LogMessage('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700534 # TODO(sosa): Generate correct non-updatecheck payload to better test
535 # update clients.
536 return self.GetNoUpdatePayload()
537
538 # Since this is an updatecheck, get information about the requester.
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700539 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800540 client_version = query.getAttribute('version')
Andrew de los Reyes52620802010-04-12 13:40:07 -0700541 channel = query.getAttribute('track')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700542 board_id = (query.hasAttribute('board') and query.getAttribute('board')
543 or self._GetDefaultBoardID())
Andrew de los Reyes52620802010-04-12 13:40:07 -0700544
Chris Sosa0356d3b2010-09-16 15:46:22 -0700545 # Separate logic as Factory requests have static url's that override
546 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700547 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700548 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700549 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700550 static_image_dir = self.static_dir
551 if label:
552 static_image_dir = os.path.join(static_image_dir, label)
553
Chris Sosa151643e2010-10-28 14:40:57 -0700554 if self.GenerateUpdatePayloadForNonFactory(board_id, client_version,
555 static_image_dir):
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700556 filename = os.path.join(static_image_dir, 'update.gz')
557 hash = self._GetHash(filename)
558 sha256 = self._GetSHA256(filename)
559 size = self._GetSize(filename)
560 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa5d342a22010-09-28 16:54:41 -0700561 if label:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700562 url = '%s/%s/update.gz' % (static_urlbase, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700563 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700564 url = '%s/update.gz' % static_urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700565
Chris Sosa7c931362010-10-11 19:49:01 -0700566 _LogMessage('Responding to client to use url %s to get image.' % url)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700567 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700568 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700569 return self.GetNoUpdatePayload()