blob: 2dd45e5f08fb7d0ec48ee98f0314ac4ed71cf8c2 [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Coordinates suspend-to-disk activities
use getopts::{self, Options};
use hiberman::cookie::HibernateCookieValue;
use hiberman::metrics::{log_hibernate_failure, log_resume_failure};
use hiberman::{self, AbortResumeOptions, HibernateOptions, ResumeInitOptions, ResumeOptions};
use log::{error, warn};
fn print_usage(message: &str, error: bool) {
if error {
eprintln!("{}", message)
} else {
println!("{}", message);
}
}
fn init_logging() -> std::result::Result<(), ()> {
if let Err(e) = hiberman::hiberlog::init() {
eprintln!("failed to initialize hiberlog: {}", e);
return Err(());
}
Ok(())
}
fn cookie_usage(error: bool, options: &Options) {
let brief = r#"Usage: hiberman cookie <path> [options]
Get or set the hibernate cookie info. With no options, gets the
current status of the hibernate cookie. Returns 0 if the cookie
indicates a valid hibernate image, or 1 otherwise.
"#;
print_usage(&options.usage(brief), error);
}
fn hiberman_cookie(args: &mut std::env::Args) -> std::result::Result<(), ()> {
// Note: Don't fire up logging immediately in this command as it's called
// during very early init, before syslog is ready.
let mut opts = Options::new();
opts.optflag(
"c",
"clear",
"Clear the cookie to indicate no valid hibernate image",
);
opts.optflag("h", "help", "Print this help text");
opts.optflag(
"s",
"set",
"Set the cookie to indicate a valid hibernate image",
);
opts.optflag("v", "verbose", "Print more during the command");
opts.optopt("V",
"value",
"Set the cookie to a specific value (options are no_resume, resume_ready, in_progress, or aborting)",
"value");
let args: Vec<String> = args.collect();
let matches = match opts.parse(args) {
Ok(m) => m,
Err(e) => {
eprintln!("Failed to parse arguments: {}", e);
cookie_usage(true, &opts);
return Err(());
}
};
if matches.opt_present("h") {
cookie_usage(false, &opts);
return Ok(());
}
let clear_cookie = matches.opt_present("c");
let set_cookie = matches.opt_present("s");
let verbose = matches.opt_present("v");
let value = matches.opt_str("V");
let path = matches.free.get(0).cloned();
let verbosity = if matches.opt_present("v") { 9 } else { 1 };
stderrlog::new()
.module(module_path!())
.verbosity(verbosity)
.init()
.unwrap();
if set_cookie || clear_cookie || value.is_some() {
let value = if let Some(value) = value {
if set_cookie || clear_cookie {
eprintln!("Cannot mix --set/--clear with --value");
return Err(());
}
match value.as_str() {
"no_resume" => HibernateCookieValue::NoResume,
"resume_ready" => HibernateCookieValue::ResumeReady,
"in_progress" => HibernateCookieValue::ResumeInProgress,
"aborting" => HibernateCookieValue::ResumeAborting,
_ => {
eprintln!("Invalid cookie value: {}", value);
cookie_usage(true, &opts);
return Err(());
}
}
} else if set_cookie {
if clear_cookie {
eprintln!("Cannot set both --set and --clear");
return Err(());
}
HibernateCookieValue::ResumeReady
} else {
HibernateCookieValue::NoResume
};
if let Err(e) = hiberman::cookie::set_hibernate_cookie(path.as_ref(), value) {
error!("Failed to write hibernate cookie: {}", e);
return Err(());
}
} else {
let value = match hiberman::cookie::get_hibernate_cookie(path.as_ref()) {
Ok(v) => v,
Err(e) => {
error!("Failed to get hibernate cookie: {}", e);
return Err(());
}
};
let is_ready = value == hiberman::cookie::HibernateCookieValue::ResumeReady;
let description = hiberman::cookie::cookie_description(value);
if verbose {
println!("Hibernate cookie is set to: {}", description);
}
if !is_ready {
return Err(());
}
}
Ok(())
}
fn cat_usage(error: bool, options: &Options) {
let brief = r#"Usage: hiberman cat [options] <file> [file...]
Print a disk file to stdout. Since disk files write to blocks
underneath the file system, they cannot be read reliably by normal
file system accesses.
"#;
print_usage(&options.usage(brief), error);
}
fn hiberman_cat(args: &mut std::env::Args) -> std::result::Result<(), ()> {
init_logging()?;
let mut opts = Options::new();
opts.optflag("l", "log", "Treat the file(s) as log files");
opts.optflag("h", "help", "Print this help text");
let args: Vec<String> = args.collect();
let matches = match opts.parse(args) {
Ok(m) => m,
Err(e) => {
error!("Failed to parse arguments: {}", e);
cat_usage(true, &opts);
return Err(());
}
};
if matches.opt_present("h") {
cat_usage(false, &opts);
return Ok(());
}
let mut result = Ok(());
let is_log = matches.opt_present("l");
for f in matches.free {
if let Err(e) = hiberman::cat::cat_disk_file(&f, is_log) {
error!("Failed to cat {}: {:?}", &f, e);
result = Err(())
}
}
result
}
fn hibernate_usage(error: bool, options: &Options) {
let brief = r#"Usage: hiberman hibernate [options]
Hibernate the system now.
"#;
print_usage(&options.usage(brief), error);
}
fn hiberman_hibernate(args: &mut std::env::Args) -> std::result::Result<(), ()> {
init_logging()?;
let mut opts = Options::new();
opts.optflag("h", "help", "Print this help text");
opts.optflag(
"k",
"no-kernel-encryption",
"Avoid using kernel based encryption for the hibernation image",
);
opts.optflag("n", "dry-run", "Create the hibernate image, but then exit rather than shutting down. This image should only be restored with --dry-run");
opts.optflag(
"p",
"platform-mode",
"Force enable the use of suspending to platform mode (S4)",
);
opts.optflag(
"u",
"unencrypted",
"Do not encrypt the hibernate image. Use only for test and debugging",
);
opts.optflag("t", "test-keys", "Use test keys for debugging");
let args: Vec<String> = args.collect();
let matches = match opts.parse(args) {
Ok(m) => m,
Err(e) => {
error!("Failed to parse arguments: {}", e);
hibernate_usage(true, &opts);
return Err(());
}
};
if matches.opt_present("h") {
hibernate_usage(false, &opts);
return Ok(());
}
let options = HibernateOptions {
dry_run: matches.opt_present("n"),
force_platform_mode: matches.opt_present("p"),
test_keys: matches.opt_present("t"),
unencrypted: matches.opt_present("u"),
no_kernel_encryption: matches.opt_present("k"),
};
if let Err(e) = hiberman::hibernate(options) {
if let Err(e) = log_hibernate_failure() {
warn!("Failed to log hibernate failure: {}", e);
}
error!("Failed to hibernate: {:?}", e);
return Err(());
}
Ok(())
}
fn resume_init_usage(error: bool, options: &Options) {
let brief = r#"Usage: hiberman resume-init [options]
Perform early init preparations, if required, to make resume from
hibernation possible this boot.
"#;
print_usage(&options.usage(brief), error);
}
fn hiberman_resume_init(args: &mut std::env::Args) -> std::result::Result<(), ()> {
let mut opts = Options::new();
opts.optflag(
"f",
"force",
"Set up a resume world even if the resume cookie is not set",
);
opts.optflag("h", "help", "Print this help text");
opts.optflag("v", "verbose", "Print more logs");
let args: Vec<String> = args.collect();
let matches = match opts.parse(args) {
Ok(m) => m,
Err(e) => {
error!("Failed to parse arguments: {}", e);
resume_init_usage(true, &opts);
return Err(());
}
};
if matches.opt_present("h") {
resume_init_usage(false, &opts);
return Ok(());
}
let verbosity = if matches.opt_present("v") { 9 } else { 1 };
// Syslog is not yet available, so just log to stderr.
stderrlog::new()
.module(module_path!())
.verbosity(verbosity)
.init()
.unwrap();
let options = ResumeInitOptions {
force: matches.opt_present("f"),
};
if let Err(e) = hiberman::resume_init(options) {
error!("Failed to initialize resume: {:#?}", e);
return Err(());
}
Ok(())
}
fn abort_resume_usage(error: bool, options: &Options) {
let brief = r#"Usage: hiberman abort-resume [options]
Send an abort request over dbus to another hiberman process currently executing a resume.
"#;
print_usage(&options.usage(brief), error);
}
fn hiberman_abort_resume(args: &mut std::env::Args) -> std::result::Result<(), ()> {
let mut opts = Options::new();
opts.optopt("m", "message", "Supply the reason for the abort", "reason");
opts.optflag("h", "help", "Print this help text");
opts.optflag("v", "verbose", "Print more logs");
let args: Vec<String> = args.collect();
let matches = match opts.parse(args) {
Ok(m) => m,
Err(e) => {
error!("Failed to parse arguments: {}", e);
abort_resume_usage(true, &opts);
return Err(());
}
};
if matches.opt_present("h") {
abort_resume_usage(false, &opts);
return Ok(());
}
let verbosity = if matches.opt_present("v") { 9 } else { 1 };
// Syslog is not yet available, so just log to stderr.
stderrlog::new()
.module(module_path!())
.verbosity(verbosity)
.init()
.unwrap();
let mut options = AbortResumeOptions::default();
if let Some(reason) = matches.opt_str("m") {
options.reason = reason;
}
if let Err(e) = hiberman::abort_resume(options) {
error!("Failed to abort resume: {:#?}", e);
return Err(());
}
Ok(())
}
fn resume_usage(error: bool, options: &Options) {
let brief = r#"Usage: hiberman resume [options]
Resume the system now. On success, does not return, but jumps back into the
resumed image.
"#;
print_usage(&options.usage(brief), error);
}
fn hiberman_resume(args: &mut std::env::Args) -> std::result::Result<(), ()> {
init_logging()?;
let mut opts = Options::new();
opts.optflag("h", "help", "Print this help text");
opts.optflag("n", "dry-run", "Create the hibernate image, but then exit rather than shutting down. This image should only be restored with --dry-run");
opts.optflag("p", "no-preloader", "Do not use the ImagePreloader");
opts.optflag(
"u",
"unencrypted",
"Do not encrypt the hibernate image. Use only for test and debugging",
);
opts.optflag("t", "test-keys", "Use test keys for debugging");
let args: Vec<String> = args.collect();
let matches = match opts.parse(args) {
Ok(m) => m,
Err(e) => {
error!("Failed to parse arguments: {}", e);
resume_usage(true, &opts);
return Err(());
}
};
if matches.opt_present("h") {
resume_usage(false, &opts);
return Ok(());
}
let options = ResumeOptions {
dry_run: matches.opt_present("n"),
no_preloader: matches.opt_present("p"),
test_keys: matches.opt_present("t"),
unencrypted: matches.opt_present("u"),
};
if let Err(e) = hiberman::resume(options) {
if let Err(e) = log_resume_failure() {
warn!("Failed to log resume: {}", e);
}
error!("Failed to resume: {:#?}", e);
return Err(());
}
Ok(())
}
fn app_usage(error: bool) {
let usage_msg = r#"Usage: hiberman subcommand [options]
This application coordinates suspend-to-disk activities. Try
hiberman <subcommand> --help for details on specific subcommands.
Valid subcommands are:
help -- Print this help text.
hibernate -- Suspend the machine to disk now.
resume-init -- Perform early initialization for resume.
resume -- Resume the system now.
abort-resume -- Send an abort request to an in-progress resume.
cat -- Write a disk file contents to stdout.
cookie -- Read or write the hibernate cookie.
"#;
print_usage(usage_msg, error);
}
fn hiberman_main() -> std::result::Result<(), ()> {
let mut args = std::env::args();
if args.next().is_none() {
eprintln!("expected executable name.");
return Err(());
}
let subcommand = match args.next() {
Some(subcommand) => subcommand,
None => {
eprintln!("expected a subcommand");
return Err(());
}
};
match subcommand.as_ref() {
"--help" | "-h" | "help" => {
app_usage(false);
Ok(())
}
"abort-resume" => hiberman_abort_resume(&mut args),
"cat" => hiberman_cat(&mut args),
"cookie" => hiberman_cookie(&mut args),
"hibernate" => hiberman_hibernate(&mut args),
"resume-init" => hiberman_resume_init(&mut args),
"resume" => hiberman_resume(&mut args),
_ => {
eprintln!("unknown subcommand: {}", subcommand);
Err(())
}
}
}
fn main() {
std::process::exit(if hiberman_main().is_ok() { 0 } else { 1 });
}
#[cfg(test)]
mod tests {
//use super::*;
}