blob: 3e1c95121fc4a6afbb221c66393fe52eaacccef3 [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/'
25DLC_IMAGE_DIR = 'build/rootfs/dlc/'
Amin Hassanid5742d32019-01-22 21:13:34 -080026LSB_RELEASE = 'etc/lsb-release'
Jae Hoon Kim5f411e42020-01-09 13:30:56 -080027DLC_IMAGE = 'dlc.img'
28IMAGELOADER_JSON = 'imageloader.json'
Amin Hassanid5742d32019-01-22 21:13:34 -080029
Amin Hassani11a88cf2019-01-29 15:31:24 -080030# This file has major and minor version numbers that the update_engine client
31# supports. These values are needed for generating a delta/full payload.
32UPDATE_ENGINE_CONF = 'etc/update_engine.conf'
33
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -080034_EXTRA_RESOURCES = (UPDATE_ENGINE_CONF,)
Amin Hassani11a88cf2019-01-29 15:31:24 -080035
Amin Hassanid5742d32019-01-22 21:13:34 -080036DLC_ID_KEY = 'DLC_ID'
Amin Hassanib5a48042019-03-18 14:30:51 -070037DLC_PACKAGE_KEY = 'DLC_PACKAGE'
Amin Hassanid5742d32019-01-22 21:13:34 -080038DLC_NAME_KEY = 'DLC_NAME'
Amin Hassani8f1cc0f2019-03-06 15:34:53 -080039DLC_APPID_KEY = 'DLC_RELEASE_APPID'
Amin Hassani2af75a92019-01-22 21:07:45 -080040
Amin Hassani22a25eb2019-01-11 14:25:02 -080041_SQUASHFS_TYPE = 'squashfs'
42_EXT4_TYPE = 'ext4'
43
Amin Hassanibc1a4792019-10-24 14:39:57 -070044MAX_ID_NAME = 40
Amin Hassanid5742d32019-01-22 21:13:34 -080045
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -080046
Xiaochu Liudeed0232018-06-26 10:25:34 -070047def HashFile(file_path):
48 """Calculate the sha256 hash of a file.
49
50 Args:
51 file_path: (str) path to the file.
52
53 Returns:
54 [str]: The sha256 hash of the file.
55 """
56 sha256 = hashlib.sha256()
57 with open(file_path, 'rb') as f:
58 for b in iter(lambda: f.read(2048), b''):
59 sha256.update(b)
60 return sha256.hexdigest()
61
62
Jae Hoon Kim5f411e42020-01-09 13:30:56 -080063def GetValueInJsonFile(json_path, key, default_value=None):
64 """Reads file containing JSON and returns value or default_value for key.
65
66 Args:
67 json_path: (str) File containing JSON.
68 key: (str) The desired key to lookup.
69 default_value: (default:None) The default value returned in case of missing
70 key.
71 """
72 with open(json_path) as fd:
73 return json.load(fd).get(key, default_value)
74
75
Amin Hassani174eb7e2019-01-18 11:11:24 -080076class DlcGenerator(object):
Xiaochu Liudeed0232018-06-26 10:25:34 -070077 """Object to generate DLC artifacts."""
78 # Block size for the DLC image.
79 # We use 4K for various reasons:
80 # 1. it's what imageloader (linux kernel) supports.
81 # 2. it's what verity supports.
82 _BLOCK_SIZE = 4096
83 # Blocks in the initial sparse image.
84 _BLOCKS = 500000
85 # Version of manifest file.
86 _MANIFEST_VERSION = 1
87
Amin Hassanicc7ffce2019-01-11 14:57:52 -080088 # The DLC root path inside the DLC module.
89 _DLC_ROOT_DIR = 'root'
90
Amin Hassanib97a5ee2019-01-23 14:44:43 -080091 def __init__(self, src_dir, sysroot, install_root_dir, fs_type,
Jae Hoon Kim2b359e42020-01-03 12:54:40 -080092 pre_allocated_blocks, version, dlc_id, dlc_package, name,
93 preload):
Xiaochu Liudeed0232018-06-26 10:25:34 -070094 """Object initializer.
95
96 Args:
Xiaochu Liudeed0232018-06-26 10:25:34 -070097 src_dir: (str) path to the DLC source root directory.
Amin Hassanib97a5ee2019-01-23 14:44:43 -080098 sysroot: (str) The path to the build root directory.
Amin Hassani2af75a92019-01-22 21:07:45 -080099 install_root_dir: (str) The path to the root installation directory.
Xiaochu Liudeed0232018-06-26 10:25:34 -0700100 fs_type: (str) file system type.
101 pre_allocated_blocks: (int) number of blocks pre-allocated on device.
102 version: (str) DLC version.
103 dlc_id: (str) DLC ID.
Amin Hassanib5a48042019-03-18 14:30:51 -0700104 dlc_package: (str) DLC Package.
Xiaochu Liudeed0232018-06-26 10:25:34 -0700105 name: (str) DLC name.
Jae Hoon Kim2b359e42020-01-03 12:54:40 -0800106 preload: (bool) allow for preloading DLC.
Xiaochu Liudeed0232018-06-26 10:25:34 -0700107 """
108 self.src_dir = src_dir
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800109 self.sysroot = sysroot
Amin Hassani2af75a92019-01-22 21:07:45 -0800110 self.install_root_dir = install_root_dir
Xiaochu Liudeed0232018-06-26 10:25:34 -0700111 self.fs_type = fs_type
112 self.pre_allocated_blocks = pre_allocated_blocks
113 self.version = version
114 self.dlc_id = dlc_id
Amin Hassanib5a48042019-03-18 14:30:51 -0700115 self.dlc_package = dlc_package
Xiaochu Liudeed0232018-06-26 10:25:34 -0700116 self.name = name
Jae Hoon Kim2b359e42020-01-03 12:54:40 -0800117 self.preload = preload
Amin Hassani2af75a92019-01-22 21:07:45 -0800118
119 self.meta_dir = os.path.join(self.install_root_dir, DLC_META_DIR,
Amin Hassanib5a48042019-03-18 14:30:51 -0700120 self.dlc_id, self.dlc_package)
Amin Hassani2af75a92019-01-22 21:07:45 -0800121 self.image_dir = os.path.join(self.install_root_dir, DLC_IMAGE_DIR,
Amin Hassanib5a48042019-03-18 14:30:51 -0700122 self.dlc_id, self.dlc_package)
Amin Hassani2af75a92019-01-22 21:07:45 -0800123 osutils.SafeMakedirs(self.meta_dir)
124 osutils.SafeMakedirs(self.image_dir)
125
Xiaochu Liudeed0232018-06-26 10:25:34 -0700126 # Create path for all final artifacts.
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800127 self.dest_image = os.path.join(self.image_dir, DLC_IMAGE)
Amin Hassani2af75a92019-01-22 21:07:45 -0800128 self.dest_table = os.path.join(self.meta_dir, 'table')
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800129 self.dest_imageloader_json = os.path.join(self.meta_dir, IMAGELOADER_JSON)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700130
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700131 # Log out the member variable values initially set.
132 logging.debug('Initial internal values of DlcGenerator: %s',
133 json.dumps(self.__dict__, sort_keys=True))
134
Xiaochu Liudeed0232018-06-26 10:25:34 -0700135 def SquashOwnerships(self, path):
136 """Squash the owernships & permissions for files.
137
138 Args:
139 path: (str) path that contains all files to be processed.
140 """
Mike Frysinger45602c72019-09-22 02:15:11 -0400141 cros_build_lib.sudo_run(['chown', '-R', '0:0', path])
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800142 cros_build_lib.sudo_run([
143 'find', path, '-exec', 'touch', '-h', '-t', '197001010000.00', '{}', '+'
144 ])
Xiaochu Liudeed0232018-06-26 10:25:34 -0700145
146 def CreateExt4Image(self):
147 """Create an ext4 image."""
148 with osutils.TempDir(prefix='dlc_') as temp_dir:
149 mount_point = os.path.join(temp_dir, 'mount_point')
150 # Create a raw image file.
151 with open(self.dest_image, 'w') as f:
152 f.truncate(self._BLOCKS * self._BLOCK_SIZE)
153 # Create an ext4 file system on the raw image.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800154 cros_build_lib.run([
155 '/sbin/mkfs.ext4', '-b',
156 str(self._BLOCK_SIZE), '-O', '^has_journal', self.dest_image
157 ],
158 capture_output=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700159 # Create the mount_point directory.
160 osutils.SafeMakedirs(mount_point)
161 # Mount the ext4 image.
162 osutils.MountDir(self.dest_image, mount_point, mount_opts=('loop', 'rw'))
Amin Hassanicc7ffce2019-01-11 14:57:52 -0800163
Xiaochu Liudeed0232018-06-26 10:25:34 -0700164 try:
Amin Hassani11a88cf2019-01-29 15:31:24 -0800165 self.SetupDlcImageFiles(mount_point)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700166 finally:
167 # Unmount the ext4 image.
168 osutils.UmountDir(mount_point)
169 # Shrink to minimum size.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800170 cros_build_lib.run(['/sbin/e2fsck', '-y', '-f', self.dest_image],
171 capture_output=True)
172 cros_build_lib.run(['/sbin/resize2fs', '-M', self.dest_image],
173 capture_output=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700174
175 def CreateSquashfsImage(self):
176 """Create a squashfs image."""
177 with osutils.TempDir(prefix='dlc_') as temp_dir:
Amin Hassani22a25eb2019-01-11 14:25:02 -0800178 squashfs_root = os.path.join(temp_dir, 'squashfs-root')
Amin Hassani11a88cf2019-01-29 15:31:24 -0800179 self.SetupDlcImageFiles(squashfs_root)
Amin Hassani22a25eb2019-01-11 14:25:02 -0800180
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800181 cros_build_lib.run([
182 'mksquashfs', squashfs_root, self.dest_image, '-4k-align', '-noappend'
183 ],
184 capture_output=True)
Amin Hassani22a25eb2019-01-11 14:25:02 -0800185
186 # We changed the ownership and permissions of the squashfs_root
187 # directory. Now we need to remove it manually.
188 osutils.RmDir(squashfs_root, sudo=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700189
Amin Hassani11a88cf2019-01-29 15:31:24 -0800190 def SetupDlcImageFiles(self, dlc_dir):
191 """Prepares the directory dlc_dir with all the files a DLC needs.
192
193 Args:
194 dlc_dir: (str) The path to where to setup files inside the DLC.
195 """
196 dlc_root_dir = os.path.join(dlc_dir, self._DLC_ROOT_DIR)
197 osutils.SafeMakedirs(dlc_root_dir)
Jae Hoon Kimd14646b2019-08-21 14:49:26 -0700198 osutils.CopyDirContents(self.src_dir, dlc_root_dir, symlinks=True)
Amin Hassani11a88cf2019-01-29 15:31:24 -0800199 self.PrepareLsbRelease(dlc_dir)
200 self.CollectExtraResources(dlc_dir)
201 self.SquashOwnerships(dlc_dir)
202
Amin Hassanid5742d32019-01-22 21:13:34 -0800203 def PrepareLsbRelease(self, dlc_dir):
204 """Prepare the file /etc/lsb-release in the DLC module.
205
206 This file is used dropping some identification parameters for the DLC.
207
208 Args:
209 dlc_dir: (str) The path to root directory of the DLC. e.g. mounted point
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800210 when we are creating the image.
Amin Hassanid5742d32019-01-22 21:13:34 -0800211 """
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800212 # Reading the platform APPID and creating the DLC APPID.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800213 platform_lsb_release = osutils.ReadFile(
214 os.path.join(self.sysroot, LSB_RELEASE))
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800215 app_id = None
216 for line in platform_lsb_release.split('\n'):
217 if line.startswith(cros_set_lsb_release.LSB_KEY_APPID_RELEASE):
218 app_id = line.split('=')[1]
219 if app_id is None:
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800220 raise Exception(
221 '%s does not have a valid key %s' %
222 (platform_lsb_release, cros_set_lsb_release.LSB_KEY_APPID_RELEASE))
Amin Hassanid5742d32019-01-22 21:13:34 -0800223
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400224 fields = (
225 (DLC_ID_KEY, self.dlc_id),
226 (DLC_PACKAGE_KEY, self.dlc_package),
227 (DLC_NAME_KEY, self.name),
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800228 # The DLC appid is generated by concatenating the platform appid with
229 # the DLC ID using an underscore. This pattern should never be changed
230 # once set otherwise it can break a lot of things!
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400231 (DLC_APPID_KEY, '%s_%s' % (app_id, self.dlc_id)),
232 )
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800233
234 lsb_release = os.path.join(dlc_dir, LSB_RELEASE)
235 osutils.SafeMakedirs(os.path.dirname(lsb_release))
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400236 content = ''.join('%s=%s\n' % (k, v) for k, v in fields)
Amin Hassanid5742d32019-01-22 21:13:34 -0800237 osutils.WriteFile(lsb_release, content)
238
Amin Hassani11a88cf2019-01-29 15:31:24 -0800239 def CollectExtraResources(self, dlc_dir):
240 """Collect the extra resources needed by the DLC module.
241
242 Look at the documentation around _EXTRA_RESOURCES.
243
244 Args:
245 dlc_dir: (str) The path to root directory of the DLC. e.g. mounted point
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800246 when we are creating the image.
Amin Hassani11a88cf2019-01-29 15:31:24 -0800247 """
248 for r in _EXTRA_RESOURCES:
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800249 source_path = os.path.join(self.sysroot, r)
Amin Hassani11a88cf2019-01-29 15:31:24 -0800250 target_path = os.path.join(dlc_dir, r)
251 osutils.SafeMakedirs(os.path.dirname(target_path))
252 shutil.copyfile(source_path, target_path)
253
Xiaochu Liudeed0232018-06-26 10:25:34 -0700254 def CreateImage(self):
255 """Create the image and copy the DLC files to it."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700256 logging.info('Creating the DLC image.')
Amin Hassani22a25eb2019-01-11 14:25:02 -0800257 if self.fs_type == _EXT4_TYPE:
Xiaochu Liudeed0232018-06-26 10:25:34 -0700258 self.CreateExt4Image()
Amin Hassani22a25eb2019-01-11 14:25:02 -0800259 elif self.fs_type == _SQUASHFS_TYPE:
Xiaochu Liudeed0232018-06-26 10:25:34 -0700260 self.CreateSquashfsImage()
261 else:
262 raise ValueError('Wrong fs type: %s used:' % self.fs_type)
263
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700264 def VerifyImageSize(self):
Xiaochu Liu36b30592019-08-06 09:39:54 -0700265 """Verify the image can fit to the reserved file."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700266 logging.info('Verifying the DLC image size.')
267 image_bytes = os.path.getsize(self.dest_image)
Xiaochu Liu36b30592019-08-06 09:39:54 -0700268 preallocated_bytes = self.pre_allocated_blocks * self._BLOCK_SIZE
269 # Verifies the actual size of the DLC image is NOT smaller than the
270 # preallocated space.
271 if preallocated_bytes < image_bytes:
272 raise ValueError(
273 'The DLC_PREALLOC_BLOCKS (%s) value set in DLC ebuild resulted in a '
274 'max size of DLC_PREALLOC_BLOCKS * 4K (%s) bytes the DLC image is '
275 'allowed to occupy. The value is smaller than the actual image size '
276 '(%s) required. Increase DLC_PREALLOC_BLOCKS in your ebuild to at '
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800277 'least %d.' %
278 (self.pre_allocated_blocks, preallocated_bytes, image_bytes,
279 self.GetOptimalImageBlockSize(image_bytes)))
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700280
281 def GetOptimalImageBlockSize(self, image_bytes):
282 """Given the image bytes, get the least amount of blocks required."""
283 return int(math.ceil(image_bytes / self._BLOCK_SIZE))
Xiaochu Liu36b30592019-08-06 09:39:54 -0700284
Xiaochu Liudeed0232018-06-26 10:25:34 -0700285 def GetImageloaderJsonContent(self, image_hash, table_hash, blocks):
286 """Return the content of imageloader.json file.
287
288 Args:
289 image_hash: (str) sha256 hash of the DLC image.
290 table_hash: (str) sha256 hash of the DLC table file.
291 blocks: (int) number of blocks in the DLC image.
292
293 Returns:
294 [str]: content of imageloader.json file.
295 """
296 return {
297 'fs-type': self.fs_type,
298 'id': self.dlc_id,
Amin Hassanib5a48042019-03-18 14:30:51 -0700299 'package': self.dlc_package,
Xiaochu Liudeed0232018-06-26 10:25:34 -0700300 'image-sha256-hash': image_hash,
301 'image-type': 'dlc',
302 'is-removable': True,
303 'manifest-version': self._MANIFEST_VERSION,
304 'name': self.name,
305 'pre-allocated-size': self.pre_allocated_blocks * self._BLOCK_SIZE,
306 'size': blocks * self._BLOCK_SIZE,
307 'table-sha256-hash': table_hash,
308 'version': self.version,
Jae Hoon Kim2b359e42020-01-03 12:54:40 -0800309 'preload-allowed': self.preload,
Xiaochu Liudeed0232018-06-26 10:25:34 -0700310 }
311
312 def GenerateVerity(self):
313 """Generate verity parameters and hashes for the image."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700314 logging.info('Generating DLC image verity.')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700315 with osutils.TempDir(prefix='dlc_') as temp_dir:
316 hash_tree = os.path.join(temp_dir, 'hash_tree')
317 # Get blocks in the image.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800318 blocks = math.ceil(os.path.getsize(self.dest_image) / self._BLOCK_SIZE)
319 result = cros_build_lib.run([
320 'verity', 'mode=create', 'alg=sha256', 'payload=' + self.dest_image,
321 'payload_blocks=' + str(blocks), 'hashtree=' + hash_tree,
322 'salt=random'
323 ],
324 capture_output=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700325 table = result.output
326
327 # Append the merkle tree to the image.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800328 osutils.WriteFile(
329 self.dest_image, osutils.ReadFile(hash_tree, mode='rb'), mode='a+b')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700330
331 # Write verity parameter to table file.
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400332 osutils.WriteFile(self.dest_table, table, mode='wb')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700333
334 # Compute image hash.
335 image_hash = HashFile(self.dest_image)
336 table_hash = HashFile(self.dest_table)
337 # Write image hash to imageloader.json file.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800338 blocks = math.ceil(os.path.getsize(self.dest_image) / self._BLOCK_SIZE)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700339 imageloader_json_content = self.GetImageloaderJsonContent(
340 image_hash, table_hash, int(blocks))
341 with open(self.dest_imageloader_json, 'w') as f:
342 json.dump(imageloader_json_content, f)
343
344 def GenerateDLC(self):
345 """Generate a DLC artifact."""
346 # Create the image and copy the DLC files to it.
347 self.CreateImage()
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700348 # Verify the image created is within preallocated size.
349 self.VerifyImageSize()
Xiaochu Liudeed0232018-06-26 10:25:34 -0700350 # Generate hash tree and other metadata.
351 self.GenerateVerity()
352
353
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800354def IsDlcPreloadingAllowed(dlc_id, dlc_meta_dir):
355 """Validates that DLC and it's packages all were built with DLC_PRELOAD=true.
356
357 Args:
358 dlc_id: (str) DLC ID.
359 dlc_meta_dir: (str) the rooth path where DLC metadata resides.
360 """
361
362 dlc_id_meta_dir = os.path.join(dlc_meta_dir, dlc_id)
363 if not os.path.exists(dlc_id_meta_dir):
364 logging.error('DLC Metadata directory (%s) does not exist for preloading ' \
365 'check, will not preload', dlc_id_meta_dir)
366 return False
367
368 packages = os.listdir(dlc_id_meta_dir)
369 if not packages:
370 logging.error('DLC ID Metadata directory (%s) does not have any ' \
371 'packages, will not preload.', dlc_id_meta_dir)
372 return False
373
374 return all([
375 GetValueInJsonFile(
376 json_path=os.path.join(dlc_id_meta_dir, package, IMAGELOADER_JSON),
377 key='preload-allowed',
378 default_value=False) for package in packages
379 ])
380
381
382def CopyAllDlcs(sysroot, install_root_dir, preload):
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800383 """Copies all DLC image files into the images directory.
384
385 Copies the DLC image files in the given build directory into the given DLC
386 image directory. If the DLC build directory does not exist, or there is no DLC
387 for that board, this function does nothing.
388
389 Args:
390 sysroot: Path to directory containing DLC images, e.g /build/<board>.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800391 install_root_dir: Path to DLC output directory, e.g.
392 src/build/images/<board>/<version>.
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800393 preload: When true, only copies DLC(s) if built with DLC_PRELOAD=true.
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800394 """
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800395 dlc_meta_dir = os.path.join(sysroot, DLC_META_DIR)
396 dlc_image_dir = os.path.join(sysroot, DLC_IMAGE_DIR)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800397
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800398 if not os.path.exists(dlc_image_dir):
399 logging.info('DLC Image directory (%s) does not exist, ignoring.',
400 dlc_image_dir)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800401 return
402
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800403 dlc_ids = os.listdir(dlc_image_dir)
404 if not dlc_ids:
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700405 logging.info('There are no DLC(s) to copy to output, ignoring.')
406 return
407
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800408 logging.info('Detected the following DLCs: %s', ', '.join(dlc_ids))
409
410 if preload:
411 logging.info(
412 'Will only copy DLC images built with preloading from %s to '
413 '%s.', dlc_image_dir, install_root_dir)
414 dlc_ids = [
415 dlc_id for dlc_id in dlc_ids
416 if IsDlcPreloadingAllowed(dlc_id, dlc_meta_dir)
417 ]
418 logging.info('Actual DLC(s) to be copied: %s', ', '.join(dlc_ids))
419
420 else:
421 logging.info('Copying all DLC images from %s to %s.', dlc_image_dir,
422 install_root_dir)
Jae Hoon Kim2d31c082019-12-04 10:39:53 -0800423 osutils.SafeMakedirs(install_root_dir)
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800424
425 for dlc_id in dlc_ids:
426 source_dlc_dir = os.path.join(dlc_image_dir, dlc_id)
427 install_dlc_dir = os.path.join(install_root_dir, dlc_id)
428 osutils.SafeMakedirs(install_dlc_dir)
429 osutils.CopyDirContents(source_dlc_dir, install_dlc_dir)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800430
Amin Hassani6c0228b2019-03-04 13:42:33 -0800431 logging.info('Done copying the DLCs to their destination.')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800432
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800433
Xiaochu Liudeed0232018-06-26 10:25:34 -0700434def GetParser():
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800435 """Creates an argument parser and returns it."""
Xiaochu Liudeed0232018-06-26 10:25:34 -0700436 parser = commandline.ArgumentParser(description=__doc__)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800437 # This script is used both for building an individual DLC or copying all final
438 # DLCs images to their final destination nearby chromiumsos_test_image.bin,
439 # etc. These two arguments are required in both cases.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800440 parser.add_argument(
441 '--sysroot',
442 type='path',
443 metavar='DIR',
444 required=True,
445 help="The root path to the board's build root, e.g. "
446 '/build/eve')
447 parser.add_argument(
448 '--install-root-dir',
449 type='path',
450 metavar='DIR',
451 required=True,
452 help='If building a specific DLC, it is the root path to'
453 ' install DLC images (%s) and metadata (%s). Otherwise it'
454 ' is the target directory where the Chrome OS images gets'
455 ' dropped in build_image, e.g. '
456 'src/build/images/<board>/latest.' % (DLC_IMAGE_DIR, DLC_META_DIR))
Amin Hassani22a25eb2019-01-11 14:25:02 -0800457
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800458 one_dlc = parser.add_argument_group('Arguments required for building only '
459 'one DLC')
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800460 one_dlc.add_argument(
461 '--src-dir',
462 type='path',
463 metavar='SRC_DIR_PATH',
464 help='Root directory path that contains all DLC files '
465 'to be packed.')
466 one_dlc.add_argument(
467 '--pre-allocated-blocks',
468 type=int,
469 metavar='PREALLOCATEDBLOCKS',
470 help='Number of blocks (block size is 4k) that need to'
471 'be pre-allocated on device.')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800472 one_dlc.add_argument('--version', metavar='VERSION', help='DLC Version.')
473 one_dlc.add_argument('--id', metavar='ID', help='DLC ID (unique per DLC).')
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800474 one_dlc.add_argument(
475 '--package',
476 metavar='PACKAGE',
477 help='The package ID that is unique within a DLC, One'
478 ' DLC cannot have duplicate package IDs.')
479 one_dlc.add_argument(
480 '--name', metavar='NAME', help='A human-readable name for the DLC.')
481 one_dlc.add_argument(
482 '--fs-type',
483 metavar='FS_TYPE',
484 default=_SQUASHFS_TYPE,
485 choices=(_SQUASHFS_TYPE, _EXT4_TYPE),
486 help='File system type of the image.')
487 one_dlc.add_argument(
488 '--preload',
489 default=False,
490 action='store_true',
491 help='Allow preloading of DLC.')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700492 return parser
493
494
Amin Hassanibc1a4792019-10-24 14:39:57 -0700495def ValidateDlcIdentifier(name):
496 """Validates the DLC identifiers like ID and package names.
497
498 The name specifications are:
499 - No underscore.
500 - First character should be only alphanumeric.
501 - Other characters can be alphanumeric and '-' (dash).
502 - Maximum length of 40 characters.
503
504 For more info see:
505 https://chromium.googlesource.com/chromiumos/platform2/+/master/dlcservice/docs/developer.md#create-a-dlc-module
506
507 Args:
508 name: The string to be validated.
509 """
510 if (not name or not re.match(r'^[a-zA-Z0-9][a-zA-Z0-9-]*$', name) or
511 len(name) > MAX_ID_NAME):
512 raise Exception('Invalid DLC identifier %s' % name)
513
514
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800515def ValidateArguments(opts):
516 """Validates the correctness of the passed arguments.
517
518 Args:
519 opts: Parsed arguments.
520 """
521 # Make sure if the intention is to build one DLC, all the required arguments
522 # are passed.
523 per_dlc_req_args = ('src_dir', 'pre_allocated_blocks', 'version', 'id',
Amin Hassanib5a48042019-03-18 14:30:51 -0700524 'package', 'name')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800525 if (opts.id and
526 not all(vars(opts)[arg] is not None for arg in per_dlc_req_args)):
527 raise Exception('If the intention is to build only one DLC, all the flags'
528 '%s required for it should be passed .' % per_dlc_req_args)
529
530 if opts.fs_type == _EXT4_TYPE:
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700531 raise Exception('ext4 unsupported for DLC, see https://crbug.com/890060')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800532
Amin Hassanibc1a4792019-10-24 14:39:57 -0700533 if opts.id:
534 ValidateDlcIdentifier(opts.id)
535 if opts.package:
536 ValidateDlcIdentifier(opts.package)
537
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800538
Xiaochu Liudeed0232018-06-26 10:25:34 -0700539def main(argv):
540 opts = GetParser().parse_args(argv)
541 opts.Freeze()
542
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800543 ValidateArguments(opts)
Amin Hassani2af75a92019-01-22 21:07:45 -0800544
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800545 if opts.id:
546 logging.info('Building DLC %s', opts.id)
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800547 dlc_generator = DlcGenerator(
548 src_dir=opts.src_dir,
549 sysroot=opts.sysroot,
550 install_root_dir=opts.install_root_dir,
551 fs_type=opts.fs_type,
552 pre_allocated_blocks=opts.pre_allocated_blocks,
553 version=opts.version,
554 dlc_id=opts.id,
555 dlc_package=opts.package,
556 name=opts.name,
557 preload=opts.preload)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800558 dlc_generator.GenerateDLC()
559 else:
Jae Hoon Kim5f411e42020-01-09 13:30:56 -0800560 CopyAllDlcs(
561 sysroot=opts.sysroot,
562 install_root_dir=opts.install_root_dir,
563 preload=opts.preload)