blob: ca8a8191d65890c8a21acc7da7132373aea67153 [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,
Amin Hassani60c683b2020-06-11 15:20:34 -0700367 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
382 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
xixuane851dfb2016-05-02 18:02:37 -0700402 def GetPayloadDir(self, device):
403 """Get directory of payload 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:
xixuane851dfb2016-05-02 18:02:37 -0700429 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700430 """
xixuane851dfb2016-05-02 18:02:37 -0700431 if os.path.isdir(self.image):
432 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800433 logging.info('Using provided payloads in %s', self.image)
434 return self.image
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600435
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200436 image_path = None
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800437 if os.path.isfile(self.image):
438 # The given path is an image.
439 image_path = self.image
440 payload_dir = self.tempdir
xixuane851dfb2016-05-02 18:02:37 -0700441 else:
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800442 # Assuming it is an xbuddy path.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000443 self.board = cros_build_lib.GetBoard(
444 device_board=device.board or GetDefaultBoard(),
445 override_board=self.board,
446 force=self.yes,
447 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700448 if not self.force and self.board != device.board:
449 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700450 raise FlashError('Device (%s) is incompatible with board %s' %
451 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700452 logging.info('Board is %s', self.board)
453
Amin Hassanic20a3c32019-06-02 21:43:21 -0700454
455 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
456 # use xbuddy library directly instead of the devserver_wrapper.
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200457 # Fetch the full payload and properties, and stateful files. If this
458 # fails, fallback to downloading the image.
459 try:
460 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000461 os.path.join(self.image, artifact_info.FULL_PAYLOAD),
462 self.board, self.version, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200463 payload_dir = os.path.dirname(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000464 ds_wrapper.TranslatedPathToLocalPath(translated_path))
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200465 ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800466 os.path.join(self.image, artifact_info.STATEFUL_PAYLOAD),
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000467 self.board, self.version, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200468 fetch_image = False
469 except (ds_wrapper.ImagePathError, ds_wrapper.ArtifactDownloadError):
470 logging.info('Could not find full_payload or stateful for "%s"',
471 self.image)
472 fetch_image = True
xixuane851dfb2016-05-02 18:02:37 -0700473
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200474 # We didn't find the full_payload, attempt to download the image.
475 if fetch_image:
476 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000477 self.image, self.board, self.version)
478 image_path = ds_wrapper.TranslatedPathToLocalPath(translated_path)
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.
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700484 payload_path = os.path.join(payload_dir,
485 auto_updater_transfer.ROOTFS_FILENAME)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800486 if not os.path.exists(payload_path):
487 paygen_payload_lib.GenerateUpdatePayload(
488 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700489 if not os.path.exists(os.path.join(
490 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800491 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
492 payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700493 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700494
495 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700496 """Perform remote device update.
497
498 The update process includes:
499 1. initialize a device instance for the given remote device.
500 2. achieve payload_dir which contains the required payloads for updating.
501 3. initialize an auto-updater instance to do RunUpdate().
502 4. After auto-update, all temp files and dir will be cleaned up.
503 """
David Pursellf1d16a62015-03-25 13:31:04 -0700504 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700505 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600506 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
507 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700508
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600509 try:
510 # Get payload directory
Zentaro Kavanagh8f79c2a2020-05-28 14:12:07 -0700511 logging.notice('Staging payloads...')
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600512 payload_dir = self.GetPayloadDir(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700513
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600514 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700515 chromeos_AU = auto_updater.ChromiumOSUpdater(
516 device=device,
517 build_name=None,
518 payload_dir=payload_dir,
Amin Hassani954fbcf2020-10-09 16:34:49 -0700519 payload_filename=auto_updater_transfer.ROOTFS_FILENAME,
Amin Hassani9800d432019-07-24 14:23:39 -0700520 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600521 do_rootfs_update=self.do_rootfs_update,
522 do_stateful_update=self.do_stateful_update,
523 reboot=self.reboot,
524 disable_verification=self.disable_verification,
525 clobber_stateful=self.clobber_stateful,
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,
Amin Hassani60c683b2020-06-11 15:20:34 -0700555 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.
573 reboot: Reboot device after update; SSH |device| scheme only.
574 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600575 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700576 ping: Ping the device before attempting update; SSH |device| scheme only.
577 disable_rootfs_verification: Remove rootfs verification after update; SSH
578 |device| scheme only.
579 clear_cache: Clear the devserver static directory.
580 yes: Assume "yes" for any prompt.
581 force: Ignore sanity checks and prompts. Overrides |yes| if True.
582 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800583 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
584 up transmissions for long haul between endpoints.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000585 version: Default version.
David Pursellf1d16a62015-03-25 13:31:04 -0700586
587 Raises:
588 FlashError: An unrecoverable error occured.
589 ValueError: Invalid parameter combination.
590 """
591 if force:
592 yes = True
593
594 if clear_cache:
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000595 ds_wrapper.DevServerWrapper.WipeStaticDirectory()
596 ds_wrapper.DevServerWrapper.CreateStaticDirectory()
David Pursellf1d16a62015-03-25 13:31:04 -0700597
598 if install:
David Pursell2e773382015-04-03 14:30:47 -0700599 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700600 raise ValueError(
601 '--install can only be used when writing to a USB device')
602 if not cros_build_lib.IsInsideChroot():
603 raise ValueError('--install can only be used inside the chroot')
604
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000605 # The user may not have specified a source image, use version as the default.
606 image = image or version
David Pursell2e773382015-04-03 14:30:47 -0700607 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
608 if device:
609 hostname, port = device.hostname, device.port
610 else:
611 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700612 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700613 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700614 hostname,
615 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700616 image,
617 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700618 src_image_to_delta=src_image_to_delta,
619 rootfs_update=rootfs_update,
620 stateful_update=stateful_update,
621 clobber_stateful=clobber_stateful,
622 reboot=reboot,
623 wipe=wipe,
624 debug=debug,
625 yes=yes,
626 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600627 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700628 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800629 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800630 send_payload_in_parallel=send_payload_in_parallel,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000631 version=version)
David Pursellf1d16a62015-03-25 13:31:04 -0700632 updater.Run()
633 elif device.scheme == commandline.DEVICE_SCHEME_USB:
634 path = osutils.ExpandPath(device.path) if device.path else ''
635 logging.info('Preparing to image the removable device %s', path)
636 imager = USBImager(path,
637 board,
638 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000639 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700640 debug=debug,
641 install=install,
642 yes=yes)
643 imager.Run()
644 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
645 logging.info('Preparing to copy image to %s', device.path)
646 imager = FileImager(device.path,
647 board,
648 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000649 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700650 debug=debug,
651 yes=yes)
652 imager.Run()