blob: d48e9ceda13ab2c94d8b67d143c2b3dd2c619620 [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,
Amin Hassanib5a48042019-03-18 14:30:51 -070079 pre_allocated_blocks, version, dlc_id, dlc_package, name):
Xiaochu Liudeed0232018-06-26 10:25:34 -070080 """Object initializer.
81
82 Args:
Xiaochu Liudeed0232018-06-26 10:25:34 -070083 src_dir: (str) path to the DLC source root directory.
Amin Hassanib97a5ee2019-01-23 14:44:43 -080084 sysroot: (str) The path to the build root directory.
Amin Hassani2af75a92019-01-22 21:07:45 -080085 install_root_dir: (str) The path to the root installation directory.
Xiaochu Liudeed0232018-06-26 10:25:34 -070086 fs_type: (str) file system type.
87 pre_allocated_blocks: (int) number of blocks pre-allocated on device.
88 version: (str) DLC version.
89 dlc_id: (str) DLC ID.
Amin Hassanib5a48042019-03-18 14:30:51 -070090 dlc_package: (str) DLC Package.
Xiaochu Liudeed0232018-06-26 10:25:34 -070091 name: (str) DLC name.
92 """
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
Amin Hassani2af75a92019-01-22 21:07:45 -0800102
103 self.meta_dir = os.path.join(self.install_root_dir, DLC_META_DIR,
Amin Hassanib5a48042019-03-18 14:30:51 -0700104 self.dlc_id, self.dlc_package)
Amin Hassani2af75a92019-01-22 21:07:45 -0800105 self.image_dir = os.path.join(self.install_root_dir, DLC_IMAGE_DIR,
Amin Hassanib5a48042019-03-18 14:30:51 -0700106 self.dlc_id, self.dlc_package)
Amin Hassani2af75a92019-01-22 21:07:45 -0800107 osutils.SafeMakedirs(self.meta_dir)
108 osutils.SafeMakedirs(self.image_dir)
109
Xiaochu Liudeed0232018-06-26 10:25:34 -0700110 # Create path for all final artifacts.
Amin Hassanib5a48042019-03-18 14:30:51 -0700111 self.dest_image = os.path.join(self.image_dir, 'dlc.img')
Amin Hassani2af75a92019-01-22 21:07:45 -0800112 self.dest_table = os.path.join(self.meta_dir, 'table')
113 self.dest_imageloader_json = os.path.join(self.meta_dir, 'imageloader.json')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700114
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700115 # Log out the member variable values initially set.
116 logging.debug('Initial internal values of DlcGenerator: %s',
117 json.dumps(self.__dict__, sort_keys=True))
118
Xiaochu Liudeed0232018-06-26 10:25:34 -0700119 def SquashOwnerships(self, path):
120 """Squash the owernships & permissions for files.
121
122 Args:
123 path: (str) path that contains all files to be processed.
124 """
Mike Frysinger45602c72019-09-22 02:15:11 -0400125 cros_build_lib.sudo_run(['chown', '-R', '0:0', path])
126 cros_build_lib.sudo_run(
Xiaochu Liudeed0232018-06-26 10:25:34 -0700127 ['find', path, '-exec', 'touch', '-h', '-t', '197001010000.00', '{}',
128 '+'])
129
130 def CreateExt4Image(self):
131 """Create an ext4 image."""
132 with osutils.TempDir(prefix='dlc_') as temp_dir:
133 mount_point = os.path.join(temp_dir, 'mount_point')
134 # Create a raw image file.
135 with open(self.dest_image, 'w') as f:
136 f.truncate(self._BLOCKS * self._BLOCK_SIZE)
137 # Create an ext4 file system on the raw image.
Mike Frysinger45602c72019-09-22 02:15:11 -0400138 cros_build_lib.run(
Xiaochu Liudeed0232018-06-26 10:25:34 -0700139 ['/sbin/mkfs.ext4', '-b', str(self._BLOCK_SIZE), '-O',
140 '^has_journal', self.dest_image], capture_output=True)
141 # Create the mount_point directory.
142 osutils.SafeMakedirs(mount_point)
143 # Mount the ext4 image.
144 osutils.MountDir(self.dest_image, mount_point, mount_opts=('loop', 'rw'))
Amin Hassanicc7ffce2019-01-11 14:57:52 -0800145
Xiaochu Liudeed0232018-06-26 10:25:34 -0700146 try:
Amin Hassani11a88cf2019-01-29 15:31:24 -0800147 self.SetupDlcImageFiles(mount_point)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700148 finally:
149 # Unmount the ext4 image.
150 osutils.UmountDir(mount_point)
151 # Shrink to minimum size.
Mike Frysinger45602c72019-09-22 02:15:11 -0400152 cros_build_lib.run(
Xiaochu Liudeed0232018-06-26 10:25:34 -0700153 ['/sbin/e2fsck', '-y', '-f', self.dest_image], capture_output=True)
Mike Frysinger45602c72019-09-22 02:15:11 -0400154 cros_build_lib.run(
Xiaochu Liudeed0232018-06-26 10:25:34 -0700155 ['/sbin/resize2fs', '-M', self.dest_image], capture_output=True)
156
157 def CreateSquashfsImage(self):
158 """Create a squashfs image."""
159 with osutils.TempDir(prefix='dlc_') as temp_dir:
Amin Hassani22a25eb2019-01-11 14:25:02 -0800160 squashfs_root = os.path.join(temp_dir, 'squashfs-root')
Amin Hassani11a88cf2019-01-29 15:31:24 -0800161 self.SetupDlcImageFiles(squashfs_root)
Amin Hassani22a25eb2019-01-11 14:25:02 -0800162
Mike Frysinger45602c72019-09-22 02:15:11 -0400163 cros_build_lib.run(['mksquashfs', squashfs_root, self.dest_image,
164 '-4k-align', '-noappend'], capture_output=True)
Amin Hassani22a25eb2019-01-11 14:25:02 -0800165
166 # We changed the ownership and permissions of the squashfs_root
167 # directory. Now we need to remove it manually.
168 osutils.RmDir(squashfs_root, sudo=True)
Xiaochu Liudeed0232018-06-26 10:25:34 -0700169
Amin Hassani11a88cf2019-01-29 15:31:24 -0800170 def SetupDlcImageFiles(self, dlc_dir):
171 """Prepares the directory dlc_dir with all the files a DLC needs.
172
173 Args:
174 dlc_dir: (str) The path to where to setup files inside the DLC.
175 """
176 dlc_root_dir = os.path.join(dlc_dir, self._DLC_ROOT_DIR)
177 osutils.SafeMakedirs(dlc_root_dir)
Jae Hoon Kimd14646b2019-08-21 14:49:26 -0700178 osutils.CopyDirContents(self.src_dir, dlc_root_dir, symlinks=True)
Amin Hassani11a88cf2019-01-29 15:31:24 -0800179 self.PrepareLsbRelease(dlc_dir)
180 self.CollectExtraResources(dlc_dir)
181 self.SquashOwnerships(dlc_dir)
182
Amin Hassanid5742d32019-01-22 21:13:34 -0800183 def PrepareLsbRelease(self, dlc_dir):
184 """Prepare the file /etc/lsb-release in the DLC module.
185
186 This file is used dropping some identification parameters for the DLC.
187
188 Args:
189 dlc_dir: (str) The path to root directory of the DLC. e.g. mounted point
190 when we are creating the image.
191 """
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800192 # Reading the platform APPID and creating the DLC APPID.
193 platform_lsb_release = osutils.ReadFile(os.path.join(self.sysroot,
194 LSB_RELEASE))
195 app_id = None
196 for line in platform_lsb_release.split('\n'):
197 if line.startswith(cros_set_lsb_release.LSB_KEY_APPID_RELEASE):
198 app_id = line.split('=')[1]
199 if app_id is None:
200 raise Exception('%s does not have a valid key %s' %
201 (platform_lsb_release,
202 cros_set_lsb_release.LSB_KEY_APPID_RELEASE))
Amin Hassanid5742d32019-01-22 21:13:34 -0800203
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400204 fields = (
205 (DLC_ID_KEY, self.dlc_id),
206 (DLC_PACKAGE_KEY, self.dlc_package),
207 (DLC_NAME_KEY, self.name),
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800208 # The DLC appid is generated by concatenating the platform appid with
209 # the DLC ID using an underscore. This pattern should never be changed
210 # once set otherwise it can break a lot of things!
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400211 (DLC_APPID_KEY, '%s_%s' % (app_id, self.dlc_id)),
212 )
Amin Hassani8f1cc0f2019-03-06 15:34:53 -0800213
214 lsb_release = os.path.join(dlc_dir, LSB_RELEASE)
215 osutils.SafeMakedirs(os.path.dirname(lsb_release))
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400216 content = ''.join('%s=%s\n' % (k, v) for k, v in fields)
Amin Hassanid5742d32019-01-22 21:13:34 -0800217 osutils.WriteFile(lsb_release, content)
218
Amin Hassani11a88cf2019-01-29 15:31:24 -0800219 def CollectExtraResources(self, dlc_dir):
220 """Collect the extra resources needed by the DLC module.
221
222 Look at the documentation around _EXTRA_RESOURCES.
223
224 Args:
225 dlc_dir: (str) The path to root directory of the DLC. e.g. mounted point
226 when we are creating the image.
227 """
228 for r in _EXTRA_RESOURCES:
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800229 source_path = os.path.join(self.sysroot, r)
Amin Hassani11a88cf2019-01-29 15:31:24 -0800230 target_path = os.path.join(dlc_dir, r)
231 osutils.SafeMakedirs(os.path.dirname(target_path))
232 shutil.copyfile(source_path, target_path)
233
Xiaochu Liudeed0232018-06-26 10:25:34 -0700234 def CreateImage(self):
235 """Create the image and copy the DLC files to it."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700236 logging.info('Creating the DLC image.')
Amin Hassani22a25eb2019-01-11 14:25:02 -0800237 if self.fs_type == _EXT4_TYPE:
Xiaochu Liudeed0232018-06-26 10:25:34 -0700238 self.CreateExt4Image()
Amin Hassani22a25eb2019-01-11 14:25:02 -0800239 elif self.fs_type == _SQUASHFS_TYPE:
Xiaochu Liudeed0232018-06-26 10:25:34 -0700240 self.CreateSquashfsImage()
241 else:
242 raise ValueError('Wrong fs type: %s used:' % self.fs_type)
243
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700244 def VerifyImageSize(self):
Xiaochu Liu36b30592019-08-06 09:39:54 -0700245 """Verify the image can fit to the reserved file."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700246 logging.info('Verifying the DLC image size.')
247 image_bytes = os.path.getsize(self.dest_image)
Xiaochu Liu36b30592019-08-06 09:39:54 -0700248 preallocated_bytes = self.pre_allocated_blocks * self._BLOCK_SIZE
249 # Verifies the actual size of the DLC image is NOT smaller than the
250 # preallocated space.
251 if preallocated_bytes < image_bytes:
252 raise ValueError(
253 'The DLC_PREALLOC_BLOCKS (%s) value set in DLC ebuild resulted in a '
254 'max size of DLC_PREALLOC_BLOCKS * 4K (%s) bytes the DLC image is '
255 'allowed to occupy. The value is smaller than the actual image size '
256 '(%s) required. Increase DLC_PREALLOC_BLOCKS in your ebuild to at '
257 'least %d.' % (
258 self.pre_allocated_blocks, preallocated_bytes, image_bytes,
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700259 self.GetOptimalImageBlockSize(image_bytes)))
260
261 def GetOptimalImageBlockSize(self, image_bytes):
262 """Given the image bytes, get the least amount of blocks required."""
263 return int(math.ceil(image_bytes / self._BLOCK_SIZE))
Xiaochu Liu36b30592019-08-06 09:39:54 -0700264
Xiaochu Liudeed0232018-06-26 10:25:34 -0700265 def GetImageloaderJsonContent(self, image_hash, table_hash, blocks):
266 """Return the content of imageloader.json file.
267
268 Args:
269 image_hash: (str) sha256 hash of the DLC image.
270 table_hash: (str) sha256 hash of the DLC table file.
271 blocks: (int) number of blocks in the DLC image.
272
273 Returns:
274 [str]: content of imageloader.json file.
275 """
276 return {
277 'fs-type': self.fs_type,
278 'id': self.dlc_id,
Amin Hassanib5a48042019-03-18 14:30:51 -0700279 'package': self.dlc_package,
Xiaochu Liudeed0232018-06-26 10:25:34 -0700280 'image-sha256-hash': image_hash,
281 'image-type': 'dlc',
282 'is-removable': True,
283 'manifest-version': self._MANIFEST_VERSION,
284 'name': self.name,
285 'pre-allocated-size': self.pre_allocated_blocks * self._BLOCK_SIZE,
286 'size': blocks * self._BLOCK_SIZE,
287 'table-sha256-hash': table_hash,
288 'version': self.version,
289 }
290
291 def GenerateVerity(self):
292 """Generate verity parameters and hashes for the image."""
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700293 logging.info('Generating DLC image verity.')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700294 with osutils.TempDir(prefix='dlc_') as temp_dir:
295 hash_tree = os.path.join(temp_dir, 'hash_tree')
296 # Get blocks in the image.
297 blocks = math.ceil(
298 os.path.getsize(self.dest_image) / self._BLOCK_SIZE)
Mike Frysinger45602c72019-09-22 02:15:11 -0400299 result = cros_build_lib.run(
Xiaochu Liudeed0232018-06-26 10:25:34 -0700300 ['verity', 'mode=create', 'alg=sha256', 'payload=' + self.dest_image,
301 'payload_blocks=' + str(blocks), 'hashtree=' + hash_tree,
302 'salt=random'], capture_output=True)
303 table = result.output
304
305 # Append the merkle tree to the image.
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400306 osutils.WriteFile(self.dest_image, osutils.ReadFile(hash_tree, mode='rb'),
307 mode='a+b')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700308
309 # Write verity parameter to table file.
Mike Frysinger1c834ea2019-10-14 04:29:41 -0400310 osutils.WriteFile(self.dest_table, table, mode='wb')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700311
312 # Compute image hash.
313 image_hash = HashFile(self.dest_image)
314 table_hash = HashFile(self.dest_table)
315 # Write image hash to imageloader.json file.
316 blocks = math.ceil(
317 os.path.getsize(self.dest_image) / self._BLOCK_SIZE)
318 imageloader_json_content = self.GetImageloaderJsonContent(
319 image_hash, table_hash, int(blocks))
320 with open(self.dest_imageloader_json, 'w') as f:
321 json.dump(imageloader_json_content, f)
322
323 def GenerateDLC(self):
324 """Generate a DLC artifact."""
325 # Create the image and copy the DLC files to it.
326 self.CreateImage()
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700327 # Verify the image created is within preallocated size.
328 self.VerifyImageSize()
Xiaochu Liudeed0232018-06-26 10:25:34 -0700329 # Generate hash tree and other metadata.
330 self.GenerateVerity()
331
332
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800333def CopyAllDlcs(sysroot, install_root_dir):
334 """Copies all DLC image files into the images directory.
335
336 Copies the DLC image files in the given build directory into the given DLC
337 image directory. If the DLC build directory does not exist, or there is no DLC
338 for that board, this function does nothing.
339
340 Args:
341 sysroot: Path to directory containing DLC images, e.g /build/<board>.
342 install_root_dir: Path to DLC output directory,
343 e.g. src/build/images/<board>/<version>.
344 """
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800345 build_dir = os.path.join(sysroot, DLC_IMAGE_DIR)
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700346 output_dir = os.path.join(install_root_dir, 'dlc')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800347
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700348 if not os.path.exists(build_dir):
Nicolas Norvezb2685752019-11-14 13:17:24 -0800349 logging.info('DLC build directory (%s) does not exist, ignoring.',
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700350 build_dir)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800351 return
352
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700353 if not os.listdir(build_dir):
354 logging.info('There are no DLC(s) to copy to output, ignoring.')
355 return
356
357 logging.info('Copying all DLC images from %s to %s.', build_dir, output_dir)
Amin Hassani6c0228b2019-03-04 13:42:33 -0800358 logging.info('Detected the following DLCs: %s',
359 ', '.join(os.listdir(build_dir)))
360
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800361 osutils.SafeMakedirs(output_dir)
362 osutils.CopyDirContents(build_dir, output_dir)
363
Amin Hassani6c0228b2019-03-04 13:42:33 -0800364 logging.info('Done copying the DLCs to their destination.')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800365
Xiaochu Liudeed0232018-06-26 10:25:34 -0700366def GetParser():
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800367 """Creates an argument parser and returns it."""
Xiaochu Liudeed0232018-06-26 10:25:34 -0700368 parser = commandline.ArgumentParser(description=__doc__)
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800369 # This script is used both for building an individual DLC or copying all final
370 # DLCs images to their final destination nearby chromiumsos_test_image.bin,
371 # etc. These two arguments are required in both cases.
372 parser.add_argument('--sysroot', type='path', metavar='DIR', required=True,
373 help="The root path to the board's build root, e.g. "
Mike Frysinger80de5012019-08-01 14:10:53 -0400374 '/build/eve')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800375 parser.add_argument('--install-root-dir', type='path', metavar='DIR',
376 required=True,
377 help='If building a specific DLC, it is the root path to'
378 ' install DLC images (%s) and metadata (%s). Otherwise it'
379 ' is the target directory where the Chrome OS images gets'
380 ' dropped in build_image, e.g. '
381 'src/build/images/<board>/latest.' % (DLC_IMAGE_DIR,
382 DLC_META_DIR))
Amin Hassani22a25eb2019-01-11 14:25:02 -0800383
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800384 one_dlc = parser.add_argument_group('Arguments required for building only '
385 'one DLC')
386 one_dlc.add_argument('--src-dir', type='path', metavar='SRC_DIR_PATH',
387 help='Root directory path that contains all DLC files '
388 'to be packed.')
389 one_dlc.add_argument('--pre-allocated-blocks', type=int,
390 metavar='PREALLOCATEDBLOCKS',
391 help='Number of blocks (block size is 4k) that need to'
392 'be pre-allocated on device.')
393 one_dlc.add_argument('--version', metavar='VERSION', help='DLC Version.')
394 one_dlc.add_argument('--id', metavar='ID', help='DLC ID (unique per DLC).')
Amin Hassanib5a48042019-03-18 14:30:51 -0700395 one_dlc.add_argument('--package', metavar='PACKAGE',
396 help='The package ID that is unique within a DLC, One'
397 ' DLC cannot have duplicate package IDs.')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800398 one_dlc.add_argument('--name', metavar='NAME',
399 help='A human-readable name for the DLC.')
400 one_dlc.add_argument('--fs-type', metavar='FS_TYPE', default=_SQUASHFS_TYPE,
401 choices=(_SQUASHFS_TYPE, _EXT4_TYPE),
402 help='File system type of the image.')
Xiaochu Liudeed0232018-06-26 10:25:34 -0700403 return parser
404
405
Amin Hassanibc1a4792019-10-24 14:39:57 -0700406def ValidateDlcIdentifier(name):
407 """Validates the DLC identifiers like ID and package names.
408
409 The name specifications are:
410 - No underscore.
411 - First character should be only alphanumeric.
412 - Other characters can be alphanumeric and '-' (dash).
413 - Maximum length of 40 characters.
414
415 For more info see:
416 https://chromium.googlesource.com/chromiumos/platform2/+/master/dlcservice/docs/developer.md#create-a-dlc-module
417
418 Args:
419 name: The string to be validated.
420 """
421 if (not name or not re.match(r'^[a-zA-Z0-9][a-zA-Z0-9-]*$', name) or
422 len(name) > MAX_ID_NAME):
423 raise Exception('Invalid DLC identifier %s' % name)
424
425
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800426def ValidateArguments(opts):
427 """Validates the correctness of the passed arguments.
428
429 Args:
430 opts: Parsed arguments.
431 """
432 # Make sure if the intention is to build one DLC, all the required arguments
433 # are passed.
434 per_dlc_req_args = ('src_dir', 'pre_allocated_blocks', 'version', 'id',
Amin Hassanib5a48042019-03-18 14:30:51 -0700435 'package', 'name')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800436 if (opts.id and
437 not all(vars(opts)[arg] is not None for arg in per_dlc_req_args)):
438 raise Exception('If the intention is to build only one DLC, all the flags'
439 '%s required for it should be passed .' % per_dlc_req_args)
440
441 if opts.fs_type == _EXT4_TYPE:
Jae Hoon Kime5b88622019-08-23 11:11:33 -0700442 raise Exception('ext4 unsupported for DLC, see https://crbug.com/890060')
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800443
Amin Hassanibc1a4792019-10-24 14:39:57 -0700444 if opts.id:
445 ValidateDlcIdentifier(opts.id)
446 if opts.package:
447 ValidateDlcIdentifier(opts.package)
448
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800449
Xiaochu Liudeed0232018-06-26 10:25:34 -0700450def main(argv):
451 opts = GetParser().parse_args(argv)
452 opts.Freeze()
453
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800454 ValidateArguments(opts)
Amin Hassani2af75a92019-01-22 21:07:45 -0800455
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800456 if opts.id:
457 logging.info('Building DLC %s', opts.id)
458 dlc_generator = DlcGenerator(src_dir=opts.src_dir,
459 sysroot=opts.sysroot,
460 install_root_dir=opts.install_root_dir,
461 fs_type=opts.fs_type,
462 pre_allocated_blocks=opts.pre_allocated_blocks,
463 version=opts.version,
464 dlc_id=opts.id,
Amin Hassanib5a48042019-03-18 14:30:51 -0700465 dlc_package=opts.package,
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800466 name=opts.name)
467 dlc_generator.GenerateDLC()
468 else:
Amin Hassanib97a5ee2019-01-23 14:44:43 -0800469 CopyAllDlcs(opts.sysroot, opts.install_root_dir)