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-initramfs"] = True 90 copy_files["image-rofs"] = True 91 92 if self.Get(DBUS_NAME, "restore_application_defaults"): 93 copy_files["image-rwfs"] = True 94 95 ## make sure files exist in archive 96 try: 97 tar = tarfile.open(outfile, "r") 98 files = {} 99 for f in tar.getnames(): 100 files[f] = True 101 tar.close() 102 for f in copy_files.keys(): 103 if f not in files: 104 raise Exception( 105 "ERROR: File not found in update archive: "+f) 106 107 except Exception as e: 108 print e 109 self.Set(DBUS_NAME, "status", "Unpack Error") 110 return 111 112 try: 113 tar = tarfile.open(outfile, "r") 114 tar.extractall(UPDATE_PATH, members=doExtract(tar, copy_files)) 115 tar.close() 116 117 if self.Get(DBUS_NAME, "clear_persistent_files"): 118 print "Removing persistent files" 119 try: 120 os.unlink(UPDATE_PATH+"/whitelist") 121 except OSError as e: 122 if (e.errno == errno.EISDIR): 123 pass 124 elif (e.errno == errno.ENOENT): 125 pass 126 else: 127 raise 128 129 try: 130 wldir = UPDATE_PATH + "/whitelist.d" 131 132 for file in os.listdir(wldir): 133 os.unlink(os.path.join(wldir, file)) 134 except OSError as e: 135 if (e.errno == errno.EISDIR): 136 pass 137 else: 138 raise 139 140 if self.Get(DBUS_NAME, "preserve_network_settings"): 141 print "Preserving network settings" 142 shutil.copy2("/run/fw_env", UPDATE_PATH+"/image-u-boot-env") 143 144 except Exception as e: 145 print e 146 self.Set(DBUS_NAME, "status", "Unpack Error") 147 148 self.Verify() 149 150 def Verify(self): 151 self.Set(DBUS_NAME, "status", "Checking Image") 152 try: 153 subprocess.check_call([ 154 "/run/initramfs/update", 155 "--no-flash", 156 "--no-save-files", 157 "--no-restore-files", 158 "--no-clean-saved-files"]) 159 160 self.Set(DBUS_NAME, "status", "Image ready to apply.") 161 if (self.Get(DBUS_NAME, "auto_apply")): 162 self.Apply() 163 except: 164 self.Set(DBUS_NAME, "auto_apply", False) 165 try: 166 subprocess.check_output([ 167 "/run/initramfs/update", 168 "--no-flash", 169 "--ignore-mount", 170 "--no-save-files", 171 "--no-restore-files", 172 "--no-clean-saved-files"], 173 stderr=subprocess.STDOUT) 174 self.Set( 175 DBUS_NAME, "status", 176 "Deferred for mounted filesystem. reboot BMC to apply.") 177 except subprocess.CalledProcessError as e: 178 self.Set( 179 DBUS_NAME, "status", "Verify error: %s" % e.output) 180 except OSError as e: 181 self.Set( 182 DBUS_NAME, "status", 183 "Verify error: problem calling update: %s" % e.strerror) 184 185 def Cleanup(self): 186 if self.progress_name: 187 try: 188 os.unlink(self.progress_name) 189 self.progress_name = None 190 except oserror as e: 191 if e.errno == EEXIST: 192 pass 193 raise 194 self.update_process = None 195 self.Set(DBUS_NAME, "status", "Idle") 196 197 @dbus.service.method( 198 DBUS_NAME, in_signature='', out_signature='') 199 def Abort(self): 200 if self.update_process: 201 try: 202 self.update_process.kill() 203 except: 204 pass 205 for file in os.listdir(UPDATE_PATH): 206 if file.startswith('image-'): 207 os.unlink(os.path.join(UPDATE_PATH, file)) 208 209 self.Cleanup() 210 211 @dbus.service.method( 212 DBUS_NAME, in_signature='', out_signature='s') 213 def GetUpdateProgress(self): 214 msg = "" 215 216 if self.update_process and self.update_process.returncode is None: 217 self.update_process.poll() 218 219 if (self.update_process is None): 220 pass 221 elif (self.update_process.returncode > 0): 222 self.Set(DBUS_NAME, "status", "Apply failed") 223 elif (self.update_process.returncode is None): 224 pass 225 else: # (self.update_process.returncode == 0) 226 files = "" 227 for file in os.listdir(UPDATE_PATH): 228 if file.startswith('image-'): 229 files = files + file 230 if files == "": 231 msg = "Apply Complete. Reboot to take effect." 232 else: 233 msg = "Apply Incomplete, Remaining:" + files 234 self.Set(DBUS_NAME, "status", msg) 235 236 msg = self.Get(DBUS_NAME, "status") + "\n" 237 if self.progress_name: 238 try: 239 prog = open(self.progress_name, 'r') 240 for line in prog: 241 # strip off initial sets of xxx\r here 242 # ignore crlf at the end 243 # cr will be -1 if no '\r' is found 244 cr = line.rfind("\r", 0, -2) 245 msg = msg + line[cr + 1:] 246 except OSError as e: 247 if (e.error == EEXIST): 248 pass 249 raise 250 return msg 251 252 @dbus.service.method( 253 DBUS_NAME, in_signature='', out_signature='') 254 def Apply(self): 255 progress = None 256 self.Set(DBUS_NAME, "status", "Writing images to flash") 257 try: 258 progress = tempfile.NamedTemporaryFile( 259 delete=False, prefix="progress.") 260 self.progress_name = progress.name 261 self.update_process = subprocess.Popen([ 262 "/run/initramfs/update"], 263 stdout=progress.file, 264 stderr=subprocess.STDOUT) 265 except Exception as e: 266 try: 267 progress.close() 268 os.unlink(progress.name) 269 self.progress_name = None 270 except: 271 pass 272 raise 273 274 try: 275 progress.close() 276 except: 277 pass 278 279 @dbus.service.method( 280 DBUS_NAME, in_signature='', out_signature='') 281 def PrepareForUpdate(self): 282 subprocess.call([ 283 "fw_setenv", 284 "openbmconce", 285 "copy-files-to-ram copy-base-filesystem-to-ram"]) 286 self.Set(DBUS_NAME, "status", "Switch to update mode in progress") 287 o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME) 288 intf = dbus.Interface(o, BMC_DBUS_NAME) 289 intf.warmReset() 290 291 292if __name__ == '__main__': 293 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 294 295 bus = get_dbus() 296 obj = BmcFlashControl(bus, OBJ_NAME) 297 mainloop = gobject.MainLoop() 298 299 obj.unmask_signals() 300 name = dbus.service.BusName(DBUS_NAME, bus) 301 302 print "Running Bmc Flash Control" 303 mainloop.run() 304 305# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 306