134c3bf9eSBrad Bishop#!/usr/bin/env python 240a360c2SBrad Bishop 3d65b2d50SCamVan Nguyen# TODO: openbmc/openbmc#2994 remove python 2 support 4d65b2d50SCamVan Nguyentry: # python 2 540a360c2SBrad Bishop import gobject 6d65b2d50SCamVan Nguyenexcept ImportError: # python 3 7d65b2d50SCamVan Nguyen from gi.repository import GObject as gobject 840a360c2SBrad Bishopimport dbus 940a360c2SBrad Bishopimport dbus.service 1040a360c2SBrad Bishopimport dbus.mainloop.glib 110c8c5d4aSMilton Millerimport subprocess 120c8c5d4aSMilton Millerimport tempfile 1340a360c2SBrad Bishopimport shutil 1440a360c2SBrad Bishopimport tarfile 1540a360c2SBrad Bishopimport os 1640a360c2SBrad Bishopfrom obmc.dbuslib.bindings import get_dbus, DbusProperties, DbusObjectManager 1740a360c2SBrad Bishop 18*75fe8cc4SPatrick WilliamsDBUS_NAME = "org.openbmc.control.BmcFlash" 19*75fe8cc4SPatrick WilliamsOBJ_NAME = "/org/openbmc/control/flash/bmc" 20*75fe8cc4SPatrick WilliamsDOWNLOAD_INTF = "org.openbmc.managers.Download" 2140a360c2SBrad Bishop 22*75fe8cc4SPatrick WilliamsBMC_DBUS_NAME = "xyz.openbmc_project.State.BMC" 23*75fe8cc4SPatrick WilliamsBMC_OBJ_NAME = "/xyz/openbmc_project/state/bmc0" 240c8c5d4aSMilton Miller 25*75fe8cc4SPatrick WilliamsUPDATE_PATH = "/run/initramfs" 2640a360c2SBrad Bishop 2740a360c2SBrad Bishop 2840a360c2SBrad Bishopdef doExtract(members, files): 2940a360c2SBrad Bishop for tarinfo in members: 3034c3bf9eSBrad Bishop if tarinfo.name in files: 3140a360c2SBrad Bishop yield tarinfo 3240a360c2SBrad Bishop 3340a360c2SBrad Bishop 343fc6b790SMilton Millerdef save_fw_env(): 353fc6b790SMilton Miller fw_env = "/etc/fw_env.config" 363fc6b790SMilton Miller lines = 0 373fc6b790SMilton Miller files = [] 38*75fe8cc4SPatrick Williams envcfg = open(fw_env, "r") 393fc6b790SMilton Miller try: 403fc6b790SMilton Miller for line in envcfg.readlines(): 413fc6b790SMilton Miller # ignore lines that are blank or start with # 42*75fe8cc4SPatrick Williams if line.startswith("#"): 4324341f9dSAdriana Kobylak continue 44*75fe8cc4SPatrick Williams if not len(line.strip()): 4524341f9dSAdriana Kobylak continue 4624341f9dSAdriana Kobylak fn = line.partition("\t")[0] 473fc6b790SMilton Miller files.append(fn) 483fc6b790SMilton Miller lines += 1 493fc6b790SMilton Miller finally: 503fc6b790SMilton Miller envcfg.close() 51*75fe8cc4SPatrick Williams if lines < 1 or lines > 2 or (lines == 2 and files[0] != files[1]): 523fc6b790SMilton Miller raise Exception("Error parsing %s\n" % fw_env) 533fc6b790SMilton Miller shutil.copyfile(files[0], os.path.join(UPDATE_PATH, "image-u-boot-env")) 543fc6b790SMilton Miller 5524341f9dSAdriana Kobylak 5640a360c2SBrad Bishopclass BmcFlashControl(DbusProperties, DbusObjectManager): 5740a360c2SBrad Bishop def __init__(self, bus, name): 58*75fe8cc4SPatrick Williams super(BmcFlashControl, self).__init__(conn=bus, object_path=name) 5940a360c2SBrad Bishop 6040a360c2SBrad Bishop self.Set(DBUS_NAME, "status", "Idle") 6140a360c2SBrad Bishop self.Set(DBUS_NAME, "filename", "") 62c8094109SMilton Miller self.Set(DBUS_NAME, "preserve_network_settings", True) 6340a360c2SBrad Bishop self.Set(DBUS_NAME, "restore_application_defaults", False) 6440a360c2SBrad Bishop self.Set(DBUS_NAME, "update_kernel_and_apps", False) 6540a360c2SBrad Bishop self.Set(DBUS_NAME, "clear_persistent_files", False) 660c8c5d4aSMilton Miller self.Set(DBUS_NAME, "auto_apply", False) 6740a360c2SBrad Bishop 6834c3bf9eSBrad Bishop bus.add_signal_receiver( 69*75fe8cc4SPatrick Williams self.download_error_handler, signal_name="DownloadError" 70*75fe8cc4SPatrick Williams ) 7134c3bf9eSBrad Bishop bus.add_signal_receiver( 72*75fe8cc4SPatrick Williams self.download_complete_handler, signal_name="DownloadComplete" 73*75fe8cc4SPatrick Williams ) 7440a360c2SBrad Bishop 750c8c5d4aSMilton Miller self.update_process = None 760c8c5d4aSMilton Miller self.progress_name = None 770c8c5d4aSMilton Miller 78*75fe8cc4SPatrick Williams @dbus.service.method(DBUS_NAME, in_signature="ss", out_signature="") 7940a360c2SBrad Bishop def updateViaTftp(self, ip, filename): 8040a360c2SBrad Bishop self.Set(DBUS_NAME, "status", "Downloading") 810c8c5d4aSMilton Miller self.TftpDownload(ip, filename) 8240a360c2SBrad Bishop 83*75fe8cc4SPatrick Williams @dbus.service.method(DBUS_NAME, in_signature="s", out_signature="") 8440a360c2SBrad Bishop def update(self, filename): 8540a360c2SBrad Bishop self.Set(DBUS_NAME, "filename", filename) 8640a360c2SBrad Bishop self.download_complete_handler(filename, filename) 8740a360c2SBrad Bishop 88*75fe8cc4SPatrick Williams @dbus.service.signal(DOWNLOAD_INTF, signature="ss") 8940a360c2SBrad Bishop def TftpDownload(self, ip, filename): 9040a360c2SBrad Bishop self.Set(DBUS_NAME, "filename", filename) 9140a360c2SBrad Bishop pass 9240a360c2SBrad Bishop 9324341f9dSAdriana Kobylak # Signal handler 9440a360c2SBrad Bishop def download_error_handler(self, filename): 95*75fe8cc4SPatrick Williams if filename == self.Get(DBUS_NAME, "filename"): 9640a360c2SBrad Bishop self.Set(DBUS_NAME, "status", "Download Error") 9740a360c2SBrad Bishop 9840a360c2SBrad Bishop def download_complete_handler(self, outfile, filename): 9924341f9dSAdriana Kobylak # do update 100*75fe8cc4SPatrick Williams if filename != self.Get(DBUS_NAME, "filename"): 10140a360c2SBrad Bishop return 10240a360c2SBrad Bishop 103d65b2d50SCamVan Nguyen print("Download complete. Updating...") 10440a360c2SBrad Bishop 10540a360c2SBrad Bishop self.Set(DBUS_NAME, "status", "Download Complete") 10640a360c2SBrad Bishop copy_files = {} 10740a360c2SBrad Bishop 10824341f9dSAdriana Kobylak # determine needed files 10934c3bf9eSBrad Bishop if not self.Get(DBUS_NAME, "update_kernel_and_apps"): 11040a360c2SBrad Bishop copy_files["image-bmc"] = True 11140a360c2SBrad Bishop else: 11240a360c2SBrad Bishop copy_files["image-kernel"] = True 11340a360c2SBrad Bishop copy_files["image-rofs"] = True 11440a360c2SBrad Bishop 11534c3bf9eSBrad Bishop if self.Get(DBUS_NAME, "restore_application_defaults"): 11640a360c2SBrad Bishop copy_files["image-rwfs"] = True 11740a360c2SBrad Bishop 11824341f9dSAdriana Kobylak # make sure files exist in archive 11940a360c2SBrad Bishop try: 12040a360c2SBrad Bishop tar = tarfile.open(outfile, "r") 12140a360c2SBrad Bishop files = {} 12240a360c2SBrad Bishop for f in tar.getnames(): 12340a360c2SBrad Bishop files[f] = True 12440a360c2SBrad Bishop tar.close() 125d65b2d50SCamVan Nguyen for f in list(copy_files.keys()): 12634c3bf9eSBrad Bishop if f not in files: 12734c3bf9eSBrad Bishop raise Exception( 128*75fe8cc4SPatrick Williams "ERROR: File not found in update archive: " + f 129*75fe8cc4SPatrick Williams ) 13040a360c2SBrad Bishop 13140a360c2SBrad Bishop except Exception as e: 132d65b2d50SCamVan Nguyen print(str(e)) 1330c8c5d4aSMilton Miller self.Set(DBUS_NAME, "status", "Unpack Error") 13440a360c2SBrad Bishop return 13540a360c2SBrad Bishop 13640a360c2SBrad Bishop try: 13740a360c2SBrad Bishop tar = tarfile.open(outfile, "r") 13840a360c2SBrad Bishop tar.extractall(UPDATE_PATH, members=doExtract(tar, copy_files)) 13940a360c2SBrad Bishop tar.close() 14040a360c2SBrad Bishop 14134c3bf9eSBrad Bishop if self.Get(DBUS_NAME, "clear_persistent_files"): 142d65b2d50SCamVan Nguyen print("Removing persistent files") 143b6cfc545SMilton Miller try: 14440a360c2SBrad Bishop os.unlink(UPDATE_PATH + "/whitelist") 145b6cfc545SMilton Miller except OSError as e: 146*75fe8cc4SPatrick Williams if e.errno == errno.EISDIR: 147b6cfc545SMilton Miller pass 148*75fe8cc4SPatrick Williams elif e.errno == errno.ENOENT: 149b6cfc545SMilton Miller pass 150b6cfc545SMilton Miller else: 151b6cfc545SMilton Miller raise 152b6cfc545SMilton Miller 153b6cfc545SMilton Miller try: 154b6cfc545SMilton Miller wldir = UPDATE_PATH + "/whitelist.d" 155b6cfc545SMilton Miller 156b6cfc545SMilton Miller for file in os.listdir(wldir): 157b6cfc545SMilton Miller os.unlink(os.path.join(wldir, file)) 158b6cfc545SMilton Miller except OSError as e: 159*75fe8cc4SPatrick Williams if e.errno == errno.EISDIR: 160b6cfc545SMilton Miller pass 161b6cfc545SMilton Miller else: 162b6cfc545SMilton Miller raise 163b6cfc545SMilton Miller 16434c3bf9eSBrad Bishop if self.Get(DBUS_NAME, "preserve_network_settings"): 165d65b2d50SCamVan Nguyen print("Preserving network settings") 1663fc6b790SMilton Miller save_fw_env() 16740a360c2SBrad Bishop 16840a360c2SBrad Bishop except Exception as e: 169d65b2d50SCamVan Nguyen print(str(e)) 1700c8c5d4aSMilton Miller self.Set(DBUS_NAME, "status", "Unpack Error") 1710c8c5d4aSMilton Miller 1720c8c5d4aSMilton Miller self.Verify() 1730c8c5d4aSMilton Miller 1740c8c5d4aSMilton Miller def Verify(self): 1750c8c5d4aSMilton Miller self.Set(DBUS_NAME, "status", "Checking Image") 1760c8c5d4aSMilton Miller try: 177*75fe8cc4SPatrick Williams subprocess.check_call( 178*75fe8cc4SPatrick Williams [ 1790c8c5d4aSMilton Miller "/run/initramfs/update", 1800c8c5d4aSMilton Miller "--no-flash", 1810c8c5d4aSMilton Miller "--no-save-files", 1820c8c5d4aSMilton Miller "--no-restore-files", 183*75fe8cc4SPatrick Williams "--no-clean-saved-files", 184*75fe8cc4SPatrick Williams ] 185*75fe8cc4SPatrick Williams ) 1860c8c5d4aSMilton Miller 1870c8c5d4aSMilton Miller self.Set(DBUS_NAME, "status", "Image ready to apply.") 188*75fe8cc4SPatrick Williams if self.Get(DBUS_NAME, "auto_apply"): 1890c8c5d4aSMilton Miller self.Apply() 19024341f9dSAdriana Kobylak except Exception: 1910c8c5d4aSMilton Miller self.Set(DBUS_NAME, "auto_apply", False) 1920c8c5d4aSMilton Miller try: 193*75fe8cc4SPatrick Williams subprocess.check_output( 194*75fe8cc4SPatrick Williams [ 1950c8c5d4aSMilton Miller "/run/initramfs/update", 1960c8c5d4aSMilton Miller "--no-flash", 1970c8c5d4aSMilton Miller "--ignore-mount", 1980c8c5d4aSMilton Miller "--no-save-files", 1990c8c5d4aSMilton Miller "--no-restore-files", 200*75fe8cc4SPatrick Williams "--no-clean-saved-files", 201*75fe8cc4SPatrick Williams ], 202*75fe8cc4SPatrick Williams stderr=subprocess.STDOUT, 203*75fe8cc4SPatrick Williams ) 20434c3bf9eSBrad Bishop self.Set( 205*75fe8cc4SPatrick Williams DBUS_NAME, 206*75fe8cc4SPatrick Williams "status", 207*75fe8cc4SPatrick Williams "Deferred for mounted filesystem. reboot BMC to apply.", 208*75fe8cc4SPatrick Williams ) 2090c8c5d4aSMilton Miller except subprocess.CalledProcessError as e: 210*75fe8cc4SPatrick Williams self.Set(DBUS_NAME, "status", "Verify error: %s" % e.output) 2110c8c5d4aSMilton Miller except OSError as e: 21234c3bf9eSBrad Bishop self.Set( 213*75fe8cc4SPatrick Williams DBUS_NAME, 214*75fe8cc4SPatrick Williams "status", 215*75fe8cc4SPatrick Williams "Verify error: problem calling update: %s" % e.strerror, 216*75fe8cc4SPatrick Williams ) 21740a360c2SBrad Bishop 2180c8c5d4aSMilton Miller def Cleanup(self): 2190c8c5d4aSMilton Miller if self.progress_name: 2200c8c5d4aSMilton Miller try: 2210c8c5d4aSMilton Miller os.unlink(self.progress_name) 2220c8c5d4aSMilton Miller self.progress_name = None 2230c8c5d4aSMilton Miller except oserror as e: 2240c8c5d4aSMilton Miller if e.errno == EEXIST: 2250c8c5d4aSMilton Miller pass 2260c8c5d4aSMilton Miller raise 2270c8c5d4aSMilton Miller self.update_process = None 2280c8c5d4aSMilton Miller self.Set(DBUS_NAME, "status", "Idle") 22940a360c2SBrad Bishop 230*75fe8cc4SPatrick Williams @dbus.service.method(DBUS_NAME, in_signature="", out_signature="") 2310c8c5d4aSMilton Miller def Abort(self): 2320c8c5d4aSMilton Miller if self.update_process: 2330c8c5d4aSMilton Miller try: 2340c8c5d4aSMilton Miller self.update_process.kill() 23524341f9dSAdriana Kobylak except Exception: 2360c8c5d4aSMilton Miller pass 2370c8c5d4aSMilton Miller for file in os.listdir(UPDATE_PATH): 238*75fe8cc4SPatrick Williams if file.startswith("image-"): 2390c8c5d4aSMilton Miller os.unlink(os.path.join(UPDATE_PATH, file)) 2400c8c5d4aSMilton Miller 24134c3bf9eSBrad Bishop self.Cleanup() 2420c8c5d4aSMilton Miller 243*75fe8cc4SPatrick Williams @dbus.service.method(DBUS_NAME, in_signature="", out_signature="s") 2440c8c5d4aSMilton Miller def GetUpdateProgress(self): 2450c8c5d4aSMilton Miller msg = "" 2460c8c5d4aSMilton Miller 2470c8c5d4aSMilton Miller if self.update_process and self.update_process.returncode is None: 2480c8c5d4aSMilton Miller self.update_process.poll() 2490c8c5d4aSMilton Miller 250*75fe8cc4SPatrick Williams if self.update_process is None: 2510c8c5d4aSMilton Miller pass 252*75fe8cc4SPatrick Williams elif self.update_process.returncode > 0: 2530c8c5d4aSMilton Miller self.Set(DBUS_NAME, "status", "Apply failed") 254*75fe8cc4SPatrick Williams elif self.update_process.returncode is None: 2550c8c5d4aSMilton Miller pass 2560c8c5d4aSMilton Miller else: # (self.update_process.returncode == 0) 2570c8c5d4aSMilton Miller files = "" 2580c8c5d4aSMilton Miller for file in os.listdir(UPDATE_PATH): 259*75fe8cc4SPatrick Williams if file.startswith("image-"): 26034c3bf9eSBrad Bishop files = files + file 2610c8c5d4aSMilton Miller if files == "": 2620c8c5d4aSMilton Miller msg = "Apply Complete. Reboot to take effect." 2630c8c5d4aSMilton Miller else: 2640c8c5d4aSMilton Miller msg = "Apply Incomplete, Remaining:" + files 2650c8c5d4aSMilton Miller self.Set(DBUS_NAME, "status", msg) 2660c8c5d4aSMilton Miller 26734c3bf9eSBrad Bishop msg = self.Get(DBUS_NAME, "status") + "\n" 2680c8c5d4aSMilton Miller if self.progress_name: 2690c8c5d4aSMilton Miller try: 270*75fe8cc4SPatrick Williams prog = open(self.progress_name, "r") 2710c8c5d4aSMilton Miller for line in prog: 2720c8c5d4aSMilton Miller # strip off initial sets of xxx\r here 2730c8c5d4aSMilton Miller # ignore crlf at the end 2740c8c5d4aSMilton Miller # cr will be -1 if no '\r' is found 2750c8c5d4aSMilton Miller cr = line.rfind("\r", 0, -2) 276*75fe8cc4SPatrick Williams msg = msg + line[(cr + 1):] 2770c8c5d4aSMilton Miller except OSError as e: 278*75fe8cc4SPatrick Williams if e.error == EEXIST: 2790c8c5d4aSMilton Miller pass 2800c8c5d4aSMilton Miller raise 2810c8c5d4aSMilton Miller return msg 2820c8c5d4aSMilton Miller 283*75fe8cc4SPatrick Williams @dbus.service.method(DBUS_NAME, in_signature="", out_signature="") 2840c8c5d4aSMilton Miller def Apply(self): 2850c8c5d4aSMilton Miller progress = None 2860c8c5d4aSMilton Miller self.Set(DBUS_NAME, "status", "Writing images to flash") 2870c8c5d4aSMilton Miller try: 2880c8c5d4aSMilton Miller progress = tempfile.NamedTemporaryFile( 289*75fe8cc4SPatrick Williams delete=False, prefix="progress." 290*75fe8cc4SPatrick Williams ) 2910c8c5d4aSMilton Miller self.progress_name = progress.name 292*75fe8cc4SPatrick Williams self.update_process = subprocess.Popen( 293*75fe8cc4SPatrick Williams ["/run/initramfs/update"], 2940c8c5d4aSMilton Miller stdout=progress.file, 295*75fe8cc4SPatrick Williams stderr=subprocess.STDOUT, 296*75fe8cc4SPatrick Williams ) 2970c8c5d4aSMilton Miller except Exception as e: 2980c8c5d4aSMilton Miller try: 2990c8c5d4aSMilton Miller progress.close() 3000c8c5d4aSMilton Miller os.unlink(progress.name) 3010c8c5d4aSMilton Miller self.progress_name = None 30224341f9dSAdriana Kobylak except Exception: 3030c8c5d4aSMilton Miller pass 3040c8c5d4aSMilton Miller raise 3050c8c5d4aSMilton Miller 3060c8c5d4aSMilton Miller try: 3070c8c5d4aSMilton Miller progress.close() 30824341f9dSAdriana Kobylak except Exception: 3090c8c5d4aSMilton Miller pass 3100c8c5d4aSMilton Miller 311*75fe8cc4SPatrick Williams @dbus.service.method(DBUS_NAME, in_signature="", out_signature="") 3120c8c5d4aSMilton Miller def PrepareForUpdate(self): 313*75fe8cc4SPatrick Williams subprocess.call( 314*75fe8cc4SPatrick Williams [ 3150c8c5d4aSMilton Miller "fw_setenv", 3160c8c5d4aSMilton Miller "openbmconce", 317*75fe8cc4SPatrick Williams "copy-files-to-ram copy-base-filesystem-to-ram", 318*75fe8cc4SPatrick Williams ] 319*75fe8cc4SPatrick Williams ) 3207bef82c0SAdriana Kobylak # Set the variable twice so that it is written to both environments of 3217bef82c0SAdriana Kobylak # the u-boot redundant environment variables since initramfs can only 3227bef82c0SAdriana Kobylak # read one of the environments. 323*75fe8cc4SPatrick Williams subprocess.call( 324*75fe8cc4SPatrick Williams [ 3257bef82c0SAdriana Kobylak "fw_setenv", 3267bef82c0SAdriana Kobylak "openbmconce", 327*75fe8cc4SPatrick Williams "copy-files-to-ram copy-base-filesystem-to-ram", 328*75fe8cc4SPatrick Williams ] 329*75fe8cc4SPatrick Williams ) 3300c8c5d4aSMilton Miller self.Set(DBUS_NAME, "status", "Switch to update mode in progress") 3310c8c5d4aSMilton Miller o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME) 3325a38763fSLei YU intf = dbus.Interface(o, "org.freedesktop.DBus.Properties") 333*75fe8cc4SPatrick Williams intf.Set( 334*75fe8cc4SPatrick Williams BMC_DBUS_NAME, 3355a38763fSLei YU "RequestedBMCTransition", 336*75fe8cc4SPatrick Williams "xyz.openbmc_project.State.BMC.Transition.Reboot", 337*75fe8cc4SPatrick Williams ) 33840a360c2SBrad Bishop 33940a360c2SBrad Bishop 340*75fe8cc4SPatrick Williamsif __name__ == "__main__": 34140a360c2SBrad Bishop dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 34240a360c2SBrad Bishop 34340a360c2SBrad Bishop bus = get_dbus() 34440a360c2SBrad Bishop obj = BmcFlashControl(bus, OBJ_NAME) 34540a360c2SBrad Bishop mainloop = gobject.MainLoop() 346f0f3efe1SBrad Bishop 347f0f3efe1SBrad Bishop obj.unmask_signals() 34870852a38SBrad Bishop name = dbus.service.BusName(DBUS_NAME, bus) 34940a360c2SBrad Bishop 350d65b2d50SCamVan Nguyen print("Running Bmc Flash Control") 35140a360c2SBrad Bishop mainloop.run() 35253066750SBrad Bishop 35353066750SBrad Bishop# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 354