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