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