blob: 902b193743436df58ca2e9a7a9b332fe8b944aa7 [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
Amin Hassani1c25d4b2019-11-22 10:59:07 -080032from chromite.lib.xbuddy import artifact_info
33
David Pursellf1d16a62015-03-25 13:31:04 -070034
Don Garrett97d7dc22015-01-20 14:07:56 -080035DEVSERVER_STATIC_DIR = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -070036 os.path.join(constants.CHROOT_SOURCE_ROOT, 'devserver', 'static'))
37
38
Ralph Nathan9b997232015-05-15 13:13:12 -070039class UsbImagerOperation(operation.ProgressBarOperation):
40 """Progress bar for flashing image to operation."""
41
42 def __init__(self, image):
43 super(UsbImagerOperation, self).__init__()
44 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070045 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070046 self._bytes = re.compile(r'(\d+) bytes')
47
48 def _GetDDPid(self):
49 """Get the Pid of dd."""
50 try:
Mike Frysinger45602c72019-09-22 02:15:11 -040051 pids = cros_build_lib.run(['pgrep', 'dd'], capture_output=True,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040052 print_cmd=False, encoding='utf-8').stdout
Ralph Nathan9b997232015-05-15 13:13:12 -070053 for pid in pids.splitlines():
54 if osutils.IsChildProcess(int(pid), name='dd'):
55 return int(pid)
56 return -1
57 except cros_build_lib.RunCommandError:
58 # If dd isn't still running, then we assume that it is finished.
59 return -1
60
61 def _PingDD(self, dd_pid):
62 """Send USR1 signal to dd to get status update."""
63 try:
64 cmd = ['kill', '-USR1', str(dd_pid)]
Mike Frysinger45602c72019-09-22 02:15:11 -040065 cros_build_lib.sudo_run(cmd, print_cmd=False)
Ralph Nathan9b997232015-05-15 13:13:12 -070066 except cros_build_lib.RunCommandError:
67 # Here we assume that dd finished in the background.
68 return
69
70 def ParseOutput(self, output=None):
71 """Parse the output of dd to update progress bar."""
72 dd_pid = self._GetDDPid()
73 if dd_pid == -1:
74 return
75
76 self._PingDD(dd_pid)
77
78 if output is None:
79 stdout = self._stdout.read()
80 stderr = self._stderr.read()
81 output = stdout + stderr
82
83 match = self._bytes.search(output)
84 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070085 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -070086
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040087 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -070088
89
Mike Frysinger32759e42016-12-21 18:40:16 -050090def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070091 """Determines if a file is a valid GPT disk.
92
93 Args:
94 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -050095 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070096 """
97 if os.path.isfile(file_path):
Mike Frysinger3d5de8f2019-10-23 00:48:39 -040098 with open(file_path, 'rb') as image_file:
Mike Frysinger32759e42016-12-21 18:40:16 -050099 if require_pmbr:
100 # Seek to the end of LBA0 and look for the PMBR boot signature.
101 image_file.seek(0x1fe)
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400102 if image_file.read(2) != b'\x55\xaa':
Mike Frysinger32759e42016-12-21 18:40:16 -0500103 return False
104 # Current file position is start of LBA1 now.
105 else:
106 # Seek to LBA1 where the GPT starts.
107 image_file.seek(0x200)
108
109 # See if there's a GPT here.
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400110 if image_file.read(8) == b'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700111 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500112
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700113 return False
114
115
116def _ChooseImageFromDirectory(dir_path):
117 """Lists all image files in |dir_path| and ask user to select one.
118
119 Args:
120 dir_path: Path to the directory.
121 """
122 images = sorted([x for x in os.listdir(dir_path) if
123 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
124 idx = 0
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400125 if not images:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700126 raise ValueError('No image found in %s.' % dir_path)
127 elif len(images) > 1:
128 idx = cros_build_lib.GetChoice(
129 'Multiple images found in %s. Please select one to continue:' % (
130 (dir_path,)),
131 images)
132
133 return os.path.join(dir_path, images[idx])
134
135
David Pursellf1d16a62015-03-25 13:31:04 -0700136class FlashError(Exception):
137 """Thrown when there is an unrecoverable error during flash."""
138
139
140class USBImager(object):
141 """Copy image to the target removable device."""
142
Gilad Arnoldd0461442015-07-07 11:52:46 -0700143 def __init__(self, device, board, image, debug=False, install=False,
144 yes=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700145 """Initalizes USBImager."""
146 self.device = device
147 self.board = board if board else cros_build_lib.GetDefaultBoard()
148 self.image = image
David Pursellf1d16a62015-03-25 13:31:04 -0700149 self.debug = debug
150 self.debug_level = logging.DEBUG if debug else logging.INFO
151 self.install = install
152 self.yes = yes
153
154 def DeviceNameToPath(self, device_name):
155 return '/dev/%s' % device_name
156
157 def GetRemovableDeviceDescription(self, device):
158 """Returns a informational description of the removable |device|.
159
160 Args:
161 device: the device name (e.g. sdc).
162
163 Returns:
164 A string describing |device| (e.g. Patriot Memory 7918 MB).
165 """
166 desc = [
167 osutils.GetDeviceInfo(device, keyword='manufacturer'),
168 osutils.GetDeviceInfo(device, keyword='product'),
169 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
170 '(%s)' % self.DeviceNameToPath(device),
171 ]
172 return ' '.join([x for x in desc if x])
173
174 def ListAllRemovableDevices(self):
175 """Returns a list of removable devices.
176
177 Returns:
178 A list of device names (e.g. ['sdb', 'sdc']).
179 """
180 devices = osutils.ListBlockDevices()
181 removable_devices = []
182 for d in devices:
183 if d.TYPE == 'disk' and d.RM == '1':
184 removable_devices.append(d.NAME)
185
186 return removable_devices
187
188 def ChooseRemovableDevice(self, devices):
189 """Lists all removable devices and asks user to select/confirm.
190
191 Args:
192 devices: a list of device names (e.g. ['sda', 'sdb']).
193
194 Returns:
195 The device name chosen by the user.
196 """
197 idx = cros_build_lib.GetChoice(
198 'Removable device(s) found. Please select/confirm to continue:',
199 [self.GetRemovableDeviceDescription(x) for x in devices])
200
201 return devices[idx]
202
203 def InstallImageToDevice(self, image, device):
204 """Installs |image| to the removable |device|.
205
206 Args:
207 image: Path to the image to copy.
208 device: Device to copy to.
209 """
210 cmd = [
211 'chromeos-install',
212 '--yes',
213 '--skip_src_removable',
214 '--skip_dst_removable',
215 '--payload_image=%s' % image,
216 '--dst=%s' % device,
217 '--skip_postinstall',
218 ]
Mike Frysinger45602c72019-09-22 02:15:11 -0400219 cros_build_lib.sudo_run(cmd,
220 print_cmd=True,
221 debug_level=logging.NOTICE,
Mike Frysinger66d32cd2019-12-17 14:55:29 -0500222 stderr=subprocess.STDOUT,
Mike Frysinger45602c72019-09-22 02:15:11 -0400223 log_output=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700224
225 def CopyImageToDevice(self, image, device):
226 """Copies |image| to the removable |device|.
227
228 Args:
229 image: Path to the image to copy.
230 device: Device to copy to.
231 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700232 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800233 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700234 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
235 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400236 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400237 encoding='utf-8', update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700238 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400239 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700240 cmd, debug_level=logging.NOTICE,
241 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700242
Brian Norris6386fde2018-10-29 13:34:28 -0700243 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
244 # up for us with a 'write' command, so we have a standards-conforming GPT.
245 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
246 # fix GPT sanity issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400247 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500248 check=False,
Mike Frysinger45602c72019-09-22 02:15:11 -0400249 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700250
Mike Frysinger45602c72019-09-22 02:15:11 -0400251 cros_build_lib.sudo_run(['partx', '-u', device],
252 debug_level=self.debug_level)
253 cros_build_lib.sudo_run(['sync', '-d', device],
254 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700255
David Pursellf1d16a62015-03-25 13:31:04 -0700256 def _GetImagePath(self):
257 """Returns the image path to use."""
258 image_path = translated_path = None
259 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700260 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700261 # TODO(wnwen): Open the tarball and if there is just one file in it,
262 # use that instead. Existing code in upload_symbols.py.
263 if cros_build_lib.BooleanPrompt(
264 prolog='The given image file is not a valid disk image. Perhaps '
265 'you forgot to untar it.',
266 prompt='Terminate the current flash process?'):
267 raise FlashError('Update terminated by user.')
268 image_path = self.image
269 elif os.path.isdir(self.image):
270 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700271 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700272 else:
273 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700274 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Don Garrett97d7dc22015-01-20 14:07:56 -0800275 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700276 image_path = ds_wrapper.TranslatedPathToLocalPath(
Don Garrett97d7dc22015-01-20 14:07:56 -0800277 translated_path, DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700278
279 logging.info('Using image %s', translated_path or image_path)
280 return image_path
281
282 def Run(self):
283 """Image the removable device."""
284 devices = self.ListAllRemovableDevices()
285
286 if self.device:
287 # If user specified a device path, check if it exists.
288 if not os.path.exists(self.device):
289 raise FlashError('Device path %s does not exist.' % self.device)
290
291 # Then check if it is removable.
292 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
293 msg = '%s is not a removable device.' % self.device
294 if not (self.yes or cros_build_lib.BooleanPrompt(
295 default=False, prolog=msg)):
296 raise FlashError('You can specify usb:// to choose from a list of '
297 'removable devices.')
298 target = None
299 if self.device:
300 # Get device name from path (e.g. sdc in /dev/sdc).
301 target = self.device.rsplit(os.path.sep, 1)[-1]
302 elif devices:
303 # Ask user to choose from the list.
304 target = self.ChooseRemovableDevice(devices)
305 else:
306 raise FlashError('No removable devices detected.')
307
308 image_path = self._GetImagePath()
309 try:
310 device = self.DeviceNameToPath(target)
311 if self.install:
312 self.InstallImageToDevice(image_path, device)
313 else:
314 self.CopyImageToDevice(image_path, device)
315 except cros_build_lib.RunCommandError:
316 logging.error('Failed copying image to device %s',
317 self.DeviceNameToPath(target))
318
319
320class FileImager(USBImager):
321 """Copy image to the target path."""
322
323 def Run(self):
324 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800325 if not os.path.isdir(os.path.dirname(self.device)):
326 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700327
328 image_path = self._GetImagePath()
329 if os.path.isdir(self.device):
330 logging.info('Copying to %s',
331 os.path.join(self.device, os.path.basename(image_path)))
332 else:
333 logging.info('Copying to %s', self.device)
334 try:
335 shutil.copy(image_path, self.device)
336 except IOError:
337 logging.error('Failed to copy image %s to %s', image_path, self.device)
338
339
340class RemoteDeviceUpdater(object):
341 """Performs update on a remote device."""
David Pursellf1d16a62015-03-25 13:31:04 -0700342 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
343 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700344 # Root working directory on the device. This directory is in the
345 # stateful partition and thus has enough space to store the payloads.
Amin Hassanidc4e7f02020-02-03 11:10:22 -0800346 DEVICE_BASE_DIR = '/usr/local/tmp/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700347 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
348 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700349
350 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
351 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700352 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600353 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800354 disable_verification=False, send_payload_in_parallel=False,
355 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700356 """Initializes RemoteDeviceUpdater"""
357 if not stateful_update and not rootfs_update:
358 raise ValueError('No update operation to perform; either stateful or'
359 ' rootfs partitions must be updated.')
360 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
361 self.ssh_hostname = ssh_hostname
362 self.ssh_port = ssh_port
363 self.image = image
364 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700365 self.src_image_to_delta = src_image_to_delta
366 self.do_stateful_update = stateful_update
367 self.do_rootfs_update = rootfs_update
368 self.disable_verification = disable_verification
369 self.clobber_stateful = clobber_stateful
370 self.reboot = reboot
371 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600372 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700373 self.ping = ping
374 # Do not wipe if debug is set.
375 self.wipe = wipe and not debug
376 self.yes = yes
377 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800378 self.send_payload_in_parallel = send_payload_in_parallel
Amin Hassanie62d2e12019-02-01 10:40:41 -0800379 self.experimental_au = experimental_au
David Pursellf1d16a62015-03-25 13:31:04 -0700380
David Pursellf1d16a62015-03-25 13:31:04 -0700381 def Cleanup(self):
382 """Cleans up the temporary directory."""
383 if self.wipe:
384 logging.info('Cleaning up temporary working directory...')
385 osutils.RmDir(self.tempdir)
386 else:
387 logging.info('You can find the log files and/or payloads in %s',
388 self.tempdir)
389
xixuane851dfb2016-05-02 18:02:37 -0700390 def GetPayloadDir(self, device):
391 """Get directory of payload for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700392
xixuane851dfb2016-05-02 18:02:37 -0700393 This method is used to obtain the directory of payload for cros-flash. The
394 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700395
xixuane851dfb2016-05-02 18:02:37 -0700396 If self.image is a directory, we directly use the provided update payload(s)
397 in this directory.
398
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800399 If self.image is an image, we will generate payloads for it and put them in
400 our temporary directory. The reason is that people may modify a local image
401 or override it (on the same path) with a different image, so in order to be
402 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700403
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800404 If non of the above cases, we use the xbuddy to first obtain the image path
405 (and possibly download it). Then we will generate the payloads in the same
406 directory the image is located. The reason is that this is what devserver
407 used to do. The path to the image generated by the devserver (or xbuddy) is
408 unique and normally nobody override its image with a different one. That is
409 why I think it is safe to put the payloads next to the image. This is a poor
410 man's version of caching but it makes cros flash faster for users who flash
411 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700412
413 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400414 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700415
416 Returns:
xixuane851dfb2016-05-02 18:02:37 -0700417 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700418 """
xixuane851dfb2016-05-02 18:02:37 -0700419 if os.path.isdir(self.image):
420 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800421 logging.info('Using provided payloads in %s', self.image)
422 return self.image
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600423
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200424 image_path = None
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800425 if os.path.isfile(self.image):
426 # The given path is an image.
427 image_path = self.image
428 payload_dir = self.tempdir
xixuane851dfb2016-05-02 18:02:37 -0700429 else:
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800430 # Assuming it is an xbuddy path.
xixuane851dfb2016-05-02 18:02:37 -0700431 self.board = cros_build_lib.GetBoard(device_board=device.board,
432 override_board=self.board,
Eashan Bhatt2f01e422019-07-25 10:31:04 -0700433 force=self.yes,
434 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700435 if not self.force and self.board != device.board:
436 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700437 raise FlashError('Device (%s) is incompatible with board %s' %
438 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700439 logging.info('Board is %s', self.board)
440
Amin Hassanic20a3c32019-06-02 21:43:21 -0700441
442 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
443 # use xbuddy library directly instead of the devserver_wrapper.
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200444 # Fetch the full payload and properties, and stateful files. If this
445 # fails, fallback to downloading the image.
446 try:
447 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800448 os.path.join(self.image, artifact_info.FULL_PAYLOAD), self.board,
Achuith Bhandarkar12f43c72019-11-21 16:44:24 -0800449 static_dir=DEVSERVER_STATIC_DIR, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200450 payload_dir = os.path.dirname(
451 ds_wrapper.TranslatedPathToLocalPath(translated_path,
452 DEVSERVER_STATIC_DIR))
453 ds_wrapper.GetImagePathWithXbuddy(
Amin Hassani1c25d4b2019-11-22 10:59:07 -0800454 os.path.join(self.image, artifact_info.STATEFUL_PAYLOAD),
455 self.board, static_dir=DEVSERVER_STATIC_DIR, silent=True)
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200456 fetch_image = False
457 except (ds_wrapper.ImagePathError, ds_wrapper.ArtifactDownloadError):
458 logging.info('Could not find full_payload or stateful for "%s"',
459 self.image)
460 fetch_image = True
xixuane851dfb2016-05-02 18:02:37 -0700461
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200462 # We didn't find the full_payload, attempt to download the image.
463 if fetch_image:
464 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
465 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
466 image_path = ds_wrapper.TranslatedPathToLocalPath(
467 translated_path, DEVSERVER_STATIC_DIR)
468 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
469 logging.notice('Using image path %s and payload directory %s',
470 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700471
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800472 # Generate rootfs and stateful update payloads if they do not exist.
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700473 payload_path = os.path.join(payload_dir,
474 auto_updater_transfer.ROOTFS_FILENAME)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800475 if not os.path.exists(payload_path):
476 paygen_payload_lib.GenerateUpdatePayload(
477 image_path, payload_path, src_image=self.src_image_to_delta)
Sanika Kulkarnic1e818f2019-09-27 11:35:14 -0700478 if not os.path.exists(os.path.join(
479 payload_dir, auto_updater_transfer.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800480 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
481 payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700482 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700483
484 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700485 """Perform remote device update.
486
487 The update process includes:
488 1. initialize a device instance for the given remote device.
489 2. achieve payload_dir which contains the required payloads for updating.
490 3. initialize an auto-updater instance to do RunUpdate().
491 4. After auto-update, all temp files and dir will be cleaned up.
492 """
David Pursellf1d16a62015-03-25 13:31:04 -0700493 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700494 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600495 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
496 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700497
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600498 try:
499 # Get payload directory
500 payload_dir = self.GetPayloadDir(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700501
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600502 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700503 chromeos_AU = auto_updater.ChromiumOSUpdater(
504 device=device,
505 build_name=None,
506 payload_dir=payload_dir,
507 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600508 do_rootfs_update=self.do_rootfs_update,
509 do_stateful_update=self.do_stateful_update,
510 reboot=self.reboot,
511 disable_verification=self.disable_verification,
512 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800513 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800514 send_payload_in_parallel=self.send_payload_in_parallel,
515 experimental_au=self.experimental_au)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600516 chromeos_AU.CheckPayloads()
Sanika Kulkarni00b9d682019-11-26 09:43:20 -0800517 chromeos_AU.PreparePayloadPropsFile()
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600518 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700519
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600520 except Exception:
521 logging.error('Device update failed.')
522 lsb_entries = sorted(device.lsb_release.items())
523 logging.info(
524 'Following are the LSB version details of the device:\n%s',
525 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
526 raise
527
528 logging.notice('Update performed successfully.')
529
530 except remote_access.RemoteAccessException:
531 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700532 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600533
David Pursellf1d16a62015-03-25 13:31:04 -0700534 finally:
535 self.Cleanup()
536
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200537
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700538def Flash(device, image, board=None, install=False, src_image_to_delta=None,
539 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600540 reboot=True, wipe=True, ssh_private_key=None, ping=True,
541 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800542 force=False, debug=False, send_payload_in_parallel=False,
543 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700544 """Flashes a device, USB drive, or file with an image.
545
546 This provides functionality common to `cros flash` and `brillo flash`
547 so that they can parse the commandline separately but still use the
548 same underlying functionality.
549
550 Args:
David Pursell2e773382015-04-03 14:30:47 -0700551 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700552 image: Path (string) to the update image. Can be a local or xbuddy path;
553 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700554 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700555 install: Install to USB using base disk layout; USB |device| scheme only.
556 src_image_to_delta: Local path to an image to be used as the base to
557 generate delta payloads; SSH |device| scheme only.
558 rootfs_update: Update rootfs partition; SSH |device| scheme only.
559 stateful_update: Update stateful partition; SSH |device| scheme only.
560 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
561 reboot: Reboot device after update; SSH |device| scheme only.
562 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600563 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700564 ping: Ping the device before attempting update; SSH |device| scheme only.
565 disable_rootfs_verification: Remove rootfs verification after update; SSH
566 |device| scheme only.
567 clear_cache: Clear the devserver static directory.
568 yes: Assume "yes" for any prompt.
569 force: Ignore sanity checks and prompts. Overrides |yes| if True.
570 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800571 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
572 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800573 experimental_au: Use the experimental features auto updater. It should be
574 deprecated once crbug.com/872441 is fixed.
David Pursellf1d16a62015-03-25 13:31:04 -0700575
576 Raises:
577 FlashError: An unrecoverable error occured.
578 ValueError: Invalid parameter combination.
579 """
580 if force:
581 yes = True
582
583 if clear_cache:
584 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800585 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700586
587 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800588 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700589 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800590 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700591
592 if install:
David Pursell2e773382015-04-03 14:30:47 -0700593 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700594 raise ValueError(
595 '--install can only be used when writing to a USB device')
596 if not cros_build_lib.IsInsideChroot():
597 raise ValueError('--install can only be used inside the chroot')
598
David Pursell2e773382015-04-03 14:30:47 -0700599 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
600 if device:
601 hostname, port = device.hostname, device.port
602 else:
603 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700604 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700605 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700606 hostname,
607 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700608 image,
609 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700610 src_image_to_delta=src_image_to_delta,
611 rootfs_update=rootfs_update,
612 stateful_update=stateful_update,
613 clobber_stateful=clobber_stateful,
614 reboot=reboot,
615 wipe=wipe,
616 debug=debug,
617 yes=yes,
618 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600619 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700620 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800621 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800622 send_payload_in_parallel=send_payload_in_parallel,
623 experimental_au=experimental_au)
David Pursellf1d16a62015-03-25 13:31:04 -0700624 updater.Run()
625 elif device.scheme == commandline.DEVICE_SCHEME_USB:
626 path = osutils.ExpandPath(device.path) if device.path else ''
627 logging.info('Preparing to image the removable device %s', path)
628 imager = USBImager(path,
629 board,
630 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700631 debug=debug,
632 install=install,
633 yes=yes)
634 imager.Run()
635 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
636 logging.info('Preparing to copy image to %s', device.path)
637 imager = FileImager(device.path,
638 board,
639 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700640 debug=debug,
641 yes=yes)
642 imager.Run()