blob: a83eafe9c81fd02828810bc1805d9de4eb37acda [file] [log] [blame]
Brad Bishop34c3bf92016-08-30 20:34:51 -04001#!/usr/bin/env python
Norman James3d6a06f2016-01-30 13:03:45 -06002
3import gobject
4import dbus
5import dbus.service
6import dbus.mainloop.glib
Milton Miller0c8c5d42016-06-29 17:41:36 -05007import subprocess
8import tempfile
Norman James3d6a06f2016-01-30 13:03:45 -06009import shutil
10import tarfile
Norman James8a65a542016-02-03 19:04:44 -060011import os
Brad Bishop84e73b52016-05-12 15:57:52 -040012from obmc.dbuslib.bindings import get_dbus, DbusProperties, DbusObjectManager
Norman James3d6a06f2016-01-30 13:03:45 -060013
14DBUS_NAME = 'org.openbmc.control.BmcFlash'
15OBJ_NAME = '/org/openbmc/control/flash/bmc'
Norman James3d6a06f2016-01-30 13:03:45 -060016DOWNLOAD_INTF = 'org.openbmc.managers.Download'
17
Milton Miller0c8c5d42016-06-29 17:41:36 -050018BMC_DBUS_NAME = 'org.openbmc.control.Bmc'
19BMC_OBJ_NAME = '/org/openbmc/control/bmc0'
20
Norman James3d6a06f2016-01-30 13:03:45 -060021UPDATE_PATH = '/run/initramfs'
22
23
Brad Bishop34c3bf92016-08-30 20:34:51 -040024def doExtract(members, files):
Norman James3d6a06f2016-01-30 13:03:45 -060025 for tarinfo in members:
Brad Bishop34c3bf92016-08-30 20:34:51 -040026 if tarinfo.name in files:
Norman James3d6a06f2016-01-30 13:03:45 -060027 yield tarinfo
28
29
Milton Miller3fc6b792017-08-15 06:57:14 +000030def save_fw_env():
31 fw_env = "/etc/fw_env.config"
32 lines = 0
33 files=[]
34 envcfg = open(fw_env, 'r')
35 try:
36 for line in envcfg.readlines():
37 # ignore lines that are blank or start with #
38 if (line.startswith("#")): continue
39 if (not len(line.strip())): continue
40 fn = line.partition("\t")[0];
41 files.append(fn)
42 lines += 1
43 finally:
44 envcfg.close()
45 if (lines < 1 or lines > 2 or (lines == 2 and files[0] != files[1])):
46 raise Exception("Error parsing %s\n" % fw_env)
47 shutil.copyfile(files[0], os.path.join(UPDATE_PATH, "image-u-boot-env"))
48
Brad Bishop34c3bf92016-08-30 20:34:51 -040049class BmcFlashControl(DbusProperties, DbusObjectManager):
50 def __init__(self, bus, name):
Brad Bishopf47f5fa2016-09-08 22:29:01 -040051 super(BmcFlashControl, self).__init__(
52 conn=bus,
53 object_path=name)
Milton Miller0c8c5d42016-06-29 17:41:36 -050054
Brad Bishop34c3bf92016-08-30 20:34:51 -040055 self.Set(DBUS_NAME, "status", "Idle")
56 self.Set(DBUS_NAME, "filename", "")
57 self.Set(DBUS_NAME, "preserve_network_settings", True)
58 self.Set(DBUS_NAME, "restore_application_defaults", False)
59 self.Set(DBUS_NAME, "update_kernel_and_apps", False)
60 self.Set(DBUS_NAME, "clear_persistent_files", False)
61 self.Set(DBUS_NAME, "auto_apply", False)
Norman James3d6a06f2016-01-30 13:03:45 -060062
Brad Bishop34c3bf92016-08-30 20:34:51 -040063 bus.add_signal_receiver(
64 self.download_error_handler, signal_name="DownloadError")
65 bus.add_signal_receiver(
66 self.download_complete_handler, signal_name="DownloadComplete")
Milton Miller0c8c5d42016-06-29 17:41:36 -050067
Brad Bishop34c3bf92016-08-30 20:34:51 -040068 self.update_process = None
69 self.progress_name = None
Norman James3d6a06f2016-01-30 13:03:45 -060070
Brad Bishop34c3bf92016-08-30 20:34:51 -040071 @dbus.service.method(
72 DBUS_NAME, in_signature='ss', out_signature='')
73 def updateViaTftp(self, ip, filename):
74 self.Set(DBUS_NAME, "status", "Downloading")
75 self.TftpDownload(ip, filename)
Milton Miller0c8c5d42016-06-29 17:41:36 -050076
Brad Bishop34c3bf92016-08-30 20:34:51 -040077 @dbus.service.method(
78 DBUS_NAME, in_signature='s', out_signature='')
79 def update(self, filename):
80 self.Set(DBUS_NAME, "filename", filename)
81 self.download_complete_handler(filename, filename)
Chris Austenea5b4012016-05-20 16:41:40 -050082
Brad Bishop34c3bf92016-08-30 20:34:51 -040083 @dbus.service.signal(DOWNLOAD_INTF, signature='ss')
84 def TftpDownload(self, ip, filename):
85 self.Set(DBUS_NAME, "filename", filename)
86 pass
Norman James3d6a06f2016-01-30 13:03:45 -060087
Brad Bishop34c3bf92016-08-30 20:34:51 -040088 ## Signal handler
89 def download_error_handler(self, filename):
90 if (filename == self.Get(DBUS_NAME, "filename")):
91 self.Set(DBUS_NAME, "status", "Download Error")
Norman James3d6a06f2016-01-30 13:03:45 -060092
Brad Bishop34c3bf92016-08-30 20:34:51 -040093 def download_complete_handler(self, outfile, filename):
94 ## do update
95 if (filename != self.Get(DBUS_NAME, "filename")):
96 return
Norman James3d6a06f2016-01-30 13:03:45 -060097
Brad Bishop34c3bf92016-08-30 20:34:51 -040098 print "Download complete. Updating..."
Norman James3d6a06f2016-01-30 13:03:45 -060099
Brad Bishop34c3bf92016-08-30 20:34:51 -0400100 self.Set(DBUS_NAME, "status", "Download Complete")
101 copy_files = {}
Norman James3d6a06f2016-01-30 13:03:45 -0600102
Brad Bishop34c3bf92016-08-30 20:34:51 -0400103 ## determine needed files
104 if not self.Get(DBUS_NAME, "update_kernel_and_apps"):
105 copy_files["image-bmc"] = True
106 else:
107 copy_files["image-kernel"] = True
Brad Bishop34c3bf92016-08-30 20:34:51 -0400108 copy_files["image-rofs"] = True
Norman James3d6a06f2016-01-30 13:03:45 -0600109
Brad Bishop34c3bf92016-08-30 20:34:51 -0400110 if self.Get(DBUS_NAME, "restore_application_defaults"):
111 copy_files["image-rwfs"] = True
Norman James8a65a542016-02-03 19:04:44 -0600112
Brad Bishop34c3bf92016-08-30 20:34:51 -0400113 ## make sure files exist in archive
114 try:
115 tar = tarfile.open(outfile, "r")
116 files = {}
117 for f in tar.getnames():
118 files[f] = True
119 tar.close()
120 for f in copy_files.keys():
121 if f not in files:
122 raise Exception(
123 "ERROR: File not found in update archive: "+f)
Milton Millerb6cfc542016-06-17 11:43:40 -0500124
Brad Bishop34c3bf92016-08-30 20:34:51 -0400125 except Exception as e:
126 print e
127 self.Set(DBUS_NAME, "status", "Unpack Error")
128 return
Milton Millerb6cfc542016-06-17 11:43:40 -0500129
Brad Bishop34c3bf92016-08-30 20:34:51 -0400130 try:
131 tar = tarfile.open(outfile, "r")
132 tar.extractall(UPDATE_PATH, members=doExtract(tar, copy_files))
133 tar.close()
Milton Millerb6cfc542016-06-17 11:43:40 -0500134
Brad Bishop34c3bf92016-08-30 20:34:51 -0400135 if self.Get(DBUS_NAME, "clear_persistent_files"):
136 print "Removing persistent files"
137 try:
138 os.unlink(UPDATE_PATH+"/whitelist")
139 except OSError as e:
140 if (e.errno == errno.EISDIR):
141 pass
142 elif (e.errno == errno.ENOENT):
143 pass
144 else:
145 raise
Norman James3d6a06f2016-01-30 13:03:45 -0600146
Brad Bishop34c3bf92016-08-30 20:34:51 -0400147 try:
148 wldir = UPDATE_PATH + "/whitelist.d"
Milton Miller0c8c5d42016-06-29 17:41:36 -0500149
Brad Bishop34c3bf92016-08-30 20:34:51 -0400150 for file in os.listdir(wldir):
151 os.unlink(os.path.join(wldir, file))
152 except OSError as e:
153 if (e.errno == errno.EISDIR):
154 pass
155 else:
156 raise
Milton Miller0c8c5d42016-06-29 17:41:36 -0500157
Brad Bishop34c3bf92016-08-30 20:34:51 -0400158 if self.Get(DBUS_NAME, "preserve_network_settings"):
159 print "Preserving network settings"
Milton Miller3fc6b792017-08-15 06:57:14 +0000160 save_fw_env()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500161
Brad Bishop34c3bf92016-08-30 20:34:51 -0400162 except Exception as e:
163 print e
164 self.Set(DBUS_NAME, "status", "Unpack Error")
Milton Miller0c8c5d42016-06-29 17:41:36 -0500165
Brad Bishop34c3bf92016-08-30 20:34:51 -0400166 self.Verify()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500167
Brad Bishop34c3bf92016-08-30 20:34:51 -0400168 def Verify(self):
169 self.Set(DBUS_NAME, "status", "Checking Image")
170 try:
171 subprocess.check_call([
172 "/run/initramfs/update",
173 "--no-flash",
174 "--no-save-files",
175 "--no-restore-files",
176 "--no-clean-saved-files"])
Milton Miller0c8c5d42016-06-29 17:41:36 -0500177
Brad Bishop34c3bf92016-08-30 20:34:51 -0400178 self.Set(DBUS_NAME, "status", "Image ready to apply.")
179 if (self.Get(DBUS_NAME, "auto_apply")):
180 self.Apply()
181 except:
182 self.Set(DBUS_NAME, "auto_apply", False)
183 try:
184 subprocess.check_output([
185 "/run/initramfs/update",
186 "--no-flash",
187 "--ignore-mount",
188 "--no-save-files",
189 "--no-restore-files",
190 "--no-clean-saved-files"],
191 stderr=subprocess.STDOUT)
192 self.Set(
193 DBUS_NAME, "status",
194 "Deferred for mounted filesystem. reboot BMC to apply.")
195 except subprocess.CalledProcessError as e:
196 self.Set(
197 DBUS_NAME, "status", "Verify error: %s" % e.output)
198 except OSError as e:
199 self.Set(
200 DBUS_NAME, "status",
201 "Verify error: problem calling update: %s" % e.strerror)
Milton Miller0c8c5d42016-06-29 17:41:36 -0500202
Brad Bishop34c3bf92016-08-30 20:34:51 -0400203 def Cleanup(self):
204 if self.progress_name:
205 try:
206 os.unlink(self.progress_name)
207 self.progress_name = None
208 except oserror as e:
209 if e.errno == EEXIST:
210 pass
211 raise
212 self.update_process = None
213 self.Set(DBUS_NAME, "status", "Idle")
Milton Miller0c8c5d42016-06-29 17:41:36 -0500214
Brad Bishop34c3bf92016-08-30 20:34:51 -0400215 @dbus.service.method(
216 DBUS_NAME, in_signature='', out_signature='')
217 def Abort(self):
218 if self.update_process:
219 try:
220 self.update_process.kill()
221 except:
222 pass
223 for file in os.listdir(UPDATE_PATH):
224 if file.startswith('image-'):
225 os.unlink(os.path.join(UPDATE_PATH, file))
Milton Miller0c8c5d42016-06-29 17:41:36 -0500226
Brad Bishop34c3bf92016-08-30 20:34:51 -0400227 self.Cleanup()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500228
Brad Bishop34c3bf92016-08-30 20:34:51 -0400229 @dbus.service.method(
230 DBUS_NAME, in_signature='', out_signature='s')
231 def GetUpdateProgress(self):
232 msg = ""
Milton Miller0c8c5d42016-06-29 17:41:36 -0500233
Brad Bishop34c3bf92016-08-30 20:34:51 -0400234 if self.update_process and self.update_process.returncode is None:
235 self.update_process.poll()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500236
Brad Bishop34c3bf92016-08-30 20:34:51 -0400237 if (self.update_process is None):
238 pass
239 elif (self.update_process.returncode > 0):
240 self.Set(DBUS_NAME, "status", "Apply failed")
241 elif (self.update_process.returncode is None):
242 pass
243 else: # (self.update_process.returncode == 0)
244 files = ""
245 for file in os.listdir(UPDATE_PATH):
246 if file.startswith('image-'):
247 files = files + file
248 if files == "":
249 msg = "Apply Complete. Reboot to take effect."
250 else:
251 msg = "Apply Incomplete, Remaining:" + files
252 self.Set(DBUS_NAME, "status", msg)
Milton Miller0c8c5d42016-06-29 17:41:36 -0500253
Brad Bishop34c3bf92016-08-30 20:34:51 -0400254 msg = self.Get(DBUS_NAME, "status") + "\n"
255 if self.progress_name:
256 try:
257 prog = open(self.progress_name, 'r')
258 for line in prog:
259 # strip off initial sets of xxx\r here
260 # ignore crlf at the end
261 # cr will be -1 if no '\r' is found
262 cr = line.rfind("\r", 0, -2)
263 msg = msg + line[cr + 1:]
264 except OSError as e:
265 if (e.error == EEXIST):
266 pass
267 raise
268 return msg
269
270 @dbus.service.method(
271 DBUS_NAME, in_signature='', out_signature='')
272 def Apply(self):
273 progress = None
274 self.Set(DBUS_NAME, "status", "Writing images to flash")
275 try:
276 progress = tempfile.NamedTemporaryFile(
277 delete=False, prefix="progress.")
278 self.progress_name = progress.name
279 self.update_process = subprocess.Popen([
280 "/run/initramfs/update"],
281 stdout=progress.file,
282 stderr=subprocess.STDOUT)
283 except Exception as e:
284 try:
285 progress.close()
286 os.unlink(progress.name)
287 self.progress_name = None
288 except:
289 pass
290 raise
291
292 try:
293 progress.close()
294 except:
295 pass
296
297 @dbus.service.method(
298 DBUS_NAME, in_signature='', out_signature='')
299 def PrepareForUpdate(self):
300 subprocess.call([
301 "fw_setenv",
302 "openbmconce",
303 "copy-files-to-ram copy-base-filesystem-to-ram"])
Adriana Kobylak7bef82c2017-10-02 19:33:48 -0500304 # Set the variable twice so that it is written to both environments of
305 # the u-boot redundant environment variables since initramfs can only
306 # read one of the environments.
307 subprocess.call([
308 "fw_setenv",
309 "openbmconce",
310 "copy-files-to-ram copy-base-filesystem-to-ram"])
Brad Bishop34c3bf92016-08-30 20:34:51 -0400311 self.Set(DBUS_NAME, "status", "Switch to update mode in progress")
312 o = bus.get_object(BMC_DBUS_NAME, BMC_OBJ_NAME)
313 intf = dbus.Interface(o, BMC_DBUS_NAME)
314 intf.warmReset()
Milton Miller0c8c5d42016-06-29 17:41:36 -0500315
Norman James3d6a06f2016-01-30 13:03:45 -0600316
317if __name__ == '__main__':
318 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
319
Brad Bishop84e73b52016-05-12 15:57:52 -0400320 bus = get_dbus()
Norman James3d6a06f2016-01-30 13:03:45 -0600321 obj = BmcFlashControl(bus, OBJ_NAME)
322 mainloop = gobject.MainLoop()
Brad Bishopf0f3efe2016-06-29 23:20:24 -0400323
324 obj.unmask_signals()
Brad Bishop70852a32016-06-29 22:58:51 -0400325 name = dbus.service.BusName(DBUS_NAME, bus)
Brad Bishop34c3bf92016-08-30 20:34:51 -0400326
Norman James3d6a06f2016-01-30 13:03:45 -0600327 print "Running Bmc Flash Control"
328 mainloop.run()
Brad Bishop53066752016-09-21 08:48:04 -0400329
330# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4