1#!/usr/bin/python -u 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 files.has_key(tarinfo.name) == True: 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(self.download_error_handler,signal_name = "DownloadError") 46 bus.add_signal_receiver(self.download_complete_handler,signal_name = "DownloadComplete") 47 48 self.update_process = None 49 self.progress_name = None 50 51 52 @dbus.service.method(DBUS_NAME, 53 in_signature='ss', out_signature='') 54 def updateViaTftp(self,ip,filename): 55 self.Set(DBUS_NAME,"status","Downloading") 56 self.TftpDownload(ip,filename) 57 58 @dbus.service.method(DBUS_NAME, 59 in_signature='s', out_signature='') 60 def update(self,filename): 61 self.Set(DBUS_NAME,"filename",filename) 62 self.download_complete_handler(filename, filename) 63 64 @dbus.service.signal(DOWNLOAD_INTF,signature='ss') 65 def TftpDownload(self,ip,filename): 66 self.Set(DBUS_NAME,"filename",filename) 67 pass 68 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 (self.Get(DBUS_NAME,"update_kernel_and_apps") == False): 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") == True): 94 copy_files["image-rwfs"] = True 95 96 97 ## make sure files exist in archive 98 try: 99 tar = tarfile.open(outfile,"r") 100 files = {} 101 for f in tar.getnames(): 102 files[f] = True 103 tar.close() 104 for f in copy_files.keys(): 105 if (files.has_key(f) == False): 106 raise Exception("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") == True): 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") == True): 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(DBUS_NAME,"status","Deferred for mounted filesystem. reboot BMC to apply.") 176 except subprocess.CalledProcessError as e: 177 self.Set(DBUS_NAME,"status","Verify error: %s" 178 % e.output) 179 except OSError as e: 180 self.Set(DBUS_NAME,"status","Verify error: problem calling update: %s" % e.strerror) 181 182 183 def Cleanup(self): 184 if self.progress_name: 185 try: 186 os.unlink(self.progress_name) 187 self.progress_name = None 188 except oserror as e: 189 if e.errno == EEXIST: 190 pass 191 raise 192 self.update_process = None 193 self.Set(DBUS_NAME,"status","Idle") 194 195 @dbus.service.method(DBUS_NAME, 196 in_signature='', out_signature='') 197 def Abort(self): 198 if self.update_process: 199 try: 200 self.update_process.kill() 201 except: 202 pass 203 for file in os.listdir(UPDATE_PATH): 204 if file.startswith('image-'): 205 os.unlink(os.path.join(UPDATE_PATH,file)) 206 207 self.Cleanup(); 208 209 @dbus.service.method(DBUS_NAME, 210 in_signature='', out_signature='s') 211 def GetUpdateProgress(self): 212 msg = "" 213 214 if self.update_process and self.update_process.returncode is None: 215 self.update_process.poll() 216 217 if (self.update_process is None): 218 pass 219 elif (self.update_process.returncode > 0): 220 self.Set(DBUS_NAME,"status","Apply failed") 221 elif (self.update_process.returncode is None): 222 pass 223 else: # (self.update_process.returncode == 0) 224 files = "" 225 for file in os.listdir(UPDATE_PATH): 226 if file.startswith('image-'): 227 files = files + file; 228 if files == "": 229 msg = "Apply Complete. Reboot to take effect." 230 else: 231 msg = "Apply Incomplete, Remaining:" + files 232 self.Set(DBUS_NAME,"status", msg) 233 234 msg = self.Get(DBUS_NAME,"status") + "\n"; 235 if self.progress_name: 236 try: 237 prog = open(self.progress_name,'r') 238 for line in prog: 239 # strip off initial sets of xxx\r here 240 # ignore crlf at the end 241 # cr will be -1 if no '\r' is found 242 cr = line.rfind("\r", 0, -2) 243 msg = msg + line[cr + 1: ] 244 except OSError as e: 245 if (e.error == EEXIST): 246 pass 247 raise 248 return msg 249 250 @dbus.service.method(DBUS_NAME, 251 in_signature='', out_signature='') 252 def Apply(self): 253 progress = None 254 self.Set(DBUS_NAME,"status","Writing images to flash") 255 try: 256 progress = tempfile.NamedTemporaryFile( 257 delete = False, prefix="progress." ) 258 self.progress_name = progress.name 259 self.update_process = subprocess.Popen([ 260 "/run/initramfs/update" ], 261 stdout = progress.file, 262 stderr = subprocess.STDOUT ) 263 except Exception as e: 264 try: 265 progress.close() 266 os.unlink(progress.name) 267 self.progress_name = None 268 except: 269 pass 270 raise 271 272 try: 273 progress.close() 274 except: 275 pass 276 277 @dbus.service.method(DBUS_NAME, 278 in_signature='', out_signature='') 279 def PrepareForUpdate(self): 280 subprocess.call([ 281 "fw_setenv", 282 "openbmconce", 283 "copy-files-to-ram copy-base-filesystem-to-ram"]) 284 self.Set(DBUS_NAME,"status","Switch to update mode in progress") 285 o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME) 286 intf = dbus.Interface(o, BMC_DBUS_NAME) 287 intf.warmReset() 288 289 290if __name__ == '__main__': 291 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 292 293 bus = get_dbus() 294 obj = BmcFlashControl(bus, OBJ_NAME) 295 mainloop = gobject.MainLoop() 296 297 obj.unmask_signals() 298 name = dbus.service.BusName(DBUS_NAME, bus) 299 300 print "Running Bmc Flash Control" 301 mainloop.run() 302 303