blob: 16c137c7ea8ae1c58512e21f755f2f702dd03bac [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 Frysinger66d32cd2019-12-17 14:55:29 -050014import subprocess
Mike Frysinger3f087aa2020-03-20 06:03:16 -040015import sys
David Pursellf1d16a62015-03-25 13:31:04 -070016import tempfile
David Pursellf1d16a62015-03-25 13:31:04 -070017
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000018from chromite.cli.cros import cros_chrome_sdk
19
xixuane851dfb2016-05-02 18:02:37 -070020from chromite.lib import auto_updater
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -070021from chromite.lib import auto_updater_transfer
David Pursellf1d16a62015-03-25 13:31:04 -070022from chromite.lib import commandline
23from chromite.lib import cros_build_lib
24from chromite.lib import cros_logging as logging
25from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070026from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070027from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070028from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070029from chromite.lib import remote_access
30
Amin Hassanic0f06fa2019-01-28 15:24:47 -080031from chromite.lib.paygen import paygen_payload_lib
32from chromite.lib.paygen import paygen_stateful_payload_lib
33
Amin Hassani1c25d4b2019-11-22 10:59:07 -080034from chromite.lib.xbuddy import artifact_info
35
David Pursellf1d16a62015-03-25 13:31:04 -070036
Mike Frysinger3f087aa2020-03-20 06:03:16 -040037assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
38
39
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000040def GetDefaultBoard():
41 """Look up default board.
42
43 In a chrome checkout, return $SDK_BOARD. In a chromeos checkout,
44 return the contents of .default_board.
45 """
46 if path_util.DetermineCheckout().type == path_util.CHECKOUT_TYPE_GCLIENT:
47 return os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
48 return cros_build_lib.GetDefaultBoard()
49
50
Ralph Nathan9b997232015-05-15 13:13:12 -070051class UsbImagerOperation(operation.ProgressBarOperation):
52 """Progress bar for flashing image to operation."""
53
54 def __init__(self, image):
55 super(UsbImagerOperation, self).__init__()
56 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070057 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070058 self._bytes = re.compile(r'(\d+) bytes')
59
60 def _GetDDPid(self):
61 """Get the Pid of dd."""
62 try:
Mike Frysinger45602c72019-09-22 02:15:11 -040063 pids = cros_build_lib.run(['pgrep', 'dd'], capture_output=True,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040064 print_cmd=False, encoding='utf-8').stdout
Ralph Nathan9b997232015-05-15 13:13:12 -070065 for pid in pids.splitlines():
66 if osutils.IsChildProcess(int(pid), name='dd'):
67 return int(pid)
68 return -1
69 except cros_build_lib.RunCommandError:
70 # If dd isn't still running, then we assume that it is finished.
71 return -1
72
73 def _PingDD(self, dd_pid):
74 """Send USR1 signal to dd to get status update."""
75 try:
76 cmd = ['kill', '-USR1', str(dd_pid)]
Mike Frysinger45602c72019-09-22 02:15:11 -040077 cros_build_lib.sudo_run(cmd, print_cmd=False)
Ralph Nathan9b997232015-05-15 13:13:12 -070078 except cros_build_lib.RunCommandError:
79 # Here we assume that dd finished in the background.
80 return
81
82 def ParseOutput(self, output=None):
83 """Parse the output of dd to update progress bar."""
84 dd_pid = self._GetDDPid()
85 if dd_pid == -1:
86 return
87
88 self._PingDD(dd_pid)
89
90 if output is None:
91 stdout = self._stdout.read()
92 stderr = self._stderr.read()
93 output = stdout + stderr
94
95 match = self._bytes.search(output)
96 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070097 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -070098
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040099 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -0700100
101
Mike Frysinger32759e42016-12-21 18:40:16 -0500102def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700103 """Determines if a file is a valid GPT disk.
104
105 Args:
106 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -0500107 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700108 """
109 if os.path.isfile(file_path):
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400110 with open(file_path, 'rb') as image_file:
Mike Frysinger32759e42016-12-21 18:40:16 -0500111 if require_pmbr:
112 # Seek to the end of LBA0 and look for the PMBR boot signature.
113 image_file.seek(0x1fe)
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400114 if image_file.read(2) != b'\x55\xaa':
Mike Frysinger32759e42016-12-21 18:40:16 -0500115 return False
116 # Current file position is start of LBA1 now.
117 else:
118 # Seek to LBA1 where the GPT starts.
119 image_file.seek(0x200)
120
121 # See if there's a GPT here.
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400122 if image_file.read(8) == b'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700123 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500124
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700125 return False
126
127
128def _ChooseImageFromDirectory(dir_path):
129 """Lists all image files in |dir_path| and ask user to select one.
130
131 Args:
132 dir_path: Path to the directory.
133 """
134 images = sorted([x for x in os.listdir(dir_path) if
135 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
136 idx = 0
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400137 if not images:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700138 raise ValueError('No image found in %s.' % dir_path)
139 elif len(images) > 1:
140 idx = cros_build_lib.GetChoice(
141 'Multiple images found in %s. Please select one to continue:' % (
142 (dir_path,)),
143 images)
144
145 return os.path.join(dir_path, images[idx])
146
147
David Pursellf1d16a62015-03-25 13:31:04 -0700148class FlashError(Exception):
149 """Thrown when there is an unrecoverable error during flash."""
150
151
152class USBImager(object):
153 """Copy image to the target removable device."""
154
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000155 def __init__(self, device, board, image, version, debug=False,
156 install=False, yes=False):
157 """Initializes USBImager."""
David Pursellf1d16a62015-03-25 13:31:04 -0700158 self.device = device
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000159 self.board = board if board else GetDefaultBoard()
David Pursellf1d16a62015-03-25 13:31:04 -0700160 self.image = image
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000161 self.version = version
David Pursellf1d16a62015-03-25 13:31:04 -0700162 self.debug = debug
163 self.debug_level = logging.DEBUG if debug else logging.INFO
164 self.install = install
165 self.yes = yes
166
167 def DeviceNameToPath(self, device_name):
168 return '/dev/%s' % device_name
169
170 def GetRemovableDeviceDescription(self, device):
171 """Returns a informational description of the removable |device|.
172
173 Args:
174 device: the device name (e.g. sdc).
175
176 Returns:
177 A string describing |device| (e.g. Patriot Memory 7918 MB).
178 """
179 desc = [
180 osutils.GetDeviceInfo(device, keyword='manufacturer'),
181 osutils.GetDeviceInfo(device, keyword='product'),
182 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
183 '(%s)' % self.DeviceNameToPath(device),
184 ]
185 return ' '.join([x for x in desc if x])
186
187 def ListAllRemovableDevices(self):
188 """Returns a list of removable devices.
189
190 Returns:
191 A list of device names (e.g. ['sdb', 'sdc']).
192 """
193 devices = osutils.ListBlockDevices()
194 removable_devices = []
195 for d in devices:
196 if d.TYPE == 'disk' and d.RM == '1':
197 removable_devices.append(d.NAME)
198
199 return removable_devices
200
201 def ChooseRemovableDevice(self, devices):
202 """Lists all removable devices and asks user to select/confirm.
203
204 Args:
205 devices: a list of device names (e.g. ['sda', 'sdb']).
206
207 Returns:
208 The device name chosen by the user.
209 """
210 idx = cros_build_lib.GetChoice(
211 'Removable device(s) found. Please select/confirm to continue:',
212 [self.GetRemovableDeviceDescription(x) for x in devices])
213
214 return devices[idx]
215
216 def InstallImageToDevice(self, image, device):
217 """Installs |image| to the removable |device|.
218
219 Args:
220 image: Path to the image to copy.
221 device: Device to copy to.
222 """
223 cmd = [
224 'chromeos-install',
225 '--yes',
226 '--skip_src_removable',
227 '--skip_dst_removable',
228 '--payload_image=%s' % image,
229 '--dst=%s' % device,
230 '--skip_postinstall',
231 ]
Mike Frysinger45602c72019-09-22 02:15:11 -0400232 cros_build_lib.sudo_run(cmd,
233 print_cmd=True,
234 debug_level=logging.NOTICE,
Mike Frysinger66d32cd2019-12-17 14:55:29 -0500235 stderr=subprocess.STDOUT,
Mike Frysinger45602c72019-09-22 02:15:11 -0400236 log_output=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700237
238 def CopyImageToDevice(self, image, device):
239 """Copies |image| to the removable |device|.
240
241 Args:
242 image: Path to the image to copy.
243 device: Device to copy to.
244 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700245 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800246 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700247 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
248 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400249 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400250 encoding='utf-8', update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700251 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400252 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700253 cmd, debug_level=logging.NOTICE,
254 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700255
Brian Norris6386fde2018-10-29 13:34:28 -0700256 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
257 # up for us with a 'write' command, so we have a standards-conforming GPT.
258 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
259 # fix GPT sanity issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400260 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500261 check=False,
Mike Frysinger45602c72019-09-22 02:15:11 -0400262 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700263
Mike Frysinger45602c72019-09-22 02:15:11 -0400264 cros_build_lib.sudo_run(['partx', '-u', device],
265 debug_level=self.debug_level)
266 cros_build_lib.sudo_run(['sync', '-d', device],
267 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700268
David Pursellf1d16a62015-03-25 13:31:04 -0700269 def _GetImagePath(self):
270 """Returns the image path to use."""
Amin Hassanie55168c2020-11-02 14:40:30 -0800271 image_path = None
David Pursellf1d16a62015-03-25 13:31:04 -0700272 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700273 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700274 # TODO(wnwen): Open the tarball and if there is just one file in it,
275 # use that instead. Existing code in upload_symbols.py.
276 if cros_build_lib.BooleanPrompt(
277 prolog='The given image file is not a valid disk image. Perhaps '
278 'you forgot to untar it.',
279 prompt='Terminate the current flash process?'):
280 raise FlashError('Update terminated by user.')
281 image_path = self.image
282 elif os.path.isdir(self.image):
283 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700284 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700285 else:
286 # Translate the xbuddy path to get the exact image to use.
Amin Hassanie55168c2020-11-02 14:40:30 -0800287 _, image_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000288 self.image, self.board, self.version)
David Pursellf1d16a62015-03-25 13:31:04 -0700289
Amin Hassanie55168c2020-11-02 14:40:30 -0800290 logging.info('Using image %s', image_path)
David Pursellf1d16a62015-03-25 13:31:04 -0700291 return image_path
292
293 def Run(self):
294 """Image the removable device."""
295 devices = self.ListAllRemovableDevices()
296
297 if self.device:
298 # If user specified a device path, check if it exists.
299 if not os.path.exists(self.device):
300 raise FlashError('Device path %s does not exist.' % self.device)
301
302 # Then check if it is removable.
303 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
304 msg = '%s is not a removable device.' % self.device
305 if not (self.yes or cros_build_lib.BooleanPrompt(
306 default=False, prolog=msg)):
307 raise FlashError('You can specify usb:// to choose from a list of '
308 'removable devices.')
309 target = None
310 if self.device:
311 # Get device name from path (e.g. sdc in /dev/sdc).
312 target = self.device.rsplit(os.path.sep, 1)[-1]
313 elif devices:
314 # Ask user to choose from the list.
315 target = self.ChooseRemovableDevice(devices)
316 else:
317 raise FlashError('No removable devices detected.')
318
319 image_path = self._GetImagePath()
320 try:
321 device = self.DeviceNameToPath(target)
322 if self.install:
323 self.InstallImageToDevice(image_path, device)
324 else:
325 self.CopyImageToDevice(image_path, device)
326 except cros_build_lib.RunCommandError:
327 logging.error('Failed copying image to device %s',
328 self.DeviceNameToPath(target))
329
330
331class FileImager(USBImager):
332 """Copy image to the target path."""
333
334 def Run(self):
335 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800336 if not os.path.isdir(os.path.dirname(self.device)):
337 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700338
339 image_path = self._GetImagePath()
340 if os.path.isdir(self.device):
341 logging.info('Copying to %s',
342 os.path.join(self.device, os.path.basename(image_path)))
343 else:
344 logging.info('Copying to %s', self.device)
345 try:
346 shutil.copy(image_path, self.device)
347 except IOError:
348 logging.error('Failed to copy image %s to %s', image_path, self.device)
349
350
351class RemoteDeviceUpdater(object):
352 """Performs update on a remote device."""
David Pursellf1d16a62015-03-25 13:31:04 -0700353 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
354 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700355 # Root working directory on the device. This directory is in the
356 # stateful partition and thus has enough space to store the payloads.
Amin Hassanidc4e7f02020-02-03 11:10:22 -0800357 DEVICE_BASE_DIR = '/usr/local/tmp/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700358 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
359 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700360
361 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
362 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700363 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600364 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800365 disable_verification=False, send_payload_in_parallel=False,
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800366 clear_tpm_owner=False, version=None,
367 copy_payloads_to_device=True):
David Pursellf1d16a62015-03-25 13:31:04 -0700368 """Initializes RemoteDeviceUpdater"""
369 if not stateful_update and not rootfs_update:
370 raise ValueError('No update operation to perform; either stateful or'
371 ' rootfs partitions must be updated.')
372 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
373 self.ssh_hostname = ssh_hostname
374 self.ssh_port = ssh_port
375 self.image = image
376 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700377 self.src_image_to_delta = src_image_to_delta
378 self.do_stateful_update = stateful_update
379 self.do_rootfs_update = rootfs_update
380 self.disable_verification = disable_verification
381 self.clobber_stateful = clobber_stateful
Yi Chou5f4e51f2020-10-22 16:33:00 +0800382 self.clear_tpm_owner = clear_tpm_owner
David Pursellf1d16a62015-03-25 13:31:04 -0700383 self.reboot = reboot
384 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600385 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700386 self.ping = ping
387 # Do not wipe if debug is set.
388 self.wipe = wipe and not debug
389 self.yes = yes
390 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800391 self.send_payload_in_parallel = send_payload_in_parallel
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000392 self.version = version
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800393 self.copy_payloads_to_device = copy_payloads_to_device
David Pursellf1d16a62015-03-25 13:31:04 -0700394
David Pursellf1d16a62015-03-25 13:31:04 -0700395 def Cleanup(self):
396 """Cleans up the temporary directory."""
397 if self.wipe:
398 logging.info('Cleaning up temporary working directory...')
399 osutils.RmDir(self.tempdir)
400 else:
401 logging.info('You can find the log files and/or payloads in %s',
402 self.tempdir)
403
Amin Hassani9fe72b62020-10-29 15:43:31 -0700404 def GetPayloadPaths(self, device):
405 """Get directory of payload and rootfs payload file name for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700406
xixuane851dfb2016-05-02 18:02:37 -0700407 This method is used to obtain the directory of payload for cros-flash. The
408 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700409
xixuane851dfb2016-05-02 18:02:37 -0700410 If self.image is a directory, we directly use the provided update payload(s)
411 in this directory.
412
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800413 If self.image is an image, we will generate payloads for it and put them in
414 our temporary directory. The reason is that people may modify a local image
415 or override it (on the same path) with a different image, so in order to be
416 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700417
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800418 If non of the above cases, we use the xbuddy to first obtain the image path
419 (and possibly download it). Then we will generate the payloads in the same
420 directory the image is located. The reason is that this is what devserver
421 used to do. The path to the image generated by the devserver (or xbuddy) is
422 unique and normally nobody override its image with a different one. That is
423 why I think it is safe to put the payloads next to the image. This is a poor
424 man's version of caching but it makes cros flash faster for users who flash
425 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700426
427 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400428 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700429
430 Returns:
Amin Hassani9fe72b62020-10-29 15:43:31 -0700431 A string tuple (payload_dir, rootfs_filename). payload_dir is the
432 directory where the update payloads are located. rootfs_filename is the
433 name of the rootfs update payload (sometimes update.gz).
David Pursellf1d16a62015-03-25 13:31:04 -0700434 """
Amin Hassani9fe72b62020-10-29 15:43:31 -0700435 rootfs_filename = auto_updater_transfer.ROOTFS_FILENAME
436
xixuane851dfb2016-05-02 18:02:37 -0700437 if os.path.isdir(self.image):
438 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800439 logging.info('Using provided payloads in %s', self.image)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700440 return self.image, rootfs_filename
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600441
Amin Hassani9fe72b62020-10-29 15:43:31 -0700442 image_path = self.image
443 payload_dir = self.tempdir
444
445 if not os.path.isfile(self.image):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800446 # Assuming it is an xbuddy path.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000447 self.board = cros_build_lib.GetBoard(
448 device_board=device.board or GetDefaultBoard(),
449 override_board=self.board,
450 force=self.yes,
451 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700452 if not self.force and self.board != device.board:
453 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700454 raise FlashError('Device (%s) is incompatible with board %s' %
455 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700456 logging.info('Board is %s', self.board)
457
Amin Hassanic20a3c32019-06-02 21:43:21 -0700458 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
459 # use xbuddy library directly instead of the devserver_wrapper.
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200460 # Fetch the full payload and properties, and stateful files. If this
461 # fails, fallback to downloading the image.
462 try:
Amin Hassanie55168c2020-11-02 14:40:30 -0800463 _, local_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000464 os.path.join(self.image, artifact_info.FULL_PAYLOAD),
465 self.board, self.version, silent=True)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700466 payload_dir, rootfs_filename = os.path.split(local_path)
467
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200468 ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800469 os.path.join(self.image, artifact_info.STATEFUL_PAYLOAD),
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000470 self.board, self.version, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200471 fetch_image = False
472 except (ds_wrapper.ImagePathError, ds_wrapper.ArtifactDownloadError):
473 logging.info('Could not find full_payload or stateful for "%s"',
474 self.image)
475 fetch_image = True
xixuane851dfb2016-05-02 18:02:37 -0700476
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200477 # We didn't find the full_payload, attempt to download the image.
478 if fetch_image:
Amin Hassanie55168c2020-11-02 14:40:30 -0800479 _, image_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000480 self.image, self.board, self.version)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200481 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
482 logging.notice('Using image path %s and payload directory %s',
483 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700484
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800485 # Generate rootfs and stateful update payloads if they do not exist.
Amin Hassani9fe72b62020-10-29 15:43:31 -0700486 payload_path = os.path.join(payload_dir, rootfs_filename)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800487 if not os.path.exists(payload_path):
488 paygen_payload_lib.GenerateUpdatePayload(
489 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700490 if not os.path.exists(os.path.join(
491 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800492 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
493 payload_dir)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700494 return payload_dir, rootfs_filename
David Pursellf1d16a62015-03-25 13:31:04 -0700495
496 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700497 """Perform remote device update.
498
499 The update process includes:
500 1. initialize a device instance for the given remote device.
501 2. achieve payload_dir which contains the required payloads for updating.
502 3. initialize an auto-updater instance to do RunUpdate().
503 4. After auto-update, all temp files and dir will be cleaned up.
504 """
David Pursellf1d16a62015-03-25 13:31:04 -0700505 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700506 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600507 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
508 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700509
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600510 try:
511 # Get payload directory
Zentaro Kavanagh8f79c2a2020-05-28 14:12:07 -0700512 logging.notice('Staging payloads...')
Amin Hassani9fe72b62020-10-29 15:43:31 -0700513 payload_dir, rootfs_filename = self.GetPayloadPaths(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700514
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600515 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700516 chromeos_AU = auto_updater.ChromiumOSUpdater(
517 device=device,
518 build_name=None,
519 payload_dir=payload_dir,
Amin Hassani9fe72b62020-10-29 15:43:31 -0700520 payload_filename=rootfs_filename,
Amin Hassani9800d432019-07-24 14:23:39 -0700521 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600522 do_rootfs_update=self.do_rootfs_update,
523 do_stateful_update=self.do_stateful_update,
524 reboot=self.reboot,
525 disable_verification=self.disable_verification,
526 clobber_stateful=self.clobber_stateful,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800527 clear_tpm_owner=self.clear_tpm_owner,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800528 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800529 send_payload_in_parallel=self.send_payload_in_parallel,
Amin Hassani3e87ce12020-10-22 10:39:36 -0700530 resolve_app_id_mismatch=True,
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800531 transfer_class=auto_updater_transfer.LocalTransfer,
532 copy_payloads_to_device=self.copy_payloads_to_device)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600533 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700534
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600535 except Exception:
536 logging.error('Device update failed.')
537 lsb_entries = sorted(device.lsb_release.items())
538 logging.info(
539 'Following are the LSB version details of the device:\n%s',
540 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
541 raise
542
543 logging.notice('Update performed successfully.')
544
545 except remote_access.RemoteAccessException:
546 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700547 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600548
David Pursellf1d16a62015-03-25 13:31:04 -0700549 finally:
550 self.Cleanup()
551
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200552
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700553def Flash(device, image, board=None, install=False, src_image_to_delta=None,
554 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600555 reboot=True, wipe=True, ssh_private_key=None, ping=True,
556 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800557 force=False, debug=False, send_payload_in_parallel=False,
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800558 clear_tpm_owner=False, version=None, copy_payloads_to_device=True):
David Pursellf1d16a62015-03-25 13:31:04 -0700559 """Flashes a device, USB drive, or file with an image.
560
561 This provides functionality common to `cros flash` and `brillo flash`
562 so that they can parse the commandline separately but still use the
563 same underlying functionality.
564
565 Args:
David Pursell2e773382015-04-03 14:30:47 -0700566 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700567 image: Path (string) to the update image. Can be a local or xbuddy path;
568 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700569 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700570 install: Install to USB using base disk layout; USB |device| scheme only.
571 src_image_to_delta: Local path to an image to be used as the base to
572 generate delta payloads; SSH |device| scheme only.
573 rootfs_update: Update rootfs partition; SSH |device| scheme only.
574 stateful_update: Update stateful partition; SSH |device| scheme only.
575 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
Yi Chou5f4e51f2020-10-22 16:33:00 +0800576 clear_tpm_owner: Clear the TPM owner on reboot; SSH |device| scheme only.
David Pursellf1d16a62015-03-25 13:31:04 -0700577 reboot: Reboot device after update; SSH |device| scheme only.
578 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600579 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700580 ping: Ping the device before attempting update; SSH |device| scheme only.
581 disable_rootfs_verification: Remove rootfs verification after update; SSH
582 |device| scheme only.
583 clear_cache: Clear the devserver static directory.
584 yes: Assume "yes" for any prompt.
585 force: Ignore sanity checks and prompts. Overrides |yes| if True.
586 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800587 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
588 up transmissions for long haul between endpoints.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000589 version: Default version.
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800590 copy_payloads_to_device: If True, update payloads are copied to the
591 Chromium OS device first. Otherwise, they are piped through SSH.
592 Currently, this only applies to the stateful payloads.
David Pursellf1d16a62015-03-25 13:31:04 -0700593
594 Raises:
595 FlashError: An unrecoverable error occured.
596 ValueError: Invalid parameter combination.
597 """
598 if force:
599 yes = True
600
601 if clear_cache:
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000602 ds_wrapper.DevServerWrapper.WipeStaticDirectory()
603 ds_wrapper.DevServerWrapper.CreateStaticDirectory()
David Pursellf1d16a62015-03-25 13:31:04 -0700604
605 if install:
David Pursell2e773382015-04-03 14:30:47 -0700606 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700607 raise ValueError(
608 '--install can only be used when writing to a USB device')
609 if not cros_build_lib.IsInsideChroot():
610 raise ValueError('--install can only be used inside the chroot')
611
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000612 # The user may not have specified a source image, use version as the default.
613 image = image or version
David Pursell2e773382015-04-03 14:30:47 -0700614 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
615 if device:
616 hostname, port = device.hostname, device.port
617 else:
618 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700619 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700620 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700621 hostname,
622 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700623 image,
624 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700625 src_image_to_delta=src_image_to_delta,
626 rootfs_update=rootfs_update,
627 stateful_update=stateful_update,
628 clobber_stateful=clobber_stateful,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800629 clear_tpm_owner=clear_tpm_owner,
David Pursellf1d16a62015-03-25 13:31:04 -0700630 reboot=reboot,
631 wipe=wipe,
632 debug=debug,
633 yes=yes,
634 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600635 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700636 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800637 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800638 send_payload_in_parallel=send_payload_in_parallel,
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800639 version=version,
640 copy_payloads_to_device=copy_payloads_to_device)
David Pursellf1d16a62015-03-25 13:31:04 -0700641 updater.Run()
642 elif device.scheme == commandline.DEVICE_SCHEME_USB:
643 path = osutils.ExpandPath(device.path) if device.path else ''
644 logging.info('Preparing to image the removable device %s', path)
645 imager = USBImager(path,
646 board,
647 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000648 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700649 debug=debug,
650 install=install,
651 yes=yes)
652 imager.Run()
653 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
654 logging.info('Preparing to copy image to %s', device.path)
655 imager = FileImager(device.path,
656 board,
657 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000658 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700659 debug=debug,
660 yes=yes)
661 imager.Run()