blob: 2c2a39ac968115eefd22c7759d270d70bd618dd7 [file] [log] [blame]
rtc@google.comded22402009-10-26 22:36:21 +00001# Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
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
8import os
Darin Petkov798fe7d2010-03-22 15:18:13 -07009import shutil
rtc@google.comded22402009-10-26 22:36:21 +000010import web
11
rtc@google.com64244662009-11-12 00:52:08 +000012class Autoupdate(BuildObject):
Darin Petkov798fe7d2010-03-22 15:18:13 -070013 # Basic functionality of handling ChromeOS autoupdate pings
rtc@google.com21a5ca32009-11-04 18:23:23 +000014 # and building/serving update images.
15 # TODO(rtc): Clean this code up and write some tests.
rtc@google.comded22402009-10-26 22:36:21 +000016
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070017 def __init__(self, serve_only=None, test_image=False, *args, **kwargs):
18 self.serve_only = serve_only
19 if serve_only:
20 web.debug('Autoupdate in "serve update images only" mode.')
21 self.test_image=test_image
22 super(Autoupdate, self).__init__(*args, **kwargs)
23
rtc@google.com21a5ca32009-11-04 18:23:23 +000024 def GetUpdatePayload(self, hash, size, url):
25 payload = """<?xml version="1.0" encoding="UTF-8"?>
26 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
27 <app appid="{%s}" status="ok">
28 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070029 <updatecheck
30 codebase="%s"
31 hash="%s"
32 needsadmin="false"
33 size="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +000034 status="ok"/>
35 </app>
36 </gupdate>
37 """
38 return payload % (self.app_id, url, hash, size)
rtc@google.comded22402009-10-26 22:36:21 +000039
rtc@google.com21a5ca32009-11-04 18:23:23 +000040 def GetNoUpdatePayload(self):
41 payload = """<?xml version="1.0" encoding="UTF-8"?>
42 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
43 <app appid="{%s}" status="ok">
44 <ping status="ok"/>
45 <updatecheck status="noupdate"/>
46 </app>
47 </gupdate>
48 """
49 return payload % self.app_id
rtc@google.comded22402009-10-26 22:36:21 +000050
Sam Leffler76382042010-02-18 09:58:42 -080051 def GetLatestImagePath(self, board_id):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070052 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
rtc@google.com21a5ca32009-11-04 18:23:23 +000053 return os.popen(cmd).read().strip()
rtc@google.comded22402009-10-26 22:36:21 +000054
rtc@google.com21a5ca32009-11-04 18:23:23 +000055 def GetLatestVersion(self, latest_image_path):
56 latest_version = latest_image_path.split('/')[-1]
Ryan Cairns1b05beb2010-02-05 17:05:24 -080057
58 # Removes the portage build prefix.
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070059 latest_version = latest_version.lstrip('g-')
rtc@google.com21a5ca32009-11-04 18:23:23 +000060 return latest_version.split('-')[0]
rtc@google.comded22402009-10-26 22:36:21 +000061
rtc@google.com21a5ca32009-11-04 18:23:23 +000062 def CanUpdate(self, client_version, latest_version):
63 """
64 Returns true iff the latest_version is greater than the client_version.
65 """
66 client_tokens = client_version.split('.')
67 latest_tokens = latest_version.split('.')
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070068 web.debug('client version %s latest version %s' \
Charlie Lee8c993082010-02-24 13:27:37 -080069 % (client_version, latest_version))
rtc@google.com21a5ca32009-11-04 18:23:23 +000070 for i in range(0,4):
71 if int(latest_tokens[i]) == int(client_tokens[i]):
72 continue
73 return int(latest_tokens[i]) > int(client_tokens[i])
rtc@google.comded22402009-10-26 22:36:21 +000074 return False
rtc@google.comded22402009-10-26 22:36:21 +000075
Darin Petkovcbcd2bd2010-04-06 10:14:08 -070076 def UnpackRootfs(self, image_path, rootfs_file):
77 if os.path.exists(rootfs_file):
78 return True
79 if self.test_image:
80 image_file = 'chromiumos_test_image.bin'
81 else:
82 image_file = 'chromiumos_image.bin'
83 os.system('rm -f %s/part_*' % image_path)
84 os.system('cd %s && ./unpack_partitions.sh %s' % (image_path, image_file))
85 shutil.move(os.path.join(image_path, 'part_3'), rootfs_file)
86 os.system('rm -f %s/part_*' % image_path)
87 return True
88
rtc@google.com21a5ca32009-11-04 18:23:23 +000089 def BuildUpdateImage(self, image_path):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070090 if self.test_image:
91 image_file = '%s/rootfs_test.image' % image_path
92 else:
93 image_file = '%s/rootfs.image' % image_path
Darin Petkovcbcd2bd2010-04-06 10:14:08 -070094
95 if not self.UnpackRootfs(image_path, image_file):
96 web.debug('failed to unpack rootfs.')
97 return False
98
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070099 update_file = '%s/update.gz' % image_path
100 if (os.path.exists(update_file) and
101 os.path.getmtime(update_file) >= os.path.getmtime(image_file)):
102 web.debug('Found cached update image %s/update.gz' % image_path)
103 else:
104 web.debug('generating update image %s/update.gz' % image_path)
105 mkupdate = '%s/mk_memento_images.sh %s' % (self.scripts_dir, image_file)
rtc@google.com21a5ca32009-11-04 18:23:23 +0000106 web.debug(mkupdate)
107 err = os.system(mkupdate)
108 if err != 0:
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700109 web.debug('failed to create update image')
rtc@google.com21a5ca32009-11-04 18:23:23 +0000110 return False
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700111 if not self.serve_only:
112 web.debug('Found an image, copying it to static')
113 try:
Darin Petkov798fe7d2010-03-22 15:18:13 -0700114 shutil.copy('%s/update.gz' % image_path, self.static_dir)
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700115 except Exception, e:
116 web.debug('Unable to copy update.gz from %s to %s' \
117 % (image_path, self.static_dir))
118 return False
rtc@google.com21a5ca32009-11-04 18:23:23 +0000119 return True
rtc@google.comded22402009-10-26 22:36:21 +0000120
rtc@google.com21a5ca32009-11-04 18:23:23 +0000121 def GetSize(self, update_path):
122 return os.path.getsize(update_path)
rtc@google.comded22402009-10-26 22:36:21 +0000123
rtc@google.com21a5ca32009-11-04 18:23:23 +0000124 def GetHash(self, update_path):
Darin Petkov8ef83452010-03-23 16:52:29 -0700125 cmd = "cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';" \
126 % update_path
127 web.debug(cmd)
128 return os.popen(cmd).read()
129
rtc@google.comded22402009-10-26 22:36:21 +0000130
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700131 def HandleUpdatePing(self, data, label=None):
rtc@google.com21a5ca32009-11-04 18:23:23 +0000132 update_dom = minidom.parseString(data)
133 root = update_dom.firstChild
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700134 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800135 client_version = query.getAttribute('version')
136 board_id = query.hasAttribute('board') and query.getAttribute('board') \
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700137 or 'x86-generic'
Charlie Lee8c993082010-02-24 13:27:37 -0800138 latest_image_path = self.GetLatestImagePath(board_id)
139 latest_version = self.GetLatestVersion(latest_image_path)
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700140 if client_version != 'ForcedUpdate' \
Charlie Lee8c993082010-02-24 13:27:37 -0800141 and not self.CanUpdate(client_version, latest_version):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700142 web.debug('no update')
rtc@google.com21a5ca32009-11-04 18:23:23 +0000143 return self.GetNoUpdatePayload()
rtc@google.com21a5ca32009-11-04 18:23:23 +0000144 hostname = web.ctx.host
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700145 if label:
146 web.debug('Client requested version %s' % label)
147 # Check that matching build exists
148 image_path = '%s/%s' % (self.static_dir, label)
149 if not os.path.exists(image_path):
150 web.debug('%s not found.' % image_path)
151 return self.GetNoUpdatePayload()
152 # Construct a response
153 ok = self.BuildUpdateImage(image_path)
154 if ok != True:
155 web.debug('Failed to build an update image')
156 return self.GetNoUpdatePayload()
157 web.debug('serving update: ')
158 hash = self.GetHash('%s/%s/update.gz' % (self.static_dir, label))
159 size = self.GetSize('%s/%s/update.gz' % (self.static_dir, label))
160 url = 'http://%s/static/archive/%s/update.gz' % (hostname, label)
161 return self.GetUpdatePayload(hash, size, url)
162 web.debug( 'DONE')
163 else:
164 web.debug('update found %s ' % latest_version)
165 ok = self.BuildUpdateImage(latest_image_path)
166 if ok != True:
167 web.debug('Failed to build an update image')
168 return self.GetNoUpdatePayload()
rtc@google.comded22402009-10-26 22:36:21 +0000169
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700170 hash = self.GetHash('%s/update.gz' % self.static_dir)
171 size = self.GetSize('%s/update.gz' % self.static_dir)
172
173 url = 'http://%s/static/update.gz' % hostname
174 return self.GetUpdatePayload(hash, size, url)