blob: 6eec74a2b000809fbd8fcf7b2256dbdd00421b5a [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
David Pursellf1d16a62015-03-25 13:31:04 -07002# Copyright 2015 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"""Install/copy the image to the device."""
7
Mike Frysinger93e8ffa2019-07-03 20:24:18 -04008from __future__ import division
David Pursellf1d16a62015-03-25 13:31:04 -07009from __future__ import print_function
10
David Pursellf1d16a62015-03-25 13:31:04 -070011import os
Ralph Nathan9b997232015-05-15 13:13:12 -070012import re
David Pursellf1d16a62015-03-25 13:31:04 -070013import shutil
Mike Frysinger3f087aa2020-03-20 06:03:16 -040014import sys
David Pursellf1d16a62015-03-25 13:31:04 -070015
Amin Hassani153f9162021-02-22 20:48:31 -080016from chromite.cli import device_imager
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000017from chromite.cli.cros import cros_chrome_sdk
18
David Pursellf1d16a62015-03-25 13:31:04 -070019from chromite.lib import commandline
20from chromite.lib import cros_build_lib
21from chromite.lib import cros_logging as logging
22from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070023from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070024from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070025from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070026from chromite.lib import remote_access
27
28
Mike Frysinger3f087aa2020-03-20 06:03:16 -040029assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
30
31
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000032def GetDefaultBoard():
33 """Look up default board.
34
35 In a chrome checkout, return $SDK_BOARD. In a chromeos checkout,
36 return the contents of .default_board.
37 """
38 if path_util.DetermineCheckout().type == path_util.CHECKOUT_TYPE_GCLIENT:
39 return os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
40 return cros_build_lib.GetDefaultBoard()
41
42
Ralph Nathan9b997232015-05-15 13:13:12 -070043class UsbImagerOperation(operation.ProgressBarOperation):
44 """Progress bar for flashing image to operation."""
45
46 def __init__(self, image):
47 super(UsbImagerOperation, self).__init__()
48 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070049 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070050 self._bytes = re.compile(r'(\d+) bytes')
51
52 def _GetDDPid(self):
53 """Get the Pid of dd."""
54 try:
Mike Frysinger45602c72019-09-22 02:15:11 -040055 pids = cros_build_lib.run(['pgrep', 'dd'], capture_output=True,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040056 print_cmd=False, encoding='utf-8').stdout
Ralph Nathan9b997232015-05-15 13:13:12 -070057 for pid in pids.splitlines():
Mike Nicholsa1414162021-04-22 20:07:22 +000058 if osutils.IsChildProcess(int(pid), name='dd'):
Ralph Nathan9b997232015-05-15 13:13:12 -070059 return int(pid)
60 return -1
61 except cros_build_lib.RunCommandError:
62 # If dd isn't still running, then we assume that it is finished.
63 return -1
64
65 def _PingDD(self, dd_pid):
66 """Send USR1 signal to dd to get status update."""
67 try:
68 cmd = ['kill', '-USR1', str(dd_pid)]
Mike Frysinger45602c72019-09-22 02:15:11 -040069 cros_build_lib.sudo_run(cmd, print_cmd=False)
Ralph Nathan9b997232015-05-15 13:13:12 -070070 except cros_build_lib.RunCommandError:
71 # Here we assume that dd finished in the background.
72 return
73
74 def ParseOutput(self, output=None):
75 """Parse the output of dd to update progress bar."""
76 dd_pid = self._GetDDPid()
77 if dd_pid == -1:
78 return
79
80 self._PingDD(dd_pid)
81
82 if output is None:
83 stdout = self._stdout.read()
84 stderr = self._stderr.read()
85 output = stdout + stderr
86
87 match = self._bytes.search(output)
88 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070089 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -070090
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040091 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -070092
93
Mike Frysinger32759e42016-12-21 18:40:16 -050094def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070095 """Determines if a file is a valid GPT disk.
96
97 Args:
98 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -050099 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700100 """
101 if os.path.isfile(file_path):
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400102 with open(file_path, 'rb') as image_file:
Mike Frysinger32759e42016-12-21 18:40:16 -0500103 if require_pmbr:
104 # Seek to the end of LBA0 and look for the PMBR boot signature.
105 image_file.seek(0x1fe)
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400106 if image_file.read(2) != b'\x55\xaa':
Mike Frysinger32759e42016-12-21 18:40:16 -0500107 return False
108 # Current file position is start of LBA1 now.
109 else:
110 # Seek to LBA1 where the GPT starts.
111 image_file.seek(0x200)
112
113 # See if there's a GPT here.
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400114 if image_file.read(8) == b'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700115 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500116
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700117 return False
118
119
120def _ChooseImageFromDirectory(dir_path):
121 """Lists all image files in |dir_path| and ask user to select one.
122
123 Args:
124 dir_path: Path to the directory.
125 """
126 images = sorted([x for x in os.listdir(dir_path) if
127 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
128 idx = 0
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400129 if not images:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700130 raise ValueError('No image found in %s.' % dir_path)
131 elif len(images) > 1:
132 idx = cros_build_lib.GetChoice(
133 'Multiple images found in %s. Please select one to continue:' % (
134 (dir_path,)),
135 images)
136
137 return os.path.join(dir_path, images[idx])
138
139
David Pursellf1d16a62015-03-25 13:31:04 -0700140class FlashError(Exception):
141 """Thrown when there is an unrecoverable error during flash."""
142
143
144class USBImager(object):
145 """Copy image to the target removable device."""
146
Amin Hassani04314b12020-12-15 15:59:54 -0800147 def __init__(self, device, board, image, version, debug=False, yes=False):
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000148 """Initializes USBImager."""
David Pursellf1d16a62015-03-25 13:31:04 -0700149 self.device = device
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000150 self.board = board if board else GetDefaultBoard()
David Pursellf1d16a62015-03-25 13:31:04 -0700151 self.image = image
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000152 self.version = version
David Pursellf1d16a62015-03-25 13:31:04 -0700153 self.debug = debug
154 self.debug_level = logging.DEBUG if debug else logging.INFO
David Pursellf1d16a62015-03-25 13:31:04 -0700155 self.yes = yes
156
157 def DeviceNameToPath(self, device_name):
158 return '/dev/%s' % device_name
159
160 def GetRemovableDeviceDescription(self, device):
161 """Returns a informational description of the removable |device|.
162
163 Args:
164 device: the device name (e.g. sdc).
165
166 Returns:
167 A string describing |device| (e.g. Patriot Memory 7918 MB).
168 """
169 desc = [
170 osutils.GetDeviceInfo(device, keyword='manufacturer'),
171 osutils.GetDeviceInfo(device, keyword='product'),
172 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
173 '(%s)' % self.DeviceNameToPath(device),
174 ]
175 return ' '.join([x for x in desc if x])
176
177 def ListAllRemovableDevices(self):
178 """Returns a list of removable devices.
179
180 Returns:
181 A list of device names (e.g. ['sdb', 'sdc']).
182 """
183 devices = osutils.ListBlockDevices()
184 removable_devices = []
185 for d in devices:
186 if d.TYPE == 'disk' and d.RM == '1':
187 removable_devices.append(d.NAME)
188
189 return removable_devices
190
191 def ChooseRemovableDevice(self, devices):
192 """Lists all removable devices and asks user to select/confirm.
193
194 Args:
195 devices: a list of device names (e.g. ['sda', 'sdb']).
196
197 Returns:
198 The device name chosen by the user.
199 """
200 idx = cros_build_lib.GetChoice(
201 'Removable device(s) found. Please select/confirm to continue:',
202 [self.GetRemovableDeviceDescription(x) for x in devices])
203
204 return devices[idx]
205
David Pursellf1d16a62015-03-25 13:31:04 -0700206 def CopyImageToDevice(self, image, device):
207 """Copies |image| to the removable |device|.
208
209 Args:
210 image: Path to the image to copy.
211 device: Device to copy to.
212 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700213 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800214 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700215 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
216 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400217 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400218 encoding='utf-8', update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700219 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400220 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700221 cmd, debug_level=logging.NOTICE,
222 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700223
Brian Norris6386fde2018-10-29 13:34:28 -0700224 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
225 # up for us with a 'write' command, so we have a standards-conforming GPT.
226 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
227 # fix GPT sanity issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400228 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500229 check=False,
Mike Frysinger45602c72019-09-22 02:15:11 -0400230 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700231
Mike Frysinger45602c72019-09-22 02:15:11 -0400232 cros_build_lib.sudo_run(['partx', '-u', device],
233 debug_level=self.debug_level)
234 cros_build_lib.sudo_run(['sync', '-d', device],
235 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700236
David Pursellf1d16a62015-03-25 13:31:04 -0700237 def _GetImagePath(self):
238 """Returns the image path to use."""
Amin Hassanie55168c2020-11-02 14:40:30 -0800239 image_path = None
David Pursellf1d16a62015-03-25 13:31:04 -0700240 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700241 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700242 # TODO(wnwen): Open the tarball and if there is just one file in it,
243 # use that instead. Existing code in upload_symbols.py.
244 if cros_build_lib.BooleanPrompt(
245 prolog='The given image file is not a valid disk image. Perhaps '
246 'you forgot to untar it.',
247 prompt='Terminate the current flash process?'):
248 raise FlashError('Update terminated by user.')
249 image_path = self.image
250 elif os.path.isdir(self.image):
251 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700252 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700253 else:
254 # Translate the xbuddy path to get the exact image to use.
Amin Hassanie55168c2020-11-02 14:40:30 -0800255 _, image_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000256 self.image, self.board, self.version)
David Pursellf1d16a62015-03-25 13:31:04 -0700257
Amin Hassanie55168c2020-11-02 14:40:30 -0800258 logging.info('Using image %s', image_path)
David Pursellf1d16a62015-03-25 13:31:04 -0700259 return image_path
260
261 def Run(self):
262 """Image the removable device."""
263 devices = self.ListAllRemovableDevices()
264
265 if self.device:
266 # If user specified a device path, check if it exists.
267 if not os.path.exists(self.device):
268 raise FlashError('Device path %s does not exist.' % self.device)
269
270 # Then check if it is removable.
271 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
272 msg = '%s is not a removable device.' % self.device
273 if not (self.yes or cros_build_lib.BooleanPrompt(
274 default=False, prolog=msg)):
275 raise FlashError('You can specify usb:// to choose from a list of '
276 'removable devices.')
277 target = None
278 if self.device:
279 # Get device name from path (e.g. sdc in /dev/sdc).
280 target = self.device.rsplit(os.path.sep, 1)[-1]
281 elif devices:
282 # Ask user to choose from the list.
283 target = self.ChooseRemovableDevice(devices)
284 else:
285 raise FlashError('No removable devices detected.')
286
287 image_path = self._GetImagePath()
288 try:
289 device = self.DeviceNameToPath(target)
Amin Hassani04314b12020-12-15 15:59:54 -0800290 self.CopyImageToDevice(image_path, device)
David Pursellf1d16a62015-03-25 13:31:04 -0700291 except cros_build_lib.RunCommandError:
292 logging.error('Failed copying image to device %s',
293 self.DeviceNameToPath(target))
294
295
296class FileImager(USBImager):
297 """Copy image to the target path."""
298
299 def Run(self):
300 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800301 if not os.path.isdir(os.path.dirname(self.device)):
302 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700303
304 image_path = self._GetImagePath()
305 if os.path.isdir(self.device):
306 logging.info('Copying to %s',
307 os.path.join(self.device, os.path.basename(image_path)))
308 else:
309 logging.info('Copying to %s', self.device)
310 try:
311 shutil.copy(image_path, self.device)
312 except IOError:
313 logging.error('Failed to copy image %s to %s', image_path, self.device)
314
315
Amin Hassani9a0199f2021-03-31 19:45:06 -0700316def Flash(device, image, board=None, version=None,
317 no_rootfs_update=False, no_stateful_update=False,
318 clobber_stateful=False, reboot=True, ssh_private_key=None, ping=True,
Daniel Erat30fd2072016-08-29 10:08:56 -0600319 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassani9a0199f2021-03-31 19:45:06 -0700320 force=False, debug=False, clear_tpm_owner=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700321 """Flashes a device, USB drive, or file with an image.
322
323 This provides functionality common to `cros flash` and `brillo flash`
324 so that they can parse the commandline separately but still use the
325 same underlying functionality.
326
327 Args:
David Pursell2e773382015-04-03 14:30:47 -0700328 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700329 image: Path (string) to the update image. Can be a local or xbuddy path;
330 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700331 board: Board to use; None to automatically detect.
Amin Hassani9a0199f2021-03-31 19:45:06 -0700332 no_rootfs_update: Don't update rootfs partition; SSH |device| scheme only.
333 no_stateful_update: Don't update stateful partition; SSH |device| scheme
334 only.
David Pursellf1d16a62015-03-25 13:31:04 -0700335 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
Yi Chou5f4e51f2020-10-22 16:33:00 +0800336 clear_tpm_owner: Clear the TPM owner on reboot; SSH |device| scheme only.
David Pursellf1d16a62015-03-25 13:31:04 -0700337 reboot: Reboot device after update; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600338 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700339 ping: Ping the device before attempting update; SSH |device| scheme only.
340 disable_rootfs_verification: Remove rootfs verification after update; SSH
341 |device| scheme only.
342 clear_cache: Clear the devserver static directory.
343 yes: Assume "yes" for any prompt.
344 force: Ignore sanity checks and prompts. Overrides |yes| if True.
345 debug: Print additional debugging messages.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000346 version: Default version.
David Pursellf1d16a62015-03-25 13:31:04 -0700347
348 Raises:
349 FlashError: An unrecoverable error occured.
350 ValueError: Invalid parameter combination.
351 """
352 if force:
353 yes = True
354
355 if clear_cache:
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000356 ds_wrapper.DevServerWrapper.WipeStaticDirectory()
357 ds_wrapper.DevServerWrapper.CreateStaticDirectory()
David Pursellf1d16a62015-03-25 13:31:04 -0700358
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000359 # The user may not have specified a source image, use version as the default.
360 image = image or version
David Pursell2e773382015-04-03 14:30:47 -0700361 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
362 if device:
363 hostname, port = device.hostname, device.port
364 else:
365 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700366 logging.notice('Preparing to update the remote device %s', hostname)
Amin Hassani153f9162021-02-22 20:48:31 -0800367
Amin Hassani9a0199f2021-03-31 19:45:06 -0700368 with remote_access.ChromiumOSDeviceHandler(
369 hostname, port=port,
370 private_key=ssh_private_key, ping=ping) as device_p:
371 device_imager.DeviceImager(
372 device_p,
373 image,
374 board=board,
375 version=version,
376 no_rootfs_update=no_rootfs_update,
377 no_stateful_update=no_stateful_update,
378 no_reboot=not reboot,
379 disable_verification=disable_rootfs_verification,
380 clobber_stateful=clobber_stateful,
381 clear_tpm_owner=clear_tpm_owner).Run()
David Pursellf1d16a62015-03-25 13:31:04 -0700382 elif device.scheme == commandline.DEVICE_SCHEME_USB:
383 path = osutils.ExpandPath(device.path) if device.path else ''
384 logging.info('Preparing to image the removable device %s', path)
385 imager = USBImager(path,
386 board,
387 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000388 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700389 debug=debug,
David Pursellf1d16a62015-03-25 13:31:04 -0700390 yes=yes)
391 imager.Run()
392 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
393 logging.info('Preparing to copy image to %s', device.path)
394 imager = FileImager(device.path,
395 board,
396 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000397 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700398 debug=debug,
399 yes=yes)
400 imager.Run()