blob: 18ad281fd31b4cc4656cdcbc9d29750b6cf7f517 [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
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000023from chromite.lib import constants
David Pursellf1d16a62015-03-25 13:31:04 -070024from chromite.lib import cros_build_lib
25from chromite.lib import cros_logging as logging
26from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070027from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070028from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070029from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070030from chromite.lib import remote_access
31
Amin Hassanic0f06fa2019-01-28 15:24:47 -080032from chromite.lib.paygen import paygen_payload_lib
33from chromite.lib.paygen import paygen_stateful_payload_lib
34
Amin Hassani1c25d4b2019-11-22 10:59:07 -080035from chromite.lib.xbuddy import artifact_info
36
David Pursellf1d16a62015-03-25 13:31:04 -070037
Mike Frysinger3f087aa2020-03-20 06:03:16 -040038assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
39
40
Don Garrett97d7dc22015-01-20 14:07:56 -080041DEVSERVER_STATIC_DIR = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -070042 os.path.join(constants.CHROOT_SOURCE_ROOT, 'devserver', 'static'))
43
44
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +000045def GetDefaultBoard():
46 """Look up default board.
47
48 In a chrome checkout, return $SDK_BOARD. In a chromeos checkout,
49 return the contents of .default_board.
50 """
51 if path_util.DetermineCheckout().type == path_util.CHECKOUT_TYPE_GCLIENT:
52 return os.environ.get(cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV)
53 return cros_build_lib.GetDefaultBoard()
54
55
Ralph Nathan9b997232015-05-15 13:13:12 -070056class UsbImagerOperation(operation.ProgressBarOperation):
57 """Progress bar for flashing image to operation."""
58
59 def __init__(self, image):
60 super(UsbImagerOperation, self).__init__()
61 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070062 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070063 self._bytes = re.compile(r'(\d+) bytes')
64
65 def _GetDDPid(self):
66 """Get the Pid of dd."""
67 try:
Mike Frysinger45602c72019-09-22 02:15:11 -040068 pids = cros_build_lib.run(['pgrep', 'dd'], capture_output=True,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040069 print_cmd=False, encoding='utf-8').stdout
Ralph Nathan9b997232015-05-15 13:13:12 -070070 for pid in pids.splitlines():
71 if osutils.IsChildProcess(int(pid), name='dd'):
72 return int(pid)
73 return -1
74 except cros_build_lib.RunCommandError:
75 # If dd isn't still running, then we assume that it is finished.
76 return -1
77
78 def _PingDD(self, dd_pid):
79 """Send USR1 signal to dd to get status update."""
80 try:
81 cmd = ['kill', '-USR1', str(dd_pid)]
Mike Frysinger45602c72019-09-22 02:15:11 -040082 cros_build_lib.sudo_run(cmd, print_cmd=False)
Ralph Nathan9b997232015-05-15 13:13:12 -070083 except cros_build_lib.RunCommandError:
84 # Here we assume that dd finished in the background.
85 return
86
87 def ParseOutput(self, output=None):
88 """Parse the output of dd to update progress bar."""
89 dd_pid = self._GetDDPid()
90 if dd_pid == -1:
91 return
92
93 self._PingDD(dd_pid)
94
95 if output is None:
96 stdout = self._stdout.read()
97 stderr = self._stderr.read()
98 output = stdout + stderr
99
100 match = self._bytes.search(output)
101 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -0700102 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -0700103
Mike Frysinger93e8ffa2019-07-03 20:24:18 -0400104 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -0700105
106
Mike Frysinger32759e42016-12-21 18:40:16 -0500107def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700108 """Determines if a file is a valid GPT disk.
109
110 Args:
111 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -0500112 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700113 """
114 if os.path.isfile(file_path):
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400115 with open(file_path, 'rb') as image_file:
Mike Frysinger32759e42016-12-21 18:40:16 -0500116 if require_pmbr:
117 # Seek to the end of LBA0 and look for the PMBR boot signature.
118 image_file.seek(0x1fe)
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400119 if image_file.read(2) != b'\x55\xaa':
Mike Frysinger32759e42016-12-21 18:40:16 -0500120 return False
121 # Current file position is start of LBA1 now.
122 else:
123 # Seek to LBA1 where the GPT starts.
124 image_file.seek(0x200)
125
126 # See if there's a GPT here.
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400127 if image_file.read(8) == b'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700128 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500129
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700130 return False
131
132
133def _ChooseImageFromDirectory(dir_path):
134 """Lists all image files in |dir_path| and ask user to select one.
135
136 Args:
137 dir_path: Path to the directory.
138 """
139 images = sorted([x for x in os.listdir(dir_path) if
140 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
141 idx = 0
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400142 if not images:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700143 raise ValueError('No image found in %s.' % dir_path)
144 elif len(images) > 1:
145 idx = cros_build_lib.GetChoice(
146 'Multiple images found in %s. Please select one to continue:' % (
147 (dir_path,)),
148 images)
149
150 return os.path.join(dir_path, images[idx])
151
152
David Pursellf1d16a62015-03-25 13:31:04 -0700153class FlashError(Exception):
154 """Thrown when there is an unrecoverable error during flash."""
155
156
157class USBImager(object):
158 """Copy image to the target removable device."""
159
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000160 def __init__(self, device, board, image, version, debug=False,
161 install=False, yes=False):
162 """Initializes USBImager."""
David Pursellf1d16a62015-03-25 13:31:04 -0700163 self.device = device
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000164 self.board = board if board else GetDefaultBoard()
David Pursellf1d16a62015-03-25 13:31:04 -0700165 self.image = image
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000166 self.version = version
David Pursellf1d16a62015-03-25 13:31:04 -0700167 self.debug = debug
168 self.debug_level = logging.DEBUG if debug else logging.INFO
169 self.install = install
170 self.yes = yes
171
172 def DeviceNameToPath(self, device_name):
173 return '/dev/%s' % device_name
174
175 def GetRemovableDeviceDescription(self, device):
176 """Returns a informational description of the removable |device|.
177
178 Args:
179 device: the device name (e.g. sdc).
180
181 Returns:
182 A string describing |device| (e.g. Patriot Memory 7918 MB).
183 """
184 desc = [
185 osutils.GetDeviceInfo(device, keyword='manufacturer'),
186 osutils.GetDeviceInfo(device, keyword='product'),
187 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
188 '(%s)' % self.DeviceNameToPath(device),
189 ]
190 return ' '.join([x for x in desc if x])
191
192 def ListAllRemovableDevices(self):
193 """Returns a list of removable devices.
194
195 Returns:
196 A list of device names (e.g. ['sdb', 'sdc']).
197 """
198 devices = osutils.ListBlockDevices()
199 removable_devices = []
200 for d in devices:
201 if d.TYPE == 'disk' and d.RM == '1':
202 removable_devices.append(d.NAME)
203
204 return removable_devices
205
206 def ChooseRemovableDevice(self, devices):
207 """Lists all removable devices and asks user to select/confirm.
208
209 Args:
210 devices: a list of device names (e.g. ['sda', 'sdb']).
211
212 Returns:
213 The device name chosen by the user.
214 """
215 idx = cros_build_lib.GetChoice(
216 'Removable device(s) found. Please select/confirm to continue:',
217 [self.GetRemovableDeviceDescription(x) for x in devices])
218
219 return devices[idx]
220
221 def InstallImageToDevice(self, image, device):
222 """Installs |image| to the removable |device|.
223
224 Args:
225 image: Path to the image to copy.
226 device: Device to copy to.
227 """
228 cmd = [
229 'chromeos-install',
230 '--yes',
231 '--skip_src_removable',
232 '--skip_dst_removable',
233 '--payload_image=%s' % image,
234 '--dst=%s' % device,
235 '--skip_postinstall',
236 ]
Mike Frysinger45602c72019-09-22 02:15:11 -0400237 cros_build_lib.sudo_run(cmd,
238 print_cmd=True,
239 debug_level=logging.NOTICE,
Mike Frysinger66d32cd2019-12-17 14:55:29 -0500240 stderr=subprocess.STDOUT,
Mike Frysinger45602c72019-09-22 02:15:11 -0400241 log_output=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700242
243 def CopyImageToDevice(self, image, device):
244 """Copies |image| to the removable |device|.
245
246 Args:
247 image: Path to the image to copy.
248 device: Device to copy to.
249 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700250 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800251 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700252 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
253 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400254 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400255 encoding='utf-8', update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700256 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400257 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700258 cmd, debug_level=logging.NOTICE,
259 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700260
Brian Norris6386fde2018-10-29 13:34:28 -0700261 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
262 # up for us with a 'write' command, so we have a standards-conforming GPT.
263 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
264 # fix GPT sanity issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400265 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500266 check=False,
Mike Frysinger45602c72019-09-22 02:15:11 -0400267 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700268
Mike Frysinger45602c72019-09-22 02:15:11 -0400269 cros_build_lib.sudo_run(['partx', '-u', device],
270 debug_level=self.debug_level)
271 cros_build_lib.sudo_run(['sync', '-d', device],
272 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700273
David Pursellf1d16a62015-03-25 13:31:04 -0700274 def _GetImagePath(self):
275 """Returns the image path to use."""
276 image_path = translated_path = None
277 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700278 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700279 # TODO(wnwen): Open the tarball and if there is just one file in it,
280 # use that instead. Existing code in upload_symbols.py.
281 if cros_build_lib.BooleanPrompt(
282 prolog='The given image file is not a valid disk image. Perhaps '
283 'you forgot to untar it.',
284 prompt='Terminate the current flash process?'):
285 raise FlashError('Update terminated by user.')
286 image_path = self.image
287 elif os.path.isdir(self.image):
288 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700289 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700290 else:
291 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700292 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000293 self.image, self.board, version=self.version,
294 static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700295 image_path = ds_wrapper.TranslatedPathToLocalPath(
Don Garrett97d7dc22015-01-20 14:07:56 -0800296 translated_path, DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700297
298 logging.info('Using image %s', translated_path or image_path)
299 return image_path
300
301 def Run(self):
302 """Image the removable device."""
303 devices = self.ListAllRemovableDevices()
304
305 if self.device:
306 # If user specified a device path, check if it exists.
307 if not os.path.exists(self.device):
308 raise FlashError('Device path %s does not exist.' % self.device)
309
310 # Then check if it is removable.
311 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
312 msg = '%s is not a removable device.' % self.device
313 if not (self.yes or cros_build_lib.BooleanPrompt(
314 default=False, prolog=msg)):
315 raise FlashError('You can specify usb:// to choose from a list of '
316 'removable devices.')
317 target = None
318 if self.device:
319 # Get device name from path (e.g. sdc in /dev/sdc).
320 target = self.device.rsplit(os.path.sep, 1)[-1]
321 elif devices:
322 # Ask user to choose from the list.
323 target = self.ChooseRemovableDevice(devices)
324 else:
325 raise FlashError('No removable devices detected.')
326
327 image_path = self._GetImagePath()
328 try:
329 device = self.DeviceNameToPath(target)
330 if self.install:
331 self.InstallImageToDevice(image_path, device)
332 else:
333 self.CopyImageToDevice(image_path, device)
334 except cros_build_lib.RunCommandError:
335 logging.error('Failed copying image to device %s',
336 self.DeviceNameToPath(target))
337
338
339class FileImager(USBImager):
340 """Copy image to the target path."""
341
342 def Run(self):
343 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800344 if not os.path.isdir(os.path.dirname(self.device)):
345 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700346
347 image_path = self._GetImagePath()
348 if os.path.isdir(self.device):
349 logging.info('Copying to %s',
350 os.path.join(self.device, os.path.basename(image_path)))
351 else:
352 logging.info('Copying to %s', self.device)
353 try:
354 shutil.copy(image_path, self.device)
355 except IOError:
356 logging.error('Failed to copy image %s to %s', image_path, self.device)
357
358
359class RemoteDeviceUpdater(object):
360 """Performs update on a remote device."""
David Pursellf1d16a62015-03-25 13:31:04 -0700361 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
362 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700363 # Root working directory on the device. This directory is in the
364 # stateful partition and thus has enough space to store the payloads.
Amin Hassanidc4e7f02020-02-03 11:10:22 -0800365 DEVICE_BASE_DIR = '/usr/local/tmp/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700366 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
367 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700368
369 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
370 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700371 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600372 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800373 disable_verification=False, send_payload_in_parallel=False,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000374 experimental_au=False, version=None):
David Pursellf1d16a62015-03-25 13:31:04 -0700375 """Initializes RemoteDeviceUpdater"""
376 if not stateful_update and not rootfs_update:
377 raise ValueError('No update operation to perform; either stateful or'
378 ' rootfs partitions must be updated.')
379 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
380 self.ssh_hostname = ssh_hostname
381 self.ssh_port = ssh_port
382 self.image = image
383 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700384 self.src_image_to_delta = src_image_to_delta
385 self.do_stateful_update = stateful_update
386 self.do_rootfs_update = rootfs_update
387 self.disable_verification = disable_verification
388 self.clobber_stateful = clobber_stateful
389 self.reboot = reboot
390 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600391 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700392 self.ping = ping
393 # Do not wipe if debug is set.
394 self.wipe = wipe and not debug
395 self.yes = yes
396 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800397 self.send_payload_in_parallel = send_payload_in_parallel
Amin Hassanie62d2e12019-02-01 10:40:41 -0800398 self.experimental_au = experimental_au
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000399 self.version = version
David Pursellf1d16a62015-03-25 13:31:04 -0700400
David Pursellf1d16a62015-03-25 13:31:04 -0700401 def Cleanup(self):
402 """Cleans up the temporary directory."""
403 if self.wipe:
404 logging.info('Cleaning up temporary working directory...')
405 osutils.RmDir(self.tempdir)
406 else:
407 logging.info('You can find the log files and/or payloads in %s',
408 self.tempdir)
409
xixuane851dfb2016-05-02 18:02:37 -0700410 def GetPayloadDir(self, device):
411 """Get directory of payload for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700412
xixuane851dfb2016-05-02 18:02:37 -0700413 This method is used to obtain the directory of payload for cros-flash. The
414 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700415
xixuane851dfb2016-05-02 18:02:37 -0700416 If self.image is a directory, we directly use the provided update payload(s)
417 in this directory.
418
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800419 If self.image is an image, we will generate payloads for it and put them in
420 our temporary directory. The reason is that people may modify a local image
421 or override it (on the same path) with a different image, so in order to be
422 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700423
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800424 If non of the above cases, we use the xbuddy to first obtain the image path
425 (and possibly download it). Then we will generate the payloads in the same
426 directory the image is located. The reason is that this is what devserver
427 used to do. The path to the image generated by the devserver (or xbuddy) is
428 unique and normally nobody override its image with a different one. That is
429 why I think it is safe to put the payloads next to the image. This is a poor
430 man's version of caching but it makes cros flash faster for users who flash
431 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700432
433 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400434 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700435
436 Returns:
xixuane851dfb2016-05-02 18:02:37 -0700437 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700438 """
xixuane851dfb2016-05-02 18:02:37 -0700439 if os.path.isdir(self.image):
440 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800441 logging.info('Using provided payloads in %s', self.image)
442 return self.image
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600443
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200444 image_path = None
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800445 if os.path.isfile(self.image):
446 # The given path is an image.
447 image_path = self.image
448 payload_dir = self.tempdir
xixuane851dfb2016-05-02 18:02:37 -0700449 else:
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800450 # Assuming it is an xbuddy path.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000451 self.board = cros_build_lib.GetBoard(
452 device_board=device.board or GetDefaultBoard(),
453 override_board=self.board,
454 force=self.yes,
455 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700456 if not self.force and self.board != device.board:
457 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700458 raise FlashError('Device (%s) is incompatible with board %s' %
459 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700460 logging.info('Board is %s', self.board)
461
Amin Hassanic20a3c32019-06-02 21:43:21 -0700462
463 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
464 # use xbuddy library directly instead of the devserver_wrapper.
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200465 # Fetch the full payload and properties, and stateful files. If this
466 # fails, fallback to downloading the image.
467 try:
468 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800469 os.path.join(self.image, artifact_info.FULL_PAYLOAD), self.board,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000470 version=self.version, static_dir=DEVSERVER_STATIC_DIR, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200471 payload_dir = os.path.dirname(
472 ds_wrapper.TranslatedPathToLocalPath(translated_path,
473 DEVSERVER_STATIC_DIR))
474 ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800475 os.path.join(self.image, artifact_info.STATEFUL_PAYLOAD),
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000476 self.board, version=self.version, static_dir=DEVSERVER_STATIC_DIR,
477 silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200478 fetch_image = False
479 except (ds_wrapper.ImagePathError, ds_wrapper.ArtifactDownloadError):
480 logging.info('Could not find full_payload or stateful for "%s"',
481 self.image)
482 fetch_image = True
xixuane851dfb2016-05-02 18:02:37 -0700483
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200484 # We didn't find the full_payload, attempt to download the image.
485 if fetch_image:
486 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000487 self.image, self.board, version=self.version,
488 static_dir=DEVSERVER_STATIC_DIR)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200489 image_path = ds_wrapper.TranslatedPathToLocalPath(
490 translated_path, DEVSERVER_STATIC_DIR)
491 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
492 logging.notice('Using image path %s and payload directory %s',
493 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700494
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800495 # Generate rootfs and stateful update payloads if they do not exist.
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700496 payload_path = os.path.join(payload_dir,
497 auto_updater_transfer.ROOTFS_FILENAME)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800498 if not os.path.exists(payload_path):
499 paygen_payload_lib.GenerateUpdatePayload(
500 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700501 if not os.path.exists(os.path.join(
502 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800503 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
504 payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700505 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700506
507 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700508 """Perform remote device update.
509
510 The update process includes:
511 1. initialize a device instance for the given remote device.
512 2. achieve payload_dir which contains the required payloads for updating.
513 3. initialize an auto-updater instance to do RunUpdate().
514 4. After auto-update, all temp files and dir will be cleaned up.
515 """
David Pursellf1d16a62015-03-25 13:31:04 -0700516 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700517 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600518 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
519 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700520
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600521 try:
522 # Get payload directory
523 payload_dir = self.GetPayloadDir(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700524
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600525 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700526 chromeos_AU = auto_updater.ChromiumOSUpdater(
527 device=device,
528 build_name=None,
529 payload_dir=payload_dir,
530 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600531 do_rootfs_update=self.do_rootfs_update,
532 do_stateful_update=self.do_stateful_update,
533 reboot=self.reboot,
534 disable_verification=self.disable_verification,
535 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800536 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800537 send_payload_in_parallel=self.send_payload_in_parallel,
Sanika Kulkarni3cb57912020-03-18 15:17:46 -0700538 experimental_au=self.experimental_au,
539 transfer_class=auto_updater_transfer.LocalTransfer)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600540 chromeos_AU.CheckPayloads()
Sanika Kulkarni00b9d682019-11-26 09:43:20 -0800541 chromeos_AU.PreparePayloadPropsFile()
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600542 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700543
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600544 except Exception:
545 logging.error('Device update failed.')
546 lsb_entries = sorted(device.lsb_release.items())
547 logging.info(
548 'Following are the LSB version details of the device:\n%s',
549 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
550 raise
551
552 logging.notice('Update performed successfully.')
553
554 except remote_access.RemoteAccessException:
555 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700556 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600557
David Pursellf1d16a62015-03-25 13:31:04 -0700558 finally:
559 self.Cleanup()
560
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200561
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700562def Flash(device, image, board=None, install=False, src_image_to_delta=None,
563 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600564 reboot=True, wipe=True, ssh_private_key=None, ping=True,
565 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800566 force=False, debug=False, send_payload_in_parallel=False,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000567 experimental_au=False, version=None):
David Pursellf1d16a62015-03-25 13:31:04 -0700568 """Flashes a device, USB drive, or file with an image.
569
570 This provides functionality common to `cros flash` and `brillo flash`
571 so that they can parse the commandline separately but still use the
572 same underlying functionality.
573
574 Args:
David Pursell2e773382015-04-03 14:30:47 -0700575 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700576 image: Path (string) to the update image. Can be a local or xbuddy path;
577 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700578 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700579 install: Install to USB using base disk layout; USB |device| scheme only.
580 src_image_to_delta: Local path to an image to be used as the base to
581 generate delta payloads; SSH |device| scheme only.
582 rootfs_update: Update rootfs partition; SSH |device| scheme only.
583 stateful_update: Update stateful partition; SSH |device| scheme only.
584 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
585 reboot: Reboot device after update; SSH |device| scheme only.
586 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600587 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700588 ping: Ping the device before attempting update; SSH |device| scheme only.
589 disable_rootfs_verification: Remove rootfs verification after update; SSH
590 |device| scheme only.
591 clear_cache: Clear the devserver static directory.
592 yes: Assume "yes" for any prompt.
593 force: Ignore sanity checks and prompts. Overrides |yes| if True.
594 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800595 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
596 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800597 experimental_au: Use the experimental features auto updater. It should be
598 deprecated once crbug.com/872441 is fixed.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000599 version: Default version.
David Pursellf1d16a62015-03-25 13:31:04 -0700600
601 Raises:
602 FlashError: An unrecoverable error occured.
603 ValueError: Invalid parameter combination.
604 """
605 if force:
606 yes = True
607
608 if clear_cache:
609 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800610 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700611
612 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800613 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700614 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800615 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700616
617 if install:
David Pursell2e773382015-04-03 14:30:47 -0700618 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700619 raise ValueError(
620 '--install can only be used when writing to a USB device')
621 if not cros_build_lib.IsInsideChroot():
622 raise ValueError('--install can only be used inside the chroot')
623
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000624 # The user may not have specified a source image, use version as the default.
625 image = image or version
David Pursell2e773382015-04-03 14:30:47 -0700626 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
627 if device:
628 hostname, port = device.hostname, device.port
629 else:
630 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700631 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700632 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700633 hostname,
634 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700635 image,
636 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700637 src_image_to_delta=src_image_to_delta,
638 rootfs_update=rootfs_update,
639 stateful_update=stateful_update,
640 clobber_stateful=clobber_stateful,
641 reboot=reboot,
642 wipe=wipe,
643 debug=debug,
644 yes=yes,
645 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600646 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700647 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800648 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800649 send_payload_in_parallel=send_payload_in_parallel,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000650 experimental_au=experimental_au,
651 version=version)
David Pursellf1d16a62015-03-25 13:31:04 -0700652 updater.Run()
653 elif device.scheme == commandline.DEVICE_SCHEME_USB:
654 path = osutils.ExpandPath(device.path) if device.path else ''
655 logging.info('Preparing to image the removable device %s', path)
656 imager = USBImager(path,
657 board,
658 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000659 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700660 debug=debug,
661 install=install,
662 yes=yes)
663 imager.Run()
664 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
665 logging.info('Preparing to copy image to %s', device.path)
666 imager = FileImager(device.path,
667 board,
668 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000669 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700670 debug=debug,
671 yes=yes)
672 imager.Run()