blob: fdc9c2ba429c1d17ec8680b9df812e7cd38f6a78 [file] [log] [blame]
David Pursellf1d16a62015-03-25 13:31:04 -07001# Copyright 2015 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Install/copy the image to the device."""
6
7from __future__ import print_function
8
9import cStringIO
10import os
11import shutil
12import tempfile
13import time
14
15from chromite.cbuildbot import constants
Ralph Nathan872ea4d2015-05-05 18:04:56 -070016from chromite.cli import command
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070017from chromite.lib import blueprint_lib
David Pursellf1d16a62015-03-25 13:31:04 -070018from chromite.lib import brick_lib
19from 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 project_sdk
27from chromite.lib import remote_access
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070028from chromite.lib import workspace_lib
David Pursellf1d16a62015-03-25 13:31:04 -070029
30
Gilad Arnold1c8eda52015-05-04 22:32:38 -070031_DEVSERVER_STATIC_DIR = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -070032 os.path.join(constants.CHROOT_SOURCE_ROOT, 'devserver', 'static'))
33
34
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070035def _IsFilePathGPTDiskImage(file_path):
36 """Determines if a file is a valid GPT disk.
37
38 Args:
39 file_path: Path to the file to test.
40 """
41 if os.path.isfile(file_path):
42 with cros_build_lib.Open(file_path) as image_file:
43 image_file.seek(0x1fe)
44 if image_file.read(10) == '\x55\xaaEFI PART':
45 return True
46 return False
47
48
49def _ChooseImageFromDirectory(dir_path):
50 """Lists all image files in |dir_path| and ask user to select one.
51
52 Args:
53 dir_path: Path to the directory.
54 """
55 images = sorted([x for x in os.listdir(dir_path) if
56 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
57 idx = 0
58 if len(images) == 0:
59 raise ValueError('No image found in %s.' % dir_path)
60 elif len(images) > 1:
61 idx = cros_build_lib.GetChoice(
62 'Multiple images found in %s. Please select one to continue:' % (
63 (dir_path,)),
64 images)
65
66 return os.path.join(dir_path, images[idx])
67
68
David Pursellf1d16a62015-03-25 13:31:04 -070069class FlashError(Exception):
70 """Thrown when there is an unrecoverable error during flash."""
71
72
73class USBImager(object):
74 """Copy image to the target removable device."""
75
76 def __init__(self, device, board, image, sdk_version=None, debug=False,
77 install=False, yes=False):
78 """Initalizes USBImager."""
79 self.device = device
80 self.board = board if board else cros_build_lib.GetDefaultBoard()
81 self.image = image
82 self.sdk_version = sdk_version
83 self.debug = debug
84 self.debug_level = logging.DEBUG if debug else logging.INFO
85 self.install = install
86 self.yes = yes
87
88 def DeviceNameToPath(self, device_name):
89 return '/dev/%s' % device_name
90
91 def GetRemovableDeviceDescription(self, device):
92 """Returns a informational description of the removable |device|.
93
94 Args:
95 device: the device name (e.g. sdc).
96
97 Returns:
98 A string describing |device| (e.g. Patriot Memory 7918 MB).
99 """
100 desc = [
101 osutils.GetDeviceInfo(device, keyword='manufacturer'),
102 osutils.GetDeviceInfo(device, keyword='product'),
103 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
104 '(%s)' % self.DeviceNameToPath(device),
105 ]
106 return ' '.join([x for x in desc if x])
107
108 def ListAllRemovableDevices(self):
109 """Returns a list of removable devices.
110
111 Returns:
112 A list of device names (e.g. ['sdb', 'sdc']).
113 """
114 devices = osutils.ListBlockDevices()
115 removable_devices = []
116 for d in devices:
117 if d.TYPE == 'disk' and d.RM == '1':
118 removable_devices.append(d.NAME)
119
120 return removable_devices
121
122 def ChooseRemovableDevice(self, devices):
123 """Lists all removable devices and asks user to select/confirm.
124
125 Args:
126 devices: a list of device names (e.g. ['sda', 'sdb']).
127
128 Returns:
129 The device name chosen by the user.
130 """
131 idx = cros_build_lib.GetChoice(
132 'Removable device(s) found. Please select/confirm to continue:',
133 [self.GetRemovableDeviceDescription(x) for x in devices])
134
135 return devices[idx]
136
137 def InstallImageToDevice(self, image, device):
138 """Installs |image| to the removable |device|.
139
140 Args:
141 image: Path to the image to copy.
142 device: Device to copy to.
143 """
144 cmd = [
145 'chromeos-install',
146 '--yes',
147 '--skip_src_removable',
148 '--skip_dst_removable',
149 '--payload_image=%s' % image,
150 '--dst=%s' % device,
151 '--skip_postinstall',
152 ]
153 cros_build_lib.SudoRunCommand(cmd)
154
155 def CopyImageToDevice(self, image, device):
156 """Copies |image| to the removable |device|.
157
158 Args:
159 image: Path to the image to copy.
160 device: Device to copy to.
161 """
162 # Use pv to display progress bar if possible.
163 cmd_base = 'pv -pretb'
164 try:
165 cros_build_lib.RunCommand(['pv', '--version'], print_cmd=False,
166 capture_output=True)
167 except cros_build_lib.RunCommandError:
168 cmd_base = 'cat'
169
170 cmd = '%s %s | dd of=%s bs=4M iflag=fullblock oflag=sync' % (
171 cmd_base, image, device)
Ralph Nathanf2c1d792015-05-05 17:16:15 -0700172
173 # We want to display the output at logging level NOTICE or less but we only
174 # want to print the command at logging levels INFO and DEBUG.
175 cros_build_lib.SudoRunCommand(
176 cmd, shell=True, debug_level=logging.NOTICE,
177 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700178 cros_build_lib.SudoRunCommand(['sync'], debug_level=self.debug_level)
179
David Pursellf1d16a62015-03-25 13:31:04 -0700180
181 def _GetImagePath(self):
182 """Returns the image path to use."""
183 image_path = translated_path = None
184 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700185 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700186 # TODO(wnwen): Open the tarball and if there is just one file in it,
187 # use that instead. Existing code in upload_symbols.py.
188 if cros_build_lib.BooleanPrompt(
189 prolog='The given image file is not a valid disk image. Perhaps '
190 'you forgot to untar it.',
191 prompt='Terminate the current flash process?'):
192 raise FlashError('Update terminated by user.')
193 image_path = self.image
194 elif os.path.isdir(self.image):
195 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700196 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700197 else:
198 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700199 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
David Pursellf1d16a62015-03-25 13:31:04 -0700200 self.image, self.board, version=self.sdk_version,
201 static_dir=_DEVSERVER_STATIC_DIR)
202 image_path = ds_wrapper.TranslatedPathToLocalPath(
203 translated_path, _DEVSERVER_STATIC_DIR)
204
205 logging.info('Using image %s', translated_path or image_path)
206 return image_path
207
208 def Run(self):
209 """Image the removable device."""
210 devices = self.ListAllRemovableDevices()
211
212 if self.device:
213 # If user specified a device path, check if it exists.
214 if not os.path.exists(self.device):
215 raise FlashError('Device path %s does not exist.' % self.device)
216
217 # Then check if it is removable.
218 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
219 msg = '%s is not a removable device.' % self.device
220 if not (self.yes or cros_build_lib.BooleanPrompt(
221 default=False, prolog=msg)):
222 raise FlashError('You can specify usb:// to choose from a list of '
223 'removable devices.')
224 target = None
225 if self.device:
226 # Get device name from path (e.g. sdc in /dev/sdc).
227 target = self.device.rsplit(os.path.sep, 1)[-1]
228 elif devices:
229 # Ask user to choose from the list.
230 target = self.ChooseRemovableDevice(devices)
231 else:
232 raise FlashError('No removable devices detected.')
233
234 image_path = self._GetImagePath()
235 try:
236 device = self.DeviceNameToPath(target)
237 if self.install:
238 self.InstallImageToDevice(image_path, device)
239 else:
240 self.CopyImageToDevice(image_path, device)
241 except cros_build_lib.RunCommandError:
242 logging.error('Failed copying image to device %s',
243 self.DeviceNameToPath(target))
244
245
246class FileImager(USBImager):
247 """Copy image to the target path."""
248
249 def Run(self):
250 """Copy the image to the path specified by self.device."""
251 if not os.path.exists(self.device):
252 raise FlashError('Path %s does not exist.' % self.device)
253
254 image_path = self._GetImagePath()
255 if os.path.isdir(self.device):
256 logging.info('Copying to %s',
257 os.path.join(self.device, os.path.basename(image_path)))
258 else:
259 logging.info('Copying to %s', self.device)
260 try:
261 shutil.copy(image_path, self.device)
262 except IOError:
263 logging.error('Failed to copy image %s to %s', image_path, self.device)
264
265
266class RemoteDeviceUpdater(object):
267 """Performs update on a remote device."""
268 DEVSERVER_FILENAME = 'devserver.py'
269 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
270 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700271 # Root working directory on the device. This directory is in the
272 # stateful partition and thus has enough space to store the payloads.
273 DEVICE_BASE_DIR = '/mnt/stateful_partition/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700274 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
275 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700276
277 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
278 rootfs_update=True, clobber_stateful=False, reboot=True,
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700279 board=None, src_image_to_delta=None, wipe=True, debug=False,
280 yes=False, force=False, ping=True, disable_verification=False,
281 sdk_version=None):
David Pursellf1d16a62015-03-25 13:31:04 -0700282 """Initializes RemoteDeviceUpdater"""
283 if not stateful_update and not rootfs_update:
284 raise ValueError('No update operation to perform; either stateful or'
285 ' rootfs partitions must be updated.')
286 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
287 self.ssh_hostname = ssh_hostname
288 self.ssh_port = ssh_port
289 self.image = image
290 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700291 self.src_image_to_delta = src_image_to_delta
292 self.do_stateful_update = stateful_update
293 self.do_rootfs_update = rootfs_update
294 self.disable_verification = disable_verification
295 self.clobber_stateful = clobber_stateful
296 self.reboot = reboot
297 self.debug = debug
298 self.ping = ping
299 # Do not wipe if debug is set.
300 self.wipe = wipe and not debug
301 self.yes = yes
302 self.force = force
303 self.sdk_version = sdk_version
304
305 # pylint: disable=unbalanced-tuple-unpacking
306 @classmethod
307 def GetUpdateStatus(cls, device, keys=None):
308 """Returns the status of the update engine on the |device|.
309
310 Retrieves the status from update engine and confirms all keys are
311 in the status.
312
313 Args:
314 device: A ChromiumOSDevice object.
315 keys: the keys to look for in the status result (defaults to
316 ['CURRENT_OP']).
317
318 Returns:
319 A list of values in the order of |keys|.
320 """
321 keys = ['CURRENT_OP'] if not keys else keys
322 result = device.RunCommand([cls.UPDATE_ENGINE_BIN, '--status'],
323 capture_output=True)
324 if not result.output:
325 raise Exception('Cannot get update status')
326
327 try:
328 status = cros_build_lib.LoadKeyValueFile(
329 cStringIO.StringIO(result.output))
330 except ValueError:
331 raise ValueError('Cannot parse update status')
332
333 values = []
334 for key in keys:
335 if key not in status:
336 raise ValueError('Missing %s in the update engine status')
337
338 values.append(status.get(key))
339
340 return values
341
342 def UpdateStateful(self, device, payload, clobber=False):
343 """Update the stateful partition of the device.
344
345 Args:
346 device: The ChromiumOSDevice object to update.
347 payload: The path to the update payload.
348 clobber: Clobber stateful partition (defaults to False).
349 """
350 # Copy latest stateful_update to device.
Gilad Arnold1c8eda52015-05-04 22:32:38 -0700351 stateful_update_bin = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -0700352 self.STATEFUL_UPDATE_BIN)
353 device.CopyToWorkDir(stateful_update_bin)
354 msg = 'Updating stateful partition'
355 logging.info('Copying stateful payload to device...')
356 device.CopyToWorkDir(payload)
357 cmd = ['sh',
358 os.path.join(device.work_dir,
359 os.path.basename(self.STATEFUL_UPDATE_BIN)),
360 os.path.join(device.work_dir, os.path.basename(payload))]
361
362 if clobber:
363 cmd.append('--stateful_change=clean')
364 msg += ' with clobber enabled'
365
366 logging.info('%s...', msg)
367 try:
368 device.RunCommand(cmd)
369 except cros_build_lib.RunCommandError:
370 logging.error('Faild to perform stateful partition update.')
371
372 def _CopyDevServerPackage(self, device, tempdir):
373 """Copy devserver package to work directory of device.
374
375 Args:
376 device: The ChromiumOSDevice object to copy the package to.
377 tempdir: The directory to temporarily store devserver package.
378 """
379 logging.info('Copying devserver package to device...')
380 src_dir = os.path.join(tempdir, 'src')
381 osutils.RmDir(src_dir, ignore_missing=True)
382 shutil.copytree(
383 ds_wrapper.DEVSERVER_PKG_DIR, src_dir,
384 ignore=shutil.ignore_patterns('*.pyc', 'tmp*', '.*', 'static', '*~'))
385 device.CopyToWorkDir(src_dir)
386 return os.path.join(device.work_dir, os.path.basename(src_dir))
387
388 def SetupRootfsUpdate(self, device):
389 """Makes sure |device| is ready for rootfs update."""
390 logging.info('Checking if update engine is idle...')
391 status, = self.GetUpdateStatus(device)
392 if status == 'UPDATE_STATUS_UPDATED_NEED_REBOOT':
393 logging.info('Device needs to reboot before updating...')
394 device.Reboot()
395 status, = self.GetUpdateStatus(device)
396
397 if status != 'UPDATE_STATUS_IDLE':
398 raise FlashError('Update engine is not idle. Status: %s' % status)
399
400 def UpdateRootfs(self, device, payload, tempdir):
401 """Update the rootfs partition of the device.
402
403 Args:
404 device: The ChromiumOSDevice object to update.
405 payload: The path to the update payload.
406 tempdir: The directory to store temporary files.
407 """
408 # Setup devserver and payload on the target device.
409 static_dir = os.path.join(device.work_dir, 'static')
410 payload_dir = os.path.join(static_dir, 'pregenerated')
411 src_dir = self._CopyDevServerPackage(device, tempdir)
412 device.RunCommand(['mkdir', '-p', payload_dir])
413 logging.info('Copying rootfs payload to device...')
414 device.CopyToDevice(payload, payload_dir)
415 devserver_bin = os.path.join(src_dir, self.DEVSERVER_FILENAME)
416 ds = ds_wrapper.RemoteDevServerWrapper(
417 device, devserver_bin, static_dir=static_dir, log_dir=device.work_dir)
418
419 logging.info('Updating rootfs partition')
420 try:
421 ds.Start()
422 # Use the localhost IP address to ensure that update engine
423 # client can connect to the devserver.
424 omaha_url = ds.GetDevServerURL(
425 ip='127.0.0.1', port=ds.port, sub_dir='update/pregenerated')
426 cmd = [self.UPDATE_ENGINE_BIN, '-check_for_update',
427 '-omaha_url=%s' % omaha_url]
428 device.RunCommand(cmd)
429
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700430 # If we are using a progress bar, update it every 0.5s instead of 10s.
431 if command.UseProgressBar():
432 update_check_interval = self.UPDATE_CHECK_INTERVAL_PROGRESSBAR
433 oper = operation.ProgressBarOperation()
434 else:
435 update_check_interval = self.UPDATE_CHECK_INTERVAL_NORMAL
436 oper = None
437 end_message_not_printed = True
438
David Pursellf1d16a62015-03-25 13:31:04 -0700439 # Loop until update is complete.
440 while True:
441 op, progress = self.GetUpdateStatus(device, ['CURRENT_OP', 'PROGRESS'])
442 logging.info('Waiting for update...status: %s at progress %s',
443 op, progress)
444
445 if op == 'UPDATE_STATUS_UPDATED_NEED_REBOOT':
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700446 logging.notice('Update completed.')
David Pursellf1d16a62015-03-25 13:31:04 -0700447 break
448
449 if op == 'UPDATE_STATUS_IDLE':
450 raise FlashError(
451 'Update failed with unexpected update status: %s' % op)
452
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700453 if oper is not None:
454 if op == 'UPDATE_STATUS_DOWNLOADING':
455 oper.ProgressBar(float(progress))
456 elif end_message_not_printed and op == 'UPDATE_STATUS_FINALIZING':
457 oper.Cleanup()
458 logging.notice('Finalizing image.')
459 end_message_not_printed = False
460
461 time.sleep(update_check_interval)
David Pursellf1d16a62015-03-25 13:31:04 -0700462
463 ds.Stop()
464 except Exception:
465 logging.error('Rootfs update failed.')
466 logging.warning(ds.TailLog() or 'No devserver log is available.')
467 raise
468 finally:
469 ds.Stop()
470 device.CopyFromDevice(ds.log_file,
471 os.path.join(tempdir, 'target_devserver.log'),
472 error_code_ok=True)
473 device.CopyFromDevice('/var/log/update_engine.log', tempdir,
474 follow_symlinks=True,
475 error_code_ok=True)
476
477 def _CheckPayloads(self, payload_dir):
478 """Checks that all update payloads exists in |payload_dir|."""
479 filenames = []
480 filenames += [ds_wrapper.ROOTFS_FILENAME] if self.do_rootfs_update else []
481 if self.do_stateful_update:
482 filenames += [ds_wrapper.STATEFUL_FILENAME]
483 for fname in filenames:
484 payload = os.path.join(payload_dir, fname)
485 if not os.path.exists(payload):
486 raise FlashError('Payload %s does not exist!' % payload)
487
488 def Verify(self, old_root_dev, new_root_dev):
489 """Verifies that the root deivce changed after reboot."""
490 assert new_root_dev and old_root_dev
491 if new_root_dev == old_root_dev:
492 raise FlashError(
493 'Failed to boot into the new version. Possibly there was a '
494 'signing problem, or an automated rollback occurred because '
495 'your new image failed to boot.')
496
497 @classmethod
498 def GetRootDev(cls, device):
499 """Get the current root device on |device|."""
500 rootdev = device.RunCommand(
501 ['rootdev', '-s'], capture_output=True).output.strip()
502 logging.debug('Current root device is %s', rootdev)
503 return rootdev
504
505 def Cleanup(self):
506 """Cleans up the temporary directory."""
507 if self.wipe:
508 logging.info('Cleaning up temporary working directory...')
509 osutils.RmDir(self.tempdir)
510 else:
511 logging.info('You can find the log files and/or payloads in %s',
512 self.tempdir)
513
514 def _CanRunDevserver(self, device, tempdir):
515 """We can run devserver on |device|.
516
517 If the stateful partition is corrupted, Python or other packages
518 (e.g. cherrypy) needed for rootfs update may be missing on |device|.
519
520 This will also use `ldconfig` to update library paths on the target
521 device if it looks like that's causing problems, which is necessary
522 for base images.
523
524 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400525 device: A ChromiumOSDevice object.
526 tempdir: A temporary directory to store files.
David Pursellf1d16a62015-03-25 13:31:04 -0700527
528 Returns:
529 True if we can start devserver; False otherwise.
530 """
531 logging.info('Checking if we can run devserver on the device.')
532 src_dir = self._CopyDevServerPackage(device, tempdir)
533 devserver_bin = os.path.join(src_dir, self.DEVSERVER_FILENAME)
534 devserver_check_command = ['python', devserver_bin, '--help']
535 try:
536 device.RunCommand(devserver_check_command)
537 except cros_build_lib.RunCommandError as e:
538 logging.warning('Cannot start devserver: %s', e)
539 if 'python: error while loading shared libraries' in str(e):
540 logging.info('Attempting to correct device library paths...')
541 try:
542 device.RunCommand(['ldconfig', '-r', '/'])
543 device.RunCommand(devserver_check_command)
544 logging.info('Library path correction successful.')
545 return True
546 except cros_build_lib.RunCommandError as e2:
547 logging.warning('Library path correction failed: %s', e2)
548
549 return False
550
551 return True
552
553 def Run(self):
554 """Performs remote device update."""
555 old_root_dev, new_root_dev = None, None
556 try:
557 device_connected = False
558 with remote_access.ChromiumOSDeviceHandler(
559 self.ssh_hostname, port=self.ssh_port,
560 base_dir=self.DEVICE_BASE_DIR, ping=self.ping) as device:
561 device_connected = True
562
David Pursellf1d16a62015-03-25 13:31:04 -0700563 payload_dir = self.tempdir
564 if os.path.isdir(self.image):
565 # If the given path is a directory, we use the provided
566 # update payload(s) in the directory.
567 payload_dir = self.image
568 logging.info('Using provided payloads in %s', payload_dir)
569 else:
570 if os.path.isfile(self.image):
571 # If the given path is an image, make sure devserver can
572 # access it and generate payloads.
573 logging.info('Using image %s', self.image)
574 ds_wrapper.GetUpdatePayloadsFromLocalPath(
575 self.image, payload_dir,
576 src_image_to_delta=self.src_image_to_delta,
577 static_dir=_DEVSERVER_STATIC_DIR)
578 else:
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700579 # We should ignore the given/inferred board value and stick to the
580 # device's basic designation. We do emit a warning for good
581 # measure.
582 # TODO(garnold) In fact we should find the board/overlay that the
583 # device inherits from and which defines the SDK "baseline" image
584 # (brillo:339).
585 if self.sdk_version and self.board and not self.force:
586 logging.warning(
587 'Ignoring board value (%s) and deferring to device; use '
588 '--force to override',
589 self.board)
590 self.board = None
591
592 self.board = cros_build_lib.GetBoard(device_board=device.board,
593 override_board=self.board,
594 force=self.yes)
595 if not self.board:
596 raise FlashError('No board identified')
597
598 if not self.force and self.board != device.board:
599 # If a board was specified, it must be compatible with the device.
600 raise FlashError('Device (%s) is incompatible with board %s',
601 device.board, self.board)
602
603 logging.info('Board is %s', self.board)
604
David Pursellf1d16a62015-03-25 13:31:04 -0700605 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700606 translated_path, resolved_path = ds_wrapper.GetImagePathWithXbuddy(
David Pursellf1d16a62015-03-25 13:31:04 -0700607 self.image, self.board, version=self.sdk_version,
608 static_dir=_DEVSERVER_STATIC_DIR, lookup_only=True)
609 logging.info('Using image %s', translated_path)
610 # Convert the translated path to be used in the update request.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700611 image_path = ds_wrapper.ConvertTranslatedPath(resolved_path,
David Pursellf1d16a62015-03-25 13:31:04 -0700612 translated_path)
613
614 # Launch a local devserver to generate/serve update payloads.
615 ds_wrapper.GetUpdatePayloads(
616 image_path, payload_dir, board=self.board,
617 src_image_to_delta=self.src_image_to_delta,
618 static_dir=_DEVSERVER_STATIC_DIR)
619
620 # Verify that all required payloads are in the payload directory.
621 self._CheckPayloads(payload_dir)
622
623 restore_stateful = False
624 if (not self._CanRunDevserver(device, self.tempdir) and
625 self.do_rootfs_update):
626 msg = ('Cannot start devserver! The stateful partition may be '
627 'corrupted.')
628 prompt = 'Attempt to restore the stateful partition?'
629 restore_stateful = self.yes or cros_build_lib.BooleanPrompt(
630 prompt=prompt, default=False, prolog=msg)
631 if not restore_stateful:
632 raise FlashError('Cannot continue to perform rootfs update!')
633
634 if restore_stateful:
635 logging.warning('Restoring the stateful partition...')
636 payload = os.path.join(payload_dir, ds_wrapper.STATEFUL_FILENAME)
637 self.UpdateStateful(device, payload, clobber=self.clobber_stateful)
638 device.Reboot()
639 if self._CanRunDevserver(device, self.tempdir):
640 logging.info('Stateful partition restored.')
641 else:
642 raise FlashError('Unable to restore stateful partition.')
643
644 # Perform device updates.
645 if self.do_rootfs_update:
646 self.SetupRootfsUpdate(device)
647 # Record the current root device. This must be done after
648 # SetupRootfsUpdate because SetupRootfsUpdate may reboot the
649 # device if there is a pending update, which changes the
650 # root device.
651 old_root_dev = self.GetRootDev(device)
652 payload = os.path.join(payload_dir, ds_wrapper.ROOTFS_FILENAME)
653 self.UpdateRootfs(device, payload, self.tempdir)
654 logging.info('Rootfs update completed.')
655
656 if self.do_stateful_update and not restore_stateful:
657 payload = os.path.join(payload_dir, ds_wrapper.STATEFUL_FILENAME)
658 self.UpdateStateful(device, payload, clobber=self.clobber_stateful)
659 logging.info('Stateful update completed.')
660
661 if self.reboot:
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700662 logging.notice('Rebooting device...')
David Pursellf1d16a62015-03-25 13:31:04 -0700663 device.Reboot()
664 if self.clobber_stateful:
665 # --clobber-stateful wipes the stateful partition and the
666 # working directory on the device no longer exists. To
667 # remedy this, we recreate the working directory here.
668 device.BaseRunCommand(['mkdir', '-p', device.work_dir])
669
670 if self.do_rootfs_update and self.reboot:
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700671 logging.notice('Verifying that the device has been updated...')
David Pursellf1d16a62015-03-25 13:31:04 -0700672 new_root_dev = self.GetRootDev(device)
673 self.Verify(old_root_dev, new_root_dev)
674
675 if self.disable_verification:
676 logging.info('Disabling rootfs verification on the device...')
677 device.DisableRootfsVerification()
678
679 except Exception:
680 logging.error('Device update failed.')
681 if device_connected and device.lsb_release:
682 lsb_entries = sorted(device.lsb_release.items())
683 logging.info('Following are the LSB version details of the device:\n%s',
684 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
685 raise
686 else:
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700687 logging.notice('Update performed successfully.')
David Pursellf1d16a62015-03-25 13:31:04 -0700688 finally:
689 self.Cleanup()
690
691
692# TODO(dpursell): replace |brick| argument with blueprints when they're ready.
Gilad Arnold94fc3b02015-04-29 13:04:32 -0700693def Flash(device, image, project_sdk_image=False, sdk_version=None, board=None,
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700694 brick_name=None, blueprint_name=None, install=False,
695 src_image_to_delta=None, rootfs_update=True, stateful_update=True,
696 clobber_stateful=False, reboot=True, wipe=True, ping=True,
697 disable_rootfs_verification=False, clear_cache=False, yes=False,
698 force=False, debug=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700699 """Flashes a device, USB drive, or file with an image.
700
701 This provides functionality common to `cros flash` and `brillo flash`
702 so that they can parse the commandline separately but still use the
703 same underlying functionality.
704
705 Args:
David Pursell2e773382015-04-03 14:30:47 -0700706 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700707 image: Path (string) to the update image. Can be a local or xbuddy path;
708 non-existant local paths are converted to xbuddy.
709 project_sdk_image: Use a clean project SDK image. Overrides |image| if True.
Gilad Arnold94fc3b02015-04-29 13:04:32 -0700710 sdk_version: Which version of SDK image to flash; autodetected if None.
David Pursellf1d16a62015-03-25 13:31:04 -0700711 board: Board to use; None to automatically detect.
Gilad Arnold23bfb222015-04-28 16:17:30 -0700712 brick_name: Brick locator to use. Overrides |board| if not None.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700713 blueprint_name: Blueprint locator to use. Overrides |board| and
714 |brick_name|.
David Pursellf1d16a62015-03-25 13:31:04 -0700715 install: Install to USB using base disk layout; USB |device| scheme only.
716 src_image_to_delta: Local path to an image to be used as the base to
717 generate delta payloads; SSH |device| scheme only.
718 rootfs_update: Update rootfs partition; SSH |device| scheme only.
719 stateful_update: Update stateful partition; SSH |device| scheme only.
720 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
721 reboot: Reboot device after update; SSH |device| scheme only.
722 wipe: Wipe temporary working directory; SSH |device| scheme only.
723 ping: Ping the device before attempting update; SSH |device| scheme only.
724 disable_rootfs_verification: Remove rootfs verification after update; SSH
725 |device| scheme only.
726 clear_cache: Clear the devserver static directory.
727 yes: Assume "yes" for any prompt.
728 force: Ignore sanity checks and prompts. Overrides |yes| if True.
729 debug: Print additional debugging messages.
730
731 Raises:
732 FlashError: An unrecoverable error occured.
733 ValueError: Invalid parameter combination.
734 """
735 if force:
736 yes = True
737
738 if clear_cache:
739 logging.info('Clearing the cache...')
740 ds_wrapper.DevServerWrapper.WipeStaticDirectory(_DEVSERVER_STATIC_DIR)
741
742 try:
743 osutils.SafeMakedirsNonRoot(_DEVSERVER_STATIC_DIR)
744 except OSError:
745 logging.error('Failed to create %s', _DEVSERVER_STATIC_DIR)
746
747 if install:
David Pursell2e773382015-04-03 14:30:47 -0700748 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700749 raise ValueError(
750 '--install can only be used when writing to a USB device')
751 if not cros_build_lib.IsInsideChroot():
752 raise ValueError('--install can only be used inside the chroot')
753
754 # If installing an SDK image, find the version and override image path.
David Pursellf1d16a62015-03-25 13:31:04 -0700755 if project_sdk_image:
David Pursellf1d16a62015-03-25 13:31:04 -0700756 image = 'project_sdk'
Gilad Arnold94fc3b02015-04-29 13:04:32 -0700757 if sdk_version is None:
758 sdk_version = project_sdk.FindVersion()
759 if not sdk_version:
760 raise FlashError('Could not find SDK version')
David Pursellf1d16a62015-03-25 13:31:04 -0700761
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700762 # We don't have enough information on the device to make a good guess on
763 # whether this device is compatible with the blueprint.
764 # TODO(bsimonnet): Add proper compatibility checks. (brbug.com/969)
765 if blueprint_name:
766 board = None
767 if image == 'latest':
768 blueprint = blueprint_lib.Blueprint(blueprint_name)
769 image_dir = os.path.join(
770 workspace_lib.WorkspacePath(), workspace_lib.WORKSPACE_IMAGES_DIR,
771 blueprint.FriendlyName(), 'latest')
772 image = _ChooseImageFromDirectory(image_dir)
773 elif not os.path.exists(image):
774 raise ValueError('Cannot find blueprint image "%s". Only "latest" and '
775 'full image path are supported.' % image)
776 elif brick_name:
777 board = brick_lib.Brick(brick_name).FriendlyName()
David Pursellf1d16a62015-03-25 13:31:04 -0700778
David Pursell2e773382015-04-03 14:30:47 -0700779 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
780 if device:
781 hostname, port = device.hostname, device.port
782 else:
783 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700784 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700785 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700786 hostname,
787 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700788 image,
789 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700790 src_image_to_delta=src_image_to_delta,
791 rootfs_update=rootfs_update,
792 stateful_update=stateful_update,
793 clobber_stateful=clobber_stateful,
794 reboot=reboot,
795 wipe=wipe,
796 debug=debug,
797 yes=yes,
798 force=force,
799 ping=ping,
800 disable_verification=disable_rootfs_verification,
801 sdk_version=sdk_version)
802 updater.Run()
803 elif device.scheme == commandline.DEVICE_SCHEME_USB:
804 path = osutils.ExpandPath(device.path) if device.path else ''
805 logging.info('Preparing to image the removable device %s', path)
806 imager = USBImager(path,
807 board,
808 image,
809 sdk_version=sdk_version,
810 debug=debug,
811 install=install,
812 yes=yes)
813 imager.Run()
814 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
815 logging.info('Preparing to copy image to %s', device.path)
816 imager = FileImager(device.path,
817 board,
818 image,
819 sdk_version=sdk_version,
820 debug=debug,
821 yes=yes)
822 imager.Run()