blob: 99eab41b684dfef6a504fca02c4744422addafc5 [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
8from __future__ import print_function
9
David Pursellf1d16a62015-03-25 13:31:04 -070010import os
Ralph Nathan9b997232015-05-15 13:13:12 -070011import re
David Pursellf1d16a62015-03-25 13:31:04 -070012import shutil
13import tempfile
David Pursellf1d16a62015-03-25 13:31:04 -070014
Aviv Keshetb7519e12016-10-04 00:50:00 -070015from chromite.lib import constants
xixuane851dfb2016-05-02 18:02:37 -070016from chromite.lib import auto_updater
David Pursellf1d16a62015-03-25 13:31:04 -070017from chromite.lib import commandline
18from chromite.lib import cros_build_lib
19from chromite.lib import cros_logging as logging
20from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070021from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070022from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070023from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070024from chromite.lib import remote_access
25
Amin Hassanic0f06fa2019-01-28 15:24:47 -080026from chromite.lib.paygen import paygen_payload_lib
27from chromite.lib.paygen import paygen_stateful_payload_lib
28
David Pursellf1d16a62015-03-25 13:31:04 -070029
Don Garrett97d7dc22015-01-20 14:07:56 -080030DEVSERVER_STATIC_DIR = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -070031 os.path.join(constants.CHROOT_SOURCE_ROOT, 'devserver', 'static'))
32
33
Ralph Nathan9b997232015-05-15 13:13:12 -070034class UsbImagerOperation(operation.ProgressBarOperation):
35 """Progress bar for flashing image to operation."""
36
37 def __init__(self, image):
38 super(UsbImagerOperation, self).__init__()
39 self._size = os.path.getsize(image)
40 self._transferred = 0.
41 self._bytes = re.compile(r'(\d+) bytes')
42
43 def _GetDDPid(self):
44 """Get the Pid of dd."""
45 try:
46 pids = cros_build_lib.RunCommand(['pgrep', 'dd'], capture_output=True,
47 print_cmd=False).output
48 for pid in pids.splitlines():
49 if osutils.IsChildProcess(int(pid), name='dd'):
50 return int(pid)
51 return -1
52 except cros_build_lib.RunCommandError:
53 # If dd isn't still running, then we assume that it is finished.
54 return -1
55
56 def _PingDD(self, dd_pid):
57 """Send USR1 signal to dd to get status update."""
58 try:
59 cmd = ['kill', '-USR1', str(dd_pid)]
60 cros_build_lib.SudoRunCommand(cmd, print_cmd=False)
61 except cros_build_lib.RunCommandError:
62 # Here we assume that dd finished in the background.
63 return
64
65 def ParseOutput(self, output=None):
66 """Parse the output of dd to update progress bar."""
67 dd_pid = self._GetDDPid()
68 if dd_pid == -1:
69 return
70
71 self._PingDD(dd_pid)
72
73 if output is None:
74 stdout = self._stdout.read()
75 stderr = self._stderr.read()
76 output = stdout + stderr
77
78 match = self._bytes.search(output)
79 if match:
80 self._transferred = match.groups()[0]
81
82 self.ProgressBar(float(self._transferred) / self._size)
83
84
Mike Frysinger32759e42016-12-21 18:40:16 -050085def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070086 """Determines if a file is a valid GPT disk.
87
88 Args:
89 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -050090 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070091 """
92 if os.path.isfile(file_path):
Mike Frysinger32759e42016-12-21 18:40:16 -050093 with open(file_path) as image_file:
94 if require_pmbr:
95 # Seek to the end of LBA0 and look for the PMBR boot signature.
96 image_file.seek(0x1fe)
97 if image_file.read(2) != '\x55\xaa':
98 return False
99 # Current file position is start of LBA1 now.
100 else:
101 # Seek to LBA1 where the GPT starts.
102 image_file.seek(0x200)
103
104 # See if there's a GPT here.
105 if image_file.read(8) == 'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700106 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500107
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700108 return False
109
110
111def _ChooseImageFromDirectory(dir_path):
112 """Lists all image files in |dir_path| and ask user to select one.
113
114 Args:
115 dir_path: Path to the directory.
116 """
117 images = sorted([x for x in os.listdir(dir_path) if
118 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
119 idx = 0
120 if len(images) == 0:
121 raise ValueError('No image found in %s.' % dir_path)
122 elif len(images) > 1:
123 idx = cros_build_lib.GetChoice(
124 'Multiple images found in %s. Please select one to continue:' % (
125 (dir_path,)),
126 images)
127
128 return os.path.join(dir_path, images[idx])
129
130
David Pursellf1d16a62015-03-25 13:31:04 -0700131class FlashError(Exception):
132 """Thrown when there is an unrecoverable error during flash."""
133
134
135class USBImager(object):
136 """Copy image to the target removable device."""
137
Gilad Arnoldd0461442015-07-07 11:52:46 -0700138 def __init__(self, device, board, image, debug=False, install=False,
139 yes=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700140 """Initalizes USBImager."""
141 self.device = device
142 self.board = board if board else cros_build_lib.GetDefaultBoard()
143 self.image = image
David Pursellf1d16a62015-03-25 13:31:04 -0700144 self.debug = debug
145 self.debug_level = logging.DEBUG if debug else logging.INFO
146 self.install = install
147 self.yes = yes
148
149 def DeviceNameToPath(self, device_name):
150 return '/dev/%s' % device_name
151
152 def GetRemovableDeviceDescription(self, device):
153 """Returns a informational description of the removable |device|.
154
155 Args:
156 device: the device name (e.g. sdc).
157
158 Returns:
159 A string describing |device| (e.g. Patriot Memory 7918 MB).
160 """
161 desc = [
162 osutils.GetDeviceInfo(device, keyword='manufacturer'),
163 osutils.GetDeviceInfo(device, keyword='product'),
164 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
165 '(%s)' % self.DeviceNameToPath(device),
166 ]
167 return ' '.join([x for x in desc if x])
168
169 def ListAllRemovableDevices(self):
170 """Returns a list of removable devices.
171
172 Returns:
173 A list of device names (e.g. ['sdb', 'sdc']).
174 """
175 devices = osutils.ListBlockDevices()
176 removable_devices = []
177 for d in devices:
178 if d.TYPE == 'disk' and d.RM == '1':
179 removable_devices.append(d.NAME)
180
181 return removable_devices
182
183 def ChooseRemovableDevice(self, devices):
184 """Lists all removable devices and asks user to select/confirm.
185
186 Args:
187 devices: a list of device names (e.g. ['sda', 'sdb']).
188
189 Returns:
190 The device name chosen by the user.
191 """
192 idx = cros_build_lib.GetChoice(
193 'Removable device(s) found. Please select/confirm to continue:',
194 [self.GetRemovableDeviceDescription(x) for x in devices])
195
196 return devices[idx]
197
198 def InstallImageToDevice(self, image, device):
199 """Installs |image| to the removable |device|.
200
201 Args:
202 image: Path to the image to copy.
203 device: Device to copy to.
204 """
205 cmd = [
206 'chromeos-install',
207 '--yes',
208 '--skip_src_removable',
209 '--skip_dst_removable',
210 '--payload_image=%s' % image,
211 '--dst=%s' % device,
212 '--skip_postinstall',
213 ]
Shuqian Zhao33242472017-08-02 18:28:44 -0700214 cros_build_lib.SudoRunCommand(cmd,
215 print_cmd=True,
216 debug_level=logging.NOTICE,
217 combine_stdout_stderr=True,
218 log_output=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700219
220 def CopyImageToDevice(self, image, device):
221 """Copies |image| to the removable |device|.
222
223 Args:
224 image: Path to the image to copy.
225 device: Device to copy to.
226 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700227 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
228 'oflag=sync']
229 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
230 op = UsbImagerOperation(image)
231 op.Run(cros_build_lib.SudoRunCommand, cmd, debug_level=logging.NOTICE,
232 update_period=0.5)
233 else:
234 cros_build_lib.SudoRunCommand(
235 cmd, debug_level=logging.NOTICE,
236 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700237
Brian Norris6386fde2018-10-29 13:34:28 -0700238 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
239 # up for us with a 'write' command, so we have a standards-conforming GPT.
240 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
241 # fix GPT sanity issues.
242 cros_build_lib.SudoRunCommand(['sfdisk', device], input='write\n',
243 error_code_ok=True,
Brian Norrisb4d99982018-10-16 16:02:49 -0700244 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700245
David Pursellf1d16a62015-03-25 13:31:04 -0700246 cros_build_lib.SudoRunCommand(['sync'], debug_level=self.debug_level)
247
David Pursellf1d16a62015-03-25 13:31:04 -0700248 def _GetImagePath(self):
249 """Returns the image path to use."""
250 image_path = translated_path = None
251 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700252 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700253 # TODO(wnwen): Open the tarball and if there is just one file in it,
254 # use that instead. Existing code in upload_symbols.py.
255 if cros_build_lib.BooleanPrompt(
256 prolog='The given image file is not a valid disk image. Perhaps '
257 'you forgot to untar it.',
258 prompt='Terminate the current flash process?'):
259 raise FlashError('Update terminated by user.')
260 image_path = self.image
261 elif os.path.isdir(self.image):
262 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700263 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700264 else:
265 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700266 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Don Garrett97d7dc22015-01-20 14:07:56 -0800267 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700268 image_path = ds_wrapper.TranslatedPathToLocalPath(
Don Garrett97d7dc22015-01-20 14:07:56 -0800269 translated_path, DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700270
271 logging.info('Using image %s', translated_path or image_path)
272 return image_path
273
274 def Run(self):
275 """Image the removable device."""
276 devices = self.ListAllRemovableDevices()
277
278 if self.device:
279 # If user specified a device path, check if it exists.
280 if not os.path.exists(self.device):
281 raise FlashError('Device path %s does not exist.' % self.device)
282
283 # Then check if it is removable.
284 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
285 msg = '%s is not a removable device.' % self.device
286 if not (self.yes or cros_build_lib.BooleanPrompt(
287 default=False, prolog=msg)):
288 raise FlashError('You can specify usb:// to choose from a list of '
289 'removable devices.')
290 target = None
291 if self.device:
292 # Get device name from path (e.g. sdc in /dev/sdc).
293 target = self.device.rsplit(os.path.sep, 1)[-1]
294 elif devices:
295 # Ask user to choose from the list.
296 target = self.ChooseRemovableDevice(devices)
297 else:
298 raise FlashError('No removable devices detected.')
299
300 image_path = self._GetImagePath()
301 try:
302 device = self.DeviceNameToPath(target)
303 if self.install:
304 self.InstallImageToDevice(image_path, device)
305 else:
306 self.CopyImageToDevice(image_path, device)
307 except cros_build_lib.RunCommandError:
308 logging.error('Failed copying image to device %s',
309 self.DeviceNameToPath(target))
310
311
312class FileImager(USBImager):
313 """Copy image to the target path."""
314
315 def Run(self):
316 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800317 if not os.path.isdir(os.path.dirname(self.device)):
318 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700319
320 image_path = self._GetImagePath()
321 if os.path.isdir(self.device):
322 logging.info('Copying to %s',
323 os.path.join(self.device, os.path.basename(image_path)))
324 else:
325 logging.info('Copying to %s', self.device)
326 try:
327 shutil.copy(image_path, self.device)
328 except IOError:
329 logging.error('Failed to copy image %s to %s', image_path, self.device)
330
331
332class RemoteDeviceUpdater(object):
333 """Performs update on a remote device."""
334 DEVSERVER_FILENAME = 'devserver.py'
335 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
336 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700337 # Root working directory on the device. This directory is in the
338 # stateful partition and thus has enough space to store the payloads.
339 DEVICE_BASE_DIR = '/mnt/stateful_partition/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700340 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
341 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700342
343 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
344 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700345 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600346 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800347 disable_verification=False, send_payload_in_parallel=False,
348 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700349 """Initializes RemoteDeviceUpdater"""
350 if not stateful_update and not rootfs_update:
351 raise ValueError('No update operation to perform; either stateful or'
352 ' rootfs partitions must be updated.')
353 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
354 self.ssh_hostname = ssh_hostname
355 self.ssh_port = ssh_port
356 self.image = image
357 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700358 self.src_image_to_delta = src_image_to_delta
359 self.do_stateful_update = stateful_update
360 self.do_rootfs_update = rootfs_update
361 self.disable_verification = disable_verification
362 self.clobber_stateful = clobber_stateful
363 self.reboot = reboot
364 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600365 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700366 self.ping = ping
367 # Do not wipe if debug is set.
368 self.wipe = wipe and not debug
369 self.yes = yes
370 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800371 self.send_payload_in_parallel = send_payload_in_parallel
Amin Hassanie62d2e12019-02-01 10:40:41 -0800372 self.experimental_au = experimental_au
David Pursellf1d16a62015-03-25 13:31:04 -0700373
David Pursellf1d16a62015-03-25 13:31:04 -0700374 def Cleanup(self):
375 """Cleans up the temporary directory."""
376 if self.wipe:
377 logging.info('Cleaning up temporary working directory...')
378 osutils.RmDir(self.tempdir)
379 else:
380 logging.info('You can find the log files and/or payloads in %s',
381 self.tempdir)
382
xixuane851dfb2016-05-02 18:02:37 -0700383 def GetPayloadDir(self, device):
384 """Get directory of payload for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700385
xixuane851dfb2016-05-02 18:02:37 -0700386 This method is used to obtain the directory of payload for cros-flash. The
387 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700388
xixuane851dfb2016-05-02 18:02:37 -0700389 If self.image is a directory, we directly use the provided update payload(s)
390 in this directory.
391
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800392 If self.image is an image, we will generate payloads for it and put them in
393 our temporary directory. The reason is that people may modify a local image
394 or override it (on the same path) with a different image, so in order to be
395 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700396
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800397 If non of the above cases, we use the xbuddy to first obtain the image path
398 (and possibly download it). Then we will generate the payloads in the same
399 directory the image is located. The reason is that this is what devserver
400 used to do. The path to the image generated by the devserver (or xbuddy) is
401 unique and normally nobody override its image with a different one. That is
402 why I think it is safe to put the payloads next to the image. This is a poor
403 man's version of caching but it makes cros flash faster for users who flash
404 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700405
406 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400407 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700408
409 Returns:
xixuane851dfb2016-05-02 18:02:37 -0700410 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700411 """
xixuane851dfb2016-05-02 18:02:37 -0700412 if os.path.isdir(self.image):
413 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800414 logging.info('Using provided payloads in %s', self.image)
415 return self.image
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600416
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800417 if os.path.isfile(self.image):
418 # The given path is an image.
419 image_path = self.image
420 payload_dir = self.tempdir
xixuane851dfb2016-05-02 18:02:37 -0700421 else:
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800422 # Assuming it is an xbuddy path.
xixuane851dfb2016-05-02 18:02:37 -0700423 self.board = cros_build_lib.GetBoard(device_board=device.board,
424 override_board=self.board,
425 force=self.yes)
426 if not self.board:
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800427 raise FlashError('No board identified.')
xixuane851dfb2016-05-02 18:02:37 -0700428 if not self.force and self.board != device.board:
429 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700430 raise FlashError('Device (%s) is incompatible with board %s' %
431 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700432 logging.info('Board is %s', self.board)
433
434 # Translate the xbuddy path to get the exact image to use.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800435 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
436 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
437 image_path = ds_wrapper.TranslatedPathToLocalPath(
438 translated_path, DEVSERVER_STATIC_DIR)
439 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
xixuane851dfb2016-05-02 18:02:37 -0700440
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800441 logging.notice('Using image path %s and payload directory %s',
442 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700443
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800444 # Generate rootfs and stateful update payloads if they do not exist.
445 payload_path = os.path.join(payload_dir, ds_wrapper.ROOTFS_FILENAME)
446 if not os.path.exists(payload_path):
447 paygen_payload_lib.GenerateUpdatePayload(
448 image_path, payload_path, src_image=self.src_image_to_delta)
449 if not os.path.exists(os.path.join(payload_dir,
450 ds_wrapper.STATEFUL_FILENAME)):
451 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
452 payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700453 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700454
455 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700456 """Perform remote device update.
457
458 The update process includes:
459 1. initialize a device instance for the given remote device.
460 2. achieve payload_dir which contains the required payloads for updating.
461 3. initialize an auto-updater instance to do RunUpdate().
462 4. After auto-update, all temp files and dir will be cleaned up.
463 """
David Pursellf1d16a62015-03-25 13:31:04 -0700464 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700465 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600466 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
467 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700468
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600469 try:
470 # Get payload directory
471 payload_dir = self.GetPayloadDir(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700472
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600473 # Do auto-update
474 chromeos_AU = auto_updater.ChromiumOSFlashUpdater(
475 device, payload_dir, self.tempdir,
476 do_rootfs_update=self.do_rootfs_update,
477 do_stateful_update=self.do_stateful_update,
478 reboot=self.reboot,
479 disable_verification=self.disable_verification,
480 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800481 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800482 send_payload_in_parallel=self.send_payload_in_parallel,
483 experimental_au=self.experimental_au)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600484 chromeos_AU.CheckPayloads()
485 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700486
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600487 except Exception:
488 logging.error('Device update failed.')
489 lsb_entries = sorted(device.lsb_release.items())
490 logging.info(
491 'Following are the LSB version details of the device:\n%s',
492 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
493 raise
494
495 logging.notice('Update performed successfully.')
496
497 except remote_access.RemoteAccessException:
498 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700499 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600500
David Pursellf1d16a62015-03-25 13:31:04 -0700501 finally:
502 self.Cleanup()
503
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700504def Flash(device, image, board=None, install=False, src_image_to_delta=None,
505 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600506 reboot=True, wipe=True, ssh_private_key=None, ping=True,
507 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800508 force=False, debug=False, send_payload_in_parallel=False,
509 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700510 """Flashes a device, USB drive, or file with an image.
511
512 This provides functionality common to `cros flash` and `brillo flash`
513 so that they can parse the commandline separately but still use the
514 same underlying functionality.
515
516 Args:
David Pursell2e773382015-04-03 14:30:47 -0700517 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700518 image: Path (string) to the update image. Can be a local or xbuddy path;
519 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700520 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700521 install: Install to USB using base disk layout; USB |device| scheme only.
522 src_image_to_delta: Local path to an image to be used as the base to
523 generate delta payloads; SSH |device| scheme only.
524 rootfs_update: Update rootfs partition; SSH |device| scheme only.
525 stateful_update: Update stateful partition; SSH |device| scheme only.
526 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
527 reboot: Reboot device after update; SSH |device| scheme only.
528 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600529 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700530 ping: Ping the device before attempting update; SSH |device| scheme only.
531 disable_rootfs_verification: Remove rootfs verification after update; SSH
532 |device| scheme only.
533 clear_cache: Clear the devserver static directory.
534 yes: Assume "yes" for any prompt.
535 force: Ignore sanity checks and prompts. Overrides |yes| if True.
536 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800537 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
538 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800539 experimental_au: Use the experimental features auto updater. It should be
540 deprecated once crbug.com/872441 is fixed.
David Pursellf1d16a62015-03-25 13:31:04 -0700541
542 Raises:
543 FlashError: An unrecoverable error occured.
544 ValueError: Invalid parameter combination.
545 """
546 if force:
547 yes = True
548
549 if clear_cache:
550 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800551 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700552
553 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800554 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700555 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800556 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700557
558 if install:
David Pursell2e773382015-04-03 14:30:47 -0700559 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700560 raise ValueError(
561 '--install can only be used when writing to a USB device')
562 if not cros_build_lib.IsInsideChroot():
563 raise ValueError('--install can only be used inside the chroot')
564
David Pursell2e773382015-04-03 14:30:47 -0700565 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
566 if device:
567 hostname, port = device.hostname, device.port
568 else:
569 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700570 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700571 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700572 hostname,
573 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700574 image,
575 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700576 src_image_to_delta=src_image_to_delta,
577 rootfs_update=rootfs_update,
578 stateful_update=stateful_update,
579 clobber_stateful=clobber_stateful,
580 reboot=reboot,
581 wipe=wipe,
582 debug=debug,
583 yes=yes,
584 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600585 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700586 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800587 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800588 send_payload_in_parallel=send_payload_in_parallel,
589 experimental_au=experimental_au)
David Pursellf1d16a62015-03-25 13:31:04 -0700590 updater.Run()
591 elif device.scheme == commandline.DEVICE_SCHEME_USB:
592 path = osutils.ExpandPath(device.path) if device.path else ''
593 logging.info('Preparing to image the removable device %s', path)
594 imager = USBImager(path,
595 board,
596 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700597 debug=debug,
598 install=install,
599 yes=yes)
600 imager.Run()
601 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
602 logging.info('Preparing to copy image to %s', device.path)
603 imager = FileImager(device.path,
604 board,
605 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700606 debug=debug,
607 yes=yes)
608 imager.Run()