blob: 4461533e45d98a53db59f354226c1e2ef19a2495 [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
9import web
10
rtc@google.com64244662009-11-12 00:52:08 +000011class Autoupdate(BuildObject):
rtc@google.com21a5ca32009-11-04 18:23:23 +000012 # Basic functionality of handling ChromeOS autoupdate pings
13 # and building/serving update images.
14 # TODO(rtc): Clean this code up and write some tests.
rtc@google.comded22402009-10-26 22:36:21 +000015
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070016 def __init__(self, serve_only=None, test_image=False, *args, **kwargs):
17 self.serve_only = serve_only
18 if serve_only:
19 web.debug('Autoupdate in "serve update images only" mode.')
20 self.test_image=test_image
21 super(Autoupdate, self).__init__(*args, **kwargs)
22
rtc@google.com21a5ca32009-11-04 18:23:23 +000023 def GetUpdatePayload(self, hash, size, url):
24 payload = """<?xml version="1.0" encoding="UTF-8"?>
25 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
26 <app appid="{%s}" status="ok">
27 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070028 <updatecheck
29 codebase="%s"
30 hash="%s"
31 needsadmin="false"
32 size="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +000033 status="ok"/>
34 </app>
35 </gupdate>
36 """
37 return payload % (self.app_id, url, hash, size)
rtc@google.comded22402009-10-26 22:36:21 +000038
rtc@google.com21a5ca32009-11-04 18:23:23 +000039 def GetNoUpdatePayload(self):
40 payload = """<?xml version="1.0" encoding="UTF-8"?>
41 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
42 <app appid="{%s}" status="ok">
43 <ping status="ok"/>
44 <updatecheck status="noupdate"/>
45 </app>
46 </gupdate>
47 """
48 return payload % self.app_id
rtc@google.comded22402009-10-26 22:36:21 +000049
Sam Leffler76382042010-02-18 09:58:42 -080050 def GetLatestImagePath(self, board_id):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070051 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
rtc@google.com21a5ca32009-11-04 18:23:23 +000052 return os.popen(cmd).read().strip()
rtc@google.comded22402009-10-26 22:36:21 +000053
rtc@google.com21a5ca32009-11-04 18:23:23 +000054 def GetLatestVersion(self, latest_image_path):
55 latest_version = latest_image_path.split('/')[-1]
Ryan Cairns1b05beb2010-02-05 17:05:24 -080056
57 # Removes the portage build prefix.
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070058 latest_version = latest_version.lstrip('g-')
rtc@google.com21a5ca32009-11-04 18:23:23 +000059 return latest_version.split('-')[0]
rtc@google.comded22402009-10-26 22:36:21 +000060
rtc@google.com21a5ca32009-11-04 18:23:23 +000061 def CanUpdate(self, client_version, latest_version):
62 """
63 Returns true iff the latest_version is greater than the client_version.
64 """
65 client_tokens = client_version.split('.')
66 latest_tokens = latest_version.split('.')
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070067 web.debug('client version %s latest version %s' \
Charlie Lee8c993082010-02-24 13:27:37 -080068 % (client_version, latest_version))
rtc@google.com21a5ca32009-11-04 18:23:23 +000069 for i in range(0,4):
70 if int(latest_tokens[i]) == int(client_tokens[i]):
71 continue
72 return int(latest_tokens[i]) > int(client_tokens[i])
rtc@google.comded22402009-10-26 22:36:21 +000073 return False
rtc@google.comded22402009-10-26 22:36:21 +000074
rtc@google.com21a5ca32009-11-04 18:23:23 +000075 def BuildUpdateImage(self, image_path):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070076 if self.test_image:
77 image_file = '%s/rootfs_test.image' % image_path
78 else:
79 image_file = '%s/rootfs.image' % image_path
80 update_file = '%s/update.gz' % image_path
81 if (os.path.exists(update_file) and
82 os.path.getmtime(update_file) >= os.path.getmtime(image_file)):
83 web.debug('Found cached update image %s/update.gz' % image_path)
84 else:
85 web.debug('generating update image %s/update.gz' % image_path)
86 mkupdate = '%s/mk_memento_images.sh %s' % (self.scripts_dir, image_file)
rtc@google.com21a5ca32009-11-04 18:23:23 +000087 web.debug(mkupdate)
88 err = os.system(mkupdate)
89 if err != 0:
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070090 web.debug('failed to create update image')
rtc@google.com21a5ca32009-11-04 18:23:23 +000091 return False
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070092 if not self.serve_only:
93 web.debug('Found an image, copying it to static')
94 try:
95 shutil.copyfile('%s/update.gz' % image_path, self.static_dir)
96 except Exception, e:
97 web.debug('Unable to copy update.gz from %s to %s' \
98 % (image_path, self.static_dir))
99 return False
rtc@google.com21a5ca32009-11-04 18:23:23 +0000100 return True
rtc@google.comded22402009-10-26 22:36:21 +0000101
rtc@google.com21a5ca32009-11-04 18:23:23 +0000102 def GetSize(self, update_path):
103 return os.path.getsize(update_path)
rtc@google.comded22402009-10-26 22:36:21 +0000104
rtc@google.com21a5ca32009-11-04 18:23:23 +0000105 def GetHash(self, update_path):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700106 update_dir = os.path.dirname(update_path)
107 if not os.path.exists('%s/cksum' % update_dir):
108 cmd = ('cat %s | openssl sha1 -binary'
109 '| openssl base64 | tr \'\\n\' \' \' | tee %s/cksum' %
110 (update_path, update_dir))
111 web.debug(cmd)
112 return os.popen(cmd).read()
113 else:
114 web.debug('using cached checksum for %s' % update_path)
115 return file('%s/cksum' % update_dir).read()
rtc@google.comded22402009-10-26 22:36:21 +0000116
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700117 def HandleUpdatePing(self, data, label=None):
rtc@google.com21a5ca32009-11-04 18:23:23 +0000118 update_dom = minidom.parseString(data)
119 root = update_dom.firstChild
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700120 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800121 client_version = query.getAttribute('version')
122 board_id = query.hasAttribute('board') and query.getAttribute('board') \
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700123 or 'x86-generic'
Charlie Lee8c993082010-02-24 13:27:37 -0800124 latest_image_path = self.GetLatestImagePath(board_id)
125 latest_version = self.GetLatestVersion(latest_image_path)
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700126 if client_version != 'ForcedUpdate' \
Charlie Lee8c993082010-02-24 13:27:37 -0800127 and not self.CanUpdate(client_version, latest_version):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700128 web.debug('no update')
rtc@google.com21a5ca32009-11-04 18:23:23 +0000129 return self.GetNoUpdatePayload()
rtc@google.com21a5ca32009-11-04 18:23:23 +0000130 hostname = web.ctx.host
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700131 if label:
132 web.debug('Client requested version %s' % label)
133 # Check that matching build exists
134 image_path = '%s/%s' % (self.static_dir, label)
135 if not os.path.exists(image_path):
136 web.debug('%s not found.' % image_path)
137 return self.GetNoUpdatePayload()
138 # Construct a response
139 ok = self.BuildUpdateImage(image_path)
140 if ok != True:
141 web.debug('Failed to build an update image')
142 return self.GetNoUpdatePayload()
143 web.debug('serving update: ')
144 hash = self.GetHash('%s/%s/update.gz' % (self.static_dir, label))
145 size = self.GetSize('%s/%s/update.gz' % (self.static_dir, label))
146 url = 'http://%s/static/archive/%s/update.gz' % (hostname, label)
147 return self.GetUpdatePayload(hash, size, url)
148 web.debug( 'DONE')
149 else:
150 web.debug('update found %s ' % latest_version)
151 ok = self.BuildUpdateImage(latest_image_path)
152 if ok != True:
153 web.debug('Failed to build an update image')
154 return self.GetNoUpdatePayload()
rtc@google.comded22402009-10-26 22:36:21 +0000155
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700156 hash = self.GetHash('%s/update.gz' % self.static_dir)
157 size = self.GetSize('%s/update.gz' % self.static_dir)
158
159 url = 'http://%s/static/update.gz' % hostname
160 return self.GetUpdatePayload(hash, size, url)