blob: 796f28d8d623e0c2a078a6f0649b962628b58fe5 [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,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000367 experimental_au=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
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
Amin Hassanie62d2e12019-02-01 10:40:41 -0800391 self.experimental_au = experimental_au
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
xixuane851dfb2016-05-02 18:02:37 -0700403 def GetPayloadDir(self, device):
404 """Get directory of payload 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:
xixuane851dfb2016-05-02 18:02:37 -0700430 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700431 """
xixuane851dfb2016-05-02 18:02:37 -0700432 if os.path.isdir(self.image):
433 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800434 logging.info('Using provided payloads in %s', self.image)
435 return self.image
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600436
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200437 image_path = None
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800438 if os.path.isfile(self.image):
439 # The given path is an image.
440 image_path = self.image
441 payload_dir = self.tempdir
xixuane851dfb2016-05-02 18:02:37 -0700442 else:
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800443 # Assuming it is an xbuddy path.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000444 self.board = cros_build_lib.GetBoard(
445 device_board=device.board or GetDefaultBoard(),
446 override_board=self.board,
447 force=self.yes,
448 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700449 if not self.force and self.board != device.board:
450 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700451 raise FlashError('Device (%s) is incompatible with board %s' %
452 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700453 logging.info('Board is %s', self.board)
454
Amin Hassanic20a3c32019-06-02 21:43:21 -0700455
456 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
457 # use xbuddy library directly instead of the devserver_wrapper.
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200458 # Fetch the full payload and properties, and stateful files. If this
459 # fails, fallback to downloading the image.
460 try:
461 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000462 os.path.join(self.image, artifact_info.FULL_PAYLOAD),
463 self.board, self.version, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200464 payload_dir = os.path.dirname(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000465 ds_wrapper.TranslatedPathToLocalPath(translated_path))
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200466 ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800467 os.path.join(self.image, artifact_info.STATEFUL_PAYLOAD),
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000468 self.board, self.version, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200469 fetch_image = False
470 except (ds_wrapper.ImagePathError, ds_wrapper.ArtifactDownloadError):
471 logging.info('Could not find full_payload or stateful for "%s"',
472 self.image)
473 fetch_image = True
xixuane851dfb2016-05-02 18:02:37 -0700474
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200475 # We didn't find the full_payload, attempt to download the image.
476 if fetch_image:
477 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000478 self.image, self.board, self.version)
479 image_path = ds_wrapper.TranslatedPathToLocalPath(translated_path)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200480 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
481 logging.notice('Using image path %s and payload directory %s',
482 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700483
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800484 # Generate rootfs and stateful update payloads if they do not exist.
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700485 payload_path = os.path.join(payload_dir,
486 auto_updater_transfer.ROOTFS_FILENAME)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800487 if not os.path.exists(payload_path):
488 paygen_payload_lib.GenerateUpdatePayload(
489 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700490 if not os.path.exists(os.path.join(
491 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800492 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
493 payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700494 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700495
496 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700497 """Perform remote device update.
498
499 The update process includes:
500 1. initialize a device instance for the given remote device.
501 2. achieve payload_dir which contains the required payloads for updating.
502 3. initialize an auto-updater instance to do RunUpdate().
503 4. After auto-update, all temp files and dir will be cleaned up.
504 """
David Pursellf1d16a62015-03-25 13:31:04 -0700505 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700506 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600507 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
508 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700509
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600510 try:
511 # Get payload directory
512 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,
519 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600520 do_rootfs_update=self.do_rootfs_update,
521 do_stateful_update=self.do_stateful_update,
522 reboot=self.reboot,
523 disable_verification=self.disable_verification,
524 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800525 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800526 send_payload_in_parallel=self.send_payload_in_parallel,
Sanika Kulkarni3cb57912020-03-18 15:17:46 -0700527 experimental_au=self.experimental_au,
528 transfer_class=auto_updater_transfer.LocalTransfer)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600529 chromeos_AU.CheckPayloads()
Sanika Kulkarni00b9d682019-11-26 09:43:20 -0800530 chromeos_AU.PreparePayloadPropsFile()
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600531 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700532
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600533 except Exception:
534 logging.error('Device update failed.')
535 lsb_entries = sorted(device.lsb_release.items())
536 logging.info(
537 'Following are the LSB version details of the device:\n%s',
538 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
539 raise
540
541 logging.notice('Update performed successfully.')
542
543 except remote_access.RemoteAccessException:
544 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700545 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600546
David Pursellf1d16a62015-03-25 13:31:04 -0700547 finally:
548 self.Cleanup()
549
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200550
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700551def Flash(device, image, board=None, install=False, src_image_to_delta=None,
552 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600553 reboot=True, wipe=True, ssh_private_key=None, ping=True,
554 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800555 force=False, debug=False, send_payload_in_parallel=False,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000556 experimental_au=False, version=None):
David Pursellf1d16a62015-03-25 13:31:04 -0700557 """Flashes a device, USB drive, or file with an image.
558
559 This provides functionality common to `cros flash` and `brillo flash`
560 so that they can parse the commandline separately but still use the
561 same underlying functionality.
562
563 Args:
David Pursell2e773382015-04-03 14:30:47 -0700564 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700565 image: Path (string) to the update image. Can be a local or xbuddy path;
566 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700567 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700568 install: Install to USB using base disk layout; USB |device| scheme only.
569 src_image_to_delta: Local path to an image to be used as the base to
570 generate delta payloads; SSH |device| scheme only.
571 rootfs_update: Update rootfs partition; SSH |device| scheme only.
572 stateful_update: Update stateful partition; SSH |device| scheme only.
573 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
574 reboot: Reboot device after update; SSH |device| scheme only.
575 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600576 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700577 ping: Ping the device before attempting update; SSH |device| scheme only.
578 disable_rootfs_verification: Remove rootfs verification after update; SSH
579 |device| scheme only.
580 clear_cache: Clear the devserver static directory.
581 yes: Assume "yes" for any prompt.
582 force: Ignore sanity checks and prompts. Overrides |yes| if True.
583 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800584 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
585 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800586 experimental_au: Use the experimental features auto updater. It should be
587 deprecated once crbug.com/872441 is fixed.
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000588 version: Default version.
David Pursellf1d16a62015-03-25 13:31:04 -0700589
590 Raises:
591 FlashError: An unrecoverable error occured.
592 ValueError: Invalid parameter combination.
593 """
594 if force:
595 yes = True
596
597 if clear_cache:
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000598 ds_wrapper.DevServerWrapper.WipeStaticDirectory()
599 ds_wrapper.DevServerWrapper.CreateStaticDirectory()
David Pursellf1d16a62015-03-25 13:31:04 -0700600
601 if install:
David Pursell2e773382015-04-03 14:30:47 -0700602 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700603 raise ValueError(
604 '--install can only be used when writing to a USB device')
605 if not cros_build_lib.IsInsideChroot():
606 raise ValueError('--install can only be used inside the chroot')
607
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000608 # The user may not have specified a source image, use version as the default.
609 image = image or version
David Pursell2e773382015-04-03 14:30:47 -0700610 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
611 if device:
612 hostname, port = device.hostname, device.port
613 else:
614 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700615 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700616 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700617 hostname,
618 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700619 image,
620 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700621 src_image_to_delta=src_image_to_delta,
622 rootfs_update=rootfs_update,
623 stateful_update=stateful_update,
624 clobber_stateful=clobber_stateful,
625 reboot=reboot,
626 wipe=wipe,
627 debug=debug,
628 yes=yes,
629 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600630 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700631 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800632 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800633 send_payload_in_parallel=send_payload_in_parallel,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000634 experimental_au=experimental_au,
635 version=version)
David Pursellf1d16a62015-03-25 13:31:04 -0700636 updater.Run()
637 elif device.scheme == commandline.DEVICE_SCHEME_USB:
638 path = osutils.ExpandPath(device.path) if device.path else ''
639 logging.info('Preparing to image the removable device %s', path)
640 imager = USBImager(path,
641 board,
642 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000643 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700644 debug=debug,
645 install=install,
646 yes=yes)
647 imager.Run()
648 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
649 logging.info('Preparing to copy image to %s', device.path)
650 imager = FileImager(device.path,
651 board,
652 image,
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000653 version,
David Pursellf1d16a62015-03-25 13:31:04 -0700654 debug=debug,
655 yes=yes)
656 imager.Run()