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