xref: /openbmc/skeleton/pyflashbmc/bmc_update.py (revision f47f5faa)
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
3040a360c2SBrad Bishopclass BmcFlashControl(DbusProperties, DbusObjectManager):
3140a360c2SBrad Bishop    def __init__(self, bus, name):
32*f47f5faaSBrad Bishop        super(BmcFlashControl, self).__init__(
33*f47f5faaSBrad Bishop            conn=bus,
34*f47f5faaSBrad Bishop            object_path=name)
3540a360c2SBrad Bishop
3640a360c2SBrad Bishop        self.Set(DBUS_NAME, "status", "Idle")
3740a360c2SBrad Bishop        self.Set(DBUS_NAME, "filename", "")
38c8094109SMilton Miller        self.Set(DBUS_NAME, "preserve_network_settings", True)
3940a360c2SBrad Bishop        self.Set(DBUS_NAME, "restore_application_defaults", False)
4040a360c2SBrad Bishop        self.Set(DBUS_NAME, "update_kernel_and_apps", False)
4140a360c2SBrad Bishop        self.Set(DBUS_NAME, "clear_persistent_files", False)
420c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "auto_apply", False)
4340a360c2SBrad Bishop
4434c3bf9eSBrad Bishop        bus.add_signal_receiver(
4534c3bf9eSBrad Bishop            self.download_error_handler, signal_name="DownloadError")
4634c3bf9eSBrad Bishop        bus.add_signal_receiver(
4734c3bf9eSBrad Bishop            self.download_complete_handler, signal_name="DownloadComplete")
4840a360c2SBrad Bishop
490c8c5d4aSMilton Miller        self.update_process = None
500c8c5d4aSMilton Miller        self.progress_name = None
510c8c5d4aSMilton Miller
5234c3bf9eSBrad Bishop    @dbus.service.method(
5334c3bf9eSBrad Bishop        DBUS_NAME, in_signature='ss', out_signature='')
5440a360c2SBrad Bishop    def updateViaTftp(self, ip, filename):
5540a360c2SBrad Bishop        self.Set(DBUS_NAME, "status", "Downloading")
560c8c5d4aSMilton Miller        self.TftpDownload(ip, filename)
5740a360c2SBrad Bishop
5834c3bf9eSBrad Bishop    @dbus.service.method(
5934c3bf9eSBrad Bishop        DBUS_NAME, in_signature='s', out_signature='')
6040a360c2SBrad Bishop    def update(self, filename):
6140a360c2SBrad Bishop        self.Set(DBUS_NAME, "filename", filename)
6240a360c2SBrad Bishop        self.download_complete_handler(filename, filename)
6340a360c2SBrad Bishop
6440a360c2SBrad Bishop    @dbus.service.signal(DOWNLOAD_INTF, signature='ss')
6540a360c2SBrad Bishop    def TftpDownload(self, ip, filename):
6640a360c2SBrad Bishop        self.Set(DBUS_NAME, "filename", filename)
6740a360c2SBrad Bishop        pass
6840a360c2SBrad Bishop
6940a360c2SBrad Bishop    ## Signal handler
7040a360c2SBrad Bishop    def download_error_handler(self, filename):
7140a360c2SBrad Bishop        if (filename == self.Get(DBUS_NAME, "filename")):
7240a360c2SBrad Bishop            self.Set(DBUS_NAME, "status", "Download Error")
7340a360c2SBrad Bishop
7440a360c2SBrad Bishop    def download_complete_handler(self, outfile, filename):
7540a360c2SBrad Bishop        ## do update
7640a360c2SBrad Bishop        if (filename != self.Get(DBUS_NAME, "filename")):
7740a360c2SBrad Bishop            return
7840a360c2SBrad Bishop
7940a360c2SBrad Bishop        print "Download complete. Updating..."
8040a360c2SBrad Bishop
8140a360c2SBrad Bishop        self.Set(DBUS_NAME, "status", "Download Complete")
8240a360c2SBrad Bishop        copy_files = {}
8340a360c2SBrad Bishop
8440a360c2SBrad Bishop        ## determine needed files
8534c3bf9eSBrad Bishop        if not self.Get(DBUS_NAME, "update_kernel_and_apps"):
8640a360c2SBrad Bishop            copy_files["image-bmc"] = True
8740a360c2SBrad Bishop        else:
8840a360c2SBrad Bishop            copy_files["image-kernel"] = True
8940a360c2SBrad Bishop            copy_files["image-initramfs"] = True
9040a360c2SBrad Bishop            copy_files["image-rofs"] = True
9140a360c2SBrad Bishop
9234c3bf9eSBrad Bishop        if self.Get(DBUS_NAME, "restore_application_defaults"):
9340a360c2SBrad Bishop            copy_files["image-rwfs"] = True
9440a360c2SBrad Bishop
9540a360c2SBrad Bishop        ## make sure files exist in archive
9640a360c2SBrad Bishop        try:
9740a360c2SBrad Bishop            tar = tarfile.open(outfile, "r")
9840a360c2SBrad Bishop            files = {}
9940a360c2SBrad Bishop            for f in tar.getnames():
10040a360c2SBrad Bishop                files[f] = True
10140a360c2SBrad Bishop            tar.close()
10240a360c2SBrad Bishop            for f in copy_files.keys():
10334c3bf9eSBrad Bishop                if f not in files:
10434c3bf9eSBrad Bishop                    raise Exception(
10534c3bf9eSBrad Bishop                        "ERROR: File not found in update archive: "+f)
10640a360c2SBrad Bishop
10740a360c2SBrad Bishop        except Exception as e:
10840a360c2SBrad Bishop            print e
1090c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Unpack Error")
11040a360c2SBrad Bishop            return
11140a360c2SBrad Bishop
11240a360c2SBrad Bishop        try:
11340a360c2SBrad Bishop            tar = tarfile.open(outfile, "r")
11440a360c2SBrad Bishop            tar.extractall(UPDATE_PATH, members=doExtract(tar, copy_files))
11540a360c2SBrad Bishop            tar.close()
11640a360c2SBrad Bishop
11734c3bf9eSBrad Bishop            if self.Get(DBUS_NAME, "clear_persistent_files"):
11840a360c2SBrad Bishop                print "Removing persistent files"
119b6cfc545SMilton Miller                try:
12040a360c2SBrad Bishop                    os.unlink(UPDATE_PATH+"/whitelist")
121b6cfc545SMilton Miller                except OSError as e:
122b6cfc545SMilton Miller                    if (e.errno == errno.EISDIR):
123b6cfc545SMilton Miller                        pass
124b6cfc545SMilton Miller                    elif (e.errno == errno.ENOENT):
125b6cfc545SMilton Miller                        pass
126b6cfc545SMilton Miller                    else:
127b6cfc545SMilton Miller                        raise
128b6cfc545SMilton Miller
129b6cfc545SMilton Miller                try:
130b6cfc545SMilton Miller                    wldir = UPDATE_PATH + "/whitelist.d"
131b6cfc545SMilton Miller
132b6cfc545SMilton Miller                    for file in os.listdir(wldir):
133b6cfc545SMilton Miller                        os.unlink(os.path.join(wldir, file))
134b6cfc545SMilton Miller                except OSError as e:
135b6cfc545SMilton Miller                    if (e.errno == errno.EISDIR):
136b6cfc545SMilton Miller                        pass
137b6cfc545SMilton Miller                    else:
138b6cfc545SMilton Miller                        raise
139b6cfc545SMilton Miller
14034c3bf9eSBrad Bishop            if self.Get(DBUS_NAME, "preserve_network_settings"):
14140a360c2SBrad Bishop                print "Preserving network settings"
142c8094109SMilton Miller                shutil.copy2("/run/fw_env", UPDATE_PATH+"/image-u-boot-env")
14340a360c2SBrad Bishop
14440a360c2SBrad Bishop        except Exception as e:
14540a360c2SBrad Bishop            print e
1460c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Unpack Error")
1470c8c5d4aSMilton Miller
1480c8c5d4aSMilton Miller        self.Verify()
1490c8c5d4aSMilton Miller
1500c8c5d4aSMilton Miller    def Verify(self):
1510c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Checking Image")
1520c8c5d4aSMilton Miller        try:
1530c8c5d4aSMilton Miller            subprocess.check_call([
1540c8c5d4aSMilton Miller                "/run/initramfs/update",
1550c8c5d4aSMilton Miller                "--no-flash",
1560c8c5d4aSMilton Miller                "--no-save-files",
1570c8c5d4aSMilton Miller                "--no-restore-files",
1580c8c5d4aSMilton Miller                "--no-clean-saved-files"])
1590c8c5d4aSMilton Miller
1600c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Image ready to apply.")
1610c8c5d4aSMilton Miller            if (self.Get(DBUS_NAME, "auto_apply")):
1620c8c5d4aSMilton Miller                self.Apply()
1630c8c5d4aSMilton Miller        except:
1640c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "auto_apply", False)
1650c8c5d4aSMilton Miller            try:
1660c8c5d4aSMilton Miller                subprocess.check_output([
1670c8c5d4aSMilton Miller                    "/run/initramfs/update",
1680c8c5d4aSMilton Miller                    "--no-flash",
1690c8c5d4aSMilton Miller                    "--ignore-mount",
1700c8c5d4aSMilton Miller                    "--no-save-files",
1710c8c5d4aSMilton Miller                    "--no-restore-files",
1720c8c5d4aSMilton Miller                    "--no-clean-saved-files"],
1730c8c5d4aSMilton Miller                    stderr=subprocess.STDOUT)
17434c3bf9eSBrad Bishop                self.Set(
17534c3bf9eSBrad Bishop                    DBUS_NAME, "status",
17634c3bf9eSBrad Bishop                    "Deferred for mounted filesystem. reboot BMC to apply.")
1770c8c5d4aSMilton Miller            except subprocess.CalledProcessError as e:
17834c3bf9eSBrad Bishop                self.Set(
17934c3bf9eSBrad Bishop                    DBUS_NAME, "status", "Verify error: %s" % e.output)
1800c8c5d4aSMilton Miller            except OSError as e:
18134c3bf9eSBrad Bishop                self.Set(
18234c3bf9eSBrad Bishop                    DBUS_NAME, "status",
18334c3bf9eSBrad Bishop                    "Verify error: problem calling update: %s" % e.strerror)
18440a360c2SBrad Bishop
1850c8c5d4aSMilton Miller    def Cleanup(self):
1860c8c5d4aSMilton Miller        if self.progress_name:
1870c8c5d4aSMilton Miller            try:
1880c8c5d4aSMilton Miller                os.unlink(self.progress_name)
1890c8c5d4aSMilton Miller                self.progress_name = None
1900c8c5d4aSMilton Miller            except oserror as e:
1910c8c5d4aSMilton Miller                if e.errno == EEXIST:
1920c8c5d4aSMilton Miller                    pass
1930c8c5d4aSMilton Miller                raise
1940c8c5d4aSMilton Miller        self.update_process = None
1950c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Idle")
19640a360c2SBrad Bishop
19734c3bf9eSBrad Bishop    @dbus.service.method(
19834c3bf9eSBrad Bishop        DBUS_NAME, in_signature='', out_signature='')
1990c8c5d4aSMilton Miller    def Abort(self):
2000c8c5d4aSMilton Miller        if self.update_process:
2010c8c5d4aSMilton Miller            try:
2020c8c5d4aSMilton Miller                self.update_process.kill()
2030c8c5d4aSMilton Miller            except:
2040c8c5d4aSMilton Miller                pass
2050c8c5d4aSMilton Miller        for file in os.listdir(UPDATE_PATH):
2060c8c5d4aSMilton Miller            if file.startswith('image-'):
2070c8c5d4aSMilton Miller                os.unlink(os.path.join(UPDATE_PATH, file))
2080c8c5d4aSMilton Miller
20934c3bf9eSBrad Bishop        self.Cleanup()
2100c8c5d4aSMilton Miller
21134c3bf9eSBrad Bishop    @dbus.service.method(
21234c3bf9eSBrad Bishop        DBUS_NAME, in_signature='', out_signature='s')
2130c8c5d4aSMilton Miller    def GetUpdateProgress(self):
2140c8c5d4aSMilton Miller        msg = ""
2150c8c5d4aSMilton Miller
2160c8c5d4aSMilton Miller        if self.update_process and self.update_process.returncode is None:
2170c8c5d4aSMilton Miller            self.update_process.poll()
2180c8c5d4aSMilton Miller
2190c8c5d4aSMilton Miller        if (self.update_process is None):
2200c8c5d4aSMilton Miller            pass
2210c8c5d4aSMilton Miller        elif (self.update_process.returncode > 0):
2220c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", "Apply failed")
2230c8c5d4aSMilton Miller        elif (self.update_process.returncode is None):
2240c8c5d4aSMilton Miller            pass
2250c8c5d4aSMilton Miller        else:            # (self.update_process.returncode == 0)
2260c8c5d4aSMilton Miller            files = ""
2270c8c5d4aSMilton Miller            for file in os.listdir(UPDATE_PATH):
2280c8c5d4aSMilton Miller                if file.startswith('image-'):
22934c3bf9eSBrad Bishop                    files = files + file
2300c8c5d4aSMilton Miller            if files == "":
2310c8c5d4aSMilton Miller                msg = "Apply Complete.  Reboot to take effect."
2320c8c5d4aSMilton Miller            else:
2330c8c5d4aSMilton Miller                msg = "Apply Incomplete, Remaining:" + files
2340c8c5d4aSMilton Miller            self.Set(DBUS_NAME, "status", msg)
2350c8c5d4aSMilton Miller
23634c3bf9eSBrad Bishop        msg = self.Get(DBUS_NAME, "status") + "\n"
2370c8c5d4aSMilton Miller        if self.progress_name:
2380c8c5d4aSMilton Miller            try:
2390c8c5d4aSMilton Miller                prog = open(self.progress_name, 'r')
2400c8c5d4aSMilton Miller                for line in prog:
2410c8c5d4aSMilton Miller                    # strip off initial sets of xxx\r here
2420c8c5d4aSMilton Miller                    # ignore crlf at the end
2430c8c5d4aSMilton Miller                    # cr will be -1 if no '\r' is found
2440c8c5d4aSMilton Miller                    cr = line.rfind("\r", 0, -2)
2450c8c5d4aSMilton Miller                    msg = msg + line[cr + 1:]
2460c8c5d4aSMilton Miller            except OSError as e:
2470c8c5d4aSMilton Miller                if (e.error == EEXIST):
2480c8c5d4aSMilton Miller                    pass
2490c8c5d4aSMilton Miller                raise
2500c8c5d4aSMilton Miller        return msg
2510c8c5d4aSMilton Miller
25234c3bf9eSBrad Bishop    @dbus.service.method(
25334c3bf9eSBrad Bishop        DBUS_NAME, in_signature='', out_signature='')
2540c8c5d4aSMilton Miller    def Apply(self):
2550c8c5d4aSMilton Miller        progress = None
2560c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Writing images to flash")
2570c8c5d4aSMilton Miller        try:
2580c8c5d4aSMilton Miller            progress = tempfile.NamedTemporaryFile(
2590c8c5d4aSMilton Miller                delete=False, prefix="progress.")
2600c8c5d4aSMilton Miller            self.progress_name = progress.name
2610c8c5d4aSMilton Miller            self.update_process = subprocess.Popen([
2620c8c5d4aSMilton Miller                "/run/initramfs/update"],
2630c8c5d4aSMilton Miller                stdout=progress.file,
2640c8c5d4aSMilton Miller                stderr=subprocess.STDOUT)
2650c8c5d4aSMilton Miller        except Exception as e:
2660c8c5d4aSMilton Miller            try:
2670c8c5d4aSMilton Miller                progress.close()
2680c8c5d4aSMilton Miller                os.unlink(progress.name)
2690c8c5d4aSMilton Miller                self.progress_name = None
2700c8c5d4aSMilton Miller            except:
2710c8c5d4aSMilton Miller                pass
2720c8c5d4aSMilton Miller            raise
2730c8c5d4aSMilton Miller
2740c8c5d4aSMilton Miller        try:
2750c8c5d4aSMilton Miller            progress.close()
2760c8c5d4aSMilton Miller        except:
2770c8c5d4aSMilton Miller            pass
2780c8c5d4aSMilton Miller
27934c3bf9eSBrad Bishop    @dbus.service.method(
28034c3bf9eSBrad Bishop        DBUS_NAME, in_signature='', out_signature='')
2810c8c5d4aSMilton Miller    def PrepareForUpdate(self):
2820c8c5d4aSMilton Miller        subprocess.call([
2830c8c5d4aSMilton Miller            "fw_setenv",
2840c8c5d4aSMilton Miller            "openbmconce",
2850c8c5d4aSMilton Miller            "copy-files-to-ram copy-base-filesystem-to-ram"])
2860c8c5d4aSMilton Miller        self.Set(DBUS_NAME, "status", "Switch to update mode in progress")
2870c8c5d4aSMilton Miller        o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME)
2880c8c5d4aSMilton Miller        intf = dbus.Interface(o, BMC_DBUS_NAME)
2890c8c5d4aSMilton Miller        intf.warmReset()
29040a360c2SBrad Bishop
29140a360c2SBrad Bishop
29240a360c2SBrad Bishopif __name__ == '__main__':
29340a360c2SBrad Bishop    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
29440a360c2SBrad Bishop
29540a360c2SBrad Bishop    bus = get_dbus()
29640a360c2SBrad Bishop    obj = BmcFlashControl(bus, OBJ_NAME)
29740a360c2SBrad Bishop    mainloop = gobject.MainLoop()
298f0f3efe1SBrad Bishop
299f0f3efe1SBrad Bishop    obj.unmask_signals()
30070852a38SBrad Bishop    name = dbus.service.BusName(DBUS_NAME, bus)
30140a360c2SBrad Bishop
30240a360c2SBrad Bishop    print "Running Bmc Flash Control"
30340a360c2SBrad Bishop    mainloop.run()
304