blob: bead104bdbe0777eca6d210d48bd29c9a7f674bb [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'
27
Amin Hassani11a88cf2019-01-29 15:31:24 -080028# This file has major and minor version numbers that the update_engine client
29# supports. These values are needed for generating a delta/full payload.
30UPDATE_ENGINE_CONF = 'etc/update_engine.conf'
31
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -080032_EXTRA_RESOURCES = (UPDATE_ENGINE_CONF,)
Amin Hassani11a88cf2019-01-29 15:31:24 -080033
Amin Hassanid5742d32019-01-22 21:13:34 -080034DLC_ID_KEY = 'DLC_ID'
Amin Hassanib5a48042019-03-18 14:30:51 -070035DLC_PACKAGE_KEY = 'DLC_PACKAGE'
Amin Hassanid5742d32019-01-22 21:13:34 -080036DLC_NAME_KEY = 'DLC_NAME'
Amin Hassani8f1cc0f2019-03-06 15:34:53 -080037DLC_APPID_KEY = 'DLC_RELEASE_APPID'
Amin Hassani2af75a92019-01-22 21:07:45 -080038
Amin Hassani22a25eb2019-01-11 14:25:02 -080039_SQUASHFS_TYPE = 'squashfs'
40_EXT4_TYPE = 'ext4'
41
Amin Hassanibc1a4792019-10-24 14:39:57 -070042MAX_ID_NAME = 40
Amin Hassanid5742d32019-01-22 21:13:34 -080043
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -080044
Xiaochu Liudeed0232018-06-26 10:25:34 -070045def HashFile(file_path):
46 """Calculate the sha256 hash of a file.
47
48 Args:
49 file_path: (str) path to the file.
50
51 Returns:
52 [str]: The sha256 hash of the file.
53 """
54 sha256 = hashlib.sha256()
55 with open(file_path, 'rb') as f:
56 for b in iter(lambda: f.read(2048), b''):
57 sha256.update(b)
58 return sha256.hexdigest()
59
60
Amin Hassani174eb7e2019-01-18 11:11:24 -080061class DlcGenerator(object):
Xiaochu Liudeed0232018-06-26 10:25:34 -070062 """Object to generate DLC artifacts."""
63 # Block size for the DLC image.
64 # We use 4K for various reasons:
65 # 1. it's what imageloader (linux kernel) supports.
66 # 2. it's what verity supports.
67 _BLOCK_SIZE = 4096
68 # Blocks in the initial sparse image.
69 _BLOCKS = 500000
70 # Version of manifest file.
71 _MANIFEST_VERSION = 1
72
Amin Hassanicc7ffce2019-01-11 14:57:52 -080073 # The DLC root path inside the DLC module.
74 _DLC_ROOT_DIR = 'root'
75
Amin Hassanib97a5ee2019-01-23 14:44:43 -080076 def __init__(self, src_dir, sysroot, install_root_dir, fs_type,
Jae Hoon Kim2b359e42020-01-03 12:54:40 -080077 pre_allocated_blocks, version, dlc_id, dlc_package, name,
78 preload):
Xiaochu Liudeed0232018-06-26 10:25:34 -070079 """Object initializer.
80
81 Args:
Xiaochu Liudeed0232018-06-26 10:25:34 -070082 src_dir: (str) path to the DLC source root directory.
Amin Hassanib97a5ee2019-01-23 14:44:43 -080083 sysroot: (str) The path to the build root directory.
Amin Hassani2af75a92019-01-22 21:07:45 -080084 install_root_dir: (str) The path to the root installation directory.
Xiaochu Liudeed0232018-06-26 10:25:34 -070085 fs_type: (str) file system type.
86 pre_allocated_blocks: (int) number of blocks pre-allocated on device.
87 version: (str) DLC version.
88 dlc_id: (str) DLC ID.
Amin Hassanib5a48042019-03-18 14:30:51 -070089 dlc_package: (str) DLC Package.
Xiaochu Liudeed0232018-06-26 10:25:34 -070090 name: (str) DLC name.
Jae Hoon Kim2b359e42020-01-03 12:54:40 -080091 preload: (bool) allow for preloading DLC.
Xiaochu Liudeed0232018-06-26 10:25:34 -070092 """
93 self.src_dir = src_dir
Amin Hassanib97a5ee2019-01-23 14:44:43 -080094 self.sysroot = sysroot
Amin Hassani2af75a92019-01-22 21:07:45 -080095 self.install_root_dir = install_root_dir
Xiaochu Liudeed0232018-06-26 10:25:34 -070096 self.fs_type = fs_type
97 self.pre_allocated_blocks = pre_allocated_blocks
98 self.version = version
99 self.dlc_id = dlc_id
Amin Hassanib5a48042019-03-18 14:30:51 -0700100 self.dlc_package = dlc_package
Xiaochu Liudeed0232018-06-26 10:25:34 -0700101 self.name = name
Jae Hoon Kim2b359e42020-01-03 12:54:40 -0800102 self.preload = preload
Amin Hassani2af75a92019-01-22 21:07:45 -0800103
104 self.meta_dir = os.path.join(self.install_root_dir, DLC_META_DIR,
Amin Hassanib5a48042019-03-18 14:30:51 -0700105 self.dlc_id, self.dlc_package)
Amin Hassani2af75a92019-01-22 21:07:45 -0800106 self.image_dir = os.path.join(self.install_root_dir, DLC_IMAGE_DIR,
Amin Hassanib5a48042019-03-18 14:30:51 -0700107 self.dlc_id, self.dlc_package)
Amin Hassani2af75a92019-01-22 21:07:45 -0800108 osutils.SafeMakedirs(self.meta_dir)
109 osutils.SafeMakedirs(self.image_dir)
110
Xiaochu Liudeed0232018-06-26 10:25:34 -0700111 # Create path for all final artifacts.
Amin Hassanib5a48042019-03-18 14:30:51 -0700112 self.dest_image = os.path.join(self.image_dir, 'dlc.img')
Amin Hassani2af75a92019-01-22 21:07:45 -0800113 self.dest_table = os.path.join(self.meta_dir, 'table')
114 self.dest_imageloader_json = os.path.join(self.meta_dir, 'imageloader.json')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700115
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700116 # Log out the member variable values initially set.
117 logging.debug('Initial internal values of DlcGenerator: %s',
118 json.dumps(self.__dict__, sort_keys=True))
119
Xiaochu Liudeed0232018-06-26 10:25:34 -0700120 def SquashOwnerships(self, path):
121 """Squash the owernships & permissions for files.
122
123 Args:
124 path: (str) path that contains all files to be processed.
125 """
Mike Frysinger45602c72019-09-22 02:15:11 -0400126 cros_build_lib.sudo_run(['chown', '-R', '0:0', path])
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800127 cros_build_lib.sudo_run([
128 'find', path, '-exec', 'touch', '-h', '-t', '197001010000.00', '{}', '+'
129 ])
Xiaochu Liudeed0232018-06-26 10:25:34 -0700130
131 def CreateExt4Image(self):
132 """Create an ext4 image."""
133 with osutils.TempDir(prefix='dlc_') as temp_dir:
134 mount_point = os.path.join(temp_dir, 'mount_point')
135 # Create a raw image file.
136 with open(self.dest_image, 'w') as f:
137 f.truncate(self._BLOCKS * self._BLOCK_SIZE)
138 # Create an ext4 file system on the raw image.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800139 cros_build_lib.run([
140 '/sbin/mkfs.ext4', '-b',
141 str(self._BLOCK_SIZE), '-O', '^has_journal', self.dest_image
142 ],
143 capture_output=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700144 # Create the mount_point directory.
145 osutils.SafeMakedirs(mount_point)
146 # Mount the ext4 image.
147 osutils.MountDir(self.dest_image, mount_point, mount_opts=('loop', 'rw'))
Amin Hassanicc7ffce2019-01-11 14:57:52 -0800148
Xiaochu Liudeed0232018-06-26 10:25:34 -0700149 try:
Amin Hassani11a88cf2019-01-29 15:31:24 -0800150 self.SetupDlcImageFiles(mount_point)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700151 finally:
152 # Unmount the ext4 image.
153 osutils.UmountDir(mount_point)
154 # Shrink to minimum size.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800155 cros_build_lib.run(['/sbin/e2fsck', '-y', '-f', self.dest_image],
156 capture_output=True)
157 cros_build_lib.run(['/sbin/resize2fs', '-M', self.dest_image],
158 capture_output=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700159
160 def CreateSquashfsImage(self):
161 """Create a squashfs image."""
162 with osutils.TempDir(prefix='dlc_') as temp_dir:
Amin Hassani22a25eb2019-01-11 14:25:02 -0800163 squashfs_root = os.path.join(temp_dir, 'squashfs-root')
Amin Hassani11a88cf2019-01-29 15:31:24 -0800164 self.SetupDlcImageFiles(squashfs_root)
Amin Hassani22a25eb2019-01-11 14:25:02 -0800165
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800166 cros_build_lib.run([
167 'mksquashfs', squashfs_root, self.dest_image, '-4k-align', '-noappend'
168 ],
169 capture_output=True)
Amin Hassani22a25eb2019-01-11 14:25:02 -0800170
171 # We changed the ownership and permissions of the squashfs_root
172 # directory. Now we need to remove it manually.
173 osutils.RmDir(squashfs_root, sudo=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700174
Amin Hassani11a88cf2019-01-29 15:31:24 -0800175 def SetupDlcImageFiles(self, dlc_dir):
176 """Prepares the directory dlc_dir with all the files a DLC needs.
177
178 Args:
179 dlc_dir: (str) The path to where to setup files inside the DLC.
180 """
181 dlc_root_dir = os.path.join(dlc_dir, self._DLC_ROOT_DIR)
182 osutils.SafeMakedirs(dlc_root_dir)
Jae Hoon Kimd14646b2019-08-21 14:49:26 -0700183 osutils.CopyDirContents(self.src_dir, dlc_root_dir, symlinks=True)
Amin Hassani11a88cf2019-01-29 15:31:24 -0800184 self.PrepareLsbRelease(dlc_dir)
185 self.CollectExtraResources(dlc_dir)
186 self.SquashOwnerships(dlc_dir)
187
Amin Hassanid5742d32019-01-22 21:13:34 -0800188 def PrepareLsbRelease(self, dlc_dir):
189 """Prepare the file /etc/lsb-release in the DLC module.
190
191 This file is used dropping some identification parameters for the DLC.
192
193 Args:
194 dlc_dir: (str) The path to root directory of the DLC. e.g. mounted point
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800195 when we are creating the image.
Amin Hassanid5742d32019-01-22 21:13:34 -0800196 """
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800197 # Reading the platform APPID and creating the DLC APPID.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800198 platform_lsb_release = osutils.ReadFile(
199 os.path.join(self.sysroot, LSB_RELEASE))
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800200 app_id = None
201 for line in platform_lsb_release.split('\n'):
202 if line.startswith(cros_set_lsb_release.LSB_KEY_APPID_RELEASE):
203 app_id = line.split('=')[1]
204 if app_id is None:
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800205 raise Exception(
206 '%s does not have a valid key %s' %
207 (platform_lsb_release, cros_set_lsb_release.LSB_KEY_APPID_RELEASE))
Amin Hassanid5742d32019-01-22 21:13:34 -0800208
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400209 fields = (
210 (DLC_ID_KEY, self.dlc_id),
211 (DLC_PACKAGE_KEY, self.dlc_package),
212 (DLC_NAME_KEY, self.name),
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800213 # The DLC appid is generated by concatenating the platform appid with
214 # the DLC ID using an underscore. This pattern should never be changed
215 # once set otherwise it can break a lot of things!
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400216 (DLC_APPID_KEY, '%s_%s' % (app_id, self.dlc_id)),
217 )
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800218
219 lsb_release = os.path.join(dlc_dir, LSB_RELEASE)
220 osutils.SafeMakedirs(os.path.dirname(lsb_release))
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400221 content = ''.join('%s=%s\n' % (k, v) for k, v in fields)
Amin Hassanid5742d32019-01-22 21:13:34 -0800222 osutils.WriteFile(lsb_release, content)
223
Amin Hassani11a88cf2019-01-29 15:31:24 -0800224 def CollectExtraResources(self, dlc_dir):
225 """Collect the extra resources needed by the DLC module.
226
227 Look at the documentation around _EXTRA_RESOURCES.
228
229 Args:
230 dlc_dir: (str) The path to root directory of the DLC. e.g. mounted point
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800231 when we are creating the image.
Amin Hassani11a88cf2019-01-29 15:31:24 -0800232 """
233 for r in _EXTRA_RESOURCES:
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800234 source_path = os.path.join(self.sysroot, r)
Amin Hassani11a88cf2019-01-29 15:31:24 -0800235 target_path = os.path.join(dlc_dir, r)
236 osutils.SafeMakedirs(os.path.dirname(target_path))
237 shutil.copyfile(source_path, target_path)
238
Xiaochu Liudeed0232018-06-26 10:25:34 -0700239 def CreateImage(self):
240 """Create the image and copy the DLC files to it."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700241 logging.info('Creating the DLC image.')
Amin Hassani22a25eb2019-01-11 14:25:02 -0800242 if self.fs_type == _EXT4_TYPE:
Xiaochu Liudeed0232018-06-26 10:25:34 -0700243 self.CreateExt4Image()
Amin Hassani22a25eb2019-01-11 14:25:02 -0800244 elif self.fs_type == _SQUASHFS_TYPE:
Xiaochu Liudeed0232018-06-26 10:25:34 -0700245 self.CreateSquashfsImage()
246 else:
247 raise ValueError('Wrong fs type: %s used:' % self.fs_type)
248
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700249 def VerifyImageSize(self):
Xiaochu Liu36b30592019-08-06 09:39:54 -0700250 """Verify the image can fit to the reserved file."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700251 logging.info('Verifying the DLC image size.')
252 image_bytes = os.path.getsize(self.dest_image)
Xiaochu Liu36b30592019-08-06 09:39:54 -0700253 preallocated_bytes = self.pre_allocated_blocks * self._BLOCK_SIZE
254 # Verifies the actual size of the DLC image is NOT smaller than the
255 # preallocated space.
256 if preallocated_bytes < image_bytes:
257 raise ValueError(
258 'The DLC_PREALLOC_BLOCKS (%s) value set in DLC ebuild resulted in a '
259 'max size of DLC_PREALLOC_BLOCKS * 4K (%s) bytes the DLC image is '
260 'allowed to occupy. The value is smaller than the actual image size '
261 '(%s) required. Increase DLC_PREALLOC_BLOCKS in your ebuild to at '
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800262 'least %d.' %
263 (self.pre_allocated_blocks, preallocated_bytes, image_bytes,
264 self.GetOptimalImageBlockSize(image_bytes)))
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700265
266 def GetOptimalImageBlockSize(self, image_bytes):
267 """Given the image bytes, get the least amount of blocks required."""
268 return int(math.ceil(image_bytes / self._BLOCK_SIZE))
Xiaochu Liu36b30592019-08-06 09:39:54 -0700269
Xiaochu Liudeed0232018-06-26 10:25:34 -0700270 def GetImageloaderJsonContent(self, image_hash, table_hash, blocks):
271 """Return the content of imageloader.json file.
272
273 Args:
274 image_hash: (str) sha256 hash of the DLC image.
275 table_hash: (str) sha256 hash of the DLC table file.
276 blocks: (int) number of blocks in the DLC image.
277
278 Returns:
279 [str]: content of imageloader.json file.
280 """
281 return {
282 'fs-type': self.fs_type,
283 'id': self.dlc_id,
Amin Hassanib5a48042019-03-18 14:30:51 -0700284 'package': self.dlc_package,
Xiaochu Liudeed0232018-06-26 10:25:34 -0700285 'image-sha256-hash': image_hash,
286 'image-type': 'dlc',
287 'is-removable': True,
288 'manifest-version': self._MANIFEST_VERSION,
289 'name': self.name,
290 'pre-allocated-size': self.pre_allocated_blocks * self._BLOCK_SIZE,
291 'size': blocks * self._BLOCK_SIZE,
292 'table-sha256-hash': table_hash,
293 'version': self.version,
Jae Hoon Kim2b359e42020-01-03 12:54:40 -0800294 'preload-allowed': self.preload,
Xiaochu Liudeed0232018-06-26 10:25:34 -0700295 }
296
297 def GenerateVerity(self):
298 """Generate verity parameters and hashes for the image."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700299 logging.info('Generating DLC image verity.')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700300 with osutils.TempDir(prefix='dlc_') as temp_dir:
301 hash_tree = os.path.join(temp_dir, 'hash_tree')
302 # Get blocks in the image.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800303 blocks = math.ceil(os.path.getsize(self.dest_image) / self._BLOCK_SIZE)
304 result = cros_build_lib.run([
305 'verity', 'mode=create', 'alg=sha256', 'payload=' + self.dest_image,
306 'payload_blocks=' + str(blocks), 'hashtree=' + hash_tree,
307 'salt=random'
308 ],
309 capture_output=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700310 table = result.output
311
312 # Append the merkle tree to the image.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800313 osutils.WriteFile(
314 self.dest_image, osutils.ReadFile(hash_tree, mode='rb'), mode='a+b')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700315
316 # Write verity parameter to table file.
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400317 osutils.WriteFile(self.dest_table, table, mode='wb')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700318
319 # Compute image hash.
320 image_hash = HashFile(self.dest_image)
321 table_hash = HashFile(self.dest_table)
322 # Write image hash to imageloader.json file.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800323 blocks = math.ceil(os.path.getsize(self.dest_image) / self._BLOCK_SIZE)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700324 imageloader_json_content = self.GetImageloaderJsonContent(
325 image_hash, table_hash, int(blocks))
326 with open(self.dest_imageloader_json, 'w') as f:
327 json.dump(imageloader_json_content, f)
328
329 def GenerateDLC(self):
330 """Generate a DLC artifact."""
331 # Create the image and copy the DLC files to it.
332 self.CreateImage()
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700333 # Verify the image created is within preallocated size.
334 self.VerifyImageSize()
Xiaochu Liudeed0232018-06-26 10:25:34 -0700335 # Generate hash tree and other metadata.
336 self.GenerateVerity()
337
338
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800339def CopyAllDlcs(sysroot, install_root_dir):
340 """Copies all DLC image files into the images directory.
341
342 Copies the DLC image files in the given build directory into the given DLC
343 image directory. If the DLC build directory does not exist, or there is no DLC
344 for that board, this function does nothing.
345
346 Args:
347 sysroot: Path to directory containing DLC images, e.g /build/<board>.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800348 install_root_dir: Path to DLC output directory, e.g.
349 src/build/images/<board>/<version>.
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800350 """
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800351 build_dir = os.path.join(sysroot, DLC_IMAGE_DIR)
352
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700353 if not os.path.exists(build_dir):
Nicolas Norvezb2685752019-11-14 13:17:24 -0800354 logging.info('DLC build directory (%s) does not exist, ignoring.',
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700355 build_dir)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800356 return
357
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700358 if not os.listdir(build_dir):
359 logging.info('There are no DLC(s) to copy to output, ignoring.')
360 return
361
Amin Hassani6c0228b2019-03-04 13:42:33 -0800362 logging.info('Detected the following DLCs: %s',
363 ', '.join(os.listdir(build_dir)))
Jae Hoon Kim2d31c082019-12-04 10:39:53 -0800364 logging.info('Copying all DLC images from %s to %s.', build_dir,
365 install_root_dir)
366 osutils.SafeMakedirs(install_root_dir)
367 osutils.CopyDirContents(build_dir, install_root_dir)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800368
Amin Hassani6c0228b2019-03-04 13:42:33 -0800369 logging.info('Done copying the DLCs to their destination.')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800370
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800371
Xiaochu Liudeed0232018-06-26 10:25:34 -0700372def GetParser():
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800373 """Creates an argument parser and returns it."""
Xiaochu Liudeed0232018-06-26 10:25:34 -0700374 parser = commandline.ArgumentParser(description=__doc__)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800375 # This script is used both for building an individual DLC or copying all final
376 # DLCs images to their final destination nearby chromiumsos_test_image.bin,
377 # etc. These two arguments are required in both cases.
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800378 parser.add_argument(
379 '--sysroot',
380 type='path',
381 metavar='DIR',
382 required=True,
383 help="The root path to the board's build root, e.g. "
384 '/build/eve')
385 parser.add_argument(
386 '--install-root-dir',
387 type='path',
388 metavar='DIR',
389 required=True,
390 help='If building a specific DLC, it is the root path to'
391 ' install DLC images (%s) and metadata (%s). Otherwise it'
392 ' is the target directory where the Chrome OS images gets'
393 ' dropped in build_image, e.g. '
394 'src/build/images/<board>/latest.' % (DLC_IMAGE_DIR, DLC_META_DIR))
Amin Hassani22a25eb2019-01-11 14:25:02 -0800395
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800396 one_dlc = parser.add_argument_group('Arguments required for building only '
397 'one DLC')
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800398 one_dlc.add_argument(
399 '--src-dir',
400 type='path',
401 metavar='SRC_DIR_PATH',
402 help='Root directory path that contains all DLC files '
403 'to be packed.')
404 one_dlc.add_argument(
405 '--pre-allocated-blocks',
406 type=int,
407 metavar='PREALLOCATEDBLOCKS',
408 help='Number of blocks (block size is 4k) that need to'
409 'be pre-allocated on device.')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800410 one_dlc.add_argument('--version', metavar='VERSION', help='DLC Version.')
411 one_dlc.add_argument('--id', metavar='ID', help='DLC ID (unique per DLC).')
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800412 one_dlc.add_argument(
413 '--package',
414 metavar='PACKAGE',
415 help='The package ID that is unique within a DLC, One'
416 ' DLC cannot have duplicate package IDs.')
417 one_dlc.add_argument(
418 '--name', metavar='NAME', help='A human-readable name for the DLC.')
419 one_dlc.add_argument(
420 '--fs-type',
421 metavar='FS_TYPE',
422 default=_SQUASHFS_TYPE,
423 choices=(_SQUASHFS_TYPE, _EXT4_TYPE),
424 help='File system type of the image.')
425 one_dlc.add_argument(
426 '--preload',
427 default=False,
428 action='store_true',
429 help='Allow preloading of DLC.')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700430 return parser
431
432
Amin Hassanibc1a4792019-10-24 14:39:57 -0700433def ValidateDlcIdentifier(name):
434 """Validates the DLC identifiers like ID and package names.
435
436 The name specifications are:
437 - No underscore.
438 - First character should be only alphanumeric.
439 - Other characters can be alphanumeric and '-' (dash).
440 - Maximum length of 40 characters.
441
442 For more info see:
443 https://chromium.googlesource.com/chromiumos/platform2/+/master/dlcservice/docs/developer.md#create-a-dlc-module
444
445 Args:
446 name: The string to be validated.
447 """
448 if (not name or not re.match(r'^[a-zA-Z0-9][a-zA-Z0-9-]*$', name) or
449 len(name) > MAX_ID_NAME):
450 raise Exception('Invalid DLC identifier %s' % name)
451
452
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800453def ValidateArguments(opts):
454 """Validates the correctness of the passed arguments.
455
456 Args:
457 opts: Parsed arguments.
458 """
459 # Make sure if the intention is to build one DLC, all the required arguments
460 # are passed.
461 per_dlc_req_args = ('src_dir', 'pre_allocated_blocks', 'version', 'id',
Amin Hassanib5a48042019-03-18 14:30:51 -0700462 'package', 'name')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800463 if (opts.id and
464 not all(vars(opts)[arg] is not None for arg in per_dlc_req_args)):
465 raise Exception('If the intention is to build only one DLC, all the flags'
466 '%s required for it should be passed .' % per_dlc_req_args)
467
468 if opts.fs_type == _EXT4_TYPE:
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700469 raise Exception('ext4 unsupported for DLC, see https://crbug.com/890060')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800470
Amin Hassanibc1a4792019-10-24 14:39:57 -0700471 if opts.id:
472 ValidateDlcIdentifier(opts.id)
473 if opts.package:
474 ValidateDlcIdentifier(opts.package)
475
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800476
Xiaochu Liudeed0232018-06-26 10:25:34 -0700477def main(argv):
478 opts = GetParser().parse_args(argv)
479 opts.Freeze()
480
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800481 ValidateArguments(opts)
Amin Hassani2af75a92019-01-22 21:07:45 -0800482
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800483 if opts.id:
484 logging.info('Building DLC %s', opts.id)
Jae Hoon Kimfaca4b02020-01-09 13:49:03 -0800485 dlc_generator = DlcGenerator(
486 src_dir=opts.src_dir,
487 sysroot=opts.sysroot,
488 install_root_dir=opts.install_root_dir,
489 fs_type=opts.fs_type,
490 pre_allocated_blocks=opts.pre_allocated_blocks,
491 version=opts.version,
492 dlc_id=opts.id,
493 dlc_package=opts.package,
494 name=opts.name,
495 preload=opts.preload)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800496 dlc_generator.GenerateDLC()
497 else:
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800498 CopyAllDlcs(opts.sysroot, opts.install_root_dir)