blob: 4f2c488ae3d3bec880b3216d5b798398c33e591a [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,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800344 disable_verification=False, send_payload_in_parallel=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700345 """Initializes RemoteDeviceUpdater"""
346 if not stateful_update and not rootfs_update:
347 raise ValueError('No update operation to perform; either stateful or'
348 ' rootfs partitions must be updated.')
349 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
350 self.ssh_hostname = ssh_hostname
351 self.ssh_port = ssh_port
352 self.image = image
353 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700354 self.src_image_to_delta = src_image_to_delta
355 self.do_stateful_update = stateful_update
356 self.do_rootfs_update = rootfs_update
357 self.disable_verification = disable_verification
358 self.clobber_stateful = clobber_stateful
359 self.reboot = reboot
360 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600361 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700362 self.ping = ping
363 # Do not wipe if debug is set.
364 self.wipe = wipe and not debug
365 self.yes = yes
366 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800367 self.send_payload_in_parallel = send_payload_in_parallel
David Pursellf1d16a62015-03-25 13:31:04 -0700368
David Pursellf1d16a62015-03-25 13:31:04 -0700369 def Cleanup(self):
370 """Cleans up the temporary directory."""
371 if self.wipe:
372 logging.info('Cleaning up temporary working directory...')
373 osutils.RmDir(self.tempdir)
374 else:
375 logging.info('You can find the log files and/or payloads in %s',
376 self.tempdir)
377
xixuane851dfb2016-05-02 18:02:37 -0700378 def GetPayloadDir(self, device):
379 """Get directory of payload for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700380
xixuane851dfb2016-05-02 18:02:37 -0700381 This method is used to obtain the directory of payload for cros-flash. The
382 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700383
xixuane851dfb2016-05-02 18:02:37 -0700384 If self.image is a directory, we directly use the provided update payload(s)
385 in this directory.
386
387 If self.image is an image, let devserver access it and generate payloads.
388
389 If not in the above cases, let devserver first obtain the image path. Then
390 devserver will access the image and generate payloads.
David Pursellf1d16a62015-03-25 13:31:04 -0700391
392 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400393 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700394
395 Returns:
xixuane851dfb2016-05-02 18:02:37 -0700396 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700397 """
xixuane851dfb2016-05-02 18:02:37 -0700398 payload_dir = self.tempdir
David Pursellf1d16a62015-03-25 13:31:04 -0700399
xixuane851dfb2016-05-02 18:02:37 -0700400 if os.path.isdir(self.image):
401 # The given path is a directory.
402 payload_dir = self.image
403 logging.info('Using provided payloads in %s', payload_dir)
404 elif os.path.isfile(self.image):
405 # The given path is an image.
406 logging.info('Using image %s', self.image)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600407 try:
408 ds_wrapper.GetUpdatePayloadsFromLocalPath(
409 self.image, payload_dir,
410 src_image_to_delta=self.src_image_to_delta,
411 static_dir=DEVSERVER_STATIC_DIR)
412 except:
413 logging.error('Unable to get payloads from local path: %s', payload_dir)
414 raise
415
xixuane851dfb2016-05-02 18:02:37 -0700416 else:
417 self.board = cros_build_lib.GetBoard(device_board=device.board,
418 override_board=self.board,
419 force=self.yes)
420 if not self.board:
421 raise FlashError('No board identified')
David Pursellf1d16a62015-03-25 13:31:04 -0700422
xixuane851dfb2016-05-02 18:02:37 -0700423 if not self.force and self.board != device.board:
424 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700425 raise FlashError('Device (%s) is incompatible with board %s' %
426 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700427
428 logging.info('Board is %s', self.board)
429
430 # Translate the xbuddy path to get the exact image to use.
431 translated_path, resolved_path = ds_wrapper.GetImagePathWithXbuddy(
432 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR,
433 lookup_only=True)
Vadim Bendebury3c993462017-08-22 15:42:16 -0700434 logging.notice('Using image %s', translated_path)
xixuane851dfb2016-05-02 18:02:37 -0700435 # Convert the translated path to be used in the update request.
436 image_path = ds_wrapper.ConvertTranslatedPath(resolved_path,
437 translated_path)
438
439 # Launch a local devserver to generate/serve update payloads.
440 ds_wrapper.GetUpdatePayloads(
441 image_path, payload_dir, board=self.board,
442 src_image_to_delta=self.src_image_to_delta,
443 static_dir=DEVSERVER_STATIC_DIR)
444
445 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700446
447 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700448 """Perform remote device update.
449
450 The update process includes:
451 1. initialize a device instance for the given remote device.
452 2. achieve payload_dir which contains the required payloads for updating.
453 3. initialize an auto-updater instance to do RunUpdate().
454 4. After auto-update, all temp files and dir will be cleaned up.
455 """
David Pursellf1d16a62015-03-25 13:31:04 -0700456 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700457 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600458 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
459 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700460
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600461 try:
462 # Get payload directory
463 payload_dir = self.GetPayloadDir(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700464
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600465 # Do auto-update
466 chromeos_AU = auto_updater.ChromiumOSFlashUpdater(
467 device, payload_dir, self.tempdir,
468 do_rootfs_update=self.do_rootfs_update,
469 do_stateful_update=self.do_stateful_update,
470 reboot=self.reboot,
471 disable_verification=self.disable_verification,
472 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800473 yes=self.yes,
474 send_payload_in_parallel=self.send_payload_in_parallel)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600475 chromeos_AU.CheckPayloads()
476 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700477
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600478 except Exception:
479 logging.error('Device update failed.')
480 lsb_entries = sorted(device.lsb_release.items())
481 logging.info(
482 'Following are the LSB version details of the device:\n%s',
483 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
484 raise
485
486 logging.notice('Update performed successfully.')
487
488 except remote_access.RemoteAccessException:
489 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700490 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600491
David Pursellf1d16a62015-03-25 13:31:04 -0700492 finally:
493 self.Cleanup()
494
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700495def Flash(device, image, board=None, install=False, src_image_to_delta=None,
496 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600497 reboot=True, wipe=True, ssh_private_key=None, ping=True,
498 disable_rootfs_verification=False, clear_cache=False, yes=False,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800499 force=False, debug=False, send_payload_in_parallel=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700500 """Flashes a device, USB drive, or file with an image.
501
502 This provides functionality common to `cros flash` and `brillo flash`
503 so that they can parse the commandline separately but still use the
504 same underlying functionality.
505
506 Args:
David Pursell2e773382015-04-03 14:30:47 -0700507 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700508 image: Path (string) to the update image. Can be a local or xbuddy path;
509 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700510 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700511 install: Install to USB using base disk layout; USB |device| scheme only.
512 src_image_to_delta: Local path to an image to be used as the base to
513 generate delta payloads; SSH |device| scheme only.
514 rootfs_update: Update rootfs partition; SSH |device| scheme only.
515 stateful_update: Update stateful partition; SSH |device| scheme only.
516 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
517 reboot: Reboot device after update; SSH |device| scheme only.
518 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600519 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700520 ping: Ping the device before attempting update; SSH |device| scheme only.
521 disable_rootfs_verification: Remove rootfs verification after update; SSH
522 |device| scheme only.
523 clear_cache: Clear the devserver static directory.
524 yes: Assume "yes" for any prompt.
525 force: Ignore sanity checks and prompts. Overrides |yes| if True.
526 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800527 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
528 up transmissions for long haul between endpoints.
David Pursellf1d16a62015-03-25 13:31:04 -0700529
530 Raises:
531 FlashError: An unrecoverable error occured.
532 ValueError: Invalid parameter combination.
533 """
534 if force:
535 yes = True
536
537 if clear_cache:
538 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800539 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700540
541 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800542 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700543 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800544 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700545
546 if install:
David Pursell2e773382015-04-03 14:30:47 -0700547 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700548 raise ValueError(
549 '--install can only be used when writing to a USB device')
550 if not cros_build_lib.IsInsideChroot():
551 raise ValueError('--install can only be used inside the chroot')
552
David Pursell2e773382015-04-03 14:30:47 -0700553 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
554 if device:
555 hostname, port = device.hostname, device.port
556 else:
557 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700558 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700559 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700560 hostname,
561 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700562 image,
563 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700564 src_image_to_delta=src_image_to_delta,
565 rootfs_update=rootfs_update,
566 stateful_update=stateful_update,
567 clobber_stateful=clobber_stateful,
568 reboot=reboot,
569 wipe=wipe,
570 debug=debug,
571 yes=yes,
572 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600573 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700574 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800575 disable_verification=disable_rootfs_verification,
576 send_payload_in_parallel=send_payload_in_parallel)
David Pursellf1d16a62015-03-25 13:31:04 -0700577 updater.Run()
578 elif device.scheme == commandline.DEVICE_SCHEME_USB:
579 path = osutils.ExpandPath(device.path) if device.path else ''
580 logging.info('Preparing to image the removable device %s', path)
581 imager = USBImager(path,
582 board,
583 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700584 debug=debug,
585 install=install,
586 yes=yes)
587 imager.Run()
588 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
589 logging.info('Preparing to copy image to %s', device.path)
590 imager = FileImager(device.path,
591 board,
592 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700593 debug=debug,
594 yes=yes)
595 imager.Run()