Merge lp:~sil2100/ubuntu-system-image/server_uncompressed-initrd into lp:ubuntu-system-image/server

Proposed by Łukasz Zemczak
Status: Merged
Merged at revision: 284
Proposed branch: lp:~sil2100/ubuntu-system-image/server_uncompressed-initrd
Merge into: lp:ubuntu-system-image/server
Diff against target: 272 lines (+177/-12)
3 files modified
lib/systemimage/generators.py (+2/-1)
lib/systemimage/tests/test_tools.py (+72/-0)
lib/systemimage/tools.py (+103/-11)
To merge this branch: bzr merge lp:~sil2100/ubuntu-system-image/server_uncompressed-initrd
Reviewer Review Type Date Requested Status
Barry Warsaw (community) Approve
Review via email: mp+285374@code.launchpad.net

Commit message

Add support for uncompressed/xz-compressed initrd images in our device tarballs. Add additional header stripping to specific devices while repacking the keyring as the initrd have custom 512 bytes at the beginning. Handle the old s-i 2 keyring paths in the recovery.

Description of the change

Add support for uncompressed/xz-compressed initrd images in our device tarballs. Add additional header stripping to specific devices while repacking the keyring as the initrd have custom 512 bytes at the beginning. Handle the old s-i 2 keyring paths in the recovery.

This merge basically has 3 fixes, all related more or less to eachother - all required to fix repacking of keyrings for devices such as krillin or arale.

To post a comment you must log in.
Revision history for this message
Łukasz Zemczak (sil2100) wrote :

Now it's ready for review basically.

Revision history for this message
Barry Warsaw (barry) wrote :

Just a few thoughts.

Revision history for this message
Łukasz Zemczak (sil2100) wrote :

Hey Barry! Thanks for the code-check. I didn't use subprocess.DEVNULL for two reasons: first, I didn't know about it, and second - the existing code was using open(os.devnull). Would have to change it in the whole testcase then - which we can do of course.

Revision history for this message
Barry Warsaw (barry) wrote :

On Feb 12, 2016, at 03:10 PM, Łukasz Zemczak wrote:

>Hey Barry! Thanks for the code-check. I didn't use subprocess.DEVNULL for two
>reasons: first, I didn't know about it, and second - the existing code was
>using open(os.devnull). Would have to change it in the whole testcase then -
>which we can do of course.

Reducing tech-debt would make for a nice sprint task! :)

Revision history for this message
Łukasz Zemczak (sil2100) wrote :

Anyway, I'll leave it as is right now since from what I see there's no subprocess.DEVNULL in Python 2.7, while system-image is currently written to be both 2.7 and 3 compatible. os.devnull seems to be the safe choice here.

That being said: the branch in overall got tested by Bhushan Shah (bshah) who reproduced the original issue and said it helped. If you could +1 the code in overall I would gladly merge it in.

Revision history for this message
Barry Warsaw (barry) wrote :

A few comments inlined, but other than that, +1

review: Approve
290. By Łukasz Zemczak

