blob: b950a02047241cfe838f6b9b04ea182f4d4e3c85 [file] [log] [blame]
Xiaochu Liudeed0232018-06-26 10:25:34 -07001# -*- coding: utf-8 -*-
2# Copyright 2018 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
Xiaochu Liudeed0232018-06-26 10:25:34 -07005"""Script to generate a DLC (Downloadable Content) artifact."""
6
Mike Frysinger93e8ffa2019-07-03 20:24:18 -04007from __future__ import division
Xiaochu Liudeed0232018-06-26 10:25:34 -07008from __future__ import print_function
9
10import hashlib
11import json
12import math
13import os
Amin Hassanibc1a4792019-10-24 14:39:57 -070014import re
Amin Hassani11a88cf2019-01-29 15:31:24 -080015import shutil
Xiaochu Liudeed0232018-06-26 10:25:34 -070016
17from chromite.lib import commandline
18from chromite.lib import cros_build_lib
Amin Hassanib97a5ee2019-01-23 14:44:43 -080019from chromite.lib import cros_logging as logging
Xiaochu Liudeed0232018-06-26 10:25:34 -070020from chromite.lib import osutils
21
Amin Hassani8f1cc0f2019-03-06 15:34:53 -080022from chromite.scripts import cros_set_lsb_release
Xiaochu Liudeed0232018-06-26 10:25:34 -070023
Amin Hassani2af75a92019-01-22 21:07:45 -080024DLC_META_DIR = 'opt/google/dlc/'
Andrew67b5fa72020-02-05 14:14:48 -080025DLC_TMP_META_DIR = 'meta'
26DLC_BUILD_DIR = 'build/rootfs/dlc/'
Amin Hassanid5742d32019-01-22 21:13:34 -080027LSB_RELEASE = 'etc/lsb-release'
Jae Hoon Kim5f411e42020-01-09 13:30:56 -080028DLC_IMAGE = 'dlc.img'
29IMAGELOADER_JSON = 'imageloader.json'
Andrew67b5fa72020-02-05 14:14:48 -080030EBUILD_PARAMETERS = 'ebuild_parameters.json'
Amin Hassanid5742d32019-01-22 21:13:34 -080031
Amin Hassani11a88cf2019-01-29 15:31:24 -080032# This file has major and minor version numbers that the update_engine client
33# supports. These values are needed for generating a delta/full payload.
34UPDATE_ENGINE_CONF = 'etc/update_engine.conf'
35
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -080036_EXTRA_RESOURCES = (UPDATE_ENGINE_CONF,)
Amin Hassani11a88cf2019-01-29 15:31:24 -080037
Amin Hassanid5742d32019-01-22 21:13:34 -080038DLC_ID_KEY = 'DLC_ID'
Amin Hassanib5a48042019-03-18 14:30:51 -070039DLC_PACKAGE_KEY = 'DLC_PACKAGE'
Amin Hassanid5742d32019-01-22 21:13:34 -080040DLC_NAME_KEY = 'DLC_NAME'
Amin Hassani8f1cc0f2019-03-06 15:34:53 -080041DLC_APPID_KEY = 'DLC_RELEASE_APPID'
Amin Hassani2af75a92019-01-22 21:07:45 -080042
Amin Hassani22a25eb2019-01-11 14:25:02 -080043_SQUASHFS_TYPE = 'squashfs'
44_EXT4_TYPE = 'ext4'
45
Amin Hassanibc1a4792019-10-24 14:39:57 -070046MAX_ID_NAME = 40
Amin Hassanid5742d32019-01-22 21:13:34 -080047
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -080048
Xiaochu Liudeed0232018-06-26 10:25:34 -070049def HashFile(file_path):
50 """Calculate the sha256 hash of a file.
51
52 Args:
53 file_path: (str) path to the file.
54
55 Returns:
56 [str]: The sha256 hash of the file.
57 """
58 sha256 = hashlib.sha256()
59 with open(file_path, 'rb') as f:
60 for b in iter(lambda: f.read(2048), b''):
61 sha256.update(b)
62 return sha256.hexdigest()
63
64
Jae Hoon Kim5f411e42020-01-09 13:30:56 -080065def GetValueInJsonFile(json_path, key, default_value=None):
66 """Reads file containing JSON and returns value or default_value for key.
67
68 Args:
69 json_path: (str) File containing JSON.
70 key: (str) The desired key to lookup.
71 default_value: (default:None) The default value returned in case of missing
72 key.
73 """
74 with open(json_path) as fd:
75 return json.load(fd).get(key, default_value)
76
77
Andrew67b5fa72020-02-05 14:14:48 -080078class EbuildParams(object):
79 """Object to store and retrieve DLC ebuild parameters.
80
81 Attributes:
82 dlc_id: (str) DLC ID.
83 dlc_package: (str) DLC package.
84 fs_type: (str) file system type.
85 pre_allocated_blocks: (int) number of blocks pre-allocated on device.
86 version: (str) DLC version.
87 name: (str) DLC name.
88 preload: (bool) allow for preloading DLC.
89 """
90
91 def __init__(self, dlc_id, dlc_package, fs_type, pre_allocated_blocks,
92 version, name, preload):
93 self.dlc_id = dlc_id
94 self.dlc_package = dlc_package
95 self.fs_type = fs_type
96 self.pre_allocated_blocks = pre_allocated_blocks
97 self.version = version
98 self.name = name
99 self.preload = preload
100
101 def StoreDlcParameters(self, install_root_dir, sudo):
102 """Store DLC parameters defined in the ebuild.
103
104 Store DLC parameters defined in the ebuild in a temporary file so they can
105 be retrieved in the build_image phase.
106
107 Args:
108 install_root_dir: (str) The path to the root installation directory.
109 sudo: (bool) Use sudo to write the file.
110 """
111 ebuild_params_path = EbuildParams.GetParamsPath(install_root_dir,
112 self.dlc_id,
113 self.dlc_package)
114 osutils.WriteFile(ebuild_params_path,
115 json.dumps(self.__dict__),
116 makedirs=True, sudo=sudo)
117
118 @staticmethod
119 def GetParamsPath(install_root_dir, dlc_id, dlc_package):
120 """Get the path to the file storing the ebuild parameters.
121
122 Args:
123 install_root_dir: (str) The path to the root installation directory.
124 dlc_id: (str) DLC ID.
125 dlc_package: (str) DLC package.
126
127 Returns:
128 [str]: Path to |EBUILD_PARAMETERS|.
129 """
130 return os.path.join(install_root_dir, DLC_BUILD_DIR, dlc_id, dlc_package,
131 EBUILD_PARAMETERS)
132
133 @classmethod
Jae Hoon Kim264b8d22020-04-06 11:49:19 -0700134 def LoadEbuildParams(cls, sysroot, dlc_id, dlc_package):
Andrew67b5fa72020-02-05 14:14:48 -0800135 """Read the stored ebuild parameters file and return a class instance.
136
137 Args:
138 dlc_id: (str) DLC ID.
139 dlc_package: (str) DLC package.
140 sysroot: (str) The path to the build root directory.
141
142 Returns:
143 [bool] : True if |ebuild_params_path| exists, False otherwise.
144 """
145 path = cls.GetParamsPath(sysroot, dlc_id, dlc_package)
146 if not os.path.exists(path):
147 return None
148
149 with open(path) as fp:
150 return cls(**json.load(fp))
151
152 def __str__(self):
153 return str(self.__dict__)
154
155
Amin Hassani174eb7e2019-01-18 11:11:24 -0800156class DlcGenerator(object):
Xiaochu Liudeed0232018-06-26 10:25:34 -0700157 """Object to generate DLC artifacts."""
158 # Block size for the DLC image.
159 # We use 4K for various reasons:
160 # 1. it's what imageloader (linux kernel) supports.
161 # 2. it's what verity supports.
162 _BLOCK_SIZE = 4096
163 # Blocks in the initial sparse image.
164 _BLOCKS = 500000
165 # Version of manifest file.
166 _MANIFEST_VERSION = 1
167
Amin Hassanicc7ffce2019-01-11 14:57:52 -0800168 # The DLC root path inside the DLC module.
169 _DLC_ROOT_DIR = 'root'
170
Andrew67b5fa72020-02-05 14:14:48 -0800171 def __init__(self, ebuild_params, sysroot, install_root_dir, src_dir=None):
Xiaochu Liudeed0232018-06-26 10:25:34 -0700172 """Object initializer.
173
174 Args:
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800175 sysroot: (str) The path to the build root directory.
Amin Hassani2af75a92019-01-22 21:07:45 -0800176 install_root_dir: (str) The path to the root installation directory.
Andrew67b5fa72020-02-05 14:14:48 -0800177 ebuild_params: (EbuildParams) Ebuild variables.
178 src_dir: (str) Optional path to the DLC source root directory. When None,
179 the default directory in |DLC_BUILD_DIR| is used.
Xiaochu Liudeed0232018-06-26 10:25:34 -0700180 """
Andrew67b5fa72020-02-05 14:14:48 -0800181 # Use a temporary directory to avoid having to use sudo every time we write
182 # into the build directory.
183 self.temp_root = osutils.TempDir(prefix='dlc', sudo_rm=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700184 self.src_dir = src_dir
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800185 self.sysroot = sysroot
Amin Hassani2af75a92019-01-22 21:07:45 -0800186 self.install_root_dir = install_root_dir
Andrew67b5fa72020-02-05 14:14:48 -0800187 self.ebuild_params = ebuild_params
188 # If the client is not overriding the src_dir, use the default one.
189 if not self.src_dir:
190 self.src_dir = os.path.join(self.sysroot, DLC_BUILD_DIR,
191 self.ebuild_params.dlc_id,
192 self.ebuild_params.dlc_package,
193 self._DLC_ROOT_DIR)
Amin Hassani2af75a92019-01-22 21:07:45 -0800194
Andrew67b5fa72020-02-05 14:14:48 -0800195 self.image_dir = os.path.join(self.temp_root.tempdir,
196 DLC_BUILD_DIR,
197 self.ebuild_params.dlc_id,
198 self.ebuild_params.dlc_package)
199
200 self.meta_dir = os.path.join(self.image_dir, DLC_TMP_META_DIR)
Amin Hassani2af75a92019-01-22 21:07:45 -0800201
Xiaochu Liudeed0232018-06-26 10:25:34 -0700202 # Create path for all final artifacts.
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800203 self.dest_image = os.path.join(self.image_dir, DLC_IMAGE)
Amin Hassani2af75a92019-01-22 21:07:45 -0800204 self.dest_table = os.path.join(self.meta_dir, 'table')
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800205 self.dest_imageloader_json = os.path.join(self.meta_dir, IMAGELOADER_JSON)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700206
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700207 # Log out the member variable values initially set.
208 logging.debug('Initial internal values of DlcGenerator: %s',
Andrew67b5fa72020-02-05 14:14:48 -0800209 repr({k:str(i) for k, i in self.__dict__.items()}))
210
211 def CopyTempContentsToBuildDir(self):
212 """Copy the temp files to the build directory using sudo."""
213 logging.info('Copy files from temporary directory (%s) to build directory '
214 '(%s).', self.temp_root.tempdir.rstrip('/') + '/.',
215 self.install_root_dir)
216 cros_build_lib.sudo_run(['cp', '-dR',
217 self.temp_root.tempdir.rstrip('/') + '/.',
218 self.install_root_dir])
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700219
Xiaochu Liudeed0232018-06-26 10:25:34 -0700220 def SquashOwnerships(self, path):
221 """Squash the owernships & permissions for files.
222
223 Args:
224 path: (str) path that contains all files to be processed.
225 """
Mike Frysinger45602c72019-09-22 02:15:11 -0400226 cros_build_lib.sudo_run(['chown', '-R', '0:0', path])
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800227 cros_build_lib.sudo_run([
228 'find', path, '-exec', 'touch', '-h', '-t', '197001010000.00', '{}', '+'
229 ])
Xiaochu Liudeed0232018-06-26 10:25:34 -0700230
231 def CreateExt4Image(self):
232 """Create an ext4 image."""
233 with osutils.TempDir(prefix='dlc_') as temp_dir:
234 mount_point = os.path.join(temp_dir, 'mount_point')
Andrew67b5fa72020-02-05 14:14:48 -0800235 # Create the directory where the image is located if it doesn't exist.
236 osutils.SafeMakedirs(os.path.split(self.dest_image)[0])
Xiaochu Liudeed0232018-06-26 10:25:34 -0700237 # Create a raw image file.
238 with open(self.dest_image, 'w') as f:
239 f.truncate(self._BLOCKS * self._BLOCK_SIZE)
240 # Create an ext4 file system on the raw image.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800241 cros_build_lib.run([
242 '/sbin/mkfs.ext4', '-b',
243 str(self._BLOCK_SIZE), '-O', '^has_journal', self.dest_image
244 ],
245 capture_output=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700246 # Create the mount_point directory.
247 osutils.SafeMakedirs(mount_point)
248 # Mount the ext4 image.
249 osutils.MountDir(self.dest_image, mount_point, mount_opts=('loop', 'rw'))
Amin Hassanicc7ffce2019-01-11 14:57:52 -0800250
Xiaochu Liudeed0232018-06-26 10:25:34 -0700251 try:
Amin Hassani11a88cf2019-01-29 15:31:24 -0800252 self.SetupDlcImageFiles(mount_point)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700253 finally:
254 # Unmount the ext4 image.
255 osutils.UmountDir(mount_point)
256 # Shrink to minimum size.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800257 cros_build_lib.run(['/sbin/e2fsck', '-y', '-f', self.dest_image],
258 capture_output=True)
259 cros_build_lib.run(['/sbin/resize2fs', '-M', self.dest_image],
260 capture_output=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700261
262 def CreateSquashfsImage(self):
263 """Create a squashfs image."""
264 with osutils.TempDir(prefix='dlc_') as temp_dir:
Amin Hassani22a25eb2019-01-11 14:25:02 -0800265 squashfs_root = os.path.join(temp_dir, 'squashfs-root')
Amin Hassani11a88cf2019-01-29 15:31:24 -0800266 self.SetupDlcImageFiles(squashfs_root)
Amin Hassani22a25eb2019-01-11 14:25:02 -0800267
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800268 cros_build_lib.run([
269 'mksquashfs', squashfs_root, self.dest_image, '-4k-align', '-noappend'
270 ],
271 capture_output=True)
Amin Hassani22a25eb2019-01-11 14:25:02 -0800272
273 # We changed the ownership and permissions of the squashfs_root
274 # directory. Now we need to remove it manually.
275 osutils.RmDir(squashfs_root, sudo=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700276
Amin Hassani11a88cf2019-01-29 15:31:24 -0800277 def SetupDlcImageFiles(self, dlc_dir):
278 """Prepares the directory dlc_dir with all the files a DLC needs.
279
280 Args:
281 dlc_dir: (str) The path to where to setup files inside the DLC.
282 """
283 dlc_root_dir = os.path.join(dlc_dir, self._DLC_ROOT_DIR)
284 osutils.SafeMakedirs(dlc_root_dir)
Jae Hoon Kimd14646b2019-08-21 14:49:26 -0700285 osutils.CopyDirContents(self.src_dir, dlc_root_dir, symlinks=True)
Amin Hassani11a88cf2019-01-29 15:31:24 -0800286 self.PrepareLsbRelease(dlc_dir)
287 self.CollectExtraResources(dlc_dir)
288 self.SquashOwnerships(dlc_dir)
289
Amin Hassanid5742d32019-01-22 21:13:34 -0800290 def PrepareLsbRelease(self, dlc_dir):
291 """Prepare the file /etc/lsb-release in the DLC module.
292
293 This file is used dropping some identification parameters for the DLC.
294
295 Args:
296 dlc_dir: (str) The path to root directory of the DLC. e.g. mounted point
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800297 when we are creating the image.
Amin Hassanid5742d32019-01-22 21:13:34 -0800298 """
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800299 # Reading the platform APPID and creating the DLC APPID.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800300 platform_lsb_release = osutils.ReadFile(
301 os.path.join(self.sysroot, LSB_RELEASE))
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800302 app_id = None
303 for line in platform_lsb_release.split('\n'):
304 if line.startswith(cros_set_lsb_release.LSB_KEY_APPID_RELEASE):
305 app_id = line.split('=')[1]
306 if app_id is None:
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800307 raise Exception(
308 '%s does not have a valid key %s' %
309 (platform_lsb_release, cros_set_lsb_release.LSB_KEY_APPID_RELEASE))
Amin Hassanid5742d32019-01-22 21:13:34 -0800310
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400311 fields = (
Andrew67b5fa72020-02-05 14:14:48 -0800312 (DLC_ID_KEY, self.ebuild_params.dlc_id),
313 (DLC_PACKAGE_KEY, self.ebuild_params.dlc_package),
314 (DLC_NAME_KEY, self.ebuild_params.name),
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800315 # The DLC appid is generated by concatenating the platform appid with
316 # the DLC ID using an underscore. This pattern should never be changed
317 # once set otherwise it can break a lot of things!
Andrew67b5fa72020-02-05 14:14:48 -0800318 (DLC_APPID_KEY, '%s_%s' % (app_id, self.ebuild_params.dlc_id)),
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400319 )
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800320
321 lsb_release = os.path.join(dlc_dir, LSB_RELEASE)
322 osutils.SafeMakedirs(os.path.dirname(lsb_release))
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400323 content = ''.join('%s=%s\n' % (k, v) for k, v in fields)
Amin Hassanid5742d32019-01-22 21:13:34 -0800324 osutils.WriteFile(lsb_release, content)
325
Amin Hassani11a88cf2019-01-29 15:31:24 -0800326 def CollectExtraResources(self, dlc_dir):
327 """Collect the extra resources needed by the DLC module.
328
329 Look at the documentation around _EXTRA_RESOURCES.
330
331 Args:
332 dlc_dir: (str) The path to root directory of the DLC. e.g. mounted point
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800333 when we are creating the image.
Amin Hassani11a88cf2019-01-29 15:31:24 -0800334 """
335 for r in _EXTRA_RESOURCES:
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800336 source_path = os.path.join(self.sysroot, r)
Amin Hassani11a88cf2019-01-29 15:31:24 -0800337 target_path = os.path.join(dlc_dir, r)
338 osutils.SafeMakedirs(os.path.dirname(target_path))
339 shutil.copyfile(source_path, target_path)
340
Xiaochu Liudeed0232018-06-26 10:25:34 -0700341 def CreateImage(self):
342 """Create the image and copy the DLC files to it."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700343 logging.info('Creating the DLC image.')
Andrew67b5fa72020-02-05 14:14:48 -0800344 if self.ebuild_params.fs_type == _EXT4_TYPE:
Xiaochu Liudeed0232018-06-26 10:25:34 -0700345 self.CreateExt4Image()
Andrew67b5fa72020-02-05 14:14:48 -0800346 elif self.ebuild_params.fs_type == _SQUASHFS_TYPE:
Xiaochu Liudeed0232018-06-26 10:25:34 -0700347 self.CreateSquashfsImage()
348 else:
Andrew67b5fa72020-02-05 14:14:48 -0800349 raise ValueError('Wrong fs type: %s used:' % self.ebuild_params.fs_type)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700350
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700351 def VerifyImageSize(self):
Xiaochu Liu36b30592019-08-06 09:39:54 -0700352 """Verify the image can fit to the reserved file."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700353 logging.info('Verifying the DLC image size.')
354 image_bytes = os.path.getsize(self.dest_image)
Andrew67b5fa72020-02-05 14:14:48 -0800355 preallocated_bytes = (self.ebuild_params.pre_allocated_blocks *
356 self._BLOCK_SIZE)
Xiaochu Liu36b30592019-08-06 09:39:54 -0700357 # Verifies the actual size of the DLC image is NOT smaller than the
358 # preallocated space.
359 if preallocated_bytes < image_bytes:
360 raise ValueError(
361 'The DLC_PREALLOC_BLOCKS (%s) value set in DLC ebuild resulted in a '
362 'max size of DLC_PREALLOC_BLOCKS * 4K (%s) bytes the DLC image is '
363 'allowed to occupy. The value is smaller than the actual image size '
364 '(%s) required. Increase DLC_PREALLOC_BLOCKS in your ebuild to at '
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800365 'least %d.' %
Andrew67b5fa72020-02-05 14:14:48 -0800366 (self.ebuild_params.pre_allocated_blocks, preallocated_bytes,
367 image_bytes, self.GetOptimalImageBlockSize(image_bytes)))
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700368
369 def GetOptimalImageBlockSize(self, image_bytes):
370 """Given the image bytes, get the least amount of blocks required."""
371 return int(math.ceil(image_bytes / self._BLOCK_SIZE))
Xiaochu Liu36b30592019-08-06 09:39:54 -0700372
Xiaochu Liudeed0232018-06-26 10:25:34 -0700373 def GetImageloaderJsonContent(self, image_hash, table_hash, blocks):
374 """Return the content of imageloader.json file.
375
376 Args:
377 image_hash: (str) sha256 hash of the DLC image.
378 table_hash: (str) sha256 hash of the DLC table file.
379 blocks: (int) number of blocks in the DLC image.
380
381 Returns:
382 [str]: content of imageloader.json file.
383 """
384 return {
Andrew67b5fa72020-02-05 14:14:48 -0800385 'fs-type': self.ebuild_params.fs_type,
386 'id': self.ebuild_params.dlc_id,
387 'package': self.ebuild_params.dlc_package,
Xiaochu Liudeed0232018-06-26 10:25:34 -0700388 'image-sha256-hash': image_hash,
389 'image-type': 'dlc',
390 'is-removable': True,
391 'manifest-version': self._MANIFEST_VERSION,
Andrew67b5fa72020-02-05 14:14:48 -0800392 'name': self.ebuild_params.name,
393 'pre-allocated-size':
394 str(self.ebuild_params.pre_allocated_blocks * self._BLOCK_SIZE),
Jae Hoon Kimbd8ae6e2020-02-03 18:54:29 -0800395 'size': str(blocks * self._BLOCK_SIZE),
Xiaochu Liudeed0232018-06-26 10:25:34 -0700396 'table-sha256-hash': table_hash,
Andrew67b5fa72020-02-05 14:14:48 -0800397 'version': self.ebuild_params.version,
398 'preload-allowed': self.ebuild_params.preload,
Xiaochu Liudeed0232018-06-26 10:25:34 -0700399 }
400
401 def GenerateVerity(self):
402 """Generate verity parameters and hashes for the image."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700403 logging.info('Generating DLC image verity.')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700404 with osutils.TempDir(prefix='dlc_') as temp_dir:
405 hash_tree = os.path.join(temp_dir, 'hash_tree')
406 # Get blocks in the image.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800407 blocks = math.ceil(os.path.getsize(self.dest_image) / self._BLOCK_SIZE)
408 result = cros_build_lib.run([
409 'verity', 'mode=create', 'alg=sha256', 'payload=' + self.dest_image,
410 'payload_blocks=' + str(blocks), 'hashtree=' + hash_tree,
411 'salt=random'
412 ],
413 capture_output=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700414 table = result.output
415
416 # Append the merkle tree to the image.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800417 osutils.WriteFile(
418 self.dest_image, osutils.ReadFile(hash_tree, mode='rb'), mode='a+b')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700419
420 # Write verity parameter to table file.
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400421 osutils.WriteFile(self.dest_table, table, mode='wb')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700422
423 # Compute image hash.
424 image_hash = HashFile(self.dest_image)
425 table_hash = HashFile(self.dest_table)
426 # Write image hash to imageloader.json file.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800427 blocks = math.ceil(os.path.getsize(self.dest_image) / self._BLOCK_SIZE)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700428 imageloader_json_content = self.GetImageloaderJsonContent(
429 image_hash, table_hash, int(blocks))
430 with open(self.dest_imageloader_json, 'w') as f:
431 json.dump(imageloader_json_content, f)
432
433 def GenerateDLC(self):
434 """Generate a DLC artifact."""
Andrew67b5fa72020-02-05 14:14:48 -0800435 # Create directories.
436 osutils.SafeMakedirs(self.image_dir)
437 osutils.SafeMakedirs(self.meta_dir)
438
439 # Create the image into |self.temp_root| and copy the DLC files to it.
Xiaochu Liudeed0232018-06-26 10:25:34 -0700440 self.CreateImage()
Andrew67b5fa72020-02-05 14:14:48 -0800441 # Verify the image created is within pre-allocated size.
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700442 self.VerifyImageSize()
Andrew67b5fa72020-02-05 14:14:48 -0800443 # Generate hash tree and other metadata and save them under
444 # |self.temp_root|.
Xiaochu Liudeed0232018-06-26 10:25:34 -0700445 self.GenerateVerity()
Andrew67b5fa72020-02-05 14:14:48 -0800446 # Copy the files from |self.temp_root| into the build directory.
447 self.CopyTempContentsToBuildDir()
448
449 # Now that the image was successfully generated, delete |ebuild_params_path|
450 # to indicate that the image in the build directory is in sync with the
451 # files installed during the build_package phase.
452 ebuild_params_path = EbuildParams.GetParamsPath(
453 self.sysroot, self.ebuild_params.dlc_id, self.ebuild_params.dlc_package)
454 osutils.SafeUnlink(ebuild_params_path, sudo=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700455
456
Andrew67b5fa72020-02-05 14:14:48 -0800457def IsDlcPreloadingAllowed(dlc_id, dlc_build_dir):
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800458 """Validates that DLC and it's packages all were built with DLC_PRELOAD=true.
459
460 Args:
461 dlc_id: (str) DLC ID.
Andrew67b5fa72020-02-05 14:14:48 -0800462 dlc_build_dir: (str) the root path where DLC build files reside.
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800463 """
464
Andrew67b5fa72020-02-05 14:14:48 -0800465 dlc_id_meta_dir = os.path.join(dlc_build_dir, dlc_id)
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800466 if not os.path.exists(dlc_id_meta_dir):
Andrew67b5fa72020-02-05 14:14:48 -0800467 logging.error('DLC build directory (%s) does not exist for preloading '
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800468 'check, will not preload', dlc_id_meta_dir)
469 return False
470
471 packages = os.listdir(dlc_id_meta_dir)
472 if not packages:
Andrew67b5fa72020-02-05 14:14:48 -0800473 logging.error('DLC ID build directory (%s) does not have any '
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800474 'packages, will not preload.', dlc_id_meta_dir)
475 return False
476
Andrew67b5fa72020-02-05 14:14:48 -0800477 for package in packages:
478 image_loader_json = os.path.join(dlc_id_meta_dir, package, DLC_TMP_META_DIR,
479 IMAGELOADER_JSON)
480 if not os.path.exists(image_loader_json):
481 logging.error('DLC metadata file (%s) does not exist, will not preload.',
482 image_loader_json)
483 return False
484 if not GetValueInJsonFile(json_path=image_loader_json,
485 key='preload-allowed', default_value=False):
486 return False
487 # All packages support preload.
488 return True
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800489
490
Andrew67b5fa72020-02-05 14:14:48 -0800491def InstallDlcImages(sysroot, dlc_id=None, install_root_dir=None, preload=False,
492 rootfs=None, src_dir=None):
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800493 """Copies all DLC image files into the images directory.
494
495 Copies the DLC image files in the given build directory into the given DLC
496 image directory. If the DLC build directory does not exist, or there is no DLC
497 for that board, this function does nothing.
498
499 Args:
500 sysroot: Path to directory containing DLC images, e.g /build/<board>.
Andrew67b5fa72020-02-05 14:14:48 -0800501 dlc_id: (str) DLC ID. If None, all the DLCs will be installed.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800502 install_root_dir: Path to DLC output directory, e.g.
Andrew67b5fa72020-02-05 14:14:48 -0800503 src/build/images/<board>/<version>. If None, the image will be generated
504 but will not be copied to a destination.
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800505 preload: When true, only copies DLC(s) if built with DLC_PRELOAD=true.
Andrew67b5fa72020-02-05 14:14:48 -0800506 rootfs: (str) Path to the platform rootfs.
507 src_dir: (str) Path to the DLC source root directory.
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800508 """
Andrew67b5fa72020-02-05 14:14:48 -0800509 dlc_build_dir = os.path.join(sysroot, DLC_BUILD_DIR)
510 if not os.path.exists(dlc_build_dir):
511 logging.info('DLC build directory (%s) does not exist, ignoring.',
512 dlc_build_dir)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800513 return
514
Andrew67b5fa72020-02-05 14:14:48 -0800515 if dlc_id is not None:
516 if not os.path.exists(os.path.join(dlc_build_dir, dlc_id)):
517 raise Exception(
518 'DLC "%s" does not exist in the build directory %s.' %
519 (dlc_id, dlc_build_dir))
520 dlc_ids = [dlc_id]
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800521 else:
Andrew67b5fa72020-02-05 14:14:48 -0800522 # Process all DLCs.
523 dlc_ids = os.listdir(dlc_build_dir)
524 if not dlc_ids:
525 logging.info('There are no DLC(s) to copy to output, ignoring.')
526 return
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800527
Andrew67b5fa72020-02-05 14:14:48 -0800528 logging.info('Detected the following DLCs: %s', ', '.join(dlc_ids))
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800529
Andrew67b5fa72020-02-05 14:14:48 -0800530 for d_id in dlc_ids:
531 dlc_id_path = os.path.join(dlc_build_dir, d_id)
532 dlc_packages = [direct for direct in os.listdir(dlc_id_path)
533 if os.path.isdir(os.path.join(dlc_id_path, direct))]
534 for d_package in dlc_packages:
535 logging.info('Building image: DLC %s', d_id)
Jae Hoon Kim264b8d22020-04-06 11:49:19 -0700536 params = EbuildParams.LoadEbuildParams(sysroot=sysroot, dlc_id=d_id,
Andrew67b5fa72020-02-05 14:14:48 -0800537 dlc_package=d_package)
538 # Because portage sandboxes every ebuild package during build_packages
539 # phase, we cannot delete the old image during that phase, but we can use
540 # the existence of the file |EBUILD_PARAMETERS| to know if the image
541 # has to be generated or not.
542 if not params:
543 logging.info('The ebuild parameters file (%s) for DLC (%s) does not '
544 'exist. This means that the image was already '
545 'generated and there is no need to create it again.',
546 EbuildParams.GetParamsPath(sysroot, d_id, d_package), d_id)
547 else:
548 dlc_generator = DlcGenerator(
549 src_dir=src_dir,
550 sysroot=sysroot,
551 install_root_dir=sysroot,
552 ebuild_params=params)
553 dlc_generator.GenerateDLC()
554
555 # Copy the dlc images to install_root_dir.
556 if install_root_dir:
557 if preload and not IsDlcPreloadingAllowed(d_id, dlc_build_dir):
558 logging.info('Skipping installation of DLC %s because the preload '
559 'flag is set and the DLC does not support preloading.',
560 d_id)
561 else:
Jae Hoon Kimb7da6fe2020-04-02 16:17:48 -0700562 osutils.SafeMakedirsNonRoot(install_root_dir)
Andrew67b5fa72020-02-05 14:14:48 -0800563 install_dlc_dir = os.path.join(install_root_dir, d_id, d_package)
Jae Hoon Kimb7da6fe2020-04-02 16:17:48 -0700564 osutils.SafeMakedirsNonRoot(install_dlc_dir)
565 source_dlc_dir = os.path.join(dlc_build_dir, d_id, d_package)
Andrew67b5fa72020-02-05 14:14:48 -0800566 for filepath in (os.path.join(source_dlc_dir, fname) for fname in
567 os.listdir(source_dlc_dir) if
568 fname.endswith('.img')):
569 logging.info('Copying DLC(%s) image from %s to %s: ', d_id,
570 filepath, install_dlc_dir)
Jae Hoon Kimb7da6fe2020-04-02 16:17:48 -0700571 shutil.copy(filepath, install_dlc_dir)
Andrew67b5fa72020-02-05 14:14:48 -0800572 logging.info('Done copying DLC to %s.', install_dlc_dir)
573 else:
574 logging.info('install_root_dir value was not provided. Copying dlc'
575 ' image skipped.')
576
577 # Create metadata directory in rootfs.
578 if rootfs:
579 meta_rootfs = os.path.join(rootfs, DLC_META_DIR, d_id, d_package)
580 osutils.SafeMakedirs(meta_rootfs, sudo=True)
581 # Copy the metadata files to rootfs.
582 meta_dir_src = os.path.join(dlc_build_dir, d_id, d_package,
583 DLC_TMP_META_DIR)
584 logging.info('Copying DLC(%s) metadata from %s to %s: ', d_id,
585 meta_dir_src, meta_rootfs)
586 # Use sudo_run since osutils.CopyDirContents doesn't support sudo.
587 cros_build_lib.sudo_run(['cp', '-dR',
588 meta_dir_src.rstrip('/') + '/.',
589 meta_rootfs], print_cmd=False, stderr=True)
590
591 else:
592 logging.info('rootfs value was not provided. Copying metadata skipped.')
593
594 logging.info('Done installing DLCs.')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800595
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800596
Xiaochu Liudeed0232018-06-26 10:25:34 -0700597def GetParser():
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800598 """Creates an argument parser and returns it."""
Xiaochu Liudeed0232018-06-26 10:25:34 -0700599 parser = commandline.ArgumentParser(description=__doc__)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800600 # This script is used both for building an individual DLC or copying all final
601 # DLCs images to their final destination nearby chromiumsos_test_image.bin,
602 # etc. These two arguments are required in both cases.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800603 parser.add_argument(
604 '--sysroot',
605 type='path',
606 metavar='DIR',
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800607 help="The root path to the board's build root, e.g. "
608 '/build/eve')
Andrew67b5fa72020-02-05 14:14:48 -0800609 # TODO(andrewlassalle): Remove src-dir in the future(2021?) if nobody uses it.
610 parser.add_argument(
611 '--src-dir',
612 type='path',
613 metavar='SRC_DIR_PATH',
614 help='Override the default Root directory path that contains all DLC '
615 'files to be packed.')
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800616 parser.add_argument(
617 '--install-root-dir',
618 type='path',
619 metavar='DIR',
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800620 help='If building a specific DLC, it is the root path to'
621 ' install DLC images (%s) and metadata (%s). Otherwise it'
622 ' is the target directory where the Chrome OS images gets'
623 ' dropped in build_image, e.g. '
Andrew67b5fa72020-02-05 14:14:48 -0800624 'src/build/images/<board>/latest.' % (DLC_BUILD_DIR, DLC_META_DIR))
Amin Hassani22a25eb2019-01-11 14:25:02 -0800625
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800626 one_dlc = parser.add_argument_group('Arguments required for building only '
627 'one DLC')
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800628 one_dlc.add_argument(
Andrew67b5fa72020-02-05 14:14:48 -0800629 '--rootfs',
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800630 type='path',
Andrew67b5fa72020-02-05 14:14:48 -0800631 metavar='ROOT_FS_PATH',
632 help='Path to the platform rootfs.')
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800633 one_dlc.add_argument(
634 '--pre-allocated-blocks',
635 type=int,
636 metavar='PREALLOCATEDBLOCKS',
637 help='Number of blocks (block size is 4k) that need to'
638 'be pre-allocated on device.')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800639 one_dlc.add_argument('--version', metavar='VERSION', help='DLC Version.')
640 one_dlc.add_argument('--id', metavar='ID', help='DLC ID (unique per DLC).')
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800641 one_dlc.add_argument(
642 '--package',
643 metavar='PACKAGE',
644 help='The package ID that is unique within a DLC, One'
645 ' DLC cannot have duplicate package IDs.')
646 one_dlc.add_argument(
647 '--name', metavar='NAME', help='A human-readable name for the DLC.')
648 one_dlc.add_argument(
649 '--fs-type',
650 metavar='FS_TYPE',
651 default=_SQUASHFS_TYPE,
652 choices=(_SQUASHFS_TYPE, _EXT4_TYPE),
653 help='File system type of the image.')
654 one_dlc.add_argument(
655 '--preload',
656 default=False,
657 action='store_true',
658 help='Allow preloading of DLC.')
Andrew67b5fa72020-02-05 14:14:48 -0800659 one_dlc.add_argument(
660 '--build-package',
661 default=False,
662 action='store_true',
663 help='Flag to indicate if the script is executed during the '
664 'build_packages phase.')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700665 return parser
666
667
Andrew67b5fa72020-02-05 14:14:48 -0800668def ValidateDlcIdentifier(parser, name):
Amin Hassanibc1a4792019-10-24 14:39:57 -0700669 """Validates the DLC identifiers like ID and package names.
670
671 The name specifications are:
672 - No underscore.
673 - First character should be only alphanumeric.
674 - Other characters can be alphanumeric and '-' (dash).
Jae Hoon Kimdb1cab82020-01-21 19:27:28 -0800675 - Maximum length of 40 (MAX_ID_NAME) characters.
Amin Hassanibc1a4792019-10-24 14:39:57 -0700676
677 For more info see:
678 https://chromium.googlesource.com/chromiumos/platform2/+/master/dlcservice/docs/developer.md#create-a-dlc-module
679
680 Args:
Andrew67b5fa72020-02-05 14:14:48 -0800681 parser: Arguments parser.
Jae Hoon Kimdb1cab82020-01-21 19:27:28 -0800682 name: The value of the string to be validated.
Amin Hassanibc1a4792019-10-24 14:39:57 -0700683 """
Jae Hoon Kimdb1cab82020-01-21 19:27:28 -0800684 errors = []
685 if not name:
686 errors.append('Must not be empty.')
687 if not name[0].isalnum():
688 errors.append('Must start with alphanumeric character.')
689 if not re.match(r'^[a-zA-Z0-9][a-zA-Z0-9-]*$', name):
690 errors.append('Must only use alphanumeric and - (dash).')
691 if len(name) > MAX_ID_NAME:
692 errors.append('Must be within %d characters.' % MAX_ID_NAME)
693
694 if errors:
695 msg = '%s is invalid:\n%s' % (name, '\n'.join(errors))
Andrew67b5fa72020-02-05 14:14:48 -0800696 parser.error(msg)
Amin Hassanibc1a4792019-10-24 14:39:57 -0700697
698
Andrew67b5fa72020-02-05 14:14:48 -0800699def ValidateArguments(parser, opts, req_flags, invalid_flags):
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800700 """Validates the correctness of the passed arguments.
701
702 Args:
Andrew67b5fa72020-02-05 14:14:48 -0800703 parser: Arguments parser.
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800704 opts: Parsed arguments.
Andrew67b5fa72020-02-05 14:14:48 -0800705 req_flags: all the required flags.
706 invalid_flags: all the flags that are not allowed.
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800707 """
708 # Make sure if the intention is to build one DLC, all the required arguments
Andrew67b5fa72020-02-05 14:14:48 -0800709 # are passed and none of the invalid ones are passed. This will ensure the
710 # script is called twice per DLC.
711 if opts.id:
712 if not all(vars(opts)[x] is not None for x in req_flags):
713 parser.error('If the intention is to build only one DLC, all the flags'
714 '%s required for it should be passed.' % req_flags)
715 if any(vars(opts)[x] is not None for x in invalid_flags):
716 parser.error('If the intention is to build only one DLC, all the flags'
717 '%s should be passed in the build_packages phase, not in '
718 'the build_image phase.' % invalid_flags)
719
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800720
721 if opts.fs_type == _EXT4_TYPE:
Andrew67b5fa72020-02-05 14:14:48 -0800722 parser.error('ext4 unsupported for DLC, see https://crbug.com/890060')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800723
Amin Hassanibc1a4792019-10-24 14:39:57 -0700724 if opts.id:
Andrew67b5fa72020-02-05 14:14:48 -0800725 ValidateDlcIdentifier(parser, opts.id)
Amin Hassanibc1a4792019-10-24 14:39:57 -0700726 if opts.package:
Andrew67b5fa72020-02-05 14:14:48 -0800727 ValidateDlcIdentifier(parser, opts.package)
Amin Hassanibc1a4792019-10-24 14:39:57 -0700728
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800729
Xiaochu Liudeed0232018-06-26 10:25:34 -0700730def main(argv):
Andrew67b5fa72020-02-05 14:14:48 -0800731 parser = GetParser()
732 opts = parser.parse_args(argv)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700733 opts.Freeze()
Andrew67b5fa72020-02-05 14:14:48 -0800734 per_dlc_req_args = ['id']
735 per_dlc_invalid_args = []
736 if opts.build_package:
737 per_dlc_req_args += ['pre_allocated_blocks', 'version', 'name',
738 'package', 'install_root_dir']
739 per_dlc_invalid_args += ['src_dir', 'sysroot']
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800740 else:
Andrew67b5fa72020-02-05 14:14:48 -0800741 per_dlc_req_args += ['sysroot']
742 per_dlc_invalid_args += ['name', 'pre_allocated_blocks', 'version',
743 'package']
744
745 ValidateArguments(parser, opts, per_dlc_req_args, per_dlc_invalid_args)
746
747 if opts.build_package:
748 logging.info('Building package: DLC %s', opts.id)
749 params = EbuildParams(dlc_id=opts.id,
750 dlc_package=opts.package,
751 fs_type=opts.fs_type,
752 name=opts.name,
753 pre_allocated_blocks=opts.pre_allocated_blocks,
754 version=opts.version,
755 preload=opts.preload)
756 params.StoreDlcParameters(install_root_dir=opts.install_root_dir, sudo=True)
757
758 else:
759 InstallDlcImages(
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800760 sysroot=opts.sysroot,
Andrew67b5fa72020-02-05 14:14:48 -0800761 dlc_id=opts.id,
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800762 install_root_dir=opts.install_root_dir,
Andrew67b5fa72020-02-05 14:14:48 -0800763 preload=opts.preload,
764 src_dir=opts.src_dir,
765 rootfs=opts.rootfs)