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