xref: /openbmc/skeleton/pyflashbmc/bmc_update.py (revision 7bef82c0)
134c3bf9eSBrad Bishop#!/usr/bin/env python
240a360c2SBrad Bishop
340a360c2SBrad Bishopimport gobject
440a360c2SBrad Bishopimport dbus
540a360c2SBrad Bishopimport dbus.service
640a360c2SBrad Bishopimport dbus.mainloop.glib
70c8c5d4aSMilton Millerimport subprocess
80c8c5d4aSMilton Millerimport tempfile
940a360c2SBrad Bishopimport shutil
1040a360c2SBrad Bishopimport tarfile
1140a360c2SBrad Bishopimport os
1240a360c2SBrad Bishopfrom obmc.dbuslib.bindings import get_dbus, DbusProperties, DbusObjectManager
1340a360c2SBrad Bishop
1440a360c2SBrad BishopDBUS_NAME = 'org.openbmc.control.BmcFlash'
1540a360c2SBrad BishopOBJ_NAME = '/org/openbmc/control/flash/bmc'
1640a360c2SBrad BishopDOWNLOAD_INTF = 'org.openbmc.managers.Download'
1740a360c2SBrad Bishop
180c8c5d4aSMilton MillerBMC_DBUS_NAME = 'org.openbmc.control.Bmc'
190c8c5d4aSMilton MillerBMC_OBJ_NAME = '/org/openbmc/control/bmc0'
200c8c5d4aSMilton Miller
2140a360c2SBrad BishopUPDATE_PATH = '/run/initramfs'
2240a360c2SBrad Bishop
2340a360c2SBrad Bishop
2440a360c2SBrad Bishopdef doExtract(members, files):
2540a360c2SBrad Bishop    for tarinfo in members:
2634c3bf9eSBrad Bishop        if tarinfo.name in files:
2740a360c2SBrad Bishop            yield tarinfo
2840a360c2SBrad Bishop
2940a360c2SBrad Bishop
303fc6b790SMilton Millerdef save_fw_env():
313fc6b790SMilton Miller    fw_env = "/etc/fw_env.config"
323fc6b790SMilton Miller    lines = 0
333fc6b790SMilton Miller    files=[]
343fc6b790SMilton Miller    envcfg = open(fw_env, 'r')
353fc6b790SMilton Miller    try:
363fc6b790SMilton Miller        for line in envcfg.readlines():
373fc6b790SMilton Miller            # ignore lines that are blank or start with #
383fc6b790SMilton Miller            if (line.startswith("#")): continue
393fc6b790SMilton Miller            if (not len(line.strip())): continue
403fc6b790SMilton Miller            fn = line.partition("\t")[0];
413fc6b790SMilton Miller            files.append(fn)
423fc6b790SMilton Miller            lines += 1
433fc6b790SMilton Miller    finally:
443fc6b790SMilton Miller        envcfg.close()
453fc6b790SMilton Miller    if (lines < 1 or lines > 2 or (lines == 2 and files[0] != files[1])):
463fc6b790SMilton Miller            raise Exception("Error parsing %s\n" % fw_env)
473fc6b790SMilton Miller    shutil.copyfile(files[0], os.path.join(UPDATE_PATH, "image-u-boot-env"))
483fc6b790SMilton Miller
4940a360c2SBrad Bishopclass BmcFlashControl(DbusProperties, DbusObjectManager):
5040a360c2SBrad Bishop    def __init__(self, bus, name):
51f47f5faaSBrad Bishop        super(BmcFlashControl, self).__init__(
52f47f5faaSBrad Bishop            conn=bus,
53f47f5faaSBrad Bishop            object_path=name)
5440a360c2SBrad Bishop
5540a360c2SBrad Bishop        self.Set(DBUS_NAME, "status", "Idle")
5640a360c2SBrad Bishop        self.Set(DBUS_NAME, "filename", "")
57c8094109SMilton Miller        self.Set(DBUS_NAME, "preserve_network_settings", True)
5840a360c2SBrad Bishop        self.Set(DBUS_NAME, "restore_application_defaults", False)
5940a360c2SBrad Bishop        self.Set(DBUS_NAME, "update_kernel_and_apps", False)
6040a360c2SBrad Bishop        self.Set(DBUS_NAME, "clear_persistent_files", False)
610c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "auto_apply", False)
6240a360c2SBrad Bishop
6334c3bf9eSBrad Bishop        bus.add_signal_receiver(
6434c3bf9eSBrad Bishop            self.download_error_handler, signal_name="DownloadError")
6534c3bf9eSBrad Bishop        bus.add_signal_receiver(
6634c3bf9eSBrad Bishop            self.download_complete_handler, signal_name="DownloadComplete")
6740a360c2SBrad Bishop
680c8c5d4aSMilton Miller        self.update_process = None
690c8c5d4aSMilton Miller        self.progress_name = None
700c8c5d4aSMilton Miller
7134c3bf9eSBrad Bishop    @dbus.service.method(
7234c3bf9eSBrad Bishop        DBUS_NAME, in_signature='ss', out_signature='')
7340a360c2SBrad Bishop    def updateViaTftp(self, ip, filename):
7440a360c2SBrad Bishop        self.Set(DBUS_NAME, "status", "Downloading")
750c8c5d4aSMilton Miller        self.TftpDownload(ip, filename)
7640a360c2SBrad Bishop
7734c3bf9eSBrad Bishop    @dbus.service.method(
7834c3bf9eSBrad Bishop        DBUS_NAME, in_signature='s', out_signature='')
7940a360c2SBrad Bishop    def update(self, filename):
8040a360c2SBrad Bishop        self.Set(DBUS_NAME, "filename", filename)
8140a360c2SBrad Bishop        self.download_complete_handler(filename, filename)
8240a360c2SBrad Bishop
8340a360c2SBrad Bishop    @dbus.service.signal(DOWNLOAD_INTF, signature='ss')
8440a360c2SBrad Bishop    def TftpDownload(self, ip, filename):
8540a360c2SBrad Bishop        self.Set(DBUS_NAME, "filename", filename)
8640a360c2SBrad Bishop        pass
8740a360c2SBrad Bishop
8840a360c2SBrad Bishop    ## Signal handler
8940a360c2SBrad Bishop    def download_error_handler(self, filename):
9040a360c2SBrad Bishop        if (filename == self.Get(DBUS_NAME, "filename")):
9140a360c2SBrad Bishop            self.Set(DBUS_NAME, "status", "Download Error")
9240a360c2SBrad Bishop
9340a360c2SBrad Bishop    def download_complete_handler(self, outfile, filename):
9440a360c2SBrad Bishop        ## do update
9540a360c2SBrad Bishop        if (filename != self.Get(DBUS_NAME, "filename")):
9640a360c2SBrad Bishop            return
9740a360c2SBrad Bishop
9840a360c2SBrad Bishop        print "Download complete. Updating..."
9940a360c2SBrad Bishop
10040a360c2SBrad Bishop        self.Set(DBUS_NAME, "status", "Download Complete")
10140a360c2SBrad Bishop        copy_files = {}
10240a360c2SBrad Bishop
10340a360c2SBrad Bishop        ## determine needed files
10434c3bf9eSBrad Bishop        if not self.Get(DBUS_NAME, "update_kernel_and_apps"):
10540a360c2SBrad Bishop            copy_files["image-bmc"] = True
10640a360c2SBrad Bishop        else:
10740a360c2SBrad Bishop            copy_files["image-kernel"] = True
10840a360c2SBrad Bishop            copy_files["image-rofs"] = True
10940a360c2SBrad Bishop
11034c3bf9eSBrad Bishop        if self.Get(DBUS_NAME, "restore_application_defaults"):
11140a360c2SBrad Bishop            copy_files["image-rwfs"] = True
11240a360c2SBrad Bishop
11340a360c2SBrad Bishop        ## make sure files exist in archive
11440a360c2SBrad Bishop        try:
11540a360c2SBrad Bishop            tar = tarfile.open(outfile, "r")
11640a360c2SBrad Bishop            files = {}
11740a360c2SBrad Bishop            for f in tar.getnames():
11840a360c2SBrad Bishop                files[f] = True
11940a360c2SBrad Bishop            tar.close()
12040a360c2SBrad Bishop            for f in copy_files.keys():
12134c3bf9eSBrad Bishop                if f not in files:
12234c3bf9eSBrad Bishop                    raise Exception(
12334c3bf9eSBrad Bishop                        "ERROR: File not found in update archive: "+f)
12440a360c2SBrad Bishop
12540a360c2SBrad Bishop        except Exception as e:
12640a360c2SBrad Bishop            print e
1270c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Unpack Error")
12840a360c2SBrad Bishop            return
12940a360c2SBrad Bishop
13040a360c2SBrad Bishop        try:
13140a360c2SBrad Bishop            tar = tarfile.open(outfile, "r")
13240a360c2SBrad Bishop            tar.extractall(UPDATE_PATH, members=doExtract(tar, copy_files))
13340a360c2SBrad Bishop            tar.close()
13440a360c2SBrad Bishop
13534c3bf9eSBrad Bishop            if self.Get(DBUS_NAME, "clear_persistent_files"):
13640a360c2SBrad Bishop                print "Removing persistent files"
137b6cfc545SMilton Miller                try:
13840a360c2SBrad Bishop                    os.unlink(UPDATE_PATH+"/whitelist")
139b6cfc545SMilton Miller                except OSError as e:
140b6cfc545SMilton Miller                    if (e.errno == errno.EISDIR):
141b6cfc545SMilton Miller                        pass
142b6cfc545SMilton Miller                    elif (e.errno == errno.ENOENT):
143b6cfc545SMilton Miller                        pass
144b6cfc545SMilton Miller                    else:
145b6cfc545SMilton Miller                        raise
146b6cfc545SMilton Miller
147b6cfc545SMilton Miller                try:
148b6cfc545SMilton Miller                    wldir = UPDATE_PATH + "/whitelist.d"
149b6cfc545SMilton Miller
150b6cfc545SMilton Miller                    for file in os.listdir(wldir):
151b6cfc545SMilton Miller                        os.unlink(os.path.join(wldir, file))
152b6cfc545SMilton Miller                except OSError as e:
153b6cfc545SMilton Miller                    if (e.errno == errno.EISDIR):
154b6cfc545SMilton Miller                        pass
155b6cfc545SMilton Miller                    else:
156b6cfc545SMilton Miller                        raise
157b6cfc545SMilton Miller
15834c3bf9eSBrad Bishop            if self.Get(DBUS_NAME, "preserve_network_settings"):
15940a360c2SBrad Bishop                print "Preserving network settings"
1603fc6b790SMilton Miller                save_fw_env()
16140a360c2SBrad Bishop
16240a360c2SBrad Bishop        except Exception as e:
16340a360c2SBrad Bishop            print e
1640c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Unpack Error")
1650c8c5d4aSMilton Miller
1660c8c5d4aSMilton Miller        self.Verify()
1670c8c5d4aSMilton Miller
1680c8c5d4aSMilton Miller    def Verify(self):
1690c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Checking Image")
1700c8c5d4aSMilton Miller        try:
1710c8c5d4aSMilton Miller            subprocess.check_call([
1720c8c5d4aSMilton Miller                "/run/initramfs/update",
1730c8c5d4aSMilton Miller                "--no-flash",
1740c8c5d4aSMilton Miller                "--no-save-files",
1750c8c5d4aSMilton Miller                "--no-restore-files",
1760c8c5d4aSMilton Miller                "--no-clean-saved-files"])
1770c8c5d4aSMilton Miller
1780c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Image ready to apply.")
1790c8c5d4aSMilton Miller            if (self.Get(DBUS_NAME, "auto_apply")):
1800c8c5d4aSMilton Miller                self.Apply()
1810c8c5d4aSMilton Miller        except:
1820c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "auto_apply", False)
1830c8c5d4aSMilton Miller            try:
1840c8c5d4aSMilton Miller                subprocess.check_output([
1850c8c5d4aSMilton Miller                    "/run/initramfs/update",
1860c8c5d4aSMilton Miller                    "--no-flash",
1870c8c5d4aSMilton Miller                    "--ignore-mount",
1880c8c5d4aSMilton Miller                    "--no-save-files",
1890c8c5d4aSMilton Miller                    "--no-restore-files",
1900c8c5d4aSMilton Miller                    "--no-clean-saved-files"],
1910c8c5d4aSMilton Miller                    stderr=subprocess.STDOUT)
19234c3bf9eSBrad Bishop                self.Set(
19334c3bf9eSBrad Bishop                    DBUS_NAME, "status",
19434c3bf9eSBrad Bishop                    "Deferred for mounted filesystem. reboot BMC to apply.")
1950c8c5d4aSMilton Miller            except subprocess.CalledProcessError as e:
19634c3bf9eSBrad Bishop                self.Set(
19734c3bf9eSBrad Bishop                    DBUS_NAME, "status", "Verify error: %s" % e.output)
1980c8c5d4aSMilton Miller            except OSError as e:
19934c3bf9eSBrad Bishop                self.Set(
20034c3bf9eSBrad Bishop                    DBUS_NAME, "status",
20134c3bf9eSBrad Bishop                    "Verify error: problem calling update: %s" % e.strerror)
20240a360c2SBrad Bishop
2030c8c5d4aSMilton Miller    def Cleanup(self):
2040c8c5d4aSMilton Miller        if self.progress_name:
2050c8c5d4aSMilton Miller            try:
2060c8c5d4aSMilton Miller                os.unlink(self.progress_name)
2070c8c5d4aSMilton Miller                self.progress_name = None
2080c8c5d4aSMilton Miller            except oserror as e:
2090c8c5d4aSMilton Miller                if e.errno == EEXIST:
2100c8c5d4aSMilton Miller                    pass
2110c8c5d4aSMilton Miller                raise
2120c8c5d4aSMilton Miller        self.update_process = None
2130c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Idle")
21440a360c2SBrad Bishop
21534c3bf9eSBrad Bishop    @dbus.service.method(
21634c3bf9eSBrad Bishop        DBUS_NAME, in_signature='', out_signature='')
2170c8c5d4aSMilton Miller    def Abort(self):
2180c8c5d4aSMilton Miller        if self.update_process:
2190c8c5d4aSMilton Miller            try:
2200c8c5d4aSMilton Miller                self.update_process.kill()
2210c8c5d4aSMilton Miller            except:
2220c8c5d4aSMilton Miller                pass
2230c8c5d4aSMilton Miller        for file in os.listdir(UPDATE_PATH):
2240c8c5d4aSMilton Miller            if file.startswith('image-'):
2250c8c5d4aSMilton Miller                os.unlink(os.path.join(UPDATE_PATH, file))
2260c8c5d4aSMilton Miller
22734c3bf9eSBrad Bishop        self.Cleanup()
2280c8c5d4aSMilton Miller
22934c3bf9eSBrad Bishop    @dbus.service.method(
23034c3bf9eSBrad Bishop        DBUS_NAME, in_signature='', out_signature='s')
2310c8c5d4aSMilton Miller    def GetUpdateProgress(self):
2320c8c5d4aSMilton Miller        msg = ""
2330c8c5d4aSMilton Miller
2340c8c5d4aSMilton Miller        if self.update_process and self.update_process.returncode is None:
2350c8c5d4aSMilton Miller            self.update_process.poll()
2360c8c5d4aSMilton Miller
2370c8c5d4aSMilton Miller        if (self.update_process is None):
2380c8c5d4aSMilton Miller            pass
2390c8c5d4aSMilton Miller        elif (self.update_process.returncode > 0):
2400c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Apply failed")
2410c8c5d4aSMilton Miller        elif (self.update_process.returncode is None):
2420c8c5d4aSMilton Miller            pass
2430c8c5d4aSMilton Miller        else:            # (self.update_process.returncode == 0)
2440c8c5d4aSMilton Miller            files = ""
2450c8c5d4aSMilton Miller            for file in os.listdir(UPDATE_PATH):
2460c8c5d4aSMilton Miller                if file.startswith('image-'):
24734c3bf9eSBrad Bishop                    files = files + file
2480c8c5d4aSMilton Miller            if files == "":
2490c8c5d4aSMilton Miller                msg = "Apply Complete.  Reboot to take effect."
2500c8c5d4aSMilton Miller            else:
2510c8c5d4aSMilton Miller                msg = "Apply Incomplete, Remaining:" + files
2520c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", msg)
2530c8c5d4aSMilton Miller
25434c3bf9eSBrad Bishop        msg = self.Get(DBUS_NAME, "status") + "\n"
2550c8c5d4aSMilton Miller        if self.progress_name:
2560c8c5d4aSMilton Miller            try:
2570c8c5d4aSMilton Miller                prog = open(self.progress_name, 'r')
2580c8c5d4aSMilton Miller                for line in prog:
2590c8c5d4aSMilton Miller                    # strip off initial sets of xxx\r here
2600c8c5d4aSMilton Miller                    # ignore crlf at the end
2610c8c5d4aSMilton Miller                    # cr will be -1 if no '\r' is found
2620c8c5d4aSMilton Miller                    cr = line.rfind("\r", 0, -2)
2630c8c5d4aSMilton Miller                    msg = msg + line[cr + 1:]
2640c8c5d4aSMilton Miller            except OSError as e:
2650c8c5d4aSMilton Miller                if (e.error == EEXIST):
2660c8c5d4aSMilton Miller                    pass
2670c8c5d4aSMilton Miller                raise
2680c8c5d4aSMilton Miller        return msg
2690c8c5d4aSMilton Miller
27034c3bf9eSBrad Bishop    @dbus.service.method(
27134c3bf9eSBrad Bishop        DBUS_NAME, in_signature='', out_signature='')
2720c8c5d4aSMilton Miller    def Apply(self):
2730c8c5d4aSMilton Miller        progress = None
2740c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Writing images to flash")
2750c8c5d4aSMilton Miller        try:
2760c8c5d4aSMilton Miller            progress = tempfile.NamedTemporaryFile(
2770c8c5d4aSMilton Miller                delete=False, prefix="progress.")
2780c8c5d4aSMilton Miller            self.progress_name = progress.name
2790c8c5d4aSMilton Miller            self.update_process = subprocess.Popen([
2800c8c5d4aSMilton Miller                "/run/initramfs/update"],
2810c8c5d4aSMilton Miller                stdout=progress.file,
2820c8c5d4aSMilton Miller                stderr=subprocess.STDOUT)
2830c8c5d4aSMilton Miller        except Exception as e:
2840c8c5d4aSMilton Miller            try:
2850c8c5d4aSMilton Miller                progress.close()
2860c8c5d4aSMilton Miller                os.unlink(progress.name)
2870c8c5d4aSMilton Miller                self.progress_name = None
2880c8c5d4aSMilton Miller            except:
2890c8c5d4aSMilton Miller                pass
2900c8c5d4aSMilton Miller            raise
2910c8c5d4aSMilton Miller
2920c8c5d4aSMilton Miller        try:
2930c8c5d4aSMilton Miller            progress.close()
2940c8c5d4aSMilton Miller        except:
2950c8c5d4aSMilton Miller            pass
2960c8c5d4aSMilton Miller
29734c3bf9eSBrad Bishop    @dbus.service.method(
29834c3bf9eSBrad Bishop        DBUS_NAME, in_signature='', out_signature='')
2990c8c5d4aSMilton Miller    def PrepareForUpdate(self):
3000c8c5d4aSMilton Miller        subprocess.call([
3010c8c5d4aSMilton Miller            "fw_setenv",
3020c8c5d4aSMilton Miller            "openbmconce",
3030c8c5d4aSMilton Miller            "copy-files-to-ram copy-base-filesystem-to-ram"])
304*7bef82c0SAdriana Kobylak        # Set the variable twice so that it is written to both environments of
305*7bef82c0SAdriana Kobylak        # the u-boot redundant environment variables since initramfs can only
306*7bef82c0SAdriana Kobylak        # read one of the environments.
307*7bef82c0SAdriana Kobylak        subprocess.call([
308*7bef82c0SAdriana Kobylak            "fw_setenv",
309*7bef82c0SAdriana Kobylak            "openbmconce",
310*7bef82c0SAdriana Kobylak            "copy-files-to-ram copy-base-filesystem-to-ram"])
3110c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Switch to update mode in progress")
3120c8c5d4aSMilton Miller        o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME)
3130c8c5d4aSMilton Miller        intf = dbus.Interface(o, BMC_DBUS_NAME)
3140c8c5d4aSMilton Miller        intf.warmReset()
31540a360c2SBrad Bishop
31640a360c2SBrad Bishop
31740a360c2SBrad Bishopif __name__ == '__main__':
31840a360c2SBrad Bishop    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
31940a360c2SBrad Bishop
32040a360c2SBrad Bishop    bus = get_dbus()
32140a360c2SBrad Bishop    obj = BmcFlashControl(bus, OBJ_NAME)
32240a360c2SBrad Bishop    mainloop = gobject.MainLoop()
323f0f3efe1SBrad Bishop
324f0f3efe1SBrad Bishop    obj.unmask_signals()
32570852a38SBrad Bishop    name = dbus.service.BusName(DBUS_NAME, bus)
32640a360c2SBrad Bishop
32740a360c2SBrad Bishop    print "Running Bmc Flash Control"
32840a360c2SBrad Bishop    mainloop.run()
32953066750SBrad Bishop
33053066750SBrad Bishop# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
331