blob: 8c5936e8b7f950aa42c8ce6eed6ebf09a1cb86bf [file] [log] [blame]
Brad Bishop34c3bf92016-08-30 20:34:51 -04001#!/usr/bin/env python
Norman James3d6a06f2016-01-30 13:03:45 -06002
CamVan Nguyend65b2d52018-02-27 15:14:41 -06003# TODO: openbmc/openbmc#2994 remove python 2 support
4try: # python 2
5 import gobject
6except ImportError: # python 3
7 from gi.repository import GObject as gobject
Norman James3d6a06f2016-01-30 13:03:45 -06008import dbus
9import dbus.service
10import dbus.mainloop.glib
Milton Miller0c8c5d42016-06-29 17:41:36 -050011import subprocess
12import tempfile
Norman James3d6a06f2016-01-30 13:03:45 -060013import shutil
14import tarfile
Norman James8a65a542016-02-03 19:04:44 -060015import os
Brad Bishop84e73b52016-05-12 15:57:52 -040016from obmc.dbuslib.bindings import get_dbus, DbusProperties, DbusObjectManager
Norman James3d6a06f2016-01-30 13:03:45 -060017
18DBUS_NAME = 'org.openbmc.control.BmcFlash'
19OBJ_NAME = '/org/openbmc/control/flash/bmc'
Norman James3d6a06f2016-01-30 13:03:45 -060020DOWNLOAD_INTF = 'org.openbmc.managers.Download'
21
Milton Miller0c8c5d42016-06-29 17:41:36 -050022BMC_DBUS_NAME = 'org.openbmc.control.Bmc'
23BMC_OBJ_NAME = '/org/openbmc/control/bmc0'
24
Norman James3d6a06f2016-01-30 13:03:45 -060025UPDATE_PATH = '/run/initramfs'
26
27
Brad Bishop34c3bf92016-08-30 20:34:51 -040028def doExtract(members, files):
Norman James3d6a06f2016-01-30 13:03:45 -060029 for tarinfo in members:
Brad Bishop34c3bf92016-08-30 20:34:51 -040030 if tarinfo.name in files:
Norman James3d6a06f2016-01-30 13:03:45 -060031 yield tarinfo
32
33
Milton Miller3fc6b792017-08-15 06:57:14 +000034def save_fw_env():
35 fw_env = "/etc/fw_env.config"
36 lines = 0
Adriana Kobylak24341f92018-01-26 15:07:23 -060037 files = []
Milton Miller3fc6b792017-08-15 06:57:14 +000038 envcfg = open(fw_env, 'r')
39 try:
40 for line in envcfg.readlines():
41 # ignore lines that are blank or start with #
Adriana Kobylak24341f92018-01-26 15:07:23 -060042 if (line.startswith("#")):
43 continue
44 if (not len(line.strip())):
45 continue
46 fn = line.partition("\t")[0]
Milton Miller3fc6b792017-08-15 06:57:14 +000047 files.append(fn)
48 lines += 1
49 finally:
50 envcfg.close()
51 if (lines < 1 or lines > 2 or (lines == 2 and files[0] != files[1])):
52 raise Exception("Error parsing %s\n" % fw_env)
53 shutil.copyfile(files[0], os.path.join(UPDATE_PATH, "image-u-boot-env"))
54
Adriana Kobylak24341f92018-01-26 15:07:23 -060055
Brad Bishop34c3bf92016-08-30 20:34:51 -040056class BmcFlashControl(DbusProperties, DbusObjectManager):
57 def __init__(self, bus, name):
Brad Bishopf47f5fa2016-09-08 22:29:01 -040058 super(BmcFlashControl, self).__init__(
59 conn=bus,
60 object_path=name)
Milton Miller0c8c5d42016-06-29 17:41:36 -050061
Brad Bishop34c3bf92016-08-30 20:34:51 -040062 self.Set(DBUS_NAME, "status", "Idle")
63 self.Set(DBUS_NAME, "filename", "")
64 self.Set(DBUS_NAME, "preserve_network_settings", True)
65 self.Set(DBUS_NAME, "restore_application_defaults", False)
66 self.Set(DBUS_NAME, "update_kernel_and_apps", False)
67 self.Set(DBUS_NAME, "clear_persistent_files", False)
68 self.Set(DBUS_NAME, "auto_apply", False)
Norman James3d6a06f2016-01-30 13:03:45 -060069
Brad Bishop34c3bf92016-08-30 20:34:51 -040070 bus.add_signal_receiver(
71 self.download_error_handler, signal_name="DownloadError")
72 bus.add_signal_receiver(
73 self.download_complete_handler, signal_name="DownloadComplete")
Milton Miller0c8c5d42016-06-29 17:41:36 -050074
Brad Bishop34c3bf92016-08-30 20:34:51 -040075 self.update_process = None
76 self.progress_name = None
Norman James3d6a06f2016-01-30 13:03:45 -060077
Brad Bishop34c3bf92016-08-30 20:34:51 -040078 @dbus.service.method(
79 DBUS_NAME, in_signature='ss', out_signature='')
80 def updateViaTftp(self, ip, filename):
81 self.Set(DBUS_NAME, "status", "Downloading")
82 self.TftpDownload(ip, filename)
Milton Miller0c8c5d42016-06-29 17:41:36 -050083
Brad Bishop34c3bf92016-08-30 20:34:51 -040084 @dbus.service.method(
85 DBUS_NAME, in_signature='s', out_signature='')
86 def update(self, filename):
87 self.Set(DBUS_NAME, "filename", filename)
88 self.download_complete_handler(filename, filename)
Chris Austenea5b4012016-05-20 16:41:40 -050089
Brad Bishop34c3bf92016-08-30 20:34:51 -040090 @dbus.service.signal(DOWNLOAD_INTF, signature='ss')
91 def TftpDownload(self, ip, filename):
92 self.Set(DBUS_NAME, "filename", filename)
93 pass
Norman James3d6a06f2016-01-30 13:03:45 -060094
Adriana Kobylak24341f92018-01-26 15:07:23 -060095 # Signal handler
Brad Bishop34c3bf92016-08-30 20:34:51 -040096 def download_error_handler(self, filename):
97 if (filename == self.Get(DBUS_NAME, "filename")):
98 self.Set(DBUS_NAME, "status", "Download Error")
Norman James3d6a06f2016-01-30 13:03:45 -060099
Brad Bishop34c3bf92016-08-30 20:34:51 -0400100 def download_complete_handler(self, outfile, filename):
Adriana Kobylak24341f92018-01-26 15:07:23 -0600101 # do update
Brad Bishop34c3bf92016-08-30 20:34:51 -0400102 if (filename != self.Get(DBUS_NAME, "filename")):
103 return
Norman James3d6a06f2016-01-30 13:03:45 -0600104
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600105 print("Download complete. Updating...")
Norman James3d6a06f2016-01-30 13:03:45 -0600106
Brad Bishop34c3bf92016-08-30 20:34:51 -0400107 self.Set(DBUS_NAME, "status", "Download Complete")
108 copy_files = {}
Norman James3d6a06f2016-01-30 13:03:45 -0600109
Adriana Kobylak24341f92018-01-26 15:07:23 -0600110 # determine needed files
Brad Bishop34c3bf92016-08-30 20:34:51 -0400111 if not self.Get(DBUS_NAME, "update_kernel_and_apps"):
112 copy_files["image-bmc"] = True
113 else:
114 copy_files["image-kernel"] = True
Brad Bishop34c3bf92016-08-30 20:34:51 -0400115 copy_files["image-rofs"] = True
Norman James3d6a06f2016-01-30 13:03:45 -0600116
Brad Bishop34c3bf92016-08-30 20:34:51 -0400117 if self.Get(DBUS_NAME, "restore_application_defaults"):
118 copy_files["image-rwfs"] = True
Norman James8a65a542016-02-03 19:04:44 -0600119
Adriana Kobylak24341f92018-01-26 15:07:23 -0600120 # make sure files exist in archive
Brad Bishop34c3bf92016-08-30 20:34:51 -0400121 try:
122 tar = tarfile.open(outfile, "r")
123 files = {}
124 for f in tar.getnames():
125 files[f] = True
126 tar.close()
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600127 for f in list(copy_files.keys()):
Brad Bishop34c3bf92016-08-30 20:34:51 -0400128 if f not in files:
129 raise Exception(
130 "ERROR: File not found in update archive: "+f)
Milton Millerb6cfc542016-06-17 11:43:40 -0500131
Brad Bishop34c3bf92016-08-30 20:34:51 -0400132 except Exception as e:
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600133 print(str(e))
Brad Bishop34c3bf92016-08-30 20:34:51 -0400134 self.Set(DBUS_NAME, "status", "Unpack Error")
135 return
Milton Millerb6cfc542016-06-17 11:43:40 -0500136
Brad Bishop34c3bf92016-08-30 20:34:51 -0400137 try:
138 tar = tarfile.open(outfile, "r")
139 tar.extractall(UPDATE_PATH, members=doExtract(tar, copy_files))
140 tar.close()
Milton Millerb6cfc542016-06-17 11:43:40 -0500141
Brad Bishop34c3bf92016-08-30 20:34:51 -0400142 if self.Get(DBUS_NAME, "clear_persistent_files"):
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600143 print("Removing persistent files")
Brad Bishop34c3bf92016-08-30 20:34:51 -0400144 try:
145 os.unlink(UPDATE_PATH+"/whitelist")
146 except OSError as e:
147 if (e.errno == errno.EISDIR):
148 pass
149 elif (e.errno == errno.ENOENT):
150 pass
151 else:
152 raise
Norman James3d6a06f2016-01-30 13:03:45 -0600153
Brad Bishop34c3bf92016-08-30 20:34:51 -0400154 try:
155 wldir = UPDATE_PATH + "/whitelist.d"
Milton Miller0c8c5d42016-06-29 17:41:36 -0500156
Brad Bishop34c3bf92016-08-30 20:34:51 -0400157 for file in os.listdir(wldir):
158 os.unlink(os.path.join(wldir, file))
159 except OSError as e:
160 if (e.errno == errno.EISDIR):
161 pass
162 else:
163 raise
Milton Miller0c8c5d42016-06-29 17:41:36 -0500164
Brad Bishop34c3bf92016-08-30 20:34:51 -0400165 if self.Get(DBUS_NAME, "preserve_network_settings"):
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600166 print("Preserving network settings")
Milton Miller3fc6b792017-08-15 06:57:14 +0000167 save_fw_env()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500168
Brad Bishop34c3bf92016-08-30 20:34:51 -0400169 except Exception as e:
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600170 print(str(e))
Brad Bishop34c3bf92016-08-30 20:34:51 -0400171 self.Set(DBUS_NAME, "status", "Unpack Error")
Milton Miller0c8c5d42016-06-29 17:41:36 -0500172
Brad Bishop34c3bf92016-08-30 20:34:51 -0400173 self.Verify()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500174
Brad Bishop34c3bf92016-08-30 20:34:51 -0400175 def Verify(self):
176 self.Set(DBUS_NAME, "status", "Checking Image")
177 try:
178 subprocess.check_call([
179 "/run/initramfs/update",
180 "--no-flash",
181 "--no-save-files",
182 "--no-restore-files",
183 "--no-clean-saved-files"])
Milton Miller0c8c5d42016-06-29 17:41:36 -0500184
Brad Bishop34c3bf92016-08-30 20:34:51 -0400185 self.Set(DBUS_NAME, "status", "Image ready to apply.")
186 if (self.Get(DBUS_NAME, "auto_apply")):
187 self.Apply()
Adriana Kobylak24341f92018-01-26 15:07:23 -0600188 except Exception:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400189 self.Set(DBUS_NAME, "auto_apply", False)
190 try:
191 subprocess.check_output([
192 "/run/initramfs/update",
193 "--no-flash",
194 "--ignore-mount",
195 "--no-save-files",
196 "--no-restore-files",
197 "--no-clean-saved-files"],
198 stderr=subprocess.STDOUT)
199 self.Set(
200 DBUS_NAME, "status",
201 "Deferred for mounted filesystem. reboot BMC to apply.")
202 except subprocess.CalledProcessError as e:
203 self.Set(
204 DBUS_NAME, "status", "Verify error: %s" % e.output)
205 except OSError as e:
206 self.Set(
207 DBUS_NAME, "status",
208 "Verify error: problem calling update: %s" % e.strerror)
Milton Miller0c8c5d42016-06-29 17:41:36 -0500209
Brad Bishop34c3bf92016-08-30 20:34:51 -0400210 def Cleanup(self):
211 if self.progress_name:
212 try:
213 os.unlink(self.progress_name)
214 self.progress_name = None
215 except oserror as e:
216 if e.errno == EEXIST:
217 pass
218 raise
219 self.update_process = None
220 self.Set(DBUS_NAME, "status", "Idle")
Milton Miller0c8c5d42016-06-29 17:41:36 -0500221
Brad Bishop34c3bf92016-08-30 20:34:51 -0400222 @dbus.service.method(
223 DBUS_NAME, in_signature='', out_signature='')
224 def Abort(self):
225 if self.update_process:
226 try:
227 self.update_process.kill()
Adriana Kobylak24341f92018-01-26 15:07:23 -0600228 except Exception:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400229 pass
230 for file in os.listdir(UPDATE_PATH):
231 if file.startswith('image-'):
232 os.unlink(os.path.join(UPDATE_PATH, file))
Milton Miller0c8c5d42016-06-29 17:41:36 -0500233
Brad Bishop34c3bf92016-08-30 20:34:51 -0400234 self.Cleanup()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500235
Brad Bishop34c3bf92016-08-30 20:34:51 -0400236 @dbus.service.method(
237 DBUS_NAME, in_signature='', out_signature='s')
238 def GetUpdateProgress(self):
239 msg = ""
Milton Miller0c8c5d42016-06-29 17:41:36 -0500240
Brad Bishop34c3bf92016-08-30 20:34:51 -0400241 if self.update_process and self.update_process.returncode is None:
242 self.update_process.poll()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500243
Brad Bishop34c3bf92016-08-30 20:34:51 -0400244 if (self.update_process is None):
245 pass
246 elif (self.update_process.returncode > 0):
247 self.Set(DBUS_NAME, "status", "Apply failed")
248 elif (self.update_process.returncode is None):
249 pass
250 else: # (self.update_process.returncode == 0)
251 files = ""
252 for file in os.listdir(UPDATE_PATH):
253 if file.startswith('image-'):
254 files = files + file
255 if files == "":
256 msg = "Apply Complete. Reboot to take effect."
257 else:
258 msg = "Apply Incomplete, Remaining:" + files
259 self.Set(DBUS_NAME, "status", msg)
Milton Miller0c8c5d42016-06-29 17:41:36 -0500260
Brad Bishop34c3bf92016-08-30 20:34:51 -0400261 msg = self.Get(DBUS_NAME, "status") + "\n"
262 if self.progress_name:
263 try:
264 prog = open(self.progress_name, 'r')
265 for line in prog:
266 # strip off initial sets of xxx\r here
267 # ignore crlf at the end
268 # cr will be -1 if no '\r' is found
269 cr = line.rfind("\r", 0, -2)
270 msg = msg + line[cr + 1:]
271 except OSError as e:
272 if (e.error == EEXIST):
273 pass
274 raise
275 return msg
276
277 @dbus.service.method(
278 DBUS_NAME, in_signature='', out_signature='')
279 def Apply(self):
280 progress = None
281 self.Set(DBUS_NAME, "status", "Writing images to flash")
282 try:
283 progress = tempfile.NamedTemporaryFile(
284 delete=False, prefix="progress.")
285 self.progress_name = progress.name
286 self.update_process = subprocess.Popen([
287 "/run/initramfs/update"],
288 stdout=progress.file,
289 stderr=subprocess.STDOUT)
290 except Exception as e:
291 try:
292 progress.close()
293 os.unlink(progress.name)
294 self.progress_name = None
Adriana Kobylak24341f92018-01-26 15:07:23 -0600295 except Exception:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400296 pass
297 raise
298
299 try:
300 progress.close()
Adriana Kobylak24341f92018-01-26 15:07:23 -0600301 except Exception:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400302 pass
303
304 @dbus.service.method(
305 DBUS_NAME, in_signature='', out_signature='')
306 def PrepareForUpdate(self):
307 subprocess.call([
308 "fw_setenv",
309 "openbmconce",
310 "copy-files-to-ram copy-base-filesystem-to-ram"])
Adriana Kobylak7bef82c2017-10-02 19:33:48 -0500311 # Set the variable twice so that it is written to both environments of
312 # the u-boot redundant environment variables since initramfs can only
313 # read one of the environments.
314 subprocess.call([
315 "fw_setenv",
316 "openbmconce",
317 "copy-files-to-ram copy-base-filesystem-to-ram"])
Brad Bishop34c3bf92016-08-30 20:34:51 -0400318 self.Set(DBUS_NAME, "status", "Switch to update mode in progress")
319 o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME)
320 intf = dbus.Interface(o, BMC_DBUS_NAME)
321 intf.warmReset()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500322
Norman James3d6a06f2016-01-30 13:03:45 -0600323
324if __name__ == '__main__':
325 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
326
Brad Bishop84e73b52016-05-12 15:57:52 -0400327 bus = get_dbus()
Norman James3d6a06f2016-01-30 13:03:45 -0600328 obj = BmcFlashControl(bus, OBJ_NAME)
329 mainloop = gobject.MainLoop()
Brad Bishopf0f3efe2016-06-29 23:20:24 -0400330
331 obj.unmask_signals()
Brad Bishop70852a32016-06-29 22:58:51 -0400332 name = dbus.service.BusName(DBUS_NAME, bus)
Brad Bishop34c3bf92016-08-30 20:34:51 -0400333
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600334 print("Running Bmc Flash Control")
Norman James3d6a06f2016-01-30 13:03:45 -0600335 mainloop.run()
Brad Bishop53066752016-09-21 08:48:04 -0400336
337# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4