blob: 2a6071d72d7229955880b23a8a47ecb29965bf59 [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,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800366 clear_tpm_owner=False, version=None):
David Pursellf1d16a62015-03-25 13:31:04 -0700367 """Initializes RemoteDeviceUpdater"""
368 if not stateful_update and not rootfs_update:
369 raise ValueError('No update operation to perform; either stateful or'
370 ' rootfs partitions must be updated.')
371 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
372 self.ssh_hostname = ssh_hostname
373 self.ssh_port = ssh_port
374 self.image = image
375 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700376 self.src_image_to_delta = src_image_to_delta
377 self.do_stateful_update = stateful_update
378 self.do_rootfs_update = rootfs_update
379 self.disable_verification = disable_verification
380 self.clobber_stateful = clobber_stateful
Yi Chou5f4e51f2020-10-22 16:33:00 +0800381 self.clear_tpm_owner = clear_tpm_owner
David Pursellf1d16a62015-03-25 13:31:04 -0700382 self.reboot = reboot
383 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600384 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700385 self.ping = ping
386 # Do not wipe if debug is set.
387 self.wipe = wipe and not debug
388 self.yes = yes
389 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800390 self.send_payload_in_parallel = send_payload_in_parallel
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000391 self.version = version
David Pursellf1d16a62015-03-25 13:31:04 -0700392
David Pursellf1d16a62015-03-25 13:31:04 -0700393 def Cleanup(self):
394 """Cleans up the temporary directory."""
395 if self.wipe:
396 logging.info('Cleaning up temporary working directory...')
397 osutils.RmDir(self.tempdir)
398 else:
399 logging.info('You can find the log files and/or payloads in %s',
400 self.tempdir)
401
Amin Hassani9fe72b62020-10-29 15:43:31 -0700402 def GetPayloadPaths(self, device):
403 """Get directory of payload and rootfs payload file name for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700404
xixuane851dfb2016-05-02 18:02:37 -0700405 This method is used to obtain the directory of payload for cros-flash. The
406 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700407
xixuane851dfb2016-05-02 18:02:37 -0700408 If self.image is a directory, we directly use the provided update payload(s)
409 in this directory.
410
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800411 If self.image is an image, we will generate payloads for it and put them in
412 our temporary directory. The reason is that people may modify a local image
413 or override it (on the same path) with a different image, so in order to be
414 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700415
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800416 If non of the above cases, we use the xbuddy to first obtain the image path
417 (and possibly download it). Then we will generate the payloads in the same
418 directory the image is located. The reason is that this is what devserver
419 used to do. The path to the image generated by the devserver (or xbuddy) is
420 unique and normally nobody override its image with a different one. That is
421 why I think it is safe to put the payloads next to the image. This is a poor
422 man's version of caching but it makes cros flash faster for users who flash
423 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700424
425 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400426 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700427
428 Returns:
Amin Hassani9fe72b62020-10-29 15:43:31 -0700429 A string tuple (payload_dir, rootfs_filename). payload_dir is the
430 directory where the update payloads are located. rootfs_filename is the
431 name of the rootfs update payload (sometimes update.gz).
David Pursellf1d16a62015-03-25 13:31:04 -0700432 """
Amin Hassani9fe72b62020-10-29 15:43:31 -0700433 rootfs_filename = auto_updater_transfer.ROOTFS_FILENAME
434
xixuane851dfb2016-05-02 18:02:37 -0700435 if os.path.isdir(self.image):
436 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800437 logging.info('Using provided payloads in %s', self.image)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700438 return self.image, rootfs_filename
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600439
Amin Hassani9fe72b62020-10-29 15:43:31 -0700440 image_path = self.image
441 payload_dir = self.tempdir
442
443 if not os.path.isfile(self.image):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800444 # Assuming it is an xbuddy path.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000445 self.board = cros_build_lib.GetBoard(
446 device_board=device.board or GetDefaultBoard(),
447 override_board=self.board,
448 force=self.yes,
449 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700450 if not self.force and self.board != device.board:
451 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700452 raise FlashError('Device (%s) is incompatible with board %s' %
453 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700454 logging.info('Board is %s', self.board)
455
Amin Hassanic20a3c32019-06-02 21:43:21 -0700456 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
457 # use xbuddy library directly instead of the devserver_wrapper.
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200458 # Fetch the full payload and properties, and stateful files. If this
459 # fails, fallback to downloading the image.
460 try:
Amin Hassanie55168c2020-11-02 14:40:30 -0800461 _, local_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000462 os.path.join(self.image, artifact_info.FULL_PAYLOAD),
463 self.board, self.version, silent=True)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700464 payload_dir, rootfs_filename = os.path.split(local_path)
465
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200466 ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800467 os.path.join(self.image, artifact_info.STATEFUL_PAYLOAD),
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000468 self.board, self.version, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200469 fetch_image = False
470 except (ds_wrapper.ImagePathError, ds_wrapper.ArtifactDownloadError):
471 logging.info('Could not find full_payload or stateful for "%s"',
472 self.image)
473 fetch_image = True
xixuane851dfb2016-05-02 18:02:37 -0700474
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200475 # We didn't find the full_payload, attempt to download the image.
476 if fetch_image:
Amin Hassanie55168c2020-11-02 14:40:30 -0800477 _, image_path = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000478 self.image, self.board, self.version)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200479 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
480 logging.notice('Using image path %s and payload directory %s',
481 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700482
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800483 # Generate rootfs and stateful update payloads if they do not exist.
Amin Hassani9fe72b62020-10-29 15:43:31 -0700484 payload_path = os.path.join(payload_dir, rootfs_filename)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800485 if not os.path.exists(payload_path):
486 paygen_payload_lib.GenerateUpdatePayload(
487 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700488 if not os.path.exists(os.path.join(
489 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800490 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
491 payload_dir)
Amin Hassani9fe72b62020-10-29 15:43:31 -0700492 return payload_dir, rootfs_filename
David Pursellf1d16a62015-03-25 13:31:04 -0700493
494 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700495 """Perform remote device update.
496
497 The update process includes:
498 1. initialize a device instance for the given remote device.
499 2. achieve payload_dir which contains the required payloads for updating.
500 3. initialize an auto-updater instance to do RunUpdate().
501 4. After auto-update, all temp files and dir will be cleaned up.
502 """
David Pursellf1d16a62015-03-25 13:31:04 -0700503 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700504 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600505 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
506 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700507
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600508 try:
509 # Get payload directory
Zentaro Kavanagh8f79c2a2020-05-28 14:12:07 -0700510 logging.notice('Staging payloads...')
Amin Hassani9fe72b62020-10-29 15:43:31 -0700511 payload_dir, rootfs_filename = self.GetPayloadPaths(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700512
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600513 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700514 chromeos_AU = auto_updater.ChromiumOSUpdater(
515 device=device,
516 build_name=None,
517 payload_dir=payload_dir,
Amin Hassani9fe72b62020-10-29 15:43:31 -0700518 payload_filename=rootfs_filename,
Amin Hassani9800d432019-07-24 14:23:39 -0700519 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600520 do_rootfs_update=self.do_rootfs_update,
521 do_stateful_update=self.do_stateful_update,
522 reboot=self.reboot,
523 disable_verification=self.disable_verification,
524 clobber_stateful=self.clobber_stateful,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800525 clear_tpm_owner=self.clear_tpm_owner,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800526 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800527 send_payload_in_parallel=self.send_payload_in_parallel,
Amin Hassani3e87ce12020-10-22 10:39:36 -0700528 resolve_app_id_mismatch=True,
Sanika Kulkarni3cb57912020-03-18 15:17:46 -0700529 transfer_class=auto_updater_transfer.LocalTransfer)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600530 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700531
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600532 except Exception:
533 logging.error('Device update failed.')
534 lsb_entries = sorted(device.lsb_release.items())
535 logging.info(
536 'Following are the LSB version details of the device:\n%s',
537 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
538 raise
539
540 logging.notice('Update performed successfully.')
541
542 except remote_access.RemoteAccessException:
543 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700544 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600545
David Pursellf1d16a62015-03-25 13:31:04 -0700546 finally:
547 self.Cleanup()
548
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200549
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700550def Flash(device, image, board=None, install=False, src_image_to_delta=None,
551 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600552 reboot=True, wipe=True, ssh_private_key=None, ping=True,
553 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800554 force=False, debug=False, send_payload_in_parallel=False,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800555 clear_tpm_owner=False, version=None):
David Pursellf1d16a62015-03-25 13:31:04 -0700556 """Flashes a device, USB drive, or file with an image.
557
558 This provides functionality common to `cros flash` and `brillo flash`
559 so that they can parse the commandline separately but still use the
560 same underlying functionality.
561
562 Args:
David Pursell2e773382015-04-03 14:30:47 -0700563 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700564 image: Path (string) to the update image. Can be a local or xbuddy path;
565 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700566 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700567 install: Install to USB using base disk layout; USB |device| scheme only.
568 src_image_to_delta: Local path to an image to be used as the base to
569 generate delta payloads; SSH |device| scheme only.
570 rootfs_update: Update rootfs partition; SSH |device| scheme only.
571 stateful_update: Update stateful partition; SSH |device| scheme only.
572 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
Yi Chou5f4e51f2020-10-22 16:33:00 +0800573 clear_tpm_owner: Clear the TPM owner on reboot; SSH |device| scheme only.
David Pursellf1d16a62015-03-25 13:31:04 -0700574 reboot: Reboot device after update; SSH |device| scheme only.
575 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600576 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700577 ping: Ping the device before attempting update; SSH |device| scheme only.
578 disable_rootfs_verification: Remove rootfs verification after update; SSH
579 |device| scheme only.
580 clear_cache: Clear the devserver static directory.
581 yes: Assume "yes" for any prompt.
582 force: Ignore sanity checks and prompts. Overrides |yes| if True.
583 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800584 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
585 up transmissions for long haul between endpoints.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000586 version: Default version.
David Pursellf1d16a62015-03-25 13:31:04 -0700587
588 Raises:
589 FlashError: An unrecoverable error occured.
590 ValueError: Invalid parameter combination.
591 """
592 if force:
593 yes = True
594
595 if clear_cache:
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000596 ds_wrapper.DevServerWrapper.WipeStaticDirectory()
597 ds_wrapper.DevServerWrapper.CreateStaticDirectory()
David Pursellf1d16a62015-03-25 13:31:04 -0700598
599 if install:
David Pursell2e773382015-04-03 14:30:47 -0700600 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700601 raise ValueError(
602 '--install can only be used when writing to a USB device')
603 if not cros_build_lib.IsInsideChroot():
604 raise ValueError('--install can only be used inside the chroot')
605
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000606 # The user may not have specified a source image, use version as the default.
607 image = image or version
David Pursell2e773382015-04-03 14:30:47 -0700608 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
609 if device:
610 hostname, port = device.hostname, device.port
611 else:
612 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700613 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700614 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700615 hostname,
616 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700617 image,
618 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700619 src_image_to_delta=src_image_to_delta,
620 rootfs_update=rootfs_update,
621 stateful_update=stateful_update,
622 clobber_stateful=clobber_stateful,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800623 clear_tpm_owner=clear_tpm_owner,
David Pursellf1d16a62015-03-25 13:31:04 -0700624 reboot=reboot,
625 wipe=wipe,
626 debug=debug,
627 yes=yes,
628 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600629 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700630 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800631 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800632 send_payload_in_parallel=send_payload_in_parallel,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000633 version=version)
David Pursellf1d16a62015-03-25 13:31:04 -0700634 updater.Run()
635 elif device.scheme == commandline.DEVICE_SCHEME_USB:
636 path = osutils.ExpandPath(device.path) if device.path else ''
637 logging.info('Preparing to image the removable device %s', path)
638 imager = USBImager(path,
639 board,
640 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000641 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700642 debug=debug,
643 install=install,
644 yes=yes)
645 imager.Run()
646 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
647 logging.info('Preparing to copy image to %s', device.path)
648 imager = FileImager(device.path,
649 board,
650 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000651 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700652 debug=debug,
653 yes=yes)
654 imager.Run()