Changes as per Barry's comments - make the buffer size variable a global constant and use assertIsNone() when it makes sense.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/systemimage/generators.py'
--- lib/systemimage/generators.py 2015-12-14 19:56:16 +0000
+++ lib/systemimage/generators.py 2016-02-17 09:23:34 +0000
@@ -1217,7 +1217,8 @@
12171217
1218 if "keyring" in options:1218 if "keyring" in options:
1219 if not tools.repack_recovery_keyring(conf, path,1219 if not tools.repack_recovery_keyring(conf, path,
1220 options['keyring']):1220 options['keyring'],
1221 device_name):
1221 if os.path.exists(path):1222 if os.path.exists(path):
1222 os.remove(path)1223 os.remove(path)
1223 return None1224 return None
12241225
=== modified file 'lib/systemimage/tests/test_tools.py'
--- lib/systemimage/tests/test_tools.py 2015-12-14 19:56:16 +0000
+++ lib/systemimage/tests/test_tools.py 2016-02-17 09:23:34 +0000
@@ -254,6 +254,31 @@
254 os.environ['PATH'] = bin_dir254 os.environ['PATH'] = bin_dir
255 self.assertFalse(tools.find_on_path("program"))255 self.assertFalse(tools.find_on_path("program"))
256256
257 def test_manipulate_recovery_header(self):
258 """Check if stripping and reattaching recovery headers works."""
259 source_path = os.path.join(self.temp_directory, "source")
260 stripped_path = os.path.join(self.temp_directory, "stripped")
261 reattached_path = os.path.join(self.temp_directory, "reattached")
262
263 header = bytearray(512)
264 contents = b"RECOVERY"
265 for i in range(0, 64):
266 header[i] = i
267 with open(source_path, "wb+") as f:
268 f.write(header)
269 f.write(contents)
270
271 stripped = tools.strip_recovery_header(source_path, stripped_path)
272 self.assertEqual(header, bytes(stripped))
273 with open(stripped_path, "rb") as f:
274 self.assertEqual(f.read(), contents)
275
276 tools.reattach_recovery_header(stripped_path, reattached_path,
277 stripped)
278 with open(reattached_path, "rb") as f, open(source_path, "rb") as fs:
279 self.assertEqual(f.read(), fs.read())
280
281
257 @unittest.skipUnless(HAS_TEST_KEYS, MISSING_KEYS_WARNING)282 @unittest.skipUnless(HAS_TEST_KEYS, MISSING_KEYS_WARNING)
258 def test_repack_recovery_keyring(self):283 def test_repack_recovery_keyring(self):
259 # Generate the keyring tarballs284 # Generate the keyring tarballs
@@ -321,6 +346,32 @@
321 self.temp_directory,346 self.temp_directory,
322 "archive-master")347 "archive-master")
323348
349 tools.reattach_recovery_header(os.path.join(self.temp_directory,
350 "initrd.gz"),
351 os.path.join(self.temp_directory,
352 "initrd.header"),
353 bytearray(512))
354
355 with open(os.devnull, "w") as devnull:
356 subprocess.call(["abootimg", "--create",
357 "%s/partitions/recovery.img" %
358 self.temp_directory,
359 "-k", "%s/kernel" % self.temp_directory,
360 "-r", "%s/initrd.header" % self.temp_directory,
361 "-f", "%s/bootimg.cfg" % self.temp_directory],
362 stderr=devnull, stdout=devnull)
363
364 subprocess.call(["tar", "Jcf",
365 "%s/recovery-spec.tar.xz" % self.temp_directory,
366 "-C", self.temp_directory,
367 "partitions/"], stderr=devnull, stdout=devnull)
368
369 # Try repacking in case of a recovery with a special header
370 tools.repack_recovery_keyring(self.config, "%s/recovery-spec.tar.xz" %
371 self.temp_directory,
372 "archive-master", "krillin")
373
374
324 def test_system_image_30_symlinks(self):375 def test_system_image_30_symlinks(self):
325 # To support system-image 3.0, generate symlinks for config.d376 # To support system-image 3.0, generate symlinks for config.d
326 version_tarball = "%s/version.tar" % self.temp_directory377 version_tarball = "%s/version.tar" % self.temp_directory
@@ -476,3 +527,24 @@
476527
477 six.assertCountEqual(528 six.assertCountEqual(
478 self, [base_image1, base_image2], delta_base)529 self, [base_image1, base_image2], delta_base)
530
531 def test_guess_file_compression(self):
532 """Check if we can correctly guess compression algorithms."""
533 test_string = "test-string"
534
535 # Simple compress/uncompress
536 test_file = os.path.join(self.temp_directory, "test.txt")
537 with open(test_file, "w+") as fd:
538 fd.write(test_string)
539
540 self.assertIsNone(tools.guess_file_compression(test_file))
541
542 xz_file = os.path.join(self.temp_directory, "test.xz")
543 tools.xz_compress(test_file, xz_file)
544 self.assertEqual(
545 tools.guess_file_compression(xz_file), "xz")
546
547 gzip_file = os.path.join(self.temp_directory, "test.gz")
548 tools.gzip_compress(test_file, gzip_file)
549 self.assertEqual(
550 tools.guess_file_compression(gzip_file), "gzip")
479551
=== modified file 'lib/systemimage/tools.py'
--- lib/systemimage/tools.py 2015-12-14 19:56:16 +0000
+++ lib/systemimage/tools.py 2016-02-17 09:23:34 +0000
@@ -32,6 +32,8 @@
32from systemimage import gpg32from systemimage import gpg
3333
3434
35READ_SIZE = 1024*1024
36
35logger = logging.getLogger(__name__)37logger = logging.getLogger(__name__)
3638
3739
@@ -183,6 +185,27 @@
183 gpg.sign_file(config, "image-signing", path.replace(".tar.xz", ".json"))185 gpg.sign_file(config, "image-signing", path.replace(".tar.xz", ".json"))
184186
185187
188def guess_file_compression(path):
189 """
190 Try to guess through the magic signature in the first bytes of the
191 file.
192 """
193
194 compressions = {
195 b"\x1f\x8b\x08": "gzip",
196 b"\xfd\x37\x7a\x58\x5a\x00": "xz"
197 }
198 length = max(len(x) for x in compressions)
199
200 with open(path, 'rb') as f:
201 start = f.read(length)
202 for magic, compression in compressions.items():
203 if start.startswith(magic):
204 return compression
205
206 return None
207
208
186def gzip_compress(path, destination=None, level=9):209def gzip_compress(path, destination=None, level=9):
187 """210 """
188 Compress a file (path) using gzip.211 Compress a file (path) using gzip.
@@ -307,9 +330,47 @@
307 mirror.ssh_key, mirror.ssh_command)330 mirror.ssh_key, mirror.ssh_command)
308331
309332
310def repack_recovery_keyring(conf, path, keyring_name):333def strip_recovery_header(source_path, dest_path):
334 """
335 Strip the first 512 bytes of the custom header from the source_path
336 file (initrd). Writes the stripped file to dest_path and returns the
337 header contents.
338 """
339
340 with open(source_path, "rb") as source:
341 header_contents = source.read(512)
342 with open(dest_path, "wb") as dest:
343 data = source.read(READ_SIZE)
344 while data:
345 dest.write(data)
346 data = source.read(READ_SIZE)
347 return header_contents
348
349
350def reattach_recovery_header(source_path, dest_path, header_contents):
351 """
352 Reattach the stripped header (in header_contents) in front of the
353 source_path file contents. This writes the end file to dest_path.
354 """
355
356 with open(dest_path, "wb") as dest:
357 dest.write(header_contents)
358 with open(source_path, "rb") as source:
359 data = source.read(READ_SIZE)
360 while data:
361 dest.write(data)
362 data = source.read(READ_SIZE)
363
364
365def repack_recovery_keyring(conf, path, keyring_name, device_name=None):
311 tempdir = tempfile.mkdtemp()366 tempdir = tempfile.mkdtemp()
312367
368 # In case of certain devices, special care of the recovery is needed
369 additional_header = False
370 if device_name in ("krillin", "vegetahd", "arale"):
371 logging.debug("Expecting additional header in recovery image.")
372 additional_header = True
373
313 xz_uncompress(path, os.path.join(tempdir, "input.tar"))374 xz_uncompress(path, os.path.join(tempdir, "input.tar"))
314375
315 input_tarball = tarfile.open(os.path.join(tempdir, "input.tar"), "r:")376 input_tarball = tarfile.open(os.path.join(tempdir, "input.tar"), "r:")
@@ -335,10 +396,25 @@
335 state_path = os.path.join(tempdir, "fakeroot_state")396 state_path = os.path.join(tempdir, "fakeroot_state")
336397
337 with chdir(os.path.join(tempdir, "initrd")):398 with chdir(os.path.join(tempdir, "initrd")):
338 gzip_uncompress(os.path.join(tempdir, "img", "initrd.img"),399 initrdimg_path = os.path.join(tempdir, "img", "initrd.img")
339 os.path.join(tempdir, "img", "initrd"))400 initrd_path = os.path.join(tempdir, "img", "initrd")
340401
341 with open(os.path.join(tempdir, "img", "initrd"), "rb") as fd:402 if additional_header:
403 # Remove the 512 header bytes before unpacking
404 tmp_path = os.path.join(tempdir, "img", "initrd.img.tmp")
405 header_contents = strip_recovery_header(initrdimg_path, tmp_path)
406 os.rename(tmp_path, initrdimg_path)
407
408 # The initrd can be either compressed or uncompressed
409 compression = guess_file_compression(initrdimg_path)
410 if compression == "gzip":
411 gzip_uncompress(initrdimg_path, initrd_path)
412 elif compression == "xz":
413 xz_uncompress(initrdimg_path, initrd_path)
414 else:
415 shutil.copyfile(initrdimg_path, initrd_path)
416
417 with open(initrd_path, "rb") as fd:
342 with open(os.path.devnull, "w") as devnull:418 with open(os.path.devnull, "w") as devnull:
343 subprocess.call(["fakeroot", "-s", state_path, "cpio", "-i"],419 subprocess.call(["fakeroot", "-s", state_path, "cpio", "-i"],
344 stdin=fd, stdout=devnull, stderr=devnull)420 stdin=fd, stdout=devnull, stderr=devnull)
@@ -346,13 +422,18 @@
346 # Swap the files422 # Swap the files
347 keyring_path = os.path.join(conf.gpg_keyring_path, keyring_name)423 keyring_path = os.path.join(conf.gpg_keyring_path, keyring_name)
348424
425 # Handle two different keyring paths in the recovery
426 dest_keyring_path = os.path.join(tempdir, "initrd", "usr", "share",
427 "system-image", keyring_name)
428 if not os.path.exists("%s.tar.xz" % dest_keyring_path):
429 dest_keyring_path = os.path.join(tempdir, "initrd", "etc",
430 "system-image", keyring_name)
431
349 shutil.copy("%s.tar.xz" % keyring_path,432 shutil.copy("%s.tar.xz" % keyring_path,
350 os.path.join(tempdir, "initrd", "usr", "share", "system-image",433 "%s.tar.xz" % dest_keyring_path)
351 "%s.tar.xz" % keyring_name))
352434
353 shutil.copy("%s.tar.xz.asc" % keyring_path,435 shutil.copy("%s.tar.xz.asc" % keyring_path,
354 os.path.join(tempdir, "initrd", "usr", "share", "system-image",436 "%s.tar.xz.asc" % dest_keyring_path)
355 "%s.tar.xz.asc" % keyring_name))
356437
357 # Re-generate the initrd438 # Re-generate the initrd
358 with chdir(os.path.join(tempdir, "initrd")):439 with chdir(os.path.join(tempdir, "initrd")):
@@ -367,8 +448,19 @@
367448
368 os.rename(os.path.join(tempdir, "img", "initrd.img"),449 os.rename(os.path.join(tempdir, "img", "initrd.img"),
369 os.path.join(tempdir, "img", "initrd.img.bak"))450 os.path.join(tempdir, "img", "initrd.img.bak"))
370 gzip_compress(os.path.join(tempdir, "img", "initrd"),451
371 os.path.join(tempdir, "img", "initrd.img"))452 if compression == "gzip":
453 gzip_compress(initrd_path, initrdimg_path)
454 elif compression == "xz":
455 xz_compress(initrd_path, initrdimg_path)
456 else:
457 shutil.copyfile(initrd_path, initrdimg_path)
458
459 if additional_header:
460 # Append the previously removed header
461 tmp_path = os.path.join(tempdir, "img", "initrd.img.tmp")
462 reattach_recovery_header(initrdimg_path, tmp_path, header_contents)
463 os.rename(tmp_path, initrdimg_path)
372464
373 # Rewrite bootimg.cfg465 # Rewrite bootimg.cfg
374 content = ""466 content = ""

Subscribers

People subscribed via source and target branches