blob: 62b0974d73b23eb26c7dc939d55af31d64a584ec [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
Chris McDonald14ac61d2021-07-21 11:49:56 -06009import logging
David Pursellf1d16a62015-03-25 13:31:04 -070010import os
Ralph Nathan9b997232015-05-15 13:13:12 -070011import re
David Pursellf1d16a62015-03-25 13:31:04 -070012import shutil
David Pursellf1d16a62015-03-25 13:31:04 -070013
Amin Hassani153f9162021-02-22 20:48:31 -080014from chromite.cli import device_imager
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000015from chromite.cli.cros import cros_chrome_sdk
David Pursellf1d16a62015-03-25 13:31:04 -070016from chromite.lib import commandline
17from chromite.lib import cros_build_lib
David Pursellf1d16a62015-03-25 13:31:04 -070018from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070019from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070020from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070021from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070022from chromite.lib import remote_access
23
24
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000025def GetDefaultBoard():
26 """Look up default board.
27
28 In a chrome checkout, return $SDK_BOARD. In a chromeos checkout,
29 return the contents of .default_board.
30 """
31 if path_util.DetermineCheckout().type == path_util.CHECKOUT_TYPE_GCLIENT:
32 return os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
33 return cros_build_lib.GetDefaultBoard()
34
35
Ralph Nathan9b997232015-05-15 13:13:12 -070036class UsbImagerOperation(operation.ProgressBarOperation):
37 """Progress bar for flashing image to operation."""
38
39 def __init__(self, image):
Jae Hoon Kimad176b82021-07-26 19:29:29 +000040 super().__init__()
Ralph Nathan9b997232015-05-15 13:13:12 -070041 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070042 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070043 self._bytes = re.compile(r'(\d+) bytes')
44
45 def _GetDDPid(self):
46 """Get the Pid of dd."""
47 try:
Mike Frysinger45602c72019-09-22 02:15:11 -040048 pids = cros_build_lib.run(['pgrep', 'dd'], capture_output=True,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040049 print_cmd=False, encoding='utf-8').stdout
Ralph Nathan9b997232015-05-15 13:13:12 -070050 for pid in pids.splitlines():
Mike Nicholsa1414162021-04-22 20:07:22 +000051 if osutils.IsChildProcess(int(pid), name='dd'):
Ralph Nathan9b997232015-05-15 13:13:12 -070052 return int(pid)
53 return -1
54 except cros_build_lib.RunCommandError:
55 # If dd isn't still running, then we assume that it is finished.
56 return -1
57
58 def _PingDD(self, dd_pid):
59 """Send USR1 signal to dd to get status update."""
60 try:
61 cmd = ['kill', '-USR1', str(dd_pid)]
Mike Frysinger45602c72019-09-22 02:15:11 -040062 cros_build_lib.sudo_run(cmd, print_cmd=False)
Ralph Nathan9b997232015-05-15 13:13:12 -070063 except cros_build_lib.RunCommandError:
64 # Here we assume that dd finished in the background.
65 return
66
67 def ParseOutput(self, output=None):
68 """Parse the output of dd to update progress bar."""
69 dd_pid = self._GetDDPid()
70 if dd_pid == -1:
71 return
72
73 self._PingDD(dd_pid)
74
75 if output is None:
76 stdout = self._stdout.read()
77 stderr = self._stderr.read()
78 output = stdout + stderr
79
80 match = self._bytes.search(output)
81 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070082 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -070083
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040084 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -070085
86
Mike Frysinger32759e42016-12-21 18:40:16 -050087def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070088 """Determines if a file is a valid GPT disk.
89
90 Args:
91 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -050092 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070093 """
94 if os.path.isfile(file_path):
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040095 with open(file_path, 'rb') as image_file:
Mike Frysinger32759e42016-12-21 18:40:16 -050096 if require_pmbr:
97 # Seek to the end of LBA0 and look for the PMBR boot signature.
98 image_file.seek(0x1fe)
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040099 if image_file.read(2) != b'\x55\xaa':
Mike Frysinger32759e42016-12-21 18:40:16 -0500100 return False
101 # Current file position is start of LBA1 now.
102 else:
103 # Seek to LBA1 where the GPT starts.
104 image_file.seek(0x200)
105
106 # See if there's a GPT here.
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400107 if image_file.read(8) == b'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700108 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500109
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700110 return False
111
112
113def _ChooseImageFromDirectory(dir_path):
114 """Lists all image files in |dir_path| and ask user to select one.
115
116 Args:
117 dir_path: Path to the directory.
118 """
119 images = sorted([x for x in os.listdir(dir_path) if
120 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
121 idx = 0
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400122 if not images:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700123 raise ValueError('No image found in %s.' % dir_path)
124 elif len(images) > 1:
125 idx = cros_build_lib.GetChoice(
126 'Multiple images found in %s. Please select one to continue:' % (
127 (dir_path,)),
128 images)
129
130 return os.path.join(dir_path, images[idx])
131
132
David Pursellf1d16a62015-03-25 13:31:04 -0700133class FlashError(Exception):
134 """Thrown when there is an unrecoverable error during flash."""
135
136
137class USBImager(object):
138 """Copy image to the target removable device."""
139
Amin Hassani04314b12020-12-15 15:59:54 -0800140 def __init__(self, device, board, image, version, debug=False, yes=False):
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000141 """Initializes USBImager."""
David Pursellf1d16a62015-03-25 13:31:04 -0700142 self.device = device
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000143 self.board = board if board else GetDefaultBoard()
David Pursellf1d16a62015-03-25 13:31:04 -0700144 self.image = image
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000145 self.version = version
David Pursellf1d16a62015-03-25 13:31:04 -0700146 self.debug = debug
147 self.debug_level = logging.DEBUG if debug else logging.INFO
David Pursellf1d16a62015-03-25 13:31:04 -0700148 self.yes = yes
149
150 def DeviceNameToPath(self, device_name):
151 return '/dev/%s' % device_name
152
153 def GetRemovableDeviceDescription(self, device):
154 """Returns a informational description of the removable |device|.
155
156 Args:
157 device: the device name (e.g. sdc).
158
159 Returns:
160 A string describing |device| (e.g. Patriot Memory 7918 MB).
161 """
162 desc = [
163 osutils.GetDeviceInfo(device, keyword='manufacturer'),
164 osutils.GetDeviceInfo(device, keyword='product'),
165 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
166 '(%s)' % self.DeviceNameToPath(device),
167 ]
168 return ' '.join([x for x in desc if x])
169
170 def ListAllRemovableDevices(self):
171 """Returns a list of removable devices.
172
173 Returns:
174 A list of device names (e.g. ['sdb', 'sdc']).
175 """
176 devices = osutils.ListBlockDevices()
177 removable_devices = []
178 for d in devices:
Jeffery Millerc77726f2022-04-14 15:46:59 -0500179 if d.TYPE == 'disk' and (d.RM == '1' or d.HOTPLUG == '1'):
David Pursellf1d16a62015-03-25 13:31:04 -0700180 removable_devices.append(d.NAME)
181
182 return removable_devices
183
184 def ChooseRemovableDevice(self, devices):
185 """Lists all removable devices and asks user to select/confirm.
186
187 Args:
188 devices: a list of device names (e.g. ['sda', 'sdb']).
189
190 Returns:
191 The device name chosen by the user.
192 """
193 idx = cros_build_lib.GetChoice(
194 'Removable device(s) found. Please select/confirm to continue:',
195 [self.GetRemovableDeviceDescription(x) for x in devices])
196
197 return devices[idx]
198
David Pursellf1d16a62015-03-25 13:31:04 -0700199 def CopyImageToDevice(self, image, device):
200 """Copies |image| to the removable |device|.
201
202 Args:
203 image: Path to the image to copy.
204 device: Device to copy to.
205 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700206 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800207 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700208 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
209 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400210 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400211 encoding='utf-8', update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700212 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400213 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700214 cmd, debug_level=logging.NOTICE,
215 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700216
Brian Norris6386fde2018-10-29 13:34:28 -0700217 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
218 # up for us with a 'write' command, so we have a standards-conforming GPT.
219 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
Sloan Johnsoncdd53b72022-06-07 20:29:24 +0000220 # fix GPT correctness issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400221 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500222 check=False,
Mike Frysinger45602c72019-09-22 02:15:11 -0400223 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700224
Mike Frysinger45602c72019-09-22 02:15:11 -0400225 cros_build_lib.sudo_run(['partx', '-u', device],
226 debug_level=self.debug_level)
227 cros_build_lib.sudo_run(['sync', '-d', device],
228 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700229
David Pursellf1d16a62015-03-25 13:31:04 -0700230 def _GetImagePath(self):
231 """Returns the image path to use."""
Amin Hassanie55168c2020-11-02 14:40:30 -0800232 image_path = None
David Pursellf1d16a62015-03-25 13:31:04 -0700233 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700234 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700235 # TODO(wnwen): Open the tarball and if there is just one file in it,
236 # use that instead. Existing code in upload_symbols.py.
237 if cros_build_lib.BooleanPrompt(
238 prolog='The given image file is not a valid disk image. Perhaps '
239 'you forgot to untar it.',
240 prompt='Terminate the current flash process?'):
241 raise FlashError('Update terminated by user.')
242 image_path = self.image
243 elif os.path.isdir(self.image):
244 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700245 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700246 else:
247 # Translate the xbuddy path to get the exact image to use.
Amin Hassanie55168c2020-11-02 14:40:30 -0800248 _, image_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000249 self.image, self.board, self.version)
David Pursellf1d16a62015-03-25 13:31:04 -0700250
Amin Hassanie55168c2020-11-02 14:40:30 -0800251 logging.info('Using image %s', image_path)
David Pursellf1d16a62015-03-25 13:31:04 -0700252 return image_path
253
254 def Run(self):
255 """Image the removable device."""
256 devices = self.ListAllRemovableDevices()
257
258 if self.device:
259 # If user specified a device path, check if it exists.
260 if not os.path.exists(self.device):
261 raise FlashError('Device path %s does not exist.' % self.device)
262
263 # Then check if it is removable.
264 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
265 msg = '%s is not a removable device.' % self.device
266 if not (self.yes or cros_build_lib.BooleanPrompt(
267 default=False, prolog=msg)):
268 raise FlashError('You can specify usb:// to choose from a list of '
269 'removable devices.')
270 target = None
271 if self.device:
272 # Get device name from path (e.g. sdc in /dev/sdc).
273 target = self.device.rsplit(os.path.sep, 1)[-1]
274 elif devices:
275 # Ask user to choose from the list.
276 target = self.ChooseRemovableDevice(devices)
277 else:
278 raise FlashError('No removable devices detected.')
279
280 image_path = self._GetImagePath()
Jae Hoon Kim7f7bc232022-05-04 23:01:16 +0000281 device = self.DeviceNameToPath(target)
282
283 device_size_bytes = osutils.GetDeviceSize(device, in_bytes=True)
284 image_size_bytes = os.path.getsize(image_path)
285 if device_size_bytes < image_size_bytes:
286 raise FlashError(
287 'Removable device %s has less storage (%d) than the image size (%d).'
288 % (device, device_size_bytes, image_size_bytes))
289
David Pursellf1d16a62015-03-25 13:31:04 -0700290 try:
Amin Hassani04314b12020-12-15 15:59:54 -0800291 self.CopyImageToDevice(image_path, device)
David Pursellf1d16a62015-03-25 13:31:04 -0700292 except cros_build_lib.RunCommandError:
293 logging.error('Failed copying image to device %s',
294 self.DeviceNameToPath(target))
295
296
297class FileImager(USBImager):
298 """Copy image to the target path."""
299
300 def Run(self):
301 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800302 if not os.path.isdir(os.path.dirname(self.device)):
303 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700304
305 image_path = self._GetImagePath()
306 if os.path.isdir(self.device):
307 logging.info('Copying to %s',
308 os.path.join(self.device, os.path.basename(image_path)))
309 else:
310 logging.info('Copying to %s', self.device)
311 try:
312 shutil.copy(image_path, self.device)
313 except IOError:
314 logging.error('Failed to copy image %s to %s', image_path, self.device)
315
316
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000317# TODO(b/190631159, b/196056723): Change default of no_minios_update to |False|.
Amin Hassani9a0199f2021-03-31 19:45:06 -0700318def Flash(device, image, board=None, version=None,
319 no_rootfs_update=False, no_stateful_update=False,
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000320 no_minios_update=True, clobber_stateful=False, reboot=True,
321 ssh_private_key=None, ping=True, disable_rootfs_verification=False,
322 clear_cache=False, yes=False, force=False, debug=False,
Daichi Hirono28831b3b2022-04-07 12:41:11 +0900323 clear_tpm_owner=False, delta=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700324 """Flashes a device, USB drive, or file with an image.
325
326 This provides functionality common to `cros flash` and `brillo flash`
327 so that they can parse the commandline separately but still use the
328 same underlying functionality.
329
330 Args:
David Pursell2e773382015-04-03 14:30:47 -0700331 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700332 image: Path (string) to the update image. Can be a local or xbuddy path;
333 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700334 board: Board to use; None to automatically detect.
Amin Hassani9a0199f2021-03-31 19:45:06 -0700335 no_rootfs_update: Don't update rootfs partition; SSH |device| scheme only.
336 no_stateful_update: Don't update stateful partition; SSH |device| scheme
337 only.
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000338 no_minios_update: Don't update miniOS partition; SSH |device| scheme only.
David Pursellf1d16a62015-03-25 13:31:04 -0700339 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
Yi Chou5f4e51f2020-10-22 16:33:00 +0800340 clear_tpm_owner: Clear the TPM owner on reboot; SSH |device| scheme only.
David Pursellf1d16a62015-03-25 13:31:04 -0700341 reboot: Reboot device after update; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600342 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700343 ping: Ping the device before attempting update; SSH |device| scheme only.
344 disable_rootfs_verification: Remove rootfs verification after update; SSH
345 |device| scheme only.
346 clear_cache: Clear the devserver static directory.
347 yes: Assume "yes" for any prompt.
Sloan Johnsoncdd53b72022-06-07 20:29:24 +0000348 force: Ignore confidence checks and prompts. Overrides |yes| if True.
David Pursellf1d16a62015-03-25 13:31:04 -0700349 debug: Print additional debugging messages.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000350 version: Default version.
Daichi Hirono28831b3b2022-04-07 12:41:11 +0900351 delta: Whether to use delta compression when tranferring image bytes.
David Pursellf1d16a62015-03-25 13:31:04 -0700352
353 Raises:
354 FlashError: An unrecoverable error occured.
355 ValueError: Invalid parameter combination.
356 """
357 if force:
358 yes = True
359
360 if clear_cache:
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000361 ds_wrapper.DevServerWrapper.WipeStaticDirectory()
362 ds_wrapper.DevServerWrapper.CreateStaticDirectory()
David Pursellf1d16a62015-03-25 13:31:04 -0700363
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000364 # The user may not have specified a source image, use version as the default.
365 image = image or version
David Pursell2e773382015-04-03 14:30:47 -0700366 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
367 if device:
368 hostname, port = device.hostname, device.port
369 else:
370 hostname, port = None, None
Amin Hassani153f9162021-02-22 20:48:31 -0800371
Amin Hassani9a0199f2021-03-31 19:45:06 -0700372 with remote_access.ChromiumOSDeviceHandler(
373 hostname, port=port,
374 private_key=ssh_private_key, ping=ping) as device_p:
375 device_imager.DeviceImager(
376 device_p,
377 image,
378 board=board,
379 version=version,
380 no_rootfs_update=no_rootfs_update,
381 no_stateful_update=no_stateful_update,
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000382 no_minios_update=no_minios_update,
Amin Hassani9a0199f2021-03-31 19:45:06 -0700383 no_reboot=not reboot,
384 disable_verification=disable_rootfs_verification,
385 clobber_stateful=clobber_stateful,
Daichi Hirono28831b3b2022-04-07 12:41:11 +0900386 clear_tpm_owner=clear_tpm_owner,
387 delta=delta).Run()
David Pursellf1d16a62015-03-25 13:31:04 -0700388 elif device.scheme == commandline.DEVICE_SCHEME_USB:
389 path = osutils.ExpandPath(device.path) if device.path else ''
390 logging.info('Preparing to image the removable device %s', path)
391 imager = USBImager(path,
392 board,
393 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000394 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700395 debug=debug,
David Pursellf1d16a62015-03-25 13:31:04 -0700396 yes=yes)
397 imager.Run()
398 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
399 logging.info('Preparing to copy image to %s', device.path)
400 imager = FileImager(device.path,
401 board,
402 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000403 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700404 debug=debug,
405 yes=yes)
406 imager.Run()