blob: 409e9b2d53ff6ae8bd084d7e8cd0d32d9105a586 [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
26
Don Garrett97d7dc22015-01-20 14:07:56 -080027DEVSERVER_STATIC_DIR = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -070028 os.path.join(constants.CHROOT_SOURCE_ROOT, 'devserver', 'static'))
29
30
Ralph Nathan9b997232015-05-15 13:13:12 -070031class UsbImagerOperation(operation.ProgressBarOperation):
32 """Progress bar for flashing image to operation."""
33
34 def __init__(self, image):
35 super(UsbImagerOperation, self).__init__()
36 self._size = os.path.getsize(image)
37 self._transferred = 0.
38 self._bytes = re.compile(r'(\d+) bytes')
39
40 def _GetDDPid(self):
41 """Get the Pid of dd."""
42 try:
43 pids = cros_build_lib.RunCommand(['pgrep', 'dd'], capture_output=True,
44 print_cmd=False).output
45 for pid in pids.splitlines():
46 if osutils.IsChildProcess(int(pid), name='dd'):
47 return int(pid)
48 return -1
49 except cros_build_lib.RunCommandError:
50 # If dd isn't still running, then we assume that it is finished.
51 return -1
52
53 def _PingDD(self, dd_pid):
54 """Send USR1 signal to dd to get status update."""
55 try:
56 cmd = ['kill', '-USR1', str(dd_pid)]
57 cros_build_lib.SudoRunCommand(cmd, print_cmd=False)
58 except cros_build_lib.RunCommandError:
59 # Here we assume that dd finished in the background.
60 return
61
62 def ParseOutput(self, output=None):
63 """Parse the output of dd to update progress bar."""
64 dd_pid = self._GetDDPid()
65 if dd_pid == -1:
66 return
67
68 self._PingDD(dd_pid)
69
70 if output is None:
71 stdout = self._stdout.read()
72 stderr = self._stderr.read()
73 output = stdout + stderr
74
75 match = self._bytes.search(output)
76 if match:
77 self._transferred = match.groups()[0]
78
79 self.ProgressBar(float(self._transferred) / self._size)
80
81
Mike Frysinger32759e42016-12-21 18:40:16 -050082def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070083 """Determines if a file is a valid GPT disk.
84
85 Args:
86 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -050087 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070088 """
89 if os.path.isfile(file_path):
Mike Frysinger32759e42016-12-21 18:40:16 -050090 with open(file_path) as image_file:
91 if require_pmbr:
92 # Seek to the end of LBA0 and look for the PMBR boot signature.
93 image_file.seek(0x1fe)
94 if image_file.read(2) != '\x55\xaa':
95 return False
96 # Current file position is start of LBA1 now.
97 else:
98 # Seek to LBA1 where the GPT starts.
99 image_file.seek(0x200)
100
101 # See if there's a GPT here.
102 if image_file.read(8) == 'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700103 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500104
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700105 return False
106
107
108def _ChooseImageFromDirectory(dir_path):
109 """Lists all image files in |dir_path| and ask user to select one.
110
111 Args:
112 dir_path: Path to the directory.
113 """
114 images = sorted([x for x in os.listdir(dir_path) if
115 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
116 idx = 0
117 if len(images) == 0:
118 raise ValueError('No image found in %s.' % dir_path)
119 elif len(images) > 1:
120 idx = cros_build_lib.GetChoice(
121 'Multiple images found in %s. Please select one to continue:' % (
122 (dir_path,)),
123 images)
124
125 return os.path.join(dir_path, images[idx])
126
127
David Pursellf1d16a62015-03-25 13:31:04 -0700128class FlashError(Exception):
129 """Thrown when there is an unrecoverable error during flash."""
130
131
132class USBImager(object):
133 """Copy image to the target removable device."""
134
Gilad Arnoldd0461442015-07-07 11:52:46 -0700135 def __init__(self, device, board, image, debug=False, install=False,
136 yes=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700137 """Initalizes USBImager."""
138 self.device = device
139 self.board = board if board else cros_build_lib.GetDefaultBoard()
140 self.image = image
David Pursellf1d16a62015-03-25 13:31:04 -0700141 self.debug = debug
142 self.debug_level = logging.DEBUG if debug else logging.INFO
143 self.install = install
144 self.yes = yes
145
146 def DeviceNameToPath(self, device_name):
147 return '/dev/%s' % device_name
148
149 def GetRemovableDeviceDescription(self, device):
150 """Returns a informational description of the removable |device|.
151
152 Args:
153 device: the device name (e.g. sdc).
154
155 Returns:
156 A string describing |device| (e.g. Patriot Memory 7918 MB).
157 """
158 desc = [
159 osutils.GetDeviceInfo(device, keyword='manufacturer'),
160 osutils.GetDeviceInfo(device, keyword='product'),
161 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
162 '(%s)' % self.DeviceNameToPath(device),
163 ]
164 return ' '.join([x for x in desc if x])
165
166 def ListAllRemovableDevices(self):
167 """Returns a list of removable devices.
168
169 Returns:
170 A list of device names (e.g. ['sdb', 'sdc']).
171 """
172 devices = osutils.ListBlockDevices()
173 removable_devices = []
174 for d in devices:
175 if d.TYPE == 'disk' and d.RM == '1':
176 removable_devices.append(d.NAME)
177
178 return removable_devices
179
180 def ChooseRemovableDevice(self, devices):
181 """Lists all removable devices and asks user to select/confirm.
182
183 Args:
184 devices: a list of device names (e.g. ['sda', 'sdb']).
185
186 Returns:
187 The device name chosen by the user.
188 """
189 idx = cros_build_lib.GetChoice(
190 'Removable device(s) found. Please select/confirm to continue:',
191 [self.GetRemovableDeviceDescription(x) for x in devices])
192
193 return devices[idx]
194
195 def InstallImageToDevice(self, image, device):
196 """Installs |image| to the removable |device|.
197
198 Args:
199 image: Path to the image to copy.
200 device: Device to copy to.
201 """
202 cmd = [
203 'chromeos-install',
204 '--yes',
205 '--skip_src_removable',
206 '--skip_dst_removable',
207 '--payload_image=%s' % image,
208 '--dst=%s' % device,
209 '--skip_postinstall',
210 ]
Shuqian Zhao33242472017-08-02 18:28:44 -0700211 cros_build_lib.SudoRunCommand(cmd,
212 print_cmd=True,
213 debug_level=logging.NOTICE,
214 combine_stdout_stderr=True,
215 log_output=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700216
217 def CopyImageToDevice(self, image, device):
218 """Copies |image| to the removable |device|.
219
220 Args:
221 image: Path to the image to copy.
222 device: Device to copy to.
223 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700224 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
225 'oflag=sync']
226 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
227 op = UsbImagerOperation(image)
228 op.Run(cros_build_lib.SudoRunCommand, cmd, debug_level=logging.NOTICE,
229 update_period=0.5)
230 else:
231 cros_build_lib.SudoRunCommand(
232 cmd, debug_level=logging.NOTICE,
233 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700234
Brian Norris6386fde2018-10-29 13:34:28 -0700235 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
236 # up for us with a 'write' command, so we have a standards-conforming GPT.
237 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
238 # fix GPT sanity issues.
239 cros_build_lib.SudoRunCommand(['sfdisk', device], input='write\n',
240 error_code_ok=True,
Brian Norrisb4d99982018-10-16 16:02:49 -0700241 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700242
David Pursellf1d16a62015-03-25 13:31:04 -0700243 cros_build_lib.SudoRunCommand(['sync'], debug_level=self.debug_level)
244
David Pursellf1d16a62015-03-25 13:31:04 -0700245 def _GetImagePath(self):
246 """Returns the image path to use."""
247 image_path = translated_path = None
248 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700249 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700250 # TODO(wnwen): Open the tarball and if there is just one file in it,
251 # use that instead. Existing code in upload_symbols.py.
252 if cros_build_lib.BooleanPrompt(
253 prolog='The given image file is not a valid disk image. Perhaps '
254 'you forgot to untar it.',
255 prompt='Terminate the current flash process?'):
256 raise FlashError('Update terminated by user.')
257 image_path = self.image
258 elif os.path.isdir(self.image):
259 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700260 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700261 else:
262 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700263 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Don Garrett97d7dc22015-01-20 14:07:56 -0800264 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700265 image_path = ds_wrapper.TranslatedPathToLocalPath(
Don Garrett97d7dc22015-01-20 14:07:56 -0800266 translated_path, DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700267
268 logging.info('Using image %s', translated_path or image_path)
269 return image_path
270
271 def Run(self):
272 """Image the removable device."""
273 devices = self.ListAllRemovableDevices()
274
275 if self.device:
276 # If user specified a device path, check if it exists.
277 if not os.path.exists(self.device):
278 raise FlashError('Device path %s does not exist.' % self.device)
279
280 # Then check if it is removable.
281 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
282 msg = '%s is not a removable device.' % self.device
283 if not (self.yes or cros_build_lib.BooleanPrompt(
284 default=False, prolog=msg)):
285 raise FlashError('You can specify usb:// to choose from a list of '
286 'removable devices.')
287 target = None
288 if self.device:
289 # Get device name from path (e.g. sdc in /dev/sdc).
290 target = self.device.rsplit(os.path.sep, 1)[-1]
291 elif devices:
292 # Ask user to choose from the list.
293 target = self.ChooseRemovableDevice(devices)
294 else:
295 raise FlashError('No removable devices detected.')
296
297 image_path = self._GetImagePath()
298 try:
299 device = self.DeviceNameToPath(target)
300 if self.install:
301 self.InstallImageToDevice(image_path, device)
302 else:
303 self.CopyImageToDevice(image_path, device)
304 except cros_build_lib.RunCommandError:
305 logging.error('Failed copying image to device %s',
306 self.DeviceNameToPath(target))
307
308
309class FileImager(USBImager):
310 """Copy image to the target path."""
311
312 def Run(self):
313 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800314 if not os.path.isdir(os.path.dirname(self.device)):
315 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700316
317 image_path = self._GetImagePath()
318 if os.path.isdir(self.device):
319 logging.info('Copying to %s',
320 os.path.join(self.device, os.path.basename(image_path)))
321 else:
322 logging.info('Copying to %s', self.device)
323 try:
324 shutil.copy(image_path, self.device)
325 except IOError:
326 logging.error('Failed to copy image %s to %s', image_path, self.device)
327
328
329class RemoteDeviceUpdater(object):
330 """Performs update on a remote device."""
331 DEVSERVER_FILENAME = 'devserver.py'
332 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
333 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700334 # Root working directory on the device. This directory is in the
335 # stateful partition and thus has enough space to store the payloads.
336 DEVICE_BASE_DIR = '/mnt/stateful_partition/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700337 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
338 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700339
340 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
341 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700342 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600343 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800344 disable_verification=False, send_payload_in_parallel=False,
345 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700346 """Initializes RemoteDeviceUpdater"""
347 if not stateful_update and not rootfs_update:
348 raise ValueError('No update operation to perform; either stateful or'
349 ' rootfs partitions must be updated.')
350 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
351 self.ssh_hostname = ssh_hostname
352 self.ssh_port = ssh_port
353 self.image = image
354 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700355 self.src_image_to_delta = src_image_to_delta
356 self.do_stateful_update = stateful_update
357 self.do_rootfs_update = rootfs_update
358 self.disable_verification = disable_verification
359 self.clobber_stateful = clobber_stateful
360 self.reboot = reboot
361 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600362 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700363 self.ping = ping
364 # Do not wipe if debug is set.
365 self.wipe = wipe and not debug
366 self.yes = yes
367 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800368 self.send_payload_in_parallel = send_payload_in_parallel
Amin Hassanie62d2e12019-02-01 10:40:41 -0800369 self.experimental_au = experimental_au
David Pursellf1d16a62015-03-25 13:31:04 -0700370
David Pursellf1d16a62015-03-25 13:31:04 -0700371 def Cleanup(self):
372 """Cleans up the temporary directory."""
373 if self.wipe:
374 logging.info('Cleaning up temporary working directory...')
375 osutils.RmDir(self.tempdir)
376 else:
377 logging.info('You can find the log files and/or payloads in %s',
378 self.tempdir)
379
xixuane851dfb2016-05-02 18:02:37 -0700380 def GetPayloadDir(self, device):
381 """Get directory of payload for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700382
xixuane851dfb2016-05-02 18:02:37 -0700383 This method is used to obtain the directory of payload for cros-flash. The
384 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700385
xixuane851dfb2016-05-02 18:02:37 -0700386 If self.image is a directory, we directly use the provided update payload(s)
387 in this directory.
388
389 If self.image is an image, let devserver access it and generate payloads.
390
391 If not in the above cases, let devserver first obtain the image path. Then
392 devserver will access the image and generate payloads.
David Pursellf1d16a62015-03-25 13:31:04 -0700393
394 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400395 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700396
397 Returns:
xixuane851dfb2016-05-02 18:02:37 -0700398 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700399 """
xixuane851dfb2016-05-02 18:02:37 -0700400 payload_dir = self.tempdir
David Pursellf1d16a62015-03-25 13:31:04 -0700401
xixuane851dfb2016-05-02 18:02:37 -0700402 if os.path.isdir(self.image):
403 # The given path is a directory.
404 payload_dir = self.image
405 logging.info('Using provided payloads in %s', payload_dir)
406 elif os.path.isfile(self.image):
407 # The given path is an image.
408 logging.info('Using image %s', self.image)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600409 try:
410 ds_wrapper.GetUpdatePayloadsFromLocalPath(
411 self.image, payload_dir,
412 src_image_to_delta=self.src_image_to_delta,
413 static_dir=DEVSERVER_STATIC_DIR)
414 except:
415 logging.error('Unable to get payloads from local path: %s', payload_dir)
416 raise
417
xixuane851dfb2016-05-02 18:02:37 -0700418 else:
419 self.board = cros_build_lib.GetBoard(device_board=device.board,
420 override_board=self.board,
421 force=self.yes)
422 if not self.board:
423 raise FlashError('No board identified')
David Pursellf1d16a62015-03-25 13:31:04 -0700424
xixuane851dfb2016-05-02 18:02:37 -0700425 if not self.force and self.board != device.board:
426 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700427 raise FlashError('Device (%s) is incompatible with board %s' %
428 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700429
430 logging.info('Board is %s', self.board)
431
432 # Translate the xbuddy path to get the exact image to use.
433 translated_path, resolved_path = ds_wrapper.GetImagePathWithXbuddy(
434 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR,
435 lookup_only=True)
Vadim Bendebury3c993462017-08-22 15:42:16 -0700436 logging.notice('Using image %s', translated_path)
xixuane851dfb2016-05-02 18:02:37 -0700437 # Convert the translated path to be used in the update request.
438 image_path = ds_wrapper.ConvertTranslatedPath(resolved_path,
439 translated_path)
440
441 # Launch a local devserver to generate/serve update payloads.
442 ds_wrapper.GetUpdatePayloads(
443 image_path, payload_dir, board=self.board,
444 src_image_to_delta=self.src_image_to_delta,
445 static_dir=DEVSERVER_STATIC_DIR)
446
447 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700448
449 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700450 """Perform remote device update.
451
452 The update process includes:
453 1. initialize a device instance for the given remote device.
454 2. achieve payload_dir which contains the required payloads for updating.
455 3. initialize an auto-updater instance to do RunUpdate().
456 4. After auto-update, all temp files and dir will be cleaned up.
457 """
David Pursellf1d16a62015-03-25 13:31:04 -0700458 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700459 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600460 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
461 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700462
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600463 try:
464 # Get payload directory
465 payload_dir = self.GetPayloadDir(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700466
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600467 # Do auto-update
468 chromeos_AU = auto_updater.ChromiumOSFlashUpdater(
469 device, payload_dir, self.tempdir,
470 do_rootfs_update=self.do_rootfs_update,
471 do_stateful_update=self.do_stateful_update,
472 reboot=self.reboot,
473 disable_verification=self.disable_verification,
474 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800475 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800476 send_payload_in_parallel=self.send_payload_in_parallel,
477 experimental_au=self.experimental_au)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600478 chromeos_AU.CheckPayloads()
479 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700480
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600481 except Exception:
482 logging.error('Device update failed.')
483 lsb_entries = sorted(device.lsb_release.items())
484 logging.info(
485 'Following are the LSB version details of the device:\n%s',
486 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
487 raise
488
489 logging.notice('Update performed successfully.')
490
491 except remote_access.RemoteAccessException:
492 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700493 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600494
David Pursellf1d16a62015-03-25 13:31:04 -0700495 finally:
496 self.Cleanup()
497
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700498def Flash(device, image, board=None, install=False, src_image_to_delta=None,
499 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600500 reboot=True, wipe=True, ssh_private_key=None, ping=True,
501 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800502 force=False, debug=False, send_payload_in_parallel=False,
503 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700504 """Flashes a device, USB drive, or file with an image.
505
506 This provides functionality common to `cros flash` and `brillo flash`
507 so that they can parse the commandline separately but still use the
508 same underlying functionality.
509
510 Args:
David Pursell2e773382015-04-03 14:30:47 -0700511 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700512 image: Path (string) to the update image. Can be a local or xbuddy path;
513 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700514 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700515 install: Install to USB using base disk layout; USB |device| scheme only.
516 src_image_to_delta: Local path to an image to be used as the base to
517 generate delta payloads; SSH |device| scheme only.
518 rootfs_update: Update rootfs partition; SSH |device| scheme only.
519 stateful_update: Update stateful partition; SSH |device| scheme only.
520 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
521 reboot: Reboot device after update; SSH |device| scheme only.
522 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600523 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700524 ping: Ping the device before attempting update; SSH |device| scheme only.
525 disable_rootfs_verification: Remove rootfs verification after update; SSH
526 |device| scheme only.
527 clear_cache: Clear the devserver static directory.
528 yes: Assume "yes" for any prompt.
529 force: Ignore sanity checks and prompts. Overrides |yes| if True.
530 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800531 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
532 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800533 experimental_au: Use the experimental features auto updater. It should be
534 deprecated once crbug.com/872441 is fixed.
David Pursellf1d16a62015-03-25 13:31:04 -0700535
536 Raises:
537 FlashError: An unrecoverable error occured.
538 ValueError: Invalid parameter combination.
539 """
540 if force:
541 yes = True
542
543 if clear_cache:
544 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800545 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700546
547 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800548 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700549 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800550 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700551
552 if install:
David Pursell2e773382015-04-03 14:30:47 -0700553 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700554 raise ValueError(
555 '--install can only be used when writing to a USB device')
556 if not cros_build_lib.IsInsideChroot():
557 raise ValueError('--install can only be used inside the chroot')
558
David Pursell2e773382015-04-03 14:30:47 -0700559 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
560 if device:
561 hostname, port = device.hostname, device.port
562 else:
563 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700564 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700565 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700566 hostname,
567 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700568 image,
569 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700570 src_image_to_delta=src_image_to_delta,
571 rootfs_update=rootfs_update,
572 stateful_update=stateful_update,
573 clobber_stateful=clobber_stateful,
574 reboot=reboot,
575 wipe=wipe,
576 debug=debug,
577 yes=yes,
578 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600579 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700580 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800581 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800582 send_payload_in_parallel=send_payload_in_parallel,
583 experimental_au=experimental_au)
David Pursellf1d16a62015-03-25 13:31:04 -0700584 updater.Run()
585 elif device.scheme == commandline.DEVICE_SCHEME_USB:
586 path = osutils.ExpandPath(device.path) if device.path else ''
587 logging.info('Preparing to image the removable device %s', path)
588 imager = USBImager(path,
589 board,
590 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700591 debug=debug,
592 install=install,
593 yes=yes)
594 imager.Run()
595 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
596 logging.info('Preparing to copy image to %s', device.path)
597 imager = FileImager(device.path,
598 board,
599 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700600 debug=debug,
601 yes=yes)
602 imager.Run()