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