blob: 50c7bb4347d95c51fe6f1b8f63ce7c46e6b9afda [file] [log] [blame]
David Pursellf1d16a62015-03-25 13:31:04 -07001# Copyright 2015 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Install/copy the image to the device."""
6
Mike Frysinger93e8ffa2019-07-03 20:24:18 -04007from __future__ import division
David Pursellf1d16a62015-03-25 13:31:04 -07008
David Pursellf1d16a62015-03-25 13:31:04 -07009import os
Ralph Nathan9b997232015-05-15 13:13:12 -070010import re
David Pursellf1d16a62015-03-25 13:31:04 -070011import shutil
David Pursellf1d16a62015-03-25 13:31:04 -070012
Amin Hassani153f9162021-02-22 20:48:31 -080013from chromite.cli import device_imager
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000014from chromite.cli.cros import cros_chrome_sdk
15
David Pursellf1d16a62015-03-25 13:31:04 -070016from chromite.lib import commandline
17from chromite.lib import cros_build_lib
18from chromite.lib import cros_logging as logging
19from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070020from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070021from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070022from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070023from chromite.lib import remote_access
24
25
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000026def GetDefaultBoard():
27 """Look up default board.
28
29 In a chrome checkout, return $SDK_BOARD. In a chromeos checkout,
30 return the contents of .default_board.
31 """
32 if path_util.DetermineCheckout().type == path_util.CHECKOUT_TYPE_GCLIENT:
33 return os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
34 return cros_build_lib.GetDefaultBoard()
35
36
Ralph Nathan9b997232015-05-15 13:13:12 -070037class UsbImagerOperation(operation.ProgressBarOperation):
38 """Progress bar for flashing image to operation."""
39
40 def __init__(self, image):
41 super(UsbImagerOperation, self).__init__()
42 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070043 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070044 self._bytes = re.compile(r'(\d+) bytes')
45
46 def _GetDDPid(self):
47 """Get the Pid of dd."""
48 try:
Mike Frysinger45602c72019-09-22 02:15:11 -040049 pids = cros_build_lib.run(['pgrep', 'dd'], capture_output=True,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040050 print_cmd=False, encoding='utf-8').stdout
Ralph Nathan9b997232015-05-15 13:13:12 -070051 for pid in pids.splitlines():
Mike Nicholsa1414162021-04-22 20:07:22 +000052 if osutils.IsChildProcess(int(pid), name='dd'):
Ralph Nathan9b997232015-05-15 13:13:12 -070053 return int(pid)
54 return -1
55 except cros_build_lib.RunCommandError:
56 # If dd isn't still running, then we assume that it is finished.
57 return -1
58
59 def _PingDD(self, dd_pid):
60 """Send USR1 signal to dd to get status update."""
61 try:
62 cmd = ['kill', '-USR1', str(dd_pid)]
Mike Frysinger45602c72019-09-22 02:15:11 -040063 cros_build_lib.sudo_run(cmd, print_cmd=False)
Ralph Nathan9b997232015-05-15 13:13:12 -070064 except cros_build_lib.RunCommandError:
65 # Here we assume that dd finished in the background.
66 return
67
68 def ParseOutput(self, output=None):
69 """Parse the output of dd to update progress bar."""
70 dd_pid = self._GetDDPid()
71 if dd_pid == -1:
72 return
73
74 self._PingDD(dd_pid)
75
76 if output is None:
77 stdout = self._stdout.read()
78 stderr = self._stderr.read()
79 output = stdout + stderr
80
81 match = self._bytes.search(output)
82 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070083 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -070084
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040085 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -070086
87
Mike Frysinger32759e42016-12-21 18:40:16 -050088def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070089 """Determines if a file is a valid GPT disk.
90
91 Args:
92 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -050093 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070094 """
95 if os.path.isfile(file_path):
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040096 with open(file_path, 'rb') as image_file:
Mike Frysinger32759e42016-12-21 18:40:16 -050097 if require_pmbr:
98 # Seek to the end of LBA0 and look for the PMBR boot signature.
99 image_file.seek(0x1fe)
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400100 if image_file.read(2) != b'\x55\xaa':
Mike Frysinger32759e42016-12-21 18:40:16 -0500101 return False
102 # Current file position is start of LBA1 now.
103 else:
104 # Seek to LBA1 where the GPT starts.
105 image_file.seek(0x200)
106
107 # See if there's a GPT here.
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400108 if image_file.read(8) == b'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700109 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500110
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700111 return False
112
113
114def _ChooseImageFromDirectory(dir_path):
115 """Lists all image files in |dir_path| and ask user to select one.
116
117 Args:
118 dir_path: Path to the directory.
119 """
120 images = sorted([x for x in os.listdir(dir_path) if
121 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
122 idx = 0
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400123 if not images:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700124 raise ValueError('No image found in %s.' % dir_path)
125 elif len(images) > 1:
126 idx = cros_build_lib.GetChoice(
127 'Multiple images found in %s. Please select one to continue:' % (
128 (dir_path,)),
129 images)
130
131 return os.path.join(dir_path, images[idx])
132
133
David Pursellf1d16a62015-03-25 13:31:04 -0700134class FlashError(Exception):
135 """Thrown when there is an unrecoverable error during flash."""
136
137
138class USBImager(object):
139 """Copy image to the target removable device."""
140
Amin Hassani04314b12020-12-15 15:59:54 -0800141 def __init__(self, device, board, image, version, debug=False, yes=False):
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000142 """Initializes USBImager."""
David Pursellf1d16a62015-03-25 13:31:04 -0700143 self.device = device
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000144 self.board = board if board else GetDefaultBoard()
David Pursellf1d16a62015-03-25 13:31:04 -0700145 self.image = image
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000146 self.version = version
David Pursellf1d16a62015-03-25 13:31:04 -0700147 self.debug = debug
148 self.debug_level = logging.DEBUG if debug else logging.INFO
David Pursellf1d16a62015-03-25 13:31:04 -0700149 self.yes = yes
150
151 def DeviceNameToPath(self, device_name):
152 return '/dev/%s' % device_name
153
154 def GetRemovableDeviceDescription(self, device):
155 """Returns a informational description of the removable |device|.
156
157 Args:
158 device: the device name (e.g. sdc).
159
160 Returns:
161 A string describing |device| (e.g. Patriot Memory 7918 MB).
162 """
163 desc = [
164 osutils.GetDeviceInfo(device, keyword='manufacturer'),
165 osutils.GetDeviceInfo(device, keyword='product'),
166 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
167 '(%s)' % self.DeviceNameToPath(device),
168 ]
169 return ' '.join([x for x in desc if x])
170
171 def ListAllRemovableDevices(self):
172 """Returns a list of removable devices.
173
174 Returns:
175 A list of device names (e.g. ['sdb', 'sdc']).
176 """
177 devices = osutils.ListBlockDevices()
178 removable_devices = []
179 for d in devices:
180 if d.TYPE == 'disk' and d.RM == '1':
181 removable_devices.append(d.NAME)
182
183 return removable_devices
184
185 def ChooseRemovableDevice(self, devices):
186 """Lists all removable devices and asks user to select/confirm.
187
188 Args:
189 devices: a list of device names (e.g. ['sda', 'sdb']).
190
191 Returns:
192 The device name chosen by the user.
193 """
194 idx = cros_build_lib.GetChoice(
195 'Removable device(s) found. Please select/confirm to continue:',
196 [self.GetRemovableDeviceDescription(x) for x in devices])
197
198 return devices[idx]
199
David Pursellf1d16a62015-03-25 13:31:04 -0700200 def CopyImageToDevice(self, image, device):
201 """Copies |image| to the removable |device|.
202
203 Args:
204 image: Path to the image to copy.
205 device: Device to copy to.
206 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700207 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800208 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700209 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
210 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400211 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400212 encoding='utf-8', update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700213 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400214 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700215 cmd, debug_level=logging.NOTICE,
216 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700217
Brian Norris6386fde2018-10-29 13:34:28 -0700218 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
219 # up for us with a 'write' command, so we have a standards-conforming GPT.
220 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
221 # fix GPT sanity issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400222 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500223 check=False,
Mike Frysinger45602c72019-09-22 02:15:11 -0400224 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700225
Mike Frysinger45602c72019-09-22 02:15:11 -0400226 cros_build_lib.sudo_run(['partx', '-u', device],
227 debug_level=self.debug_level)
228 cros_build_lib.sudo_run(['sync', '-d', device],
229 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700230
David Pursellf1d16a62015-03-25 13:31:04 -0700231 def _GetImagePath(self):
232 """Returns the image path to use."""
Amin Hassanie55168c2020-11-02 14:40:30 -0800233 image_path = None
David Pursellf1d16a62015-03-25 13:31:04 -0700234 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700235 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700236 # TODO(wnwen): Open the tarball and if there is just one file in it,
237 # use that instead. Existing code in upload_symbols.py.
238 if cros_build_lib.BooleanPrompt(
239 prolog='The given image file is not a valid disk image. Perhaps '
240 'you forgot to untar it.',
241 prompt='Terminate the current flash process?'):
242 raise FlashError('Update terminated by user.')
243 image_path = self.image
244 elif os.path.isdir(self.image):
245 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700246 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700247 else:
248 # Translate the xbuddy path to get the exact image to use.
Amin Hassanie55168c2020-11-02 14:40:30 -0800249 _, image_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000250 self.image, self.board, self.version)
David Pursellf1d16a62015-03-25 13:31:04 -0700251
Amin Hassanie55168c2020-11-02 14:40:30 -0800252 logging.info('Using image %s', image_path)
David Pursellf1d16a62015-03-25 13:31:04 -0700253 return image_path
254
255 def Run(self):
256 """Image the removable device."""
257 devices = self.ListAllRemovableDevices()
258
259 if self.device:
260 # If user specified a device path, check if it exists.
261 if not os.path.exists(self.device):
262 raise FlashError('Device path %s does not exist.' % self.device)
263
264 # Then check if it is removable.
265 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
266 msg = '%s is not a removable device.' % self.device
267 if not (self.yes or cros_build_lib.BooleanPrompt(
268 default=False, prolog=msg)):
269 raise FlashError('You can specify usb:// to choose from a list of '
270 'removable devices.')
271 target = None
272 if self.device:
273 # Get device name from path (e.g. sdc in /dev/sdc).
274 target = self.device.rsplit(os.path.sep, 1)[-1]
275 elif devices:
276 # Ask user to choose from the list.
277 target = self.ChooseRemovableDevice(devices)
278 else:
279 raise FlashError('No removable devices detected.')
280
281 image_path = self._GetImagePath()
282 try:
283 device = self.DeviceNameToPath(target)
Amin Hassani04314b12020-12-15 15:59:54 -0800284 self.CopyImageToDevice(image_path, device)
David Pursellf1d16a62015-03-25 13:31:04 -0700285 except cros_build_lib.RunCommandError:
286 logging.error('Failed copying image to device %s',
287 self.DeviceNameToPath(target))
288
289
290class FileImager(USBImager):
291 """Copy image to the target path."""
292
293 def Run(self):
294 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800295 if not os.path.isdir(os.path.dirname(self.device)):
296 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700297
298 image_path = self._GetImagePath()
299 if os.path.isdir(self.device):
300 logging.info('Copying to %s',
301 os.path.join(self.device, os.path.basename(image_path)))
302 else:
303 logging.info('Copying to %s', self.device)
304 try:
305 shutil.copy(image_path, self.device)
306 except IOError:
307 logging.error('Failed to copy image %s to %s', image_path, self.device)
308
309
Amin Hassani9a0199f2021-03-31 19:45:06 -0700310def Flash(device, image, board=None, version=None,
311 no_rootfs_update=False, no_stateful_update=False,
312 clobber_stateful=False, reboot=True, ssh_private_key=None, ping=True,
Daniel Erat30fd2072016-08-29 10:08:56 -0600313 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassani9a0199f2021-03-31 19:45:06 -0700314 force=False, debug=False, clear_tpm_owner=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700315 """Flashes a device, USB drive, or file with an image.
316
317 This provides functionality common to `cros flash` and `brillo flash`
318 so that they can parse the commandline separately but still use the
319 same underlying functionality.
320
321 Args:
David Pursell2e773382015-04-03 14:30:47 -0700322 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700323 image: Path (string) to the update image. Can be a local or xbuddy path;
324 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700325 board: Board to use; None to automatically detect.
Amin Hassani9a0199f2021-03-31 19:45:06 -0700326 no_rootfs_update: Don't update rootfs partition; SSH |device| scheme only.
327 no_stateful_update: Don't update stateful partition; SSH |device| scheme
328 only.
David Pursellf1d16a62015-03-25 13:31:04 -0700329 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
Yi Chou5f4e51f2020-10-22 16:33:00 +0800330 clear_tpm_owner: Clear the TPM owner on reboot; SSH |device| scheme only.
David Pursellf1d16a62015-03-25 13:31:04 -0700331 reboot: Reboot device after update; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600332 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700333 ping: Ping the device before attempting update; SSH |device| scheme only.
334 disable_rootfs_verification: Remove rootfs verification after update; SSH
335 |device| scheme only.
336 clear_cache: Clear the devserver static directory.
337 yes: Assume "yes" for any prompt.
338 force: Ignore sanity checks and prompts. Overrides |yes| if True.
339 debug: Print additional debugging messages.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000340 version: Default version.
David Pursellf1d16a62015-03-25 13:31:04 -0700341
342 Raises:
343 FlashError: An unrecoverable error occured.
344 ValueError: Invalid parameter combination.
345 """
346 if force:
347 yes = True
348
349 if clear_cache:
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000350 ds_wrapper.DevServerWrapper.WipeStaticDirectory()
351 ds_wrapper.DevServerWrapper.CreateStaticDirectory()
David Pursellf1d16a62015-03-25 13:31:04 -0700352
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000353 # The user may not have specified a source image, use version as the default.
354 image = image or version
David Pursell2e773382015-04-03 14:30:47 -0700355 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
356 if device:
357 hostname, port = device.hostname, device.port
358 else:
359 hostname, port = None, None
Amin Hassani153f9162021-02-22 20:48:31 -0800360
Amin Hassani9a0199f2021-03-31 19:45:06 -0700361 with remote_access.ChromiumOSDeviceHandler(
362 hostname, port=port,
363 private_key=ssh_private_key, ping=ping) as device_p:
364 device_imager.DeviceImager(
365 device_p,
366 image,
367 board=board,
368 version=version,
369 no_rootfs_update=no_rootfs_update,
370 no_stateful_update=no_stateful_update,
371 no_reboot=not reboot,
372 disable_verification=disable_rootfs_verification,
373 clobber_stateful=clobber_stateful,
374 clear_tpm_owner=clear_tpm_owner).Run()
David Pursellf1d16a62015-03-25 13:31:04 -0700375 elif device.scheme == commandline.DEVICE_SCHEME_USB:
376 path = osutils.ExpandPath(device.path) if device.path else ''
377 logging.info('Preparing to image the removable device %s', path)
378 imager = USBImager(path,
379 board,
380 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000381 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700382 debug=debug,
David Pursellf1d16a62015-03-25 13:31:04 -0700383 yes=yes)
384 imager.Run()
385 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
386 logging.info('Preparing to copy image to %s', device.path)
387 imager = FileImager(device.path,
388 board,
389 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000390 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700391 debug=debug,
392 yes=yes)
393 imager.Run()