xref: /openbmc/skeleton/pyflashbmc/bmc_update.py (revision 62d11c3e)
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-rofs"] = True
90
91        if self.Get(DBUS_NAME, "restore_application_defaults"):
92            copy_files["image-rwfs"] = True
93
94        ## make sure files exist in archive
95        try:
96            tar = tarfile.open(outfile, "r")
97            files = {}
98            for f in tar.getnames():
99                files[f] = True
100            tar.close()
101            for f in copy_files.keys():
102                if f not in files:
103                    raise Exception(
104                        "ERROR: File not found in update archive: "+f)
105
106        except Exception as e:
107            print e
108            self.Set(DBUS_NAME, "status", "Unpack Error")
109            return
110
111        try:
112            tar = tarfile.open(outfile, "r")
113            tar.extractall(UPDATE_PATH, members=doExtract(tar, copy_files))
114            tar.close()
115
116            if self.Get(DBUS_NAME, "clear_persistent_files"):
117                print "Removing persistent files"
118                try:
119                    os.unlink(UPDATE_PATH+"/whitelist")
120                except OSError as e:
121                    if (e.errno == errno.EISDIR):
122                        pass
123                    elif (e.errno == errno.ENOENT):
124                        pass
125                    else:
126                        raise
127
128                try:
129                    wldir = UPDATE_PATH + "/whitelist.d"
130
131                    for file in os.listdir(wldir):
132                        os.unlink(os.path.join(wldir, file))
133                except OSError as e:
134                    if (e.errno == errno.EISDIR):
135                        pass
136                    else:
137                        raise
138
139            if self.Get(DBUS_NAME, "preserve_network_settings"):
140                print "Preserving network settings"
141                shutil.copy2("/run/fw_env", UPDATE_PATH+"/image-u-boot-env")
142
143        except Exception as e:
144            print e
145            self.Set(DBUS_NAME, "status", "Unpack Error")
146
147        self.Verify()
148
149    def Verify(self):
150        self.Set(DBUS_NAME, "status", "Checking Image")
151        try:
152            subprocess.check_call([
153                "/run/initramfs/update",
154                "--no-flash",
155                "--no-save-files",
156                "--no-restore-files",
157                "--no-clean-saved-files"])
158
159            self.Set(DBUS_NAME, "status", "Image ready to apply.")
160            if (self.Get(DBUS_NAME, "auto_apply")):
161                self.Apply()
162        except:
163            self.Set(DBUS_NAME, "auto_apply", False)
164            try:
165                subprocess.check_output([
166                    "/run/initramfs/update",
167                    "--no-flash",
168                    "--ignore-mount",
169                    "--no-save-files",
170                    "--no-restore-files",
171                    "--no-clean-saved-files"],
172                    stderr=subprocess.STDOUT)
173                self.Set(
174                    DBUS_NAME, "status",
175                    "Deferred for mounted filesystem. reboot BMC to apply.")
176            except subprocess.CalledProcessError as e:
177                self.Set(
178                    DBUS_NAME, "status", "Verify error: %s" % e.output)
179            except OSError as e:
180                self.Set(
181                    DBUS_NAME, "status",
182                    "Verify error: problem calling update: %s" % e.strerror)
183
184    def Cleanup(self):
185        if self.progress_name:
186            try:
187                os.unlink(self.progress_name)
188                self.progress_name = None
189            except oserror as e:
190                if e.errno == EEXIST:
191                    pass
192                raise
193        self.update_process = None
194        self.Set(DBUS_NAME, "status", "Idle")
195
196    @dbus.service.method(
197        DBUS_NAME, in_signature='', out_signature='')
198    def Abort(self):
199        if self.update_process:
200            try:
201                self.update_process.kill()
202            except:
203                pass
204        for file in os.listdir(UPDATE_PATH):
205            if file.startswith('image-'):
206                os.unlink(os.path.join(UPDATE_PATH, file))
207
208        self.Cleanup()
209
210    @dbus.service.method(
211        DBUS_NAME, in_signature='', out_signature='s')
212    def GetUpdateProgress(self):
213        msg = ""
214
215        if self.update_process and self.update_process.returncode is None:
216            self.update_process.poll()
217
218        if (self.update_process is None):
219            pass
220        elif (self.update_process.returncode > 0):
221            self.Set(DBUS_NAME, "status", "Apply failed")
222        elif (self.update_process.returncode is None):
223            pass
224        else:            # (self.update_process.returncode == 0)
225            files = ""
226            for file in os.listdir(UPDATE_PATH):
227                if file.startswith('image-'):
228                    files = files + file
229            if files == "":
230                msg = "Apply Complete.  Reboot to take effect."
231            else:
232                msg = "Apply Incomplete, Remaining:" + files
233            self.Set(DBUS_NAME, "status", msg)
234
235        msg = self.Get(DBUS_NAME, "status") + "\n"
236        if self.progress_name:
237            try:
238                prog = open(self.progress_name, 'r')
239                for line in prog:
240                    # strip off initial sets of xxx\r here
241                    # ignore crlf at the end
242                    # cr will be -1 if no '\r' is found
243                    cr = line.rfind("\r", 0, -2)
244                    msg = msg + line[cr + 1:]
245            except OSError as e:
246                if (e.error == EEXIST):
247                    pass
248                raise
249        return msg
250
251    @dbus.service.method(
252        DBUS_NAME, in_signature='', out_signature='')
253    def Apply(self):
254        progress = None
255        self.Set(DBUS_NAME, "status", "Writing images to flash")
256        try:
257            progress = tempfile.NamedTemporaryFile(
258                delete=False, prefix="progress.")
259            self.progress_name = progress.name
260            self.update_process = subprocess.Popen([
261                "/run/initramfs/update"],
262                stdout=progress.file,
263                stderr=subprocess.STDOUT)
264        except Exception as e:
265            try:
266                progress.close()
267                os.unlink(progress.name)
268                self.progress_name = None
269            except:
270                pass
271            raise
272
273        try:
274            progress.close()
275        except:
276            pass
277
278    @dbus.service.method(
279        DBUS_NAME, in_signature='', out_signature='')
280    def PrepareForUpdate(self):
281        subprocess.call([
282            "fw_setenv",
283            "openbmconce",
284            "copy-files-to-ram copy-base-filesystem-to-ram"])
285        self.Set(DBUS_NAME, "status", "Switch to update mode in progress")
286        o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME)
287        intf = dbus.Interface(o, BMC_DBUS_NAME)
288        intf.warmReset()
289
290
291if __name__ == '__main__':
292    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
293
294    bus = get_dbus()
295    obj = BmcFlashControl(bus, OBJ_NAME)
296    mainloop = gobject.MainLoop()
297
298    obj.unmask_signals()
299    name = dbus.service.BusName(DBUS_NAME, bus)
300
301    print "Running Bmc Flash Control"
302    mainloop.run()
303
304# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
305