blob: 3764caeaf207b16381045ac66f358d0de0e37247 [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 -070015import tempfile
David Pursellf1d16a62015-03-25 13:31:04 -070016
Amin Hassani153f9162021-02-22 20:48:31 -080017from chromite.cli import device_imager
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
Amin Hassani04314b12020-12-15 15:59:54 -0800155 def __init__(self, device, board, image, version, debug=False, yes=False):
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000156 """Initializes USBImager."""
David Pursellf1d16a62015-03-25 13:31:04 -0700157 self.device = device
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000158 self.board = board if board else GetDefaultBoard()
David Pursellf1d16a62015-03-25 13:31:04 -0700159 self.image = image
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000160 self.version = version
David Pursellf1d16a62015-03-25 13:31:04 -0700161 self.debug = debug
162 self.debug_level = logging.DEBUG if debug else logging.INFO
David Pursellf1d16a62015-03-25 13:31:04 -0700163 self.yes = yes
164
165 def DeviceNameToPath(self, device_name):
166 return '/dev/%s' % device_name
167
168 def GetRemovableDeviceDescription(self, device):
169 """Returns a informational description of the removable |device|.
170
171 Args:
172 device: the device name (e.g. sdc).
173
174 Returns:
175 A string describing |device| (e.g. Patriot Memory 7918 MB).
176 """
177 desc = [
178 osutils.GetDeviceInfo(device, keyword='manufacturer'),
179 osutils.GetDeviceInfo(device, keyword='product'),
180 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
181 '(%s)' % self.DeviceNameToPath(device),
182 ]
183 return ' '.join([x for x in desc if x])
184
185 def ListAllRemovableDevices(self):
186 """Returns a list of removable devices.
187
188 Returns:
189 A list of device names (e.g. ['sdb', 'sdc']).
190 """
191 devices = osutils.ListBlockDevices()
192 removable_devices = []
193 for d in devices:
194 if d.TYPE == 'disk' and d.RM == '1':
195 removable_devices.append(d.NAME)
196
197 return removable_devices
198
199 def ChooseRemovableDevice(self, devices):
200 """Lists all removable devices and asks user to select/confirm.
201
202 Args:
203 devices: a list of device names (e.g. ['sda', 'sdb']).
204
205 Returns:
206 The device name chosen by the user.
207 """
208 idx = cros_build_lib.GetChoice(
209 'Removable device(s) found. Please select/confirm to continue:',
210 [self.GetRemovableDeviceDescription(x) for x in devices])
211
212 return devices[idx]
213
David Pursellf1d16a62015-03-25 13:31:04 -0700214 def CopyImageToDevice(self, image, device):
215 """Copies |image| to the removable |device|.
216
217 Args:
218 image: Path to the image to copy.
219 device: Device to copy to.
220 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700221 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800222 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700223 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
224 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400225 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400226 encoding='utf-8', update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700227 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400228 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700229 cmd, debug_level=logging.NOTICE,
230 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700231
Brian Norris6386fde2018-10-29 13:34:28 -0700232 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
233 # up for us with a 'write' command, so we have a standards-conforming GPT.
234 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
235 # fix GPT sanity issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400236 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500237 check=False,
Mike Frysinger45602c72019-09-22 02:15:11 -0400238 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700239
Mike Frysinger45602c72019-09-22 02:15:11 -0400240 cros_build_lib.sudo_run(['partx', '-u', device],
241 debug_level=self.debug_level)
242 cros_build_lib.sudo_run(['sync', '-d', device],
243 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700244
David Pursellf1d16a62015-03-25 13:31:04 -0700245 def _GetImagePath(self):
246 """Returns the image path to use."""
Amin Hassanie55168c2020-11-02 14:40:30 -0800247 image_path = None
David Pursellf1d16a62015-03-25 13:31:04 -0700248 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700249 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700250 # TODO(wnwen): Open the tarball and if there is just one file in it,
251 # use that instead. Existing code in upload_symbols.py.
252 if cros_build_lib.BooleanPrompt(
253 prolog='The given image file is not a valid disk image. Perhaps '
254 'you forgot to untar it.',
255 prompt='Terminate the current flash process?'):
256 raise FlashError('Update terminated by user.')
257 image_path = self.image
258 elif os.path.isdir(self.image):
259 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700260 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700261 else:
262 # Translate the xbuddy path to get the exact image to use.
Amin Hassanie55168c2020-11-02 14:40:30 -0800263 _, image_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000264 self.image, self.board, self.version)
David Pursellf1d16a62015-03-25 13:31:04 -0700265
Amin Hassanie55168c2020-11-02 14:40:30 -0800266 logging.info('Using image %s', image_path)
David Pursellf1d16a62015-03-25 13:31:04 -0700267 return image_path
268
269 def Run(self):
270 """Image the removable device."""
271 devices = self.ListAllRemovableDevices()
272
273 if self.device:
274 # If user specified a device path, check if it exists.
275 if not os.path.exists(self.device):
276 raise FlashError('Device path %s does not exist.' % self.device)
277
278 # Then check if it is removable.
279 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
280 msg = '%s is not a removable device.' % self.device
281 if not (self.yes or cros_build_lib.BooleanPrompt(
282 default=False, prolog=msg)):
283 raise FlashError('You can specify usb:// to choose from a list of '
284 'removable devices.')
285 target = None
286 if self.device:
287 # Get device name from path (e.g. sdc in /dev/sdc).
288 target = self.device.rsplit(os.path.sep, 1)[-1]
289 elif devices:
290 # Ask user to choose from the list.
291 target = self.ChooseRemovableDevice(devices)
292 else:
293 raise FlashError('No removable devices detected.')
294
295 image_path = self._GetImagePath()
296 try:
297 device = self.DeviceNameToPath(target)
Amin Hassani04314b12020-12-15 15:59:54 -0800298 self.CopyImageToDevice(image_path, device)
David Pursellf1d16a62015-03-25 13:31:04 -0700299 except cros_build_lib.RunCommandError:
300 logging.error('Failed copying image to device %s',
301 self.DeviceNameToPath(target))
302
303
304class FileImager(USBImager):
305 """Copy image to the target path."""
306
307 def Run(self):
308 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800309 if not os.path.isdir(os.path.dirname(self.device)):
310 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700311
312 image_path = self._GetImagePath()
313 if os.path.isdir(self.device):
314 logging.info('Copying to %s',
315 os.path.join(self.device, os.path.basename(image_path)))
316 else:
317 logging.info('Copying to %s', self.device)
318 try:
319 shutil.copy(image_path, self.device)
320 except IOError:
321 logging.error('Failed to copy image %s to %s', image_path, self.device)
322
323
324class RemoteDeviceUpdater(object):
325 """Performs update on a remote device."""
David Pursellf1d16a62015-03-25 13:31:04 -0700326 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
327 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700328 # Root working directory on the device. This directory is in the
329 # stateful partition and thus has enough space to store the payloads.
Amin Hassanidc4e7f02020-02-03 11:10:22 -0800330 DEVICE_BASE_DIR = '/usr/local/tmp/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700331 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
332 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700333
334 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
335 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700336 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600337 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800338 disable_verification=False, send_payload_in_parallel=False,
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800339 clear_tpm_owner=False, version=None,
340 copy_payloads_to_device=True):
David Pursellf1d16a62015-03-25 13:31:04 -0700341 """Initializes RemoteDeviceUpdater"""
342 if not stateful_update and not rootfs_update:
343 raise ValueError('No update operation to perform; either stateful or'
344 ' rootfs partitions must be updated.')
345 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
346 self.ssh_hostname = ssh_hostname
347 self.ssh_port = ssh_port
348 self.image = image
349 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700350 self.src_image_to_delta = src_image_to_delta
351 self.do_stateful_update = stateful_update
352 self.do_rootfs_update = rootfs_update
353 self.disable_verification = disable_verification
354 self.clobber_stateful = clobber_stateful
Yi Chou5f4e51f2020-10-22 16:33:00 +0800355 self.clear_tpm_owner = clear_tpm_owner
David Pursellf1d16a62015-03-25 13:31:04 -0700356 self.reboot = reboot
357 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600358 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700359 self.ping = ping
360 # Do not wipe if debug is set.
361 self.wipe = wipe and not debug
362 self.yes = yes
363 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800364 self.send_payload_in_parallel = send_payload_in_parallel
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000365 self.version = version
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800366 self.copy_payloads_to_device = copy_payloads_to_device
David Pursellf1d16a62015-03-25 13:31:04 -0700367
David Pursellf1d16a62015-03-25 13:31:04 -0700368 def Cleanup(self):
369 """Cleans up the temporary directory."""
370 if self.wipe:
371 logging.info('Cleaning up temporary working directory...')
372 osutils.RmDir(self.tempdir)
373 else:
374 logging.info('You can find the log files and/or payloads in %s',
375 self.tempdir)
376
Amin Hassani9fe72b62020-10-29 15:43:31 -0700377 def GetPayloadPaths(self, device):
378 """Get directory of payload and rootfs payload file name for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700379
xixuane851dfb2016-05-02 18:02:37 -0700380 This method is used to obtain the directory of payload for cros-flash. The
381 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700382
xixuane851dfb2016-05-02 18:02:37 -0700383 If self.image is a directory, we directly use the provided update payload(s)
384 in this directory.
385
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800386 If self.image is an image, we will generate payloads for it and put them in
387 our temporary directory. The reason is that people may modify a local image
388 or override it (on the same path) with a different image, so in order to be
389 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700390
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800391 If non of the above cases, we use the xbuddy to first obtain the image path
392 (and possibly download it). Then we will generate the payloads in the same
393 directory the image is located. The reason is that this is what devserver
394 used to do. The path to the image generated by the devserver (or xbuddy) is
395 unique and normally nobody override its image with a different one. That is
396 why I think it is safe to put the payloads next to the image. This is a poor
397 man's version of caching but it makes cros flash faster for users who flash
398 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700399
400 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400401 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700402
403 Returns:
Amin Hassani9fe72b62020-10-29 15:43:31 -0700404 A string tuple (payload_dir, rootfs_filename). payload_dir is the
405 directory where the update payloads are located. rootfs_filename is the
406 name of the rootfs update payload (sometimes update.gz).
David Pursellf1d16a62015-03-25 13:31:04 -0700407 """
Amin Hassani9fe72b62020-10-29 15:43:31 -0700408 rootfs_filename = auto_updater_transfer.ROOTFS_FILENAME
409
xixuane851dfb2016-05-02 18:02:37 -0700410 if os.path.isdir(self.image):
411 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800412 logging.info('Using provided payloads in %s', self.image)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700413 return self.image, rootfs_filename
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600414
Amin Hassani9fe72b62020-10-29 15:43:31 -0700415 image_path = self.image
416 payload_dir = self.tempdir
417
418 if not os.path.isfile(self.image):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800419 # Assuming it is an xbuddy path.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000420 self.board = cros_build_lib.GetBoard(
421 device_board=device.board or GetDefaultBoard(),
422 override_board=self.board,
423 force=self.yes,
424 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700425 if not self.force and self.board != device.board:
426 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700427 raise FlashError('Device (%s) is incompatible with board %s' %
428 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700429 logging.info('Board is %s', self.board)
430
Amin Hassanic20a3c32019-06-02 21:43:21 -0700431 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
432 # use xbuddy library directly instead of the devserver_wrapper.
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200433 # Fetch the full payload and properties, and stateful files. If this
434 # fails, fallback to downloading the image.
435 try:
Amin Hassanie55168c2020-11-02 14:40:30 -0800436 _, local_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000437 os.path.join(self.image, artifact_info.FULL_PAYLOAD),
438 self.board, self.version, silent=True)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700439 payload_dir, rootfs_filename = os.path.split(local_path)
440
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200441 ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800442 os.path.join(self.image, artifact_info.STATEFUL_PAYLOAD),
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000443 self.board, self.version, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200444 fetch_image = False
445 except (ds_wrapper.ImagePathError, ds_wrapper.ArtifactDownloadError):
446 logging.info('Could not find full_payload or stateful for "%s"',
447 self.image)
448 fetch_image = True
xixuane851dfb2016-05-02 18:02:37 -0700449
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200450 # We didn't find the full_payload, attempt to download the image.
451 if fetch_image:
Amin Hassanie55168c2020-11-02 14:40:30 -0800452 _, image_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000453 self.image, self.board, self.version)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200454 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
455 logging.notice('Using image path %s and payload directory %s',
456 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700457
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800458 # Generate rootfs and stateful update payloads if they do not exist.
Amin Hassani9fe72b62020-10-29 15:43:31 -0700459 payload_path = os.path.join(payload_dir, rootfs_filename)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800460 if not os.path.exists(payload_path):
461 paygen_payload_lib.GenerateUpdatePayload(
462 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700463 if not os.path.exists(os.path.join(
464 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800465 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
466 payload_dir)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700467 return payload_dir, rootfs_filename
David Pursellf1d16a62015-03-25 13:31:04 -0700468
469 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700470 """Perform remote device update.
471
472 The update process includes:
473 1. initialize a device instance for the given remote device.
474 2. achieve payload_dir which contains the required payloads for updating.
475 3. initialize an auto-updater instance to do RunUpdate().
476 4. After auto-update, all temp files and dir will be cleaned up.
477 """
David Pursellf1d16a62015-03-25 13:31:04 -0700478 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700479 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600480 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
481 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700482
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600483 try:
484 # Get payload directory
Zentaro Kavanagh8f79c2a2020-05-28 14:12:07 -0700485 logging.notice('Staging payloads...')
Amin Hassani9fe72b62020-10-29 15:43:31 -0700486 payload_dir, rootfs_filename = self.GetPayloadPaths(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700487
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600488 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700489 chromeos_AU = auto_updater.ChromiumOSUpdater(
490 device=device,
491 build_name=None,
492 payload_dir=payload_dir,
Amin Hassani9fe72b62020-10-29 15:43:31 -0700493 payload_filename=rootfs_filename,
Amin Hassani9800d432019-07-24 14:23:39 -0700494 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600495 do_rootfs_update=self.do_rootfs_update,
496 do_stateful_update=self.do_stateful_update,
497 reboot=self.reboot,
498 disable_verification=self.disable_verification,
499 clobber_stateful=self.clobber_stateful,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800500 clear_tpm_owner=self.clear_tpm_owner,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800501 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800502 send_payload_in_parallel=self.send_payload_in_parallel,
Amin Hassani3e87ce12020-10-22 10:39:36 -0700503 resolve_app_id_mismatch=True,
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800504 transfer_class=auto_updater_transfer.LocalTransfer,
505 copy_payloads_to_device=self.copy_payloads_to_device)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600506 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700507
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600508 except Exception:
509 logging.error('Device update failed.')
510 lsb_entries = sorted(device.lsb_release.items())
511 logging.info(
512 'Following are the LSB version details of the device:\n%s',
513 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
514 raise
515
516 logging.notice('Update performed successfully.')
517
518 except remote_access.RemoteAccessException:
519 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700520 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600521
David Pursellf1d16a62015-03-25 13:31:04 -0700522 finally:
523 self.Cleanup()
524
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200525
Amin Hassani04314b12020-12-15 15:59:54 -0800526def Flash(device, image, board=None, src_image_to_delta=None,
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700527 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600528 reboot=True, wipe=True, ssh_private_key=None, ping=True,
529 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800530 force=False, debug=False, send_payload_in_parallel=False,
Amin Hassani153f9162021-02-22 20:48:31 -0800531 clear_tpm_owner=False, version=None, copy_payloads_to_device=True,
532 exp_new_flash=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700533 """Flashes a device, USB drive, or file with an image.
534
535 This provides functionality common to `cros flash` and `brillo flash`
536 so that they can parse the commandline separately but still use the
537 same underlying functionality.
538
539 Args:
David Pursell2e773382015-04-03 14:30:47 -0700540 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700541 image: Path (string) to the update image. Can be a local or xbuddy path;
542 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700543 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700544 src_image_to_delta: Local path to an image to be used as the base to
545 generate delta payloads; SSH |device| scheme only.
546 rootfs_update: Update rootfs partition; SSH |device| scheme only.
547 stateful_update: Update stateful partition; SSH |device| scheme only.
548 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
Yi Chou5f4e51f2020-10-22 16:33:00 +0800549 clear_tpm_owner: Clear the TPM owner on reboot; SSH |device| scheme only.
David Pursellf1d16a62015-03-25 13:31:04 -0700550 reboot: Reboot device after update; SSH |device| scheme only.
551 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600552 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700553 ping: Ping the device before attempting update; SSH |device| scheme only.
554 disable_rootfs_verification: Remove rootfs verification after update; SSH
555 |device| scheme only.
556 clear_cache: Clear the devserver static directory.
557 yes: Assume "yes" for any prompt.
558 force: Ignore sanity checks and prompts. Overrides |yes| if True.
559 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800560 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
561 up transmissions for long haul between endpoints.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000562 version: Default version.
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800563 copy_payloads_to_device: If True, update payloads are copied to the
564 Chromium OS device first. Otherwise, they are piped through SSH.
565 Currently, this only applies to the stateful payloads.
Amin Hassani153f9162021-02-22 20:48:31 -0800566 exp_new_flash: Whether to use device_imager.
David Pursellf1d16a62015-03-25 13:31:04 -0700567
568 Raises:
569 FlashError: An unrecoverable error occured.
570 ValueError: Invalid parameter combination.
571 """
572 if force:
573 yes = True
574
575 if clear_cache:
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000576 ds_wrapper.DevServerWrapper.WipeStaticDirectory()
577 ds_wrapper.DevServerWrapper.CreateStaticDirectory()
David Pursellf1d16a62015-03-25 13:31:04 -0700578
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000579 # The user may not have specified a source image, use version as the default.
580 image = image or version
David Pursell2e773382015-04-03 14:30:47 -0700581 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
582 if device:
583 hostname, port = device.hostname, device.port
584 else:
585 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700586 logging.notice('Preparing to update the remote device %s', hostname)
Amin Hassani153f9162021-02-22 20:48:31 -0800587
588 if exp_new_flash:
589 with remote_access.ChromiumOSDeviceHandler(
590 hostname, port=port,
591 private_key=ssh_private_key, ping=ping) as device_p:
592 device_imager.DeviceImager(
593 device_p,
594 image,
595 no_rootfs_update=not rootfs_update,
596 no_stateful_update=not stateful_update,
597 no_reboot=not reboot,
598 disable_verification=disable_rootfs_verification,
599 clobber_stateful=clobber_stateful,
600 clear_tpm_owner=clear_tpm_owner).Run()
601 return
602
David Pursellf1d16a62015-03-25 13:31:04 -0700603 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700604 hostname,
605 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700606 image,
607 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700608 src_image_to_delta=src_image_to_delta,
609 rootfs_update=rootfs_update,
610 stateful_update=stateful_update,
611 clobber_stateful=clobber_stateful,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800612 clear_tpm_owner=clear_tpm_owner,
David Pursellf1d16a62015-03-25 13:31:04 -0700613 reboot=reboot,
614 wipe=wipe,
615 debug=debug,
616 yes=yes,
617 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600618 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700619 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800620 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800621 send_payload_in_parallel=send_payload_in_parallel,
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800622 version=version,
623 copy_payloads_to_device=copy_payloads_to_device)
David Pursellf1d16a62015-03-25 13:31:04 -0700624 updater.Run()
625 elif device.scheme == commandline.DEVICE_SCHEME_USB:
626 path = osutils.ExpandPath(device.path) if device.path else ''
627 logging.info('Preparing to image the removable device %s', path)
628 imager = USBImager(path,
629 board,
630 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000631 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700632 debug=debug,
David Pursellf1d16a62015-03-25 13:31:04 -0700633 yes=yes)
634 imager.Run()
635 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
636 logging.info('Preparing to copy image to %s', device.path)
637 imager = FileImager(device.path,
638 board,
639 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000640 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700641 debug=debug,
642 yes=yes)
643 imager.Run()