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
1=== modified file 'lib/systemimage/generators.py'
2--- lib/systemimage/generators.py 2015-12-14 19:56:16 +0000
3+++ lib/systemimage/generators.py 2016-02-17 09:23:34 +0000
4@@ -1217,7 +1217,8 @@
5
6 if "keyring" in options:
7 if not tools.repack_recovery_keyring(conf, path,
8- options['keyring']):
9+ options['keyring'],
10+ device_name):
11 if os.path.exists(path):
12 os.remove(path)
13 return None
14
15=== modified file 'lib/systemimage/tests/test_tools.py'
16--- lib/systemimage/tests/test_tools.py 2015-12-14 19:56:16 +0000
17+++ lib/systemimage/tests/test_tools.py 2016-02-17 09:23:34 +0000
18@@ -254,6 +254,31 @@
19 os.environ['PATH'] = bin_dir
20 self.assertFalse(tools.find_on_path("program"))
21
22+ def test_manipulate_recovery_header(self):
23+ """Check if stripping and reattaching recovery headers works."""
24+ source_path = os.path.join(self.temp_directory, "source")
25+ stripped_path = os.path.join(self.temp_directory, "stripped")
26+ reattached_path = os.path.join(self.temp_directory, "reattached")
27+
28+ header = bytearray(512)
29+ contents = b"RECOVERY"
30+ for i in range(0, 64):
31+ header[i] = i
32+ with open(source_path, "wb+") as f:
33+ f.write(header)
34+ f.write(contents)
35+
36+ stripped = tools.strip_recovery_header(source_path, stripped_path)
37+ self.assertEqual(header, bytes(stripped))
38+ with open(stripped_path, "rb") as f:
39+ self.assertEqual(f.read(), contents)
40+
41+ tools.reattach_recovery_header(stripped_path, reattached_path,
42+ stripped)
43+ with open(reattached_path, "rb") as f, open(source_path, "rb") as fs:
44+ self.assertEqual(f.read(), fs.read())
45+
46+
47 @unittest.skipUnless(HAS_TEST_KEYS, MISSING_KEYS_WARNING)
48 def test_repack_recovery_keyring(self):
49 # Generate the keyring tarballs
50@@ -321,6 +346,32 @@
51 self.temp_directory,
52 "archive-master")
53
54+ tools.reattach_recovery_header(os.path.join(self.temp_directory,
55+ "initrd.gz"),
56+ os.path.join(self.temp_directory,
57+ "initrd.header"),
58+ bytearray(512))
59+
60+ with open(os.devnull, "w") as devnull:
61+ subprocess.call(["abootimg", "--create",
62+ "%s/partitions/recovery.img" %
63+ self.temp_directory,
64+ "-k", "%s/kernel" % self.temp_directory,
65+ "-r", "%s/initrd.header" % self.temp_directory,
66+ "-f", "%s/bootimg.cfg" % self.temp_directory],
67+ stderr=devnull, stdout=devnull)
68+
69+ subprocess.call(["tar", "Jcf",
70+ "%s/recovery-spec.tar.xz" % self.temp_directory,
71+ "-C", self.temp_directory,
72+ "partitions/"], stderr=devnull, stdout=devnull)
73+
74+ # Try repacking in case of a recovery with a special header
75+ tools.repack_recovery_keyring(self.config, "%s/recovery-spec.tar.xz" %
76+ self.temp_directory,
77+ "archive-master", "krillin")
78+
79+
80 def test_system_image_30_symlinks(self):
81 # To support system-image 3.0, generate symlinks for config.d
82 version_tarball = "%s/version.tar" % self.temp_directory
83@@ -476,3 +527,24 @@
84
85 six.assertCountEqual(
86 self, [base_image1, base_image2], delta_base)
87+
88+ def test_guess_file_compression(self):
89+ """Check if we can correctly guess compression algorithms."""
90+ test_string = "test-string"
91+
92+ # Simple compress/uncompress
93+ test_file = os.path.join(self.temp_directory, "test.txt")
94+ with open(test_file, "w+") as fd:
95+ fd.write(test_string)
96+
97+ self.assertIsNone(tools.guess_file_compression(test_file))
98+
99+ xz_file = os.path.join(self.temp_directory, "test.xz")
100+ tools.xz_compress(test_file, xz_file)
101+ self.assertEqual(
102+ tools.guess_file_compression(xz_file), "xz")
103+
104+ gzip_file = os.path.join(self.temp_directory, "test.gz")
105+ tools.gzip_compress(test_file, gzip_file)
106+ self.assertEqual(
107+ tools.guess_file_compression(gzip_file), "gzip")
108
109=== modified file 'lib/systemimage/tools.py'
110--- lib/systemimage/tools.py 2015-12-14 19:56:16 +0000
111+++ lib/systemimage/tools.py 2016-02-17 09:23:34 +0000
112@@ -32,6 +32,8 @@
113 from systemimage import gpg
114
115
116+READ_SIZE = 1024*1024
117+
118 logger = logging.getLogger(__name__)
119
120
121@@ -183,6 +185,27 @@
122 gpg.sign_file(config, "image-signing", path.replace(".tar.xz", ".json"))
123
124
125+def guess_file_compression(path):
126+ """
127+ Try to guess through the magic signature in the first bytes of the
128+ file.
129+ """
130+
131+ compressions = {
132+ b"\x1f\x8b\x08": "gzip",
133+ b"\xfd\x37\x7a\x58\x5a\x00": "xz"
134+ }
135+ length = max(len(x) for x in compressions)
136+
137+ with open(path, 'rb') as f:
138+ start = f.read(length)
139+ for magic, compression in compressions.items():
140+ if start.startswith(magic):
141+ return compression
142+
143+ return None
144+
145+
146 def gzip_compress(path, destination=None, level=9):
147 """
148 Compress a file (path) using gzip.
149@@ -307,9 +330,47 @@
150 mirror.ssh_key, mirror.ssh_command)
151
152
153-def repack_recovery_keyring(conf, path, keyring_name):
154+def strip_recovery_header(source_path, dest_path):
155+ """
156+ Strip the first 512 bytes of the custom header from the source_path
157+ file (initrd). Writes the stripped file to dest_path and returns the
158+ header contents.
159+ """
160+
161+ with open(source_path, "rb") as source:
162+ header_contents = source.read(512)
163+ with open(dest_path, "wb") as dest:
164+ data = source.read(READ_SIZE)
165+ while data:
166+ dest.write(data)
167+ data = source.read(READ_SIZE)
168+ return header_contents
169+
170+
171+def reattach_recovery_header(source_path, dest_path, header_contents):
172+ """
173+ Reattach the stripped header (in header_contents) in front of the
174+ source_path file contents. This writes the end file to dest_path.
175+ """
176+
177+ with open(dest_path, "wb") as dest:
178+ dest.write(header_contents)
179+ with open(source_path, "rb") as source:
180+ data = source.read(READ_SIZE)
181+ while data:
182+ dest.write(data)
183+ data = source.read(READ_SIZE)
184+
185+
186+def repack_recovery_keyring(conf, path, keyring_name, device_name=None):
187 tempdir = tempfile.mkdtemp()
188
189+ # In case of certain devices, special care of the recovery is needed
190+ additional_header = False
191+ if device_name in ("krillin", "vegetahd", "arale"):
192+ logging.debug("Expecting additional header in recovery image.")
193+ additional_header = True
194+
195 xz_uncompress(path, os.path.join(tempdir, "input.tar"))
196
197 input_tarball = tarfile.open(os.path.join(tempdir, "input.tar"), "r:")
198@@ -335,10 +396,25 @@
199 state_path = os.path.join(tempdir, "fakeroot_state")
200
201 with chdir(os.path.join(tempdir, "initrd")):
202- gzip_uncompress(os.path.join(tempdir, "img", "initrd.img"),
203- os.path.join(tempdir, "img", "initrd"))
204-
205- with open(os.path.join(tempdir, "img", "initrd"), "rb") as fd:
206+ initrdimg_path = os.path.join(tempdir, "img", "initrd.img")
207+ initrd_path = os.path.join(tempdir, "img", "initrd")
208+
209+ if additional_header:
210+ # Remove the 512 header bytes before unpacking
211+ tmp_path = os.path.join(tempdir, "img", "initrd.img.tmp")
212+ header_contents = strip_recovery_header(initrdimg_path, tmp_path)
213+ os.rename(tmp_path, initrdimg_path)
214+
215+ # The initrd can be either compressed or uncompressed
216+ compression = guess_file_compression(initrdimg_path)
217+ if compression == "gzip":
218+ gzip_uncompress(initrdimg_path, initrd_path)
219+ elif compression == "xz":
220+ xz_uncompress(initrdimg_path, initrd_path)
221+ else:
222+ shutil.copyfile(initrdimg_path, initrd_path)
223+
224+ with open(initrd_path, "rb") as fd:
225 with open(os.path.devnull, "w") as devnull:
226 subprocess.call(["fakeroot", "-s", state_path, "cpio", "-i"],
227 stdin=fd, stdout=devnull, stderr=devnull)
228@@ -346,13 +422,18 @@
229 # Swap the files
230 keyring_path = os.path.join(conf.gpg_keyring_path, keyring_name)
231
232+ # Handle two different keyring paths in the recovery
233+ dest_keyring_path = os.path.join(tempdir, "initrd", "usr", "share",
234+ "system-image", keyring_name)
235+ if not os.path.exists("%s.tar.xz" % dest_keyring_path):
236+ dest_keyring_path = os.path.join(tempdir, "initrd", "etc",
237+ "system-image", keyring_name)
238+
239 shutil.copy("%s.tar.xz" % keyring_path,
240- os.path.join(tempdir, "initrd", "usr", "share", "system-image",
241- "%s.tar.xz" % keyring_name))
242+ "%s.tar.xz" % dest_keyring_path)
243
244 shutil.copy("%s.tar.xz.asc" % keyring_path,
245- os.path.join(tempdir, "initrd", "usr", "share", "system-image",
246- "%s.tar.xz.asc" % keyring_name))
247+ "%s.tar.xz.asc" % dest_keyring_path)
248
249 # Re-generate the initrd
250 with chdir(os.path.join(tempdir, "initrd")):
251@@ -367,8 +448,19 @@
252
253 os.rename(os.path.join(tempdir, "img", "initrd.img"),
254 os.path.join(tempdir, "img", "initrd.img.bak"))
255- gzip_compress(os.path.join(tempdir, "img", "initrd"),
256- os.path.join(tempdir, "img", "initrd.img"))
257+
258+ if compression == "gzip":
259+ gzip_compress(initrd_path, initrdimg_path)
260+ elif compression == "xz":
261+ xz_compress(initrd_path, initrdimg_path)
262+ else:
263+ shutil.copyfile(initrd_path, initrdimg_path)
264+
265+ if additional_header:
266+ # Append the previously removed header
267+ tmp_path = os.path.join(tempdir, "img", "initrd.img.tmp")
268+ reattach_recovery_header(initrdimg_path, tmp_path, header_contents)
269+ os.rename(tmp_path, initrdimg_path)
270
271 # Rewrite bootimg.cfg
272 content = ""

Subscribers

People subscribed via source and target branches