blob: c892521f4f851d91f19fee049fb3268887e17e14 [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."""
David Pursellf1d16a62015-03-25 13:31:04 -0700335 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,
Eashan Bhatt2f01e422019-07-25 10:31:04 -0700425 force=self.yes,
426 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700427 if not self.force and self.board != device.board:
428 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700429 raise FlashError('Device (%s) is incompatible with board %s' %
430 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700431 logging.info('Board is %s', self.board)
432
433 # Translate the xbuddy path to get the exact image to use.
Amin Hassanic20a3c32019-06-02 21:43:21 -0700434
435 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
436 # use xbuddy library directly instead of the devserver_wrapper.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800437 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
438 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
439 image_path = ds_wrapper.TranslatedPathToLocalPath(
440 translated_path, DEVSERVER_STATIC_DIR)
441 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
xixuane851dfb2016-05-02 18:02:37 -0700442
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800443 logging.notice('Using image path %s and payload directory %s',
444 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700445
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800446 # Generate rootfs and stateful update payloads if they do not exist.
Amin Hassanic20a3c32019-06-02 21:43:21 -0700447 payload_path = os.path.join(payload_dir, auto_updater.ROOTFS_FILENAME)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800448 if not os.path.exists(payload_path):
449 paygen_payload_lib.GenerateUpdatePayload(
450 image_path, payload_path, src_image=self.src_image_to_delta)
451 if not os.path.exists(os.path.join(payload_dir,
Amin Hassanic20a3c32019-06-02 21:43:21 -0700452 auto_updater.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800453 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
454 payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700455 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700456
457 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700458 """Perform remote device update.
459
460 The update process includes:
461 1. initialize a device instance for the given remote device.
462 2. achieve payload_dir which contains the required payloads for updating.
463 3. initialize an auto-updater instance to do RunUpdate().
464 4. After auto-update, all temp files and dir will be cleaned up.
465 """
David Pursellf1d16a62015-03-25 13:31:04 -0700466 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700467 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600468 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
469 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700470
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600471 try:
472 # Get payload directory
473 payload_dir = self.GetPayloadDir(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700474
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600475 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700476 chromeos_AU = auto_updater.ChromiumOSUpdater(
477 device=device,
478 build_name=None,
479 payload_dir=payload_dir,
480 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600481 do_rootfs_update=self.do_rootfs_update,
482 do_stateful_update=self.do_stateful_update,
483 reboot=self.reboot,
484 disable_verification=self.disable_verification,
485 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800486 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800487 send_payload_in_parallel=self.send_payload_in_parallel,
488 experimental_au=self.experimental_au)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600489 chromeos_AU.CheckPayloads()
Amin Hassanif85be122019-09-19 17:10:34 -0700490 chromeos_AU.ResolveAPPIDMismatchIfAny()
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600491 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700492
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600493 except Exception:
494 logging.error('Device update failed.')
495 lsb_entries = sorted(device.lsb_release.items())
496 logging.info(
497 'Following are the LSB version details of the device:\n%s',
498 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
499 raise
500
501 logging.notice('Update performed successfully.')
502
503 except remote_access.RemoteAccessException:
504 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700505 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600506
David Pursellf1d16a62015-03-25 13:31:04 -0700507 finally:
508 self.Cleanup()
509
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700510def Flash(device, image, board=None, install=False, src_image_to_delta=None,
511 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600512 reboot=True, wipe=True, ssh_private_key=None, ping=True,
513 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800514 force=False, debug=False, send_payload_in_parallel=False,
515 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700516 """Flashes a device, USB drive, or file with an image.
517
518 This provides functionality common to `cros flash` and `brillo flash`
519 so that they can parse the commandline separately but still use the
520 same underlying functionality.
521
522 Args:
David Pursell2e773382015-04-03 14:30:47 -0700523 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700524 image: Path (string) to the update image. Can be a local or xbuddy path;
525 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700526 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700527 install: Install to USB using base disk layout; USB |device| scheme only.
528 src_image_to_delta: Local path to an image to be used as the base to
529 generate delta payloads; SSH |device| scheme only.
530 rootfs_update: Update rootfs partition; SSH |device| scheme only.
531 stateful_update: Update stateful partition; SSH |device| scheme only.
532 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
533 reboot: Reboot device after update; SSH |device| scheme only.
534 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600535 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700536 ping: Ping the device before attempting update; SSH |device| scheme only.
537 disable_rootfs_verification: Remove rootfs verification after update; SSH
538 |device| scheme only.
539 clear_cache: Clear the devserver static directory.
540 yes: Assume "yes" for any prompt.
541 force: Ignore sanity checks and prompts. Overrides |yes| if True.
542 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800543 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
544 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800545 experimental_au: Use the experimental features auto updater. It should be
546 deprecated once crbug.com/872441 is fixed.
David Pursellf1d16a62015-03-25 13:31:04 -0700547
548 Raises:
549 FlashError: An unrecoverable error occured.
550 ValueError: Invalid parameter combination.
551 """
552 if force:
553 yes = True
554
555 if clear_cache:
556 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800557 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700558
559 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800560 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700561 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800562 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700563
564 if install:
David Pursell2e773382015-04-03 14:30:47 -0700565 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700566 raise ValueError(
567 '--install can only be used when writing to a USB device')
568 if not cros_build_lib.IsInsideChroot():
569 raise ValueError('--install can only be used inside the chroot')
570
David Pursell2e773382015-04-03 14:30:47 -0700571 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
572 if device:
573 hostname, port = device.hostname, device.port
574 else:
575 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700576 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700577 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700578 hostname,
579 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700580 image,
581 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700582 src_image_to_delta=src_image_to_delta,
583 rootfs_update=rootfs_update,
584 stateful_update=stateful_update,
585 clobber_stateful=clobber_stateful,
586 reboot=reboot,
587 wipe=wipe,
588 debug=debug,
589 yes=yes,
590 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600591 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700592 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800593 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800594 send_payload_in_parallel=send_payload_in_parallel,
595 experimental_au=experimental_au)
David Pursellf1d16a62015-03-25 13:31:04 -0700596 updater.Run()
597 elif device.scheme == commandline.DEVICE_SCHEME_USB:
598 path = osutils.ExpandPath(device.path) if device.path else ''
599 logging.info('Preparing to image the removable device %s', path)
600 imager = USBImager(path,
601 board,
602 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700603 debug=debug,
604 install=install,
605 yes=yes)
606 imager.Run()
607 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
608 logging.info('Preparing to copy image to %s', device.path)
609 imager = FileImager(device.path,
610 board,
611 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700612 debug=debug,
613 yes=yes)
614 imager.Run()