blob: 10b5bfd1e38ea11c8b0ecac35c4e364c0818a14f [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
14import tempfile
David Pursellf1d16a62015-03-25 13:31:04 -070015
Aviv Keshetb7519e12016-10-04 00:50:00 -070016from chromite.lib import constants
xixuane851dfb2016-05-02 18:02:37 -070017from chromite.lib import auto_updater
David Pursellf1d16a62015-03-25 13:31:04 -070018from chromite.lib import commandline
19from chromite.lib import cros_build_lib
20from chromite.lib import cros_logging as logging
21from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070022from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070023from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070024from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070025from chromite.lib import remote_access
26
Amin Hassanic0f06fa2019-01-28 15:24:47 -080027from chromite.lib.paygen import paygen_payload_lib
28from chromite.lib.paygen import paygen_stateful_payload_lib
29
David Pursellf1d16a62015-03-25 13:31:04 -070030
Don Garrett97d7dc22015-01-20 14:07:56 -080031DEVSERVER_STATIC_DIR = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -070032 os.path.join(constants.CHROOT_SOURCE_ROOT, 'devserver', 'static'))
33
34
Ralph Nathan9b997232015-05-15 13:13:12 -070035class UsbImagerOperation(operation.ProgressBarOperation):
36 """Progress bar for flashing image to operation."""
37
38 def __init__(self, image):
39 super(UsbImagerOperation, self).__init__()
40 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070041 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070042 self._bytes = re.compile(r'(\d+) bytes')
43
44 def _GetDDPid(self):
45 """Get the Pid of dd."""
46 try:
47 pids = cros_build_lib.RunCommand(['pgrep', 'dd'], capture_output=True,
48 print_cmd=False).output
49 for pid in pids.splitlines():
50 if osutils.IsChildProcess(int(pid), name='dd'):
51 return int(pid)
52 return -1
53 except cros_build_lib.RunCommandError:
54 # If dd isn't still running, then we assume that it is finished.
55 return -1
56
57 def _PingDD(self, dd_pid):
58 """Send USR1 signal to dd to get status update."""
59 try:
60 cmd = ['kill', '-USR1', str(dd_pid)]
61 cros_build_lib.SudoRunCommand(cmd, print_cmd=False)
62 except cros_build_lib.RunCommandError:
63 # Here we assume that dd finished in the background.
64 return
65
66 def ParseOutput(self, output=None):
67 """Parse the output of dd to update progress bar."""
68 dd_pid = self._GetDDPid()
69 if dd_pid == -1:
70 return
71
72 self._PingDD(dd_pid)
73
74 if output is None:
75 stdout = self._stdout.read()
76 stderr = self._stderr.read()
77 output = stdout + stderr
78
79 match = self._bytes.search(output)
80 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070081 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -070082
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040083 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -070084
85
Mike Frysinger32759e42016-12-21 18:40:16 -050086def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070087 """Determines if a file is a valid GPT disk.
88
89 Args:
90 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -050091 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070092 """
93 if os.path.isfile(file_path):
Mike Frysinger32759e42016-12-21 18:40:16 -050094 with open(file_path) as image_file:
95 if require_pmbr:
96 # Seek to the end of LBA0 and look for the PMBR boot signature.
97 image_file.seek(0x1fe)
98 if image_file.read(2) != '\x55\xaa':
99 return False
100 # Current file position is start of LBA1 now.
101 else:
102 # Seek to LBA1 where the GPT starts.
103 image_file.seek(0x200)
104
105 # See if there's a GPT here.
106 if image_file.read(8) == 'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700107 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500108
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700109 return False
110
111
112def _ChooseImageFromDirectory(dir_path):
113 """Lists all image files in |dir_path| and ask user to select one.
114
115 Args:
116 dir_path: Path to the directory.
117 """
118 images = sorted([x for x in os.listdir(dir_path) if
119 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
120 idx = 0
121 if len(images) == 0:
122 raise ValueError('No image found in %s.' % dir_path)
123 elif len(images) > 1:
124 idx = cros_build_lib.GetChoice(
125 'Multiple images found in %s. Please select one to continue:' % (
126 (dir_path,)),
127 images)
128
129 return os.path.join(dir_path, images[idx])
130
131
David Pursellf1d16a62015-03-25 13:31:04 -0700132class FlashError(Exception):
133 """Thrown when there is an unrecoverable error during flash."""
134
135
136class USBImager(object):
137 """Copy image to the target removable device."""
138
Gilad Arnoldd0461442015-07-07 11:52:46 -0700139 def __init__(self, device, board, image, debug=False, install=False,
140 yes=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700141 """Initalizes USBImager."""
142 self.device = device
143 self.board = board if board else cros_build_lib.GetDefaultBoard()
144 self.image = image
David Pursellf1d16a62015-03-25 13:31:04 -0700145 self.debug = debug
146 self.debug_level = logging.DEBUG if debug else logging.INFO
147 self.install = install
148 self.yes = yes
149
150 def DeviceNameToPath(self, device_name):
151 return '/dev/%s' % device_name
152
153 def GetRemovableDeviceDescription(self, device):
154 """Returns a informational description of the removable |device|.
155
156 Args:
157 device: the device name (e.g. sdc).
158
159 Returns:
160 A string describing |device| (e.g. Patriot Memory 7918 MB).
161 """
162 desc = [
163 osutils.GetDeviceInfo(device, keyword='manufacturer'),
164 osutils.GetDeviceInfo(device, keyword='product'),
165 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
166 '(%s)' % self.DeviceNameToPath(device),
167 ]
168 return ' '.join([x for x in desc if x])
169
170 def ListAllRemovableDevices(self):
171 """Returns a list of removable devices.
172
173 Returns:
174 A list of device names (e.g. ['sdb', 'sdc']).
175 """
176 devices = osutils.ListBlockDevices()
177 removable_devices = []
178 for d in devices:
179 if d.TYPE == 'disk' and d.RM == '1':
180 removable_devices.append(d.NAME)
181
182 return removable_devices
183
184 def ChooseRemovableDevice(self, devices):
185 """Lists all removable devices and asks user to select/confirm.
186
187 Args:
188 devices: a list of device names (e.g. ['sda', 'sdb']).
189
190 Returns:
191 The device name chosen by the user.
192 """
193 idx = cros_build_lib.GetChoice(
194 'Removable device(s) found. Please select/confirm to continue:',
195 [self.GetRemovableDeviceDescription(x) for x in devices])
196
197 return devices[idx]
198
199 def InstallImageToDevice(self, image, device):
200 """Installs |image| to the removable |device|.
201
202 Args:
203 image: Path to the image to copy.
204 device: Device to copy to.
205 """
206 cmd = [
207 'chromeos-install',
208 '--yes',
209 '--skip_src_removable',
210 '--skip_dst_removable',
211 '--payload_image=%s' % image,
212 '--dst=%s' % device,
213 '--skip_postinstall',
214 ]
Shuqian Zhao33242472017-08-02 18:28:44 -0700215 cros_build_lib.SudoRunCommand(cmd,
216 print_cmd=True,
217 debug_level=logging.NOTICE,
218 combine_stdout_stderr=True,
219 log_output=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700220
221 def CopyImageToDevice(self, image, device):
222 """Copies |image| to the removable |device|.
223
224 Args:
225 image: Path to the image to copy.
226 device: Device to copy to.
227 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700228 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
229 'oflag=sync']
230 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
231 op = UsbImagerOperation(image)
232 op.Run(cros_build_lib.SudoRunCommand, cmd, debug_level=logging.NOTICE,
233 update_period=0.5)
234 else:
235 cros_build_lib.SudoRunCommand(
236 cmd, debug_level=logging.NOTICE,
237 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700238
Brian Norris6386fde2018-10-29 13:34:28 -0700239 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
240 # up for us with a 'write' command, so we have a standards-conforming GPT.
241 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
242 # fix GPT sanity issues.
243 cros_build_lib.SudoRunCommand(['sfdisk', device], input='write\n',
244 error_code_ok=True,
Brian Norrisb4d99982018-10-16 16:02:49 -0700245 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700246
David Pursellf1d16a62015-03-25 13:31:04 -0700247 cros_build_lib.SudoRunCommand(['sync'], debug_level=self.debug_level)
248
David Pursellf1d16a62015-03-25 13:31:04 -0700249 def _GetImagePath(self):
250 """Returns the image path to use."""
251 image_path = translated_path = None
252 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700253 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700254 # TODO(wnwen): Open the tarball and if there is just one file in it,
255 # use that instead. Existing code in upload_symbols.py.
256 if cros_build_lib.BooleanPrompt(
257 prolog='The given image file is not a valid disk image. Perhaps '
258 'you forgot to untar it.',
259 prompt='Terminate the current flash process?'):
260 raise FlashError('Update terminated by user.')
261 image_path = self.image
262 elif os.path.isdir(self.image):
263 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700264 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700265 else:
266 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700267 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Don Garrett97d7dc22015-01-20 14:07:56 -0800268 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700269 image_path = ds_wrapper.TranslatedPathToLocalPath(
Don Garrett97d7dc22015-01-20 14:07:56 -0800270 translated_path, DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700271
272 logging.info('Using image %s', translated_path or image_path)
273 return image_path
274
275 def Run(self):
276 """Image the removable device."""
277 devices = self.ListAllRemovableDevices()
278
279 if self.device:
280 # If user specified a device path, check if it exists.
281 if not os.path.exists(self.device):
282 raise FlashError('Device path %s does not exist.' % self.device)
283
284 # Then check if it is removable.
285 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
286 msg = '%s is not a removable device.' % self.device
287 if not (self.yes or cros_build_lib.BooleanPrompt(
288 default=False, prolog=msg)):
289 raise FlashError('You can specify usb:// to choose from a list of '
290 'removable devices.')
291 target = None
292 if self.device:
293 # Get device name from path (e.g. sdc in /dev/sdc).
294 target = self.device.rsplit(os.path.sep, 1)[-1]
295 elif devices:
296 # Ask user to choose from the list.
297 target = self.ChooseRemovableDevice(devices)
298 else:
299 raise FlashError('No removable devices detected.')
300
301 image_path = self._GetImagePath()
302 try:
303 device = self.DeviceNameToPath(target)
304 if self.install:
305 self.InstallImageToDevice(image_path, device)
306 else:
307 self.CopyImageToDevice(image_path, device)
308 except cros_build_lib.RunCommandError:
309 logging.error('Failed copying image to device %s',
310 self.DeviceNameToPath(target))
311
312
313class FileImager(USBImager):
314 """Copy image to the target path."""
315
316 def Run(self):
317 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800318 if not os.path.isdir(os.path.dirname(self.device)):
319 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700320
321 image_path = self._GetImagePath()
322 if os.path.isdir(self.device):
323 logging.info('Copying to %s',
324 os.path.join(self.device, os.path.basename(image_path)))
325 else:
326 logging.info('Copying to %s', self.device)
327 try:
328 shutil.copy(image_path, self.device)
329 except IOError:
330 logging.error('Failed to copy image %s to %s', image_path, self.device)
331
332
333class RemoteDeviceUpdater(object):
334 """Performs update on a remote device."""
335 DEVSERVER_FILENAME = 'devserver.py'
336 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
337 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700338 # Root working directory on the device. This directory is in the
339 # stateful partition and thus has enough space to store the payloads.
340 DEVICE_BASE_DIR = '/mnt/stateful_partition/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700341 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
342 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700343
344 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
345 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700346 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600347 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800348 disable_verification=False, send_payload_in_parallel=False,
349 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700350 """Initializes RemoteDeviceUpdater"""
351 if not stateful_update and not rootfs_update:
352 raise ValueError('No update operation to perform; either stateful or'
353 ' rootfs partitions must be updated.')
354 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
355 self.ssh_hostname = ssh_hostname
356 self.ssh_port = ssh_port
357 self.image = image
358 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700359 self.src_image_to_delta = src_image_to_delta
360 self.do_stateful_update = stateful_update
361 self.do_rootfs_update = rootfs_update
362 self.disable_verification = disable_verification
363 self.clobber_stateful = clobber_stateful
364 self.reboot = reboot
365 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600366 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700367 self.ping = ping
368 # Do not wipe if debug is set.
369 self.wipe = wipe and not debug
370 self.yes = yes
371 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800372 self.send_payload_in_parallel = send_payload_in_parallel
Amin Hassanie62d2e12019-02-01 10:40:41 -0800373 self.experimental_au = experimental_au
David Pursellf1d16a62015-03-25 13:31:04 -0700374
David Pursellf1d16a62015-03-25 13:31:04 -0700375 def Cleanup(self):
376 """Cleans up the temporary directory."""
377 if self.wipe:
378 logging.info('Cleaning up temporary working directory...')
379 osutils.RmDir(self.tempdir)
380 else:
381 logging.info('You can find the log files and/or payloads in %s',
382 self.tempdir)
383
xixuane851dfb2016-05-02 18:02:37 -0700384 def GetPayloadDir(self, device):
385 """Get directory of payload for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700386
xixuane851dfb2016-05-02 18:02:37 -0700387 This method is used to obtain the directory of payload for cros-flash. The
388 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700389
xixuane851dfb2016-05-02 18:02:37 -0700390 If self.image is a directory, we directly use the provided update payload(s)
391 in this directory.
392
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800393 If self.image is an image, we will generate payloads for it and put them in
394 our temporary directory. The reason is that people may modify a local image
395 or override it (on the same path) with a different image, so in order to be
396 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700397
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800398 If non of the above cases, we use the xbuddy to first obtain the image path
399 (and possibly download it). Then we will generate the payloads in the same
400 directory the image is located. The reason is that this is what devserver
401 used to do. The path to the image generated by the devserver (or xbuddy) is
402 unique and normally nobody override its image with a different one. That is
403 why I think it is safe to put the payloads next to the image. This is a poor
404 man's version of caching but it makes cros flash faster for users who flash
405 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700406
407 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400408 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700409
410 Returns:
xixuane851dfb2016-05-02 18:02:37 -0700411 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700412 """
xixuane851dfb2016-05-02 18:02:37 -0700413 if os.path.isdir(self.image):
414 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800415 logging.info('Using provided payloads in %s', self.image)
416 return self.image
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600417
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800418 if os.path.isfile(self.image):
419 # The given path is an image.
420 image_path = self.image
421 payload_dir = self.tempdir
xixuane851dfb2016-05-02 18:02:37 -0700422 else:
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800423 # Assuming it is an xbuddy path.
xixuane851dfb2016-05-02 18:02:37 -0700424 self.board = cros_build_lib.GetBoard(device_board=device.board,
425 override_board=self.board,
Eashan Bhatt2f01e422019-07-25 10:31:04 -0700426 force=self.yes,
427 strict=True)
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
Amin Hassani9800d432019-07-24 14:23:39 -0700474 chromeos_AU = auto_updater.ChromiumOSUpdater(
475 device=device,
476 build_name=None,
477 payload_dir=payload_dir,
478 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600479 do_rootfs_update=self.do_rootfs_update,
480 do_stateful_update=self.do_stateful_update,
481 reboot=self.reboot,
482 disable_verification=self.disable_verification,
483 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800484 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800485 send_payload_in_parallel=self.send_payload_in_parallel,
486 experimental_au=self.experimental_au)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600487 chromeos_AU.CheckPayloads()
488 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700489
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600490 except Exception:
491 logging.error('Device update failed.')
492 lsb_entries = sorted(device.lsb_release.items())
493 logging.info(
494 'Following are the LSB version details of the device:\n%s',
495 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
496 raise
497
498 logging.notice('Update performed successfully.')
499
500 except remote_access.RemoteAccessException:
501 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700502 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600503
David Pursellf1d16a62015-03-25 13:31:04 -0700504 finally:
505 self.Cleanup()
506
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700507def Flash(device, image, board=None, install=False, src_image_to_delta=None,
508 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600509 reboot=True, wipe=True, ssh_private_key=None, ping=True,
510 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800511 force=False, debug=False, send_payload_in_parallel=False,
512 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700513 """Flashes a device, USB drive, or file with an image.
514
515 This provides functionality common to `cros flash` and `brillo flash`
516 so that they can parse the commandline separately but still use the
517 same underlying functionality.
518
519 Args:
David Pursell2e773382015-04-03 14:30:47 -0700520 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700521 image: Path (string) to the update image. Can be a local or xbuddy path;
522 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700523 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700524 install: Install to USB using base disk layout; USB |device| scheme only.
525 src_image_to_delta: Local path to an image to be used as the base to
526 generate delta payloads; SSH |device| scheme only.
527 rootfs_update: Update rootfs partition; SSH |device| scheme only.
528 stateful_update: Update stateful partition; SSH |device| scheme only.
529 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
530 reboot: Reboot device after update; SSH |device| scheme only.
531 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600532 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700533 ping: Ping the device before attempting update; SSH |device| scheme only.
534 disable_rootfs_verification: Remove rootfs verification after update; SSH
535 |device| scheme only.
536 clear_cache: Clear the devserver static directory.
537 yes: Assume "yes" for any prompt.
538 force: Ignore sanity checks and prompts. Overrides |yes| if True.
539 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800540 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
541 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800542 experimental_au: Use the experimental features auto updater. It should be
543 deprecated once crbug.com/872441 is fixed.
David Pursellf1d16a62015-03-25 13:31:04 -0700544
545 Raises:
546 FlashError: An unrecoverable error occured.
547 ValueError: Invalid parameter combination.
548 """
549 if force:
550 yes = True
551
552 if clear_cache:
553 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800554 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700555
556 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800557 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700558 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800559 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700560
561 if install:
David Pursell2e773382015-04-03 14:30:47 -0700562 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700563 raise ValueError(
564 '--install can only be used when writing to a USB device')
565 if not cros_build_lib.IsInsideChroot():
566 raise ValueError('--install can only be used inside the chroot')
567
David Pursell2e773382015-04-03 14:30:47 -0700568 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
569 if device:
570 hostname, port = device.hostname, device.port
571 else:
572 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700573 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700574 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700575 hostname,
576 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700577 image,
578 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700579 src_image_to_delta=src_image_to_delta,
580 rootfs_update=rootfs_update,
581 stateful_update=stateful_update,
582 clobber_stateful=clobber_stateful,
583 reboot=reboot,
584 wipe=wipe,
585 debug=debug,
586 yes=yes,
587 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600588 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700589 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800590 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800591 send_payload_in_parallel=send_payload_in_parallel,
592 experimental_au=experimental_au)
David Pursellf1d16a62015-03-25 13:31:04 -0700593 updater.Run()
594 elif device.scheme == commandline.DEVICE_SCHEME_USB:
595 path = osutils.ExpandPath(device.path) if device.path else ''
596 logging.info('Preparing to image the removable device %s', path)
597 imager = USBImager(path,
598 board,
599 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700600 debug=debug,
601 install=install,
602 yes=yes)
603 imager.Run()
604 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
605 logging.info('Preparing to copy image to %s', device.path)
606 imager = FileImager(device.path,
607 board,
608 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700609 debug=debug,
610 yes=yes)
611 imager.Run()