blob: 351b451b94555ba37d8f5a67c1c20a92bdd54dc9 [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 Sosa62f720b2010-10-26 21:39:48 -070034 use_cached=False, port=8080, src_image='', *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070035 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -070036 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -070037 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 15:46:22 -070038 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 16:54:41 -070039 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -070040 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -070041 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -070042 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -070043
Chris Sosab63a9282010-09-02 10:43:23 -070044 self.client_prefix = client_prefix
Chris Sosa0356d3b2010-09-16 15:46:22 -070045 self.forced_image = forced_image
Chris Sosa5d342a22010-09-28 16:54:41 -070046 self.use_cached = use_cached
Chris Sosa62f720b2010-10-26 21:39:48 -070047 self.src_image = src_image
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070048
Chris Sosa0356d3b2010-09-16 15:46:22 -070049 def _GetSecondsSinceMidnight(self):
50 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070051 now = time.localtime()
52 return now[3] * 3600 + now[4] * 60 + now[5]
53
Chris Sosa0356d3b2010-09-16 15:46:22 -070054 def _GetDefaultBoardID(self):
55 """Returns the default board id stored in .default_board."""
56 board_file = '%s/.default_board' % (self.scripts_dir)
57 try:
58 return open(board_file).read()
59 except IOError:
60 return 'x86-generic'
61
62 def _GetLatestImageDir(self, board_id):
63 """Returns the latest image dir based on shell script."""
64 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
65 return os.popen(cmd).read().strip()
66
67 def _GetVersionFromDir(self, image_dir):
68 """Returns the version of the image based on the name of the directory."""
69 latest_version = os.path.basename(image_dir)
70 return latest_version.split('-')[0]
71
72 def _CanUpdate(self, client_version, latest_version):
73 """Returns true if the latest_version is greater than the client_version."""
74 client_tokens = client_version.replace('_', '').split('.')
75 latest_tokens = latest_version.replace('_', '').split('.')
Chris Sosa7c931362010-10-11 19:49:01 -070076 _LogMessage('client version %s latest version %s'
Chris Sosa0356d3b2010-09-16 15:46:22 -070077 % (client_version, latest_version))
78 for i in range(4):
79 if int(latest_tokens[i]) == int(client_tokens[i]):
80 continue
81 return int(latest_tokens[i]) > int(client_tokens[i])
82 return False
83
84 def _UnpackStatefulPartition(self, image_path, stateful_file):
85 """Given an image, unpacks its stateful partition to stateful_file."""
86 image_dir = os.path.dirname(image_path)
87 image_file = os.path.basename(image_path)
88
89 get_offset = '$(cgpt show -b -i 1 %s)' % image_file
90 get_size = '$(cgpt show -s -i 1 %s)' % image_file
91 unpack_command = (
92 'cd %s && '
93 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file,
94 stateful_file, get_offset,
95 get_size))
Chris Sosa7c931362010-10-11 19:49:01 -070096 _LogMessage(unpack_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -070097 return os.system(unpack_command) == 0
98
99 def _UnpackZip(self, image_dir):
100 """Unpacks an image.zip into a given directory."""
101 image = os.path.join(image_dir, self._GetImageName())
102 if os.path.exists(image):
103 return True
104 else:
105 # -n, never clobber an existing file, in case we get invoked
106 # simultaneously by multiple request handlers. This means that
107 # we're assuming each image.zip file lives in a versioned
108 # directory (a la Buildbot).
109 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
110
111 def _GetImageName(self):
112 """Returns the name of the image that should be used."""
113 if self.use_test_image:
114 image_name = 'chromiumos_test_image.bin'
115 else:
116 image_name = 'chromiumos_image.bin'
117 return image_name
118
119 def _IsImageNewerThanCached(self, image_path, cached_file_path):
120 """Returns true if the image is newer than the cached image."""
121 if os.path.exists(cached_file_path) and os.path.exists(image_path):
Chris Sosa7c931362010-10-11 19:49:01 -0700122 _LogMessage('Usable cached image found at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700123 return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path)
124 elif not os.path.exists(cached_file_path) and not os.path.exists(image_path):
125 raise Exception('Image does not exist and cached image missing')
126 else:
127 # Only one is missing, figure out which one.
128 if os.path.exists(image_path):
Chris Sosa7c931362010-10-11 19:49:01 -0700129 _LogMessage('No cached image found - image generation required.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700130 return True
131 else:
Chris Sosa7c931362010-10-11 19:49:01 -0700132 _LogMessage('Cached image found to serve at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700133 return False
134
135 def _GetSize(self, update_path):
136 """Returns the size of the file given."""
137 return os.path.getsize(update_path)
138
139 def _GetHash(self, update_path):
140 """Returns the sha1 of the file given."""
141 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
142 % update_path)
143 return os.popen(cmd).read().rstrip()
144
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700145 def _IsDeltaFormatFile(self, filename):
146 try:
147 file_handle = open(filename, 'r')
148 delta_magic = 'CrAU'
149 magic = file_handle.read(len(delta_magic))
150 return magic == delta_magic
151 except Exception:
152 return False
153
Darin Petkov91436cb2010-09-28 08:52:17 -0700154 # TODO(petkov): Consider optimizing getting both SHA-1 and SHA-256 so that
155 # it takes advantage of reduced I/O and multiple processors. Something like:
156 # % tee < FILE > /dev/null \
157 # >( openssl dgst -sha256 -binary | openssl base64 ) \
158 # >( openssl sha1 -binary | openssl base64 )
159 def _GetSHA256(self, update_path):
160 """Returns the sha256 of the file given."""
161 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
162 update_path)
163 return os.popen(cmd).read().rstrip()
164
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700165 def GetUpdatePayload(self, hash, sha256, size, url, is_delta_format):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700166 """Returns a payload to the client corresponding to a new update.
167
168 Args:
169 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700170 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700171 size: size of update blob
172 url: where to find update blob
173 Returns:
174 Xml string to be passed back to client.
175 """
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700176 delta = 'false'
177 if is_delta_format:
178 delta = 'true'
rtc@google.com21a5ca32009-11-04 18:23:23 +0000179 payload = """<?xml version="1.0" encoding="UTF-8"?>
180 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700181 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000182 <app appid="{%s}" status="ok">
183 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700184 <updatecheck
185 codebase="%s"
186 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700187 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700188 needsadmin="false"
189 size="%s"
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700190 IsDelta="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +0000191 status="ok"/>
192 </app>
193 </gupdate>
194 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700195 return payload % (self._GetSecondsSinceMidnight(),
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700196 self.app_id, url, hash, sha256, size, delta)
rtc@google.comded22402009-10-26 22:36:21 +0000197
rtc@google.com21a5ca32009-11-04 18:23:23 +0000198 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700199 """Returns a payload to the client corresponding to no update."""
200 payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
201 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0" >
202 < daystart elapsed_seconds = "%s" />
203 < app appid = "{%s}" status = "ok" >
204 < ping status = "ok" />
205 < updatecheck status = "noupdate" />
206 </ app >
207 </ gupdate >
rtc@google.com21a5ca32009-11-04 18:23:23 +0000208 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700209 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000210
Chris Sosa0356d3b2010-09-16 15:46:22 -0700211 def GenerateUpdateFile(self, image_path):
212 """Generates an update gz given a full path to an image.
213
214 Args:
215 image_path: Full path to image.
216 Returns:
217 Path to created update_payload or None on error.
218 """
219 image_dir = os.path.dirname(image_path)
220 update_path = os.path.join(image_dir, 'update.gz')
Chris Sosa7c931362010-10-11 19:49:01 -0700221 _LogMessage('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700222
223 mkupdate_command = (
Chris Sosa62f720b2010-10-26 21:39:48 -0700224 '%s/cros_generate_update_payload --image="%s" --output="%s" '
225 '--patch_kernel --noold_style --src_image="%s"' % (
226 self.scripts_dir, image_path,
227 update_path, self.src_image))
228 _LogMessage(mkupdate_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700229 if os.system(mkupdate_command) != 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700230 _LogMessage('Failed to create base update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700231 return None
232
233 return update_path
234
235 def GenerateStatefulFile(self, image_path):
236 """Generates a stateful update gz given a full path to an image.
237
238 Args:
239 image_path: Full path to image.
240 Returns:
241 Path to created stateful update_payload or None on error.
242 """
243 stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path)
244
245 # Unpack to get stateful partition.
246 if self._UnpackStatefulPartition(image_path, stateful_partition_path):
247 mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path
248 if os.system(mkstatefulupdate_command) == 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700249 _LogMessage('Successfully generated %s.gz' % stateful_partition_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700250 return '%s.gz' % stateful_partition_path
251
Chris Sosa7c931362010-10-11 19:49:01 -0700252 _LogMessage('Failed to create stateful update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700253 return None
254
255 def MoveImagesToStaticDir(self, update_path, stateful_update_path,
256 static_image_dir):
257 """Moves gz files from their directories to serving directories.
258
259 Args:
260 update_path: full path to main update gz.
261 stateful_update_path: full path to stateful partition gz.
262 static_image_dir: where to put files.
263 Returns:
264 Returns True if the files were moved over successfully.
265 """
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700266 try:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700267 shutil.copy(update_path, static_image_dir)
268 shutil.copy(stateful_update_path, static_image_dir)
269 os.remove(update_path)
270 os.remove(stateful_update_path)
271 except Exception:
Chris Sosa7c931362010-10-11 19:49:01 -0700272 _LogMessage('Failed to move %s and %s to %s' % (update_path,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700273 stateful_update_path,
274 static_image_dir))
275 return False
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700276
rtc@google.com21a5ca32009-11-04 18:23:23 +0000277 return True
rtc@google.comded22402009-10-26 22:36:21 +0000278
Chris Sosa0356d3b2010-09-16 15:46:22 -0700279 def GenerateUpdateImage(self, image_path, move_to_static_dir=False,
280 static_image_dir=None):
281 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000282
Chris Sosa0356d3b2010-09-16 15:46:22 -0700283 Args:
284 image_path: full path to the image.
285 move_to_static_dir: Moves the files from their dir to the static dir.
286 static_image_dir: the directory to move images to after generating.
287 Returns:
288 True if the update payload was created successfully.
289 """
Chris Sosa7c931362010-10-11 19:49:01 -0700290 _LogMessage('Generating update for image %s' % image_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700291 update_path = self.GenerateUpdateFile(image_path)
292 stateful_update_path = self.GenerateStatefulFile(image_path)
293 if not update_path or not stateful_update_path:
Chris Sosa7c931362010-10-11 19:49:01 -0700294 _LogMessage('Failed to generate update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700295 return False
296
297 if move_to_static_dir:
298 return self.MoveImagesToStaticDir(update_path, stateful_update_path,
299 static_image_dir)
300 else:
301 return True
302
303 def GenerateLatestUpdateImage(self, board_id, client_version,
304 static_image_dir=None):
305 """Generates an update using the latest image that has been built.
306
307 This will only generate an update if the newest update is newer than that
308 on the client or client_version is 'ForcedUpdate'.
309
310 Args:
311 board_id: Name of the board.
312 client_version: Current version of the client or 'ForcedUpdate'
313 static_image_dir: the directory to move images to after generating.
314 Returns:
315 True if the update payload was created successfully.
316 """
317 latest_image_dir = self._GetLatestImageDir(board_id)
318 latest_version = self._GetVersionFromDir(latest_image_dir)
319 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
320
Chris Sosa7c931362010-10-11 19:49:01 -0700321 _LogMessage('Preparing to generate update from latest built image %s.' %
Chris Sosa0356d3b2010-09-16 15:46:22 -0700322 latest_image_path)
323
324 # Check to see whether or not we should update.
325 if client_version != 'ForcedUpdate' and not self._CanUpdate(
326 client_version, latest_version):
Chris Sosa7c931362010-10-11 19:49:01 -0700327 _LogMessage('no update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700328 return False
329
330 cached_file_path = os.path.join(static_image_dir, 'update.gz')
331 if (os.path.exists(cached_file_path) and
332 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
333 return True
334
335 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
336 static_image_dir=static_image_dir)
337
338 def GenerateImageFromZip(self, static_image_dir):
339 """Generates an update from an image zip file.
340
341 This method assumes you have an image.zip in directory you are serving
342 from. If this file is newer than a previously cached file, it will unzip
343 this file, create a payload and serve it.
344
345 Args:
346 static_image_dir: Directory where the zip file exists.
347 Returns:
348 True if the update payload was created successfully.
349 """
Chris Sosa7c931362010-10-11 19:49:01 -0700350 _LogMessage('Preparing to generate update from zip in %s.' % static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700351 image_path = os.path.join(static_image_dir, self._GetImageName())
352 cached_file_path = os.path.join(static_image_dir, 'update.gz')
353 zip_file_path = os.path.join(static_image_dir, 'image.zip')
354 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path):
355 return True
356
357 if not self._UnpackZip(static_image_dir):
Chris Sosa7c931362010-10-11 19:49:01 -0700358 _LogMessage('unzip image.zip failed.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700359 return False
360
361 return self.GenerateUpdateImage(image_path, move_to_static_dir=False,
362 static_image_dir=None)
Darin Petkov8ef83452010-03-23 16:52:29 -0700363
Andrew de los Reyes52620802010-04-12 13:40:07 -0700364 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
365 """Imports a factory-floor server configuration file. The file should
366 be in this format:
367 config = [
368 {
369 'qual_ids': set([1, 2, 3, "x86-generic"]),
370 'factory_image': 'generic-factory.gz',
371 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
372 'release_image': 'generic-release.gz',
373 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
374 'oempartitionimg_image': 'generic-oem.gz',
375 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700376 'efipartitionimg_image': 'generic-efi.gz',
377 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700378 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800379 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800380 'firmware_image': 'generic-firmware.gz',
381 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700382 },
383 {
384 'qual_ids': set([6]),
385 'factory_image': '6-factory.gz',
386 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
387 'release_image': '6-release.gz',
388 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
389 'oempartitionimg_image': '6-oem.gz',
390 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700391 'efipartitionimg_image': '6-efi.gz',
392 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700393 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800394 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800395 'firmware_image': '6-firmware.gz',
396 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700397 },
398 ]
399 The server will look for the files by name in the static files
400 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700401
Andrew de los Reyes52620802010-04-12 13:40:07 -0700402 If validate_checksums is True, validates checksums and exits. If
403 a checksum mismatch is found, it's printed to the screen.
404 """
405 f = open(filename, 'r')
406 output = {}
407 exec(f.read(), output)
408 self.factory_config = output['config']
409 success = True
410 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800411 for key in stanza.copy().iterkeys():
412 suffix = '_image'
413 if key.endswith(suffix):
414 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700415 stanza[kind + '_size'] = self._GetSize(os.path.join(
416 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800417 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700418 factory_checksum = self._GetHash(os.path.join(self.static_dir,
419 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800420 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700421 print ('Error: checksum mismatch for %s. Expected "%s" but file '
422 'has checksum "%s".' % (stanza[kind + '_image'],
423 stanza[kind + '_checksum'],
424 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800425 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700426
Andrew de los Reyes52620802010-04-12 13:40:07 -0700427 if validate_checksums:
428 if success is False:
429 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700430
Andrew de los Reyes52620802010-04-12 13:40:07 -0700431 print 'Config file looks good.'
432
433 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700434 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700435 for stanza in self.factory_config:
436 if board_id not in stanza['qual_ids']:
437 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700438 if kind + '_image' not in stanza:
439 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700440 return (stanza[kind + '_image'],
441 stanza[kind + '_checksum'],
442 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700443 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000444
Chris Sosa7c931362010-10-11 19:49:01 -0700445 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700446 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
447 if filename is None:
Chris Sosa7c931362010-10-11 19:49:01 -0700448 _LogMessage('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700449 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700450 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700451 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa7c931362010-10-11 19:49:01 -0700452 _LogMessage('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700453 # Factory install is using memento updater which is using the sha-1 hash so
454 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700455 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700456
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700457 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700458 """Handles an update ping from an update client.
459
460 Args:
461 data: xml blob from client.
462 label: optional label for the update.
463 Returns:
464 Update payload message for client.
465 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700466 # Set hostname as the hostname that the client is calling to and set up
467 # the url base.
468 self.hostname = cherrypy.request.base
469 if self.urlbase:
470 static_urlbase = self.urlbase
471 elif self.serve_only:
472 static_urlbase = '%s/static/archive' % self.hostname
473 else:
474 static_urlbase = '%s/static' % self.hostname
475
476 _LogMessage('Using static url base %s' % static_urlbase)
477 _LogMessage('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700478
479 # Check the client prefix to make sure you can support this type of update.
Chris Sosa9841e1c2010-10-14 10:51:45 -0700480 update_dom = minidom.parseString(data)
481 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700482 if (root.hasAttribute('updaterversion') and
483 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
Chris Sosa7c931362010-10-11 19:49:01 -0700484 _LogMessage('Got update from unsupported updater:' +
Chris Sosa0356d3b2010-09-16 15:46:22 -0700485 root.getAttribute('updaterversion'))
Andrew de los Reyes9223f132010-05-07 17:08:17 -0700486 return self.GetNoUpdatePayload()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700487
488 # We only generate update payloads for updatecheck requests.
489 update_check = root.getElementsByTagName('o:updatecheck')
490 if not update_check:
Chris Sosa7c931362010-10-11 19:49:01 -0700491 _LogMessage('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700492 # TODO(sosa): Generate correct non-updatecheck payload to better test
493 # update clients.
494 return self.GetNoUpdatePayload()
495
496 # Since this is an updatecheck, get information about the requester.
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700497 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800498 client_version = query.getAttribute('version')
Andrew de los Reyes52620802010-04-12 13:40:07 -0700499 channel = query.getAttribute('track')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700500 board_id = (query.hasAttribute('board') and query.getAttribute('board')
501 or self._GetDefaultBoardID())
Andrew de los Reyes52620802010-04-12 13:40:07 -0700502
Chris Sosa0356d3b2010-09-16 15:46:22 -0700503 # Separate logic as Factory requests have static url's that override
504 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700505 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700506 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700507 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700508 static_image_dir = self.static_dir
509 if label:
510 static_image_dir = os.path.join(static_image_dir, label)
511
Chris Sosa5d342a22010-09-28 16:54:41 -0700512 # Prefer cached image if it exists.
513 if self.use_cached and os.path.exists(os.path.join(static_image_dir,
514 'update.gz')):
Chris Sosa7c931362010-10-11 19:49:01 -0700515 _LogMessage('Using cached image regardless of timestamps.')
Chris Sosa5d342a22010-09-28 16:54:41 -0700516 has_built_image = True
Chris Sosa0356d3b2010-09-16 15:46:22 -0700517 else:
Chris Sosa5d342a22010-09-28 16:54:41 -0700518 if self.forced_image:
519 has_built_image = self.GenerateUpdateImage(
520 self.forced_image, move_to_static_dir=True,
521 static_image_dir=static_image_dir)
522 # Now that we've generated it, clear out so that other pings of same
523 # devserver instance do not generate new images.
524 self.forced_image = None
525 elif self.serve_only:
526 has_built_image = self.GenerateImageFromZip(static_image_dir)
527 else:
528 has_built_image = self.GenerateLatestUpdateImage(board_id,
529 client_version,
530 static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700531
532 if has_built_image:
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700533 filename = os.path.join(static_image_dir, 'update.gz')
534 hash = self._GetHash(filename)
535 sha256 = self._GetSHA256(filename)
536 size = self._GetSize(filename)
537 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa5d342a22010-09-28 16:54:41 -0700538 if label:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700539 url = '%s/%s/update.gz' % (static_urlbase, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700540 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700541 url = '%s/update.gz' % static_urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700542
Chris Sosa7c931362010-10-11 19:49:01 -0700543 _LogMessage('Responding to client to use url %s to get image.' % url)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700544 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700545 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700546 return self.GetNoUpdatePayload()