blob: da5493859b576e50e6a3749f15ac5509b5723504 [file] [log] [blame]
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module for updating the stateful partition on the device.
Use this module to update the stateful partition given a stateful payload
(e.g. stateful.tgz) on the device. This module untars/uncompresses the payload
on the device into var_new and dev_image_new directories. Optionally, you can
ask this module to reset a stateful partition by preparing it to be clobbered on
reboot.
"""
import logging
import os
import tempfile
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import osutils
class Error(Exception):
"""Base exception class of StatefulUpdater errors."""
class StatefulUpdater:
"""The module for updating the stateful partition."""
UPDATE_TYPE_STANDARD = "standard"
UPDATE_TYPE_CLOBBER = "clobber"
_VAR_DIR = "var_new"
_DEV_IMAGE_DIR = "dev_image_new"
_UPDATE_TYPE_FILE = ".update_available"
def __init__(self, device, stateful_dir=constants.STATEFUL_DIR):
"""Initializes the module.
Args:
device: The ChromiumOsDevice to be updated.
stateful_dir: The stateful directory on the Chromium OS device.
"""
self._device = device
self._stateful_dir = stateful_dir
self._var_dir = os.path.join(self._stateful_dir, self._VAR_DIR)
self._dev_image_dir = os.path.join(
self._stateful_dir, self._DEV_IMAGE_DIR
)
self._update_type_file = os.path.join(
self._stateful_dir, self._UPDATE_TYPE_FILE
)
def Update(self, payload_path, is_payload_on_device=True, update_type=None):
"""Updates the stateful partition given the update file.
Args:
payload_path: The path to the stateful update (stateful.tgz). It can
also be just a file descriptor.
is_payload_on_device: True if the payload is on the device. False
if it is on the workstation.
update_type: The type of the stateful update to be marked. Accepted
values: 'standard' (default) and 'clobber'.
"""
try:
cmd = [
"tar",
"--ignore-command-error",
"--overwrite",
"--selinux",
"--directory",
self._stateful_dir,
"-xzf",
]
if is_payload_on_device:
if not self._device.IfPathExists(payload_path):
raise Error("Missing the file: %s" % payload_path)
cmd += [payload_path]
self._device.run(cmd)
elif isinstance(payload_path, int):
cmd += ["-"]
self._device.run(cmd, input=payload_path)
else:
with open(payload_path, "rb") as f:
cmd += ["-"]
self._device.run(cmd, input=f)
except cros_build_lib.RunCommandError as e:
raise Error("Failed to untar the stateful update with error %s" % e)
# Make sure target directories are generated on the device.
if not self._device.IfPathExists(
self._var_dir
) or not self._device.IfPathExists(self._dev_image_dir):
raise Error("Missing var or dev_image in stateful payload.")
self._MarkUpdateType(
update_type
if update_type is not None
else self.UPDATE_TYPE_STANDARD
)
def _MarkUpdateType(self, update_type):
"""Marks the type of the update.
Args:
update_type: The type of the update to be marked. See Update()
"""
if update_type not in (
self.UPDATE_TYPE_CLOBBER,
self.UPDATE_TYPE_STANDARD,
):
raise Error("Invalid update type %s" % update_type)
with tempfile.NamedTemporaryFile() as f:
if update_type == self.UPDATE_TYPE_STANDARD:
logging.notice("Performing standard stateful update...")
elif update_type == self.UPDATE_TYPE_CLOBBER:
logging.notice(
"Restoring stateful to factory_install " "with dev_image..."
)
osutils.WriteFile(f.name, "clobber")
try:
self._device.CopyToDevice(f.name, self._update_type_file, "scp")
except cros_build_lib.RunCommandError as e:
raise Error(
"Failed to copy update type file to device with error %s"
% e
)
def Reset(self):
"""Resets the stateful partition."""
logging.info("Resetting stateful update state.")
try:
self._device.run(
[
"rm",
"-rf",
self._update_type_file,
self._var_dir,
self._dev_image_dir,
]
)
except cros_build_lib.RunCommandError as e:
logging.warning(
"(ignoring) Failed to delete stateful update paths with"
" error: %s",
e,
)