blob: 609ee868f7b0fa9b27bc479de71d6ce8d673da8b [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Implement common functions and definitions used throughout the app and library.
use std::process::Command;
use anyhow::{Context, Result};
use log::{error, warn};
use thiserror::Error as ThisError;
/// Define the number of pages in a larger chunk used to read and write the
/// hibernate data file.
pub const BUFFER_PAGES: usize = 32;
#[derive(Debug, ThisError)]
pub enum HibernateError {
/// Cookie error
#[error("Cookie error: {0}")]
CookieError(String),
/// Dbus error
#[error("Dbus error: {0}")]
DbusError(String),
/// Failed to copy the FD for the polling context.
#[error("Failed to fallocate the file: {0}")]
FallocateError(sys_util::Error),
/// Error getting the fiemap
#[error("Error getting the fiemap: {0}")]
FiemapError(sys_util::Error),
/// First data byte mismatch
#[error("First data byte mismatch")]
FirstDataByteMismatch(),
/// Header content hash mismatch
#[error("Header content hash mismatch")]
HeaderContentHashMismatch(),
/// Header content length mismatch
#[error("Header content length mismatch")]
HeaderContentLengthMismatch(),
/// Header incomplete
#[error("Header incomplete")]
HeaderIncomplete(),
/// Invalid fiemap
#[error("Invalid fiemap: {0}")]
InvalidFiemapError(String),
/// Image unencrypted
#[error("Image unencrypted")]
ImageUnencryptedError(),
/// Key manager error
#[error("Key manager error: {0}")]
KeyManagerError(String),
/// Metadata error
#[error("Metadata error: {0}")]
MetadataError(String),
/// Failed to lock process memory.
#[error("Failed to mlockall: {0}")]
MlockallError(sys_util::Error),
/// Mmap error.
#[error("mmap error: {0}")]
MmapError(sys_util::Error),
/// I/O size error
#[error("I/O size error: {0}")]
IoSizeError(String),
/// Snapshot device error.
#[error("Snapshot device error: {0}")]
SnapshotError(String),
/// Snapshot ioctl error.
#[error("Snapshot ioctl error: {0}: {1}")]
SnapshotIoctlError(String, sys_util::Error),
}
/// Options taken from the command line affecting hibernate.
#[derive(Default)]
pub struct HibernateOptions {
pub dry_run: bool,
pub unencrypted: bool,
pub test_keys: bool,
}
/// Options taken from the command line affecting resume.
#[derive(Default)]
pub struct ResumeOptions {
pub dry_run: bool,
pub unencrypted: bool,
pub test_keys: bool,
pub no_preloader: bool,
}
/// Get the page size on this system.
pub fn get_page_size() -> usize {
// Safe because sysconf() returns a long and has no other side effects.
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
}
/// Get the amount of free memory (in pages) on this system.
pub fn get_available_pages() -> usize {
// Safe because sysconf() returns a long and has no other side effects.
unsafe { libc::sysconf(libc::_SC_AVPHYS_PAGES) as usize }
}
/// Get the total amount of memory (in pages) on this system.
pub fn get_total_memory_pages() -> usize {
// Safe because sysconf() returns a long and has no other side effects.
let pagecount = unsafe { libc::sysconf(libc::_SC_PHYS_PAGES) as usize };
if pagecount == 0 {
warn!(
"Failed to get total memory (got {}). Assuming 4GB.",
pagecount
);
// Just return 4GB worth of pages if the result is unknown, the minimum
// we're ever going to see on a hibernating system.
let pages_per_mb = 1024 * 1024 / get_page_size();
let pages_per_gb = pages_per_mb * 1024;
return pages_per_gb * 4;
}
pagecount
}
/// Return the underlying partition device the hibernate files reside on.
/// Note: this still needs to return the real partition, even if stateful
/// is mounted on a dm-snapshot. Otherwise, resume activities won't work
/// across the transition.
pub fn path_to_stateful_part() -> Result<String> {
let rootdev = path_to_stateful_block()?;
Ok(format!("{}p1", rootdev))
}
/// Determine the path to the block device containing the stateful partition.
/// Farm this out to rootdev to keep the magic in one place.
pub fn path_to_stateful_block() -> Result<String> {
let output = Command::new("/usr/bin/rootdev")
.arg("-d")
.output()
.context("Cannot get rootdev")?;
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
pub struct LockedProcessMemory {}
impl Drop for LockedProcessMemory {
fn drop(&mut self) {
unlock_process_memory();
}
}
/// Lock all present and future memory belonging to this process, preventing it
/// from being paged out. Returns a LockedProcessMemory token, which undoes the
/// operation when dropped.
pub fn lock_process_memory() -> Result<LockedProcessMemory> {
// This is safe because mlockall() does not modify memory, it only ensures
// it doesn't get swapped out, which maintains Rust's safety guarantees.
let rc = unsafe { libc::mlockall(libc::MCL_CURRENT | libc::MCL_FUTURE) };
if rc < 0 {
return Err(HibernateError::MlockallError(sys_util::Error::last()))
.context("Cannot lock process memory");
}
Ok(LockedProcessMemory {})
}
/// Unlock memory belonging to this process, allowing it to be paged out once
/// more.
fn unlock_process_memory() {
// This is safe because while munlockall() is a foreign function, it has
// no immediately observable side effects on program execution.
unsafe {
libc::munlockall();
};
}