blob: 202851643f475802185ada8fb0284c6936d3f2e8 [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."""
271 image_path = translated_path = None
272 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.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700287 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000288 self.image, self.board, self.version)
289 image_path = ds_wrapper.TranslatedPathToLocalPath(translated_path)
David Pursellf1d16a62015-03-25 13:31:04 -0700290
291 logging.info('Using image %s', translated_path or image_path)
292 return image_path
293
294 def Run(self):
295 """Image the removable device."""
296 devices = self.ListAllRemovableDevices()
297
298 if self.device:
299 # If user specified a device path, check if it exists.
300 if not os.path.exists(self.device):
301 raise FlashError('Device path %s does not exist.' % self.device)
302
303 # Then check if it is removable.
304 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
305 msg = '%s is not a removable device.' % self.device
306 if not (self.yes or cros_build_lib.BooleanPrompt(
307 default=False, prolog=msg)):
308 raise FlashError('You can specify usb:// to choose from a list of '
309 'removable devices.')
310 target = None
311 if self.device:
312 # Get device name from path (e.g. sdc in /dev/sdc).
313 target = self.device.rsplit(os.path.sep, 1)[-1]
314 elif devices:
315 # Ask user to choose from the list.
316 target = self.ChooseRemovableDevice(devices)
317 else:
318 raise FlashError('No removable devices detected.')
319
320 image_path = self._GetImagePath()
321 try:
322 device = self.DeviceNameToPath(target)
323 if self.install:
324 self.InstallImageToDevice(image_path, device)
325 else:
326 self.CopyImageToDevice(image_path, device)
327 except cros_build_lib.RunCommandError:
328 logging.error('Failed copying image to device %s',
329 self.DeviceNameToPath(target))
330
331
332class FileImager(USBImager):
333 """Copy image to the target path."""
334
335 def Run(self):
336 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800337 if not os.path.isdir(os.path.dirname(self.device)):
338 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700339
340 image_path = self._GetImagePath()
341 if os.path.isdir(self.device):
342 logging.info('Copying to %s',
343 os.path.join(self.device, os.path.basename(image_path)))
344 else:
345 logging.info('Copying to %s', self.device)
346 try:
347 shutil.copy(image_path, self.device)
348 except IOError:
349 logging.error('Failed to copy image %s to %s', image_path, self.device)
350
351
352class RemoteDeviceUpdater(object):
353 """Performs update on a remote device."""
David Pursellf1d16a62015-03-25 13:31:04 -0700354 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
355 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700356 # Root working directory on the device. This directory is in the
357 # stateful partition and thus has enough space to store the payloads.
Amin Hassanidc4e7f02020-02-03 11:10:22 -0800358 DEVICE_BASE_DIR = '/usr/local/tmp/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700359 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
360 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700361
362 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
363 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700364 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600365 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800366 disable_verification=False, send_payload_in_parallel=False,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800367 clear_tpm_owner=False, version=None):
David Pursellf1d16a62015-03-25 13:31:04 -0700368 """Initializes RemoteDeviceUpdater"""
369 if not stateful_update and not rootfs_update:
370 raise ValueError('No update operation to perform; either stateful or'
371 ' rootfs partitions must be updated.')
372 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
373 self.ssh_hostname = ssh_hostname
374 self.ssh_port = ssh_port
375 self.image = image
376 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700377 self.src_image_to_delta = src_image_to_delta
378 self.do_stateful_update = stateful_update
379 self.do_rootfs_update = rootfs_update
380 self.disable_verification = disable_verification
381 self.clobber_stateful = clobber_stateful
Yi Chou5f4e51f2020-10-22 16:33:00 +0800382 self.clear_tpm_owner = clear_tpm_owner
David Pursellf1d16a62015-03-25 13:31:04 -0700383 self.reboot = reboot
384 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600385 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700386 self.ping = ping
387 # Do not wipe if debug is set.
388 self.wipe = wipe and not debug
389 self.yes = yes
390 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800391 self.send_payload_in_parallel = send_payload_in_parallel
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000392 self.version = version
David Pursellf1d16a62015-03-25 13:31:04 -0700393
David Pursellf1d16a62015-03-25 13:31:04 -0700394 def Cleanup(self):
395 """Cleans up the temporary directory."""
396 if self.wipe:
397 logging.info('Cleaning up temporary working directory...')
398 osutils.RmDir(self.tempdir)
399 else:
400 logging.info('You can find the log files and/or payloads in %s',
401 self.tempdir)
402
Amin Hassani15b2c072020-10-20 11:23:51 -0700403 def GetPayloadPaths(self, device):
404 """Get directory of payload and rootfs payload file name for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700405
xixuane851dfb2016-05-02 18:02:37 -0700406 This method is used to obtain the directory of payload for cros-flash. The
407 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700408
xixuane851dfb2016-05-02 18:02:37 -0700409 If self.image is a directory, we directly use the provided update payload(s)
410 in this directory.
411
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800412 If self.image is an image, we will generate payloads for it and put them in
413 our temporary directory. The reason is that people may modify a local image
414 or override it (on the same path) with a different image, so in order to be
415 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700416
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800417 If non of the above cases, we use the xbuddy to first obtain the image path
418 (and possibly download it). Then we will generate the payloads in the same
419 directory the image is located. The reason is that this is what devserver
420 used to do. The path to the image generated by the devserver (or xbuddy) is
421 unique and normally nobody override its image with a different one. That is
422 why I think it is safe to put the payloads next to the image. This is a poor
423 man's version of caching but it makes cros flash faster for users who flash
424 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700425
426 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400427 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700428
429 Returns:
Amin Hassani15b2c072020-10-20 11:23:51 -0700430 A string tuple (payload_dir, rootfs_filename). payload_dir is the
431 directory where the update payloads are located. rootfs_filename is the
432 name of the rootfs update payload (sometimes update.gz).
David Pursellf1d16a62015-03-25 13:31:04 -0700433 """
Amin Hassani15b2c072020-10-20 11:23:51 -0700434 rootfs_filename = auto_updater_transfer.ROOTFS_FILENAME
435
xixuane851dfb2016-05-02 18:02:37 -0700436 if os.path.isdir(self.image):
437 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800438 logging.info('Using provided payloads in %s', self.image)
Amin Hassani15b2c072020-10-20 11:23:51 -0700439 return self.image, rootfs_filename
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600440
Amin Hassani15b2c072020-10-20 11:23:51 -0700441 image_path = self.image
442 payload_dir = self.tempdir
443
444 if not os.path.isfile(self.image):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800445 # Assuming it is an xbuddy path.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000446 self.board = cros_build_lib.GetBoard(
447 device_board=device.board or GetDefaultBoard(),
448 override_board=self.board,
449 force=self.yes,
450 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700451 if not self.force and self.board != device.board:
452 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700453 raise FlashError('Device (%s) is incompatible with board %s' %
454 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700455 logging.info('Board is %s', self.board)
456
Amin Hassanic20a3c32019-06-02 21:43:21 -0700457
458 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
459 # use xbuddy library directly instead of the devserver_wrapper.
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200460 # Fetch the full payload and properties, and stateful files. If this
461 # fails, fallback to downloading the image.
462 try:
463 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000464 os.path.join(self.image, artifact_info.FULL_PAYLOAD),
465 self.board, self.version, silent=True)
Amin Hassani15b2c072020-10-20 11:23:51 -0700466 local_path = ds_wrapper.TranslatedPathToLocalPath(translated_path)
467 payload_dir, rootfs_filename = os.path.split(local_path)
468
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200469 ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800470 os.path.join(self.image, artifact_info.STATEFUL_PAYLOAD),
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000471 self.board, self.version, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200472 fetch_image = False
473 except (ds_wrapper.ImagePathError, ds_wrapper.ArtifactDownloadError):
474 logging.info('Could not find full_payload or stateful for "%s"',
475 self.image)
476 fetch_image = True
xixuane851dfb2016-05-02 18:02:37 -0700477
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200478 # We didn't find the full_payload, attempt to download the image.
479 if fetch_image:
480 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000481 self.image, self.board, self.version)
482 image_path = ds_wrapper.TranslatedPathToLocalPath(translated_path)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200483 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
484 logging.notice('Using image path %s and payload directory %s',
485 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700486
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800487 # Generate rootfs and stateful update payloads if they do not exist.
Amin Hassani15b2c072020-10-20 11:23:51 -0700488 payload_path = os.path.join(payload_dir, rootfs_filename)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800489 if not os.path.exists(payload_path):
490 paygen_payload_lib.GenerateUpdatePayload(
491 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700492 if not os.path.exists(os.path.join(
493 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800494 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
495 payload_dir)
Amin Hassani15b2c072020-10-20 11:23:51 -0700496 return payload_dir, rootfs_filename
David Pursellf1d16a62015-03-25 13:31:04 -0700497
498 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700499 """Perform remote device update.
500
501 The update process includes:
502 1. initialize a device instance for the given remote device.
503 2. achieve payload_dir which contains the required payloads for updating.
504 3. initialize an auto-updater instance to do RunUpdate().
505 4. After auto-update, all temp files and dir will be cleaned up.
506 """
David Pursellf1d16a62015-03-25 13:31:04 -0700507 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700508 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600509 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
510 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700511
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600512 try:
513 # Get payload directory
Zentaro Kavanagh8f79c2a2020-05-28 14:12:07 -0700514 logging.notice('Staging payloads...')
Amin Hassani15b2c072020-10-20 11:23:51 -0700515 payload_dir, rootfs_filename = self.GetPayloadPaths(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700516
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600517 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700518 chromeos_AU = auto_updater.ChromiumOSUpdater(
519 device=device,
520 build_name=None,
521 payload_dir=payload_dir,
Amin Hassani15b2c072020-10-20 11:23:51 -0700522 payload_filename=rootfs_filename,
Amin Hassani9800d432019-07-24 14:23:39 -0700523 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600524 do_rootfs_update=self.do_rootfs_update,
525 do_stateful_update=self.do_stateful_update,
526 reboot=self.reboot,
527 disable_verification=self.disable_verification,
528 clobber_stateful=self.clobber_stateful,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800529 clear_tpm_owner=self.clear_tpm_owner,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800530 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800531 send_payload_in_parallel=self.send_payload_in_parallel,
Amin Hassani3e87ce12020-10-22 10:39:36 -0700532 resolve_app_id_mismatch=True,
Sanika Kulkarni3cb57912020-03-18 15:17:46 -0700533 transfer_class=auto_updater_transfer.LocalTransfer)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600534 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700535
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600536 except Exception:
537 logging.error('Device update failed.')
538 lsb_entries = sorted(device.lsb_release.items())
539 logging.info(
540 'Following are the LSB version details of the device:\n%s',
541 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
542 raise
543
544 logging.notice('Update performed successfully.')
545
546 except remote_access.RemoteAccessException:
547 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700548 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600549
David Pursellf1d16a62015-03-25 13:31:04 -0700550 finally:
551 self.Cleanup()
552
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200553
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700554def Flash(device, image, board=None, install=False, src_image_to_delta=None,
555 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600556 reboot=True, wipe=True, ssh_private_key=None, ping=True,
557 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800558 force=False, debug=False, send_payload_in_parallel=False,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800559 clear_tpm_owner=False, version=None):
David Pursellf1d16a62015-03-25 13:31:04 -0700560 """Flashes a device, USB drive, or file with an image.
561
562 This provides functionality common to `cros flash` and `brillo flash`
563 so that they can parse the commandline separately but still use the
564 same underlying functionality.
565
566 Args:
David Pursell2e773382015-04-03 14:30:47 -0700567 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700568 image: Path (string) to the update image. Can be a local or xbuddy path;
569 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700570 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700571 install: Install to USB using base disk layout; USB |device| scheme only.
572 src_image_to_delta: Local path to an image to be used as the base to
573 generate delta payloads; SSH |device| scheme only.
574 rootfs_update: Update rootfs partition; SSH |device| scheme only.
575 stateful_update: Update stateful partition; SSH |device| scheme only.
576 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
Yi Chou5f4e51f2020-10-22 16:33:00 +0800577 clear_tpm_owner: Clear the TPM owner on reboot; SSH |device| scheme only.
David Pursellf1d16a62015-03-25 13:31:04 -0700578 reboot: Reboot device after update; SSH |device| scheme only.
579 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600580 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700581 ping: Ping the device before attempting update; SSH |device| scheme only.
582 disable_rootfs_verification: Remove rootfs verification after update; SSH
583 |device| scheme only.
584 clear_cache: Clear the devserver static directory.
585 yes: Assume "yes" for any prompt.
586 force: Ignore sanity checks and prompts. Overrides |yes| if True.
587 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800588 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
589 up transmissions for long haul between endpoints.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000590 version: Default version.
David Pursellf1d16a62015-03-25 13:31:04 -0700591
592 Raises:
593 FlashError: An unrecoverable error occured.
594 ValueError: Invalid parameter combination.
595 """
596 if force:
597 yes = True
598
599 if clear_cache:
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000600 ds_wrapper.DevServerWrapper.WipeStaticDirectory()
601 ds_wrapper.DevServerWrapper.CreateStaticDirectory()
David Pursellf1d16a62015-03-25 13:31:04 -0700602
603 if install:
David Pursell2e773382015-04-03 14:30:47 -0700604 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700605 raise ValueError(
606 '--install can only be used when writing to a USB device')
607 if not cros_build_lib.IsInsideChroot():
608 raise ValueError('--install can only be used inside the chroot')
609
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000610 # The user may not have specified a source image, use version as the default.
611 image = image or version
David Pursell2e773382015-04-03 14:30:47 -0700612 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
613 if device:
614 hostname, port = device.hostname, device.port
615 else:
616 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700617 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700618 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700619 hostname,
620 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700621 image,
622 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700623 src_image_to_delta=src_image_to_delta,
624 rootfs_update=rootfs_update,
625 stateful_update=stateful_update,
626 clobber_stateful=clobber_stateful,
Yi Chou5f4e51f2020-10-22 16:33:00 +0800627 clear_tpm_owner=clear_tpm_owner,
David Pursellf1d16a62015-03-25 13:31:04 -0700628 reboot=reboot,
629 wipe=wipe,
630 debug=debug,
631 yes=yes,
632 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600633 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700634 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800635 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800636 send_payload_in_parallel=send_payload_in_parallel,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000637 version=version)
David Pursellf1d16a62015-03-25 13:31:04 -0700638 updater.Run()
639 elif device.scheme == commandline.DEVICE_SCHEME_USB:
640 path = osutils.ExpandPath(device.path) if device.path else ''
641 logging.info('Preparing to image the removable device %s', path)
642 imager = USBImager(path,
643 board,
644 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000645 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700646 debug=debug,
647 install=install,
648 yes=yes)
649 imager.Run()
650 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
651 logging.info('Preparing to copy image to %s', device.path)
652 imager = FileImager(device.path,
653 board,
654 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000655 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700656 debug=debug,
657 yes=yes)
658 imager.Run()