From 770c5dbd03d38224e8b27465510b08dd4a23b064 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Tue, 7 May 2024 14:04:53 +0200 Subject: [PATCH] system-config-api: add functions to read/write time and timezone Signed-off-by: Dietmar Maurer --- proxmox-system-config-api/Cargo.toml | 3 +- proxmox-system-config-api/src/api_types.rs | 27 ++++++++++- proxmox-system-config-api/src/lib.rs | 6 ++- proxmox-system-config-api/src/time.rs | 55 ++++++++++++++++++++++ 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 proxmox-system-config-api/src/time.rs diff --git a/proxmox-system-config-api/Cargo.toml b/proxmox-system-config-api/Cargo.toml index 9ed8858c..37850699 100644 --- a/proxmox-system-config-api/Cargo.toml +++ b/proxmox-system-config-api/Cargo.toml @@ -19,9 +19,10 @@ serde_json = { workspace = true } proxmox-sys = { workspace = true, optional = true } proxmox-schema = { workspace = true, features = ["api-macro", "api-types"] } +proxmox-time = { workspace = true, optional = true } proxmox-product-config = { workspace = true, optional = true } [features] default = [ "proxmox-product-config/default" ] -impl = [ "dep:proxmox-sys", "proxmox-product-config/impl"] \ No newline at end of file +impl = [ "dep:proxmox-sys", "dep:proxmox-time", "proxmox-product-config/impl"] \ No newline at end of file diff --git a/proxmox-system-config-api/src/api_types.rs b/proxmox-system-config-api/src/api_types.rs index 75f2788a..6daf0ed9 100644 --- a/proxmox-system-config-api/src/api_types.rs +++ b/proxmox-system-config-api/src/api_types.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; use proxmox_schema::api; use proxmox_schema::api_types::IP_FORMAT; +use proxmox_schema::api_types::TIME_ZONE_SCHEMA; use proxmox_schema::Schema; use proxmox_schema::StringSchema; @@ -80,7 +81,6 @@ pub struct ResolvConfWithDigest { pub digest: ConfigDigest, } - #[api()] #[derive(Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] @@ -93,3 +93,28 @@ pub enum DeletableResolvConfProperty { /// Delete third nameserver entry Dns3, } + +#[api( + properties: { + timezone: { + schema: TIME_ZONE_SCHEMA, + }, + time: { + type: i64, + description: "Seconds since 1970-01-01 00:00:00 UTC.", + minimum: 1_297_163_644, + }, + localtime: { + type: i64, + description: "Seconds since 1970-01-01 00:00:00 UTC. (local time)", + minimum: 1_297_163_644, + }, + } +)] +#[derive(Serialize, Deserialize)] +/// Server time and timezone. +pub struct ServerTimeInfo { + pub timezone: String, + pub time: i64, + pub localtime: i64, +} diff --git a/proxmox-system-config-api/src/lib.rs b/proxmox-system-config-api/src/lib.rs index 649a54d4..1d0894ab 100644 --- a/proxmox-system-config-api/src/lib.rs +++ b/proxmox-system-config-api/src/lib.rs @@ -1,4 +1,5 @@ mod api_types; +pub use api_types::ServerTimeInfo; pub use api_types::{DeletableResolvConfProperty, ResolvConf, ResolvConfWithDigest}; pub use api_types::{ FIRST_DNS_SERVER_SCHEMA, SEARCH_DOMAIN_SCHEMA, SECOND_DNS_SERVER_SCHEMA, @@ -6,6 +7,7 @@ pub use api_types::{ }; #[cfg(feature = "impl")] -mod resolv_conf; +pub mod resolv_conf; + #[cfg(feature = "impl")] -pub use resolv_conf::*; +pub mod time; diff --git a/proxmox-system-config-api/src/time.rs b/proxmox-system-config-api/src/time.rs new file mode 100644 index 00000000..269c95ef --- /dev/null +++ b/proxmox-system-config-api/src/time.rs @@ -0,0 +1,55 @@ +use anyhow::{bail, format_err, Error}; + +use proxmox_product_config::replace_system_config; +use proxmox_sys::fs::file_read_firstline; + +use crate::api_types::ServerTimeInfo; + +pub fn read_etc_localtime() -> Result { + // use /etc/timezone + if let Ok(line) = file_read_firstline("/etc/timezone") { + return Ok(line.trim().to_owned()); + } + + // otherwise guess from the /etc/localtime symlink + let link = std::fs::read_link("/etc/localtime") + .map_err(|err| format_err!("failed to guess timezone - {}", err))?; + + let link = link.to_string_lossy(); + match link.rfind("/zoneinfo/") { + Some(pos) => Ok(link[(pos + 10)..].to_string()), + None => Ok(link.to_string()), + } +} + +pub fn set_timezone(timezone: String) -> Result<(), Error> { + let path = std::path::PathBuf::from(format!("/usr/share/zoneinfo/{}", timezone)); + + if !path.exists() { + bail!("No such timezone."); + } + + replace_system_config("/etc/timezone", timezone.as_bytes())?; + + let _ = std::fs::remove_file("/etc/localtime"); + + use std::os::unix::fs::symlink; + symlink(path, "/etc/localtime")?; + + Ok(()) +} + +/// Read server time and time zone settings. +pub fn get_server_time_info() -> Result { + let time = proxmox_time::epoch_i64(); + let tm = proxmox_time::localtime(time)?; + let offset = tm.tm_gmtoff; + + let localtime = time + offset; + + Ok(ServerTimeInfo { + timezone: read_etc_localtime()?, + time: time, + localtime: localtime, + }) +}