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