blob: 85b98e06a52bd2054f514ba9f3853850d8f12f31 [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.
5
6"""Script to generate a DLC (Downloadable Content) artifact."""
7
Mike Frysinger93e8ffa2019-07-03 20:24:18 -04008from __future__ import division
Xiaochu Liudeed0232018-06-26 10:25:34 -07009from __future__ import print_function
10
11import hashlib
12import json
13import math
14import os
Amin Hassanibc1a4792019-10-24 14:39:57 -070015import re
Amin Hassani11a88cf2019-01-29 15:31:24 -080016import shutil
Xiaochu Liudeed0232018-06-26 10:25:34 -070017
18from chromite.lib import commandline
19from chromite.lib import cros_build_lib
Amin Hassanib97a5ee2019-01-23 14:44:43 -080020from chromite.lib import cros_logging as logging
Xiaochu Liudeed0232018-06-26 10:25:34 -070021from chromite.lib import osutils
22
Amin Hassani8f1cc0f2019-03-06 15:34:53 -080023from chromite.scripts import cros_set_lsb_release
Xiaochu Liudeed0232018-06-26 10:25:34 -070024
Amin Hassani2af75a92019-01-22 21:07:45 -080025DLC_META_DIR = 'opt/google/dlc/'
26DLC_IMAGE_DIR = 'build/rootfs/dlc/'
Amin Hassanid5742d32019-01-22 21:13:34 -080027LSB_RELEASE = 'etc/lsb-release'
28
Amin Hassani11a88cf2019-01-29 15:31:24 -080029# This file has major and minor version numbers that the update_engine client
30# supports. These values are needed for generating a delta/full payload.
31UPDATE_ENGINE_CONF = 'etc/update_engine.conf'
32
33_EXTRA_RESOURCES = (
34 UPDATE_ENGINE_CONF,
35)
36
Amin Hassanid5742d32019-01-22 21:13:34 -080037DLC_ID_KEY = 'DLC_ID'
Amin Hassanib5a48042019-03-18 14:30:51 -070038DLC_PACKAGE_KEY = 'DLC_PACKAGE'
Amin Hassanid5742d32019-01-22 21:13:34 -080039DLC_NAME_KEY = 'DLC_NAME'
Amin Hassani8f1cc0f2019-03-06 15:34:53 -080040DLC_APPID_KEY = 'DLC_RELEASE_APPID'
Amin Hassani2af75a92019-01-22 21:07:45 -080041
Amin Hassani22a25eb2019-01-11 14:25:02 -080042_SQUASHFS_TYPE = 'squashfs'
43_EXT4_TYPE = 'ext4'
44
Amin Hassanibc1a4792019-10-24 14:39:57 -070045MAX_ID_NAME = 40
Amin Hassanid5742d32019-01-22 21:13:34 -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
Amin Hassani174eb7e2019-01-18 11:11:24 -080063class DlcGenerator(object):
Xiaochu Liudeed0232018-06-26 10:25:34 -070064 """Object to generate DLC artifacts."""
65 # Block size for the DLC image.
66 # We use 4K for various reasons:
67 # 1. it's what imageloader (linux kernel) supports.
68 # 2. it's what verity supports.
69 _BLOCK_SIZE = 4096
70 # Blocks in the initial sparse image.
71 _BLOCKS = 500000
72 # Version of manifest file.
73 _MANIFEST_VERSION = 1
74
Amin Hassanicc7ffce2019-01-11 14:57:52 -080075 # The DLC root path inside the DLC module.
76 _DLC_ROOT_DIR = 'root'
77
Amin Hassanib97a5ee2019-01-23 14:44:43 -080078 def __init__(self, src_dir, sysroot, install_root_dir, fs_type,
Jae Hoon Kim2b359e42020-01-03 12:54:40 -080079 pre_allocated_blocks, version, dlc_id, dlc_package, name,
80 preload):
Xiaochu Liudeed0232018-06-26 10:25:34 -070081 """Object initializer.
82
83 Args:
Xiaochu Liudeed0232018-06-26 10:25:34 -070084 src_dir: (str) path to the DLC source root directory.
Amin Hassanib97a5ee2019-01-23 14:44:43 -080085 sysroot: (str) The path to the build root directory.
Amin Hassani2af75a92019-01-22 21:07:45 -080086 install_root_dir: (str) The path to the root installation directory.
Xiaochu Liudeed0232018-06-26 10:25:34 -070087 fs_type: (str) file system type.
88 pre_allocated_blocks: (int) number of blocks pre-allocated on device.
89 version: (str) DLC version.
90 dlc_id: (str) DLC ID.
Amin Hassanib5a48042019-03-18 14:30:51 -070091 dlc_package: (str) DLC Package.
Xiaochu Liudeed0232018-06-26 10:25:34 -070092 name: (str) DLC name.
Jae Hoon Kim2b359e42020-01-03 12:54:40 -080093 preload: (bool) allow for preloading DLC.
Xiaochu Liudeed0232018-06-26 10:25:34 -070094 """
95 self.src_dir = src_dir
Amin Hassanib97a5ee2019-01-23 14:44:43 -080096 self.sysroot = sysroot
Amin Hassani2af75a92019-01-22 21:07:45 -080097 self.install_root_dir = install_root_dir
Xiaochu Liudeed0232018-06-26 10:25:34 -070098 self.fs_type = fs_type
99 self.pre_allocated_blocks = pre_allocated_blocks
100 self.version = version
101 self.dlc_id = dlc_id
Amin Hassanib5a48042019-03-18 14:30:51 -0700102 self.dlc_package = dlc_package
Xiaochu Liudeed0232018-06-26 10:25:34 -0700103 self.name = name
Jae Hoon Kim2b359e42020-01-03 12:54:40 -0800104 self.preload = preload
Amin Hassani2af75a92019-01-22 21:07:45 -0800105
106 self.meta_dir = os.path.join(self.install_root_dir, DLC_META_DIR,
Amin Hassanib5a48042019-03-18 14:30:51 -0700107 self.dlc_id, self.dlc_package)
Amin Hassani2af75a92019-01-22 21:07:45 -0800108 self.image_dir = os.path.join(self.install_root_dir, DLC_IMAGE_DIR,
Amin Hassanib5a48042019-03-18 14:30:51 -0700109 self.dlc_id, self.dlc_package)
Amin Hassani2af75a92019-01-22 21:07:45 -0800110 osutils.SafeMakedirs(self.meta_dir)
111 osutils.SafeMakedirs(self.image_dir)
112
Xiaochu Liudeed0232018-06-26 10:25:34 -0700113 # Create path for all final artifacts.
Amin Hassanib5a48042019-03-18 14:30:51 -0700114 self.dest_image = os.path.join(self.image_dir, 'dlc.img')
Amin Hassani2af75a92019-01-22 21:07:45 -0800115 self.dest_table = os.path.join(self.meta_dir, 'table')
116 self.dest_imageloader_json = os.path.join(self.meta_dir, 'imageloader.json')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700117
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700118 # Log out the member variable values initially set.
119 logging.debug('Initial internal values of DlcGenerator: %s',
120 json.dumps(self.__dict__, sort_keys=True))
121
Xiaochu Liudeed0232018-06-26 10:25:34 -0700122 def SquashOwnerships(self, path):
123 """Squash the owernships & permissions for files.
124
125 Args:
126 path: (str) path that contains all files to be processed.
127 """
Mike Frysinger45602c72019-09-22 02:15:11 -0400128 cros_build_lib.sudo_run(['chown', '-R', '0:0', path])
129 cros_build_lib.sudo_run(
Xiaochu Liudeed0232018-06-26 10:25:34 -0700130 ['find', path, '-exec', 'touch', '-h', '-t', '197001010000.00', '{}',
131 '+'])
132
133 def CreateExt4Image(self):
134 """Create an ext4 image."""
135 with osutils.TempDir(prefix='dlc_') as temp_dir:
136 mount_point = os.path.join(temp_dir, 'mount_point')
137 # Create a raw image file.
138 with open(self.dest_image, 'w') as f:
139 f.truncate(self._BLOCKS * self._BLOCK_SIZE)
140 # Create an ext4 file system on the raw image.
Mike Frysinger45602c72019-09-22 02:15:11 -0400141 cros_build_lib.run(
Xiaochu Liudeed0232018-06-26 10:25:34 -0700142 ['/sbin/mkfs.ext4', '-b', str(self._BLOCK_SIZE), '-O',
143 '^has_journal', self.dest_image], capture_output=True)
144 # 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.
Mike Frysinger45602c72019-09-22 02:15:11 -0400155 cros_build_lib.run(
Xiaochu Liudeed0232018-06-26 10:25:34 -0700156 ['/sbin/e2fsck', '-y', '-f', self.dest_image], capture_output=True)
Mike Frysinger45602c72019-09-22 02:15:11 -0400157 cros_build_lib.run(
Xiaochu Liudeed0232018-06-26 10:25:34 -0700158 ['/sbin/resize2fs', '-M', self.dest_image], capture_output=True)
159
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
Mike Frysinger45602c72019-09-22 02:15:11 -0400166 cros_build_lib.run(['mksquashfs', squashfs_root, self.dest_image,
167 '-4k-align', '-noappend'], capture_output=True)
Amin Hassani22a25eb2019-01-11 14:25:02 -0800168
169 # We changed the ownership and permissions of the squashfs_root
170 # directory. Now we need to remove it manually.
171 osutils.RmDir(squashfs_root, sudo=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700172
Amin Hassani11a88cf2019-01-29 15:31:24 -0800173 def SetupDlcImageFiles(self, dlc_dir):
174 """Prepares the directory dlc_dir with all the files a DLC needs.
175
176 Args:
177 dlc_dir: (str) The path to where to setup files inside the DLC.
178 """
179 dlc_root_dir = os.path.join(dlc_dir, self._DLC_ROOT_DIR)
180 osutils.SafeMakedirs(dlc_root_dir)
Jae Hoon Kimd14646b2019-08-21 14:49:26 -0700181 osutils.CopyDirContents(self.src_dir, dlc_root_dir, symlinks=True)
Amin Hassani11a88cf2019-01-29 15:31:24 -0800182 self.PrepareLsbRelease(dlc_dir)
183 self.CollectExtraResources(dlc_dir)
184 self.SquashOwnerships(dlc_dir)
185
Amin Hassanid5742d32019-01-22 21:13:34 -0800186 def PrepareLsbRelease(self, dlc_dir):
187 """Prepare the file /etc/lsb-release in the DLC module.
188
189 This file is used dropping some identification parameters for the DLC.
190
191 Args:
192 dlc_dir: (str) The path to root directory of the DLC. e.g. mounted point
193 when we are creating the image.
194 """
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800195 # Reading the platform APPID and creating the DLC APPID.
196 platform_lsb_release = osutils.ReadFile(os.path.join(self.sysroot,
197 LSB_RELEASE))
198 app_id = None
199 for line in platform_lsb_release.split('\n'):
200 if line.startswith(cros_set_lsb_release.LSB_KEY_APPID_RELEASE):
201 app_id = line.split('=')[1]
202 if app_id is None:
203 raise Exception('%s does not have a valid key %s' %
204 (platform_lsb_release,
205 cros_set_lsb_release.LSB_KEY_APPID_RELEASE))
Amin Hassanid5742d32019-01-22 21:13:34 -0800206
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400207 fields = (
208 (DLC_ID_KEY, self.dlc_id),
209 (DLC_PACKAGE_KEY, self.dlc_package),
210 (DLC_NAME_KEY, self.name),
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800211 # The DLC appid is generated by concatenating the platform appid with
212 # the DLC ID using an underscore. This pattern should never be changed
213 # once set otherwise it can break a lot of things!
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400214 (DLC_APPID_KEY, '%s_%s' % (app_id, self.dlc_id)),
215 )
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800216
217 lsb_release = os.path.join(dlc_dir, LSB_RELEASE)
218 osutils.SafeMakedirs(os.path.dirname(lsb_release))
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400219 content = ''.join('%s=%s\n' % (k, v) for k, v in fields)
Amin Hassanid5742d32019-01-22 21:13:34 -0800220 osutils.WriteFile(lsb_release, content)
221
Amin Hassani11a88cf2019-01-29 15:31:24 -0800222 def CollectExtraResources(self, dlc_dir):
223 """Collect the extra resources needed by the DLC module.
224
225 Look at the documentation around _EXTRA_RESOURCES.
226
227 Args:
228 dlc_dir: (str) The path to root directory of the DLC. e.g. mounted point
229 when we are creating the image.
230 """
231 for r in _EXTRA_RESOURCES:
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800232 source_path = os.path.join(self.sysroot, r)
Amin Hassani11a88cf2019-01-29 15:31:24 -0800233 target_path = os.path.join(dlc_dir, r)
234 osutils.SafeMakedirs(os.path.dirname(target_path))
235 shutil.copyfile(source_path, target_path)
236
Xiaochu Liudeed0232018-06-26 10:25:34 -0700237 def CreateImage(self):
238 """Create the image and copy the DLC files to it."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700239 logging.info('Creating the DLC image.')
Amin Hassani22a25eb2019-01-11 14:25:02 -0800240 if self.fs_type == _EXT4_TYPE:
Xiaochu Liudeed0232018-06-26 10:25:34 -0700241 self.CreateExt4Image()
Amin Hassani22a25eb2019-01-11 14:25:02 -0800242 elif self.fs_type == _SQUASHFS_TYPE:
Xiaochu Liudeed0232018-06-26 10:25:34 -0700243 self.CreateSquashfsImage()
244 else:
245 raise ValueError('Wrong fs type: %s used:' % self.fs_type)
246
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700247 def VerifyImageSize(self):
Xiaochu Liu36b30592019-08-06 09:39:54 -0700248 """Verify the image can fit to the reserved file."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700249 logging.info('Verifying the DLC image size.')
250 image_bytes = os.path.getsize(self.dest_image)
Xiaochu Liu36b30592019-08-06 09:39:54 -0700251 preallocated_bytes = self.pre_allocated_blocks * self._BLOCK_SIZE
252 # Verifies the actual size of the DLC image is NOT smaller than the
253 # preallocated space.
254 if preallocated_bytes < image_bytes:
255 raise ValueError(
256 'The DLC_PREALLOC_BLOCKS (%s) value set in DLC ebuild resulted in a '
257 'max size of DLC_PREALLOC_BLOCKS * 4K (%s) bytes the DLC image is '
258 'allowed to occupy. The value is smaller than the actual image size '
259 '(%s) required. Increase DLC_PREALLOC_BLOCKS in your ebuild to at '
260 'least %d.' % (
261 self.pre_allocated_blocks, preallocated_bytes, image_bytes,
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700262 self.GetOptimalImageBlockSize(image_bytes)))
263
264 def GetOptimalImageBlockSize(self, image_bytes):
265 """Given the image bytes, get the least amount of blocks required."""
266 return int(math.ceil(image_bytes / self._BLOCK_SIZE))
Xiaochu Liu36b30592019-08-06 09:39:54 -0700267
Xiaochu Liudeed0232018-06-26 10:25:34 -0700268 def GetImageloaderJsonContent(self, image_hash, table_hash, blocks):
269 """Return the content of imageloader.json file.
270
271 Args:
272 image_hash: (str) sha256 hash of the DLC image.
273 table_hash: (str) sha256 hash of the DLC table file.
274 blocks: (int) number of blocks in the DLC image.
275
276 Returns:
277 [str]: content of imageloader.json file.
278 """
279 return {
280 'fs-type': self.fs_type,
281 'id': self.dlc_id,
Amin Hassanib5a48042019-03-18 14:30:51 -0700282 'package': self.dlc_package,
Xiaochu Liudeed0232018-06-26 10:25:34 -0700283 'image-sha256-hash': image_hash,
284 'image-type': 'dlc',
285 'is-removable': True,
286 'manifest-version': self._MANIFEST_VERSION,
287 'name': self.name,
288 'pre-allocated-size': self.pre_allocated_blocks * self._BLOCK_SIZE,
289 'size': blocks * self._BLOCK_SIZE,
290 'table-sha256-hash': table_hash,
291 'version': self.version,
Jae Hoon Kim2b359e42020-01-03 12:54:40 -0800292 'preload-allowed': self.preload,
Xiaochu Liudeed0232018-06-26 10:25:34 -0700293 }
294
295 def GenerateVerity(self):
296 """Generate verity parameters and hashes for the image."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700297 logging.info('Generating DLC image verity.')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700298 with osutils.TempDir(prefix='dlc_') as temp_dir:
299 hash_tree = os.path.join(temp_dir, 'hash_tree')
300 # Get blocks in the image.
301 blocks = math.ceil(
302 os.path.getsize(self.dest_image) / self._BLOCK_SIZE)
Mike Frysinger45602c72019-09-22 02:15:11 -0400303 result = cros_build_lib.run(
Xiaochu Liudeed0232018-06-26 10:25:34 -0700304 ['verity', 'mode=create', 'alg=sha256', 'payload=' + self.dest_image,
305 'payload_blocks=' + str(blocks), 'hashtree=' + hash_tree,
306 'salt=random'], capture_output=True)
307 table = result.output
308
309 # Append the merkle tree to the image.
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400310 osutils.WriteFile(self.dest_image, osutils.ReadFile(hash_tree, mode='rb'),
311 mode='a+b')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700312
313 # Write verity parameter to table file.
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400314 osutils.WriteFile(self.dest_table, table, mode='wb')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700315
316 # Compute image hash.
317 image_hash = HashFile(self.dest_image)
318 table_hash = HashFile(self.dest_table)
319 # Write image hash to imageloader.json file.
320 blocks = math.ceil(
321 os.path.getsize(self.dest_image) / self._BLOCK_SIZE)
322 imageloader_json_content = self.GetImageloaderJsonContent(
323 image_hash, table_hash, int(blocks))
324 with open(self.dest_imageloader_json, 'w') as f:
325 json.dump(imageloader_json_content, f)
326
327 def GenerateDLC(self):
328 """Generate a DLC artifact."""
329 # Create the image and copy the DLC files to it.
330 self.CreateImage()
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700331 # Verify the image created is within preallocated size.
332 self.VerifyImageSize()
Xiaochu Liudeed0232018-06-26 10:25:34 -0700333 # Generate hash tree and other metadata.
334 self.GenerateVerity()
335
336
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800337def CopyAllDlcs(sysroot, install_root_dir):
338 """Copies all DLC image files into the images directory.
339
340 Copies the DLC image files in the given build directory into the given DLC
341 image directory. If the DLC build directory does not exist, or there is no DLC
342 for that board, this function does nothing.
343
344 Args:
345 sysroot: Path to directory containing DLC images, e.g /build/<board>.
346 install_root_dir: Path to DLC output directory,
347 e.g. src/build/images/<board>/<version>.
348 """
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800349 build_dir = os.path.join(sysroot, DLC_IMAGE_DIR)
350
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700351 if not os.path.exists(build_dir):
Nicolas Norvezb2685752019-11-14 13:17:24 -0800352 logging.info('DLC build directory (%s) does not exist, ignoring.',
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700353 build_dir)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800354 return
355
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700356 if not os.listdir(build_dir):
357 logging.info('There are no DLC(s) to copy to output, ignoring.')
358 return
359
Amin Hassani6c0228b2019-03-04 13:42:33 -0800360 logging.info('Detected the following DLCs: %s',
361 ', '.join(os.listdir(build_dir)))
Jae Hoon Kim2d31c082019-12-04 10:39:53 -0800362 logging.info('Copying all DLC images from %s to %s.', build_dir,
363 install_root_dir)
364 osutils.SafeMakedirs(install_root_dir)
365 osutils.CopyDirContents(build_dir, install_root_dir)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800366
Amin Hassani6c0228b2019-03-04 13:42:33 -0800367 logging.info('Done copying the DLCs to their destination.')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800368
Xiaochu Liudeed0232018-06-26 10:25:34 -0700369def GetParser():
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800370 """Creates an argument parser and returns it."""
Xiaochu Liudeed0232018-06-26 10:25:34 -0700371 parser = commandline.ArgumentParser(description=__doc__)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800372 # This script is used both for building an individual DLC or copying all final
373 # DLCs images to their final destination nearby chromiumsos_test_image.bin,
374 # etc. These two arguments are required in both cases.
375 parser.add_argument('--sysroot', type='path', metavar='DIR', required=True,
376 help="The root path to the board's build root, e.g. "
Mike Frysinger80de5012019-08-01 14:10:53 -0400377 '/build/eve')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800378 parser.add_argument('--install-root-dir', type='path', metavar='DIR',
379 required=True,
380 help='If building a specific DLC, it is the root path to'
381 ' install DLC images (%s) and metadata (%s). Otherwise it'
382 ' is the target directory where the Chrome OS images gets'
383 ' dropped in build_image, e.g. '
384 'src/build/images/<board>/latest.' % (DLC_IMAGE_DIR,
385 DLC_META_DIR))
Amin Hassani22a25eb2019-01-11 14:25:02 -0800386
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800387 one_dlc = parser.add_argument_group('Arguments required for building only '
388 'one DLC')
389 one_dlc.add_argument('--src-dir', type='path', metavar='SRC_DIR_PATH',
390 help='Root directory path that contains all DLC files '
391 'to be packed.')
392 one_dlc.add_argument('--pre-allocated-blocks', type=int,
393 metavar='PREALLOCATEDBLOCKS',
394 help='Number of blocks (block size is 4k) that need to'
395 'be pre-allocated on device.')
396 one_dlc.add_argument('--version', metavar='VERSION', help='DLC Version.')
397 one_dlc.add_argument('--id', metavar='ID', help='DLC ID (unique per DLC).')
Amin Hassanib5a48042019-03-18 14:30:51 -0700398 one_dlc.add_argument('--package', metavar='PACKAGE',
399 help='The package ID that is unique within a DLC, One'
400 ' DLC cannot have duplicate package IDs.')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800401 one_dlc.add_argument('--name', metavar='NAME',
402 help='A human-readable name for the DLC.')
403 one_dlc.add_argument('--fs-type', metavar='FS_TYPE', default=_SQUASHFS_TYPE,
404 choices=(_SQUASHFS_TYPE, _EXT4_TYPE),
405 help='File system type of the image.')
Jae Hoon Kim2b359e42020-01-03 12:54:40 -0800406 one_dlc.add_argument('--preload', default=False, action='store_true',
407 help='Allow preloading of DLC.')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700408 return parser
409
410
Amin Hassanibc1a4792019-10-24 14:39:57 -0700411def ValidateDlcIdentifier(name):
412 """Validates the DLC identifiers like ID and package names.
413
414 The name specifications are:
415 - No underscore.
416 - First character should be only alphanumeric.
417 - Other characters can be alphanumeric and '-' (dash).
418 - Maximum length of 40 characters.
419
420 For more info see:
421 https://chromium.googlesource.com/chromiumos/platform2/+/master/dlcservice/docs/developer.md#create-a-dlc-module
422
423 Args:
424 name: The string to be validated.
425 """
426 if (not name or not re.match(r'^[a-zA-Z0-9][a-zA-Z0-9-]*$', name) or
427 len(name) > MAX_ID_NAME):
428 raise Exception('Invalid DLC identifier %s' % name)
429
430
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800431def ValidateArguments(opts):
432 """Validates the correctness of the passed arguments.
433
434 Args:
435 opts: Parsed arguments.
436 """
437 # Make sure if the intention is to build one DLC, all the required arguments
438 # are passed.
439 per_dlc_req_args = ('src_dir', 'pre_allocated_blocks', 'version', 'id',
Amin Hassanib5a48042019-03-18 14:30:51 -0700440 'package', 'name')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800441 if (opts.id and
442 not all(vars(opts)[arg] is not None for arg in per_dlc_req_args)):
443 raise Exception('If the intention is to build only one DLC, all the flags'
444 '%s required for it should be passed .' % per_dlc_req_args)
445
446 if opts.fs_type == _EXT4_TYPE:
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700447 raise Exception('ext4 unsupported for DLC, see https://crbug.com/890060')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800448
Amin Hassanibc1a4792019-10-24 14:39:57 -0700449 if opts.id:
450 ValidateDlcIdentifier(opts.id)
451 if opts.package:
452 ValidateDlcIdentifier(opts.package)
453
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800454
Xiaochu Liudeed0232018-06-26 10:25:34 -0700455def main(argv):
456 opts = GetParser().parse_args(argv)
457 opts.Freeze()
458
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800459 ValidateArguments(opts)
Amin Hassani2af75a92019-01-22 21:07:45 -0800460
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800461 if opts.id:
462 logging.info('Building DLC %s', opts.id)
463 dlc_generator = DlcGenerator(src_dir=opts.src_dir,
464 sysroot=opts.sysroot,
465 install_root_dir=opts.install_root_dir,
466 fs_type=opts.fs_type,
467 pre_allocated_blocks=opts.pre_allocated_blocks,
468 version=opts.version,
469 dlc_id=opts.id,
Amin Hassanib5a48042019-03-18 14:30:51 -0700470 dlc_package=opts.package,
Jae Hoon Kim2b359e42020-01-03 12:54:40 -0800471 name=opts.name,
472 preload=opts.preload)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800473 dlc_generator.GenerateDLC()
474 else:
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800475 CopyAllDlcs(opts.sysroot, opts.install_root_dir)