blob: 3628c740842ef96f88b1492242d007aac6ac961d [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()
490 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700491
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600492 except Exception:
493 logging.error('Device update failed.')
494 lsb_entries = sorted(device.lsb_release.items())
495 logging.info(
496 'Following are the LSB version details of the device:\n%s',
497 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
498 raise
499
500 logging.notice('Update performed successfully.')
501
502 except remote_access.RemoteAccessException:
503 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700504 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600505
David Pursellf1d16a62015-03-25 13:31:04 -0700506 finally:
507 self.Cleanup()
508
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700509def Flash(device, image, board=None, install=False, src_image_to_delta=None,
510 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600511 reboot=True, wipe=True, ssh_private_key=None, ping=True,
512 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800513 force=False, debug=False, send_payload_in_parallel=False,
514 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700515 """Flashes a device, USB drive, or file with an image.
516
517 This provides functionality common to `cros flash` and `brillo flash`
518 so that they can parse the commandline separately but still use the
519 same underlying functionality.
520
521 Args:
David Pursell2e773382015-04-03 14:30:47 -0700522 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700523 image: Path (string) to the update image. Can be a local or xbuddy path;
524 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700525 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700526 install: Install to USB using base disk layout; USB |device| scheme only.
527 src_image_to_delta: Local path to an image to be used as the base to
528 generate delta payloads; SSH |device| scheme only.
529 rootfs_update: Update rootfs partition; SSH |device| scheme only.
530 stateful_update: Update stateful partition; SSH |device| scheme only.
531 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
532 reboot: Reboot device after update; SSH |device| scheme only.
533 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600534 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700535 ping: Ping the device before attempting update; SSH |device| scheme only.
536 disable_rootfs_verification: Remove rootfs verification after update; SSH
537 |device| scheme only.
538 clear_cache: Clear the devserver static directory.
539 yes: Assume "yes" for any prompt.
540 force: Ignore sanity checks and prompts. Overrides |yes| if True.
541 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800542 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
543 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800544 experimental_au: Use the experimental features auto updater. It should be
545 deprecated once crbug.com/872441 is fixed.
David Pursellf1d16a62015-03-25 13:31:04 -0700546
547 Raises:
548 FlashError: An unrecoverable error occured.
549 ValueError: Invalid parameter combination.
550 """
551 if force:
552 yes = True
553
554 if clear_cache:
555 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800556 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700557
558 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800559 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700560 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800561 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700562
563 if install:
David Pursell2e773382015-04-03 14:30:47 -0700564 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700565 raise ValueError(
566 '--install can only be used when writing to a USB device')
567 if not cros_build_lib.IsInsideChroot():
568 raise ValueError('--install can only be used inside the chroot')
569
David Pursell2e773382015-04-03 14:30:47 -0700570 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
571 if device:
572 hostname, port = device.hostname, device.port
573 else:
574 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700575 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700576 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700577 hostname,
578 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700579 image,
580 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700581 src_image_to_delta=src_image_to_delta,
582 rootfs_update=rootfs_update,
583 stateful_update=stateful_update,
584 clobber_stateful=clobber_stateful,
585 reboot=reboot,
586 wipe=wipe,
587 debug=debug,
588 yes=yes,
589 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600590 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700591 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800592 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800593 send_payload_in_parallel=send_payload_in_parallel,
594 experimental_au=experimental_au)
David Pursellf1d16a62015-03-25 13:31:04 -0700595 updater.Run()
596 elif device.scheme == commandline.DEVICE_SCHEME_USB:
597 path = osutils.ExpandPath(device.path) if device.path else ''
598 logging.info('Preparing to image the removable device %s', path)
599 imager = USBImager(path,
600 board,
601 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700602 debug=debug,
603 install=install,
604 yes=yes)
605 imager.Run()
606 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
607 logging.info('Preparing to copy image to %s', device.path)
608 imager = FileImager(device.path,
609 board,
610 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700611 debug=debug,
612 yes=yes)
613 imager.Run()