xref: /openbmc/skeleton/pyflashbmc/bmc_update.py (revision 75fe8cc4)
134c3bf9eSBrad Bishop#!/usr/bin/env python
240a360c2SBrad Bishop
3d65b2d50SCamVan Nguyen# TODO: openbmc/openbmc#2994 remove python 2 support
4d65b2d50SCamVan Nguyentry:  # python 2
540a360c2SBrad Bishop    import gobject
6d65b2d50SCamVan Nguyenexcept ImportError:  # python 3
7d65b2d50SCamVan Nguyen    from gi.repository import GObject as gobject
840a360c2SBrad Bishopimport dbus
940a360c2SBrad Bishopimport dbus.service
1040a360c2SBrad Bishopimport dbus.mainloop.glib
110c8c5d4aSMilton Millerimport subprocess
120c8c5d4aSMilton Millerimport tempfile
1340a360c2SBrad Bishopimport shutil
1440a360c2SBrad Bishopimport tarfile
1540a360c2SBrad Bishopimport os
1640a360c2SBrad Bishopfrom obmc.dbuslib.bindings import get_dbus, DbusProperties, DbusObjectManager
1740a360c2SBrad Bishop
18*75fe8cc4SPatrick WilliamsDBUS_NAME = "org.openbmc.control.BmcFlash"
19*75fe8cc4SPatrick WilliamsOBJ_NAME = "/org/openbmc/control/flash/bmc"
20*75fe8cc4SPatrick WilliamsDOWNLOAD_INTF = "org.openbmc.managers.Download"
2140a360c2SBrad Bishop
22*75fe8cc4SPatrick WilliamsBMC_DBUS_NAME = "xyz.openbmc_project.State.BMC"
23*75fe8cc4SPatrick WilliamsBMC_OBJ_NAME = "/xyz/openbmc_project/state/bmc0"
240c8c5d4aSMilton Miller
25*75fe8cc4SPatrick WilliamsUPDATE_PATH = "/run/initramfs"
2640a360c2SBrad Bishop
2740a360c2SBrad Bishop
2840a360c2SBrad Bishopdef doExtract(members, files):
2940a360c2SBrad Bishop    for tarinfo in members:
3034c3bf9eSBrad Bishop        if tarinfo.name in files:
3140a360c2SBrad Bishop            yield tarinfo
3240a360c2SBrad Bishop
3340a360c2SBrad Bishop
343fc6b790SMilton Millerdef save_fw_env():
353fc6b790SMilton Miller    fw_env = "/etc/fw_env.config"
363fc6b790SMilton Miller    lines = 0
373fc6b790SMilton Miller    files = []
38*75fe8cc4SPatrick Williams    envcfg = open(fw_env, "r")
393fc6b790SMilton Miller    try:
403fc6b790SMilton Miller        for line in envcfg.readlines():
413fc6b790SMilton Miller            # ignore lines that are blank or start with #
42*75fe8cc4SPatrick Williams            if line.startswith("#"):
4324341f9dSAdriana Kobylak                continue
44*75fe8cc4SPatrick Williams            if not len(line.strip()):
4524341f9dSAdriana Kobylak                continue
4624341f9dSAdriana Kobylak            fn = line.partition("\t")[0]
473fc6b790SMilton Miller            files.append(fn)
483fc6b790SMilton Miller            lines += 1
493fc6b790SMilton Miller    finally:
503fc6b790SMilton Miller        envcfg.close()
51*75fe8cc4SPatrick Williams    if lines < 1 or lines > 2 or (lines == 2 and files[0] != files[1]):
523fc6b790SMilton Miller        raise Exception("Error parsing %s\n" % fw_env)
533fc6b790SMilton Miller    shutil.copyfile(files[0], os.path.join(UPDATE_PATH, "image-u-boot-env"))
543fc6b790SMilton Miller
5524341f9dSAdriana Kobylak
5640a360c2SBrad Bishopclass BmcFlashControl(DbusProperties, DbusObjectManager):
5740a360c2SBrad Bishop    def __init__(self, bus, name):
58*75fe8cc4SPatrick Williams        super(BmcFlashControl, self).__init__(conn=bus, object_path=name)
5940a360c2SBrad Bishop
6040a360c2SBrad Bishop        self.Set(DBUS_NAME, "status", "Idle")
6140a360c2SBrad Bishop        self.Set(DBUS_NAME, "filename", "")
62c8094109SMilton Miller        self.Set(DBUS_NAME, "preserve_network_settings", True)
6340a360c2SBrad Bishop        self.Set(DBUS_NAME, "restore_application_defaults", False)
6440a360c2SBrad Bishop        self.Set(DBUS_NAME, "update_kernel_and_apps", False)
6540a360c2SBrad Bishop        self.Set(DBUS_NAME, "clear_persistent_files", False)
660c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "auto_apply", False)
6740a360c2SBrad Bishop
6834c3bf9eSBrad Bishop        bus.add_signal_receiver(
69*75fe8cc4SPatrick Williams            self.download_error_handler, signal_name="DownloadError"
70*75fe8cc4SPatrick Williams        )
7134c3bf9eSBrad Bishop        bus.add_signal_receiver(
72*75fe8cc4SPatrick Williams            self.download_complete_handler, signal_name="DownloadComplete"
73*75fe8cc4SPatrick Williams        )
7440a360c2SBrad Bishop
750c8c5d4aSMilton Miller        self.update_process = None
760c8c5d4aSMilton Miller        self.progress_name = None
770c8c5d4aSMilton Miller
78*75fe8cc4SPatrick Williams    @dbus.service.method(DBUS_NAME, in_signature="ss", out_signature="")
7940a360c2SBrad Bishop    def updateViaTftp(self, ip, filename):
8040a360c2SBrad Bishop        self.Set(DBUS_NAME, "status", "Downloading")
810c8c5d4aSMilton Miller        self.TftpDownload(ip, filename)
8240a360c2SBrad Bishop
83*75fe8cc4SPatrick Williams    @dbus.service.method(DBUS_NAME, in_signature="s", out_signature="")
8440a360c2SBrad Bishop    def update(self, filename):
8540a360c2SBrad Bishop        self.Set(DBUS_NAME, "filename", filename)
8640a360c2SBrad Bishop        self.download_complete_handler(filename, filename)
8740a360c2SBrad Bishop
88*75fe8cc4SPatrick Williams    @dbus.service.signal(DOWNLOAD_INTF, signature="ss")
8940a360c2SBrad Bishop    def TftpDownload(self, ip, filename):
9040a360c2SBrad Bishop        self.Set(DBUS_NAME, "filename", filename)
9140a360c2SBrad Bishop        pass
9240a360c2SBrad Bishop
9324341f9dSAdriana Kobylak    # Signal handler
9440a360c2SBrad Bishop    def download_error_handler(self, filename):
95*75fe8cc4SPatrick Williams        if filename == self.Get(DBUS_NAME, "filename"):
9640a360c2SBrad Bishop            self.Set(DBUS_NAME, "status", "Download Error")
9740a360c2SBrad Bishop
9840a360c2SBrad Bishop    def download_complete_handler(self, outfile, filename):
9924341f9dSAdriana Kobylak        # do update
100*75fe8cc4SPatrick Williams        if filename != self.Get(DBUS_NAME, "filename"):
10140a360c2SBrad Bishop            return
10240a360c2SBrad Bishop
103d65b2d50SCamVan Nguyen        print("Download complete. Updating...")
10440a360c2SBrad Bishop
10540a360c2SBrad Bishop        self.Set(DBUS_NAME, "status", "Download Complete")
10640a360c2SBrad Bishop        copy_files = {}
10740a360c2SBrad Bishop
10824341f9dSAdriana Kobylak        # determine needed files
10934c3bf9eSBrad Bishop        if not self.Get(DBUS_NAME, "update_kernel_and_apps"):
11040a360c2SBrad Bishop            copy_files["image-bmc"] = True
11140a360c2SBrad Bishop        else:
11240a360c2SBrad Bishop            copy_files["image-kernel"] = True
11340a360c2SBrad Bishop            copy_files["image-rofs"] = True
11440a360c2SBrad Bishop
11534c3bf9eSBrad Bishop        if self.Get(DBUS_NAME, "restore_application_defaults"):
11640a360c2SBrad Bishop            copy_files["image-rwfs"] = True
11740a360c2SBrad Bishop
11824341f9dSAdriana Kobylak        # make sure files exist in archive
11940a360c2SBrad Bishop        try:
12040a360c2SBrad Bishop            tar = tarfile.open(outfile, "r")
12140a360c2SBrad Bishop            files = {}
12240a360c2SBrad Bishop            for f in tar.getnames():
12340a360c2SBrad Bishop                files[f] = True
12440a360c2SBrad Bishop            tar.close()
125d65b2d50SCamVan Nguyen            for f in list(copy_files.keys()):
12634c3bf9eSBrad Bishop                if f not in files:
12734c3bf9eSBrad Bishop                    raise Exception(
128*75fe8cc4SPatrick Williams                        "ERROR: File not found in update archive: " + f
129*75fe8cc4SPatrick Williams                    )
13040a360c2SBrad Bishop
13140a360c2SBrad Bishop        except Exception as e:
132d65b2d50SCamVan Nguyen            print(str(e))
1330c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Unpack Error")
13440a360c2SBrad Bishop            return
13540a360c2SBrad Bishop
13640a360c2SBrad Bishop        try:
13740a360c2SBrad Bishop            tar = tarfile.open(outfile, "r")
13840a360c2SBrad Bishop            tar.extractall(UPDATE_PATH, members=doExtract(tar, copy_files))
13940a360c2SBrad Bishop            tar.close()
14040a360c2SBrad Bishop
14134c3bf9eSBrad Bishop            if self.Get(DBUS_NAME, "clear_persistent_files"):
142d65b2d50SCamVan Nguyen                print("Removing persistent files")
143b6cfc545SMilton Miller                try:
14440a360c2SBrad Bishop                    os.unlink(UPDATE_PATH + "/whitelist")
145b6cfc545SMilton Miller                except OSError as e:
146*75fe8cc4SPatrick Williams                    if e.errno == errno.EISDIR:
147b6cfc545SMilton Miller                        pass
148*75fe8cc4SPatrick Williams                    elif e.errno == errno.ENOENT:
149b6cfc545SMilton Miller                        pass
150b6cfc545SMilton Miller                    else:
151b6cfc545SMilton Miller                        raise
152b6cfc545SMilton Miller
153b6cfc545SMilton Miller                try:
154b6cfc545SMilton Miller                    wldir = UPDATE_PATH + "/whitelist.d"
155b6cfc545SMilton Miller
156b6cfc545SMilton Miller                    for file in os.listdir(wldir):
157b6cfc545SMilton Miller                        os.unlink(os.path.join(wldir, file))
158b6cfc545SMilton Miller                except OSError as e:
159*75fe8cc4SPatrick Williams                    if e.errno == errno.EISDIR:
160b6cfc545SMilton Miller                        pass
161b6cfc545SMilton Miller                    else:
162b6cfc545SMilton Miller                        raise
163b6cfc545SMilton Miller
16434c3bf9eSBrad Bishop            if self.Get(DBUS_NAME, "preserve_network_settings"):
165d65b2d50SCamVan Nguyen                print("Preserving network settings")
1663fc6b790SMilton Miller                save_fw_env()
16740a360c2SBrad Bishop
16840a360c2SBrad Bishop        except Exception as e:
169d65b2d50SCamVan Nguyen            print(str(e))
1700c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Unpack Error")
1710c8c5d4aSMilton Miller
1720c8c5d4aSMilton Miller        self.Verify()
1730c8c5d4aSMilton Miller
1740c8c5d4aSMilton Miller    def Verify(self):
1750c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Checking Image")
1760c8c5d4aSMilton Miller        try:
177*75fe8cc4SPatrick Williams            subprocess.check_call(
178*75fe8cc4SPatrick Williams                [
1790c8c5d4aSMilton Miller                    "/run/initramfs/update",
1800c8c5d4aSMilton Miller                    "--no-flash",
1810c8c5d4aSMilton Miller                    "--no-save-files",
1820c8c5d4aSMilton Miller                    "--no-restore-files",
183*75fe8cc4SPatrick Williams                    "--no-clean-saved-files",
184*75fe8cc4SPatrick Williams                ]
185*75fe8cc4SPatrick Williams            )
1860c8c5d4aSMilton Miller
1870c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Image ready to apply.")
188*75fe8cc4SPatrick Williams            if self.Get(DBUS_NAME, "auto_apply"):
1890c8c5d4aSMilton Miller                self.Apply()
19024341f9dSAdriana Kobylak        except Exception:
1910c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "auto_apply", False)
1920c8c5d4aSMilton Miller            try:
193*75fe8cc4SPatrick Williams                subprocess.check_output(
194*75fe8cc4SPatrick Williams                    [
1950c8c5d4aSMilton Miller                        "/run/initramfs/update",
1960c8c5d4aSMilton Miller                        "--no-flash",
1970c8c5d4aSMilton Miller                        "--ignore-mount",
1980c8c5d4aSMilton Miller                        "--no-save-files",
1990c8c5d4aSMilton Miller                        "--no-restore-files",
200*75fe8cc4SPatrick Williams                        "--no-clean-saved-files",
201*75fe8cc4SPatrick Williams                    ],
202*75fe8cc4SPatrick Williams                    stderr=subprocess.STDOUT,
203*75fe8cc4SPatrick Williams                )
20434c3bf9eSBrad Bishop                self.Set(
205*75fe8cc4SPatrick Williams                    DBUS_NAME,
206*75fe8cc4SPatrick Williams                    "status",
207*75fe8cc4SPatrick Williams                    "Deferred for mounted filesystem. reboot BMC to apply.",
208*75fe8cc4SPatrick Williams                )
2090c8c5d4aSMilton Miller            except subprocess.CalledProcessError as e:
210*75fe8cc4SPatrick Williams                self.Set(DBUS_NAME, "status", "Verify error: %s" % e.output)
2110c8c5d4aSMilton Miller            except OSError as e:
21234c3bf9eSBrad Bishop                self.Set(
213*75fe8cc4SPatrick Williams                    DBUS_NAME,
214*75fe8cc4SPatrick Williams                    "status",
215*75fe8cc4SPatrick Williams                    "Verify error: problem calling update: %s" % e.strerror,
216*75fe8cc4SPatrick Williams                )
21740a360c2SBrad Bishop
2180c8c5d4aSMilton Miller    def Cleanup(self):
2190c8c5d4aSMilton Miller        if self.progress_name:
2200c8c5d4aSMilton Miller            try:
2210c8c5d4aSMilton Miller                os.unlink(self.progress_name)
2220c8c5d4aSMilton Miller                self.progress_name = None
2230c8c5d4aSMilton Miller            except oserror as e:
2240c8c5d4aSMilton Miller                if e.errno == EEXIST:
2250c8c5d4aSMilton Miller                    pass
2260c8c5d4aSMilton Miller                raise
2270c8c5d4aSMilton Miller        self.update_process = None
2280c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Idle")
22940a360c2SBrad Bishop
230*75fe8cc4SPatrick Williams    @dbus.service.method(DBUS_NAME, in_signature="", out_signature="")
2310c8c5d4aSMilton Miller    def Abort(self):
2320c8c5d4aSMilton Miller        if self.update_process:
2330c8c5d4aSMilton Miller            try:
2340c8c5d4aSMilton Miller                self.update_process.kill()
23524341f9dSAdriana Kobylak            except Exception:
2360c8c5d4aSMilton Miller                pass
2370c8c5d4aSMilton Miller        for file in os.listdir(UPDATE_PATH):
238*75fe8cc4SPatrick Williams            if file.startswith("image-"):
2390c8c5d4aSMilton Miller                os.unlink(os.path.join(UPDATE_PATH, file))
2400c8c5d4aSMilton Miller
24134c3bf9eSBrad Bishop        self.Cleanup()
2420c8c5d4aSMilton Miller
243*75fe8cc4SPatrick Williams    @dbus.service.method(DBUS_NAME, in_signature="", out_signature="s")
2440c8c5d4aSMilton Miller    def GetUpdateProgress(self):
2450c8c5d4aSMilton Miller        msg = ""
2460c8c5d4aSMilton Miller
2470c8c5d4aSMilton Miller        if self.update_process and self.update_process.returncode is None:
2480c8c5d4aSMilton Miller            self.update_process.poll()
2490c8c5d4aSMilton Miller
250*75fe8cc4SPatrick Williams        if self.update_process is None:
2510c8c5d4aSMilton Miller            pass
252*75fe8cc4SPatrick Williams        elif self.update_process.returncode > 0:
2530c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Apply failed")
254*75fe8cc4SPatrick Williams        elif self.update_process.returncode is None:
2550c8c5d4aSMilton Miller            pass
2560c8c5d4aSMilton Miller        else:  # (self.update_process.returncode == 0)
2570c8c5d4aSMilton Miller            files = ""
2580c8c5d4aSMilton Miller            for file in os.listdir(UPDATE_PATH):
259*75fe8cc4SPatrick Williams                if file.startswith("image-"):
26034c3bf9eSBrad Bishop                    files = files + file
2610c8c5d4aSMilton Miller            if files == "":
2620c8c5d4aSMilton Miller                msg = "Apply Complete.  Reboot to take effect."
2630c8c5d4aSMilton Miller            else:
2640c8c5d4aSMilton Miller                msg = "Apply Incomplete, Remaining:" + files
2650c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", msg)
2660c8c5d4aSMilton Miller
26734c3bf9eSBrad Bishop        msg = self.Get(DBUS_NAME, "status") + "\n"
2680c8c5d4aSMilton Miller        if self.progress_name:
2690c8c5d4aSMilton Miller            try:
270*75fe8cc4SPatrick Williams                prog = open(self.progress_name, "r")
2710c8c5d4aSMilton Miller                for line in prog:
2720c8c5d4aSMilton Miller                    # strip off initial sets of xxx\r here
2730c8c5d4aSMilton Miller                    # ignore crlf at the end
2740c8c5d4aSMilton Miller                    # cr will be -1 if no '\r' is found
2750c8c5d4aSMilton Miller                    cr = line.rfind("\r", 0, -2)
276*75fe8cc4SPatrick Williams                    msg = msg + line[(cr + 1):]
2770c8c5d4aSMilton Miller            except OSError as e:
278*75fe8cc4SPatrick Williams                if e.error == EEXIST:
2790c8c5d4aSMilton Miller                    pass
2800c8c5d4aSMilton Miller                raise
2810c8c5d4aSMilton Miller        return msg
2820c8c5d4aSMilton Miller
283*75fe8cc4SPatrick Williams    @dbus.service.method(DBUS_NAME, in_signature="", out_signature="")
2840c8c5d4aSMilton Miller    def Apply(self):
2850c8c5d4aSMilton Miller        progress = None
2860c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Writing images to flash")
2870c8c5d4aSMilton Miller        try:
2880c8c5d4aSMilton Miller            progress = tempfile.NamedTemporaryFile(
289*75fe8cc4SPatrick Williams                delete=False, prefix="progress."
290*75fe8cc4SPatrick Williams            )
2910c8c5d4aSMilton Miller            self.progress_name = progress.name
292*75fe8cc4SPatrick Williams            self.update_process = subprocess.Popen(
293*75fe8cc4SPatrick Williams                ["/run/initramfs/update"],
2940c8c5d4aSMilton Miller                stdout=progress.file,
295*75fe8cc4SPatrick Williams                stderr=subprocess.STDOUT,
296*75fe8cc4SPatrick Williams            )
2970c8c5d4aSMilton Miller        except Exception as e:
2980c8c5d4aSMilton Miller            try:
2990c8c5d4aSMilton Miller                progress.close()
3000c8c5d4aSMilton Miller                os.unlink(progress.name)
3010c8c5d4aSMilton Miller                self.progress_name = None
30224341f9dSAdriana Kobylak            except Exception:
3030c8c5d4aSMilton Miller                pass
3040c8c5d4aSMilton Miller            raise
3050c8c5d4aSMilton Miller
3060c8c5d4aSMilton Miller        try:
3070c8c5d4aSMilton Miller            progress.close()
30824341f9dSAdriana Kobylak        except Exception:
3090c8c5d4aSMilton Miller            pass
3100c8c5d4aSMilton Miller
311*75fe8cc4SPatrick Williams    @dbus.service.method(DBUS_NAME, in_signature="", out_signature="")
3120c8c5d4aSMilton Miller    def PrepareForUpdate(self):
313*75fe8cc4SPatrick Williams        subprocess.call(
314*75fe8cc4SPatrick Williams            [
3150c8c5d4aSMilton Miller                "fw_setenv",
3160c8c5d4aSMilton Miller                "openbmconce",
317*75fe8cc4SPatrick Williams                "copy-files-to-ram copy-base-filesystem-to-ram",
318*75fe8cc4SPatrick Williams            ]
319*75fe8cc4SPatrick Williams        )
3207bef82c0SAdriana Kobylak        # Set the variable twice so that it is written to both environments of
3217bef82c0SAdriana Kobylak        # the u-boot redundant environment variables since initramfs can only
3227bef82c0SAdriana Kobylak        # read one of the environments.
323*75fe8cc4SPatrick Williams        subprocess.call(
324*75fe8cc4SPatrick Williams            [
3257bef82c0SAdriana Kobylak                "fw_setenv",
3267bef82c0SAdriana Kobylak                "openbmconce",
327*75fe8cc4SPatrick Williams                "copy-files-to-ram copy-base-filesystem-to-ram",
328*75fe8cc4SPatrick Williams            ]
329*75fe8cc4SPatrick Williams        )
3300c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Switch to update mode in progress")
3310c8c5d4aSMilton Miller        o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME)
3325a38763fSLei YU        intf = dbus.Interface(o, "org.freedesktop.DBus.Properties")
333*75fe8cc4SPatrick Williams        intf.Set(
334*75fe8cc4SPatrick Williams            BMC_DBUS_NAME,
3355a38763fSLei YU            "RequestedBMCTransition",
336*75fe8cc4SPatrick Williams            "xyz.openbmc_project.State.BMC.Transition.Reboot",
337*75fe8cc4SPatrick Williams        )
33840a360c2SBrad Bishop
33940a360c2SBrad Bishop
340*75fe8cc4SPatrick Williamsif __name__ == "__main__":
34140a360c2SBrad Bishop    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
34240a360c2SBrad Bishop
34340a360c2SBrad Bishop    bus = get_dbus()
34440a360c2SBrad Bishop    obj = BmcFlashControl(bus, OBJ_NAME)
34540a360c2SBrad Bishop    mainloop = gobject.MainLoop()
346f0f3efe1SBrad Bishop
347f0f3efe1SBrad Bishop    obj.unmask_signals()
34870852a38SBrad Bishop    name = dbus.service.BusName(DBUS_NAME, bus)
34940a360c2SBrad Bishop
350d65b2d50SCamVan Nguyen    print("Running Bmc Flash Control")
35140a360c2SBrad Bishop    mainloop.run()
35253066750SBrad Bishop
35353066750SBrad Bishop# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
354