blob: 58455dc453b7085437e91b0fcda595740436dd8e [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
Chris Sosa7c931362010-10-11 19:49:01 -07007import cherrypy
rtc@google.comded22402009-10-26 22:36:21 +00008import os
Darin Petkov798fe7d2010-03-22 15:18:13 -07009import shutil
Chris Sosa05491b12010-11-08 17:14:16 -080010import subprocess
Chris Sosade91f672010-11-16 10:05:44 -080011import tempfile
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070012import time
Chris Sosa7c931362010-10-11 19:49:01 -070013
Chris Sosa05491b12010-11-08 17:14:16 -080014
Chris Sosa7c931362010-10-11 19:49:01 -070015def _LogMessage(message):
16 cherrypy.log(message, 'UPDATE')
rtc@google.comded22402009-10-26 22:36:21 +000017
Chris Sosa0356d3b2010-09-16 15:46:22 -070018
rtc@google.com64244662009-11-12 00:52:08 +000019class Autoupdate(BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -070020 """Class that contains functionality that handles Chrome OS update pings.
21
22 Members:
23 serve_only: Serve images from a pre-built image.zip file. static_dir
24 must be set to the location of the image.zip.
25 factory_config: Path to the factory config file if handling factory
26 requests.
27 use_test_image: Use chromiumos_test_image.bin rather than the standard.
28 static_url_base: base URL, other than devserver, for update images.
29 client_prefix: The prefix for the update engine client.
30 forced_image: Path to an image to use for all updates.
31 """
rtc@google.comded22402009-10-26 22:36:21 +000032
Sean O'Connor1f7fd362010-04-07 16:34:52 -070033 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Chris Sosa0356d3b2010-09-16 15:46:22 -070034 factory_config_path=None, client_prefix=None, forced_image=None,
Chris Sosade91f672010-11-16 10:05:44 -080035 use_cached=False, port=8080, src_image='', vm=False, board=None,
Chris Sosae67b78f2010-11-04 17:33:16 -070036 *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070037 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -070038 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -070039 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 15:46:22 -070040 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 16:54:41 -070041 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -070042 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -070043 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -070044 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -070045
Chris Sosab63a9282010-09-02 10:43:23 -070046 self.client_prefix = client_prefix
Chris Sosa0356d3b2010-09-16 15:46:22 -070047 self.forced_image = forced_image
Chris Sosade91f672010-11-16 10:05:44 -080048 self.use_cached = use_cached
Chris Sosa62f720b2010-10-26 21:39:48 -070049 self.src_image = src_image
Chris Sosa4136e692010-10-28 23:42:37 -070050 self.vm = vm
Chris Sosae67b78f2010-11-04 17:33:16 -070051 self.board = board
Chris Sosa05491b12010-11-08 17:14:16 -080052 self.crosutils = os.path.join(os.path.dirname(__file__), '../../scripts')
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070053
Chris Sosa0356d3b2010-09-16 15:46:22 -070054 def _GetSecondsSinceMidnight(self):
55 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070056 now = time.localtime()
57 return now[3] * 3600 + now[4] * 60 + now[5]
58
Chris Sosa0356d3b2010-09-16 15:46:22 -070059 def _GetDefaultBoardID(self):
60 """Returns the default board id stored in .default_board."""
61 board_file = '%s/.default_board' % (self.scripts_dir)
62 try:
63 return open(board_file).read()
64 except IOError:
65 return 'x86-generic'
66
67 def _GetLatestImageDir(self, board_id):
68 """Returns the latest image dir based on shell script."""
69 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
70 return os.popen(cmd).read().strip()
71
72 def _GetVersionFromDir(self, image_dir):
73 """Returns the version of the image based on the name of the directory."""
74 latest_version = os.path.basename(image_dir)
75 return latest_version.split('-')[0]
76
77 def _CanUpdate(self, client_version, latest_version):
Chris Sosade91f672010-11-16 10:05:44 -080078 """Returns true if the latest_version is greater than the client_version."""
Chris Sosa0356d3b2010-09-16 15:46:22 -070079 client_tokens = client_version.replace('_', '').split('.')
80 latest_tokens = latest_version.replace('_', '').split('.')
Chris Sosa7c931362010-10-11 19:49:01 -070081 _LogMessage('client version %s latest version %s'
Chris Sosade91f672010-11-16 10:05:44 -080082 % (client_version, latest_version))
Chris Sosa0356d3b2010-09-16 15:46:22 -070083 for i in range(4):
84 if int(latest_tokens[i]) == int(client_tokens[i]):
85 continue
86 return int(latest_tokens[i]) > int(client_tokens[i])
87 return False
88
Chris Sosa0356d3b2010-09-16 15:46:22 -070089 def _UnpackZip(self, image_dir):
90 """Unpacks an image.zip into a given directory."""
91 image = os.path.join(image_dir, self._GetImageName())
92 if os.path.exists(image):
93 return True
94 else:
95 # -n, never clobber an existing file, in case we get invoked
96 # simultaneously by multiple request handlers. This means that
97 # we're assuming each image.zip file lives in a versioned
98 # directory (a la Buildbot).
99 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
100
101 def _GetImageName(self):
102 """Returns the name of the image that should be used."""
103 if self.use_test_image:
104 image_name = 'chromiumos_test_image.bin'
105 else:
106 image_name = 'chromiumos_image.bin'
107 return image_name
108
Chris Sosade91f672010-11-16 10:05:44 -0800109 def _IsImageNewerThanCached(self, image_path, cached_file_path):
110 """Returns true if the image is newer than the cached image."""
111 if os.path.exists(cached_file_path) and os.path.exists(image_path):
112 _LogMessage('Usable cached image found at %s.' % cached_file_path)
113 return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path)
114 elif not os.path.exists(cached_file_path) and not os.path.exists(image_path):
115 raise Exception('Image does not exist and cached image missing')
116 else:
117 # Only one is missing, figure out which one.
118 if os.path.exists(image_path):
119 _LogMessage('No cached image found - image generation required.')
120 return True
121 else:
122 _LogMessage('Cached image found to serve at %s.' % cached_file_path)
123 return False
124
Chris Sosa0356d3b2010-09-16 15:46:22 -0700125 def _GetSize(self, update_path):
126 """Returns the size of the file given."""
127 return os.path.getsize(update_path)
128
129 def _GetHash(self, update_path):
130 """Returns the sha1 of the file given."""
131 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
132 % update_path)
133 return os.popen(cmd).read().rstrip()
134
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700135 def _IsDeltaFormatFile(self, filename):
136 try:
137 file_handle = open(filename, 'r')
138 delta_magic = 'CrAU'
139 magic = file_handle.read(len(delta_magic))
140 return magic == delta_magic
141 except Exception:
142 return False
143
Darin Petkov91436cb2010-09-28 08:52:17 -0700144 # TODO(petkov): Consider optimizing getting both SHA-1 and SHA-256 so that
145 # it takes advantage of reduced I/O and multiple processors. Something like:
146 # % tee < FILE > /dev/null \
147 # >( openssl dgst -sha256 -binary | openssl base64 ) \
148 # >( openssl sha1 -binary | openssl base64 )
149 def _GetSHA256(self, update_path):
150 """Returns the sha256 of the file given."""
151 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
152 update_path)
153 return os.popen(cmd).read().rstrip()
154
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700155 def GetUpdatePayload(self, hash, sha256, size, url, is_delta_format):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700156 """Returns a payload to the client corresponding to a new update.
157
158 Args:
159 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700160 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700161 size: size of update blob
162 url: where to find update blob
163 Returns:
164 Xml string to be passed back to client.
165 """
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700166 delta = 'false'
167 if is_delta_format:
168 delta = 'true'
rtc@google.com21a5ca32009-11-04 18:23:23 +0000169 payload = """<?xml version="1.0" encoding="UTF-8"?>
170 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700171 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000172 <app appid="{%s}" status="ok">
173 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700174 <updatecheck
175 codebase="%s"
176 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700177 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700178 needsadmin="false"
179 size="%s"
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700180 IsDelta="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +0000181 status="ok"/>
182 </app>
183 </gupdate>
184 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700185 return payload % (self._GetSecondsSinceMidnight(),
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700186 self.app_id, url, hash, sha256, size, delta)
rtc@google.comded22402009-10-26 22:36:21 +0000187
rtc@google.com21a5ca32009-11-04 18:23:23 +0000188 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700189 """Returns a payload to the client corresponding to no update."""
190 payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
191 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0" >
192 < daystart elapsed_seconds = "%s" />
193 < app appid = "{%s}" status = "ok" >
194 < ping status = "ok" />
195 < updatecheck status = "noupdate" />
196 </ app >
197 </ gupdate >
rtc@google.com21a5ca32009-11-04 18:23:23 +0000198 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700199 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000200
Chris Sosade91f672010-11-16 10:05:44 -0800201 def GenerateUpdateFile(self, image_path):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700202 """Generates an update gz given a full path to an image.
203
204 Args:
205 image_path: Full path to image.
206 Returns:
207 Path to created update_payload or None on error.
208 """
Chris Sosade91f672010-11-16 10:05:44 -0800209 image_dir = os.path.dirname(image_path)
210 update_path = os.path.join(image_dir, 'update.gz')
Chris Sosa4136e692010-10-28 23:42:37 -0700211 patch_kernel_flag = '--patch_kernel'
Chris Sosa7c931362010-10-11 19:49:01 -0700212 _LogMessage('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700213
Chris Sosa4136e692010-10-28 23:42:37 -0700214 # Don't patch the kernel for vm images as they don't need the patch.
215 if self.vm:
216 patch_kernel_flag = ''
217
Chris Sosa0356d3b2010-09-16 15:46:22 -0700218 mkupdate_command = (
Chris Sosa62f720b2010-10-26 21:39:48 -0700219 '%s/cros_generate_update_payload --image="%s" --output="%s" '
Chris Sosa4136e692010-10-28 23:42:37 -0700220 '%s --noold_style --src_image="%s"' % (
221 self.scripts_dir, image_path, update_path, patch_kernel_flag,
Chris Sosade91f672010-11-16 10:05:44 -0800222 self.src_image))
Chris Sosa62f720b2010-10-26 21:39:48 -0700223 _LogMessage(mkupdate_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700224 if os.system(mkupdate_command) != 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700225 _LogMessage('Failed to create base update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700226 return None
227
228 return update_path
229
Chris Sosade91f672010-11-16 10:05:44 -0800230 def GenerateStatefulFile(self, image_path):
231 """Generates a stateful update given a full path to an image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700232
233 Args:
234 image_path: Full path to image.
235 Returns:
Chris Sosade91f672010-11-16 10:05:44 -0800236 Path to created stateful update_payload.
Chris Sosa908fd6f2010-11-10 17:31:18 -0800237 Raises:
238 A subprocess exception if the update generator fails to generate a
239 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700240 """
Chris Sosade91f672010-11-16 10:05:44 -0800241 work_dir = os.path.dirname(image_path)
242 output_gz = os.path.join(work_dir, 'stateful.tgz')
Chris Sosa908fd6f2010-11-10 17:31:18 -0800243 subprocess.check_call(
244 ['%s/cros_generate_stateful_update_payload' % self.crosutils,
245 '--image=%s' % image_path,
Chris Sosade91f672010-11-16 10:05:44 -0800246 '--output_dir=%s' % work_dir,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800247 ])
Chris Sosa05491b12010-11-08 17:14:16 -0800248 return output_gz
Chris Sosa0356d3b2010-09-16 15:46:22 -0700249
Chris Sosade91f672010-11-16 10:05:44 -0800250 def MoveImagesToStaticDir(self, update_path, stateful_update_path,
251 static_image_dir):
252 """Moves gz files from their directories to serving directories.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700253
Chris Sosade91f672010-11-16 10:05:44 -0800254 Args:
255 update_path: full path to main update gz.
256 stateful_update_path: full path to stateful partition gz.
257 static_image_dir: where to put files.
258 Returns:
259 Returns True if the files were moved over successfully.
260 """
261 try:
262 shutil.copy(update_path, static_image_dir)
263 shutil.copy(stateful_update_path, static_image_dir)
264 os.remove(update_path)
265 os.remove(stateful_update_path)
266 except Exception:
267 _LogMessage('Failed to move %s and %s to %s' % (update_path,
268 stateful_update_path,
269 static_image_dir))
270 return False
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700271
Chris Sosade91f672010-11-16 10:05:44 -0800272 return True
rtc@google.comded22402009-10-26 22:36:21 +0000273
Chris Sosade91f672010-11-16 10:05:44 -0800274 def GenerateUpdateImage(self, image_path, move_to_static_dir=False,
275 static_image_dir=None):
276 """Generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000277
Chris Sosa0356d3b2010-09-16 15:46:22 -0700278 Args:
279 image_path: full path to the image.
280 move_to_static_dir: Moves the files from their dir to the static dir.
281 static_image_dir: the directory to move images to after generating.
282 Returns:
Chris Sosade91f672010-11-16 10:05:44 -0800283 True if the update payload was created successfully.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700284 """
Chris Sosa7c931362010-10-11 19:49:01 -0700285 _LogMessage('Generating update for image %s' % image_path)
Chris Sosade91f672010-11-16 10:05:44 -0800286 update_path = self.GenerateUpdateFile(image_path)
287 if update_path:
288 stateful_update_path = self.GenerateStatefulFile(image_path)
289 if move_to_static_dir:
290 return self.MoveImagesToStaticDir(update_path, stateful_update_path,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800291 static_image_dir)
Chris Sosade91f672010-11-16 10:05:44 -0800292 return True
Chris Sosae67b78f2010-11-04 17:33:16 -0700293
294 _LogMessage('Failed to generate update')
Chris Sosade91f672010-11-16 10:05:44 -0800295 return False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700296
297 def GenerateLatestUpdateImage(self, board_id, client_version,
Chris Sosade91f672010-11-16 10:05:44 -0800298 static_image_dir=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700299 """Generates an update using the latest image that has been built.
300
301 This will only generate an update if the newest update is newer than that
302 on the client or client_version is 'ForcedUpdate'.
303
304 Args:
305 board_id: Name of the board.
306 client_version: Current version of the client or 'ForcedUpdate'
307 static_image_dir: the directory to move images to after generating.
308 Returns:
Chris Sosade91f672010-11-16 10:05:44 -0800309 True if the update payload was created successfully.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700310 """
311 latest_image_dir = self._GetLatestImageDir(board_id)
312 latest_version = self._GetVersionFromDir(latest_image_dir)
313 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
314
Chris Sosa7c931362010-10-11 19:49:01 -0700315 _LogMessage('Preparing to generate update from latest built image %s.' %
Chris Sosa0356d3b2010-09-16 15:46:22 -0700316 latest_image_path)
317
318 # Check to see whether or not we should update.
319 if client_version != 'ForcedUpdate' and not self._CanUpdate(
320 client_version, latest_version):
Chris Sosa7c931362010-10-11 19:49:01 -0700321 _LogMessage('no update')
Chris Sosade91f672010-11-16 10:05:44 -0800322 return False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700323
Chris Sosade91f672010-11-16 10:05:44 -0800324 cached_file_path = os.path.join(static_image_dir, 'update.gz')
325 if (os.path.exists(cached_file_path) and
326 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
327 return True
328
329 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
330 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700331
332 def GenerateImageFromZip(self, static_image_dir):
333 """Generates an update from an image zip file.
334
335 This method assumes you have an image.zip in directory you are serving
336 from. If this file is newer than a previously cached file, it will unzip
337 this file, create a payload and serve it.
338
339 Args:
340 static_image_dir: Directory where the zip file exists.
341 Returns:
Chris Sosade91f672010-11-16 10:05:44 -0800342 True if the update payload was created successfully.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700343 """
Chris Sosade91f672010-11-16 10:05:44 -0800344 _LogMessage('Preparing to generate update from zip in %s.' % static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700345 image_path = os.path.join(static_image_dir, self._GetImageName())
Chris Sosade91f672010-11-16 10:05:44 -0800346 cached_file_path = os.path.join(static_image_dir, 'update.gz')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700347 zip_file_path = os.path.join(static_image_dir, 'image.zip')
Chris Sosade91f672010-11-16 10:05:44 -0800348 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path):
349 return True
Chris Sosa0356d3b2010-09-16 15:46:22 -0700350
351 if not self._UnpackZip(static_image_dir):
Chris Sosa7c931362010-10-11 19:49:01 -0700352 _LogMessage('unzip image.zip failed.')
Chris Sosade91f672010-11-16 10:05:44 -0800353 return False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700354
Chris Sosade91f672010-11-16 10:05:44 -0800355 return self.GenerateUpdateImage(image_path, move_to_static_dir=False,
356 static_image_dir=None)
Darin Petkov8ef83452010-03-23 16:52:29 -0700357
Andrew de los Reyes52620802010-04-12 13:40:07 -0700358 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
359 """Imports a factory-floor server configuration file. The file should
360 be in this format:
361 config = [
362 {
363 'qual_ids': set([1, 2, 3, "x86-generic"]),
364 'factory_image': 'generic-factory.gz',
365 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
366 'release_image': 'generic-release.gz',
367 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
368 'oempartitionimg_image': 'generic-oem.gz',
369 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700370 'efipartitionimg_image': 'generic-efi.gz',
371 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700372 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800373 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800374 'firmware_image': 'generic-firmware.gz',
375 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700376 },
377 {
378 'qual_ids': set([6]),
379 'factory_image': '6-factory.gz',
380 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
381 'release_image': '6-release.gz',
382 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
383 'oempartitionimg_image': '6-oem.gz',
384 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700385 'efipartitionimg_image': '6-efi.gz',
386 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700387 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800388 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800389 'firmware_image': '6-firmware.gz',
390 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700391 },
392 ]
393 The server will look for the files by name in the static files
394 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700395
Andrew de los Reyes52620802010-04-12 13:40:07 -0700396 If validate_checksums is True, validates checksums and exits. If
397 a checksum mismatch is found, it's printed to the screen.
398 """
399 f = open(filename, 'r')
400 output = {}
401 exec(f.read(), output)
402 self.factory_config = output['config']
403 success = True
404 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800405 for key in stanza.copy().iterkeys():
406 suffix = '_image'
407 if key.endswith(suffix):
408 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700409 stanza[kind + '_size'] = self._GetSize(os.path.join(
410 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800411 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700412 factory_checksum = self._GetHash(os.path.join(self.static_dir,
413 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800414 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700415 print ('Error: checksum mismatch for %s. Expected "%s" but file '
416 'has checksum "%s".' % (stanza[kind + '_image'],
417 stanza[kind + '_checksum'],
418 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800419 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700420
Andrew de los Reyes52620802010-04-12 13:40:07 -0700421 if validate_checksums:
422 if success is False:
423 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700424
Andrew de los Reyes52620802010-04-12 13:40:07 -0700425 print 'Config file looks good.'
426
427 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700428 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700429 for stanza in self.factory_config:
430 if board_id not in stanza['qual_ids']:
431 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700432 if kind + '_image' not in stanza:
433 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700434 return (stanza[kind + '_image'],
435 stanza[kind + '_checksum'],
436 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700437 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000438
Chris Sosa7c931362010-10-11 19:49:01 -0700439 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700440 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
441 if filename is None:
Chris Sosa7c931362010-10-11 19:49:01 -0700442 _LogMessage('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700443 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700444 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700445 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa7c931362010-10-11 19:49:01 -0700446 _LogMessage('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700447 # Factory install is using memento updater which is using the sha-1 hash so
448 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700449 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700450
Chris Sosa151643e2010-10-28 14:40:57 -0700451 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
452 static_image_dir):
Chris Sosade91f672010-11-16 10:05:44 -0800453 """Generates an update for non-factory and returns True on success."""
454 if self.use_cached and os.path.exists(os.path.join(static_image_dir,
455 'update.gz')):
456 _LogMessage('Using cached image regardless of timestamps.')
457 return True
Don Garrett710470d2010-11-15 17:43:44 -0800458 else:
Chris Sosade91f672010-11-16 10:05:44 -0800459 if self.forced_image:
460 has_built_image = self.GenerateUpdateImage(
461 self.forced_image, move_to_static_dir=True,
462 static_image_dir=static_image_dir)
463 return has_built_image
464 elif self.serve_only:
465 return self.GenerateImageFromZip(static_image_dir)
466 else:
467 if board_id:
468 return self.GenerateLatestUpdateImage(board_id,
469 client_version,
470 static_image_dir)
Don Garrett710470d2010-11-15 17:43:44 -0800471
Chris Sosade91f672010-11-16 10:05:44 -0800472 _LogMessage('You must set --board for pre-generating latest update.')
473 return False
Chris Sosa2c048f12010-10-27 16:05:27 -0700474
475 def PreGenerateUpdate(self):
Chris Sosade91f672010-11-16 10:05:44 -0800476 """Pre-generates an update. Returns True on success."""
Chris Sosa2c048f12010-10-27 16:05:27 -0700477 # Does not work with factory config.
478 assert(not self.factory_config)
479 _LogMessage('Pre-generating the update payload.')
480 # Does not work with labels so just use static dir.
Chris Sosae67b78f2010-11-04 17:33:16 -0700481 if self.GenerateUpdatePayloadForNonFactory(self.board, '0.0.0.0',
482 self.static_dir):
Chris Sosade91f672010-11-16 10:05:44 -0800483 # Force the devserver to use the pre-generated payload.
484 self.use_cached = True
Chris Sosae67b78f2010-11-04 17:33:16 -0700485 _LogMessage('Pre-generated update successfully.')
486 return True
Chris Sosa2c048f12010-10-27 16:05:27 -0700487 else:
488 _LogMessage('Failed to pre-generate update.')
Chris Sosae67b78f2010-11-04 17:33:16 -0700489 return False
Chris Sosa2c048f12010-10-27 16:05:27 -0700490
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700491 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700492 """Handles an update ping from an update client.
493
494 Args:
495 data: xml blob from client.
496 label: optional label for the update.
497 Returns:
498 Update payload message for client.
499 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700500 # Set hostname as the hostname that the client is calling to and set up
501 # the url base.
502 self.hostname = cherrypy.request.base
503 if self.urlbase:
504 static_urlbase = self.urlbase
505 elif self.serve_only:
506 static_urlbase = '%s/static/archive' % self.hostname
507 else:
508 static_urlbase = '%s/static' % self.hostname
509
510 _LogMessage('Using static url base %s' % static_urlbase)
511 _LogMessage('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700512
513 # Check the client prefix to make sure you can support this type of update.
Chris Sosa9841e1c2010-10-14 10:51:45 -0700514 update_dom = minidom.parseString(data)
515 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700516 if (root.hasAttribute('updaterversion') and
517 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
Chris Sosa7c931362010-10-11 19:49:01 -0700518 _LogMessage('Got update from unsupported updater:' +
Chris Sosa0356d3b2010-09-16 15:46:22 -0700519 root.getAttribute('updaterversion'))
Andrew de los Reyes9223f132010-05-07 17:08:17 -0700520 return self.GetNoUpdatePayload()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700521
522 # We only generate update payloads for updatecheck requests.
523 update_check = root.getElementsByTagName('o:updatecheck')
524 if not update_check:
Chris Sosa7c931362010-10-11 19:49:01 -0700525 _LogMessage('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700526 # TODO(sosa): Generate correct non-updatecheck payload to better test
527 # update clients.
528 return self.GetNoUpdatePayload()
529
530 # Since this is an updatecheck, get information about the requester.
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700531 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800532 client_version = query.getAttribute('version')
Andrew de los Reyes52620802010-04-12 13:40:07 -0700533 channel = query.getAttribute('track')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700534 board_id = (query.hasAttribute('board') and query.getAttribute('board')
535 or self._GetDefaultBoardID())
Andrew de los Reyes52620802010-04-12 13:40:07 -0700536
Chris Sosa0356d3b2010-09-16 15:46:22 -0700537 # Separate logic as Factory requests have static url's that override
538 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700539 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700540 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700541 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700542 static_image_dir = self.static_dir
543 if label:
544 static_image_dir = os.path.join(static_image_dir, label)
545
Chris Sosade91f672010-11-16 10:05:44 -0800546 if self.GenerateUpdatePayloadForNonFactory(board_id, client_version,
547 static_image_dir):
548 filename = os.path.join(static_image_dir, 'update.gz')
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700549 hash = self._GetHash(filename)
550 sha256 = self._GetSHA256(filename)
551 size = self._GetSize(filename)
552 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa5d342a22010-09-28 16:54:41 -0700553 if label:
Chris Sosade91f672010-11-16 10:05:44 -0800554 url = '%s/%s/update.gz' % (static_urlbase, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700555 else:
Chris Sosade91f672010-11-16 10:05:44 -0800556 url = '%s/update.gz' % static_urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700557
Chris Sosa7c931362010-10-11 19:49:01 -0700558 _LogMessage('Responding to client to use url %s to get image.' % url)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700559 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700560 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700561 return self.GetNoUpdatePayload()