xref: /openbmc/skeleton/pyflashbmc/bmc_update.py (revision 1664c8ee6df323b85272d1c56c9bb42c7380bbf3)
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