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