blob: 8fca57d5d564423bdd129e02a820caeae27e95ce [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,
519 experimental_au=self.experimental_au)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600520 chromeos_AU.CheckPayloads()
Sanika Kulkarni00b9d682019-11-26 09:43:20 -0800521 chromeos_AU.PreparePayloadPropsFile()
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600522 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700523
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600524 except Exception:
525 logging.error('Device update failed.')
526 lsb_entries = sorted(device.lsb_release.items())
527 logging.info(
528 'Following are the LSB version details of the device:\n%s',
529 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
530 raise
531
532 logging.notice('Update performed successfully.')
533
534 except remote_access.RemoteAccessException:
535 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700536 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600537
David Pursellf1d16a62015-03-25 13:31:04 -0700538 finally:
539 self.Cleanup()
540
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200541
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700542def Flash(device, image, board=None, install=False, src_image_to_delta=None,
543 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600544 reboot=True, wipe=True, ssh_private_key=None, ping=True,
545 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800546 force=False, debug=False, send_payload_in_parallel=False,
547 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700548 """Flashes a device, USB drive, or file with an image.
549
550 This provides functionality common to `cros flash` and `brillo flash`
551 so that they can parse the commandline separately but still use the
552 same underlying functionality.
553
554 Args:
David Pursell2e773382015-04-03 14:30:47 -0700555 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700556 image: Path (string) to the update image. Can be a local or xbuddy path;
557 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700558 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700559 install: Install to USB using base disk layout; USB |device| scheme only.
560 src_image_to_delta: Local path to an image to be used as the base to
561 generate delta payloads; SSH |device| scheme only.
562 rootfs_update: Update rootfs partition; SSH |device| scheme only.
563 stateful_update: Update stateful partition; SSH |device| scheme only.
564 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
565 reboot: Reboot device after update; SSH |device| scheme only.
566 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600567 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700568 ping: Ping the device before attempting update; SSH |device| scheme only.
569 disable_rootfs_verification: Remove rootfs verification after update; SSH
570 |device| scheme only.
571 clear_cache: Clear the devserver static directory.
572 yes: Assume "yes" for any prompt.
573 force: Ignore sanity checks and prompts. Overrides |yes| if True.
574 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800575 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
576 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800577 experimental_au: Use the experimental features auto updater. It should be
578 deprecated once crbug.com/872441 is fixed.
David Pursellf1d16a62015-03-25 13:31:04 -0700579
580 Raises:
581 FlashError: An unrecoverable error occured.
582 ValueError: Invalid parameter combination.
583 """
584 if force:
585 yes = True
586
587 if clear_cache:
588 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800589 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700590
591 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800592 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700593 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800594 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700595
596 if install:
David Pursell2e773382015-04-03 14:30:47 -0700597 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700598 raise ValueError(
599 '--install can only be used when writing to a USB device')
600 if not cros_build_lib.IsInsideChroot():
601 raise ValueError('--install can only be used inside the chroot')
602
David Pursell2e773382015-04-03 14:30:47 -0700603 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
604 if device:
605 hostname, port = device.hostname, device.port
606 else:
607 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700608 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700609 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700610 hostname,
611 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700612 image,
613 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700614 src_image_to_delta=src_image_to_delta,
615 rootfs_update=rootfs_update,
616 stateful_update=stateful_update,
617 clobber_stateful=clobber_stateful,
618 reboot=reboot,
619 wipe=wipe,
620 debug=debug,
621 yes=yes,
622 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600623 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700624 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800625 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800626 send_payload_in_parallel=send_payload_in_parallel,
627 experimental_au=experimental_au)
David Pursellf1d16a62015-03-25 13:31:04 -0700628 updater.Run()
629 elif device.scheme == commandline.DEVICE_SCHEME_USB:
630 path = osutils.ExpandPath(device.path) if device.path else ''
631 logging.info('Preparing to image the removable device %s', path)
632 imager = USBImager(path,
633 board,
634 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700635 debug=debug,
636 install=install,
637 yes=yes)
638 imager.Run()
639 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
640 logging.info('Preparing to copy image to %s', device.path)
641 imager = FileImager(device.path,
642 board,
643 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700644 debug=debug,
645 yes=yes)
646 imager.Run()