blob: b1fae3ae740b9f4ed71f944725657ba5a2f41897 [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
David Pursellf1d16a62015-03-25 13:31:04 -070015import tempfile
David Pursellf1d16a62015-03-25 13:31:04 -070016
Aviv Keshetb7519e12016-10-04 00:50:00 -070017from chromite.lib import constants
xixuane851dfb2016-05-02 18:02:37 -070018from chromite.lib import auto_updater
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -070019from chromite.lib import auto_updater_transfer
David Pursellf1d16a62015-03-25 13:31:04 -070020from chromite.lib import commandline
21from chromite.lib import cros_build_lib
22from chromite.lib import cros_logging as logging
23from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070024from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070025from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070026from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070027from chromite.lib import remote_access
28
Amin Hassanic0f06fa2019-01-28 15:24:47 -080029from chromite.lib.paygen import paygen_payload_lib
30from chromite.lib.paygen import paygen_stateful_payload_lib
31
David Pursellf1d16a62015-03-25 13:31:04 -070032
Don Garrett97d7dc22015-01-20 14:07:56 -080033DEVSERVER_STATIC_DIR = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -070034 os.path.join(constants.CHROOT_SOURCE_ROOT, 'devserver', 'static'))
35
36
Ralph Nathan9b997232015-05-15 13:13:12 -070037class UsbImagerOperation(operation.ProgressBarOperation):
38 """Progress bar for flashing image to operation."""
39
40 def __init__(self, image):
41 super(UsbImagerOperation, self).__init__()
42 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070043 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070044 self._bytes = re.compile(r'(\d+) bytes')
45
46 def _GetDDPid(self):
47 """Get the Pid of dd."""
48 try:
Mike Frysinger45602c72019-09-22 02:15:11 -040049 pids = cros_build_lib.run(['pgrep', 'dd'], capture_output=True,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040050 print_cmd=False, encoding='utf-8').stdout
Ralph Nathan9b997232015-05-15 13:13:12 -070051 for pid in pids.splitlines():
52 if osutils.IsChildProcess(int(pid), name='dd'):
53 return int(pid)
54 return -1
55 except cros_build_lib.RunCommandError:
56 # If dd isn't still running, then we assume that it is finished.
57 return -1
58
59 def _PingDD(self, dd_pid):
60 """Send USR1 signal to dd to get status update."""
61 try:
62 cmd = ['kill', '-USR1', str(dd_pid)]
Mike Frysinger45602c72019-09-22 02:15:11 -040063 cros_build_lib.sudo_run(cmd, print_cmd=False)
Ralph Nathan9b997232015-05-15 13:13:12 -070064 except cros_build_lib.RunCommandError:
65 # Here we assume that dd finished in the background.
66 return
67
68 def ParseOutput(self, output=None):
69 """Parse the output of dd to update progress bar."""
70 dd_pid = self._GetDDPid()
71 if dd_pid == -1:
72 return
73
74 self._PingDD(dd_pid)
75
76 if output is None:
77 stdout = self._stdout.read()
78 stderr = self._stderr.read()
79 output = stdout + stderr
80
81 match = self._bytes.search(output)
82 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070083 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -070084
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040085 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -070086
87
Mike Frysinger32759e42016-12-21 18:40:16 -050088def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070089 """Determines if a file is a valid GPT disk.
90
91 Args:
92 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -050093 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070094 """
95 if os.path.isfile(file_path):
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040096 with open(file_path, 'rb') as image_file:
Mike Frysinger32759e42016-12-21 18:40:16 -050097 if require_pmbr:
98 # Seek to the end of LBA0 and look for the PMBR boot signature.
99 image_file.seek(0x1fe)
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400100 if image_file.read(2) != b'\x55\xaa':
Mike Frysinger32759e42016-12-21 18:40:16 -0500101 return False
102 # Current file position is start of LBA1 now.
103 else:
104 # Seek to LBA1 where the GPT starts.
105 image_file.seek(0x200)
106
107 # See if there's a GPT here.
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400108 if image_file.read(8) == b'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700109 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500110
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700111 return False
112
113
114def _ChooseImageFromDirectory(dir_path):
115 """Lists all image files in |dir_path| and ask user to select one.
116
117 Args:
118 dir_path: Path to the directory.
119 """
120 images = sorted([x for x in os.listdir(dir_path) if
121 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
122 idx = 0
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400123 if not images:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700124 raise ValueError('No image found in %s.' % dir_path)
125 elif len(images) > 1:
126 idx = cros_build_lib.GetChoice(
127 'Multiple images found in %s. Please select one to continue:' % (
128 (dir_path,)),
129 images)
130
131 return os.path.join(dir_path, images[idx])
132
133
David Pursellf1d16a62015-03-25 13:31:04 -0700134class FlashError(Exception):
135 """Thrown when there is an unrecoverable error during flash."""
136
137
138class USBImager(object):
139 """Copy image to the target removable device."""
140
Gilad Arnoldd0461442015-07-07 11:52:46 -0700141 def __init__(self, device, board, image, debug=False, install=False,
142 yes=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700143 """Initalizes USBImager."""
144 self.device = device
145 self.board = board if board else cros_build_lib.GetDefaultBoard()
146 self.image = image
David Pursellf1d16a62015-03-25 13:31:04 -0700147 self.debug = debug
148 self.debug_level = logging.DEBUG if debug else logging.INFO
149 self.install = install
150 self.yes = yes
151
152 def DeviceNameToPath(self, device_name):
153 return '/dev/%s' % device_name
154
155 def GetRemovableDeviceDescription(self, device):
156 """Returns a informational description of the removable |device|.
157
158 Args:
159 device: the device name (e.g. sdc).
160
161 Returns:
162 A string describing |device| (e.g. Patriot Memory 7918 MB).
163 """
164 desc = [
165 osutils.GetDeviceInfo(device, keyword='manufacturer'),
166 osutils.GetDeviceInfo(device, keyword='product'),
167 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
168 '(%s)' % self.DeviceNameToPath(device),
169 ]
170 return ' '.join([x for x in desc if x])
171
172 def ListAllRemovableDevices(self):
173 """Returns a list of removable devices.
174
175 Returns:
176 A list of device names (e.g. ['sdb', 'sdc']).
177 """
178 devices = osutils.ListBlockDevices()
179 removable_devices = []
180 for d in devices:
181 if d.TYPE == 'disk' and d.RM == '1':
182 removable_devices.append(d.NAME)
183
184 return removable_devices
185
186 def ChooseRemovableDevice(self, devices):
187 """Lists all removable devices and asks user to select/confirm.
188
189 Args:
190 devices: a list of device names (e.g. ['sda', 'sdb']).
191
192 Returns:
193 The device name chosen by the user.
194 """
195 idx = cros_build_lib.GetChoice(
196 'Removable device(s) found. Please select/confirm to continue:',
197 [self.GetRemovableDeviceDescription(x) for x in devices])
198
199 return devices[idx]
200
201 def InstallImageToDevice(self, image, device):
202 """Installs |image| to the removable |device|.
203
204 Args:
205 image: Path to the image to copy.
206 device: Device to copy to.
207 """
208 cmd = [
209 'chromeos-install',
210 '--yes',
211 '--skip_src_removable',
212 '--skip_dst_removable',
213 '--payload_image=%s' % image,
214 '--dst=%s' % device,
215 '--skip_postinstall',
216 ]
Mike Frysinger45602c72019-09-22 02:15:11 -0400217 cros_build_lib.sudo_run(cmd,
218 print_cmd=True,
219 debug_level=logging.NOTICE,
Mike Frysinger66d32cd2019-12-17 14:55:29 -0500220 stderr=subprocess.STDOUT,
Mike Frysinger45602c72019-09-22 02:15:11 -0400221 log_output=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700222
223 def CopyImageToDevice(self, image, device):
224 """Copies |image| to the removable |device|.
225
226 Args:
227 image: Path to the image to copy.
228 device: Device to copy to.
229 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700230 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800231 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700232 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
233 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400234 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400235 encoding='utf-8', update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700236 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400237 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700238 cmd, debug_level=logging.NOTICE,
239 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700240
Brian Norris6386fde2018-10-29 13:34:28 -0700241 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
242 # up for us with a 'write' command, so we have a standards-conforming GPT.
243 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
244 # fix GPT sanity issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400245 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500246 check=False,
Mike Frysinger45602c72019-09-22 02:15:11 -0400247 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700248
Mike Frysinger45602c72019-09-22 02:15:11 -0400249 cros_build_lib.sudo_run(['partx', '-u', device],
250 debug_level=self.debug_level)
251 cros_build_lib.sudo_run(['sync', '-d', device],
252 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700253
David Pursellf1d16a62015-03-25 13:31:04 -0700254 def _GetImagePath(self):
255 """Returns the image path to use."""
256 image_path = translated_path = None
257 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700258 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700259 # TODO(wnwen): Open the tarball and if there is just one file in it,
260 # use that instead. Existing code in upload_symbols.py.
261 if cros_build_lib.BooleanPrompt(
262 prolog='The given image file is not a valid disk image. Perhaps '
263 'you forgot to untar it.',
264 prompt='Terminate the current flash process?'):
265 raise FlashError('Update terminated by user.')
266 image_path = self.image
267 elif os.path.isdir(self.image):
268 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700269 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700270 else:
271 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700272 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Don Garrett97d7dc22015-01-20 14:07:56 -0800273 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700274 image_path = ds_wrapper.TranslatedPathToLocalPath(
Don Garrett97d7dc22015-01-20 14:07:56 -0800275 translated_path, DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700276
277 logging.info('Using image %s', translated_path or image_path)
278 return image_path
279
280 def Run(self):
281 """Image the removable device."""
282 devices = self.ListAllRemovableDevices()
283
284 if self.device:
285 # If user specified a device path, check if it exists.
286 if not os.path.exists(self.device):
287 raise FlashError('Device path %s does not exist.' % self.device)
288
289 # Then check if it is removable.
290 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
291 msg = '%s is not a removable device.' % self.device
292 if not (self.yes or cros_build_lib.BooleanPrompt(
293 default=False, prolog=msg)):
294 raise FlashError('You can specify usb:// to choose from a list of '
295 'removable devices.')
296 target = None
297 if self.device:
298 # Get device name from path (e.g. sdc in /dev/sdc).
299 target = self.device.rsplit(os.path.sep, 1)[-1]
300 elif devices:
301 # Ask user to choose from the list.
302 target = self.ChooseRemovableDevice(devices)
303 else:
304 raise FlashError('No removable devices detected.')
305
306 image_path = self._GetImagePath()
307 try:
308 device = self.DeviceNameToPath(target)
309 if self.install:
310 self.InstallImageToDevice(image_path, device)
311 else:
312 self.CopyImageToDevice(image_path, device)
313 except cros_build_lib.RunCommandError:
314 logging.error('Failed copying image to device %s',
315 self.DeviceNameToPath(target))
316
317
318class FileImager(USBImager):
319 """Copy image to the target path."""
320
321 def Run(self):
322 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800323 if not os.path.isdir(os.path.dirname(self.device)):
324 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700325
326 image_path = self._GetImagePath()
327 if os.path.isdir(self.device):
328 logging.info('Copying to %s',
329 os.path.join(self.device, os.path.basename(image_path)))
330 else:
331 logging.info('Copying to %s', self.device)
332 try:
333 shutil.copy(image_path, self.device)
334 except IOError:
335 logging.error('Failed to copy image %s to %s', image_path, self.device)
336
337
338class RemoteDeviceUpdater(object):
339 """Performs update on a remote device."""
David Pursellf1d16a62015-03-25 13:31:04 -0700340 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
341 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700342 # Root working directory on the device. This directory is in the
343 # stateful partition and thus has enough space to store the payloads.
344 DEVICE_BASE_DIR = '/mnt/stateful_partition/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700345 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
346 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700347
348 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
349 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700350 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600351 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800352 disable_verification=False, send_payload_in_parallel=False,
353 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700354 """Initializes RemoteDeviceUpdater"""
355 if not stateful_update and not rootfs_update:
356 raise ValueError('No update operation to perform; either stateful or'
357 ' rootfs partitions must be updated.')
358 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
359 self.ssh_hostname = ssh_hostname
360 self.ssh_port = ssh_port
361 self.image = image
362 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700363 self.src_image_to_delta = src_image_to_delta
364 self.do_stateful_update = stateful_update
365 self.do_rootfs_update = rootfs_update
366 self.disable_verification = disable_verification
367 self.clobber_stateful = clobber_stateful
368 self.reboot = reboot
369 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600370 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700371 self.ping = ping
372 # Do not wipe if debug is set.
373 self.wipe = wipe and not debug
374 self.yes = yes
375 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800376 self.send_payload_in_parallel = send_payload_in_parallel
Amin Hassanie62d2e12019-02-01 10:40:41 -0800377 self.experimental_au = experimental_au
David Pursellf1d16a62015-03-25 13:31:04 -0700378
David Pursellf1d16a62015-03-25 13:31:04 -0700379 def Cleanup(self):
380 """Cleans up the temporary directory."""
381 if self.wipe:
382 logging.info('Cleaning up temporary working directory...')
383 osutils.RmDir(self.tempdir)
384 else:
385 logging.info('You can find the log files and/or payloads in %s',
386 self.tempdir)
387
xixuane851dfb2016-05-02 18:02:37 -0700388 def GetPayloadDir(self, device):
389 """Get directory of payload for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700390
xixuane851dfb2016-05-02 18:02:37 -0700391 This method is used to obtain the directory of payload for cros-flash. The
392 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700393
xixuane851dfb2016-05-02 18:02:37 -0700394 If self.image is a directory, we directly use the provided update payload(s)
395 in this directory.
396
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800397 If self.image is an image, we will generate payloads for it and put them in
398 our temporary directory. The reason is that people may modify a local image
399 or override it (on the same path) with a different image, so in order to be
400 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700401
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800402 If non of the above cases, we use the xbuddy to first obtain the image path
403 (and possibly download it). Then we will generate the payloads in the same
404 directory the image is located. The reason is that this is what devserver
405 used to do. The path to the image generated by the devserver (or xbuddy) is
406 unique and normally nobody override its image with a different one. That is
407 why I think it is safe to put the payloads next to the image. This is a poor
408 man's version of caching but it makes cros flash faster for users who flash
409 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700410
411 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400412 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700413
414 Returns:
xixuane851dfb2016-05-02 18:02:37 -0700415 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700416 """
xixuane851dfb2016-05-02 18:02:37 -0700417 if os.path.isdir(self.image):
418 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800419 logging.info('Using provided payloads in %s', self.image)
420 return self.image
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600421
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200422 image_path = None
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800423 if os.path.isfile(self.image):
424 # The given path is an image.
425 image_path = self.image
426 payload_dir = self.tempdir
xixuane851dfb2016-05-02 18:02:37 -0700427 else:
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800428 # Assuming it is an xbuddy path.
xixuane851dfb2016-05-02 18:02:37 -0700429 self.board = cros_build_lib.GetBoard(device_board=device.board,
430 override_board=self.board,
Eashan Bhatt2f01e422019-07-25 10:31:04 -0700431 force=self.yes,
432 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700433 if not self.force and self.board != device.board:
434 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700435 raise FlashError('Device (%s) is incompatible with board %s' %
436 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700437 logging.info('Board is %s', self.board)
438
Amin Hassanic20a3c32019-06-02 21:43:21 -0700439
440 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
441 # use xbuddy library directly instead of the devserver_wrapper.
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200442 # Fetch the full payload and properties, and stateful files. If this
443 # fails, fallback to downloading the image.
444 try:
445 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
446 os.path.join(self.image, 'full_payload'), self.board,
Achuith Bhandarkar12f43c72019-11-21 16:44:24 -0800447 static_dir=DEVSERVER_STATIC_DIR, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200448 payload_dir = os.path.dirname(
449 ds_wrapper.TranslatedPathToLocalPath(translated_path,
450 DEVSERVER_STATIC_DIR))
451 ds_wrapper.GetImagePathWithXbuddy(
452 os.path.join(self.image, 'stateful'), 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 fetch_image = False
455 except (ds_wrapper.ImagePathError, ds_wrapper.ArtifactDownloadError):
456 logging.info('Could not find full_payload or stateful for "%s"',
457 self.image)
458 fetch_image = True
xixuane851dfb2016-05-02 18:02:37 -0700459
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200460 # We didn't find the full_payload, attempt to download the image.
461 if fetch_image:
462 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
463 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
464 image_path = ds_wrapper.TranslatedPathToLocalPath(
465 translated_path, DEVSERVER_STATIC_DIR)
466 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
467 logging.notice('Using image path %s and payload directory %s',
468 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700469
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800470 # Generate rootfs and stateful update payloads if they do not exist.
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700471 payload_path = os.path.join(payload_dir,
472 auto_updater_transfer.ROOTFS_FILENAME)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800473 if not os.path.exists(payload_path):
474 paygen_payload_lib.GenerateUpdatePayload(
475 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700476 if not os.path.exists(os.path.join(
477 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800478 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
479 payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700480 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700481
482 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700483 """Perform remote device update.
484
485 The update process includes:
486 1. initialize a device instance for the given remote device.
487 2. achieve payload_dir which contains the required payloads for updating.
488 3. initialize an auto-updater instance to do RunUpdate().
489 4. After auto-update, all temp files and dir will be cleaned up.
490 """
David Pursellf1d16a62015-03-25 13:31:04 -0700491 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700492 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600493 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
494 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700495
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600496 try:
497 # Get payload directory
498 payload_dir = self.GetPayloadDir(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700499
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600500 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700501 chromeos_AU = auto_updater.ChromiumOSUpdater(
502 device=device,
503 build_name=None,
504 payload_dir=payload_dir,
505 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600506 do_rootfs_update=self.do_rootfs_update,
507 do_stateful_update=self.do_stateful_update,
508 reboot=self.reboot,
509 disable_verification=self.disable_verification,
510 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800511 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800512 send_payload_in_parallel=self.send_payload_in_parallel,
513 experimental_au=self.experimental_au)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600514 chromeos_AU.CheckPayloads()
Sanika Kulkarni00b9d682019-11-26 09:43:20 -0800515 chromeos_AU.PreparePayloadPropsFile()
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600516 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700517
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600518 except Exception:
519 logging.error('Device update failed.')
520 lsb_entries = sorted(device.lsb_release.items())
521 logging.info(
522 'Following are the LSB version details of the device:\n%s',
523 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
524 raise
525
526 logging.notice('Update performed successfully.')
527
528 except remote_access.RemoteAccessException:
529 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700530 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600531
David Pursellf1d16a62015-03-25 13:31:04 -0700532 finally:
533 self.Cleanup()
534
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200535
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700536def Flash(device, image, board=None, install=False, src_image_to_delta=None,
537 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600538 reboot=True, wipe=True, ssh_private_key=None, ping=True,
539 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800540 force=False, debug=False, send_payload_in_parallel=False,
541 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700542 """Flashes a device, USB drive, or file with an image.
543
544 This provides functionality common to `cros flash` and `brillo flash`
545 so that they can parse the commandline separately but still use the
546 same underlying functionality.
547
548 Args:
David Pursell2e773382015-04-03 14:30:47 -0700549 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700550 image: Path (string) to the update image. Can be a local or xbuddy path;
551 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700552 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700553 install: Install to USB using base disk layout; USB |device| scheme only.
554 src_image_to_delta: Local path to an image to be used as the base to
555 generate delta payloads; SSH |device| scheme only.
556 rootfs_update: Update rootfs partition; SSH |device| scheme only.
557 stateful_update: Update stateful partition; SSH |device| scheme only.
558 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
559 reboot: Reboot device after update; SSH |device| scheme only.
560 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600561 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700562 ping: Ping the device before attempting update; SSH |device| scheme only.
563 disable_rootfs_verification: Remove rootfs verification after update; SSH
564 |device| scheme only.
565 clear_cache: Clear the devserver static directory.
566 yes: Assume "yes" for any prompt.
567 force: Ignore sanity checks and prompts. Overrides |yes| if True.
568 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800569 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
570 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800571 experimental_au: Use the experimental features auto updater. It should be
572 deprecated once crbug.com/872441 is fixed.
David Pursellf1d16a62015-03-25 13:31:04 -0700573
574 Raises:
575 FlashError: An unrecoverable error occured.
576 ValueError: Invalid parameter combination.
577 """
578 if force:
579 yes = True
580
581 if clear_cache:
582 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800583 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700584
585 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800586 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700587 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800588 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700589
590 if install:
David Pursell2e773382015-04-03 14:30:47 -0700591 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700592 raise ValueError(
593 '--install can only be used when writing to a USB device')
594 if not cros_build_lib.IsInsideChroot():
595 raise ValueError('--install can only be used inside the chroot')
596
David Pursell2e773382015-04-03 14:30:47 -0700597 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
598 if device:
599 hostname, port = device.hostname, device.port
600 else:
601 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700602 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700603 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700604 hostname,
605 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700606 image,
607 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700608 src_image_to_delta=src_image_to_delta,
609 rootfs_update=rootfs_update,
610 stateful_update=stateful_update,
611 clobber_stateful=clobber_stateful,
612 reboot=reboot,
613 wipe=wipe,
614 debug=debug,
615 yes=yes,
616 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600617 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700618 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800619 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800620 send_payload_in_parallel=send_payload_in_parallel,
621 experimental_au=experimental_au)
David Pursellf1d16a62015-03-25 13:31:04 -0700622 updater.Run()
623 elif device.scheme == commandline.DEVICE_SCHEME_USB:
624 path = osutils.ExpandPath(device.path) if device.path else ''
625 logging.info('Preparing to image the removable device %s', path)
626 imager = USBImager(path,
627 board,
628 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700629 debug=debug,
630 install=install,
631 yes=yes)
632 imager.Run()
633 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
634 logging.info('Preparing to copy image to %s', device.path)
635 imager = FileImager(device.path,
636 board,
637 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700638 debug=debug,
639 yes=yes)
640 imager.Run()