blob: 912c3c2511a11a04ecacf0b99ae387ea5f5b4890 [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
David Pursellf1d16a62015-03-25 13:31:04 -070018from chromite.lib import commandline
19from chromite.lib import cros_build_lib
20from chromite.lib import cros_logging as logging
21from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070022from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070023from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070024from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070025from chromite.lib import remote_access
26
Amin Hassanic0f06fa2019-01-28 15:24:47 -080027from chromite.lib.paygen import paygen_payload_lib
28from chromite.lib.paygen import paygen_stateful_payload_lib
29
David Pursellf1d16a62015-03-25 13:31:04 -070030
Don Garrett97d7dc22015-01-20 14:07:56 -080031DEVSERVER_STATIC_DIR = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -070032 os.path.join(constants.CHROOT_SOURCE_ROOT, 'devserver', 'static'))
33
34
Ralph Nathan9b997232015-05-15 13:13:12 -070035class UsbImagerOperation(operation.ProgressBarOperation):
36 """Progress bar for flashing image to operation."""
37
38 def __init__(self, image):
39 super(UsbImagerOperation, self).__init__()
40 self._size = os.path.getsize(image)
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070041 self._transferred = 0
Ralph Nathan9b997232015-05-15 13:13:12 -070042 self._bytes = re.compile(r'(\d+) bytes')
43
44 def _GetDDPid(self):
45 """Get the Pid of dd."""
46 try:
Mike Frysinger45602c72019-09-22 02:15:11 -040047 pids = cros_build_lib.run(['pgrep', 'dd'], capture_output=True,
48 print_cmd=False).output
Ralph Nathan9b997232015-05-15 13:13:12 -070049 for pid in pids.splitlines():
50 if osutils.IsChildProcess(int(pid), name='dd'):
51 return int(pid)
52 return -1
53 except cros_build_lib.RunCommandError:
54 # If dd isn't still running, then we assume that it is finished.
55 return -1
56
57 def _PingDD(self, dd_pid):
58 """Send USR1 signal to dd to get status update."""
59 try:
60 cmd = ['kill', '-USR1', str(dd_pid)]
Mike Frysinger45602c72019-09-22 02:15:11 -040061 cros_build_lib.sudo_run(cmd, print_cmd=False)
Ralph Nathan9b997232015-05-15 13:13:12 -070062 except cros_build_lib.RunCommandError:
63 # Here we assume that dd finished in the background.
64 return
65
66 def ParseOutput(self, output=None):
67 """Parse the output of dd to update progress bar."""
68 dd_pid = self._GetDDPid()
69 if dd_pid == -1:
70 return
71
72 self._PingDD(dd_pid)
73
74 if output is None:
75 stdout = self._stdout.read()
76 stderr = self._stderr.read()
77 output = stdout + stderr
78
79 match = self._bytes.search(output)
80 if match:
Matthew Bleckercff0f2d2019-08-26 12:52:51 -070081 self._transferred = int(match.groups()[0])
Ralph Nathan9b997232015-05-15 13:13:12 -070082
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040083 self.ProgressBar(self._transferred / self._size)
Ralph Nathan9b997232015-05-15 13:13:12 -070084
85
Mike Frysinger32759e42016-12-21 18:40:16 -050086def _IsFilePathGPTDiskImage(file_path, require_pmbr=False):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070087 """Determines if a file is a valid GPT disk.
88
89 Args:
90 file_path: Path to the file to test.
Mike Frysinger32759e42016-12-21 18:40:16 -050091 require_pmbr: Whether to require a PMBR in LBA0.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070092 """
93 if os.path.isfile(file_path):
Mike Frysinger32759e42016-12-21 18:40:16 -050094 with open(file_path) as image_file:
95 if require_pmbr:
96 # Seek to the end of LBA0 and look for the PMBR boot signature.
97 image_file.seek(0x1fe)
98 if image_file.read(2) != '\x55\xaa':
99 return False
100 # Current file position is start of LBA1 now.
101 else:
102 # Seek to LBA1 where the GPT starts.
103 image_file.seek(0x200)
104
105 # See if there's a GPT here.
106 if image_file.read(8) == 'EFI PART':
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700107 return True
Mike Frysinger32759e42016-12-21 18:40:16 -0500108
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700109 return False
110
111
112def _ChooseImageFromDirectory(dir_path):
113 """Lists all image files in |dir_path| and ask user to select one.
114
115 Args:
116 dir_path: Path to the directory.
117 """
118 images = sorted([x for x in os.listdir(dir_path) if
119 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
120 idx = 0
Mike Frysinger53ffaae2019-08-27 16:30:27 -0400121 if not images:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700122 raise ValueError('No image found in %s.' % dir_path)
123 elif len(images) > 1:
124 idx = cros_build_lib.GetChoice(
125 'Multiple images found in %s. Please select one to continue:' % (
126 (dir_path,)),
127 images)
128
129 return os.path.join(dir_path, images[idx])
130
131
David Pursellf1d16a62015-03-25 13:31:04 -0700132class FlashError(Exception):
133 """Thrown when there is an unrecoverable error during flash."""
134
135
136class USBImager(object):
137 """Copy image to the target removable device."""
138
Gilad Arnoldd0461442015-07-07 11:52:46 -0700139 def __init__(self, device, board, image, debug=False, install=False,
140 yes=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700141 """Initalizes USBImager."""
142 self.device = device
143 self.board = board if board else cros_build_lib.GetDefaultBoard()
144 self.image = image
David Pursellf1d16a62015-03-25 13:31:04 -0700145 self.debug = debug
146 self.debug_level = logging.DEBUG if debug else logging.INFO
147 self.install = install
148 self.yes = yes
149
150 def DeviceNameToPath(self, device_name):
151 return '/dev/%s' % device_name
152
153 def GetRemovableDeviceDescription(self, device):
154 """Returns a informational description of the removable |device|.
155
156 Args:
157 device: the device name (e.g. sdc).
158
159 Returns:
160 A string describing |device| (e.g. Patriot Memory 7918 MB).
161 """
162 desc = [
163 osutils.GetDeviceInfo(device, keyword='manufacturer'),
164 osutils.GetDeviceInfo(device, keyword='product'),
165 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
166 '(%s)' % self.DeviceNameToPath(device),
167 ]
168 return ' '.join([x for x in desc if x])
169
170 def ListAllRemovableDevices(self):
171 """Returns a list of removable devices.
172
173 Returns:
174 A list of device names (e.g. ['sdb', 'sdc']).
175 """
176 devices = osutils.ListBlockDevices()
177 removable_devices = []
178 for d in devices:
179 if d.TYPE == 'disk' and d.RM == '1':
180 removable_devices.append(d.NAME)
181
182 return removable_devices
183
184 def ChooseRemovableDevice(self, devices):
185 """Lists all removable devices and asks user to select/confirm.
186
187 Args:
188 devices: a list of device names (e.g. ['sda', 'sdb']).
189
190 Returns:
191 The device name chosen by the user.
192 """
193 idx = cros_build_lib.GetChoice(
194 'Removable device(s) found. Please select/confirm to continue:',
195 [self.GetRemovableDeviceDescription(x) for x in devices])
196
197 return devices[idx]
198
199 def InstallImageToDevice(self, image, device):
200 """Installs |image| to the removable |device|.
201
202 Args:
203 image: Path to the image to copy.
204 device: Device to copy to.
205 """
206 cmd = [
207 'chromeos-install',
208 '--yes',
209 '--skip_src_removable',
210 '--skip_dst_removable',
211 '--payload_image=%s' % image,
212 '--dst=%s' % device,
213 '--skip_postinstall',
214 ]
Mike Frysinger45602c72019-09-22 02:15:11 -0400215 cros_build_lib.sudo_run(cmd,
216 print_cmd=True,
217 debug_level=logging.NOTICE,
218 combine_stdout_stderr=True,
219 log_output=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700220
221 def CopyImageToDevice(self, image, device):
222 """Copies |image| to the removable |device|.
223
224 Args:
225 image: Path to the image to copy.
226 device: Device to copy to.
227 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700228 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800229 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700230 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
231 op = UsbImagerOperation(image)
Mike Frysinger45602c72019-09-22 02:15:11 -0400232 op.Run(cros_build_lib.sudo_run, cmd, debug_level=logging.NOTICE,
Ralph Nathan9b997232015-05-15 13:13:12 -0700233 update_period=0.5)
234 else:
Mike Frysinger45602c72019-09-22 02:15:11 -0400235 cros_build_lib.sudo_run(
Ralph Nathan9b997232015-05-15 13:13:12 -0700236 cmd, debug_level=logging.NOTICE,
237 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700238
Brian Norris6386fde2018-10-29 13:34:28 -0700239 # dd likely didn't put the backup GPT in the last block. sfdisk fixes this
240 # up for us with a 'write' command, so we have a standards-conforming GPT.
241 # Ignore errors because sfdisk (util-linux < v2.32) isn't always happy to
242 # fix GPT sanity issues.
Mike Frysinger45602c72019-09-22 02:15:11 -0400243 cros_build_lib.sudo_run(['sfdisk', device], input='write\n',
244 error_code_ok=True,
245 debug_level=self.debug_level)
Brian Norris6386fde2018-10-29 13:34:28 -0700246
Mike Frysinger45602c72019-09-22 02:15:11 -0400247 cros_build_lib.sudo_run(['partx', '-u', device],
248 debug_level=self.debug_level)
249 cros_build_lib.sudo_run(['sync', '-d', device],
250 debug_level=self.debug_level)
David Pursellf1d16a62015-03-25 13:31:04 -0700251
David Pursellf1d16a62015-03-25 13:31:04 -0700252 def _GetImagePath(self):
253 """Returns the image path to use."""
254 image_path = translated_path = None
255 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700256 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700257 # TODO(wnwen): Open the tarball and if there is just one file in it,
258 # use that instead. Existing code in upload_symbols.py.
259 if cros_build_lib.BooleanPrompt(
260 prolog='The given image file is not a valid disk image. Perhaps '
261 'you forgot to untar it.',
262 prompt='Terminate the current flash process?'):
263 raise FlashError('Update terminated by user.')
264 image_path = self.image
265 elif os.path.isdir(self.image):
266 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700267 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700268 else:
269 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700270 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Don Garrett97d7dc22015-01-20 14:07:56 -0800271 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700272 image_path = ds_wrapper.TranslatedPathToLocalPath(
Don Garrett97d7dc22015-01-20 14:07:56 -0800273 translated_path, DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700274
275 logging.info('Using image %s', translated_path or image_path)
276 return image_path
277
278 def Run(self):
279 """Image the removable device."""
280 devices = self.ListAllRemovableDevices()
281
282 if self.device:
283 # If user specified a device path, check if it exists.
284 if not os.path.exists(self.device):
285 raise FlashError('Device path %s does not exist.' % self.device)
286
287 # Then check if it is removable.
288 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
289 msg = '%s is not a removable device.' % self.device
290 if not (self.yes or cros_build_lib.BooleanPrompt(
291 default=False, prolog=msg)):
292 raise FlashError('You can specify usb:// to choose from a list of '
293 'removable devices.')
294 target = None
295 if self.device:
296 # Get device name from path (e.g. sdc in /dev/sdc).
297 target = self.device.rsplit(os.path.sep, 1)[-1]
298 elif devices:
299 # Ask user to choose from the list.
300 target = self.ChooseRemovableDevice(devices)
301 else:
302 raise FlashError('No removable devices detected.')
303
304 image_path = self._GetImagePath()
305 try:
306 device = self.DeviceNameToPath(target)
307 if self.install:
308 self.InstallImageToDevice(image_path, device)
309 else:
310 self.CopyImageToDevice(image_path, device)
311 except cros_build_lib.RunCommandError:
312 logging.error('Failed copying image to device %s',
313 self.DeviceNameToPath(target))
314
315
316class FileImager(USBImager):
317 """Copy image to the target path."""
318
319 def Run(self):
320 """Copy the image to the path specified by self.device."""
Mao Huangc4777e82016-03-14 20:20:08 +0800321 if not os.path.isdir(os.path.dirname(self.device)):
322 raise FlashError('Parent of path %s is not a directory.' % self.device)
David Pursellf1d16a62015-03-25 13:31:04 -0700323
324 image_path = self._GetImagePath()
325 if os.path.isdir(self.device):
326 logging.info('Copying to %s',
327 os.path.join(self.device, os.path.basename(image_path)))
328 else:
329 logging.info('Copying to %s', self.device)
330 try:
331 shutil.copy(image_path, self.device)
332 except IOError:
333 logging.error('Failed to copy image %s to %s', image_path, self.device)
334
335
336class RemoteDeviceUpdater(object):
337 """Performs update on a remote device."""
David Pursellf1d16a62015-03-25 13:31:04 -0700338 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
339 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700340 # Root working directory on the device. This directory is in the
341 # stateful partition and thus has enough space to store the payloads.
342 DEVICE_BASE_DIR = '/mnt/stateful_partition/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700343 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
344 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700345
346 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
347 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700348 board=None, src_image_to_delta=None, wipe=True, debug=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600349 yes=False, force=False, ssh_private_key=None, ping=True,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800350 disable_verification=False, send_payload_in_parallel=False,
351 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700352 """Initializes RemoteDeviceUpdater"""
353 if not stateful_update and not rootfs_update:
354 raise ValueError('No update operation to perform; either stateful or'
355 ' rootfs partitions must be updated.')
356 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
357 self.ssh_hostname = ssh_hostname
358 self.ssh_port = ssh_port
359 self.image = image
360 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700361 self.src_image_to_delta = src_image_to_delta
362 self.do_stateful_update = stateful_update
363 self.do_rootfs_update = rootfs_update
364 self.disable_verification = disable_verification
365 self.clobber_stateful = clobber_stateful
366 self.reboot = reboot
367 self.debug = debug
Daniel Erat30fd2072016-08-29 10:08:56 -0600368 self.ssh_private_key = ssh_private_key
David Pursellf1d16a62015-03-25 13:31:04 -0700369 self.ping = ping
370 # Do not wipe if debug is set.
371 self.wipe = wipe and not debug
372 self.yes = yes
373 self.force = force
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800374 self.send_payload_in_parallel = send_payload_in_parallel
Amin Hassanie62d2e12019-02-01 10:40:41 -0800375 self.experimental_au = experimental_au
David Pursellf1d16a62015-03-25 13:31:04 -0700376
David Pursellf1d16a62015-03-25 13:31:04 -0700377 def Cleanup(self):
378 """Cleans up the temporary directory."""
379 if self.wipe:
380 logging.info('Cleaning up temporary working directory...')
381 osutils.RmDir(self.tempdir)
382 else:
383 logging.info('You can find the log files and/or payloads in %s',
384 self.tempdir)
385
xixuane851dfb2016-05-02 18:02:37 -0700386 def GetPayloadDir(self, device):
387 """Get directory of payload for update.
David Pursellf1d16a62015-03-25 13:31:04 -0700388
xixuane851dfb2016-05-02 18:02:37 -0700389 This method is used to obtain the directory of payload for cros-flash. The
390 given path 'self.image' is passed in when initializing RemoteDeviceUpdater.
David Pursellf1d16a62015-03-25 13:31:04 -0700391
xixuane851dfb2016-05-02 18:02:37 -0700392 If self.image is a directory, we directly use the provided update payload(s)
393 in this directory.
394
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800395 If self.image is an image, we will generate payloads for it and put them in
396 our temporary directory. The reason is that people may modify a local image
397 or override it (on the same path) with a different image, so in order to be
398 safe each time we need to generate the payloads and not cache them.
xixuane851dfb2016-05-02 18:02:37 -0700399
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800400 If non of the above cases, we use the xbuddy to first obtain the image path
401 (and possibly download it). Then we will generate the payloads in the same
402 directory the image is located. The reason is that this is what devserver
403 used to do. The path to the image generated by the devserver (or xbuddy) is
404 unique and normally nobody override its image with a different one. That is
405 why I think it is safe to put the payloads next to the image. This is a poor
406 man's version of caching but it makes cros flash faster for users who flash
407 the same image multiple times (without doing any change to the image).
David Pursellf1d16a62015-03-25 13:31:04 -0700408
409 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400410 device: A ChromiumOSDevice object.
David Pursellf1d16a62015-03-25 13:31:04 -0700411
412 Returns:
xixuane851dfb2016-05-02 18:02:37 -0700413 A string payload_dir, that represents the payload directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700414 """
xixuane851dfb2016-05-02 18:02:37 -0700415 if os.path.isdir(self.image):
416 # The given path is a directory.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800417 logging.info('Using provided payloads in %s', self.image)
418 return self.image
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600419
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800420 if os.path.isfile(self.image):
421 # The given path is an image.
422 image_path = self.image
423 payload_dir = self.tempdir
xixuane851dfb2016-05-02 18:02:37 -0700424 else:
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800425 # Assuming it is an xbuddy path.
xixuane851dfb2016-05-02 18:02:37 -0700426 self.board = cros_build_lib.GetBoard(device_board=device.board,
427 override_board=self.board,
Eashan Bhatt2f01e422019-07-25 10:31:04 -0700428 force=self.yes,
429 strict=True)
xixuane851dfb2016-05-02 18:02:37 -0700430 if not self.force and self.board != device.board:
431 # If a board was specified, it must be compatible with the device.
Brian Norrisfab3fb22016-06-02 14:59:20 -0700432 raise FlashError('Device (%s) is incompatible with board %s' %
433 (device.board, self.board))
xixuane851dfb2016-05-02 18:02:37 -0700434 logging.info('Board is %s', self.board)
435
436 # Translate the xbuddy path to get the exact image to use.
Amin Hassanic20a3c32019-06-02 21:43:21 -0700437
438 # TODO(crbug.com/872441): Once devserver code has been moved to chromite,
439 # use xbuddy library directly instead of the devserver_wrapper.
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800440 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
441 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
442 image_path = ds_wrapper.TranslatedPathToLocalPath(
443 translated_path, DEVSERVER_STATIC_DIR)
444 payload_dir = os.path.join(os.path.dirname(image_path), 'payloads')
xixuane851dfb2016-05-02 18:02:37 -0700445
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800446 logging.notice('Using image path %s and payload directory %s',
447 image_path, payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700448
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800449 # Generate rootfs and stateful update payloads if they do not exist.
Amin Hassanic20a3c32019-06-02 21:43:21 -0700450 payload_path = os.path.join(payload_dir, auto_updater.ROOTFS_FILENAME)
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800451 if not os.path.exists(payload_path):
452 paygen_payload_lib.GenerateUpdatePayload(
453 image_path, payload_path, src_image=self.src_image_to_delta)
454 if not os.path.exists(os.path.join(payload_dir,
Amin Hassanic20a3c32019-06-02 21:43:21 -0700455 auto_updater.STATEFUL_FILENAME)):
Amin Hassanic0f06fa2019-01-28 15:24:47 -0800456 paygen_stateful_payload_lib.GenerateStatefulPayload(image_path,
457 payload_dir)
xixuane851dfb2016-05-02 18:02:37 -0700458 return payload_dir
David Pursellf1d16a62015-03-25 13:31:04 -0700459
460 def Run(self):
xixuane851dfb2016-05-02 18:02:37 -0700461 """Perform remote device update.
462
463 The update process includes:
464 1. initialize a device instance for the given remote device.
465 2. achieve payload_dir which contains the required payloads for updating.
466 3. initialize an auto-updater instance to do RunUpdate().
467 4. After auto-update, all temp files and dir will be cleaned up.
468 """
David Pursellf1d16a62015-03-25 13:31:04 -0700469 try:
David Pursellf1d16a62015-03-25 13:31:04 -0700470 with remote_access.ChromiumOSDeviceHandler(
Daniel Erat30fd2072016-08-29 10:08:56 -0600471 self.ssh_hostname, port=self.ssh_port, base_dir=self.DEVICE_BASE_DIR,
472 private_key=self.ssh_private_key, ping=self.ping) as device:
David Pursellf1d16a62015-03-25 13:31:04 -0700473
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600474 try:
475 # Get payload directory
476 payload_dir = self.GetPayloadDir(device)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700477
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600478 # Do auto-update
Amin Hassani9800d432019-07-24 14:23:39 -0700479 chromeos_AU = auto_updater.ChromiumOSUpdater(
480 device=device,
481 build_name=None,
482 payload_dir=payload_dir,
483 tempdir=self.tempdir,
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600484 do_rootfs_update=self.do_rootfs_update,
485 do_stateful_update=self.do_stateful_update,
486 reboot=self.reboot,
487 disable_verification=self.disable_verification,
488 clobber_stateful=self.clobber_stateful,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800489 yes=self.yes,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800490 send_payload_in_parallel=self.send_payload_in_parallel,
491 experimental_au=self.experimental_au)
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600492 chromeos_AU.CheckPayloads()
Amin Hassanif85be122019-09-19 17:10:34 -0700493 chromeos_AU.ResolveAPPIDMismatchIfAny()
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600494 chromeos_AU.RunUpdate()
David Pursellf1d16a62015-03-25 13:31:04 -0700495
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600496 except Exception:
497 logging.error('Device update failed.')
498 lsb_entries = sorted(device.lsb_release.items())
499 logging.info(
500 'Following are the LSB version details of the device:\n%s',
501 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
502 raise
503
504 logging.notice('Update performed successfully.')
505
506 except remote_access.RemoteAccessException:
507 logging.error('Remote device failed to initialize.')
David Pursellf1d16a62015-03-25 13:31:04 -0700508 raise
Steven Bennetts4304c7e2017-09-05 12:30:30 -0600509
David Pursellf1d16a62015-03-25 13:31:04 -0700510 finally:
511 self.Cleanup()
512
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700513def Flash(device, image, board=None, install=False, src_image_to_delta=None,
514 rootfs_update=True, stateful_update=True, clobber_stateful=False,
Daniel Erat30fd2072016-08-29 10:08:56 -0600515 reboot=True, wipe=True, ssh_private_key=None, ping=True,
516 disable_rootfs_verification=False, clear_cache=False, yes=False,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800517 force=False, debug=False, send_payload_in_parallel=False,
518 experimental_au=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700519 """Flashes a device, USB drive, or file with an image.
520
521 This provides functionality common to `cros flash` and `brillo flash`
522 so that they can parse the commandline separately but still use the
523 same underlying functionality.
524
525 Args:
David Pursell2e773382015-04-03 14:30:47 -0700526 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700527 image: Path (string) to the update image. Can be a local or xbuddy path;
528 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700529 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700530 install: Install to USB using base disk layout; USB |device| scheme only.
531 src_image_to_delta: Local path to an image to be used as the base to
532 generate delta payloads; SSH |device| scheme only.
533 rootfs_update: Update rootfs partition; SSH |device| scheme only.
534 stateful_update: Update stateful partition; SSH |device| scheme only.
535 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
536 reboot: Reboot device after update; SSH |device| scheme only.
537 wipe: Wipe temporary working directory; SSH |device| scheme only.
Daniel Erat30fd2072016-08-29 10:08:56 -0600538 ssh_private_key: Path to an SSH private key file; None to use test keys.
David Pursellf1d16a62015-03-25 13:31:04 -0700539 ping: Ping the device before attempting update; SSH |device| scheme only.
540 disable_rootfs_verification: Remove rootfs verification after update; SSH
541 |device| scheme only.
542 clear_cache: Clear the devserver static directory.
543 yes: Assume "yes" for any prompt.
544 force: Ignore sanity checks and prompts. Overrides |yes| if True.
545 debug: Print additional debugging messages.
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800546 send_payload_in_parallel: Transfer payloads in chunks in parallel to speed
547 up transmissions for long haul between endpoints.
Amin Hassanie62d2e12019-02-01 10:40:41 -0800548 experimental_au: Use the experimental features auto updater. It should be
549 deprecated once crbug.com/872441 is fixed.
David Pursellf1d16a62015-03-25 13:31:04 -0700550
551 Raises:
552 FlashError: An unrecoverable error occured.
553 ValueError: Invalid parameter combination.
554 """
555 if force:
556 yes = True
557
558 if clear_cache:
559 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800560 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700561
562 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800563 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700564 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800565 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700566
567 if install:
David Pursell2e773382015-04-03 14:30:47 -0700568 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700569 raise ValueError(
570 '--install can only be used when writing to a USB device')
571 if not cros_build_lib.IsInsideChroot():
572 raise ValueError('--install can only be used inside the chroot')
573
David Pursell2e773382015-04-03 14:30:47 -0700574 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
575 if device:
576 hostname, port = device.hostname, device.port
577 else:
578 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700579 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700580 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700581 hostname,
582 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700583 image,
584 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700585 src_image_to_delta=src_image_to_delta,
586 rootfs_update=rootfs_update,
587 stateful_update=stateful_update,
588 clobber_stateful=clobber_stateful,
589 reboot=reboot,
590 wipe=wipe,
591 debug=debug,
592 yes=yes,
593 force=force,
Daniel Erat30fd2072016-08-29 10:08:56 -0600594 ssh_private_key=ssh_private_key,
David Pursellf1d16a62015-03-25 13:31:04 -0700595 ping=ping,
Chung-yih Wang3145bf52017-10-24 16:08:02 +0800596 disable_verification=disable_rootfs_verification,
Amin Hassanie62d2e12019-02-01 10:40:41 -0800597 send_payload_in_parallel=send_payload_in_parallel,
598 experimental_au=experimental_au)
David Pursellf1d16a62015-03-25 13:31:04 -0700599 updater.Run()
600 elif device.scheme == commandline.DEVICE_SCHEME_USB:
601 path = osutils.ExpandPath(device.path) if device.path else ''
602 logging.info('Preparing to image the removable device %s', path)
603 imager = USBImager(path,
604 board,
605 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700606 debug=debug,
607 install=install,
608 yes=yes)
609 imager.Run()
610 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
611 logging.info('Preparing to copy image to %s', device.path)
612 imager = FileImager(device.path,
613 board,
614 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700615 debug=debug,
616 yes=yes)
617 imager.Run()