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