blob: c17c103332b1c8ba383534f57fe599847931cb55 [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
Don Garrett710470d2010-11-15 17:43:44 -08007
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 Sosa05491b12010-11-08 17:14:16 -080011import subprocess
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,
Don Garrett710470d2010-11-15 17:43:44 -080035 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 Sosa62f720b2010-10-26 21:39:48 -070048 self.src_image = src_image
Chris Sosa4136e692010-10-28 23:42:37 -070049 self.vm = vm
Chris Sosae67b78f2010-11-04 17:33:16 -070050 self.board = board
Chris Sosa05491b12010-11-08 17:14:16 -080051 self.crosutils = os.path.join(os.path.dirname(__file__), '../../scripts')
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070052
Chris Sosa0356d3b2010-09-16 15:46:22 -070053 def _GetSecondsSinceMidnight(self):
54 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070055 now = time.localtime()
56 return now[3] * 3600 + now[4] * 60 + now[5]
57
Chris Sosa0356d3b2010-09-16 15:46:22 -070058 def _GetDefaultBoardID(self):
59 """Returns the default board id stored in .default_board."""
60 board_file = '%s/.default_board' % (self.scripts_dir)
61 try:
62 return open(board_file).read()
63 except IOError:
64 return 'x86-generic'
65
66 def _GetLatestImageDir(self, board_id):
67 """Returns the latest image dir based on shell script."""
68 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
69 return os.popen(cmd).read().strip()
70
71 def _GetVersionFromDir(self, image_dir):
72 """Returns the version of the image based on the name of the directory."""
73 latest_version = os.path.basename(image_dir)
74 return latest_version.split('-')[0]
75
76 def _CanUpdate(self, client_version, latest_version):
Don Garrett710470d2010-11-15 17:43:44 -080077 """Returns true if the latest_version is greater than the client_version.
78 """
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'
Don Garrett710470d2010-11-15 17:43: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 Sosa0356d3b2010-09-16 15:46:22 -0700109 def _GetSize(self, update_path):
110 """Returns the size of the file given."""
111 return os.path.getsize(update_path)
112
113 def _GetHash(self, update_path):
114 """Returns the sha1 of the file given."""
115 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
116 % update_path)
117 return os.popen(cmd).read().rstrip()
118
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700119 def _IsDeltaFormatFile(self, filename):
120 try:
121 file_handle = open(filename, 'r')
122 delta_magic = 'CrAU'
123 magic = file_handle.read(len(delta_magic))
124 return magic == delta_magic
125 except Exception:
126 return False
127
Darin Petkov91436cb2010-09-28 08:52:17 -0700128 # TODO(petkov): Consider optimizing getting both SHA-1 and SHA-256 so that
129 # it takes advantage of reduced I/O and multiple processors. Something like:
130 # % tee < FILE > /dev/null \
131 # >( openssl dgst -sha256 -binary | openssl base64 ) \
132 # >( openssl sha1 -binary | openssl base64 )
133 def _GetSHA256(self, update_path):
134 """Returns the sha256 of the file given."""
135 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
136 update_path)
137 return os.popen(cmd).read().rstrip()
138
Don Garrett710470d2010-11-15 17:43:44 -0800139 def _GetMd5(self, update_path):
140 """Returns the md5 checksum of the file given."""
141 cmd = ("md5sum %s | awk '{print $1}'" % update_path)
142 return os.popen(cmd).read().rstrip()
143
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700144 def GetUpdatePayload(self, hash, sha256, size, url, is_delta_format):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700145 """Returns a payload to the client corresponding to a new update.
146
147 Args:
148 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700149 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700150 size: size of update blob
151 url: where to find update blob
152 Returns:
153 Xml string to be passed back to client.
154 """
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700155 delta = 'false'
156 if is_delta_format:
157 delta = 'true'
rtc@google.com21a5ca32009-11-04 18:23:23 +0000158 payload = """<?xml version="1.0" encoding="UTF-8"?>
159 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700160 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000161 <app appid="{%s}" status="ok">
162 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700163 <updatecheck
164 codebase="%s"
165 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700166 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700167 needsadmin="false"
168 size="%s"
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700169 IsDelta="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +0000170 status="ok"/>
171 </app>
172 </gupdate>
173 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700174 return payload % (self._GetSecondsSinceMidnight(),
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700175 self.app_id, url, hash, sha256, size, delta)
rtc@google.comded22402009-10-26 22:36:21 +0000176
rtc@google.com21a5ca32009-11-04 18:23:23 +0000177 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700178 """Returns a payload to the client corresponding to no update."""
179 payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
180 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0" >
181 < daystart elapsed_seconds = "%s" />
182 < app appid = "{%s}" status = "ok" >
183 < ping status = "ok" />
184 < updatecheck status = "noupdate" />
185 </ app >
186 </ gupdate >
rtc@google.com21a5ca32009-11-04 18:23:23 +0000187 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700188 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000189
Don Garrett710470d2010-11-15 17:43:44 -0800190 def GenerateUpdateFile(self, src_image, image_path, static_image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700191 """Generates an update gz given a full path to an image.
192
193 Args:
194 image_path: Full path to image.
195 Returns:
196 Path to created update_payload or None on error.
197 """
Don Garrett710470d2010-11-15 17:43:44 -0800198 update_path = os.path.join(static_image_dir, 'update.gz')
Chris Sosa4136e692010-10-28 23:42:37 -0700199 patch_kernel_flag = '--patch_kernel'
Chris Sosa7c931362010-10-11 19:49:01 -0700200 _LogMessage('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700201
Chris Sosa4136e692010-10-28 23:42:37 -0700202 # Don't patch the kernel for vm images as they don't need the patch.
203 if self.vm:
204 patch_kernel_flag = ''
205
Chris Sosa0356d3b2010-09-16 15:46:22 -0700206 mkupdate_command = (
Chris Sosa62f720b2010-10-26 21:39:48 -0700207 '%s/cros_generate_update_payload --image="%s" --output="%s" '
Chris Sosa4136e692010-10-28 23:42:37 -0700208 '%s --noold_style --src_image="%s"' % (
209 self.scripts_dir, image_path, update_path, patch_kernel_flag,
Don Garrett710470d2010-11-15 17:43:44 -0800210 src_image))
Chris Sosa62f720b2010-10-26 21:39:48 -0700211 _LogMessage(mkupdate_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700212 if os.system(mkupdate_command) != 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700213 _LogMessage('Failed to create base update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700214 return None
215
216 return update_path
217
Don Garrett710470d2010-11-15 17:43:44 -0800218 def GenerateStatefulFile(self, image_path, static_image_dir):
219 """Generates a stateful update payload given a full path to an image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700220
221 Args:
222 image_path: Full path to image.
223 Returns:
Don Garrett710470d2010-11-15 17:43:44 -0800224 Path to created stateful update_payload or None on error.
Chris Sosa908fd6f2010-11-10 17:31:18 -0800225 Raises:
226 A subprocess exception if the update generator fails to generate a
227 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700228 """
Don Garrett710470d2010-11-15 17:43:44 -0800229 output_gz = os.path.join(static_image_dir, 'stateful.tgz')
Chris Sosa908fd6f2010-11-10 17:31:18 -0800230 subprocess.check_call(
231 ['%s/cros_generate_stateful_update_payload' % self.crosutils,
232 '--image=%s' % image_path,
Don Garrett710470d2010-11-15 17:43:44 -0800233 '--output_dir=%s' % static_image_dir,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800234 ])
Chris Sosa05491b12010-11-08 17:14:16 -0800235 return output_gz
Chris Sosa0356d3b2010-09-16 15:46:22 -0700236
Don Garrett710470d2010-11-15 17:43:44 -0800237 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
238 """Find directory to store a cached update.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700239
Don Garrett710470d2010-11-15 17:43:44 -0800240 Given one, or two images for an update, this finds which
241 cache directory should hold the update files, even if they don't exist
242 yet. The directory will be inside static_image_dir, and of the form:
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700243
Don Garrett710470d2010-11-15 17:43:44 -0800244 Non-delta updates:
245 cache/12345678
rtc@google.comded22402009-10-26 22:36:21 +0000246
Don Garrett710470d2010-11-15 17:43:44 -0800247 Delta updates:
248 cache/12345678_12345678
249 """
250 # If there is no src, we only have an image file, check image for changes
251 if not src_image:
252 return os.path.join('cache', self._GetMd5(dest_image))
253
254 # If we have src and dest, we are a delta, and check both for changes
255 return os.path.join('cache',
256 "%s_%s" % (self._GetMd5(src_image),
257 self._GetMd5(dest_image)))
258
259 def GenerateUpdateImage(self, src_image, image_path, static_image_dir):
260 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000261
Chris Sosa0356d3b2010-09-16 15:46:22 -0700262 Args:
263 image_path: full path to the image.
264 move_to_static_dir: Moves the files from their dir to the static dir.
265 static_image_dir: the directory to move images to after generating.
266 Returns:
Don Garrett710470d2010-11-15 17:43:44 -0800267 update filename (not directory) on success, or None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700268 """
Don Garrett710470d2010-11-15 17:43:44 -0800269 update_file = None
270 stateful_update_file = None
271
272 # Actually do the generation
Chris Sosa7c931362010-10-11 19:49:01 -0700273 _LogMessage('Generating update for image %s' % image_path)
Don Garrett710470d2010-11-15 17:43:44 -0800274 update_file = self.GenerateUpdateFile(src_image,
275 image_path,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800276 static_image_dir)
Don Garrett710470d2010-11-15 17:43:44 -0800277
278 if update_file:
279 stateful_update_file = self.GenerateStatefulFile(image_path,
280 static_image_dir)
281
282 if update_file and stateful_update_file:
283 return os.path.basename(update_file)
Chris Sosae67b78f2010-11-04 17:33:16 -0700284
285 _LogMessage('Failed to generate update')
Don Garrett710470d2010-11-15 17:43:44 -0800286
287 # Cleanup incomplete files, if they exist
288 if update_file and os.path.exists(update_file):
289 os.remove(update_file)
290
291 return None
292
293 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
294 """Force generates an update payload based on the given image_path.
295
296 Args:
297 image_path: full path to the image.
298 move_to_static_dir: Moves the files from their dir to the static dir.
299 static_image_dir: the directory to move images to after generating.
300 Returns:
301 update filename (not directory) relative to static_image_dir on success,
302 or None
303 """
304 _LogMessage('Generating update for src %s image %s' % (self.src_image,
305 image_path))
306
307 # Which sub_dir of static_image_dir should hold our cached update image
308 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
309 _LogMessage('Caching in sub_dir "%s"' % cache_sub_dir)
310
311 # Check for a cached image to use
312 if os.path.exists(os.path.join(static_image_dir,
313 cache_sub_dir,
314 'update.gz')):
315 return os.path.join(cache_sub_dir, 'update.gz')
316
317 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
318
319 # Create the directory for the cache values
320 if not os.path.exists(full_cache_dir):
321 os.makedirs(full_cache_dir)
322
323 gen_image = self.GenerateUpdateImage(self.src_image,
324 image_path,
325 full_cache_dir)
326
327 # If the generation worked
328 if gen_image:
329 return os.path.join(cache_sub_dir, gen_image)
330 else:
331 return None
332
Chris Sosa0356d3b2010-09-16 15:46:22 -0700333
334 def GenerateLatestUpdateImage(self, board_id, client_version,
Don Garrett710470d2010-11-15 17:43:44 -0800335 static_image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700336 """Generates an update using the latest image that has been built.
337
338 This will only generate an update if the newest update is newer than that
339 on the client or client_version is 'ForcedUpdate'.
340
341 Args:
342 board_id: Name of the board.
343 client_version: Current version of the client or 'ForcedUpdate'
344 static_image_dir: the directory to move images to after generating.
345 Returns:
Don Garrett710470d2010-11-15 17:43:44 -0800346 Name of the update image relative to static_image_dir or None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700347 """
348 latest_image_dir = self._GetLatestImageDir(board_id)
349 latest_version = self._GetVersionFromDir(latest_image_dir)
350 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
351
Chris Sosa7c931362010-10-11 19:49:01 -0700352 _LogMessage('Preparing to generate update from latest built image %s.' %
Chris Sosa0356d3b2010-09-16 15:46:22 -0700353 latest_image_path)
354
355 # Check to see whether or not we should update.
356 if client_version != 'ForcedUpdate' and not self._CanUpdate(
357 client_version, latest_version):
Chris Sosa7c931362010-10-11 19:49:01 -0700358 _LogMessage('no update')
Don Garrett710470d2010-11-15 17:43:44 -0800359 return None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700360
Don Garrett710470d2010-11-15 17:43:44 -0800361 return self.GenerateUpdateImageWithCache(latest_image_path,
362 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700363
364 def GenerateImageFromZip(self, static_image_dir):
365 """Generates an update from an image zip file.
366
367 This method assumes you have an image.zip in directory you are serving
368 from. If this file is newer than a previously cached file, it will unzip
369 this file, create a payload and serve it.
370
371 Args:
372 static_image_dir: Directory where the zip file exists.
373 Returns:
Don Garrett710470d2010-11-15 17:43:44 -0800374 Name of the update payload relative to static_image_dir if successful.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700375 """
Don Garrett710470d2010-11-15 17:43:44 -0800376 _LogMessage('Preparing to generate update from zip in %s.' %
377 static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700378 image_path = os.path.join(static_image_dir, self._GetImageName())
Chris Sosa0356d3b2010-09-16 15:46:22 -0700379 zip_file_path = os.path.join(static_image_dir, 'image.zip')
Don Garrett710470d2010-11-15 17:43:44 -0800380
381 # TODO(dgarrett): Either work caching into this path before
382 # we unpack, or remove zip support (sosa is considering).
383 # It does currently cache, but after the unpack.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700384
385 if not self._UnpackZip(static_image_dir):
Chris Sosa7c931362010-10-11 19:49:01 -0700386 _LogMessage('unzip image.zip failed.')
Don Garrett710470d2010-11-15 17:43:44 -0800387 return None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700388
Don Garrett710470d2010-11-15 17:43:44 -0800389 return self.GenerateUpdateImageWithCache(image_path,
390 static_image_dir=static_image_dir)
Darin Petkov8ef83452010-03-23 16:52:29 -0700391
Andrew de los Reyes52620802010-04-12 13:40:07 -0700392 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
393 """Imports a factory-floor server configuration file. The file should
394 be in this format:
395 config = [
396 {
397 'qual_ids': set([1, 2, 3, "x86-generic"]),
398 'factory_image': 'generic-factory.gz',
399 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
400 'release_image': 'generic-release.gz',
401 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
402 'oempartitionimg_image': 'generic-oem.gz',
403 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700404 'efipartitionimg_image': 'generic-efi.gz',
405 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700406 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800407 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800408 'firmware_image': 'generic-firmware.gz',
409 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700410 },
411 {
412 'qual_ids': set([6]),
413 'factory_image': '6-factory.gz',
414 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
415 'release_image': '6-release.gz',
416 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
417 'oempartitionimg_image': '6-oem.gz',
418 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700419 'efipartitionimg_image': '6-efi.gz',
420 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700421 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800422 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800423 'firmware_image': '6-firmware.gz',
424 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700425 },
426 ]
427 The server will look for the files by name in the static files
428 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700429
Andrew de los Reyes52620802010-04-12 13:40:07 -0700430 If validate_checksums is True, validates checksums and exits. If
431 a checksum mismatch is found, it's printed to the screen.
432 """
433 f = open(filename, 'r')
434 output = {}
435 exec(f.read(), output)
436 self.factory_config = output['config']
437 success = True
438 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800439 for key in stanza.copy().iterkeys():
440 suffix = '_image'
441 if key.endswith(suffix):
442 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700443 stanza[kind + '_size'] = self._GetSize(os.path.join(
444 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800445 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700446 factory_checksum = self._GetHash(os.path.join(self.static_dir,
447 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800448 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700449 print ('Error: checksum mismatch for %s. Expected "%s" but file '
450 'has checksum "%s".' % (stanza[kind + '_image'],
451 stanza[kind + '_checksum'],
452 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800453 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700454
Andrew de los Reyes52620802010-04-12 13:40:07 -0700455 if validate_checksums:
456 if success is False:
457 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700458
Andrew de los Reyes52620802010-04-12 13:40:07 -0700459 print 'Config file looks good.'
460
461 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700462 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700463 for stanza in self.factory_config:
464 if board_id not in stanza['qual_ids']:
465 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700466 if kind + '_image' not in stanza:
467 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700468 return (stanza[kind + '_image'],
469 stanza[kind + '_checksum'],
470 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700471 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000472
Chris Sosa7c931362010-10-11 19:49:01 -0700473 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700474 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
475 if filename is None:
Chris Sosa7c931362010-10-11 19:49:01 -0700476 _LogMessage('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700477 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700478 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700479 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa7c931362010-10-11 19:49:01 -0700480 _LogMessage('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700481 # Factory install is using memento updater which is using the sha-1 hash so
482 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700483 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700484
Chris Sosa151643e2010-10-28 14:40:57 -0700485 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
486 static_image_dir):
Don Garrett710470d2010-11-15 17:43:44 -0800487 """Generates an update for non-factory image.
Chris Sosae67b78f2010-11-04 17:33:16 -0700488
Don Garrett710470d2010-11-15 17:43:44 -0800489 Returns:
490 file name relative to static_image_dir on success.
491 """
492 if self.forced_image:
493 return self.GenerateUpdateImageWithCache(
494 self.forced_image,
495 static_image_dir=static_image_dir)
496 elif self.serve_only:
497 return self.GenerateImageFromZip(static_image_dir)
498 else:
499 if board_id:
500 return self.GenerateLatestUpdateImage(board_id,
501 client_version,
502 static_image_dir)
503
504 _LogMessage('You must set --board for pre-generating latest update.')
505 return None
Chris Sosa2c048f12010-10-27 16:05:27 -0700506
507 def PreGenerateUpdate(self):
Don Garrett710470d2010-11-15 17:43:44 -0800508 """Pre-generates an update. Returns True on success.
509 """
Chris Sosa2c048f12010-10-27 16:05:27 -0700510 # Does not work with factory config.
511 assert(not self.factory_config)
512 _LogMessage('Pre-generating the update payload.')
513 # Does not work with labels so just use static dir.
Chris Sosae67b78f2010-11-04 17:33:16 -0700514 if self.GenerateUpdatePayloadForNonFactory(self.board, '0.0.0.0',
515 self.static_dir):
Chris Sosae67b78f2010-11-04 17:33:16 -0700516 _LogMessage('Pre-generated update successfully.')
517 return True
Chris Sosa2c048f12010-10-27 16:05:27 -0700518 else:
519 _LogMessage('Failed to pre-generate update.')
Chris Sosae67b78f2010-11-04 17:33:16 -0700520 return False
Chris Sosa2c048f12010-10-27 16:05:27 -0700521
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700522 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700523 """Handles an update ping from an update client.
524
525 Args:
526 data: xml blob from client.
527 label: optional label for the update.
528 Returns:
529 Update payload message for client.
530 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700531 # Set hostname as the hostname that the client is calling to and set up
532 # the url base.
533 self.hostname = cherrypy.request.base
534 if self.urlbase:
535 static_urlbase = self.urlbase
536 elif self.serve_only:
537 static_urlbase = '%s/static/archive' % self.hostname
538 else:
539 static_urlbase = '%s/static' % self.hostname
540
541 _LogMessage('Using static url base %s' % static_urlbase)
542 _LogMessage('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700543
544 # Check the client prefix to make sure you can support this type of update.
Chris Sosa9841e1c2010-10-14 10:51:45 -0700545 update_dom = minidom.parseString(data)
546 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700547 if (root.hasAttribute('updaterversion') and
548 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
Chris Sosa7c931362010-10-11 19:49:01 -0700549 _LogMessage('Got update from unsupported updater:' +
Chris Sosa0356d3b2010-09-16 15:46:22 -0700550 root.getAttribute('updaterversion'))
Andrew de los Reyes9223f132010-05-07 17:08:17 -0700551 return self.GetNoUpdatePayload()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700552
553 # We only generate update payloads for updatecheck requests.
554 update_check = root.getElementsByTagName('o:updatecheck')
555 if not update_check:
Chris Sosa7c931362010-10-11 19:49:01 -0700556 _LogMessage('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700557 # TODO(sosa): Generate correct non-updatecheck payload to better test
558 # update clients.
559 return self.GetNoUpdatePayload()
560
561 # Since this is an updatecheck, get information about the requester.
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700562 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800563 client_version = query.getAttribute('version')
Andrew de los Reyes52620802010-04-12 13:40:07 -0700564 channel = query.getAttribute('track')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700565 board_id = (query.hasAttribute('board') and query.getAttribute('board')
566 or self._GetDefaultBoardID())
Andrew de los Reyes52620802010-04-12 13:40:07 -0700567
Chris Sosa0356d3b2010-09-16 15:46:22 -0700568 # Separate logic as Factory requests have static url's that override
569 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700570 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700571 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700572 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700573 static_image_dir = self.static_dir
574 if label:
575 static_image_dir = os.path.join(static_image_dir, label)
576
Don Garrett710470d2010-11-15 17:43:44 -0800577 payload_path = self.GenerateUpdatePayloadForNonFactory(board_id,
578 client_version,
579 static_image_dir)
580 if payload_path:
581 filename = os.path.join(static_image_dir, payload_path)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700582 hash = self._GetHash(filename)
583 sha256 = self._GetSHA256(filename)
584 size = self._GetSize(filename)
585 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa5d342a22010-09-28 16:54:41 -0700586 if label:
Don Garrett710470d2010-11-15 17:43:44 -0800587 url = '%s/%s/%s' % (static_urlbase, label, payload_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700588 else:
Don Garrett710470d2010-11-15 17:43:44 -0800589 url = '%s/%s' % (static_urlbase, payload_path)
Chris Sosa5d342a22010-09-28 16:54:41 -0700590
Chris Sosa7c931362010-10-11 19:49:01 -0700591 _LogMessage('Responding to client to use url %s to get image.' % url)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700592 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700593 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700594 return self.GetNoUpdatePayload()