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