use std::{
fmt,
io::Write,
os::fd::{BorrowedFd, RawFd},
sync::OnceLock,
thread::ThreadId,
time::{SystemTime, UNIX_EPOCH},
};
use btoi::btoi;
use data_encoding::HEXLOWER;
use nix::{
errno::Errno,
unistd::{write, Pid, Uid},
};
use serde_json::{Map, Value};
use crate::{
config::*,
err::SydResult,
fs::is_active_fd,
ofd::{lock_fd, unlock_fd},
proc::{proc_cmdline, proc_comm, proc_cwd, proc_tty},
retry::retry_on_eintr,
syslog::LogLevel,
};
#[macro_export]
macro_rules! printf {
() => {{}};
($($arg:tt)*) => {{
use ::std::io::Write as _;
let __s = format!($($arg)*);
::std::io::stdout().write_all(__s.as_bytes())
.map_err(|error| $crate::err::err2no(&error))
}};
}
#[macro_export]
macro_rules! printfln {
() => {{
use ::std::io::Write as _;
::std::io::stdout().write_all(b"\n")
.map_err(|error| $crate::err::err2no(&error))
}};
($($arg:tt)*) => {{
use ::std::io::Write as _;
let mut __s = format!($($arg)*);
__s.push('\n');
::std::io::stdout().write_all(__s.as_bytes())
.map_err(|error| $crate::err::err2no(&error))
}};
}
pub(crate) static LOG_TTY: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub(crate) static LOG_FD: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(-42);
pub(crate) static LOG_MAIN_TID: OnceLock<ThreadId> = OnceLock::new();
pub(crate) fn log_init_main() -> SydResult<()> {
LOG_MAIN_TID
.set(std::thread::current().id())
.map_err(|_| Errno::EBUSY.into())
}
pub(crate) fn log_set_panic_hook() {
#[expect(clippy::cognitive_complexity)]
std::panic::set_hook(Box::new(|info| {
let this = std::thread::current();
let name = this.name().unwrap_or("?");
let err = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => &**s,
None => "?",
},
};
let file = info.location().map(|l| l.file());
let line = info.location().map(|l| l.line());
if log_is_main() {
crate::alert!("ctx": "panic",
"op": "panic_syd_main_thread",
"msg": err, "file": file, "line": line);
std::process::exit(101);
} else {
crate::crit!("ctx": "panic",
"op": format!("panic_{name}_thread"),
"msg": err, "file": file, "line": line);
}
}));
}
#[inline]
fn log_is_main() -> bool {
LOG_MAIN_TID
.get()
.map(|&tid| tid == std::thread::current().id())
.unwrap_or(false)
}
#[macro_export]
macro_rules! emerg {
($($key:literal : $value:expr),+) => {
if $crate::log_enabled!($crate::syslog::LogLevel::Emergent) {
let timestamp = $crate::log::now();
let mut map = serde_json::Map::new();
$(
if let Ok(value) = serde_json::to_value($value) {
map.insert($key.to_string(), value);
} else {
map.insert($key.to_string(), serde_json::Value::Null);
}
)+
if !map.is_empty() {
$crate::log::log($crate::syslog::LogLevel::Emergent, timestamp, map);
}
}
}
}
#[macro_export]
macro_rules! alert {
($($key:literal : $value:expr),+) => {
if $crate::log_enabled!($crate::syslog::LogLevel::Alert) {
let timestamp = $crate::log::now();
let mut map = serde_json::Map::new();
$(
if let Ok(value) = serde_json::to_value($value) {
map.insert($key.to_string(), value);
} else {
map.insert($key.to_string(), serde_json::Value::Null);
}
)+
if !map.is_empty() {
$crate::log::log($crate::syslog::LogLevel::Alert, timestamp, map);
}
}
}
}
#[macro_export]
macro_rules! crit {
($($key:literal : $value:expr),+) => {
if $crate::log_enabled!($crate::syslog::LogLevel::Crit) {
let timestamp = $crate::log::now();
let mut map = serde_json::Map::new();
$(
if let Ok(value) = serde_json::to_value($value) {
map.insert($key.to_string(), value);
} else {
map.insert($key.to_string(), serde_json::Value::Null);
}
)+
if !map.is_empty() {
$crate::log::log($crate::syslog::LogLevel::Crit, timestamp, map);
}
}
}
}
#[macro_export]
macro_rules! error {
($($key:literal : $value:expr),+) => {
if $crate::log_enabled!($crate::syslog::LogLevel::Err) {
let timestamp = $crate::log::now();
let mut map = serde_json::Map::new();
$(
if let Ok(value) = serde_json::to_value($value) {
map.insert($key.to_string(), value);
} else {
map.insert($key.to_string(), serde_json::Value::Null);
}
)+
if !map.is_empty() {
$crate::log::log($crate::syslog::LogLevel::Err, timestamp, map);
}
}
}
}
#[macro_export]
macro_rules! warn {
($($key:literal : $value:expr),+) => {
if $crate::log_enabled!($crate::syslog::LogLevel::Warn) {
let timestamp = $crate::log::now();
let mut map = serde_json::Map::new();
$(
if let Ok(value) = serde_json::to_value($value) {
map.insert($key.to_string(), value);
} else {
map.insert($key.to_string(), serde_json::Value::Null);
}
)+
if !map.is_empty() {
$crate::log::log($crate::syslog::LogLevel::Warn, timestamp, map);
}
}
}
}
#[macro_export]
macro_rules! notice {
($($key:literal : $value:expr),+) => {
if $crate::log_enabled!($crate::syslog::LogLevel::Notice) {
let timestamp = $crate::log::now();
let mut map = serde_json::Map::new();
$(
if let Ok(value) = serde_json::to_value($value) {
map.insert($key.to_string(), value);
} else {
map.insert($key.to_string(), serde_json::Value::Null);
}
)+
if !map.is_empty() {
$crate::log::log($crate::syslog::LogLevel::Notice, timestamp, map);
}
}
}
}
#[macro_export]
macro_rules! info {
($($key:literal : $value:expr),+) => {
if $crate::log_enabled!($crate::syslog::LogLevel::Info) {
let timestamp = $crate::log::now();
let mut map = serde_json::Map::new();
$(
if let Ok(value) = serde_json::to_value($value) {
map.insert($key.to_string(), value);
} else {
map.insert($key.to_string(), serde_json::Value::Null);
}
)+
if !map.is_empty() {
$crate::log::log($crate::syslog::LogLevel::Info, timestamp, map);
}
}
}
}
#[macro_export]
macro_rules! debug {
($($key:literal : $value:expr),+) => {
if $crate::log_enabled!($crate::syslog::LogLevel::Debug) {
let timestamp = $crate::log::now();
let mut map = serde_json::Map::new();
$(
if let Ok(value) = serde_json::to_value($value) {
map.insert($key.to_string(), value);
} else {
map.insert($key.to_string(), serde_json::Value::Null);
}
)+
if !map.is_empty() {
$crate::log::log($crate::syslog::LogLevel::Debug, timestamp, map);
}
}
}
}
pub(crate) struct LockedWriter<'a> {
fd: BorrowedFd<'a>,
}
impl<'a> LockedWriter<'a> {
pub(crate) fn new(fd: BorrowedFd<'a>) -> Result<Self, Errno> {
retry_on_eintr(|| lock_fd(fd, true, true))?;
Ok(Self { fd })
}
}
impl Drop for LockedWriter<'_> {
fn drop(&mut self) {
let _ = self.flush();
let _ = unlock_fd(self.fd);
}
}
impl Write for LockedWriter<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
write(self.fd, buf).map_err(|e| std::io::Error::from_raw_os_error(e as i32))
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[cfg(feature = "log")]
#[expect(clippy::cognitive_complexity)]
pub fn log_init(default_level: LogLevel, default_log_fd: Option<RawFd>) -> Result<(), Errno> {
use std::os::unix::ffi::OsStrExt;
use crate::syslog::{init_global_syslog, parse_loglevel};
let level = if let Some(val) = std::env::var_os(ENV_LOG) {
parse_loglevel(val.as_os_str().as_bytes(), default_level)
} else {
default_level
};
let fd = match std::env::var_os(ENV_LOG_FD) {
None => default_log_fd,
Some(val) => {
let fd = btoi::<RawFd>(val.as_os_str().as_bytes()).map_err(|_| Errno::EBADF)?;
if fd >= 0 {
let fd = unsafe { BorrowedFd::borrow_raw(fd) };
if !is_active_fd(fd) {
return Err(Errno::EBADF);
}
}
Some(fd)
}
};
if let Some(fd) = fd {
LOG_FD.store(fd, std::sync::atomic::Ordering::Relaxed);
}
let mut tty = std::env::var_os(ENV_FORCE_TTY).is_some();
if !tty {
if std::env::var_os(ENV_QUIET_TTY).is_none() {
let fd = fd.unwrap_or(libc::STDERR_FILENO);
tty = unsafe { libc::isatty(fd) } == 1;
} else {
tty = false;
}
}
LOG_TTY.store(tty, std::sync::atomic::Ordering::Relaxed);
let mut logbuflen = 0usize;
let mut use_stack = true;
if let Some(var) = std::env::var_os(ENV_LOG_BUF_LEN) {
logbuflen = parse_size::Config::new()
.with_binary()
.parse_size(var.as_bytes())
.or(Err(Errno::EINVAL))?
.try_into()
.or(Err(Errno::EINVAL))?;
use_stack = false;
}
init_global_syslog(logbuflen, fd, level, use_stack)?;
if std::env::var_os(ENV_SING).is_none() {
info!("ctx": "init", "op": "sing", "chapter": 24,
"msg": "Change return success. Going and coming without error. Action brings good fortune.");
std::env::set_var(ENV_SING, "");
}
Ok(())
}
#[cfg(feature = "log")]
pub fn log_init_simple(default_level: LogLevel) -> Result<(), Errno> {
use std::os::unix::ffi::OsStrExt;
use crate::syslog::{global_syslog, init_global_syslog, parse_loglevel};
let level = if let Some(val) = std::env::var_os(ENV_LOG) {
parse_loglevel(val.as_os_str().as_bytes(), default_level)
} else {
default_level
};
let fd = match std::env::var_os(ENV_LOG_FD) {
None => libc::STDERR_FILENO,
Some(val) => {
let fd = btoi::<RawFd>(val.as_os_str().as_bytes()).map_err(|_| Errno::EBADF)?;
if fd >= 0 {
let fd = unsafe { BorrowedFd::borrow_raw(fd) };
if !is_active_fd(fd) {
return Err(Errno::EBADF);
}
}
fd
}
};
LOG_FD.store(fd, std::sync::atomic::Ordering::Relaxed);
let mut tty = std::env::var_os(ENV_FORCE_TTY).is_some();
if !tty {
if std::env::var_os(ENV_QUIET_TTY).is_none() {
tty = unsafe { libc::isatty(fd) } == 1;
} else {
tty = false;
}
}
LOG_TTY.store(tty, std::sync::atomic::Ordering::Relaxed);
init_global_syslog(0, Some(fd), level, true)?;
if let Some(sys) = global_syslog() {
sys.lock();
}
Ok(())
}
#[cfg(feature = "log")]
#[expect(clippy::cognitive_complexity)]
pub fn log(level: crate::syslog::LogLevel, timestamp: u64, mut msg: Map<String, Value>) {
let sys = if let Some(sys) = crate::syslog::global_syslog() {
sys
} else {
return; };
let add_context = level.as_u8() <= crate::syslog::LogLevel::Notice.as_u8();
let tty = LOG_TTY.load(std::sync::atomic::Ordering::Relaxed);
if let Some(pid_v) = msg.remove("pid").and_then(|v| v.as_i64()) {
#[expect(clippy::cast_possible_truncation)]
let pid = Pid::from_raw(pid_v as libc::pid_t);
if pid.as_raw() != 0 {
if add_context {
if let Ok(cmd) = proc_cmdline(pid) {
msg.insert("cmd".to_string(), cmd.to_string().into());
}
} else if let Ok(cmd) = proc_comm(pid) {
msg.insert("cmd".to_string(), cmd.to_string().into());
}
if let Ok(dir) = proc_cwd(pid) {
msg.insert("cwd".to_string(), dir.to_string().into());
}
if add_context {
if let Ok(tty) = proc_tty(pid) {
msg.insert("tty".to_string(), tty.to_string().into());
}
}
}
msg.insert("pid".to_string(), pid.as_raw().into());
}
if add_context {
msg.insert("uid".to_string(), Uid::current().as_raw().into());
}
let syd = nix::unistd::gettid().as_raw().into();
msg.insert("syd".to_string(), Value::Number(syd));
if let Ok(date) = format_iso8601(timestamp) {
msg.insert("time".to_string(), date.into());
} else {
msg.insert("time".to_string(), timestamp.into());
}
if let Some(src) = msg.remove("req") {
msg.insert("req".to_string(), src);
}
if let Some(m) = msg.remove("msg") {
msg.insert("msg".to_string(), m);
}
if let Some(tip) = msg.remove("tip") {
msg.insert("tip".to_string(), tip);
}
msg.retain(|_, value| !value.is_null());
let msg_data = serde_json::to_string(&msg).unwrap_or_else(|e| {
let e = serde_json::to_string(&format!("{e:?}")).unwrap_or("?".to_string());
format!("{{\"ctx\":\"log\",\"op\":\"serialize\",\"error\": \"{e}\"}}")
});
let msg_pretty = if tty {
Some(serde_json::to_string_pretty(&msg).unwrap_or_else(|e| {
let e = serde_json::to_string(&format!("{e:?}")).unwrap_or("?".to_string());
format!("{{\"ctx\":\"log\",\"op\":\"serialize\",\"error\": \"{e}\"}}")
}))
} else {
None
};
sys.write_log(level, &msg_data, msg_pretty.as_deref());
}
#[cfg(not(feature = "log"))]
#[inline(always)]
#[expect(clippy::cognitive_complexity)]
pub fn log_init(_default_level: LogLevel, default_log_fd: Option<RawFd>) -> Result<(), Errno> {
use std::os::unix::ffi::OsStrExt;
let fd = match std::env::var_os(ENV_LOG_FD) {
None => default_log_fd,
Some(val) => {
let fd = btoi::<RawFd>(val.as_os_str().as_bytes()).map_err(|_| Errno::EBADF)?;
if fd >= 0 {
let fd = unsafe { BorrowedFd::borrow_raw(fd) };
if !is_active_fd(fd) {
return Err(Errno::EBADF);
}
}
Some(fd)
}
};
if let Some(fd) = fd {
LOG_FD.store(fd, std::sync::atomic::Ordering::Relaxed);
}
let mut tty = std::env::var_os(ENV_FORCE_TTY).is_some();
if !tty {
if std::env::var_os(ENV_QUIET_TTY).is_none() {
let fd = fd.unwrap_or(libc::STDERR_FILENO);
tty = unsafe { libc::isatty(fd) } == 1;
} else {
tty = false;
}
}
LOG_TTY.store(tty, std::sync::atomic::Ordering::Relaxed);
let fd = match std::env::var_os(ENV_LOG_FD) {
None => default_log_fd,
Some(val) => {
let fd = btoi::<RawFd>(val.as_os_str().as_bytes()).map_err(|_| Errno::EBADF)?;
if fd >= 0 {
let fd = unsafe { BorrowedFd::borrow_raw(fd) };
if !is_active_fd(fd) {
return Err(Errno::EBADF);
}
}
Some(fd)
}
};
if let Some(fd) = fd {
LOG_FD.store(fd, std::sync::atomic::Ordering::Relaxed);
}
if std::env::var_os(ENV_SING).is_none() {
info!("ctx": "init", "op": "sing", "chapter": 24,
"msg": "Change return success. Going and coming without error. Action brings good fortune.");
std::env::set_var(ENV_SING, "");
}
Ok(())
}
#[cfg(not(feature = "log"))]
#[inline(always)]
pub fn log_init_simple(default_level: LogLevel) -> Result<(), Errno> {
log_init(default_level, Some(libc::STDERR_FILENO))
}
#[cfg(not(feature = "log"))]
#[expect(clippy::cognitive_complexity)]
pub fn log(level: crate::syslog::LogLevel, timestamp: u64, mut msg: Map<String, Value>) {
let fd = LOG_FD.load(std::sync::atomic::Ordering::Relaxed);
let fd = if fd < 0 {
return; } else {
unsafe { BorrowedFd::borrow_raw(fd) }
};
let add_context = level.as_u8() <= crate::syslog::LogLevel::Notice.as_u8();
let tty = LOG_TTY.load(std::sync::atomic::Ordering::Relaxed);
if let Some(pid_v) = msg.remove("pid").and_then(|v| v.as_i64()) {
#[expect(clippy::cast_possible_truncation)]
let pid = Pid::from_raw(pid_v as libc::pid_t);
if pid.as_raw() != 0 {
if add_context {
if let Ok(cmd) = proc_cmdline(pid) {
msg.insert("cmd".to_string(), cmd.to_string().into());
}
} else if let Ok(cmd) = proc_comm(pid) {
msg.insert("cmd".to_string(), cmd.to_string().into());
}
if let Ok(dir) = proc_cwd(pid) {
msg.insert("cwd".to_string(), dir.to_string().into());
}
if add_context {
if let Ok(tty) = proc_tty(pid) {
msg.insert("tty".to_string(), tty.to_string().into());
}
}
}
msg.insert("pid".to_string(), pid.as_raw().into());
}
if add_context {
msg.insert("uid".to_string(), Uid::current().as_raw().into());
}
let syd = nix::unistd::gettid().as_raw().into();
msg.insert("syd".to_string(), Value::Number(syd));
if let Ok(date) = format_iso8601(timestamp) {
msg.insert("time".to_string(), date.into());
} else {
msg.insert("time".to_string(), timestamp.into());
}
if let Some(src) = msg.remove("req") {
msg.insert("req".to_string(), src);
}
if let Some(m) = msg.remove("msg") {
msg.insert("msg".to_string(), m);
}
if let Some(tip) = msg.remove("tip") {
msg.insert("tip".to_string(), tip);
}
msg.retain(|_, value| !value.is_null());
let msg_data = serde_json::to_string(&msg).unwrap_or_else(|e| {
let e = serde_json::to_string(&format!("{e:?}")).unwrap_or("?".to_string());
format!("{{\"ctx\":\"log\",\"op\":\"serialize\",\"error\": \"{e}\"}}")
});
let msg_info = if tty {
Some(serde_json::to_string_pretty(&msg).unwrap_or_else(|e| {
let e = serde_json::to_string(&format!("{e:?}")).unwrap_or("?".to_string());
format!("{{\"ctx\":\"log\",\"op\":\"serialize\",\"error\": \"{e}\"}}")
}))
} else {
None
};
if let Ok(mut writer) = LockedWriter::new(fd).map(std::io::BufWriter::new) {
if let Some(ref msg) = msg_info {
let _ = writer.write_all(msg.as_bytes());
} else {
let _ = writer.write_all(msg_data.as_bytes());
}
let _ = writer.write_all(b"\n");
}
}
pub fn now() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
fn format_iso8601(timestamp: u64) -> Result<String, Errno> {
let t = i64::try_from(timestamp).or(Err(Errno::EINVAL))?;
let t: Tm = t.try_into()?;
Ok(t.to_string())
}
pub fn log_untrusted_buf(buf: &[u8]) -> (String, bool) {
if contains_ascii_unprintable(buf) {
(HEXLOWER.encode(buf), true)
} else if let Ok(s) = std::str::from_utf8(buf) {
(s.to_string(), false)
} else {
(HEXLOWER.encode(buf), true)
}
}
pub fn contains_ascii_unprintable(buf: &[u8]) -> bool {
buf.iter().any(|byte| !is_ascii_printable(*byte))
}
pub fn is_ascii_printable(byte: u8) -> bool {
(0x20..=0x7e).contains(&byte)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct Tm {
tm_year: i32, tm_mon: i32, tm_mday: i32, tm_wday: i32, tm_yday: i32, tm_hour: i32, tm_min: i32, tm_sec: i32, }
impl Tm {
pub fn year(&self) -> i32 {
self.tm_year.saturating_add(1900)
}
pub fn month(&self) -> i32 {
self.tm_mon.saturating_add(1)
}
pub fn day(&self) -> i32 {
self.tm_mday
}
pub fn weekday(&self) -> i32 {
self.tm_wday
}
pub fn hour(&self) -> i32 {
self.tm_hour
}
pub fn minute(&self) -> i32 {
self.tm_min
}
pub fn second(&self) -> i32 {
self.tm_sec
}
}
impl TryFrom<i64> for Tm {
type Error = Errno;
fn try_from(t: i64) -> Result<Self, Errno> {
const LEAPOCH: i64 = 951_868_800; const SECS_PER_DAY: i64 = 86_400;
const DAYS_PER_400Y: i64 = 146_097;
const DAYS_PER_100Y: i64 = 36_524;
const DAYS_PER_4Y: i64 = 1_461;
let limit_unit = 31_622_400i64; let low = i64::from(i32::MIN)
.checked_mul(limit_unit)
.ok_or(Errno::EOVERFLOW)?;
let high = i64::from(i32::MAX)
.checked_mul(limit_unit)
.ok_or(Errno::EOVERFLOW)?;
if t < low || t > high {
return Err(Errno::EOVERFLOW);
}
let secs = t.checked_sub(LEAPOCH).ok_or(Errno::EOVERFLOW)?;
let mut days = secs.checked_div(SECS_PER_DAY).ok_or(Errno::EOVERFLOW)?;
let mut remsecs = secs.checked_rem(SECS_PER_DAY).ok_or(Errno::EOVERFLOW)?;
if remsecs < 0 {
remsecs = remsecs.checked_add(SECS_PER_DAY).ok_or(Errno::EOVERFLOW)?;
days = days.checked_sub(1).ok_or(Errno::EOVERFLOW)?;
}
let mut wday = (3i64)
.checked_add(days)
.ok_or(Errno::EOVERFLOW)?
.checked_rem(7)
.ok_or(Errno::EOVERFLOW)?;
if wday < 0 {
wday = wday.checked_add(7).ok_or(Errno::EOVERFLOW)?;
}
let mut qc_cycles = days.checked_div(DAYS_PER_400Y).ok_or(Errno::EOVERFLOW)?;
let mut remdays = days.checked_rem(DAYS_PER_400Y).ok_or(Errno::EOVERFLOW)?;
if remdays < 0 {
remdays = remdays.checked_add(DAYS_PER_400Y).ok_or(Errno::EOVERFLOW)?;
qc_cycles = qc_cycles.checked_sub(1).ok_or(Errno::EOVERFLOW)?;
}
let mut c_cycles = remdays.checked_div(DAYS_PER_100Y).ok_or(Errno::EOVERFLOW)?;
if c_cycles == 4 {
c_cycles = c_cycles.checked_sub(1).ok_or(Errno::EOVERFLOW)?;
}
remdays = remdays
.checked_sub(
c_cycles
.checked_mul(DAYS_PER_100Y)
.ok_or(Errno::EOVERFLOW)?,
)
.ok_or(Errno::EOVERFLOW)?;
let mut q_cycles = remdays.checked_div(DAYS_PER_4Y).ok_or(Errno::EOVERFLOW)?;
if q_cycles == 25 {
q_cycles = q_cycles.checked_sub(1).ok_or(Errno::EOVERFLOW)?;
}
remdays = remdays
.checked_sub(q_cycles.checked_mul(DAYS_PER_4Y).ok_or(Errno::EOVERFLOW)?)
.ok_or(Errno::EOVERFLOW)?;
let mut remyears = remdays.checked_div(365).ok_or(Errno::EOVERFLOW)?;
if remyears == 4 {
remyears = remyears.checked_sub(1).ok_or(Errno::EOVERFLOW)?;
}
remdays = remdays
.checked_sub(remyears.checked_mul(365).ok_or(Errno::EOVERFLOW)?)
.ok_or(Errno::EOVERFLOW)?;
let leap = remyears == 0 && (q_cycles != 0 || c_cycles == 0);
let leap_i = if leap { 1i64 } else { 0i64 };
let mut yday = remdays
.checked_add(59)
.ok_or(Errno::EOVERFLOW)?
.checked_add(leap_i)
.ok_or(Errno::EOVERFLOW)?;
let yday_lim = 365i64.checked_add(leap_i).ok_or(Errno::EOVERFLOW)?;
if yday >= yday_lim {
yday = yday.checked_sub(yday_lim).ok_or(Errno::EOVERFLOW)?;
}
let years = remyears
.checked_add(4i64.checked_mul(q_cycles).ok_or(Errno::EOVERFLOW)?)
.ok_or(Errno::EOVERFLOW)?
.checked_add(100i64.checked_mul(c_cycles).ok_or(Errno::EOVERFLOW)?)
.ok_or(Errno::EOVERFLOW)?
.checked_add(400i64.checked_mul(qc_cycles).ok_or(Errno::EOVERFLOW)?)
.ok_or(Errno::EOVERFLOW)?;
let dim: [i64; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
let mut months = 0i64;
let mut rd = remdays;
while months < 12 {
let d = *dim
.get(usize::try_from(months).or(Err(Errno::EOVERFLOW))?)
.ok_or(Errno::EOVERFLOW)?;
if d > rd {
break;
}
rd = rd.checked_sub(d).ok_or(Errno::EOVERFLOW)?;
months = months.checked_add(1).ok_or(Errno::EOVERFLOW)?;
}
let mut years_adj = years;
let mut months_adj = months;
if months_adj >= 10 {
months_adj = months_adj.checked_sub(12).ok_or(Errno::EOVERFLOW)?;
years_adj = years_adj.checked_add(1).ok_or(Errno::EOVERFLOW)?;
}
let years_plus_100 = years_adj.checked_add(100).ok_or(Errno::EOVERFLOW)?;
if years_plus_100 > i64::from(i32::MAX) || years_plus_100 < i64::from(i32::MIN) {
return Err(Errno::EOVERFLOW);
}
let hour = remsecs.checked_div(3600).ok_or(Errno::EOVERFLOW)?;
let min = remsecs
.checked_div(60)
.ok_or(Errno::EOVERFLOW)?
.checked_rem(60)
.ok_or(Errno::EOVERFLOW)?;
let sec = remsecs.checked_rem(60).ok_or(Errno::EOVERFLOW)?;
Ok(Self {
tm_year: i32::try_from(years_plus_100).or(Err(Errno::EOVERFLOW))?,
tm_mon: i32::try_from(months_adj.checked_add(2).ok_or(Errno::EOVERFLOW)?)
.or(Err(Errno::EOVERFLOW))?,
tm_mday: i32::try_from(rd.checked_add(1).ok_or(Errno::EOVERFLOW)?)
.or(Err(Errno::EOVERFLOW))?,
tm_wday: i32::try_from(wday).or(Err(Errno::EOVERFLOW))?,
tm_yday: i32::try_from(yday).or(Err(Errno::EOVERFLOW))?,
tm_hour: i32::try_from(hour).or(Err(Errno::EOVERFLOW))?,
tm_min: i32::try_from(min).or(Err(Errno::EOVERFLOW))?,
tm_sec: i32::try_from(sec).or(Err(Errno::EOVERFLOW))?,
})
}
}
impl fmt::Display for Tm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let year = self.year();
let month = self.month();
let day = self.day();
let hour = self.hour();
let minute = self.minute();
let second = self.second();
write!(
f,
"{year:04}{month:02}{day:02}T{hour:02}{minute:02}{second:02}Z"
)
}
}