xref: /openbmc/skeleton/pyflashbmc/bmc_update.py (revision a7ac805b)
1#!/usr/bin/env python
2
3import gobject
4import dbus
5import dbus.service
6import dbus.mainloop.glib
7import subprocess
8import tempfile
9import shutil
10import tarfile
11import os
12from obmc.dbuslib.bindings import get_dbus, DbusProperties, DbusObjectManager
13
14DBUS_NAME = 'org.openbmc.control.BmcFlash'
15OBJ_NAME = '/org/openbmc/control/flash/bmc'
16DOWNLOAD_INTF = 'org.openbmc.managers.Download'
17
18BMC_DBUS_NAME = 'org.openbmc.control.Bmc'
19BMC_OBJ_NAME = '/org/openbmc/control/bmc0'
20
21UPDATE_PATH = '/run/initramfs'
22
23
24def doExtract(members, files):
25    for tarinfo in members:
26        if tarinfo.name in files:
27            yield tarinfo
28
29
30class BmcFlashControl(DbusProperties, DbusObjectManager):
31    def __init__(self, bus, name):
32        super(BmcFlashControl, self).__init__(
33            conn=bus,
34            object_path=name)
35
36        self.Set(DBUS_NAME, "status", "Idle")
37        self.Set(DBUS_NAME, "filename", "")
38        self.Set(DBUS_NAME, "preserve_network_settings", True)
39        self.Set(DBUS_NAME, "restore_application_defaults", False)
40        self.Set(DBUS_NAME, "update_kernel_and_apps", False)
41        self.Set(DBUS_NAME, "clear_persistent_files", False)
42        self.Set(DBUS_NAME, "auto_apply", False)
43
44        bus.add_signal_receiver(
45            self.download_error_handler, signal_name="DownloadError")
46        bus.add_signal_receiver(
47            self.download_complete_handler, signal_name="DownloadComplete")
48
49        self.update_process = None
50        self.progress_name = None
51
52    @dbus.service.method(
53        DBUS_NAME, in_signature='ss', out_signature='')
54    def updateViaTftp(self, ip, filename):
55        self.Set(DBUS_NAME, "status", "Downloading")
56        self.TftpDownload(ip, filename)
57
58    @dbus.service.method(
59        DBUS_NAME, in_signature='s', out_signature='')
60    def update(self, filename):
61        self.Set(DBUS_NAME, "filename", filename)
62        self.download_complete_handler(filename, filename)
63
64    @dbus.service.signal(DOWNLOAD_INTF, signature='ss')
65    def TftpDownload(self, ip, filename):
66        self.Set(DBUS_NAME, "filename", filename)
67        pass
68
69    ## Signal handler
70    def download_error_handler(self, filename):
71        if (filename == self.Get(DBUS_NAME, "filename")):
72            self.Set(DBUS_NAME, "status", "Download Error")
73
74    def download_complete_handler(self, outfile, filename):
75        ## do update
76        if (filename != self.Get(DBUS_NAME, "filename")):
77            return
78
79        print "Download complete. Updating..."
80
81        self.Set(DBUS_NAME, "status", "Download Complete")
82        copy_files = {}
83
84        ## determine needed files
85        if not self.Get(DBUS_NAME, "update_kernel_and_apps"):
86            copy_files["image-bmc"] = True
87        else:
88            copy_files["image-kernel"] = True
89            copy_files["image-initramfs"] = True
90            copy_files["image-rofs"] = True
91
92        if self.Get(DBUS_NAME, "restore_application_defaults"):
93            copy_files["image-rwfs"] = True
94
95        ## make sure files exist in archive
96        try:
97            tar = tarfile.open(outfile, "r")
98            files = {}
99            for f in tar.getnames():
100                files[f] = True
101            tar.close()
102            for f in copy_files.keys():
103                if f not in files:
104                    raise Exception(
105                        "ERROR: File not found in update archive: "+f)
106
107        except Exception as e:
108            print e
109            self.Set(DBUS_NAME, "status", "Unpack Error")
110            return
111
112        try:
113            tar = tarfile.open(outfile, "r")
114            tar.extractall(UPDATE_PATH, members=doExtract(tar, copy_files))
115            tar.close()
116
117            if self.Get(DBUS_NAME, "clear_persistent_files"):
118                print "Removing persistent files"
119                try:
120                    os.unlink(UPDATE_PATH+"/whitelist")
121                except OSError as e:
122                    if (e.errno == errno.EISDIR):
123                        pass
124                    elif (e.errno == errno.ENOENT):
125                        pass
126                    else:
127                        raise
128
129                try:
130                    wldir = UPDATE_PATH + "/whitelist.d"
131
132                    for file in os.listdir(wldir):
133                        os.unlink(os.path.join(wldir, file))
134                except OSError as e:
135                    if (e.errno == errno.EISDIR):
136                        pass
137                    else:
138                        raise
139
140            if self.Get(DBUS_NAME, "preserve_network_settings"):
141                print "Preserving network settings"
142                shutil.copy2("/run/fw_env", UPDATE_PATH+"/image-u-boot-env")
143
144        except Exception as e:
145            print e
146            self.Set(DBUS_NAME, "status", "Unpack Error")
147
148        self.Verify()
149
150    def Verify(self):
151        self.Set(DBUS_NAME, "status", "Checking Image")
152        try:
153            subprocess.check_call([
154                "/run/initramfs/update",
155                "--no-flash",
156                "--no-save-files",
157                "--no-restore-files",
158                "--no-clean-saved-files"])
159
160            self.Set(DBUS_NAME, "status", "Image ready to apply.")
161            if (self.Get(DBUS_NAME, "auto_apply")):
162                self.Apply()
163        except:
164            self.Set(DBUS_NAME, "auto_apply", False)
165            try:
166                subprocess.check_output([
167                    "/run/initramfs/update",
168                    "--no-flash",
169                    "--ignore-mount",
170                    "--no-save-files",
171                    "--no-restore-files",
172                    "--no-clean-saved-files"],
173                    stderr=subprocess.STDOUT)
174                self.Set(
175                    DBUS_NAME, "status",
176                    "Deferred for mounted filesystem. reboot BMC to apply.")
177            except subprocess.CalledProcessError as e:
178                self.Set(
179                    DBUS_NAME, "status", "Verify error: %s" % e.output)
180            except OSError as e:
181                self.Set(
182                    DBUS_NAME, "status",
183                    "Verify error: problem calling update: %s" % e.strerror)
184
185    def Cleanup(self):
186        if self.progress_name:
187            try:
188                os.unlink(self.progress_name)
189                self.progress_name = None
190            except oserror as e:
191                if e.errno == EEXIST:
192                    pass
193                raise
194        self.update_process = None
195        self.Set(DBUS_NAME, "status", "Idle")
196
197    @dbus.service.method(
198        DBUS_NAME, in_signature='', out_signature='')
199    def Abort(self):
200        if self.update_process:
201            try:
202                self.update_process.kill()
203            except:
204                pass
205        for file in os.listdir(UPDATE_PATH):
206            if file.startswith('image-'):
207                os.unlink(os.path.join(UPDATE_PATH, file))
208
209        self.Cleanup()
210
211    @dbus.service.method(
212        DBUS_NAME, in_signature='', out_signature='s')
213    def GetUpdateProgress(self):
214        msg = ""
215
216        if self.update_process and self.update_process.returncode is None:
217            self.update_process.poll()
218
219        if (self.update_process is None):
220            pass
221        elif (self.update_process.returncode > 0):
222            self.Set(DBUS_NAME, "status", "Apply failed")
223        elif (self.update_process.returncode is None):
224            pass
225        else:            # (self.update_process.returncode == 0)
226            files = ""
227            for file in os.listdir(UPDATE_PATH):
228                if file.startswith('image-'):
229                    files = files + file
230            if files == "":
231                msg = "Apply Complete.  Reboot to take effect."
232            else:
233                msg = "Apply Incomplete, Remaining:" + files
234            self.Set(DBUS_NAME, "status", msg)
235
236        msg = self.Get(DBUS_NAME, "status") + "\n"
237        if self.progress_name:
238            try:
239                prog = open(self.progress_name, 'r')
240                for line in prog:
241                    # strip off initial sets of xxx\r here
242                    # ignore crlf at the end
243                    # cr will be -1 if no '\r' is found
244                    cr = line.rfind("\r", 0, -2)
245                    msg = msg + line[cr + 1:]
246            except OSError as e:
247                if (e.error == EEXIST):
248                    pass
249                raise
250        return msg
251
252    @dbus.service.method(
253        DBUS_NAME, in_signature='', out_signature='')
254    def Apply(self):
255        progress = None
256        self.Set(DBUS_NAME, "status", "Writing images to flash")
257        try:
258            progress = tempfile.NamedTemporaryFile(
259                delete=False, prefix="progress.")
260            self.progress_name = progress.name
261            self.update_process = subprocess.Popen([
262                "/run/initramfs/update"],
263                stdout=progress.file,
264                stderr=subprocess.STDOUT)
265        except Exception as e:
266            try:
267                progress.close()
268                os.unlink(progress.name)
269                self.progress_name = None
270            except:
271                pass
272            raise
273
274        try:
275            progress.close()
276        except:
277            pass
278
279    @dbus.service.method(
280        DBUS_NAME, in_signature='', out_signature='')
281    def PrepareForUpdate(self):
282        subprocess.call([
283            "fw_setenv",
284            "openbmconce",
285            "copy-files-to-ram copy-base-filesystem-to-ram"])
286        self.Set(DBUS_NAME, "status", "Switch to update mode in progress")
287        o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME)
288        intf = dbus.Interface(o, BMC_DBUS_NAME)
289        intf.warmReset()
290
291
292if __name__ == '__main__':
293    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
294
295    bus = get_dbus()
296    obj = BmcFlashControl(bus, OBJ_NAME)
297    mainloop = gobject.MainLoop()
298
299    obj.unmask_signals()
300    name = dbus.service.BusName(DBUS_NAME, bus)
301
302    print "Running Bmc Flash Control"
303    mainloop.run()
304
305# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
306