blob: d07a02b98dbad6bf0026404b39c37097567253d3 [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
Mike Frysinger66d32cd2019-12-17 14:55:29 -050014import subprocess
Mike Frysinger3f087aa2020-03-20 06:03:16 -040015import sys
David Pursellf1d16a62015-03-25 13:31:04 -070016import tempfile
David Pursellf1d16a62015-03-25 13:31:04 -070017
Aviv Keshetb7519e12016-10-04 00:50:00 -070018from chromite.lib import constants
xixuane851dfb2016-05-02 18:02:37 -070019from chromite.lib import auto_updater
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -070020from chromite.lib import auto_updater_transfer
David Pursellf1d16a62015-03-25 13:31:04 -070021from chromite.lib import commandline
22from chromite.lib import cros_build_lib
23from chromite.lib import cros_logging as logging
24from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070025from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070026from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070027from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070028from chromite.lib import remote_access
29
Amin Hassanic0f06fa2019-01-28 15:24:47 -080030from chromite.lib.paygen import paygen_payload_lib
31from chromite.lib.paygen import paygen_stateful_payload_lib
32
Amin Hassani1c25d4b2019-11-22 10:59:07 -080033from chromite.lib.xbuddy import artifact_info
34
David Pursellf1d16a62015-03-25 13:31:04 -070035
Mike Frysinger3f087aa2020-03-20 06:03:16 -040036assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
37
38
Don Garrett97d7dc22015-01-20 14:07:56 -080039DEVSERVER_STATIC_DIR = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -070040 os.path.join(constants.CHROOT_SOURCE_ROOT, 'devserver', 'static'))
41
42
Ralph Nathan9b997232015-05-15 13:13:12 -070043class UsbImagerOperation(operation.ProgressBarOperation):
44 """Progress bar for flashing image to operation."""
45
46 def __init__(self, image):
47 super(UsbImagerOperation, self).__init__()
48 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070049 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070050 self._bytes = re.compile(r'(\d+) bytes')
51
52 def _GetDDPid(self):
53 """Get the Pid of dd."""
54 try:
Mike Frysinger45602c72019-09-22 02:15:11 -040055 pids = cros_build_lib.run(['pgrep', 'dd'], capture_output=True,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040056 print_cmd=False, encoding='utf-8').stdout
Ralph Nathan9b997232015-05-15 13:13:12 -070057 for pid in pids.splitlines():
58 if osutils.IsChildProcess(int(pid), name='dd'):
59 return int(pid)
60 return -1
61 except cros_build_lib.RunCommandError:
62 # If dd isn't still running, then we assume that it is finished.
63 return -1
64
65 def _PingDD(self, dd_pid):
66 """Send USR1 signal to dd to get status update."""
67 try:
68 cmd = ['kill', '-USR1', str(dd_pid)]
Mike Frysinger45602c72019-09-22 02:15:11 -040069 cros_build_lib.sudo_run(cmd, print_cmd=False)
Ralph Nathan9b997232015-05-15 13:13:12 -070070 except cros_build_lib.RunCommandError:
71 # Here we assume that dd finished in the background.
72 return
73
74 def ParseOutput(self, output=None):
75 """Parse the output of dd to update progress bar."""
76 dd_pid = self._GetDDPid()
77 if dd_pid == -1:
78 return
79
80 self._PingDD(dd_pid)
81
82 if output is None:
83 stdout = self._stdout.read()
84 stderr = self._stderr.read()
85 output = stdout + stderr
86
87 match = self._bytes.search(output)
88 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070089 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -070090
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040091 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -070092
93
Mike Frysinger32759e42016-12-21 18:40:16 -050094def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070095 """Determines if a file is a valid GPT disk.
96
97 Args:
98 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -050099 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700100 """
101 if os.path.isfile(file_path):
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400102 with open(file_path, 'rb') as image_file:
Mike Frysinger32759e42016-12-21 18:40:16 -0500103 if require_pmbr:
104 # Seek to the end of LBA0 and look for the PMBR boot signature.
105 image_file.seek(0x1fe)
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400106 if image_file.read(2) != b'\x55\xaa':
Mike Frysinger32759e42016-12-21 18:40:16 -0500107 return False
108 # Current file position is start of LBA1 now.
109 else:
110 # Seek to LBA1 where the GPT starts.
111 image_file.seek(0x200)
112
113 # See if there's a GPT here.
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400114 if image_file.read(8) == b'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700115 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500116
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700117 return False
118
119
120def _ChooseImageFromDirectory(dir_path):
121 """Lists all image files in |dir_path| and ask user to select one.
122
123 Args:
124 dir_path: Path to the directory.
125 """
126 images = sorted([x for x in os.listdir(dir_path) if
127 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
128 idx = 0
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400129 if not images:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700130 raise ValueError('No image found in %s.' % dir_path)
131 elif len(images) > 1:
132 idx = cros_build_lib.GetChoice(
133 'Multiple images found in %s. Please select one to continue:' % (
134 (dir_path,)),
135 images)
136
137 return os.path.join(dir_path, images[idx])
138
139
David Pursellf1d16a62015-03-25 13:31:04 -0700140class FlashError(Exception):
141 """Thrown when there is an unrecoverable error during flash."""
142
143
144class USBImager(object):
145 """Copy image to the target removable device."""
146
Gilad Arnoldd0461442015-07-07 11:52:46 -0700147 def __init__(self, device, board, image, debug=False, install=False,
148 yes=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700149 """Initalizes USBImager."""
150 self.device = device
151 self.board = board if board else cros_build_lib.GetDefaultBoard()
152 self.image = image
David Pursellf1d16a62015-03-25 13:31:04 -0700153 self.debug = debug
154 self.debug_level = logging.DEBUG if debug else logging.INFO
155 self.install = install
156 self.yes = yes
157
158 def DeviceNameToPath(self, device_name):
159 return '/dev/%s' % device_name
160
161 def GetRemovableDeviceDescription(self, device):
162 """Returns a informational description of the removable |device|.
163
164 Args:
165 device: the device name (e.g. sdc).
166
167 Returns:
168 A string describing |device| (e.g. Patriot Memory 7918 MB).
169 """
170 desc = [
171 osutils.GetDeviceInfo(device, keyword='manufacturer'),
172 osutils.GetDeviceInfo(device, keyword='product'),
173 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
174 '(%s)' % self.DeviceNameToPath(device),
175 ]
176 return ' '.join([x for x in desc if x])
177
178 def ListAllRemovableDevices(self):
179 """Returns a list of removable devices.
180
181 Returns:
182 A list of device names (e.g. ['sdb', 'sdc']).
183 """
184 devices = osutils.ListBlockDevices()
185 removable_devices = []
186 for d in devices:
187 if d.TYPE == 'disk' and d.RM == '1':
188 removable_devices.append(d.NAME)
189
190 return removable_devices
191
192 def ChooseRemovableDevice(self, devices):
193 """Lists all removable devices and asks user to select/confirm.
194
195 Args:
196 devices: a list of device names (e.g. ['sda', 'sdb']).
197
198 Returns:
199 The device name chosen by the user.
200 """
201 idx = cros_build_lib.GetChoice(
202 'Removable device(s) found. Please select/confirm to continue:',
203 [self.GetRemovableDeviceDescription(x) for x in devices])
204
205 return devices[idx]
206
207 def InstallImageToDevice(self, image, device):
208 """Installs |image| to the removable |device|.
209
210 Args:
211 image: Path to the image to copy.
212 device: Device to copy to.
213 """
214 cmd = [
215 'chromeos-install',
216 '--yes',
217 '--skip_src_removable',
218 '--skip_dst_removable',
219 '--payload_image=%s' % image,
220 '--dst=%s' % device,
221 '--skip_postinstall',
222 ]
Mike Frysinger45602c72019-09-22 02:15:11 -0400223 cros_build_lib.sudo_run(cmd,
224 print_cmd=True,
225 debug_level=logging.NOTICE,
Mike Frysinger66d32cd2019-12-17 14:55:29 -0500226 stderr=subprocess.STDOUT,
Mike Frysinger45602c72019-09-22 02:15:11 -0400227 log_output=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700228
229 def CopyImageToDevice(self, image, device):
230 """Copies |image| to the removable |device|.
231
232 Args:
233 image: Path to the image to copy.
234 device: Device to copy to.
235 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700236 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800237 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700238 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
239 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400240 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400241 encoding='utf-8', update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700242 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400243 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700244 cmd, debug_level=logging.NOTICE,
245 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700246
Brian Norris6386fde2018-10-29 13:34:28 -0700247 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
248 # up for us with a 'write' command, so we have a standards-conforming GPT.
249 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
250 # fix GPT sanity issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400251 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500252 check=False,
Mike Frysinger45602c72019-09-22 02:15:11 -0400253 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700254
Mike Frysinger45602c72019-09-22 02:15:11 -0400255 cros_build_lib.sudo_run(['partx', '-u', device],
256 debug_level=self.debug_level)
257 cros_build_lib.sudo_run(['sync', '-d', device],
258 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700259
David Pursellf1d16a62015-03-25 13:31:04 -0700260 def _GetImagePath(self):
261 """Returns the image path to use."""
262 image_path = translated_path = None
263 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700264 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700265 # TODO(wnwen): Open the tarball and if there is just one file in it,
266 # use that instead. Existing code in upload_symbols.py.
267 if cros_build_lib.BooleanPrompt(
268 prolog='The given image file is not a valid disk image. Perhaps '
269 'you forgot to untar it.',
270 prompt='Terminate the current flash process?'):
271 raise FlashError('Update terminated by user.')
272 image_path = self.image
273 elif os.path.isdir(self.image):
274 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700275 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700276 else:
277 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700278 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Don Garrett97d7dc22015-01-20 14:07:56 -0800279 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700280 image_path = ds_wrapper.TranslatedPathToLocalPath(
Don Garrett97d7dc22015-01-20 14:07:56 -0800281 translated_path, DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700282
283 logging.info('Using image %s', translated_path or image_path)
284 return image_path
285
286 def Run(self):
287 """Image the removable device."""
288 devices = self.ListAllRemovableDevices()
289
290 if self.device:
291 # If user specified a device path, check if it exists.
292 if not os.path.exists(self.device):
293 raise FlashError('Device path %s does not exist.' % self.device)
294
295 # Then check if it is removable.
296 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
297 msg = '%s is not a removable device.' % self.device
298 if not (self.yes or cros_build_lib.BooleanPrompt(
299 default=False, prolog=msg)):
300 raise FlashError('You can specify usb:// to choose from a list of '
301 'removable devices.')
302 target = None
303 if self.device:
304 # Get device name from path (e.g. sdc in /dev/sdc).
305 target = self.device.rsplit(os.path.sep, 1)[-1]
306 elif devices:
307 # Ask user to choose from the list.
308 target = self.ChooseRemovableDevice(devices)
309 else:
310 raise FlashError('No removable devices detected.')
311
312 image_path = self._GetImagePath()
313 try:
314 device = self.DeviceNameToPath(target)
315 if self.install:
316 self.InstallImageToDevice(image_path, device)
317 else:
318 self.CopyImageToDevice(image_path, device)
319 except cros_build_lib.RunCommandError:
320 logging.error('Failed copying image to device %s',
321 self.DeviceNameToPath(target))
322
323
324class FileImager(USBImager):
325 """Copy image to the target path."""
326
327 def Run(self):
328 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800329 if not os.path.isdir(os.path.dirname(self.device)):
330 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700331
332 image_path = self._GetImagePath()
333 if os.path.isdir(self.device):
334 logging.info('Copying to %s',
335 os.path.join(self.device, os.path.basename(image_path)))
336 else:
337 logging.info('Copying to %s', self.device)
338 try:
339 shutil.copy(image_path, self.device)
340 except IOError:
341 logging.error('Failed to copy image %s to %s', image_path, self.device)
342
343
344class RemoteDeviceUpdater(object):
345 """Performs update on a remote device."""
David Pursellf1d16a62015-03-25 13:31:04 -0700346 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
347 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700348 # Root working directory on the device. This directory is in the
349 # stateful partition and thus has enough space to store the payloads.
Amin Hassanidc4e7f02020-02-03 11:10:22 -0800350 DEVICE_BASE_DIR = '/usr/local/tmp/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700351 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
352 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700353
354 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
355 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700356 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600357 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800358 disable_verification=False, send_payload_in_parallel=False,
359 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700360 """Initializes RemoteDeviceUpdater"""
361 if not stateful_update and not rootfs_update:
362 raise ValueError('No update operation to perform; either stateful or'
363 ' rootfs partitions must be updated.')
364 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
365 self.ssh_hostname = ssh_hostname
366 self.ssh_port = ssh_port
367 self.image = image
368 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700369 self.src_image_to_delta = src_image_to_delta
370 self.do_stateful_update = stateful_update
371 self.do_rootfs_update = rootfs_update
372 self.disable_verification = disable_verification
373 self.clobber_stateful = clobber_stateful
374 self.reboot = reboot
375 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600376 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700377 self.ping = ping
378 # Do not wipe if debug is set.
379 self.wipe = wipe and not debug
380 self.yes = yes
381 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800382 self.send_payload_in_parallel = send_payload_in_parallel
Amin Hassanie62d2e12019-02-01 10:40:41 -0800383 self.experimental_au = experimental_au
David Pursellf1d16a62015-03-25 13:31:04 -0700384
David Pursellf1d16a62015-03-25 13:31:04 -0700385 def Cleanup(self):
386 """Cleans up the temporary directory."""
387 if self.wipe:
388 logging.info('Cleaning up temporary working directory...')
389 osutils.RmDir(self.tempdir)
390 else:
391 logging.info('You can find the log files and/or payloads in %s',
392 self.tempdir)
393
xixuane851dfb2016-05-02 18:02:37 -0700394 def GetPayloadDir(self, device):
395 """Get directory of payload for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700396
xixuane851dfb2016-05-02 18:02:37 -0700397 This method is used to obtain the directory of payload for cros-flash. The
398 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700399
xixuane851dfb2016-05-02 18:02:37 -0700400 If self.image is a directory, we directly use the provided update payload(s)
401 in this directory.
402
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800403 If self.image is an image, we will generate payloads for it and put them in
404 our temporary directory. The reason is that people may modify a local image
405 or override it (on the same path) with a different image, so in order to be
406 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700407
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800408 If non of the above cases, we use the xbuddy to first obtain the image path
409 (and possibly download it). Then we will generate the payloads in the same
410 directory the image is located. The reason is that this is what devserver
411 used to do. The path to the image generated by the devserver (or xbuddy) is
412 unique and normally nobody override its image with a different one. That is
413 why I think it is safe to put the payloads next to the image. This is a poor
414 man's version of caching but it makes cros flash faster for users who flash
415 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700416
417 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400418 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700419
420 Returns:
xixuane851dfb2016-05-02 18:02:37 -0700421 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700422 """
xixuane851dfb2016-05-02 18:02:37 -0700423 if os.path.isdir(self.image):
424 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800425 logging.info('Using provided payloads in %s', self.image)
426 return self.image
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600427
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200428 image_path = None
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800429 if os.path.isfile(self.image):
430 # The given path is an image.
431 image_path = self.image
432 payload_dir = self.tempdir
xixuane851dfb2016-05-02 18:02:37 -0700433 else:
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800434 # Assuming it is an xbuddy path.
xixuane851dfb2016-05-02 18:02:37 -0700435 self.board = cros_build_lib.GetBoard(device_board=device.board,
436 override_board=self.board,
Eashan Bhatt2f01e422019-07-25 10:31:04 -0700437 force=self.yes,
438 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700439 if not self.force and self.board != device.board:
440 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700441 raise FlashError('Device (%s) is incompatible with board %s' %
442 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700443 logging.info('Board is %s', self.board)
444
Amin Hassanic20a3c32019-06-02 21:43:21 -0700445
446 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
447 # use xbuddy library directly instead of the devserver_wrapper.
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200448 # Fetch the full payload and properties, and stateful files. If this
449 # fails, fallback to downloading the image.
450 try:
451 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800452 os.path.join(self.image, artifact_info.FULL_PAYLOAD), self.board,
Achuith Bhandarkar12f43c72019-11-21 16:44:24 -0800453 static_dir=DEVSERVER_STATIC_DIR, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200454 payload_dir = os.path.dirname(
455 ds_wrapper.TranslatedPathToLocalPath(translated_path,
456 DEVSERVER_STATIC_DIR))
457 ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800458 os.path.join(self.image, artifact_info.STATEFUL_PAYLOAD),
459 self.board, static_dir=DEVSERVER_STATIC_DIR, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200460 fetch_image = False
461 except (ds_wrapper.ImagePathError, ds_wrapper.ArtifactDownloadError):
462 logging.info('Could not find full_payload or stateful for "%s"',
463 self.image)
464 fetch_image = True
xixuane851dfb2016-05-02 18:02:37 -0700465
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200466 # We didn't find the full_payload, attempt to download the image.
467 if fetch_image:
468 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
469 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
470 image_path = ds_wrapper.TranslatedPathToLocalPath(
471 translated_path, DEVSERVER_STATIC_DIR)
472 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
473 logging.notice('Using image path %s and payload directory %s',
474 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700475
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800476 # Generate rootfs and stateful update payloads if they do not exist.
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700477 payload_path = os.path.join(payload_dir,
478 auto_updater_transfer.ROOTFS_FILENAME)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800479 if not os.path.exists(payload_path):
480 paygen_payload_lib.GenerateUpdatePayload(
481 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700482 if not os.path.exists(os.path.join(
483 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800484 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
485 payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700486 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700487
488 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700489 """Perform remote device update.
490
491 The update process includes:
492 1. initialize a device instance for the given remote device.
493 2. achieve payload_dir which contains the required payloads for updating.
494 3. initialize an auto-updater instance to do RunUpdate().
495 4. After auto-update, all temp files and dir will be cleaned up.
496 """
David Pursellf1d16a62015-03-25 13:31:04 -0700497 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700498 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600499 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
500 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700501
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600502 try:
503 # Get payload directory
504 payload_dir = self.GetPayloadDir(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700505
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600506 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700507 chromeos_AU = auto_updater.ChromiumOSUpdater(
508 device=device,
509 build_name=None,
510 payload_dir=payload_dir,
511 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600512 do_rootfs_update=self.do_rootfs_update,
513 do_stateful_update=self.do_stateful_update,
514 reboot=self.reboot,
515 disable_verification=self.disable_verification,
516 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800517 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800518 send_payload_in_parallel=self.send_payload_in_parallel,
Sanika Kulkarni3cb57912020-03-18 15:17:46 -0700519 experimental_au=self.experimental_au,
520 transfer_class=auto_updater_transfer.LocalTransfer)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600521 chromeos_AU.CheckPayloads()
Sanika Kulkarni00b9d682019-11-26 09:43:20 -0800522 chromeos_AU.PreparePayloadPropsFile()
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600523 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700524
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600525 except Exception:
526 logging.error('Device update failed.')
527 lsb_entries = sorted(device.lsb_release.items())
528 logging.info(
529 'Following are the LSB version details of the device:\n%s',
530 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
531 raise
532
533 logging.notice('Update performed successfully.')
534
535 except remote_access.RemoteAccessException:
536 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700537 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600538
David Pursellf1d16a62015-03-25 13:31:04 -0700539 finally:
540 self.Cleanup()
541
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200542
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700543def Flash(device, image, board=None, install=False, src_image_to_delta=None,
544 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600545 reboot=True, wipe=True, ssh_private_key=None, ping=True,
546 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800547 force=False, debug=False, send_payload_in_parallel=False,
548 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700549 """Flashes a device, USB drive, or file with an image.
550
551 This provides functionality common to `cros flash` and `brillo flash`
552 so that they can parse the commandline separately but still use the
553 same underlying functionality.
554
555 Args:
David Pursell2e773382015-04-03 14:30:47 -0700556 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700557 image: Path (string) to the update image. Can be a local or xbuddy path;
558 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700559 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700560 install: Install to USB using base disk layout; USB |device| scheme only.
561 src_image_to_delta: Local path to an image to be used as the base to
562 generate delta payloads; SSH |device| scheme only.
563 rootfs_update: Update rootfs partition; SSH |device| scheme only.
564 stateful_update: Update stateful partition; SSH |device| scheme only.
565 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
566 reboot: Reboot device after update; SSH |device| scheme only.
567 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600568 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700569 ping: Ping the device before attempting update; SSH |device| scheme only.
570 disable_rootfs_verification: Remove rootfs verification after update; SSH
571 |device| scheme only.
572 clear_cache: Clear the devserver static directory.
573 yes: Assume "yes" for any prompt.
574 force: Ignore sanity checks and prompts. Overrides |yes| if True.
575 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800576 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
577 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800578 experimental_au: Use the experimental features auto updater. It should be
579 deprecated once crbug.com/872441 is fixed.
David Pursellf1d16a62015-03-25 13:31:04 -0700580
581 Raises:
582 FlashError: An unrecoverable error occured.
583 ValueError: Invalid parameter combination.
584 """
585 if force:
586 yes = True
587
588 if clear_cache:
589 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800590 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700591
592 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800593 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700594 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800595 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700596
597 if install:
David Pursell2e773382015-04-03 14:30:47 -0700598 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700599 raise ValueError(
600 '--install can only be used when writing to a USB device')
601 if not cros_build_lib.IsInsideChroot():
602 raise ValueError('--install can only be used inside the chroot')
603
David Pursell2e773382015-04-03 14:30:47 -0700604 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
605 if device:
606 hostname, port = device.hostname, device.port
607 else:
608 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700609 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700610 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700611 hostname,
612 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700613 image,
614 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700615 src_image_to_delta=src_image_to_delta,
616 rootfs_update=rootfs_update,
617 stateful_update=stateful_update,
618 clobber_stateful=clobber_stateful,
619 reboot=reboot,
620 wipe=wipe,
621 debug=debug,
622 yes=yes,
623 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600624 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700625 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800626 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800627 send_payload_in_parallel=send_payload_in_parallel,
628 experimental_au=experimental_au)
David Pursellf1d16a62015-03-25 13:31:04 -0700629 updater.Run()
630 elif device.scheme == commandline.DEVICE_SCHEME_USB:
631 path = osutils.ExpandPath(device.path) if device.path else ''
632 logging.info('Preparing to image the removable device %s', path)
633 imager = USBImager(path,
634 board,
635 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700636 debug=debug,
637 install=install,
638 yes=yes)
639 imager.Run()
640 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
641 logging.info('Preparing to copy image to %s', device.path)
642 imager = FileImager(device.path,
643 board,
644 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700645 debug=debug,
646 yes=yes)
647 imager.Run()