add spec
[platform/upstream/swup.git] / swup.py
1 #!/usr/bin/python
2
3 import ConfigParser
4 from optparse import OptionParser
5 import urllib2
6 from lxml import etree
7 #from BeautifulSoup import *
8 import hashlib
9 import os
10 import tempfile
11 import shutil
12 import sys
13 import zipfile
14 import rpm
15 import subprocess as sub
16
17 update_repo="file:///home/nashif/system-updates/repo"
18 update_cache="/tmp/updates"
19
20
21
22 class FakeSecHead(object):
23     def __init__(self, fp):
24         self.fp = fp
25         self.sechead = '[os-release]\n'
26     def readline(self):
27         if self.sechead:
28             try: return self.sechead
29             finally: self.sechead = None
30         else: return self.fp.readline()
31
32 def get_current_version():
33     config = ConfigParser.SafeConfigParser()
34     config.readfp(FakeSecHead(open('/etc/os-release')))
35     return dict(config.items('os-release'))
36
37  
38 def checksum(fileName, checksum_type="sha256", excludeLine="", includeLine=""):
39     """Compute sha256 hash of the specified file"""
40     m = hashlib.sha256()
41     if checksum_type == "md5":
42         m = hashlib.md5()
43
44     try:
45         fd = open(fileName,"rb")
46     except IOError:
47         print "Unable to open the file in readmode:", filename
48         return
49     content = fd.readlines()
50     fd.close()
51     for eachLine in content:
52         if excludeLine and eachLine.startswith(excludeLine):
53             continue
54         m.update(eachLine)
55     m.update(includeLine)
56     return m.hexdigest()
57
58
59 def probe_updates():
60     print "Checking for new updates..."
61     response = urllib2.urlopen("%s/data/updatemd.xml" % update_repo )
62     updatemd = response.read()
63     if not os.path.exists("%s/data" %update_cache):
64         os.mkdir("%s/data" %update_cache)
65
66     fp = open("%s/data/updatemd.xml" % update_cache , "w")
67     fp.write(updatemd)
68     fp.close()
69
70     updatemd_local = open("%s/data/updatemd.xml" % update_cache )
71     root = etree.XML(updatemd_local.read())
72     data = root.xpath("//data[@type='update']")[0]
73     loc = data.xpath("location")[0]
74     href = loc.attrib['href']
75     chksum = data.xpath("checksum")[0]
76     chksum_type = chksum.attrib['type']
77     
78     if os.path.exists("%s/data/updates.xml" % update_cache):
79         cur_sum = checksum("%s/data/updates.xml" % update_cache, checksum_type=chksum_type) 
80         if cur_sum ==  chksum.text:
81             print "Using file from cache, no new updates on server."
82         else:
83             print "Fetching new updates..."
84             get_new_update_list(href)
85     else:
86         get_new_update_list(href)
87
88
89 def parse_updates():
90
91     updates = {}
92
93     fp = open("%s/data/updates.xml" % update_cache , "r")
94     updates_root = etree.XML(fp.read())
95     updates_el = updates_root.xpath("//update")
96     for update in updates_el:
97         up = {}
98         attr = update.attrib
99         up['id'] = attr['id']
100         up['checksum'] = update.xpath("checksum")[0].text
101         up['title'] = update.xpath("title")[0].text 
102         loc = update.xpath("location")[0]
103         up['location'] = "%s" % ( loc.attrib['href'])
104         
105         updates[up['id']] = up
106     return updates
107
108
109 def download_update(update_data):
110     u = update_data
111     location = u['location']
112     if not os.path.exists("%s/downloads/%s" % (update_cache,location)):
113         update_file = urllib2.urlopen("%s/%s" % (update_repo, location) )
114         location = os.path.basename(location)
115         announced_csum = u['checksum']
116         update_raw = update_file.read()
117         fp = open("%s/downloads/%s" % (update_cache,location) , "w")
118         fp.write(update_raw)
119         fp.close()
120         downloaded_csum = checksum("%s/downloads/%s" % (update_cache,location), "sha256")
121         # Verify Checksum
122         if downloaded_csum != announced_csum:
123             print "Error: Checksum mismatch"
124             os.remove("%s/downloads/%s" % (update_cache,location))
125     else:
126         print "%s already downloaded" % location    
127
128 def download_all_updates(update_label=None):
129     updates = parse_updates()
130
131     if update_label is not None:
132         u = updates[update_label]
133         download_update(u)
134     else:
135         for k in updates.keys():
136             u = updates[k]
137             download_update(u)
138         
139
140 def get_new_update_list(location):
141     up = urllib2.urlopen("%s/%s" % (update_repo, location) )
142     update_raw = up.read()
143     fp = open("%s/data/updates.xml" % update_cache , "w")
144     fp.write(update_raw)
145     fp.close()
146
147
148 def prepare_update(update_data):
149     u = update_data
150     location = u['location']
151     # unzip
152     if os.path.exists("%s/downloads/%s" % (update_cache,location)) and not os.path.exists("%s/downloads/%s" % (update_cache,u['id'])):    
153         zfile = zipfile.ZipFile("%s/downloads/%s" % (update_cache,location))
154         for name in zfile.namelist():            
155             (dirname, filename) = os.path.split(name)
156             print "Decompressing " + filename + " on " + dirname
157             if not os.path.exists("%s/downloads/%s" % (update_cache, dirname)):
158                 os.mkdir("%s/downloads/%s" % (update_cache, dirname))            
159             if filename != "":
160                 fd = open("%s/downloads/%s" % (update_cache, name),"w")
161                 fd.write(zfile.read(name))
162                 fd.close()
163     # apply deltas
164     print "Delta Packages:"
165     for delta in os.listdir("%s/downloads/%s/delta" % (update_cache,u['id'])):
166         
167
168         ts = rpm.TransactionSet()
169         delta_location = "%s/downloads/%s/delta/%s" % (update_cache, u['id'], delta)
170         fdno = os.open(delta_location, os.O_RDONLY)
171         
172         hdr = ts.hdrFromFdno(fdno)
173         os.close(fdno)
174         target_rpm =  "%s-%s-%s.%s.rpm" % (hdr['name'], hdr['version'], hdr['release'], hdr['arch'])
175         target_location = "%s/downloads/%s/packages/%s" % (update_cache, u['id'], target_rpm)
176         version = "_%s.%s.drpm" % (hdr['release'], hdr['arch'])
177         #print version
178         original_rpm = "%s.%s.rpm" %( delta.replace(version, ""), hdr['arch'] )
179         original_rpm = original_rpm.replace("_%s" % hdr['version'], "")
180         print "   %s" %original_rpm
181         print " + %s" %delta
182         print " = %s" %target_rpm
183
184         # Verify
185         
186         mi = ts.dbMatch("name", hdr['name'])
187         Found = False
188         for r in mi:
189             installed = "%s-%s-%s.%s.rpm" % (r.name, r.version, r.release, r.arch)
190             #original = "%s-%s-%s.%s" % (hdr['name'], hdr['version'], hdr['release'], hdr['arch'])        
191             print installed
192
193             if installed == original_rpm:
194                 Found = True
195         if Found:
196             print "Original availale, delta can be applied. Applying now..."
197             # apply delta here
198             if os.path.exists("/usr/bin/applydeltarpm"):
199                 if not os.path.exists(target_location):
200                     cmd = '/usr/bin/applydeltarpm %s %s' % (delta_location, target_location) 
201                     print cmd
202                     p = sub.Popen(["/usr/bin/applydeltarpm", delta_location, target_location] ,stdout=sub.PIPE,stderr=sub.PIPE)
203                     output, errors = p.communicate()
204                     print output
205                 else:
206                     print "Target already exists"
207         else:
208             print "Error: original not available, can't apply delta. We have %s instead of %s" % (installed, original_rpm)
209
210
211
212 def apply_update(update_data):
213     pass
214
215 def list_updates():
216     updates = parse_updates()
217     for k in updates.keys():
218         u = updates[k]
219         print "%s" %u['id']
220         print "    %s" %u['title']
221
222
223 parser = OptionParser()
224 parser.add_option("-V", "--os-version", action="store_true", dest="osver", default=False,
225                   help="Current OS Version")
226 parser.add_option("-l", "--list-updates", action="store_true", dest="listupdates", default=False,
227                   help="List updates")
228 parser.add_option("-d", "--download-only", action="store_true", dest="downloadonly", default=False,
229                   help="Download only")
230 parser.add_option("-i", "--install",  dest="install", metavar="LABEL",
231                   help="Install update")
232 parser.add_option("-a", "--install-all",  dest="installall", action="store_true", default=False,
233                   help="Install all updates")
234 parser.add_option("-r", "--recommended",  dest="recommended", action="store_true", default=False,
235                   help="Install recommended updates only")
236 parser.add_option("-q", "--quiet",
237                   action="store_false", dest="verbose", default=True,
238                   help="don't print status messages to stdout")
239
240 (options, args) = parser.parse_args()
241
242 if not os.path.exists(update_cache):
243     os.mkdir("%s" % update_cache)
244     os.mkdir("%s/downloads" % update_cache)
245
246 if options.osver:
247     os_release = get_current_version()
248     print os_release['version_id'].strip('"')
249
250 if options.listupdates:
251     probe_updates()
252     list_updates()
253
254 if options.downloadonly:
255     probe_updates()
256     download_all_updates()
257
258 if options.install is not None:
259     probe_updates()
260     updates = parse_updates()
261     if not updates.has_key(options.install):
262         print "%s is not available for installation. Abort." %options.install
263         sys.exit()
264     u = updates[options.install]
265     download_update(u)
266     prepare_update(u)
267
268
269