blob: daa0efadbdd661af7d48ba9f9a6f4fc5822fda24 [file] [log] [blame]
// Copyright 2019 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Provides the command "set_time" for crosh which provides a way to set the system time before the
// network time is available.
use std::error;
use std::fmt::{self, Display};
use std::io::Read;
use std::process::{self, Stdio};
use dbus::blocking::Connection;
use log::error;
use remain::sorted;
use tlsdate_dbus::client::OrgTorprojectTlsdate;
use crate::dispatcher::{self, Arguments, Command, Dispatcher};
use crate::util::DEFAULT_DBUS_TIMEOUT;
#[sorted]
enum Error {
Communication,
DBus,
InvalidTime,
NetworkTimeSet,
UnexpectedResponse(Option<u32>),
Wrapped(String),
}
impl Display for Error {
#[remain::check]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
#[sorted]
match self {
Communication => write!(f, "Time not set. There was a Communication error."),
DBus => write!(
f,
"Time not set. Unable to communicate with the time service."
),
InvalidTime => write!(f, "Requested time was invalid (too large or too small)"),
NetworkTimeSet => write!(f, "Time not set. Network time cannot be overridden."),
UnexpectedResponse(code) => {
if let Some(value) = code {
write!(f, "An unexpected response was received: {}", value)
} else {
write!(f, "An unexpected response was received.")
}
}
Wrapped(err) => write!(f, "{}", err),
}
}
}
impl<T: error::Error> From<T> for Error {
fn from(err: T) -> Self {
Error::Wrapped(format!("{:?}", err))
}
}
pub fn register(dispatcher: &mut Dispatcher) {
dispatcher.register_command(
Command::new(
"set_time",
"<time string>",
r#"Sets the system time if the the system has been unable to get it from the
network. The time may be initialized only once per-boot.
The <time string> uses the format of the GNU coreutils date command.
https://www.gnu.org/software/coreutils/manual/html_node/Date-input-formats.html
https://www.gnu.org/software/coreutils/faq/coreutils-faq.html#The-date-command-is-not-working-right_002e
Please see the timekeeping documentation for more information in general:
https://www.chromium.org/chromium-os/chromiumos-design-docs/timekeeping/"#,
)
.set_command_callback(Some(execute_set_time)),
);
}
fn execute_set_time(_cmd: &Command, args: &Arguments) -> Result<(), dispatcher::Error> {
if args.get_args().is_empty() {
eprintln!(
r#"A date/time specification is required.
E.g., set_time 10 February 2012 11:21am
(Remember to set your timezone in Settings first.)"#
);
return Err(dispatcher::Error::CommandReturnedError);
}
let timestamp = parse_date_string(&args.get_args().join(" ")).map_err(|err| {
eprintln!("Unable to understand the specified time: {}", err);
dispatcher::Error::CommandReturnedError
})?;
match set_time(timestamp) {
Ok(()) => {
println!("Time has been set.");
Ok(())
}
Err(err) => {
error!("{}", err);
Err(dispatcher::Error::CommandReturnedError)
}
}
}
fn set_time(timestamp: i64) -> Result<(), Error> {
let connection = Connection::new_system().or(Err(Error::DBus))?;
let conn_path = connection.with_proxy(
"org.torproject.tlsdate",
"/org/torproject/tlsdate",
DEFAULT_DBUS_TIMEOUT,
);
match conn_path.set_time(timestamp).or(Err(Error::DBus))? {
0 => Ok(()),
1 => Err(Error::InvalidTime),
2 => Err(Error::NetworkTimeSet),
3 => Err(Error::Communication),
code => Err(Error::UnexpectedResponse(Some(code))),
}
}
// Rather than reimplementing the human entered date parsing logic from coreutils, just use `date`
// for now.
fn parse_date_string(date: &str) -> Result<i64, Error> {
let mut child = process::Command::new("date")
.arg("+%s")
.arg("-d")
.arg(date)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.spawn()?;
let mut string_result = String::new();
child
.stdout
.take()
.unwrap()
.read_to_string(&mut string_result)?;
child.wait()?;
string_result
.trim()
.parse::<i64>()
.map_err(|err| err.into())
}