xref: /openbmc/skeleton/pyflashbmc/bmc_update.py (revision 0c8c5d4a)
140a360c2SBrad Bishop#!/usr/bin/python -u
240a360c2SBrad Bishop
340a360c2SBrad Bishopimport gobject
440a360c2SBrad Bishopimport dbus
540a360c2SBrad Bishopimport dbus.service
640a360c2SBrad Bishopimport dbus.mainloop.glib
7*0c8c5d4aSMilton Millerimport subprocess
8*0c8c5d4aSMilton Millerimport tempfile
940a360c2SBrad Bishopimport shutil
1040a360c2SBrad Bishopimport tarfile
1140a360c2SBrad Bishopimport os
1240a360c2SBrad Bishopfrom obmc.dbuslib.bindings import get_dbus, DbusProperties, DbusObjectManager
1340a360c2SBrad Bishop
1440a360c2SBrad BishopDBUS_NAME = 'org.openbmc.control.BmcFlash'
1540a360c2SBrad BishopOBJ_NAME = '/org/openbmc/control/flash/bmc'
1640a360c2SBrad BishopFLASH_INTF = 'org.openbmc.Flash'
1740a360c2SBrad BishopDOWNLOAD_INTF = 'org.openbmc.managers.Download'
1840a360c2SBrad Bishop
19*0c8c5d4aSMilton MillerBMC_DBUS_NAME = 'org.openbmc.control.Bmc'
20*0c8c5d4aSMilton MillerBMC_OBJ_NAME = '/org/openbmc/control/bmc0'
21*0c8c5d4aSMilton Miller
2240a360c2SBrad BishopUPDATE_PATH = '/run/initramfs'
2340a360c2SBrad Bishop
2440a360c2SBrad Bishop
2540a360c2SBrad Bishopdef doExtract(members,files):
2640a360c2SBrad Bishop    for tarinfo in members:
2740a360c2SBrad Bishop        if files.has_key(tarinfo.name) == True:
2840a360c2SBrad Bishop            yield tarinfo
2940a360c2SBrad Bishop
3040a360c2SBrad Bishop
3140a360c2SBrad Bishopclass BmcFlashControl(DbusProperties,DbusObjectManager):
3240a360c2SBrad Bishop	def __init__(self,bus,name):
3340a360c2SBrad Bishop		self.dbus_objects = { }
3440a360c2SBrad Bishop		DbusProperties.__init__(self)
3540a360c2SBrad Bishop		DbusObjectManager.__init__(self)
3640a360c2SBrad Bishop		dbus.service.Object.__init__(self,bus,name)
3740a360c2SBrad Bishop
3840a360c2SBrad Bishop		self.Set(DBUS_NAME,"status","Idle")
3940a360c2SBrad Bishop		self.Set(DBUS_NAME,"filename","")
40c8094109SMilton Miller		self.Set(DBUS_NAME,"preserve_network_settings",True)
4140a360c2SBrad Bishop		self.Set(DBUS_NAME,"restore_application_defaults",False)
4240a360c2SBrad Bishop		self.Set(DBUS_NAME,"update_kernel_and_apps",False)
4340a360c2SBrad Bishop		self.Set(DBUS_NAME,"clear_persistent_files",False)
44*0c8c5d4aSMilton Miller		self.Set(DBUS_NAME,"auto_apply",False)
4540a360c2SBrad Bishop
4640a360c2SBrad Bishop		bus.add_signal_receiver(self.download_error_handler,signal_name = "DownloadError")
4740a360c2SBrad Bishop		bus.add_signal_receiver(self.download_complete_handler,signal_name = "DownloadComplete")
4840a360c2SBrad Bishop
49*0c8c5d4aSMilton Miller		self.update_process = None
50*0c8c5d4aSMilton Miller		self.progress_name = None
51*0c8c5d4aSMilton Miller
5240a360c2SBrad Bishop		self.InterfacesAdded(name,self.properties)
5340a360c2SBrad Bishop
5440a360c2SBrad Bishop
5540a360c2SBrad Bishop	@dbus.service.method(DBUS_NAME,
5640a360c2SBrad Bishop		in_signature='ss', out_signature='')
5740a360c2SBrad Bishop	def updateViaTftp(self,ip,filename):
5840a360c2SBrad Bishop		self.Set(DBUS_NAME,"status","Downloading")
59*0c8c5d4aSMilton Miller		self.TftpDownload(ip,filename)
6040a360c2SBrad Bishop
6140a360c2SBrad Bishop	@dbus.service.method(DBUS_NAME,
6240a360c2SBrad Bishop		in_signature='s', out_signature='')
6340a360c2SBrad Bishop	def update(self,filename):
6440a360c2SBrad Bishop		self.Set(DBUS_NAME,"filename",filename)
6540a360c2SBrad Bishop		self.download_complete_handler(filename, filename)
6640a360c2SBrad Bishop
6740a360c2SBrad Bishop	@dbus.service.signal(DOWNLOAD_INTF,signature='ss')
6840a360c2SBrad Bishop	def TftpDownload(self,ip,filename):
6940a360c2SBrad Bishop		self.Set(DBUS_NAME,"filename",filename)
7040a360c2SBrad Bishop		pass
7140a360c2SBrad Bishop
7240a360c2SBrad Bishop
7340a360c2SBrad Bishop	## Signal handler
7440a360c2SBrad Bishop	def download_error_handler(self,filename):
7540a360c2SBrad Bishop		if (filename == self.Get(DBUS_NAME,"filename")):
7640a360c2SBrad Bishop			self.Set(DBUS_NAME,"status","Download Error")
7740a360c2SBrad Bishop
7840a360c2SBrad Bishop	def download_complete_handler(self,outfile,filename):
7940a360c2SBrad Bishop		## do update
8040a360c2SBrad Bishop		if (filename != self.Get(DBUS_NAME,"filename")):
8140a360c2SBrad Bishop			return
8240a360c2SBrad Bishop
8340a360c2SBrad Bishop		print "Download complete. Updating..."
8440a360c2SBrad Bishop
8540a360c2SBrad Bishop		self.Set(DBUS_NAME,"status","Download Complete")
8640a360c2SBrad Bishop		copy_files = {}
8740a360c2SBrad Bishop
8840a360c2SBrad Bishop		## determine needed files
8940a360c2SBrad Bishop		if (self.Get(DBUS_NAME,"update_kernel_and_apps") == False):
9040a360c2SBrad Bishop			copy_files["image-bmc"] = True
9140a360c2SBrad Bishop		else:
9240a360c2SBrad Bishop			copy_files["image-kernel"] = True
9340a360c2SBrad Bishop			copy_files["image-initramfs"] = True
9440a360c2SBrad Bishop			copy_files["image-rofs"] = True
9540a360c2SBrad Bishop
9640a360c2SBrad Bishop		if (self.Get(DBUS_NAME,"restore_application_defaults") == True):
9740a360c2SBrad Bishop			copy_files["image-rwfs"] = True
9840a360c2SBrad Bishop
9940a360c2SBrad Bishop
10040a360c2SBrad Bishop		## make sure files exist in archive
10140a360c2SBrad Bishop		try:
10240a360c2SBrad Bishop			tar = tarfile.open(outfile,"r")
10340a360c2SBrad Bishop			files = {}
10440a360c2SBrad Bishop			for f in tar.getnames():
10540a360c2SBrad Bishop				files[f] = True
10640a360c2SBrad Bishop			tar.close()
10740a360c2SBrad Bishop			for f in copy_files.keys():
10840a360c2SBrad Bishop				if (files.has_key(f) == False):
10940a360c2SBrad Bishop					raise Exception("ERROR: File not found in update archive: "+f)
11040a360c2SBrad Bishop
11140a360c2SBrad Bishop		except Exception as e:
11240a360c2SBrad Bishop			print e
113*0c8c5d4aSMilton Miller			self.Set(DBUS_NAME,"status","Unpack Error")
11440a360c2SBrad Bishop			return
11540a360c2SBrad Bishop
11640a360c2SBrad Bishop		try:
11740a360c2SBrad Bishop			tar = tarfile.open(outfile,"r")
11840a360c2SBrad Bishop			tar.extractall(UPDATE_PATH,members=doExtract(tar,copy_files))
11940a360c2SBrad Bishop			tar.close()
12040a360c2SBrad Bishop
12140a360c2SBrad Bishop			if (self.Get(DBUS_NAME,"clear_persistent_files") == True):
12240a360c2SBrad Bishop				print "Removing persistent files"
123b6cfc545SMilton Miller				try:
12440a360c2SBrad Bishop					os.unlink(UPDATE_PATH+"/whitelist")
125b6cfc545SMilton Miller				except OSError as e:
126b6cfc545SMilton Miller					if (e.errno == errno.EISDIR):
127b6cfc545SMilton Miller						pass
128b6cfc545SMilton Miller					elif (e.errno == errno.ENOENT):
129b6cfc545SMilton Miller						pass
130b6cfc545SMilton Miller					else:
131b6cfc545SMilton Miller						raise
132b6cfc545SMilton Miller
133b6cfc545SMilton Miller				try:
134b6cfc545SMilton Miller					wldir = UPDATE_PATH + "/whitelist.d"
135b6cfc545SMilton Miller
136b6cfc545SMilton Miller					for file in os.listdir(wldir):
137b6cfc545SMilton Miller						os.unlink(os.path.join(wldir,file))
138b6cfc545SMilton Miller				except OSError as e:
139b6cfc545SMilton Miller					if (e.errno == errno.EISDIR):
140b6cfc545SMilton Miller						pass
141b6cfc545SMilton Miller					else:
142b6cfc545SMilton Miller						raise
143b6cfc545SMilton Miller
14440a360c2SBrad Bishop			if (self.Get(DBUS_NAME,"preserve_network_settings") == True):
14540a360c2SBrad Bishop				print "Preserving network settings"
146c8094109SMilton Miller				shutil.copy2("/run/fw_env",UPDATE_PATH+"/image-u-boot-env")
14740a360c2SBrad Bishop
14840a360c2SBrad Bishop		except Exception as e:
14940a360c2SBrad Bishop			print e
150*0c8c5d4aSMilton Miller			self.Set(DBUS_NAME,"status","Unpack Error")
151*0c8c5d4aSMilton Miller
152*0c8c5d4aSMilton Miller		self.Verify()
153*0c8c5d4aSMilton Miller
154*0c8c5d4aSMilton Miller	def Verify(self):
155*0c8c5d4aSMilton Miller		self.Set(DBUS_NAME,"status","Checking Image")
156*0c8c5d4aSMilton Miller		try:
157*0c8c5d4aSMilton Miller			subprocess.check_call([
158*0c8c5d4aSMilton Miller				"/run/initramfs/update",
159*0c8c5d4aSMilton Miller				"--no-flash",
160*0c8c5d4aSMilton Miller				"--no-save-files",
161*0c8c5d4aSMilton Miller				"--no-restore-files",
162*0c8c5d4aSMilton Miller				"--no-clean-saved-files" ])
163*0c8c5d4aSMilton Miller
164*0c8c5d4aSMilton Miller			self.Set(DBUS_NAME,"status","Image ready to apply.")
165*0c8c5d4aSMilton Miller			if (self.Get(DBUS_NAME,"auto_apply")):
166*0c8c5d4aSMilton Miller				self.Apply()
167*0c8c5d4aSMilton Miller		except:
168*0c8c5d4aSMilton Miller			self.Set(DBUS_NAME,"auto_apply",False)
169*0c8c5d4aSMilton Miller			try:
170*0c8c5d4aSMilton Miller				subprocess.check_output([
171*0c8c5d4aSMilton Miller					"/run/initramfs/update",
172*0c8c5d4aSMilton Miller					"--no-flash",
173*0c8c5d4aSMilton Miller					"--ignore-mount",
174*0c8c5d4aSMilton Miller					"--no-save-files",
175*0c8c5d4aSMilton Miller					"--no-restore-files",
176*0c8c5d4aSMilton Miller					"--no-clean-saved-files" ],
177*0c8c5d4aSMilton Miller					stderr = subprocess.STDOUT)
178*0c8c5d4aSMilton Miller				self.Set(DBUS_NAME,"status","Deferred for mounted filesystem. reboot BMC to apply.")
179*0c8c5d4aSMilton Miller			except subprocess.CalledProcessError as e:
180*0c8c5d4aSMilton Miller				self.Set(DBUS_NAME,"status","Verify error: %s"
181*0c8c5d4aSMilton Miller					%  e.output)
182*0c8c5d4aSMilton Miller			except OSError as e:
183*0c8c5d4aSMilton Miller				self.Set(DBUS_NAME,"status","Verify error: problem calling update: %s" %  e.strerror)
18440a360c2SBrad Bishop
18540a360c2SBrad Bishop
186*0c8c5d4aSMilton Miller	def Cleanup(self):
187*0c8c5d4aSMilton Miller		if self.progress_name:
188*0c8c5d4aSMilton Miller			try:
189*0c8c5d4aSMilton Miller				os.unlink(self.progress_name)
190*0c8c5d4aSMilton Miller				self.progress_name = None
191*0c8c5d4aSMilton Miller			except oserror as e:
192*0c8c5d4aSMilton Miller				if e.errno == EEXIST:
193*0c8c5d4aSMilton Miller					pass
194*0c8c5d4aSMilton Miller				raise
195*0c8c5d4aSMilton Miller		self.update_process = None
196*0c8c5d4aSMilton Miller		self.Set(DBUS_NAME,"status","Idle")
19740a360c2SBrad Bishop
198*0c8c5d4aSMilton Miller	@dbus.service.method(DBUS_NAME,
199*0c8c5d4aSMilton Miller		in_signature='', out_signature='')
200*0c8c5d4aSMilton Miller	def Abort(self):
201*0c8c5d4aSMilton Miller		if self.update_process:
202*0c8c5d4aSMilton Miller			try:
203*0c8c5d4aSMilton Miller				self.update_process.kill()
204*0c8c5d4aSMilton Miller			except:
205*0c8c5d4aSMilton Miller				pass
206*0c8c5d4aSMilton Miller		for file in os.listdir(UPDATE_PATH):
207*0c8c5d4aSMilton Miller			if file.startswith('image-'):
208*0c8c5d4aSMilton Miller				os.unlink(os.path.join(UPDATE_PATH,file))
209*0c8c5d4aSMilton Miller
210*0c8c5d4aSMilton Miller		self.Cleanup();
211*0c8c5d4aSMilton Miller
212*0c8c5d4aSMilton Miller	@dbus.service.method(DBUS_NAME,
213*0c8c5d4aSMilton Miller		in_signature='', out_signature='s')
214*0c8c5d4aSMilton Miller	def GetUpdateProgress(self):
215*0c8c5d4aSMilton Miller		msg = ""
216*0c8c5d4aSMilton Miller
217*0c8c5d4aSMilton Miller		if self.update_process and self.update_process.returncode is None:
218*0c8c5d4aSMilton Miller			self.update_process.poll()
219*0c8c5d4aSMilton Miller
220*0c8c5d4aSMilton Miller		if (self.update_process is None):
221*0c8c5d4aSMilton Miller			pass
222*0c8c5d4aSMilton Miller		elif (self.update_process.returncode > 0):
223*0c8c5d4aSMilton Miller			self.Set(DBUS_NAME,"status","Apply failed")
224*0c8c5d4aSMilton Miller		elif (self.update_process.returncode is None):
225*0c8c5d4aSMilton Miller			pass
226*0c8c5d4aSMilton Miller		else:			# (self.update_process.returncode == 0)
227*0c8c5d4aSMilton Miller			files = ""
228*0c8c5d4aSMilton Miller			for file in os.listdir(UPDATE_PATH):
229*0c8c5d4aSMilton Miller				if file.startswith('image-'):
230*0c8c5d4aSMilton Miller					files = files + file;
231*0c8c5d4aSMilton Miller			if files == "":
232*0c8c5d4aSMilton Miller				msg = "Apply Complete.  Reboot to take effect."
233*0c8c5d4aSMilton Miller			else:
234*0c8c5d4aSMilton Miller				msg = "Apply Incomplete, Remaining:" + files
235*0c8c5d4aSMilton Miller			self.Set(DBUS_NAME,"status", msg)
236*0c8c5d4aSMilton Miller
237*0c8c5d4aSMilton Miller		msg = self.Get(DBUS_NAME,"status") + "\n";
238*0c8c5d4aSMilton Miller		if self.progress_name:
239*0c8c5d4aSMilton Miller			try:
240*0c8c5d4aSMilton Miller				prog = open(self.progress_name,'r')
241*0c8c5d4aSMilton Miller				for line in prog:
242*0c8c5d4aSMilton Miller					# strip off initial sets of xxx\r here
243*0c8c5d4aSMilton Miller					# ignore crlf at the end
244*0c8c5d4aSMilton Miller					# cr will be -1 if no '\r' is found
245*0c8c5d4aSMilton Miller					cr = line.rfind("\r", 0, -2)
246*0c8c5d4aSMilton Miller					msg = msg + line[cr + 1: ]
247*0c8c5d4aSMilton Miller			except OSError as e:
248*0c8c5d4aSMilton Miller				if (e.error == EEXIST):
249*0c8c5d4aSMilton Miller					pass
250*0c8c5d4aSMilton Miller				raise
251*0c8c5d4aSMilton Miller		return msg
252*0c8c5d4aSMilton Miller
253*0c8c5d4aSMilton Miller	@dbus.service.method(DBUS_NAME,
254*0c8c5d4aSMilton Miller		in_signature='', out_signature='')
255*0c8c5d4aSMilton Miller	def Apply(self):
256*0c8c5d4aSMilton Miller		progress = None
257*0c8c5d4aSMilton Miller		self.Set(DBUS_NAME,"status","Writing images to flash")
258*0c8c5d4aSMilton Miller		try:
259*0c8c5d4aSMilton Miller			progress = tempfile.NamedTemporaryFile(
260*0c8c5d4aSMilton Miller				delete = False, prefix="progress." )
261*0c8c5d4aSMilton Miller			self.progress_name = progress.name
262*0c8c5d4aSMilton Miller			self.update_process = subprocess.Popen([
263*0c8c5d4aSMilton Miller				"/run/initramfs/update" ],
264*0c8c5d4aSMilton Miller				stdout = progress.file,
265*0c8c5d4aSMilton Miller				stderr = subprocess.STDOUT )
266*0c8c5d4aSMilton Miller		except Exception as e:
267*0c8c5d4aSMilton Miller			try:
268*0c8c5d4aSMilton Miller				progress.close()
269*0c8c5d4aSMilton Miller				os.unlink(progress.name)
270*0c8c5d4aSMilton Miller				self.progress_name = None
271*0c8c5d4aSMilton Miller			except:
272*0c8c5d4aSMilton Miller				pass
273*0c8c5d4aSMilton Miller			raise
274*0c8c5d4aSMilton Miller
275*0c8c5d4aSMilton Miller		try:
276*0c8c5d4aSMilton Miller			progress.close()
277*0c8c5d4aSMilton Miller		except:
278*0c8c5d4aSMilton Miller			pass
279*0c8c5d4aSMilton Miller
280*0c8c5d4aSMilton Miller	@dbus.service.method(DBUS_NAME,
281*0c8c5d4aSMilton Miller		in_signature='', out_signature='')
282*0c8c5d4aSMilton Miller	def PrepareForUpdate(self):
283*0c8c5d4aSMilton Miller		subprocess.call([
284*0c8c5d4aSMilton Miller			"fw_setenv",
285*0c8c5d4aSMilton Miller			"openbmconce",
286*0c8c5d4aSMilton Miller			"copy-files-to-ram copy-base-filesystem-to-ram"])
287*0c8c5d4aSMilton Miller		self.Set(DBUS_NAME,"status","Switch to update mode in progress")
288*0c8c5d4aSMilton Miller		o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME)
289*0c8c5d4aSMilton Miller		intf = dbus.Interface(o, BMC_DBUS_NAME)
290*0c8c5d4aSMilton Miller		intf.warmReset()
29140a360c2SBrad Bishop
29240a360c2SBrad Bishop
29340a360c2SBrad Bishopif __name__ == '__main__':
29440a360c2SBrad Bishop    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
29540a360c2SBrad Bishop
29640a360c2SBrad Bishop    bus = get_dbus()
29740a360c2SBrad Bishop    name = dbus.service.BusName(DBUS_NAME, bus)
29840a360c2SBrad Bishop    obj = BmcFlashControl(bus, OBJ_NAME)
29940a360c2SBrad Bishop    mainloop = gobject.MainLoop()
30040a360c2SBrad Bishop
30140a360c2SBrad Bishop    print "Running Bmc Flash Control"
30240a360c2SBrad Bishop    mainloop.run()
30340a360c2SBrad Bishop
304