blob: c2046e4ac3c5bd15f3ce99690aa08808221b618b [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
Patrick Williams75fe8cc2022-07-22 16:12:12 -050018DBUS_NAME = "org.openbmc.control.BmcFlash"
19OBJ_NAME = "/org/openbmc/control/flash/bmc"
20DOWNLOAD_INTF = "org.openbmc.managers.Download"
Norman James3d6a06f2016-01-30 13:03:45 -060021
Patrick Williams75fe8cc2022-07-22 16:12:12 -050022BMC_DBUS_NAME = "xyz.openbmc_project.State.BMC"
23BMC_OBJ_NAME = "/xyz/openbmc_project/state/bmc0"
Milton Miller0c8c5d42016-06-29 17:41:36 -050024
Patrick Williams75fe8cc2022-07-22 16:12:12 -050025UPDATE_PATH = "/run/initramfs"
Norman James3d6a06f2016-01-30 13:03:45 -060026
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 = []
Patrick Williams75fe8cc2022-07-22 16:12:12 -050038 envcfg = open(fw_env, "r")
Milton Miller3fc6b792017-08-15 06:57:14 +000039 try:
40 for line in envcfg.readlines():
41 # ignore lines that are blank or start with #
Patrick Williams75fe8cc2022-07-22 16:12:12 -050042 if line.startswith("#"):
Adriana Kobylak24341f92018-01-26 15:07:23 -060043 continue
Patrick Williams75fe8cc2022-07-22 16:12:12 -050044 if not len(line.strip()):
Adriana Kobylak24341f92018-01-26 15:07:23 -060045 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()
Patrick Williams75fe8cc2022-07-22 16:12:12 -050051 if lines < 1 or lines > 2 or (lines == 2 and files[0] != files[1]):
52 raise Exception("Error parsing %s\n" % fw_env)
Milton Miller3fc6b792017-08-15 06:57:14 +000053 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):
Patrick Williams75fe8cc2022-07-22 16:12:12 -050058 super(BmcFlashControl, self).__init__(conn=bus, object_path=name)
Milton Miller0c8c5d42016-06-29 17:41:36 -050059
Brad Bishop34c3bf92016-08-30 20:34:51 -040060 self.Set(DBUS_NAME, "status", "Idle")
61 self.Set(DBUS_NAME, "filename", "")
62 self.Set(DBUS_NAME, "preserve_network_settings", True)
63 self.Set(DBUS_NAME, "restore_application_defaults", False)
64 self.Set(DBUS_NAME, "update_kernel_and_apps", False)
65 self.Set(DBUS_NAME, "clear_persistent_files", False)
66 self.Set(DBUS_NAME, "auto_apply", False)
Norman James3d6a06f2016-01-30 13:03:45 -060067
Brad Bishop34c3bf92016-08-30 20:34:51 -040068 bus.add_signal_receiver(
Patrick Williams75fe8cc2022-07-22 16:12:12 -050069 self.download_error_handler, signal_name="DownloadError"
70 )
Brad Bishop34c3bf92016-08-30 20:34:51 -040071 bus.add_signal_receiver(
Patrick Williams75fe8cc2022-07-22 16:12:12 -050072 self.download_complete_handler, signal_name="DownloadComplete"
73 )
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
Patrick Williams75fe8cc2022-07-22 16:12:12 -050078 @dbus.service.method(DBUS_NAME, in_signature="ss", out_signature="")
Brad Bishop34c3bf92016-08-30 20:34:51 -040079 def updateViaTftp(self, ip, filename):
80 self.Set(DBUS_NAME, "status", "Downloading")
81 self.TftpDownload(ip, filename)
Milton Miller0c8c5d42016-06-29 17:41:36 -050082
Patrick Williams75fe8cc2022-07-22 16:12:12 -050083 @dbus.service.method(DBUS_NAME, in_signature="s", out_signature="")
Brad Bishop34c3bf92016-08-30 20:34:51 -040084 def update(self, filename):
85 self.Set(DBUS_NAME, "filename", filename)
86 self.download_complete_handler(filename, filename)
Chris Austenea5b4012016-05-20 16:41:40 -050087
Patrick Williams75fe8cc2022-07-22 16:12:12 -050088 @dbus.service.signal(DOWNLOAD_INTF, signature="ss")
Brad Bishop34c3bf92016-08-30 20:34:51 -040089 def TftpDownload(self, ip, filename):
90 self.Set(DBUS_NAME, "filename", filename)
91 pass
Norman James3d6a06f2016-01-30 13:03:45 -060092
Adriana Kobylak24341f92018-01-26 15:07:23 -060093 # Signal handler
Brad Bishop34c3bf92016-08-30 20:34:51 -040094 def download_error_handler(self, filename):
Patrick Williams75fe8cc2022-07-22 16:12:12 -050095 if filename == self.Get(DBUS_NAME, "filename"):
Brad Bishop34c3bf92016-08-30 20:34:51 -040096 self.Set(DBUS_NAME, "status", "Download Error")
Norman James3d6a06f2016-01-30 13:03:45 -060097
Brad Bishop34c3bf92016-08-30 20:34:51 -040098 def download_complete_handler(self, outfile, filename):
Adriana Kobylak24341f92018-01-26 15:07:23 -060099 # do update
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500100 if filename != self.Get(DBUS_NAME, "filename"):
Brad Bishop34c3bf92016-08-30 20:34:51 -0400101 return
Norman James3d6a06f2016-01-30 13:03:45 -0600102
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600103 print("Download complete. Updating...")
Norman James3d6a06f2016-01-30 13:03:45 -0600104
Brad Bishop34c3bf92016-08-30 20:34:51 -0400105 self.Set(DBUS_NAME, "status", "Download Complete")
106 copy_files = {}
Norman James3d6a06f2016-01-30 13:03:45 -0600107
Adriana Kobylak24341f92018-01-26 15:07:23 -0600108 # determine needed files
Brad Bishop34c3bf92016-08-30 20:34:51 -0400109 if not self.Get(DBUS_NAME, "update_kernel_and_apps"):
110 copy_files["image-bmc"] = True
111 else:
112 copy_files["image-kernel"] = True
Brad Bishop34c3bf92016-08-30 20:34:51 -0400113 copy_files["image-rofs"] = True
Norman James3d6a06f2016-01-30 13:03:45 -0600114
Brad Bishop34c3bf92016-08-30 20:34:51 -0400115 if self.Get(DBUS_NAME, "restore_application_defaults"):
116 copy_files["image-rwfs"] = True
Norman James8a65a542016-02-03 19:04:44 -0600117
Adriana Kobylak24341f92018-01-26 15:07:23 -0600118 # make sure files exist in archive
Brad Bishop34c3bf92016-08-30 20:34:51 -0400119 try:
120 tar = tarfile.open(outfile, "r")
121 files = {}
122 for f in tar.getnames():
123 files[f] = True
124 tar.close()
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600125 for f in list(copy_files.keys()):
Brad Bishop34c3bf92016-08-30 20:34:51 -0400126 if f not in files:
127 raise Exception(
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500128 "ERROR: File not found in update archive: " + f
129 )
Milton Millerb6cfc542016-06-17 11:43:40 -0500130
Brad Bishop34c3bf92016-08-30 20:34:51 -0400131 except Exception as e:
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600132 print(str(e))
Brad Bishop34c3bf92016-08-30 20:34:51 -0400133 self.Set(DBUS_NAME, "status", "Unpack Error")
134 return
Milton Millerb6cfc542016-06-17 11:43:40 -0500135
Brad Bishop34c3bf92016-08-30 20:34:51 -0400136 try:
137 tar = tarfile.open(outfile, "r")
138 tar.extractall(UPDATE_PATH, members=doExtract(tar, copy_files))
139 tar.close()
Milton Millerb6cfc542016-06-17 11:43:40 -0500140
Brad Bishop34c3bf92016-08-30 20:34:51 -0400141 if self.Get(DBUS_NAME, "clear_persistent_files"):
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600142 print("Removing persistent files")
Brad Bishop34c3bf92016-08-30 20:34:51 -0400143 try:
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500144 os.unlink(UPDATE_PATH + "/whitelist")
Brad Bishop34c3bf92016-08-30 20:34:51 -0400145 except OSError as e:
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500146 if e.errno == errno.EISDIR:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400147 pass
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500148 elif e.errno == errno.ENOENT:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400149 pass
150 else:
151 raise
Norman James3d6a06f2016-01-30 13:03:45 -0600152
Brad Bishop34c3bf92016-08-30 20:34:51 -0400153 try:
154 wldir = UPDATE_PATH + "/whitelist.d"
Milton Miller0c8c5d42016-06-29 17:41:36 -0500155
Brad Bishop34c3bf92016-08-30 20:34:51 -0400156 for file in os.listdir(wldir):
157 os.unlink(os.path.join(wldir, file))
158 except OSError as e:
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500159 if e.errno == errno.EISDIR:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400160 pass
161 else:
162 raise
Milton Miller0c8c5d42016-06-29 17:41:36 -0500163
Brad Bishop34c3bf92016-08-30 20:34:51 -0400164 if self.Get(DBUS_NAME, "preserve_network_settings"):
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600165 print("Preserving network settings")
Milton Miller3fc6b792017-08-15 06:57:14 +0000166 save_fw_env()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500167
Brad Bishop34c3bf92016-08-30 20:34:51 -0400168 except Exception as e:
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600169 print(str(e))
Brad Bishop34c3bf92016-08-30 20:34:51 -0400170 self.Set(DBUS_NAME, "status", "Unpack Error")
Milton Miller0c8c5d42016-06-29 17:41:36 -0500171
Brad Bishop34c3bf92016-08-30 20:34:51 -0400172 self.Verify()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500173
Brad Bishop34c3bf92016-08-30 20:34:51 -0400174 def Verify(self):
175 self.Set(DBUS_NAME, "status", "Checking Image")
176 try:
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500177 subprocess.check_call(
178 [
179 "/run/initramfs/update",
180 "--no-flash",
181 "--no-save-files",
182 "--no-restore-files",
183 "--no-clean-saved-files",
184 ]
185 )
Milton Miller0c8c5d42016-06-29 17:41:36 -0500186
Brad Bishop34c3bf92016-08-30 20:34:51 -0400187 self.Set(DBUS_NAME, "status", "Image ready to apply.")
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500188 if self.Get(DBUS_NAME, "auto_apply"):
Brad Bishop34c3bf92016-08-30 20:34:51 -0400189 self.Apply()
Adriana Kobylak24341f92018-01-26 15:07:23 -0600190 except Exception:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400191 self.Set(DBUS_NAME, "auto_apply", False)
192 try:
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500193 subprocess.check_output(
194 [
195 "/run/initramfs/update",
196 "--no-flash",
197 "--ignore-mount",
198 "--no-save-files",
199 "--no-restore-files",
200 "--no-clean-saved-files",
201 ],
202 stderr=subprocess.STDOUT,
203 )
Brad Bishop34c3bf92016-08-30 20:34:51 -0400204 self.Set(
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500205 DBUS_NAME,
206 "status",
207 "Deferred for mounted filesystem. reboot BMC to apply.",
208 )
Brad Bishop34c3bf92016-08-30 20:34:51 -0400209 except subprocess.CalledProcessError as e:
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500210 self.Set(DBUS_NAME, "status", "Verify error: %s" % e.output)
Brad Bishop34c3bf92016-08-30 20:34:51 -0400211 except OSError as e:
212 self.Set(
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500213 DBUS_NAME,
214 "status",
215 "Verify error: problem calling update: %s" % e.strerror,
216 )
Milton Miller0c8c5d42016-06-29 17:41:36 -0500217
Brad Bishop34c3bf92016-08-30 20:34:51 -0400218 def Cleanup(self):
219 if self.progress_name:
220 try:
221 os.unlink(self.progress_name)
222 self.progress_name = None
223 except oserror as e:
224 if e.errno == EEXIST:
225 pass
226 raise
227 self.update_process = None
228 self.Set(DBUS_NAME, "status", "Idle")
Milton Miller0c8c5d42016-06-29 17:41:36 -0500229
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500230 @dbus.service.method(DBUS_NAME, in_signature="", out_signature="")
Brad Bishop34c3bf92016-08-30 20:34:51 -0400231 def Abort(self):
232 if self.update_process:
233 try:
234 self.update_process.kill()
Adriana Kobylak24341f92018-01-26 15:07:23 -0600235 except Exception:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400236 pass
237 for file in os.listdir(UPDATE_PATH):
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500238 if file.startswith("image-"):
Brad Bishop34c3bf92016-08-30 20:34:51 -0400239 os.unlink(os.path.join(UPDATE_PATH, file))
Milton Miller0c8c5d42016-06-29 17:41:36 -0500240
Brad Bishop34c3bf92016-08-30 20:34:51 -0400241 self.Cleanup()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500242
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500243 @dbus.service.method(DBUS_NAME, in_signature="", out_signature="s")
Brad Bishop34c3bf92016-08-30 20:34:51 -0400244 def GetUpdateProgress(self):
245 msg = ""
Milton Miller0c8c5d42016-06-29 17:41:36 -0500246
Brad Bishop34c3bf92016-08-30 20:34:51 -0400247 if self.update_process and self.update_process.returncode is None:
248 self.update_process.poll()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500249
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500250 if self.update_process is None:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400251 pass
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500252 elif self.update_process.returncode > 0:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400253 self.Set(DBUS_NAME, "status", "Apply failed")
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500254 elif self.update_process.returncode is None:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400255 pass
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500256 else: # (self.update_process.returncode == 0)
Brad Bishop34c3bf92016-08-30 20:34:51 -0400257 files = ""
258 for file in os.listdir(UPDATE_PATH):
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500259 if file.startswith("image-"):
Brad Bishop34c3bf92016-08-30 20:34:51 -0400260 files = files + file
261 if files == "":
262 msg = "Apply Complete. Reboot to take effect."
263 else:
264 msg = "Apply Incomplete, Remaining:" + files
265 self.Set(DBUS_NAME, "status", msg)
Milton Miller0c8c5d42016-06-29 17:41:36 -0500266
Brad Bishop34c3bf92016-08-30 20:34:51 -0400267 msg = self.Get(DBUS_NAME, "status") + "\n"
268 if self.progress_name:
269 try:
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500270 prog = open(self.progress_name, "r")
Brad Bishop34c3bf92016-08-30 20:34:51 -0400271 for line in prog:
272 # strip off initial sets of xxx\r here
273 # ignore crlf at the end
274 # cr will be -1 if no '\r' is found
275 cr = line.rfind("\r", 0, -2)
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500276 msg = msg + line[(cr + 1):]
Brad Bishop34c3bf92016-08-30 20:34:51 -0400277 except OSError as e:
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500278 if e.error == EEXIST:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400279 pass
280 raise
281 return msg
282
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500283 @dbus.service.method(DBUS_NAME, in_signature="", out_signature="")
Brad Bishop34c3bf92016-08-30 20:34:51 -0400284 def Apply(self):
285 progress = None
286 self.Set(DBUS_NAME, "status", "Writing images to flash")
287 try:
288 progress = tempfile.NamedTemporaryFile(
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500289 delete=False, prefix="progress."
290 )
Brad Bishop34c3bf92016-08-30 20:34:51 -0400291 self.progress_name = progress.name
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500292 self.update_process = subprocess.Popen(
293 ["/run/initramfs/update"],
Brad Bishop34c3bf92016-08-30 20:34:51 -0400294 stdout=progress.file,
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500295 stderr=subprocess.STDOUT,
296 )
Brad Bishop34c3bf92016-08-30 20:34:51 -0400297 except Exception as e:
298 try:
299 progress.close()
300 os.unlink(progress.name)
301 self.progress_name = None
Adriana Kobylak24341f92018-01-26 15:07:23 -0600302 except Exception:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400303 pass
304 raise
305
306 try:
307 progress.close()
Adriana Kobylak24341f92018-01-26 15:07:23 -0600308 except Exception:
Brad Bishop34c3bf92016-08-30 20:34:51 -0400309 pass
310
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500311 @dbus.service.method(DBUS_NAME, in_signature="", out_signature="")
Brad Bishop34c3bf92016-08-30 20:34:51 -0400312 def PrepareForUpdate(self):
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500313 subprocess.call(
314 [
315 "fw_setenv",
316 "openbmconce",
317 "copy-files-to-ram copy-base-filesystem-to-ram",
318 ]
319 )
Adriana Kobylak7bef82c2017-10-02 19:33:48 -0500320 # Set the variable twice so that it is written to both environments of
321 # the u-boot redundant environment variables since initramfs can only
322 # read one of the environments.
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500323 subprocess.call(
324 [
325 "fw_setenv",
326 "openbmconce",
327 "copy-files-to-ram copy-base-filesystem-to-ram",
328 ]
329 )
Brad Bishop34c3bf92016-08-30 20:34:51 -0400330 self.Set(DBUS_NAME, "status", "Switch to update mode in progress")
331 o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME)
Lei YU5a387632018-04-04 16:51:22 +0800332 intf = dbus.Interface(o, "org.freedesktop.DBus.Properties")
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500333 intf.Set(
334 BMC_DBUS_NAME,
335 "RequestedBMCTransition",
336 "xyz.openbmc_project.State.BMC.Transition.Reboot",
337 )
Milton Miller0c8c5d42016-06-29 17:41:36 -0500338
Norman James3d6a06f2016-01-30 13:03:45 -0600339
Patrick Williams75fe8cc2022-07-22 16:12:12 -0500340if __name__ == "__main__":
Norman James3d6a06f2016-01-30 13:03:45 -0600341 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
342
Brad Bishop84e73b52016-05-12 15:57:52 -0400343 bus = get_dbus()
Norman James3d6a06f2016-01-30 13:03:45 -0600344 obj = BmcFlashControl(bus, OBJ_NAME)
345 mainloop = gobject.MainLoop()
Brad Bishopf0f3efe2016-06-29 23:20:24 -0400346
347 obj.unmask_signals()
Brad Bishop70852a32016-06-29 22:58:51 -0400348 name = dbus.service.BusName(DBUS_NAME, bus)
Brad Bishop34c3bf92016-08-30 20:34:51 -0400349
CamVan Nguyend65b2d52018-02-27 15:14:41 -0600350 print("Running Bmc Flash Control")
Norman James3d6a06f2016-01-30 13:03:45 -0600351 mainloop.run()
Brad Bishop53066752016-09-21 08:48:04 -0400352
353# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4