1use std::{
13 fmt,
14 io::Write,
15 os::fd::{BorrowedFd, RawFd},
16 sync::OnceLock,
17 thread::ThreadId,
18 time::{SystemTime, UNIX_EPOCH},
19};
20
21use btoi::btoi;
22use data_encoding::HEXLOWER;
23use nix::{
24 errno::Errno,
25 unistd::{write, Pid, Uid},
26};
27use serde_json::{Map, Value};
28
29use crate::{
30 config::*,
31 err::SydResult,
32 fs::{is_active_fd, lock_fd, retry_on_eintr, unlock_fd},
33 proc::{proc_cmdline, proc_comm, proc_cwd, proc_tty},
34 syslog::LogLevel,
35};
36
37#[macro_export]
39macro_rules! printf {
40 () => {{}};
41 ($($arg:tt)*) => {{
42 use ::std::io::Write as _;
43 let __s = format!($($arg)*);
44 ::std::io::stdout().write_all(__s.as_bytes())
45 .map_err(|error| $crate::err::err2no(&error))
46 }};
47}
48
49#[macro_export]
51macro_rules! printfln {
52 () => {{
53 use ::std::io::Write as _;
54 ::std::io::stdout().write_all(b"\n")
55 .map_err(|error| $crate::err::err2no(&error))
56 }};
57 ($($arg:tt)*) => {{
58 use ::std::io::Write as _;
59 let mut __s = format!($($arg)*);
60 __s.push('\n');
61 ::std::io::stdout().write_all(__s.as_bytes())
62 .map_err(|error| $crate::err::err2no(&error))
63 }};
64}
65
66pub(crate) static LOG_TTY: std::sync::atomic::AtomicBool =
68 std::sync::atomic::AtomicBool::new(false);
69pub(crate) static LOG_FD: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(-42);
71
72pub(crate) static LOG_MAIN_TID: OnceLock<ThreadId> = OnceLock::new();
74
75pub(crate) fn log_init_main() -> SydResult<()> {
77 LOG_MAIN_TID
78 .set(std::thread::current().id())
79 .map_err(|_| Errno::EBUSY.into())
80}
81
82pub(crate) fn log_set_panic_hook() {
84 #[expect(clippy::cognitive_complexity)]
88 std::panic::set_hook(Box::new(|info| {
89 let this = std::thread::current();
90 let name = this.name().unwrap_or("?");
91
92 let err = match info.payload().downcast_ref::<&'static str>() {
93 Some(s) => *s,
94 None => match info.payload().downcast_ref::<String>() {
95 Some(s) => &**s,
96 None => "?",
97 },
98 };
99
100 let file = info.location().map(|l| l.file());
101 let line = info.location().map(|l| l.line());
102
103 if log_is_main() {
104 crate::alert!("ctx": "panic",
107 "op": "panic_syd_main_thread",
108 "msg": err, "file": file, "line": line);
109 std::process::exit(101);
110 } else {
111 crate::crit!("ctx": "panic",
112 "op": format!("panic_{name}_thread"),
113 "msg": err, "file": file, "line": line);
114 }
115 }));
116}
117
118#[inline]
120fn log_is_main() -> bool {
121 LOG_MAIN_TID
122 .get()
123 .map(|&tid| tid == std::thread::current().id())
124 .unwrap_or(false)
125}
126
127#[macro_export]
129macro_rules! emerg {
130 ($($key:literal : $value:expr),+) => {
131 if $crate::log_enabled!($crate::syslog::LogLevel::Emergent) {
132 let timestamp = $crate::log::now();
133 let mut map = serde_json::Map::new();
134 $(
135 if let Ok(value) = serde_json::to_value($value) {
136 map.insert($key.to_string(), value);
137 } else {
138 map.insert($key.to_string(), serde_json::Value::Null);
139 }
140 )+
141 if !map.is_empty() {
142 $crate::log::log($crate::syslog::LogLevel::Emergent, timestamp, map);
143 }
144 }
145 }
146}
147
148#[macro_export]
150macro_rules! alert {
151 ($($key:literal : $value:expr),+) => {
152 if $crate::log_enabled!($crate::syslog::LogLevel::Alert) {
153 let timestamp = $crate::log::now();
154 let mut map = serde_json::Map::new();
155 $(
156 if let Ok(value) = serde_json::to_value($value) {
157 map.insert($key.to_string(), value);
158 } else {
159 map.insert($key.to_string(), serde_json::Value::Null);
160 }
161 )+
162 if !map.is_empty() {
163 $crate::log::log($crate::syslog::LogLevel::Alert, timestamp, map);
164 }
165 }
166 }
167}
168
169#[macro_export]
171macro_rules! crit {
172 ($($key:literal : $value:expr),+) => {
173 if $crate::log_enabled!($crate::syslog::LogLevel::Crit) {
174 let timestamp = $crate::log::now();
175 let mut map = serde_json::Map::new();
176 $(
177 if let Ok(value) = serde_json::to_value($value) {
178 map.insert($key.to_string(), value);
179 } else {
180 map.insert($key.to_string(), serde_json::Value::Null);
181 }
182 )+
183 if !map.is_empty() {
184 $crate::log::log($crate::syslog::LogLevel::Crit, timestamp, map);
185 }
186 }
187 }
188}
189
190#[macro_export]
192macro_rules! error {
193 ($($key:literal : $value:expr),+) => {
194 if $crate::log_enabled!($crate::syslog::LogLevel::Err) {
195 let timestamp = $crate::log::now();
196 let mut map = serde_json::Map::new();
197 $(
198 if let Ok(value) = serde_json::to_value($value) {
199 map.insert($key.to_string(), value);
200 } else {
201 map.insert($key.to_string(), serde_json::Value::Null);
202 }
203 )+
204 if !map.is_empty() {
205 $crate::log::log($crate::syslog::LogLevel::Err, timestamp, map);
206 }
207 }
208 }
209}
210
211#[macro_export]
213macro_rules! warn {
214 ($($key:literal : $value:expr),+) => {
215 if $crate::log_enabled!($crate::syslog::LogLevel::Warn) {
216 let timestamp = $crate::log::now();
217 let mut map = serde_json::Map::new();
218 $(
219 if let Ok(value) = serde_json::to_value($value) {
220 map.insert($key.to_string(), value);
221 } else {
222 map.insert($key.to_string(), serde_json::Value::Null);
223 }
224 )+
225 if !map.is_empty() {
226 $crate::log::log($crate::syslog::LogLevel::Warn, timestamp, map);
227 }
228 }
229 }
230}
231
232#[macro_export]
234macro_rules! notice {
235 ($($key:literal : $value:expr),+) => {
236 if $crate::log_enabled!($crate::syslog::LogLevel::Notice) {
237 let timestamp = $crate::log::now();
238 let mut map = serde_json::Map::new();
239 $(
240 if let Ok(value) = serde_json::to_value($value) {
241 map.insert($key.to_string(), value);
242 } else {
243 map.insert($key.to_string(), serde_json::Value::Null);
244 }
245 )+
246 if !map.is_empty() {
247 $crate::log::log($crate::syslog::LogLevel::Notice, timestamp, map);
248 }
249 }
250 }
251}
252
253#[macro_export]
255macro_rules! info {
256 ($($key:literal : $value:expr),+) => {
257 if $crate::log_enabled!($crate::syslog::LogLevel::Info) {
258 let timestamp = $crate::log::now();
259 let mut map = serde_json::Map::new();
260 $(
261 if let Ok(value) = serde_json::to_value($value) {
262 map.insert($key.to_string(), value);
263 } else {
264 map.insert($key.to_string(), serde_json::Value::Null);
265 }
266 )+
267 if !map.is_empty() {
268 $crate::log::log($crate::syslog::LogLevel::Info, timestamp, map);
269 }
270 }
271 }
272}
273
274#[macro_export]
276macro_rules! debug {
277 ($($key:literal : $value:expr),+) => {
278 if $crate::log_enabled!($crate::syslog::LogLevel::Debug) {
279 let timestamp = $crate::log::now();
280 let mut map = serde_json::Map::new();
281 $(
282 if let Ok(value) = serde_json::to_value($value) {
283 map.insert($key.to_string(), value);
284 } else {
285 map.insert($key.to_string(), serde_json::Value::Null);
286 }
287 )+
288 if !map.is_empty() {
289 $crate::log::log($crate::syslog::LogLevel::Debug, timestamp, map);
290 }
291 }
292 }
293}
294
295pub(crate) struct LockedWriter<'a> {
298 fd: BorrowedFd<'a>,
299}
300
301impl<'a> LockedWriter<'a> {
302 pub(crate) fn new(fd: BorrowedFd<'a>) -> Result<Self, Errno> {
303 retry_on_eintr(|| lock_fd(fd, true, true))?;
304 Ok(Self { fd })
305 }
306}
307
308impl Drop for LockedWriter<'_> {
309 fn drop(&mut self) {
310 let _ = self.flush();
311 let _ = unlock_fd(self.fd);
312 }
313}
314
315impl Write for LockedWriter<'_> {
316 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
317 write(self.fd, buf).map_err(|e| std::io::Error::from_raw_os_error(e as i32))
318 }
319
320 fn flush(&mut self) -> std::io::Result<()> {
321 Ok(())
322 }
323}
324
325#[cfg(feature = "log")]
327#[expect(clippy::cognitive_complexity)]
328pub fn log_init(default_level: LogLevel, default_log_fd: Option<RawFd>) -> Result<(), Errno> {
329 use std::os::unix::ffi::OsStrExt;
330
331 use crate::syslog::{init_global_syslog, parse_loglevel};
332
333 let level = if let Some(val) = std::env::var_os(ENV_LOG) {
336 parse_loglevel(val.as_os_str().as_bytes(), default_level)
337 } else {
338 default_level
339 };
340
341 let fd = match std::env::var_os(ENV_LOG_FD) {
347 None => default_log_fd,
348 Some(val) => {
349 let fd = btoi::<RawFd>(val.as_os_str().as_bytes()).map_err(|_| Errno::EBADF)?;
350 if fd >= 0 {
351 let fd = unsafe { BorrowedFd::borrow_raw(fd) };
353 if !is_active_fd(fd) {
354 return Err(Errno::EBADF);
355 }
356 }
357 Some(fd)
358 }
359 };
360 if let Some(fd) = fd {
361 LOG_FD.store(fd, std::sync::atomic::Ordering::Relaxed);
362 }
363
364 let mut tty = std::env::var_os(ENV_FORCE_TTY).is_some();
366 if !tty {
367 if std::env::var_os(ENV_QUIET_TTY).is_none() {
368 let fd = fd.unwrap_or(libc::STDERR_FILENO);
369 tty = unsafe { libc::isatty(fd) } == 1;
372 } else {
373 tty = false;
374 }
375 }
376
377 LOG_TTY.store(tty, std::sync::atomic::Ordering::Relaxed);
379
380 let mut logbuflen = 0usize;
386 let mut use_stack = true;
387 if let Some(var) = std::env::var_os(ENV_LOG_BUF_LEN) {
388 logbuflen = parse_size::Config::new()
389 .with_binary()
390 .parse_size(var.as_bytes())
391 .or(Err(Errno::EINVAL))?
392 .try_into()
393 .or(Err(Errno::EINVAL))?;
394 use_stack = false;
395 }
396 init_global_syslog(logbuflen, fd, level, use_stack)?;
397
398 if std::env::var_os(ENV_SING).is_none() {
402 info!("ctx": "init", "op": "sing", "chapter": 24,
403 "msg": "Change return success. Going and coming without error. Action brings good fortune.");
404 std::env::set_var(ENV_SING, "");
405 }
406
407 Ok(())
408}
409
410#[cfg(feature = "log")]
414pub fn log_init_simple(default_level: LogLevel) -> Result<(), Errno> {
415 use std::os::unix::ffi::OsStrExt;
416
417 use crate::syslog::{global_syslog, init_global_syslog, parse_loglevel};
418
419 let level = if let Some(val) = std::env::var_os(ENV_LOG) {
422 parse_loglevel(val.as_os_str().as_bytes(), default_level)
423 } else {
424 default_level
425 };
426
427 let fd = match std::env::var_os(ENV_LOG_FD) {
433 None => libc::STDERR_FILENO,
434 Some(val) => {
435 let fd = btoi::<RawFd>(val.as_os_str().as_bytes()).map_err(|_| Errno::EBADF)?;
436 if fd >= 0 {
437 let fd = unsafe { BorrowedFd::borrow_raw(fd) };
439 if !is_active_fd(fd) {
440 return Err(Errno::EBADF);
441 }
442 }
443 fd
444 }
445 };
446 LOG_FD.store(fd, std::sync::atomic::Ordering::Relaxed);
447
448 let mut tty = std::env::var_os(ENV_FORCE_TTY).is_some();
450 if !tty {
451 if std::env::var_os(ENV_QUIET_TTY).is_none() {
452 tty = unsafe { libc::isatty(fd) } == 1;
455 } else {
456 tty = false;
457 }
458 }
459
460 LOG_TTY.store(tty, std::sync::atomic::Ordering::Relaxed);
462
463 init_global_syslog(0, Some(fd), level, true)?;
465
466 if let Some(sys) = global_syslog() {
467 sys.lock();
470 }
471
472 Ok(())
473}
474
475#[cfg(feature = "log")]
477#[expect(clippy::cognitive_complexity)]
478pub fn log(level: crate::syslog::LogLevel, timestamp: u64, mut msg: Map<String, Value>) {
479 let sys = if let Some(sys) = crate::syslog::global_syslog() {
480 sys
481 } else {
482 return; };
484
485 let add_context = level.as_u8() <= crate::syslog::LogLevel::Notice.as_u8();
487 let tty = LOG_TTY.load(std::sync::atomic::Ordering::Relaxed);
488
489 if let Some(pid_v) = msg.remove("pid").and_then(|v| v.as_i64()) {
492 #[expect(clippy::cast_possible_truncation)]
493 let pid = Pid::from_raw(pid_v as libc::pid_t);
494 if pid.as_raw() != 0 {
495 if add_context {
496 if let Ok(cmd) = proc_cmdline(pid) {
497 msg.insert("cmd".to_string(), cmd.to_string().into());
498 }
499 } else if let Ok(cmd) = proc_comm(pid) {
500 msg.insert("cmd".to_string(), cmd.to_string().into());
501 }
502 if let Ok(dir) = proc_cwd(pid) {
503 msg.insert("cwd".to_string(), dir.to_string().into());
504 }
505 if add_context {
506 if let Ok(tty) = proc_tty(pid) {
507 msg.insert("tty".to_string(), tty.to_string().into());
508 }
509 }
510 }
511 msg.insert("pid".to_string(), pid.as_raw().into());
512 }
513
514 if add_context {
516 msg.insert("uid".to_string(), Uid::current().as_raw().into());
517 }
518
519 let syd = nix::unistd::gettid().as_raw().into();
521 msg.insert("syd".to_string(), Value::Number(syd));
522
523 if let Ok(date) = format_iso8601(timestamp) {
525 msg.insert("time".to_string(), date.into());
526 } else {
527 msg.insert("time".to_string(), timestamp.into());
528 }
529
530 if let Some(src) = msg.remove("req") {
532 msg.insert("req".to_string(), src);
533 }
534 if let Some(m) = msg.remove("msg") {
535 msg.insert("msg".to_string(), m);
536 }
537 if let Some(tip) = msg.remove("tip") {
538 msg.insert("tip".to_string(), tip);
539 }
540
541 let msg_data = serde_json::to_string(&msg).unwrap_or_else(|e| {
543 let e = serde_json::to_string(&format!("{e:?}")).unwrap_or("?".to_string());
544 format!("{{\"ctx\":\"log\",\"op\":\"serialize\",\"error\": \"{e}\"}}")
545 });
546 let msg_pretty = if tty {
547 Some(serde_json::to_string_pretty(&msg).unwrap_or_else(|e| {
549 let e = serde_json::to_string(&format!("{e:?}")).unwrap_or("?".to_string());
550 format!("{{\"ctx\":\"log\",\"op\":\"serialize\",\"error\": \"{e}\"}}")
551 }))
552 } else {
553 None
554 };
555
556 sys.write_log(level, &msg_data, msg_pretty.as_deref());
557}
558
559#[cfg(not(feature = "log"))]
561#[inline(always)]
562#[expect(clippy::cognitive_complexity)]
563pub fn log_init(_default_level: LogLevel, default_log_fd: Option<RawFd>) -> Result<(), Errno> {
564 use std::os::unix::ffi::OsStrExt;
565
566 let fd = match std::env::var_os(ENV_LOG_FD) {
572 None => default_log_fd,
573 Some(val) => {
574 let fd = btoi::<RawFd>(val.as_os_str().as_bytes()).map_err(|_| Errno::EBADF)?;
575 if fd >= 0 {
576 let fd = unsafe { BorrowedFd::borrow_raw(fd) };
578 if !is_active_fd(fd) {
579 return Err(Errno::EBADF);
580 }
581 }
582 Some(fd)
583 }
584 };
585 if let Some(fd) = fd {
586 LOG_FD.store(fd, std::sync::atomic::Ordering::Relaxed);
587 }
588
589 let mut tty = std::env::var_os(ENV_FORCE_TTY).is_some();
591 if !tty {
592 if std::env::var_os(ENV_QUIET_TTY).is_none() {
593 let fd = fd.unwrap_or(libc::STDERR_FILENO);
594 tty = unsafe { libc::isatty(fd) } == 1;
597 } else {
598 tty = false;
599 }
600 }
601
602 LOG_TTY.store(tty, std::sync::atomic::Ordering::Relaxed);
604
605 let fd = match std::env::var_os(ENV_LOG_FD) {
611 None => default_log_fd,
612 Some(val) => {
613 let fd = btoi::<RawFd>(val.as_os_str().as_bytes()).map_err(|_| Errno::EBADF)?;
614 if fd >= 0 {
615 let fd = unsafe { BorrowedFd::borrow_raw(fd) };
617 if !is_active_fd(fd) {
618 return Err(Errno::EBADF);
619 }
620 }
621 Some(fd)
622 }
623 };
624 if let Some(fd) = fd {
625 LOG_FD.store(fd, std::sync::atomic::Ordering::Relaxed);
626 }
627
628 if std::env::var_os(ENV_SING).is_none() {
632 info!("ctx": "init", "op": "sing", "chapter": 24,
633 "msg": "Change return success. Going and coming without error. Action brings good fortune.");
634 std::env::set_var(ENV_SING, "");
635 }
636
637 Ok(())
638}
639
640#[cfg(not(feature = "log"))]
644#[inline(always)]
645pub fn log_init_simple(default_level: LogLevel) -> Result<(), Errno> {
646 log_init(default_level, Some(libc::STDERR_FILENO))
647}
648
649#[cfg(not(feature = "log"))]
651#[expect(clippy::cognitive_complexity)]
652pub fn log(level: crate::syslog::LogLevel, timestamp: u64, mut msg: Map<String, Value>) {
653 let fd = LOG_FD.load(std::sync::atomic::Ordering::Relaxed);
655 let fd = if fd < 0 {
656 return; } else {
658 unsafe { BorrowedFd::borrow_raw(fd) }
660 };
661
662 let add_context = level.as_u8() <= crate::syslog::LogLevel::Notice.as_u8();
664 let tty = LOG_TTY.load(std::sync::atomic::Ordering::Relaxed);
665
666 if let Some(pid_v) = msg.remove("pid").and_then(|v| v.as_i64()) {
669 #[expect(clippy::cast_possible_truncation)]
670 let pid = Pid::from_raw(pid_v as libc::pid_t);
671 if pid.as_raw() != 0 {
672 if add_context {
673 if let Ok(cmd) = proc_cmdline(pid) {
674 msg.insert("cmd".to_string(), cmd.to_string().into());
675 }
676 } else if let Ok(cmd) = proc_comm(pid) {
677 msg.insert("cmd".to_string(), cmd.to_string().into());
678 }
679 if let Ok(dir) = proc_cwd(pid) {
680 msg.insert("cwd".to_string(), dir.to_string().into());
681 }
682 if add_context {
683 if let Ok(tty) = proc_tty(pid) {
684 msg.insert("tty".to_string(), tty.to_string().into());
685 }
686 }
687 }
688 msg.insert("pid".to_string(), pid.as_raw().into());
689 }
690
691 if add_context {
693 msg.insert("uid".to_string(), Uid::current().as_raw().into());
694 }
695
696 let syd = nix::unistd::gettid().as_raw().into();
698 msg.insert("syd".to_string(), Value::Number(syd));
699
700 if let Ok(date) = format_iso8601(timestamp) {
702 msg.insert("time".to_string(), date.into());
703 } else {
704 msg.insert("time".to_string(), timestamp.into());
705 }
706
707 if let Some(src) = msg.remove("req") {
709 msg.insert("req".to_string(), src);
710 }
711 if let Some(m) = msg.remove("msg") {
712 msg.insert("msg".to_string(), m);
713 }
714 if let Some(tip) = msg.remove("tip") {
715 msg.insert("tip".to_string(), tip);
716 }
717
718 let msg_data = serde_json::to_string(&msg).unwrap_or_else(|e| {
720 let e = serde_json::to_string(&format!("{e:?}")).unwrap_or("?".to_string());
721 format!("{{\"ctx\":\"log\",\"op\":\"serialize\",\"error\": \"{e}\"}}")
722 });
723
724 let msg_info = if tty {
725 Some(serde_json::to_string_pretty(&msg).unwrap_or_else(|e| {
727 let e = serde_json::to_string(&format!("{e:?}")).unwrap_or("?".to_string());
728 format!("{{\"ctx\":\"log\",\"op\":\"serialize\",\"error\": \"{e}\"}}")
729 }))
730 } else {
731 None
732 };
733
734 if let Ok(mut writer) = LockedWriter::new(fd).map(std::io::BufWriter::new) {
736 if let Some(ref msg) = msg_info {
737 let _ = writer.write_all(msg.as_bytes());
738 } else {
739 let _ = writer.write_all(msg_data.as_bytes());
740 }
741 let _ = writer.write_all(b"\n");
742 }
743}
744
745pub fn now() -> u64 {
747 SystemTime::now()
748 .duration_since(UNIX_EPOCH)
749 .unwrap_or_default()
750 .as_secs()
751}
752
753fn format_iso8601(timestamp: u64) -> Result<String, Errno> {
759 let t = i64::try_from(timestamp).or(Err(Errno::EINVAL))?;
760 let t: Tm = t.try_into()?;
761 Ok(t.to_string())
762}
763
764pub fn log_untrusted_buf(buf: &[u8]) -> (String, bool) {
767 if contains_ascii_unprintable(buf) {
768 (HEXLOWER.encode(buf), true)
769 } else if let Ok(s) = std::str::from_utf8(buf) {
770 (s.to_string(), false)
771 } else {
772 (HEXLOWER.encode(buf), true)
773 }
774}
775
776pub fn contains_ascii_unprintable(buf: &[u8]) -> bool {
778 buf.iter().any(|byte| !is_ascii_printable(*byte))
779}
780
781pub fn is_ascii_printable(byte: u8) -> bool {
783 (0x20..=0x7e).contains(&byte)
784}
785
786#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
788pub struct Tm {
789 tm_year: i32, tm_mon: i32, tm_mday: i32, tm_wday: i32, tm_yday: i32, tm_hour: i32, tm_min: i32, tm_sec: i32, }
798
799impl Tm {
800 pub fn year(&self) -> i32 {
802 self.tm_year.saturating_add(1900)
803 }
804
805 pub fn month(&self) -> i32 {
807 self.tm_mon.saturating_add(1)
808 }
809
810 pub fn day(&self) -> i32 {
812 self.tm_mday
813 }
814
815 pub fn hour(&self) -> i32 {
817 self.tm_hour
818 }
819
820 pub fn minute(&self) -> i32 {
822 self.tm_min
823 }
824
825 pub fn second(&self) -> i32 {
827 self.tm_sec
828 }
829}
830
831impl TryFrom<i64> for Tm {
832 type Error = Errno;
833
834 fn try_from(t: i64) -> Result<Self, Errno> {
835 const LEAPOCH: i64 = 951_868_800; const SECS_PER_DAY: i64 = 86_400;
837 const DAYS_PER_400Y: i64 = 146_097;
838 const DAYS_PER_100Y: i64 = 36_524;
839 const DAYS_PER_4Y: i64 = 1_461;
840
841 let limit_unit = 31_622_400i64; let low = i64::from(i32::MIN)
844 .checked_mul(limit_unit)
845 .ok_or(Errno::EOVERFLOW)?;
846 let high = i64::from(i32::MAX)
847 .checked_mul(limit_unit)
848 .ok_or(Errno::EOVERFLOW)?;
849 if t < low || t > high {
850 return Err(Errno::EOVERFLOW);
851 }
852
853 let secs = t.checked_sub(LEAPOCH).ok_or(Errno::EOVERFLOW)?;
854 let mut days = secs.checked_div(SECS_PER_DAY).ok_or(Errno::EOVERFLOW)?;
855 let mut remsecs = secs.checked_rem(SECS_PER_DAY).ok_or(Errno::EOVERFLOW)?;
856 if remsecs < 0 {
857 remsecs = remsecs.checked_add(SECS_PER_DAY).ok_or(Errno::EOVERFLOW)?;
858 days = days.checked_sub(1).ok_or(Errno::EOVERFLOW)?;
859 }
860
861 let mut wday = (3i64)
863 .checked_add(days)
864 .ok_or(Errno::EOVERFLOW)?
865 .checked_rem(7)
866 .ok_or(Errno::EOVERFLOW)?;
867 if wday < 0 {
868 wday = wday.checked_add(7).ok_or(Errno::EOVERFLOW)?;
869 }
870
871 let mut qc_cycles = days.checked_div(DAYS_PER_400Y).ok_or(Errno::EOVERFLOW)?;
873 let mut remdays = days.checked_rem(DAYS_PER_400Y).ok_or(Errno::EOVERFLOW)?;
874 if remdays < 0 {
875 remdays = remdays.checked_add(DAYS_PER_400Y).ok_or(Errno::EOVERFLOW)?;
876 qc_cycles = qc_cycles.checked_sub(1).ok_or(Errno::EOVERFLOW)?;
877 }
878
879 let mut c_cycles = remdays.checked_div(DAYS_PER_100Y).ok_or(Errno::EOVERFLOW)?;
880 if c_cycles == 4 {
881 c_cycles = c_cycles.checked_sub(1).ok_or(Errno::EOVERFLOW)?;
882 }
883 remdays = remdays
884 .checked_sub(
885 c_cycles
886 .checked_mul(DAYS_PER_100Y)
887 .ok_or(Errno::EOVERFLOW)?,
888 )
889 .ok_or(Errno::EOVERFLOW)?;
890
891 let mut q_cycles = remdays.checked_div(DAYS_PER_4Y).ok_or(Errno::EOVERFLOW)?;
892 if q_cycles == 25 {
893 q_cycles = q_cycles.checked_sub(1).ok_or(Errno::EOVERFLOW)?;
894 }
895 remdays = remdays
896 .checked_sub(q_cycles.checked_mul(DAYS_PER_4Y).ok_or(Errno::EOVERFLOW)?)
897 .ok_or(Errno::EOVERFLOW)?;
898
899 let mut remyears = remdays.checked_div(365).ok_or(Errno::EOVERFLOW)?;
900 if remyears == 4 {
901 remyears = remyears.checked_sub(1).ok_or(Errno::EOVERFLOW)?;
902 }
903 remdays = remdays
904 .checked_sub(remyears.checked_mul(365).ok_or(Errno::EOVERFLOW)?)
905 .ok_or(Errno::EOVERFLOW)?;
906
907 let leap = remyears == 0 && (q_cycles != 0 || c_cycles == 0);
908 let leap_i = if leap { 1i64 } else { 0i64 };
909
910 let mut yday = remdays
911 .checked_add(59)
912 .ok_or(Errno::EOVERFLOW)?
913 .checked_add(leap_i)
914 .ok_or(Errno::EOVERFLOW)?;
915 let yday_lim = 365i64.checked_add(leap_i).ok_or(Errno::EOVERFLOW)?;
916 if yday >= yday_lim {
917 yday = yday.checked_sub(yday_lim).ok_or(Errno::EOVERFLOW)?;
918 }
919
920 let years = remyears
921 .checked_add(4i64.checked_mul(q_cycles).ok_or(Errno::EOVERFLOW)?)
922 .ok_or(Errno::EOVERFLOW)?
923 .checked_add(100i64.checked_mul(c_cycles).ok_or(Errno::EOVERFLOW)?)
924 .ok_or(Errno::EOVERFLOW)?
925 .checked_add(400i64.checked_mul(qc_cycles).ok_or(Errno::EOVERFLOW)?)
926 .ok_or(Errno::EOVERFLOW)?;
927
928 let dim: [i64; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
930 let mut months = 0i64;
931 let mut rd = remdays;
932 while months < 12 {
933 let d = *dim
934 .get(usize::try_from(months).or(Err(Errno::EOVERFLOW))?)
935 .ok_or(Errno::EOVERFLOW)?;
936 if d > rd {
937 break;
938 }
939 rd = rd.checked_sub(d).ok_or(Errno::EOVERFLOW)?;
940 months = months.checked_add(1).ok_or(Errno::EOVERFLOW)?;
941 }
942
943 let mut years_adj = years;
944 let mut months_adj = months;
945 if months_adj >= 10 {
946 months_adj = months_adj.checked_sub(12).ok_or(Errno::EOVERFLOW)?;
947 years_adj = years_adj.checked_add(1).ok_or(Errno::EOVERFLOW)?;
948 }
949
950 let years_plus_100 = years_adj.checked_add(100).ok_or(Errno::EOVERFLOW)?;
951 if years_plus_100 > i64::from(i32::MAX) || years_plus_100 < i64::from(i32::MIN) {
952 return Err(Errno::EOVERFLOW);
953 }
954
955 let hour = remsecs.checked_div(3600).ok_or(Errno::EOVERFLOW)?;
956 let min = remsecs
957 .checked_div(60)
958 .ok_or(Errno::EOVERFLOW)?
959 .checked_rem(60)
960 .ok_or(Errno::EOVERFLOW)?;
961 let sec = remsecs.checked_rem(60).ok_or(Errno::EOVERFLOW)?;
962
963 Ok(Self {
964 tm_year: i32::try_from(years_plus_100).or(Err(Errno::EOVERFLOW))?,
965 tm_mon: i32::try_from(months_adj.checked_add(2).ok_or(Errno::EOVERFLOW)?)
966 .or(Err(Errno::EOVERFLOW))?,
967 tm_mday: i32::try_from(rd.checked_add(1).ok_or(Errno::EOVERFLOW)?)
968 .or(Err(Errno::EOVERFLOW))?,
969 tm_wday: i32::try_from(wday).or(Err(Errno::EOVERFLOW))?,
970 tm_yday: i32::try_from(yday).or(Err(Errno::EOVERFLOW))?,
971 tm_hour: i32::try_from(hour).or(Err(Errno::EOVERFLOW))?,
972 tm_min: i32::try_from(min).or(Err(Errno::EOVERFLOW))?,
973 tm_sec: i32::try_from(sec).or(Err(Errno::EOVERFLOW))?,
974 })
975 }
976}
977
978impl fmt::Display for Tm {
979 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
980 let year = self.year();
981 let month = self.month();
982 let day = self.day();
983 let hour = self.hour();
984 let minute = self.minute();
985 let second = self.second();
986
987 write!(
989 f,
990 "{year:04}{month:02}{day:02}T{hour:02}{minute:02}{second:02}Z"
991 )
992 }
993}