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