1#!/usr/bin/env python 2 3import gobject 4import dbus 5import dbus.service 6import dbus.mainloop.glib 7import subprocess 8import tempfile 9import shutil 10import tarfile 11import os 12from obmc.dbuslib.bindings import get_dbus, DbusProperties, DbusObjectManager 13 14DBUS_NAME = 'org.openbmc.control.BmcFlash' 15OBJ_NAME = '/org/openbmc/control/flash/bmc' 16DOWNLOAD_INTF = 'org.openbmc.managers.Download' 17 18BMC_DBUS_NAME = 'org.openbmc.control.Bmc' 19BMC_OBJ_NAME = '/org/openbmc/control/bmc0' 20 21UPDATE_PATH = '/run/initramfs' 22 23 24def doExtract(members, files): 25 for tarinfo in members: 26 if tarinfo.name in files: 27 yield tarinfo 28 29 30class BmcFlashControl(DbusProperties, DbusObjectManager): 31 def __init__(self, bus, name): 32 super(BmcFlashControl, self).__init__( 33 conn=bus, 34 object_path=name) 35 36 self.Set(DBUS_NAME, "status", "Idle") 37 self.Set(DBUS_NAME, "filename", "") 38 self.Set(DBUS_NAME, "preserve_network_settings", True) 39 self.Set(DBUS_NAME, "restore_application_defaults", False) 40 self.Set(DBUS_NAME, "update_kernel_and_apps", False) 41 self.Set(DBUS_NAME, "clear_persistent_files", False) 42 self.Set(DBUS_NAME, "auto_apply", False) 43 44 bus.add_signal_receiver( 45 self.download_error_handler, signal_name="DownloadError") 46 bus.add_signal_receiver( 47 self.download_complete_handler, signal_name="DownloadComplete") 48 49 self.update_process = None 50 self.progress_name = None 51 52 @dbus.service.method( 53 DBUS_NAME, in_signature='ss', out_signature='') 54 def updateViaTftp(self, ip, filename): 55 self.Set(DBUS_NAME, "status", "Downloading") 56 self.TftpDownload(ip, filename) 57 58 @dbus.service.method( 59 DBUS_NAME, in_signature='s', out_signature='') 60 def update(self, filename): 61 self.Set(DBUS_NAME, "filename", filename) 62 self.download_complete_handler(filename, filename) 63 64 @dbus.service.signal(DOWNLOAD_INTF, signature='ss') 65 def TftpDownload(self, ip, filename): 66 self.Set(DBUS_NAME, "filename", filename) 67 pass 68 69 ## Signal handler 70 def download_error_handler(self, filename): 71 if (filename == self.Get(DBUS_NAME, "filename")): 72 self.Set(DBUS_NAME, "status", "Download Error") 73 74 def download_complete_handler(self, outfile, filename): 75 ## do update 76 if (filename != self.Get(DBUS_NAME, "filename")): 77 return 78 79 print "Download complete. Updating..." 80 81 self.Set(DBUS_NAME, "status", "Download Complete") 82 copy_files = {} 83 84 ## determine needed files 85 if not self.Get(DBUS_NAME, "update_kernel_and_apps"): 86 copy_files["image-bmc"] = True 87 else: 88 copy_files["image-kernel"] = True 89 copy_files["image-rofs"] = True 90 91 if self.Get(DBUS_NAME, "restore_application_defaults"): 92 copy_files["image-rwfs"] = True 93 94 ## make sure files exist in archive 95 try: 96 tar = tarfile.open(outfile, "r") 97 files = {} 98 for f in tar.getnames(): 99 files[f] = True 100 tar.close() 101 for f in copy_files.keys(): 102 if f not in files: 103 raise Exception( 104 "ERROR: File not found in update archive: "+f) 105 106 except Exception as e: 107 print e 108 self.Set(DBUS_NAME, "status", "Unpack Error") 109 return 110 111 try: 112 tar = tarfile.open(outfile, "r") 113 tar.extractall(UPDATE_PATH, members=doExtract(tar, copy_files)) 114 tar.close() 115 116 if self.Get(DBUS_NAME, "clear_persistent_files"): 117 print "Removing persistent files" 118 try: 119 os.unlink(UPDATE_PATH+"/whitelist") 120 except OSError as e: 121 if (e.errno == errno.EISDIR): 122 pass 123 elif (e.errno == errno.ENOENT): 124 pass 125 else: 126 raise 127 128 try: 129 wldir = UPDATE_PATH + "/whitelist.d" 130 131 for file in os.listdir(wldir): 132 os.unlink(os.path.join(wldir, file)) 133 except OSError as e: 134 if (e.errno == errno.EISDIR): 135 pass 136 else: 137 raise 138 139 if self.Get(DBUS_NAME, "preserve_network_settings"): 140 print "Preserving network settings" 141 shutil.copy2("/run/fw_env", UPDATE_PATH+"/image-u-boot-env") 142 143 except Exception as e: 144 print e 145 self.Set(DBUS_NAME, "status", "Unpack Error") 146 147 self.Verify() 148 149 def Verify(self): 150 self.Set(DBUS_NAME, "status", "Checking Image") 151 try: 152 subprocess.check_call([ 153 "/run/initramfs/update", 154 "--no-flash", 155 "--no-save-files", 156 "--no-restore-files", 157 "--no-clean-saved-files"]) 158 159 self.Set(DBUS_NAME, "status", "Image ready to apply.") 160 if (self.Get(DBUS_NAME, "auto_apply")): 161 self.Apply() 162 except: 163 self.Set(DBUS_NAME, "auto_apply", False) 164 try: 165 subprocess.check_output([ 166 "/run/initramfs/update", 167 "--no-flash", 168 "--ignore-mount", 169 "--no-save-files", 170 "--no-restore-files", 171 "--no-clean-saved-files"], 172 stderr=subprocess.STDOUT) 173 self.Set( 174 DBUS_NAME, "status", 175 "Deferred for mounted filesystem. reboot BMC to apply.") 176 except subprocess.CalledProcessError as e: 177 self.Set( 178 DBUS_NAME, "status", "Verify error: %s" % e.output) 179 except OSError as e: 180 self.Set( 181 DBUS_NAME, "status", 182 "Verify error: problem calling update: %s" % e.strerror) 183 184 def Cleanup(self): 185 if self.progress_name: 186 try: 187 os.unlink(self.progress_name) 188 self.progress_name = None 189 except oserror as e: 190 if e.errno == EEXIST: 191 pass 192 raise 193 self.update_process = None 194 self.Set(DBUS_NAME, "status", "Idle") 195 196 @dbus.service.method( 197 DBUS_NAME, in_signature='', out_signature='') 198 def Abort(self): 199 if self.update_process: 200 try: 201 self.update_process.kill() 202 except: 203 pass 204 for file in os.listdir(UPDATE_PATH): 205 if file.startswith('image-'): 206 os.unlink(os.path.join(UPDATE_PATH, file)) 207 208 self.Cleanup() 209 210 @dbus.service.method( 211 DBUS_NAME, in_signature='', out_signature='s') 212 def GetUpdateProgress(self): 213 msg = "" 214 215 if self.update_process and self.update_process.returncode is None: 216 self.update_process.poll() 217 218 if (self.update_process is None): 219 pass 220 elif (self.update_process.returncode > 0): 221 self.Set(DBUS_NAME, "status", "Apply failed") 222 elif (self.update_process.returncode is None): 223 pass 224 else: # (self.update_process.returncode == 0) 225 files = "" 226 for file in os.listdir(UPDATE_PATH): 227 if file.startswith('image-'): 228 files = files + file 229 if files == "": 230 msg = "Apply Complete. Reboot to take effect." 231 else: 232 msg = "Apply Incomplete, Remaining:" + files 233 self.Set(DBUS_NAME, "status", msg) 234 235 msg = self.Get(DBUS_NAME, "status") + "\n" 236 if self.progress_name: 237 try: 238 prog = open(self.progress_name, 'r') 239 for line in prog: 240 # strip off initial sets of xxx\r here 241 # ignore crlf at the end 242 # cr will be -1 if no '\r' is found 243 cr = line.rfind("\r", 0, -2) 244 msg = msg + line[cr + 1:] 245 except OSError as e: 246 if (e.error == EEXIST): 247 pass 248 raise 249 return msg 250 251 @dbus.service.method( 252 DBUS_NAME, in_signature='', out_signature='') 253 def Apply(self): 254 progress = None 255 self.Set(DBUS_NAME, "status", "Writing images to flash") 256 try: 257 progress = tempfile.NamedTemporaryFile( 258 delete=False, prefix="progress.") 259 self.progress_name = progress.name 260 self.update_process = subprocess.Popen([ 261 "/run/initramfs/update"], 262 stdout=progress.file, 263 stderr=subprocess.STDOUT) 264 except Exception as e: 265 try: 266 progress.close() 267 os.unlink(progress.name) 268 self.progress_name = None 269 except: 270 pass 271 raise 272 273 try: 274 progress.close() 275 except: 276 pass 277 278 @dbus.service.method( 279 DBUS_NAME, in_signature='', out_signature='') 280 def PrepareForUpdate(self): 281 subprocess.call([ 282 "fw_setenv", 283 "openbmconce", 284 "copy-files-to-ram copy-base-filesystem-to-ram"]) 285 self.Set(DBUS_NAME, "status", "Switch to update mode in progress") 286 o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME) 287 intf = dbus.Interface(o, BMC_DBUS_NAME) 288 intf.warmReset() 289 290 291if __name__ == '__main__': 292 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 293 294 bus = get_dbus() 295 obj = BmcFlashControl(bus, OBJ_NAME) 296 mainloop = gobject.MainLoop() 297 298 obj.unmask_signals() 299 name = dbus.service.BusName(DBUS_NAME, bus) 300 301 print "Running Bmc Flash Control" 302 mainloop.run() 303 304# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 305