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