blob: 4863e40f78f9230699a83b4010cab4a3b9ae9425 [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
Ralph Nathan9b997232015-05-15 13:13:12 -070011import re
David Pursellf1d16a62015-03-25 13:31:04 -070012import shutil
13import tempfile
14import time
15
16from chromite.cbuildbot import constants
Ralph Nathan872ea4d2015-05-05 18:04:56 -070017from chromite.cli import command
David Pursellf1d16a62015-03-25 13:31:04 -070018from chromite.lib import commandline
19from chromite.lib import cros_build_lib
20from chromite.lib import cros_logging as logging
21from chromite.lib import dev_server_wrapper as ds_wrapper
Ralph Nathan872ea4d2015-05-05 18:04:56 -070022from chromite.lib import operation
David Pursellf1d16a62015-03-25 13:31:04 -070023from chromite.lib import osutils
Gilad Arnold1c8eda52015-05-04 22:32:38 -070024from chromite.lib import path_util
David Pursellf1d16a62015-03-25 13:31:04 -070025from chromite.lib import remote_access
26
27
Don Garrett97d7dc22015-01-20 14:07:56 -080028DEVSERVER_STATIC_DIR = path_util.FromChrootPath(
David Pursellf1d16a62015-03-25 13:31:04 -070029 os.path.join(constants.CHROOT_SOURCE_ROOT, 'devserver', 'static'))
30
31
Ralph Nathan9b997232015-05-15 13:13:12 -070032class UsbImagerOperation(operation.ProgressBarOperation):
33 """Progress bar for flashing image to operation."""
34
35 def __init__(self, image):
36 super(UsbImagerOperation, self).__init__()
37 self._size = os.path.getsize(image)
38 self._transferred = 0.
39 self._bytes = re.compile(r'(\d+) bytes')
40
41 def _GetDDPid(self):
42 """Get the Pid of dd."""
43 try:
44 pids = cros_build_lib.RunCommand(['pgrep', 'dd'], capture_output=True,
45 print_cmd=False).output
46 for pid in pids.splitlines():
47 if osutils.IsChildProcess(int(pid), name='dd'):
48 return int(pid)
49 return -1
50 except cros_build_lib.RunCommandError:
51 # If dd isn't still running, then we assume that it is finished.
52 return -1
53
54 def _PingDD(self, dd_pid):
55 """Send USR1 signal to dd to get status update."""
56 try:
57 cmd = ['kill', '-USR1', str(dd_pid)]
58 cros_build_lib.SudoRunCommand(cmd, print_cmd=False)
59 except cros_build_lib.RunCommandError:
60 # Here we assume that dd finished in the background.
61 return
62
63 def ParseOutput(self, output=None):
64 """Parse the output of dd to update progress bar."""
65 dd_pid = self._GetDDPid()
66 if dd_pid == -1:
67 return
68
69 self._PingDD(dd_pid)
70
71 if output is None:
72 stdout = self._stdout.read()
73 stderr = self._stderr.read()
74 output = stdout + stderr
75
76 match = self._bytes.search(output)
77 if match:
78 self._transferred = match.groups()[0]
79
80 self.ProgressBar(float(self._transferred) / self._size)
81
82
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070083def _IsFilePathGPTDiskImage(file_path):
84 """Determines if a file is a valid GPT disk.
85
86 Args:
87 file_path: Path to the file to test.
88 """
89 if os.path.isfile(file_path):
90 with cros_build_lib.Open(file_path) as image_file:
91 image_file.seek(0x1fe)
92 if image_file.read(10) == '\x55\xaaEFI PART':
93 return True
94 return False
95
96
97def _ChooseImageFromDirectory(dir_path):
98 """Lists all image files in |dir_path| and ask user to select one.
99
100 Args:
101 dir_path: Path to the directory.
102 """
103 images = sorted([x for x in os.listdir(dir_path) if
104 _IsFilePathGPTDiskImage(os.path.join(dir_path, x))])
105 idx = 0
106 if len(images) == 0:
107 raise ValueError('No image found in %s.' % dir_path)
108 elif len(images) > 1:
109 idx = cros_build_lib.GetChoice(
110 'Multiple images found in %s. Please select one to continue:' % (
111 (dir_path,)),
112 images)
113
114 return os.path.join(dir_path, images[idx])
115
116
David Pursellf1d16a62015-03-25 13:31:04 -0700117class FlashError(Exception):
118 """Thrown when there is an unrecoverable error during flash."""
119
120
121class USBImager(object):
122 """Copy image to the target removable device."""
123
Gilad Arnoldd0461442015-07-07 11:52:46 -0700124 def __init__(self, device, board, image, debug=False, install=False,
125 yes=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700126 """Initalizes USBImager."""
127 self.device = device
128 self.board = board if board else cros_build_lib.GetDefaultBoard()
129 self.image = image
David Pursellf1d16a62015-03-25 13:31:04 -0700130 self.debug = debug
131 self.debug_level = logging.DEBUG if debug else logging.INFO
132 self.install = install
133 self.yes = yes
134
135 def DeviceNameToPath(self, device_name):
136 return '/dev/%s' % device_name
137
138 def GetRemovableDeviceDescription(self, device):
139 """Returns a informational description of the removable |device|.
140
141 Args:
142 device: the device name (e.g. sdc).
143
144 Returns:
145 A string describing |device| (e.g. Patriot Memory 7918 MB).
146 """
147 desc = [
148 osutils.GetDeviceInfo(device, keyword='manufacturer'),
149 osutils.GetDeviceInfo(device, keyword='product'),
150 osutils.GetDeviceSize(self.DeviceNameToPath(device)),
151 '(%s)' % self.DeviceNameToPath(device),
152 ]
153 return ' '.join([x for x in desc if x])
154
155 def ListAllRemovableDevices(self):
156 """Returns a list of removable devices.
157
158 Returns:
159 A list of device names (e.g. ['sdb', 'sdc']).
160 """
161 devices = osutils.ListBlockDevices()
162 removable_devices = []
163 for d in devices:
164 if d.TYPE == 'disk' and d.RM == '1':
165 removable_devices.append(d.NAME)
166
167 return removable_devices
168
169 def ChooseRemovableDevice(self, devices):
170 """Lists all removable devices and asks user to select/confirm.
171
172 Args:
173 devices: a list of device names (e.g. ['sda', 'sdb']).
174
175 Returns:
176 The device name chosen by the user.
177 """
178 idx = cros_build_lib.GetChoice(
179 'Removable device(s) found. Please select/confirm to continue:',
180 [self.GetRemovableDeviceDescription(x) for x in devices])
181
182 return devices[idx]
183
184 def InstallImageToDevice(self, image, device):
185 """Installs |image| to the removable |device|.
186
187 Args:
188 image: Path to the image to copy.
189 device: Device to copy to.
190 """
191 cmd = [
192 'chromeos-install',
193 '--yes',
194 '--skip_src_removable',
195 '--skip_dst_removable',
196 '--payload_image=%s' % image,
197 '--dst=%s' % device,
198 '--skip_postinstall',
199 ]
200 cros_build_lib.SudoRunCommand(cmd)
201
202 def CopyImageToDevice(self, image, device):
203 """Copies |image| to the removable |device|.
204
205 Args:
206 image: Path to the image to copy.
207 device: Device to copy to.
208 """
Ralph Nathan9b997232015-05-15 13:13:12 -0700209 cmd = ['dd', 'if=%s' % image, 'of=%s' % device, 'bs=4M', 'iflag=fullblock',
210 'oflag=sync']
211 if logging.getLogger().getEffectiveLevel() <= logging.NOTICE:
212 op = UsbImagerOperation(image)
213 op.Run(cros_build_lib.SudoRunCommand, cmd, debug_level=logging.NOTICE,
214 update_period=0.5)
215 else:
216 cros_build_lib.SudoRunCommand(
217 cmd, debug_level=logging.NOTICE,
218 print_cmd=logging.getLogger().getEffectiveLevel() < logging.NOTICE)
David Pursellf1d16a62015-03-25 13:31:04 -0700219
David Pursellf1d16a62015-03-25 13:31:04 -0700220 cros_build_lib.SudoRunCommand(['sync'], debug_level=self.debug_level)
221
David Pursellf1d16a62015-03-25 13:31:04 -0700222 def _GetImagePath(self):
223 """Returns the image path to use."""
224 image_path = translated_path = None
225 if os.path.isfile(self.image):
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700226 if not self.yes and not _IsFilePathGPTDiskImage(self.image):
David Pursellf1d16a62015-03-25 13:31:04 -0700227 # TODO(wnwen): Open the tarball and if there is just one file in it,
228 # use that instead. Existing code in upload_symbols.py.
229 if cros_build_lib.BooleanPrompt(
230 prolog='The given image file is not a valid disk image. Perhaps '
231 'you forgot to untar it.',
232 prompt='Terminate the current flash process?'):
233 raise FlashError('Update terminated by user.')
234 image_path = self.image
235 elif os.path.isdir(self.image):
236 # Ask user which image (*.bin) in the folder to use.
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700237 image_path = _ChooseImageFromDirectory(self.image)
David Pursellf1d16a62015-03-25 13:31:04 -0700238 else:
239 # Translate the xbuddy path to get the exact image to use.
Gilad Arnolde62ec902015-04-24 14:41:02 -0700240 translated_path, _ = ds_wrapper.GetImagePathWithXbuddy(
Don Garrett97d7dc22015-01-20 14:07:56 -0800241 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700242 image_path = ds_wrapper.TranslatedPathToLocalPath(
Don Garrett97d7dc22015-01-20 14:07:56 -0800243 translated_path, DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700244
245 logging.info('Using image %s', translated_path or image_path)
246 return image_path
247
248 def Run(self):
249 """Image the removable device."""
250 devices = self.ListAllRemovableDevices()
251
252 if self.device:
253 # If user specified a device path, check if it exists.
254 if not os.path.exists(self.device):
255 raise FlashError('Device path %s does not exist.' % self.device)
256
257 # Then check if it is removable.
258 if self.device not in [self.DeviceNameToPath(x) for x in devices]:
259 msg = '%s is not a removable device.' % self.device
260 if not (self.yes or cros_build_lib.BooleanPrompt(
261 default=False, prolog=msg)):
262 raise FlashError('You can specify usb:// to choose from a list of '
263 'removable devices.')
264 target = None
265 if self.device:
266 # Get device name from path (e.g. sdc in /dev/sdc).
267 target = self.device.rsplit(os.path.sep, 1)[-1]
268 elif devices:
269 # Ask user to choose from the list.
270 target = self.ChooseRemovableDevice(devices)
271 else:
272 raise FlashError('No removable devices detected.')
273
274 image_path = self._GetImagePath()
275 try:
276 device = self.DeviceNameToPath(target)
277 if self.install:
278 self.InstallImageToDevice(image_path, device)
279 else:
280 self.CopyImageToDevice(image_path, device)
281 except cros_build_lib.RunCommandError:
282 logging.error('Failed copying image to device %s',
283 self.DeviceNameToPath(target))
284
285
286class FileImager(USBImager):
287 """Copy image to the target path."""
288
289 def Run(self):
290 """Copy the image to the path specified by self.device."""
291 if not os.path.exists(self.device):
292 raise FlashError('Path %s does not exist.' % self.device)
293
294 image_path = self._GetImagePath()
295 if os.path.isdir(self.device):
296 logging.info('Copying to %s',
297 os.path.join(self.device, os.path.basename(image_path)))
298 else:
299 logging.info('Copying to %s', self.device)
300 try:
301 shutil.copy(image_path, self.device)
302 except IOError:
303 logging.error('Failed to copy image %s to %s', image_path, self.device)
304
305
306class RemoteDeviceUpdater(object):
307 """Performs update on a remote device."""
308 DEVSERVER_FILENAME = 'devserver.py'
309 STATEFUL_UPDATE_BIN = '/usr/bin/stateful_update'
310 UPDATE_ENGINE_BIN = 'update_engine_client'
David Pursellf1d16a62015-03-25 13:31:04 -0700311 # Root working directory on the device. This directory is in the
312 # stateful partition and thus has enough space to store the payloads.
313 DEVICE_BASE_DIR = '/mnt/stateful_partition/cros-flash'
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700314 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5
315 UPDATE_CHECK_INTERVAL_NORMAL = 10
David Pursellf1d16a62015-03-25 13:31:04 -0700316
317 def __init__(self, ssh_hostname, ssh_port, image, stateful_update=True,
318 rootfs_update=True, clobber_stateful=False, reboot=True,
Gilad Arnoldd0461442015-07-07 11:52:46 -0700319 board=None, src_image_to_delta=None, wipe=True, debug=False,
320 yes=False, force=False, ping=True,
Gilad Arnold821547c2015-07-07 08:57:48 -0700321 disable_verification=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700322 """Initializes RemoteDeviceUpdater"""
323 if not stateful_update and not rootfs_update:
324 raise ValueError('No update operation to perform; either stateful or'
325 ' rootfs partitions must be updated.')
326 self.tempdir = tempfile.mkdtemp(prefix='cros-flash')
327 self.ssh_hostname = ssh_hostname
328 self.ssh_port = ssh_port
329 self.image = image
330 self.board = board
David Pursellf1d16a62015-03-25 13:31:04 -0700331 self.src_image_to_delta = src_image_to_delta
332 self.do_stateful_update = stateful_update
333 self.do_rootfs_update = rootfs_update
334 self.disable_verification = disable_verification
335 self.clobber_stateful = clobber_stateful
336 self.reboot = reboot
337 self.debug = debug
338 self.ping = ping
339 # Do not wipe if debug is set.
340 self.wipe = wipe and not debug
341 self.yes = yes
342 self.force = force
David Pursellf1d16a62015-03-25 13:31:04 -0700343
344 # pylint: disable=unbalanced-tuple-unpacking
345 @classmethod
346 def GetUpdateStatus(cls, device, keys=None):
347 """Returns the status of the update engine on the |device|.
348
349 Retrieves the status from update engine and confirms all keys are
350 in the status.
351
352 Args:
353 device: A ChromiumOSDevice object.
354 keys: the keys to look for in the status result (defaults to
355 ['CURRENT_OP']).
356
357 Returns:
358 A list of values in the order of |keys|.
359 """
360 keys = ['CURRENT_OP'] if not keys else keys
361 result = device.RunCommand([cls.UPDATE_ENGINE_BIN, '--status'],
362 capture_output=True)
363 if not result.output:
364 raise Exception('Cannot get update status')
365
366 try:
367 status = cros_build_lib.LoadKeyValueFile(
368 cStringIO.StringIO(result.output))
369 except ValueError:
370 raise ValueError('Cannot parse update status')
371
372 values = []
373 for key in keys:
374 if key not in status:
375 raise ValueError('Missing %s in the update engine status')
376
377 values.append(status.get(key))
378
379 return values
380
381 def UpdateStateful(self, device, payload, clobber=False):
382 """Update the stateful partition of the device.
383
384 Args:
385 device: The ChromiumOSDevice object to update.
386 payload: The path to the update payload.
387 clobber: Clobber stateful partition (defaults to False).
388 """
389 # Copy latest stateful_update to device.
Gilad Arnoldd0461442015-07-07 11:52:46 -0700390 stateful_update_bin = path_util.FromChrootPath(self.STATEFUL_UPDATE_BIN)
David Pursellf1d16a62015-03-25 13:31:04 -0700391 device.CopyToWorkDir(stateful_update_bin)
392 msg = 'Updating stateful partition'
393 logging.info('Copying stateful payload to device...')
394 device.CopyToWorkDir(payload)
395 cmd = ['sh',
396 os.path.join(device.work_dir,
397 os.path.basename(self.STATEFUL_UPDATE_BIN)),
398 os.path.join(device.work_dir, os.path.basename(payload))]
399
400 if clobber:
401 cmd.append('--stateful_change=clean')
402 msg += ' with clobber enabled'
403
404 logging.info('%s...', msg)
405 try:
406 device.RunCommand(cmd)
407 except cros_build_lib.RunCommandError:
408 logging.error('Faild to perform stateful partition update.')
409
410 def _CopyDevServerPackage(self, device, tempdir):
411 """Copy devserver package to work directory of device.
412
413 Args:
414 device: The ChromiumOSDevice object to copy the package to.
415 tempdir: The directory to temporarily store devserver package.
416 """
417 logging.info('Copying devserver package to device...')
418 src_dir = os.path.join(tempdir, 'src')
419 osutils.RmDir(src_dir, ignore_missing=True)
420 shutil.copytree(
421 ds_wrapper.DEVSERVER_PKG_DIR, src_dir,
422 ignore=shutil.ignore_patterns('*.pyc', 'tmp*', '.*', 'static', '*~'))
423 device.CopyToWorkDir(src_dir)
424 return os.path.join(device.work_dir, os.path.basename(src_dir))
425
426 def SetupRootfsUpdate(self, device):
427 """Makes sure |device| is ready for rootfs update."""
428 logging.info('Checking if update engine is idle...')
429 status, = self.GetUpdateStatus(device)
430 if status == 'UPDATE_STATUS_UPDATED_NEED_REBOOT':
431 logging.info('Device needs to reboot before updating...')
432 device.Reboot()
433 status, = self.GetUpdateStatus(device)
434
435 if status != 'UPDATE_STATUS_IDLE':
436 raise FlashError('Update engine is not idle. Status: %s' % status)
437
438 def UpdateRootfs(self, device, payload, tempdir):
439 """Update the rootfs partition of the device.
440
441 Args:
442 device: The ChromiumOSDevice object to update.
443 payload: The path to the update payload.
444 tempdir: The directory to store temporary files.
445 """
446 # Setup devserver and payload on the target device.
447 static_dir = os.path.join(device.work_dir, 'static')
448 payload_dir = os.path.join(static_dir, 'pregenerated')
449 src_dir = self._CopyDevServerPackage(device, tempdir)
450 device.RunCommand(['mkdir', '-p', payload_dir])
451 logging.info('Copying rootfs payload to device...')
452 device.CopyToDevice(payload, payload_dir)
453 devserver_bin = os.path.join(src_dir, self.DEVSERVER_FILENAME)
454 ds = ds_wrapper.RemoteDevServerWrapper(
Gilad Arnoldd0461442015-07-07 11:52:46 -0700455 device, devserver_bin, static_dir=static_dir, log_dir=device.work_dir)
David Pursellf1d16a62015-03-25 13:31:04 -0700456
457 logging.info('Updating rootfs partition')
458 try:
459 ds.Start()
460 # Use the localhost IP address to ensure that update engine
461 # client can connect to the devserver.
462 omaha_url = ds.GetDevServerURL(
463 ip='127.0.0.1', port=ds.port, sub_dir='update/pregenerated')
464 cmd = [self.UPDATE_ENGINE_BIN, '-check_for_update',
465 '-omaha_url=%s' % omaha_url]
466 device.RunCommand(cmd)
467
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700468 # If we are using a progress bar, update it every 0.5s instead of 10s.
469 if command.UseProgressBar():
470 update_check_interval = self.UPDATE_CHECK_INTERVAL_PROGRESSBAR
471 oper = operation.ProgressBarOperation()
472 else:
473 update_check_interval = self.UPDATE_CHECK_INTERVAL_NORMAL
474 oper = None
475 end_message_not_printed = True
476
David Pursellf1d16a62015-03-25 13:31:04 -0700477 # Loop until update is complete.
478 while True:
479 op, progress = self.GetUpdateStatus(device, ['CURRENT_OP', 'PROGRESS'])
480 logging.info('Waiting for update...status: %s at progress %s',
481 op, progress)
482
483 if op == 'UPDATE_STATUS_UPDATED_NEED_REBOOT':
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700484 logging.notice('Update completed.')
David Pursellf1d16a62015-03-25 13:31:04 -0700485 break
486
487 if op == 'UPDATE_STATUS_IDLE':
488 raise FlashError(
489 'Update failed with unexpected update status: %s' % op)
490
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700491 if oper is not None:
492 if op == 'UPDATE_STATUS_DOWNLOADING':
493 oper.ProgressBar(float(progress))
494 elif end_message_not_printed and op == 'UPDATE_STATUS_FINALIZING':
495 oper.Cleanup()
496 logging.notice('Finalizing image.')
497 end_message_not_printed = False
498
499 time.sleep(update_check_interval)
David Pursellf1d16a62015-03-25 13:31:04 -0700500
501 ds.Stop()
502 except Exception:
503 logging.error('Rootfs update failed.')
504 logging.warning(ds.TailLog() or 'No devserver log is available.')
505 raise
506 finally:
507 ds.Stop()
508 device.CopyFromDevice(ds.log_file,
509 os.path.join(tempdir, 'target_devserver.log'),
510 error_code_ok=True)
511 device.CopyFromDevice('/var/log/update_engine.log', tempdir,
512 follow_symlinks=True,
513 error_code_ok=True)
514
515 def _CheckPayloads(self, payload_dir):
516 """Checks that all update payloads exists in |payload_dir|."""
517 filenames = []
518 filenames += [ds_wrapper.ROOTFS_FILENAME] if self.do_rootfs_update else []
519 if self.do_stateful_update:
520 filenames += [ds_wrapper.STATEFUL_FILENAME]
521 for fname in filenames:
522 payload = os.path.join(payload_dir, fname)
523 if not os.path.exists(payload):
524 raise FlashError('Payload %s does not exist!' % payload)
525
526 def Verify(self, old_root_dev, new_root_dev):
527 """Verifies that the root deivce changed after reboot."""
528 assert new_root_dev and old_root_dev
529 if new_root_dev == old_root_dev:
530 raise FlashError(
531 'Failed to boot into the new version. Possibly there was a '
532 'signing problem, or an automated rollback occurred because '
533 'your new image failed to boot.')
534
535 @classmethod
536 def GetRootDev(cls, device):
537 """Get the current root device on |device|."""
538 rootdev = device.RunCommand(
539 ['rootdev', '-s'], capture_output=True).output.strip()
540 logging.debug('Current root device is %s', rootdev)
541 return rootdev
542
543 def Cleanup(self):
544 """Cleans up the temporary directory."""
545 if self.wipe:
546 logging.info('Cleaning up temporary working directory...')
547 osutils.RmDir(self.tempdir)
548 else:
549 logging.info('You can find the log files and/or payloads in %s',
550 self.tempdir)
551
552 def _CanRunDevserver(self, device, tempdir):
553 """We can run devserver on |device|.
554
555 If the stateful partition is corrupted, Python or other packages
556 (e.g. cherrypy) needed for rootfs update may be missing on |device|.
557
558 This will also use `ldconfig` to update library paths on the target
559 device if it looks like that's causing problems, which is necessary
560 for base images.
561
562 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400563 device: A ChromiumOSDevice object.
564 tempdir: A temporary directory to store files.
David Pursellf1d16a62015-03-25 13:31:04 -0700565
566 Returns:
567 True if we can start devserver; False otherwise.
568 """
569 logging.info('Checking if we can run devserver on the device.')
570 src_dir = self._CopyDevServerPackage(device, tempdir)
571 devserver_bin = os.path.join(src_dir, self.DEVSERVER_FILENAME)
572 devserver_check_command = ['python', devserver_bin, '--help']
573 try:
574 device.RunCommand(devserver_check_command)
575 except cros_build_lib.RunCommandError as e:
576 logging.warning('Cannot start devserver: %s', e)
577 if 'python: error while loading shared libraries' in str(e):
578 logging.info('Attempting to correct device library paths...')
579 try:
580 device.RunCommand(['ldconfig', '-r', '/'])
581 device.RunCommand(devserver_check_command)
582 logging.info('Library path correction successful.')
583 return True
584 except cros_build_lib.RunCommandError as e2:
585 logging.warning('Library path correction failed: %s', e2)
586
587 return False
588
589 return True
590
591 def Run(self):
592 """Performs remote device update."""
593 old_root_dev, new_root_dev = None, None
594 try:
595 device_connected = False
596 with remote_access.ChromiumOSDeviceHandler(
597 self.ssh_hostname, port=self.ssh_port,
598 base_dir=self.DEVICE_BASE_DIR, ping=self.ping) as device:
599 device_connected = True
600
David Pursellf1d16a62015-03-25 13:31:04 -0700601 payload_dir = self.tempdir
602 if os.path.isdir(self.image):
Gilad Arnold821547c2015-07-07 08:57:48 -0700603 # If the given path is a directory, we use the provided update
604 # payload(s) in the directory.
David Pursellf1d16a62015-03-25 13:31:04 -0700605 payload_dir = self.image
606 logging.info('Using provided payloads in %s', payload_dir)
Gilad Arnold821547c2015-07-07 08:57:48 -0700607 elif os.path.isfile(self.image):
608 # If the given path is an image, make sure devserver can access it
609 # and generate payloads.
610 logging.info('Using image %s', self.image)
611 ds_wrapper.GetUpdatePayloadsFromLocalPath(
612 self.image, payload_dir,
613 src_image_to_delta=self.src_image_to_delta,
Don Garrett97d7dc22015-01-20 14:07:56 -0800614 static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700615 else:
Gilad Arnold821547c2015-07-07 08:57:48 -0700616 self.board = cros_build_lib.GetBoard(device_board=device.board,
617 override_board=self.board,
618 force=self.yes)
619 if not self.board:
620 raise FlashError('No board identified')
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700621
Gilad Arnold821547c2015-07-07 08:57:48 -0700622 if not self.force and self.board != device.board:
623 # If a board was specified, it must be compatible with the device.
624 raise FlashError('Device (%s) is incompatible with board %s',
625 device.board, self.board)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700626
Gilad Arnold821547c2015-07-07 08:57:48 -0700627 logging.info('Board is %s', self.board)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700628
Gilad Arnold821547c2015-07-07 08:57:48 -0700629 # Translate the xbuddy path to get the exact image to use.
630 translated_path, resolved_path = ds_wrapper.GetImagePathWithXbuddy(
Don Garrett97d7dc22015-01-20 14:07:56 -0800631 self.image, self.board, static_dir=DEVSERVER_STATIC_DIR,
Gilad Arnold821547c2015-07-07 08:57:48 -0700632 lookup_only=True)
633 logging.info('Using image %s', translated_path)
634 # Convert the translated path to be used in the update request.
635 image_path = ds_wrapper.ConvertTranslatedPath(resolved_path,
636 translated_path)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700637
Gilad Arnold821547c2015-07-07 08:57:48 -0700638 # Launch a local devserver to generate/serve update payloads.
639 ds_wrapper.GetUpdatePayloads(
640 image_path, payload_dir, board=self.board,
641 src_image_to_delta=self.src_image_to_delta,
Don Garrett97d7dc22015-01-20 14:07:56 -0800642 static_dir=DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700643
644 # Verify that all required payloads are in the payload directory.
645 self._CheckPayloads(payload_dir)
646
647 restore_stateful = False
648 if (not self._CanRunDevserver(device, self.tempdir) and
649 self.do_rootfs_update):
650 msg = ('Cannot start devserver! The stateful partition may be '
651 'corrupted.')
652 prompt = 'Attempt to restore the stateful partition?'
653 restore_stateful = self.yes or cros_build_lib.BooleanPrompt(
654 prompt=prompt, default=False, prolog=msg)
655 if not restore_stateful:
656 raise FlashError('Cannot continue to perform rootfs update!')
657
658 if restore_stateful:
659 logging.warning('Restoring the stateful partition...')
660 payload = os.path.join(payload_dir, ds_wrapper.STATEFUL_FILENAME)
661 self.UpdateStateful(device, payload, clobber=self.clobber_stateful)
662 device.Reboot()
663 if self._CanRunDevserver(device, self.tempdir):
664 logging.info('Stateful partition restored.')
665 else:
666 raise FlashError('Unable to restore stateful partition.')
667
668 # Perform device updates.
669 if self.do_rootfs_update:
670 self.SetupRootfsUpdate(device)
671 # Record the current root device. This must be done after
672 # SetupRootfsUpdate because SetupRootfsUpdate may reboot the
673 # device if there is a pending update, which changes the
674 # root device.
675 old_root_dev = self.GetRootDev(device)
676 payload = os.path.join(payload_dir, ds_wrapper.ROOTFS_FILENAME)
677 self.UpdateRootfs(device, payload, self.tempdir)
678 logging.info('Rootfs update completed.')
679
680 if self.do_stateful_update and not restore_stateful:
681 payload = os.path.join(payload_dir, ds_wrapper.STATEFUL_FILENAME)
682 self.UpdateStateful(device, payload, clobber=self.clobber_stateful)
683 logging.info('Stateful update completed.')
684
685 if self.reboot:
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700686 logging.notice('Rebooting device...')
David Pursellf1d16a62015-03-25 13:31:04 -0700687 device.Reboot()
688 if self.clobber_stateful:
689 # --clobber-stateful wipes the stateful partition and the
690 # working directory on the device no longer exists. To
691 # remedy this, we recreate the working directory here.
692 device.BaseRunCommand(['mkdir', '-p', device.work_dir])
693
694 if self.do_rootfs_update and self.reboot:
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700695 logging.notice('Verifying that the device has been updated...')
David Pursellf1d16a62015-03-25 13:31:04 -0700696 new_root_dev = self.GetRootDev(device)
697 self.Verify(old_root_dev, new_root_dev)
698
699 if self.disable_verification:
700 logging.info('Disabling rootfs verification on the device...')
701 device.DisableRootfsVerification()
702
703 except Exception:
704 logging.error('Device update failed.')
705 if device_connected and device.lsb_release:
706 lsb_entries = sorted(device.lsb_release.items())
707 logging.info('Following are the LSB version details of the device:\n%s',
708 '\n'.join('%s=%s' % (k, v) for k, v in lsb_entries))
709 raise
710 else:
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700711 logging.notice('Update performed successfully.')
David Pursellf1d16a62015-03-25 13:31:04 -0700712 finally:
713 self.Cleanup()
714
715
Gilad Arnoldbfcfaff2015-07-07 10:08:02 -0700716def Flash(device, image, board=None, install=False, src_image_to_delta=None,
717 rootfs_update=True, stateful_update=True, clobber_stateful=False,
718 reboot=True, wipe=True, ping=True, disable_rootfs_verification=False,
719 clear_cache=False, yes=False, force=False, debug=False):
David Pursellf1d16a62015-03-25 13:31:04 -0700720 """Flashes a device, USB drive, or file with an image.
721
722 This provides functionality common to `cros flash` and `brillo flash`
723 so that they can parse the commandline separately but still use the
724 same underlying functionality.
725
726 Args:
David Pursell2e773382015-04-03 14:30:47 -0700727 device: commandline.Device object; None to use the default device.
David Pursellf1d16a62015-03-25 13:31:04 -0700728 image: Path (string) to the update image. Can be a local or xbuddy path;
729 non-existant local paths are converted to xbuddy.
David Pursellf1d16a62015-03-25 13:31:04 -0700730 board: Board to use; None to automatically detect.
David Pursellf1d16a62015-03-25 13:31:04 -0700731 install: Install to USB using base disk layout; USB |device| scheme only.
732 src_image_to_delta: Local path to an image to be used as the base to
733 generate delta payloads; SSH |device| scheme only.
734 rootfs_update: Update rootfs partition; SSH |device| scheme only.
735 stateful_update: Update stateful partition; SSH |device| scheme only.
736 clobber_stateful: Clobber stateful partition; SSH |device| scheme only.
737 reboot: Reboot device after update; SSH |device| scheme only.
738 wipe: Wipe temporary working directory; SSH |device| scheme only.
739 ping: Ping the device before attempting update; SSH |device| scheme only.
740 disable_rootfs_verification: Remove rootfs verification after update; SSH
741 |device| scheme only.
742 clear_cache: Clear the devserver static directory.
743 yes: Assume "yes" for any prompt.
744 force: Ignore sanity checks and prompts. Overrides |yes| if True.
745 debug: Print additional debugging messages.
746
747 Raises:
748 FlashError: An unrecoverable error occured.
749 ValueError: Invalid parameter combination.
750 """
751 if force:
752 yes = True
753
754 if clear_cache:
755 logging.info('Clearing the cache...')
Don Garrett97d7dc22015-01-20 14:07:56 -0800756 ds_wrapper.DevServerWrapper.WipeStaticDirectory(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700757
758 try:
Don Garrett97d7dc22015-01-20 14:07:56 -0800759 osutils.SafeMakedirsNonRoot(DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700760 except OSError:
Don Garrett97d7dc22015-01-20 14:07:56 -0800761 logging.error('Failed to create %s', DEVSERVER_STATIC_DIR)
David Pursellf1d16a62015-03-25 13:31:04 -0700762
763 if install:
David Pursell2e773382015-04-03 14:30:47 -0700764 if not device or device.scheme != commandline.DEVICE_SCHEME_USB:
David Pursellf1d16a62015-03-25 13:31:04 -0700765 raise ValueError(
766 '--install can only be used when writing to a USB device')
767 if not cros_build_lib.IsInsideChroot():
768 raise ValueError('--install can only be used inside the chroot')
769
David Pursell2e773382015-04-03 14:30:47 -0700770 if not device or device.scheme == commandline.DEVICE_SCHEME_SSH:
771 if device:
772 hostname, port = device.hostname, device.port
773 else:
774 hostname, port = None, None
Ralph Nathan872ea4d2015-05-05 18:04:56 -0700775 logging.notice('Preparing to update the remote device %s', hostname)
David Pursellf1d16a62015-03-25 13:31:04 -0700776 updater = RemoteDeviceUpdater(
David Pursell2e773382015-04-03 14:30:47 -0700777 hostname,
778 port,
David Pursellf1d16a62015-03-25 13:31:04 -0700779 image,
780 board=board,
David Pursellf1d16a62015-03-25 13:31:04 -0700781 src_image_to_delta=src_image_to_delta,
782 rootfs_update=rootfs_update,
783 stateful_update=stateful_update,
784 clobber_stateful=clobber_stateful,
785 reboot=reboot,
786 wipe=wipe,
787 debug=debug,
788 yes=yes,
789 force=force,
790 ping=ping,
Gilad Arnold821547c2015-07-07 08:57:48 -0700791 disable_verification=disable_rootfs_verification)
David Pursellf1d16a62015-03-25 13:31:04 -0700792 updater.Run()
793 elif device.scheme == commandline.DEVICE_SCHEME_USB:
794 path = osutils.ExpandPath(device.path) if device.path else ''
795 logging.info('Preparing to image the removable device %s', path)
796 imager = USBImager(path,
797 board,
798 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700799 debug=debug,
800 install=install,
801 yes=yes)
802 imager.Run()
803 elif device.scheme == commandline.DEVICE_SCHEME_FILE:
804 logging.info('Preparing to copy image to %s', device.path)
805 imager = FileImager(device.path,
806 board,
807 image,
David Pursellf1d16a62015-03-25 13:31:04 -0700808 debug=debug,
809 yes=yes)
810 imager.Run()