blob: 9c4c1a77228d4ec9f1a01066d751903d7ed283d7 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Executes early resume initialization.
use std::path::PathBuf;
use anyhow::{Context, Result};
use log::info;
use crate::cookie::{
cookie_description, get_hibernate_cookie, set_hibernate_cookie, HibernateCookieValue,
};
use crate::hiberutil::{HibernateError, ResumeInitOptions};
use crate::volume::VolumeManager;
pub struct ResumeInitConductor {
options: ResumeInitOptions,
}
impl ResumeInitConductor {
pub fn new(options: ResumeInitOptions) -> Self {
Self { options }
}
pub fn resume_init(&mut self) -> Result<()> {
let cookie =
get_hibernate_cookie::<PathBuf>(None).context("Failed to get hibernate cookie")?;
if cookie != HibernateCookieValue::ResumeReady {
if self.options.force {
info!("Hibernate cookie was not set, continuing anyway due to --force");
// In the most common case, no resume from hibernate will be imminent.
} else if cookie == HibernateCookieValue::NoResume {
info!("Hibernate cookie was not set, doing nothing");
return Err(HibernateError::CookieError(
"Cookie not set, doing nothing".to_string(),
))
.context("Not preparing for resume");
// This is the error path, where the system rebooted unexpectedly
// while a resume or abort was underway. If a resume was
// interrupted, the snapshots may contain data we want to preserve
// (logs for investigation). If an abort was interrupted, the
// stateful disk could be halfway merged. Either way, set up the
// snapshots for a merge later in boot.
} else if (cookie == HibernateCookieValue::ResumeInProgress)
|| (cookie == HibernateCookieValue::ResumeAborting)
{
info!(
"Hibernate interrupted (cookie was {}), wiring up snapshots",
cookie_description(cookie)
);
self.setup_snapshots()?;
// The snapshots are valid and wired. Indicate to the main
// hiberman resume process that it should immediately abort and
// merge.
set_hibernate_cookie::<PathBuf>(None, HibernateCookieValue::ResumeAborting)
.context("Failed to set hibernate cookie to ResumeAborting")?;
return Ok(());
}
}
self.setup_snapshots()?;
// The snapshots are valid, so indicate that a resume is in progress,
// and the main resume process later should go for it.
set_hibernate_cookie::<PathBuf>(None, HibernateCookieValue::ResumeInProgress)
.context("Failed to set hibernate cookie to ResumeInProgress")?;
info!("Done with resume init");
Ok(())
}
/// Wire up the snapshot images on top of the logical volumes.
fn setup_snapshots(&self) -> Result<()> {
// First clear the cookie to try and minimize the chances of getting
// stuck in a boot loop. If this is the first time through (eg for a
// valid resume image), the snapshots are not yet valid and have no
// data, so not wiring them up upon interruption is the right thing to
// do. If this is not the first time around (eg crash/powerloss during
// resume), then this at least avoids getting stuck in a boot loop due
// to a crash within this setup code.
set_hibernate_cookie::<PathBuf>(None, HibernateCookieValue::NoResume)
.context("Failed to set hibernate cookie to NoResume")?;
let mut volmgr = VolumeManager::new().context("Failed to create volume manager")?;
volmgr
.setup_hibernate_lv(false)
.context("Failed to set up hibernate LV")?;
volmgr
.setup_stateful_snapshots()
.context("Failed to set up stateful snapshots")?;
// Change the thinpool to be read-only to avoid accidental thinpool
// metadata changes that somehow get around the snapshot. Ideally we'd
// do this before activating all the LVs under the snapshots, but doing
// the activation seems to flip the pool back to being writeable.
let ro_thinpool = volmgr
.activate_thinpool_ro()
.context("Failed to activate thinpool RO")?;
if let Some(mut ro_thinpool) = ro_thinpool {
ro_thinpool.dont_deactivate();
}
Ok(())
}
}