blob: e2ef9566b74df29e07f7d3ecaaa69311f206355b [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
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -070018from chromite.lib import auto_updater_transfer
David Pursellf1d16a62015-03-25 13:31:04 -070019from chromite.lib import commandline
20from chromite.lib import cros_build_lib
21from chromite.lib import cros_logging as logging
22from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070023from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070024from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070025from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070026from chromite.lib import remote_access
27
Amin Hassanic0f06fa2019-01-28 15:24:47 -080028from chromite.lib.paygen import paygen_payload_lib
29from chromite.lib.paygen import paygen_stateful_payload_lib
30
David Pursellf1d16a62015-03-25 13:31:04 -070031
Don Garrett97d7dc22015-01-20 14:07:56 -080032DEVSERVER_STATIC_DIR = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -070033 os.path.join(constants.CHROOT_SOURCE_ROOT, 'devserver', 'static'))
34
35
Ralph Nathan9b997232015-05-15 13:13:12 -070036class UsbImagerOperation(operation.ProgressBarOperation):
37 """Progress bar for flashing image to operation."""
38
39 def __init__(self, image):
40 super(UsbImagerOperation, self).__init__()
41 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070042 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070043 self._bytes = re.compile(r'(\d+) bytes')
44
45 def _GetDDPid(self):
46 """Get the Pid of dd."""
47 try:
Mike Frysinger45602c72019-09-22 02:15:11 -040048 pids = cros_build_lib.run(['pgrep', 'dd'], capture_output=True,
49 print_cmd=False).output
Ralph Nathan9b997232015-05-15 13:13:12 -070050 for pid in pids.splitlines():
51 if osutils.IsChildProcess(int(pid), name='dd'):
52 return int(pid)
53 return -1
54 except cros_build_lib.RunCommandError:
55 # If dd isn't still running, then we assume that it is finished.
56 return -1
57
58 def _PingDD(self, dd_pid):
59 """Send USR1 signal to dd to get status update."""
60 try:
61 cmd = ['kill', '-USR1', str(dd_pid)]
Mike Frysinger45602c72019-09-22 02:15:11 -040062 cros_build_lib.sudo_run(cmd, print_cmd=False)
Ralph Nathan9b997232015-05-15 13:13:12 -070063 except cros_build_lib.RunCommandError:
64 # Here we assume that dd finished in the background.
65 return
66
67 def ParseOutput(self, output=None):
68 """Parse the output of dd to update progress bar."""
69 dd_pid = self._GetDDPid()
70 if dd_pid == -1:
71 return
72
73 self._PingDD(dd_pid)
74
75 if output is None:
76 stdout = self._stdout.read()
77 stderr = self._stderr.read()
78 output = stdout + stderr
79
80 match = self._bytes.search(output)
81 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070082 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -070083
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040084 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -070085
86
Mike Frysinger32759e42016-12-21 18:40:16 -050087def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070088 """Determines if a file is a valid GPT disk.
89
90 Args:
91 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -050092 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070093 """
94 if os.path.isfile(file_path):
Mike Frysinger32759e42016-12-21 18:40:16 -050095 with open(file_path) as image_file:
96 if require_pmbr:
97 # Seek to the end of LBA0 and look for the PMBR boot signature.
98 image_file.seek(0x1fe)
99 if image_file.read(2) != '\x55\xaa':
100 return False
101 # Current file position is start of LBA1 now.
102 else:
103 # Seek to LBA1 where the GPT starts.
104 image_file.seek(0x200)
105
106 # See if there's a GPT here.
107 if image_file.read(8) == 'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700108 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500109
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700110 return False
111
112
113def _ChooseImageFromDirectory(dir_path):
114 """Lists all image files in |dir_path| and ask user to select one.
115
116 Args:
117 dir_path: Path to the directory.
118 """
119 images = sorted([x for x in os.listdir(dir_path) if
120 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
121 idx = 0
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400122 if not images:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700123 raise ValueError('No image found in %s.' % dir_path)
124 elif len(images) > 1:
125 idx = cros_build_lib.GetChoice(
126 'Multiple images found in %s. Please select one to continue:' % (
127 (dir_path,)),
128 images)
129
130 return os.path.join(dir_path, images[idx])
131
132
David Pursellf1d16a62015-03-25 13:31:04 -0700133class FlashError(Exception):
134 """Thrown when there is an unrecoverable error during flash."""
135
136
137class USBImager(object):
138 """Copy image to the target removable device."""
139
Gilad Arnoldd0461442015-07-07 11:52:46 -0700140 def __init__(self, device, board, image, debug=False, install=False,
141 yes=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700142 """Initalizes USBImager."""
143 self.device = device
144 self.board = board if board else cros_build_lib.GetDefaultBoard()
145 self.image = image
David Pursellf1d16a62015-03-25 13:31:04 -0700146 self.debug = debug
147 self.debug_level = logging.DEBUG if debug else logging.INFO
148 self.install = install
149 self.yes = yes
150
151 def DeviceNameToPath(self, device_name):
152 return '/dev/%s' % device_name
153
154 def GetRemovableDeviceDescription(self, device):
155 """Returns a informational description of the removable |device|.
156
157 Args:
158 device: the device name (e.g. sdc).
159
160 Returns:
161 A string describing |device| (e.g. Patriot Memory 7918 MB).
162 """
163 desc = [
164 osutils.GetDeviceInfo(device, keyword='manufacturer'),
165 osutils.GetDeviceInfo(device, keyword='product'),
166 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
167 '(%s)' % self.DeviceNameToPath(device),
168 ]
169 return ' '.join([x for x in desc if x])
170
171 def ListAllRemovableDevices(self):
172 """Returns a list of removable devices.
173
174 Returns:
175 A list of device names (e.g. ['sdb', 'sdc']).
176 """
177 devices = osutils.ListBlockDevices()
178 removable_devices = []
179 for d in devices:
180 if d.TYPE == 'disk' and d.RM == '1':
181 removable_devices.append(d.NAME)
182
183 return removable_devices
184
185 def ChooseRemovableDevice(self, devices):
186 """Lists all removable devices and asks user to select/confirm.
187
188 Args:
189 devices: a list of device names (e.g. ['sda', 'sdb']).
190
191 Returns:
192 The device name chosen by the user.
193 """
194 idx = cros_build_lib.GetChoice(
195 'Removable device(s) found. Please select/confirm to continue:',
196 [self.GetRemovableDeviceDescription(x) for x in devices])
197
198 return devices[idx]
199
200 def InstallImageToDevice(self, image, device):
201 """Installs |image| to the removable |device|.
202
203 Args:
204 image: Path to the image to copy.
205 device: Device to copy to.
206 """
207 cmd = [
208 'chromeos-install',
209 '--yes',
210 '--skip_src_removable',
211 '--skip_dst_removable',
212 '--payload_image=%s' % image,
213 '--dst=%s' % device,
214 '--skip_postinstall',
215 ]
Mike Frysinger45602c72019-09-22 02:15:11 -0400216 cros_build_lib.sudo_run(cmd,
217 print_cmd=True,
218 debug_level=logging.NOTICE,
219 combine_stdout_stderr=True,
220 log_output=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700221
222 def CopyImageToDevice(self, image, device):
223 """Copies |image| to the removable |device|.
224
225 Args:
226 image: Path to the image to copy.
227 device: Device to copy to.
228 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700229 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800230 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700231 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
232 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400233 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Ralph Nathan9b997232015-05-15 13:13:12 -0700234 update_period=0.5)
235 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400236 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700237 cmd, debug_level=logging.NOTICE,
238 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700239
Brian Norris6386fde2018-10-29 13:34:28 -0700240 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
241 # up for us with a 'write' command, so we have a standards-conforming GPT.
242 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
243 # fix GPT sanity issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400244 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
245 error_code_ok=True,
246 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700247
Mike Frysinger45602c72019-09-22 02:15:11 -0400248 cros_build_lib.sudo_run(['partx', '-u', device],
249 debug_level=self.debug_level)
250 cros_build_lib.sudo_run(['sync', '-d', device],
251 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700252
David Pursellf1d16a62015-03-25 13:31:04 -0700253 def _GetImagePath(self):
254 """Returns the image path to use."""
255 image_path = translated_path = None
256 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700257 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700258 # TODO(wnwen): Open the tarball and if there is just one file in it,
259 # use that instead. Existing code in upload_symbols.py.
260 if cros_build_lib.BooleanPrompt(
261 prolog='The given image file is not a valid disk image. Perhaps '
262 'you forgot to untar it.',
263 prompt='Terminate the current flash process?'):
264 raise FlashError('Update terminated by user.')
265 image_path = self.image
266 elif os.path.isdir(self.image):
267 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700268 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700269 else:
270 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700271 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Don Garrett97d7dc22015-01-20 14:07:56 -0800272 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700273 image_path = ds_wrapper.TranslatedPathToLocalPath(
Don Garrett97d7dc22015-01-20 14:07:56 -0800274 translated_path, DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700275
276 logging.info('Using image %s', translated_path or image_path)
277 return image_path
278
279 def Run(self):
280 """Image the removable device."""
281 devices = self.ListAllRemovableDevices()
282
283 if self.device:
284 # If user specified a device path, check if it exists.
285 if not os.path.exists(self.device):
286 raise FlashError('Device path %s does not exist.' % self.device)
287
288 # Then check if it is removable.
289 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
290 msg = '%s is not a removable device.' % self.device
291 if not (self.yes or cros_build_lib.BooleanPrompt(
292 default=False, prolog=msg)):
293 raise FlashError('You can specify usb:// to choose from a list of '
294 'removable devices.')
295 target = None
296 if self.device:
297 # Get device name from path (e.g. sdc in /dev/sdc).
298 target = self.device.rsplit(os.path.sep, 1)[-1]
299 elif devices:
300 # Ask user to choose from the list.
301 target = self.ChooseRemovableDevice(devices)
302 else:
303 raise FlashError('No removable devices detected.')
304
305 image_path = self._GetImagePath()
306 try:
307 device = self.DeviceNameToPath(target)
308 if self.install:
309 self.InstallImageToDevice(image_path, device)
310 else:
311 self.CopyImageToDevice(image_path, device)
312 except cros_build_lib.RunCommandError:
313 logging.error('Failed copying image to device %s',
314 self.DeviceNameToPath(target))
315
316
317class FileImager(USBImager):
318 """Copy image to the target path."""
319
320 def Run(self):
321 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800322 if not os.path.isdir(os.path.dirname(self.device)):
323 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700324
325 image_path = self._GetImagePath()
326 if os.path.isdir(self.device):
327 logging.info('Copying to %s',
328 os.path.join(self.device, os.path.basename(image_path)))
329 else:
330 logging.info('Copying to %s', self.device)
331 try:
332 shutil.copy(image_path, self.device)
333 except IOError:
334 logging.error('Failed to copy image %s to %s', image_path, self.device)
335
336
337class RemoteDeviceUpdater(object):
338 """Performs update on a remote device."""
David Pursellf1d16a62015-03-25 13:31:04 -0700339 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
340 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700341 # Root working directory on the device. This directory is in the
342 # stateful partition and thus has enough space to store the payloads.
343 DEVICE_BASE_DIR = '/mnt/stateful_partition/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700344 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
345 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700346
347 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
348 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700349 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600350 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800351 disable_verification=False, send_payload_in_parallel=False,
352 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700353 """Initializes RemoteDeviceUpdater"""
354 if not stateful_update and not rootfs_update:
355 raise ValueError('No update operation to perform; either stateful or'
356 ' rootfs partitions must be updated.')
357 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
358 self.ssh_hostname = ssh_hostname
359 self.ssh_port = ssh_port
360 self.image = image
361 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700362 self.src_image_to_delta = src_image_to_delta
363 self.do_stateful_update = stateful_update
364 self.do_rootfs_update = rootfs_update
365 self.disable_verification = disable_verification
366 self.clobber_stateful = clobber_stateful
367 self.reboot = reboot
368 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600369 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700370 self.ping = ping
371 # Do not wipe if debug is set.
372 self.wipe = wipe and not debug
373 self.yes = yes
374 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800375 self.send_payload_in_parallel = send_payload_in_parallel
Amin Hassanie62d2e12019-02-01 10:40:41 -0800376 self.experimental_au = experimental_au
David Pursellf1d16a62015-03-25 13:31:04 -0700377
David Pursellf1d16a62015-03-25 13:31:04 -0700378 def Cleanup(self):
379 """Cleans up the temporary directory."""
380 if self.wipe:
381 logging.info('Cleaning up temporary working directory...')
382 osutils.RmDir(self.tempdir)
383 else:
384 logging.info('You can find the log files and/or payloads in %s',
385 self.tempdir)
386
xixuane851dfb2016-05-02 18:02:37 -0700387 def GetPayloadDir(self, device):
388 """Get directory of payload for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700389
xixuane851dfb2016-05-02 18:02:37 -0700390 This method is used to obtain the directory of payload for cros-flash. The
391 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700392
xixuane851dfb2016-05-02 18:02:37 -0700393 If self.image is a directory, we directly use the provided update payload(s)
394 in this directory.
395
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800396 If self.image is an image, we will generate payloads for it and put them in
397 our temporary directory. The reason is that people may modify a local image
398 or override it (on the same path) with a different image, so in order to be
399 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700400
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800401 If non of the above cases, we use the xbuddy to first obtain the image path
402 (and possibly download it). Then we will generate the payloads in the same
403 directory the image is located. The reason is that this is what devserver
404 used to do. The path to the image generated by the devserver (or xbuddy) is
405 unique and normally nobody override its image with a different one. That is
406 why I think it is safe to put the payloads next to the image. This is a poor
407 man's version of caching but it makes cros flash faster for users who flash
408 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700409
410 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400411 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700412
413 Returns:
xixuane851dfb2016-05-02 18:02:37 -0700414 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700415 """
xixuane851dfb2016-05-02 18:02:37 -0700416 if os.path.isdir(self.image):
417 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800418 logging.info('Using provided payloads in %s', self.image)
419 return self.image
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600420
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800421 if os.path.isfile(self.image):
422 # The given path is an image.
423 image_path = self.image
424 payload_dir = self.tempdir
xixuane851dfb2016-05-02 18:02:37 -0700425 else:
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800426 # Assuming it is an xbuddy path.
xixuane851dfb2016-05-02 18:02:37 -0700427 self.board = cros_build_lib.GetBoard(device_board=device.board,
428 override_board=self.board,
Eashan Bhatt2f01e422019-07-25 10:31:04 -0700429 force=self.yes,
430 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700431 if not self.force and self.board != device.board:
432 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700433 raise FlashError('Device (%s) is incompatible with board %s' %
434 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700435 logging.info('Board is %s', self.board)
436
437 # Translate the xbuddy path to get the exact image to use.
Amin Hassanic20a3c32019-06-02 21:43:21 -0700438
439 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
440 # use xbuddy library directly instead of the devserver_wrapper.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800441 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
442 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
443 image_path = ds_wrapper.TranslatedPathToLocalPath(
444 translated_path, DEVSERVER_STATIC_DIR)
445 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
xixuane851dfb2016-05-02 18:02:37 -0700446
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800447 logging.notice('Using image path %s and payload directory %s',
448 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700449
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800450 # Generate rootfs and stateful update payloads if they do not exist.
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700451 payload_path = os.path.join(payload_dir,
452 auto_updater_transfer.ROOTFS_FILENAME)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800453 if not os.path.exists(payload_path):
454 paygen_payload_lib.GenerateUpdatePayload(
455 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700456 if not os.path.exists(os.path.join(
457 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800458 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
459 payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700460 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700461
462 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700463 """Perform remote device update.
464
465 The update process includes:
466 1. initialize a device instance for the given remote device.
467 2. achieve payload_dir which contains the required payloads for updating.
468 3. initialize an auto-updater instance to do RunUpdate().
469 4. After auto-update, all temp files and dir will be cleaned up.
470 """
David Pursellf1d16a62015-03-25 13:31:04 -0700471 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700472 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600473 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
474 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700475
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600476 try:
477 # Get payload directory
478 payload_dir = self.GetPayloadDir(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700479
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600480 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700481 chromeos_AU = auto_updater.ChromiumOSUpdater(
482 device=device,
483 build_name=None,
484 payload_dir=payload_dir,
485 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600486 do_rootfs_update=self.do_rootfs_update,
487 do_stateful_update=self.do_stateful_update,
488 reboot=self.reboot,
489 disable_verification=self.disable_verification,
490 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800491 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800492 send_payload_in_parallel=self.send_payload_in_parallel,
493 experimental_au=self.experimental_au)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600494 chromeos_AU.CheckPayloads()
Amin Hassanif85be122019-09-19 17:10:34 -0700495 chromeos_AU.ResolveAPPIDMismatchIfAny()
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600496 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700497
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600498 except Exception:
499 logging.error('Device update failed.')
500 lsb_entries = sorted(device.lsb_release.items())
501 logging.info(
502 'Following are the LSB version details of the device:\n%s',
503 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
504 raise
505
506 logging.notice('Update performed successfully.')
507
508 except remote_access.RemoteAccessException:
509 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700510 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600511
David Pursellf1d16a62015-03-25 13:31:04 -0700512 finally:
513 self.Cleanup()
514
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700515def Flash(device, image, board=None, install=False, src_image_to_delta=None,
516 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600517 reboot=True, wipe=True, ssh_private_key=None, ping=True,
518 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800519 force=False, debug=False, send_payload_in_parallel=False,
520 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700521 """Flashes a device, USB drive, or file with an image.
522
523 This provides functionality common to `cros flash` and `brillo flash`
524 so that they can parse the commandline separately but still use the
525 same underlying functionality.
526
527 Args:
David Pursell2e773382015-04-03 14:30:47 -0700528 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700529 image: Path (string) to the update image. Can be a local or xbuddy path;
530 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700531 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700532 install: Install to USB using base disk layout; USB |device| scheme only.
533 src_image_to_delta: Local path to an image to be used as the base to
534 generate delta payloads; SSH |device| scheme only.
535 rootfs_update: Update rootfs partition; SSH |device| scheme only.
536 stateful_update: Update stateful partition; SSH |device| scheme only.
537 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
538 reboot: Reboot device after update; SSH |device| scheme only.
539 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600540 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700541 ping: Ping the device before attempting update; SSH |device| scheme only.
542 disable_rootfs_verification: Remove rootfs verification after update; SSH
543 |device| scheme only.
544 clear_cache: Clear the devserver static directory.
545 yes: Assume "yes" for any prompt.
546 force: Ignore sanity checks and prompts. Overrides |yes| if True.
547 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800548 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
549 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800550 experimental_au: Use the experimental features auto updater. It should be
551 deprecated once crbug.com/872441 is fixed.
David Pursellf1d16a62015-03-25 13:31:04 -0700552
553 Raises:
554 FlashError: An unrecoverable error occured.
555 ValueError: Invalid parameter combination.
556 """
557 if force:
558 yes = True
559
560 if clear_cache:
561 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800562 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700563
564 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800565 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700566 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800567 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700568
569 if install:
David Pursell2e773382015-04-03 14:30:47 -0700570 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700571 raise ValueError(
572 '--install can only be used when writing to a USB device')
573 if not cros_build_lib.IsInsideChroot():
574 raise ValueError('--install can only be used inside the chroot')
575
David Pursell2e773382015-04-03 14:30:47 -0700576 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
577 if device:
578 hostname, port = device.hostname, device.port
579 else:
580 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700581 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700582 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700583 hostname,
584 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700585 image,
586 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700587 src_image_to_delta=src_image_to_delta,
588 rootfs_update=rootfs_update,
589 stateful_update=stateful_update,
590 clobber_stateful=clobber_stateful,
591 reboot=reboot,
592 wipe=wipe,
593 debug=debug,
594 yes=yes,
595 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600596 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700597 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800598 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800599 send_payload_in_parallel=send_payload_in_parallel,
600 experimental_au=experimental_au)
David Pursellf1d16a62015-03-25 13:31:04 -0700601 updater.Run()
602 elif device.scheme == commandline.DEVICE_SCHEME_USB:
603 path = osutils.ExpandPath(device.path) if device.path else ''
604 logging.info('Preparing to image the removable device %s', path)
605 imager = USBImager(path,
606 board,
607 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700608 debug=debug,
609 install=install,
610 yes=yes)
611 imager.Run()
612 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
613 logging.info('Preparing to copy image to %s', device.path)
614 imager = FileImager(device.path,
615 board,
616 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700617 debug=debug,
618 yes=yes)
619 imager.Run()