blob: cb1b2528c1eb25e4a22984cbd897150d1e1b27b8 [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
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000017from chromite.cli.cros import cros_chrome_sdk
18
xixuane851dfb2016-05-02 18:02:37 -070019from chromite.lib import auto_updater
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -070020from chromite.lib import auto_updater_transfer
David Pursellf1d16a62015-03-25 13:31:04 -070021from chromite.lib import commandline
22from chromite.lib import cros_build_lib
23from chromite.lib import cros_logging as logging
24from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070025from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070026from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070027from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070028from chromite.lib import remote_access
29
Amin Hassanic0f06fa2019-01-28 15:24:47 -080030from chromite.lib.paygen import paygen_payload_lib
31from chromite.lib.paygen import paygen_stateful_payload_lib
32
Amin Hassani1c25d4b2019-11-22 10:59:07 -080033from chromite.lib.xbuddy import artifact_info
34
David Pursellf1d16a62015-03-25 13:31:04 -070035
Mike Frysinger3f087aa2020-03-20 06:03:16 -040036assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
37
38
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000039def GetDefaultBoard():
40 """Look up default board.
41
42 In a chrome checkout, return $SDK_BOARD. In a chromeos checkout,
43 return the contents of .default_board.
44 """
45 if path_util.DetermineCheckout().type == path_util.CHECKOUT_TYPE_GCLIENT:
46 return os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
47 return cros_build_lib.GetDefaultBoard()
48
49
Ralph Nathan9b997232015-05-15 13:13:12 -070050class UsbImagerOperation(operation.ProgressBarOperation):
51 """Progress bar for flashing image to operation."""
52
53 def __init__(self, image):
54 super(UsbImagerOperation, self).__init__()
55 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070056 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070057 self._bytes = re.compile(r'(\d+) bytes')
58
59 def _GetDDPid(self):
60 """Get the Pid of dd."""
61 try:
Mike Frysinger45602c72019-09-22 02:15:11 -040062 pids = cros_build_lib.run(['pgrep', 'dd'], capture_output=True,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040063 print_cmd=False, encoding='utf-8').stdout
Ralph Nathan9b997232015-05-15 13:13:12 -070064 for pid in pids.splitlines():
65 if osutils.IsChildProcess(int(pid), name='dd'):
66 return int(pid)
67 return -1
68 except cros_build_lib.RunCommandError:
69 # If dd isn't still running, then we assume that it is finished.
70 return -1
71
72 def _PingDD(self, dd_pid):
73 """Send USR1 signal to dd to get status update."""
74 try:
75 cmd = ['kill', '-USR1', str(dd_pid)]
Mike Frysinger45602c72019-09-22 02:15:11 -040076 cros_build_lib.sudo_run(cmd, print_cmd=False)
Ralph Nathan9b997232015-05-15 13:13:12 -070077 except cros_build_lib.RunCommandError:
78 # Here we assume that dd finished in the background.
79 return
80
81 def ParseOutput(self, output=None):
82 """Parse the output of dd to update progress bar."""
83 dd_pid = self._GetDDPid()
84 if dd_pid == -1:
85 return
86
87 self._PingDD(dd_pid)
88
89 if output is None:
90 stdout = self._stdout.read()
91 stderr = self._stderr.read()
92 output = stdout + stderr
93
94 match = self._bytes.search(output)
95 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070096 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -070097
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040098 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -070099
100
Mike Frysinger32759e42016-12-21 18:40:16 -0500101def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700102 """Determines if a file is a valid GPT disk.
103
104 Args:
105 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -0500106 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700107 """
108 if os.path.isfile(file_path):
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400109 with open(file_path, 'rb') as image_file:
Mike Frysinger32759e42016-12-21 18:40:16 -0500110 if require_pmbr:
111 # Seek to the end of LBA0 and look for the PMBR boot signature.
112 image_file.seek(0x1fe)
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400113 if image_file.read(2) != b'\x55\xaa':
Mike Frysinger32759e42016-12-21 18:40:16 -0500114 return False
115 # Current file position is start of LBA1 now.
116 else:
117 # Seek to LBA1 where the GPT starts.
118 image_file.seek(0x200)
119
120 # See if there's a GPT here.
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400121 if image_file.read(8) == b'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700122 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500123
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700124 return False
125
126
127def _ChooseImageFromDirectory(dir_path):
128 """Lists all image files in |dir_path| and ask user to select one.
129
130 Args:
131 dir_path: Path to the directory.
132 """
133 images = sorted([x for x in os.listdir(dir_path) if
134 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
135 idx = 0
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400136 if not images:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700137 raise ValueError('No image found in %s.' % dir_path)
138 elif len(images) > 1:
139 idx = cros_build_lib.GetChoice(
140 'Multiple images found in %s. Please select one to continue:' % (
141 (dir_path,)),
142 images)
143
144 return os.path.join(dir_path, images[idx])
145
146
David Pursellf1d16a62015-03-25 13:31:04 -0700147class FlashError(Exception):
148 """Thrown when there is an unrecoverable error during flash."""
149
150
151class USBImager(object):
152 """Copy image to the target removable device."""
153
Amin Hassani04314b12020-12-15 15:59:54 -0800154 def __init__(self, device, board, image, version, debug=False, yes=False):
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000155 """Initializes USBImager."""
David Pursellf1d16a62015-03-25 13:31:04 -0700156 self.device = device
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000157 self.board = board if board else GetDefaultBoard()
David Pursellf1d16a62015-03-25 13:31:04 -0700158 self.image = image
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000159 self.version = version
David Pursellf1d16a62015-03-25 13:31:04 -0700160 self.debug = debug
161 self.debug_level = logging.DEBUG if debug else logging.INFO
David Pursellf1d16a62015-03-25 13:31:04 -0700162 self.yes = yes
163
164 def DeviceNameToPath(self, device_name):
165 return '/dev/%s' % device_name
166
167 def GetRemovableDeviceDescription(self, device):
168 """Returns a informational description of the removable |device|.
169
170 Args:
171 device: the device name (e.g. sdc).
172
173 Returns:
174 A string describing |device| (e.g. Patriot Memory 7918 MB).
175 """
176 desc = [
177 osutils.GetDeviceInfo(device, keyword='manufacturer'),
178 osutils.GetDeviceInfo(device, keyword='product'),
179 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
180 '(%s)' % self.DeviceNameToPath(device),
181 ]
182 return ' '.join([x for x in desc if x])
183
184 def ListAllRemovableDevices(self):
185 """Returns a list of removable devices.
186
187 Returns:
188 A list of device names (e.g. ['sdb', 'sdc']).
189 """
190 devices = osutils.ListBlockDevices()
191 removable_devices = []
192 for d in devices:
193 if d.TYPE == 'disk' and d.RM == '1':
194 removable_devices.append(d.NAME)
195
196 return removable_devices
197
198 def ChooseRemovableDevice(self, devices):
199 """Lists all removable devices and asks user to select/confirm.
200
201 Args:
202 devices: a list of device names (e.g. ['sda', 'sdb']).
203
204 Returns:
205 The device name chosen by the user.
206 """
207 idx = cros_build_lib.GetChoice(
208 'Removable device(s) found. Please select/confirm to continue:',
209 [self.GetRemovableDeviceDescription(x) for x in devices])
210
211 return devices[idx]
212
David Pursellf1d16a62015-03-25 13:31:04 -0700213 def CopyImageToDevice(self, image, device):
214 """Copies |image| to the removable |device|.
215
216 Args:
217 image: Path to the image to copy.
218 device: Device to copy to.
219 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700220 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800221 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700222 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
223 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400224 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400225 encoding='utf-8', update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700226 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400227 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700228 cmd, debug_level=logging.NOTICE,
229 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700230
Brian Norris6386fde2018-10-29 13:34:28 -0700231 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
232 # up for us with a 'write' command, so we have a standards-conforming GPT.
233 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
234 # fix GPT sanity issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400235 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500236 check=False,
Mike Frysinger45602c72019-09-22 02:15:11 -0400237 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700238
Mike Frysinger45602c72019-09-22 02:15:11 -0400239 cros_build_lib.sudo_run(['partx', '-u', device],
240 debug_level=self.debug_level)
241 cros_build_lib.sudo_run(['sync', '-d', device],
242 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700243
David Pursellf1d16a62015-03-25 13:31:04 -0700244 def _GetImagePath(self):
245 """Returns the image path to use."""
Amin Hassanie55168c2020-11-02 14:40:30 -0800246 image_path = None
David Pursellf1d16a62015-03-25 13:31:04 -0700247 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700248 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700249 # TODO(wnwen): Open the tarball and if there is just one file in it,
250 # use that instead. Existing code in upload_symbols.py.
251 if cros_build_lib.BooleanPrompt(
252 prolog='The given image file is not a valid disk image. Perhaps '
253 'you forgot to untar it.',
254 prompt='Terminate the current flash process?'):
255 raise FlashError('Update terminated by user.')
256 image_path = self.image
257 elif os.path.isdir(self.image):
258 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700259 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700260 else:
261 # Translate the xbuddy path to get the exact image to use.
Amin Hassanie55168c2020-11-02 14:40:30 -0800262 _, image_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000263 self.image, self.board, self.version)
David Pursellf1d16a62015-03-25 13:31:04 -0700264
Amin Hassanie55168c2020-11-02 14:40:30 -0800265 logging.info('Using image %s', image_path)
David Pursellf1d16a62015-03-25 13:31:04 -0700266 return image_path
267
268 def Run(self):
269 """Image the removable device."""
270 devices = self.ListAllRemovableDevices()
271
272 if self.device:
273 # If user specified a device path, check if it exists.
274 if not os.path.exists(self.device):
275 raise FlashError('Device path %s does not exist.' % self.device)
276
277 # Then check if it is removable.
278 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
279 msg = '%s is not a removable device.' % self.device
280 if not (self.yes or cros_build_lib.BooleanPrompt(
281 default=False, prolog=msg)):
282 raise FlashError('You can specify usb:// to choose from a list of '
283 'removable devices.')
284 target = None
285 if self.device:
286 # Get device name from path (e.g. sdc in /dev/sdc).
287 target = self.device.rsplit(os.path.sep, 1)[-1]
288 elif devices:
289 # Ask user to choose from the list.
290 target = self.ChooseRemovableDevice(devices)
291 else:
292 raise FlashError('No removable devices detected.')
293
294 image_path = self._GetImagePath()
295 try:
296 device = self.DeviceNameToPath(target)
Amin Hassani04314b12020-12-15 15:59:54 -0800297 self.CopyImageToDevice(image_path, device)
David Pursellf1d16a62015-03-25 13:31:04 -0700298 except cros_build_lib.RunCommandError:
299 logging.error('Failed copying image to device %s',
300 self.DeviceNameToPath(target))
301
302
303class FileImager(USBImager):
304 """Copy image to the target path."""
305
306 def Run(self):
307 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800308 if not os.path.isdir(os.path.dirname(self.device)):
309 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700310
311 image_path = self._GetImagePath()
312 if os.path.isdir(self.device):
313 logging.info('Copying to %s',
314 os.path.join(self.device, os.path.basename(image_path)))
315 else:
316 logging.info('Copying to %s', self.device)
317 try:
318 shutil.copy(image_path, self.device)
319 except IOError:
320 logging.error('Failed to copy image %s to %s', image_path, self.device)
321
322
323class RemoteDeviceUpdater(object):
324 """Performs update on a remote device."""
David Pursellf1d16a62015-03-25 13:31:04 -0700325 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
326 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700327 # Root working directory on the device. This directory is in the
328 # stateful partition and thus has enough space to store the payloads.
Amin Hassanidc4e7f02020-02-03 11:10:22 -0800329 DEVICE_BASE_DIR = '/usr/local/tmp/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700330 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
331 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700332
333 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
334 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700335 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600336 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800337 disable_verification=False, send_payload_in_parallel=False,
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800338 clear_tpm_owner=False, version=None,
339 copy_payloads_to_device=True):
David Pursellf1d16a62015-03-25 13:31:04 -0700340 """Initializes RemoteDeviceUpdater"""
341 if not stateful_update and not rootfs_update:
342 raise ValueError('No update operation to perform; either stateful or'
343 ' rootfs partitions must be updated.')
344 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
345 self.ssh_hostname = ssh_hostname
346 self.ssh_port = ssh_port
347 self.image = image
348 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700349 self.src_image_to_delta = src_image_to_delta
350 self.do_stateful_update = stateful_update
351 self.do_rootfs_update = rootfs_update
352 self.disable_verification = disable_verification
353 self.clobber_stateful = clobber_stateful
Yi Chou5f4e51f2020-10-22 16:33:00 +0800354 self.clear_tpm_owner = clear_tpm_owner
David Pursellf1d16a62015-03-25 13:31:04 -0700355 self.reboot = reboot
356 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600357 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700358 self.ping = ping
359 # Do not wipe if debug is set.
360 self.wipe = wipe and not debug
361 self.yes = yes
362 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800363 self.send_payload_in_parallel = send_payload_in_parallel
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000364 self.version = version
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800365 self.copy_payloads_to_device = copy_payloads_to_device
David Pursellf1d16a62015-03-25 13:31:04 -0700366
David Pursellf1d16a62015-03-25 13:31:04 -0700367 def Cleanup(self):
368 """Cleans up the temporary directory."""
369 if self.wipe:
370 logging.info('Cleaning up temporary working directory...')
371 osutils.RmDir(self.tempdir)
372 else:
373 logging.info('You can find the log files and/or payloads in %s',
374 self.tempdir)
375
Amin Hassani9fe72b62020-10-29 15:43:31 -0700376 def GetPayloadPaths(self, device):
377 """Get directory of payload and rootfs payload file name for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700378
xixuane851dfb2016-05-02 18:02:37 -0700379 This method is used to obtain the directory of payload for cros-flash. The
380 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700381
xixuane851dfb2016-05-02 18:02:37 -0700382 If self.image is a directory, we directly use the provided update payload(s)
383 in this directory.
384
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800385 If self.image is an image, we will generate payloads for it and put them in
386 our temporary directory. The reason is that people may modify a local image
387 or override it (on the same path) with a different image, so in order to be
388 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700389
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800390 If non of the above cases, we use the xbuddy to first obtain the image path
391 (and possibly download it). Then we will generate the payloads in the same
392 directory the image is located. The reason is that this is what devserver
393 used to do. The path to the image generated by the devserver (or xbuddy) is
394 unique and normally nobody override its image with a different one. That is
395 why I think it is safe to put the payloads next to the image. This is a poor
396 man's version of caching but it makes cros flash faster for users who flash
397 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700398
399 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400400 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700401
402 Returns:
Amin Hassani9fe72b62020-10-29 15:43:31 -0700403 A string tuple (payload_dir, rootfs_filename). payload_dir is the
404 directory where the update payloads are located. rootfs_filename is the
405 name of the rootfs update payload (sometimes update.gz).
David Pursellf1d16a62015-03-25 13:31:04 -0700406 """
Amin Hassani9fe72b62020-10-29 15:43:31 -0700407 rootfs_filename = auto_updater_transfer.ROOTFS_FILENAME
408
xixuane851dfb2016-05-02 18:02:37 -0700409 if os.path.isdir(self.image):
410 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800411 logging.info('Using provided payloads in %s', self.image)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700412 return self.image, rootfs_filename
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600413
Amin Hassani9fe72b62020-10-29 15:43:31 -0700414 image_path = self.image
415 payload_dir = self.tempdir
416
417 if not os.path.isfile(self.image):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800418 # Assuming it is an xbuddy path.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000419 self.board = cros_build_lib.GetBoard(
420 device_board=device.board or GetDefaultBoard(),
421 override_board=self.board,
422 force=self.yes,
423 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700424 if not self.force and self.board != device.board:
425 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700426 raise FlashError('Device (%s) is incompatible with board %s' %
427 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700428 logging.info('Board is %s', self.board)
429
Amin Hassanic20a3c32019-06-02 21:43:21 -0700430 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
431 # use xbuddy library directly instead of the devserver_wrapper.
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200432 # Fetch the full payload and properties, and stateful files. If this
433 # fails, fallback to downloading the image.
434 try:
Amin Hassanie55168c2020-11-02 14:40:30 -0800435 _, local_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000436 os.path.join(self.image, artifact_info.FULL_PAYLOAD),
437 self.board, self.version, silent=True)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700438 payload_dir, rootfs_filename = os.path.split(local_path)
439
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200440 ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800441 os.path.join(self.image, artifact_info.STATEFUL_PAYLOAD),
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000442 self.board, self.version, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200443 fetch_image = False
444 except (ds_wrapper.ImagePathError, ds_wrapper.ArtifactDownloadError):
445 logging.info('Could not find full_payload or stateful for "%s"',
446 self.image)
447 fetch_image = True
xixuane851dfb2016-05-02 18:02:37 -0700448
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200449 # We didn't find the full_payload, attempt to download the image.
450 if fetch_image:
Amin Hassanie55168c2020-11-02 14:40:30 -0800451 _, image_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000452 self.image, self.board, self.version)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200453 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
454 logging.notice('Using image path %s and payload directory %s',
455 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700456
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800457 # Generate rootfs and stateful update payloads if they do not exist.
Amin Hassani9fe72b62020-10-29 15:43:31 -0700458 payload_path = os.path.join(payload_dir, rootfs_filename)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800459 if not os.path.exists(payload_path):
460 paygen_payload_lib.GenerateUpdatePayload(
461 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700462 if not os.path.exists(os.path.join(
463 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800464 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
465 payload_dir)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700466 return payload_dir, rootfs_filename
David Pursellf1d16a62015-03-25 13:31:04 -0700467
468 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700469 """Perform remote device update.
470
471 The update process includes:
472 1. initialize a device instance for the given remote device.
473 2. achieve payload_dir which contains the required payloads for updating.
474 3. initialize an auto-updater instance to do RunUpdate().
475 4. After auto-update, all temp files and dir will be cleaned up.
476 """
David Pursellf1d16a62015-03-25 13:31:04 -0700477 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700478 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600479 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
480 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700481
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600482 try:
483 # Get payload directory
Zentaro Kavanagh8f79c2a2020-05-28 14:12:07 -0700484 logging.notice('Staging payloads...')
Amin Hassani9fe72b62020-10-29 15:43:31 -0700485 payload_dir, rootfs_filename = self.GetPayloadPaths(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700486
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600487 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700488 chromeos_AU = auto_updater.ChromiumOSUpdater(
489 device=device,
490 build_name=None,
491 payload_dir=payload_dir,
Amin Hassani9fe72b62020-10-29 15:43:31 -0700492 payload_filename=rootfs_filename,
Amin Hassani9800d432019-07-24 14:23:39 -0700493 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600494 do_rootfs_update=self.do_rootfs_update,
495 do_stateful_update=self.do_stateful_update,
496 reboot=self.reboot,
497 disable_verification=self.disable_verification,
498 clobber_stateful=self.clobber_stateful,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800499 clear_tpm_owner=self.clear_tpm_owner,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800500 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800501 send_payload_in_parallel=self.send_payload_in_parallel,
Amin Hassani3e87ce12020-10-22 10:39:36 -0700502 resolve_app_id_mismatch=True,
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800503 transfer_class=auto_updater_transfer.LocalTransfer,
504 copy_payloads_to_device=self.copy_payloads_to_device)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600505 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700506
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600507 except Exception:
508 logging.error('Device update failed.')
509 lsb_entries = sorted(device.lsb_release.items())
510 logging.info(
511 'Following are the LSB version details of the device:\n%s',
512 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
513 raise
514
515 logging.notice('Update performed successfully.')
516
517 except remote_access.RemoteAccessException:
518 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700519 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600520
David Pursellf1d16a62015-03-25 13:31:04 -0700521 finally:
522 self.Cleanup()
523
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200524
Amin Hassani04314b12020-12-15 15:59:54 -0800525def Flash(device, image, board=None, src_image_to_delta=None,
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700526 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600527 reboot=True, wipe=True, ssh_private_key=None, ping=True,
528 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800529 force=False, debug=False, send_payload_in_parallel=False,
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800530 clear_tpm_owner=False, version=None, copy_payloads_to_device=True):
David Pursellf1d16a62015-03-25 13:31:04 -0700531 """Flashes a device, USB drive, or file with an image.
532
533 This provides functionality common to `cros flash` and `brillo flash`
534 so that they can parse the commandline separately but still use the
535 same underlying functionality.
536
537 Args:
David Pursell2e773382015-04-03 14:30:47 -0700538 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700539 image: Path (string) to the update image. Can be a local or xbuddy path;
540 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700541 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700542 src_image_to_delta: Local path to an image to be used as the base to
543 generate delta payloads; SSH |device| scheme only.
544 rootfs_update: Update rootfs partition; SSH |device| scheme only.
545 stateful_update: Update stateful partition; SSH |device| scheme only.
546 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
Yi Chou5f4e51f2020-10-22 16:33:00 +0800547 clear_tpm_owner: Clear the TPM owner on reboot; SSH |device| scheme only.
David Pursellf1d16a62015-03-25 13:31:04 -0700548 reboot: Reboot device after update; SSH |device| scheme only.
549 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600550 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700551 ping: Ping the device before attempting update; SSH |device| scheme only.
552 disable_rootfs_verification: Remove rootfs verification after update; SSH
553 |device| scheme only.
554 clear_cache: Clear the devserver static directory.
555 yes: Assume "yes" for any prompt.
556 force: Ignore sanity checks and prompts. Overrides |yes| if True.
557 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800558 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
559 up transmissions for long haul between endpoints.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000560 version: Default version.
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800561 copy_payloads_to_device: If True, update payloads are copied to the
562 Chromium OS device first. Otherwise, they are piped through SSH.
563 Currently, this only applies to the stateful payloads.
David Pursellf1d16a62015-03-25 13:31:04 -0700564
565 Raises:
566 FlashError: An unrecoverable error occured.
567 ValueError: Invalid parameter combination.
568 """
569 if force:
570 yes = True
571
572 if clear_cache:
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000573 ds_wrapper.DevServerWrapper.WipeStaticDirectory()
574 ds_wrapper.DevServerWrapper.CreateStaticDirectory()
David Pursellf1d16a62015-03-25 13:31:04 -0700575
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000576 # The user may not have specified a source image, use version as the default.
577 image = image or version
David Pursell2e773382015-04-03 14:30:47 -0700578 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
579 if device:
580 hostname, port = device.hostname, device.port
581 else:
582 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700583 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700584 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700585 hostname,
586 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700587 image,
588 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700589 src_image_to_delta=src_image_to_delta,
590 rootfs_update=rootfs_update,
591 stateful_update=stateful_update,
592 clobber_stateful=clobber_stateful,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800593 clear_tpm_owner=clear_tpm_owner,
David Pursellf1d16a62015-03-25 13:31:04 -0700594 reboot=reboot,
595 wipe=wipe,
596 debug=debug,
597 yes=yes,
598 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600599 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700600 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800601 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800602 send_payload_in_parallel=send_payload_in_parallel,
Amin Hassani9ed30bc2020-11-23 14:31:49 -0800603 version=version,
604 copy_payloads_to_device=copy_payloads_to_device)
David Pursellf1d16a62015-03-25 13:31:04 -0700605 updater.Run()
606 elif device.scheme == commandline.DEVICE_SCHEME_USB:
607 path = osutils.ExpandPath(device.path) if device.path else ''
608 logging.info('Preparing to image the removable device %s', path)
609 imager = USBImager(path,
610 board,
611 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000612 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700613 debug=debug,
David Pursellf1d16a62015-03-25 13:31:04 -0700614 yes=yes)
615 imager.Run()
616 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
617 logging.info('Preparing to copy image to %s', device.path)
618 imager = FileImager(device.path,
619 board,
620 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000621 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700622 debug=debug,
623 yes=yes)
624 imager.Run()