1use lua_types::{LuaError, LuaExit, LuaType, LuaValue};
17use crate::state_stub::{LuaState, LuaStateStubExt as _};
18use lua_vm::state::OsExecuteReason;
19
20const STRFTIME_OPTIONS: &[u8] =
27 b"aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%||EcECExEXEyEYOdOeOHOIOmOMOSOuOUOVOwOWOy";
28
29const SIZE_TIME_FMT: usize = 250;
30
31#[derive(Debug, Default, Clone)]
44pub struct TmFields {
45 pub tm_sec: i32,
46 pub tm_min: i32,
47 pub tm_hour: i32,
48 pub tm_mday: i32,
49 pub tm_mon: i32,
50 pub tm_year: i32,
51 pub tm_wday: i32,
52 pub tm_yday: i32,
53 pub tm_isdst: i32,
54}
55
56struct ByteDisplay<'a>(&'a [u8]);
63
64impl std::fmt::Display for ByteDisplay<'_> {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 for &b in self.0 {
67 write!(f, "{}", b as char)?;
68 }
69 Ok(())
70 }
71}
72
73fn set_field(state: &mut LuaState, key: &[u8], value: i32, delta: i32) -> Result<(), LuaError> {
79 state.push(LuaValue::Int((value as i64) + (delta as i64)));
80 state.set_field(-2, key)?;
81 Ok(())
82}
83
84fn set_bool_field(state: &mut LuaState, key: &[u8], value: i32) -> Result<(), LuaError> {
88 if value < 0 {
89 return Ok(());
90 }
91 state.push(LuaValue::Bool(value != 0));
92 state.set_field(-2, key)?;
93 Ok(())
94}
95
96fn set_all_fields(state: &mut LuaState, stm: &TmFields) -> Result<(), LuaError> {
101 set_field(state, b"year", stm.tm_year, 1900)?;
102 set_field(state, b"month", stm.tm_mon, 1)?;
103 set_field(state, b"day", stm.tm_mday, 0)?;
104 set_field(state, b"hour", stm.tm_hour, 0)?;
105 set_field(state, b"min", stm.tm_min, 0)?;
106 set_field(state, b"sec", stm.tm_sec, 0)?;
107 set_field(state, b"yday", stm.tm_yday, 1)?;
108 set_field(state, b"wday", stm.tm_wday, 1)?;
109 set_bool_field(state, b"isdst", stm.tm_isdst)?;
110 Ok(())
111}
112
113fn get_bool_field(state: &mut LuaState, key: &[u8]) -> Result<i32, LuaError> {
117 let ty = state.get_field(-1, key)?;
118 let res = if matches!(ty, LuaType::Nil) {
119 -1i32
120 } else {
121 state.to_boolean(-1) as i32
122 };
123 state.pop_n(1);
124 Ok(res)
125}
126
127fn get_field(
138 state: &mut LuaState,
139 key: &[u8],
140 d: i32,
141 delta: i32,
142) -> Result<i32, LuaError> {
143 let ty = state.get_field(-1, key)?;
144 let maybe_int = state.to_integer_x(-1);
145 let res: i32 = match maybe_int {
146 Some(res) => {
147 let in_bounds = if res >= 0 {
149 res.saturating_sub(delta as i64) <= (i32::MAX as i64)
150 } else {
151 (i32::MIN as i64).saturating_add(delta as i64) <= res
152 };
153 if !in_bounds {
154 state.pop_n(1);
155 return Err(LuaError::runtime(format_args!(
156 "field '{}' is out-of-bound",
157 ByteDisplay(key),
158 )));
159 }
160 (res - delta as i64) as i32
161 }
162 None => {
163 if !matches!(ty, LuaType::Nil) {
164 state.pop_n(1);
165 return Err(LuaError::runtime(format_args!(
166 "field '{}' is not an integer",
167 ByteDisplay(key),
168 )));
169 } else if d < 0 {
170 state.pop_n(1);
171 return Err(LuaError::runtime(format_args!(
172 "field '{}' missing in date table",
173 ByteDisplay(key),
174 )));
175 }
176 d
177 }
178 };
179 state.pop_n(1);
180 Ok(res)
181}
182
183fn check_strftime_option<'a>(
200 _state: &mut LuaState,
201 conv: &'a [u8],
202 cc: &mut [u8; 4],
203) -> Result<&'a [u8], LuaError> {
204 let options = STRFTIME_OPTIONS;
205 let mut oplen: usize = 1;
206 let mut i: usize = 0;
207
208 while i < options.len() && oplen <= conv.len() {
209 if options[i] == b'|' {
210 oplen += 1;
213 i += oplen;
214 } else if i + oplen <= options.len() && conv[..oplen] == options[i..i + oplen] {
215 debug_assert!(oplen <= 2, "STRFTIME_OPTIONS only has 1- and 2-char specifiers");
217 cc[1..=oplen].copy_from_slice(&conv[..oplen]);
218 cc[oplen + 1] = 0;
219 return Ok(&conv[oplen..]);
220 } else {
221 i += oplen;
222 }
223 }
224 Err(LuaError::arg_error(
225 1,
226 "invalid conversion specifier",
227 ))
228}
229
230fn check_time(state: &mut LuaState, arg: i32) -> Result<i64, LuaError> {
238 let t = state.check_arg_integer(arg)?;
239 Ok(t)
240}
241
242fn unix_now(state: &LuaState) -> Result<i64, LuaError> {
244 if let Some(now_fn) = state.global().unix_time_hook {
245 return Ok(now_fn());
246 }
247
248 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
249 {
250 let _ = state;
251 return Err(LuaError::runtime(format_args!(
252 "current time not available in this host"
253 )));
254 }
255
256 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
257 {
258 use std::time::{SystemTime, UNIX_EPOCH};
259 Ok(SystemTime::now()
260 .duration_since(UNIX_EPOCH)
261 .map(|d| d.as_secs() as i64)
262 .unwrap_or(0))
263 }
264}
265
266fn native_temp_name() -> Result<Vec<u8>, LuaError> {
267 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
268 {
269 return Err(LuaError::runtime(format_args!(
270 "temporary filenames not available in this host"
271 )));
272 }
273
274 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
275 {
276 use std::sync::atomic::{AtomicU64, Ordering};
277 use std::time::{SystemTime, UNIX_EPOCH};
278
279 static COUNTER: AtomicU64 = AtomicU64::new(0);
280
281 let mut dir: Vec<u8> = {
282 let path = std::env::temp_dir();
283 #[cfg(unix)]
284 {
285 use std::os::unix::ffi::OsStrExt;
286 path.as_os_str().as_bytes().to_vec()
287 }
288 #[cfg(not(unix))]
289 {
290 path.to_string_lossy().as_bytes().to_vec()
291 }
292 };
293 if dir.last().copied() != Some(b'/') && dir.last().copied() != Some(b'\\') {
294 dir.push(b'/');
295 }
296
297 let nanos = SystemTime::now()
298 .duration_since(UNIX_EPOCH)
299 .map(|d| d.as_nanos())
300 .unwrap_or(0);
301 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
302
303 let suffix = format!("lua_{:x}_{:x}_{:x}", std::process::id(), nanos, n);
304 dir.extend_from_slice(suffix.as_bytes());
305 Ok(dir)
306 }
307}
308
309fn host_temp_name(state: &LuaState) -> Result<Vec<u8>, LuaError> {
310 match state.global().temp_name_hook {
311 Some(temp_fn) => temp_fn(),
312 None => native_temp_name(),
313 }
314}
315
316fn decompose_utc(t: i64) -> TmFields {
327 let days = t.div_euclid(86_400);
328 let sod = t.rem_euclid(86_400) as i32;
329
330 let tm_hour = sod / 3600;
331 let tm_min = (sod / 60) % 60;
332 let tm_sec = sod % 60;
333
334 let z = days + 719_468;
335 let era = (if z >= 0 { z } else { z - 146_096 }).div_euclid(146_097);
336 let doe = z - era * 146_097;
337 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
338 let y = yoe + era * 400;
339 let doy_mar = doe - (365 * yoe + yoe / 4 - yoe / 100);
340 let mp = (5 * doy_mar + 2) / 153;
341 let day = (doy_mar - (153 * mp + 2) / 5 + 1) as i32;
342 let month: i32 = if mp < 10 { (mp + 3) as i32 } else { (mp - 9) as i32 };
343 let year = y + if month <= 2 { 1 } else { 0 };
344
345 let leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
346 const DAYS_BEFORE_MONTH: [i32; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
347 let tm_yday = DAYS_BEFORE_MONTH[(month - 1) as usize]
348 + (day - 1)
349 + if leap && month > 2 { 1 } else { 0 };
350
351 let tm_wday = (days + 4).rem_euclid(7) as i32;
352
353 TmFields {
354 tm_sec,
355 tm_min,
356 tm_hour,
357 tm_mday: day,
358 tm_mon: month - 1,
359 tm_year: (year - 1900) as i32,
360 tm_wday,
361 tm_yday,
362 tm_isdst: 0,
363 }
364}
365
366fn compose_utc(tm: &TmFields) -> i64 {
374 let mut y: i64 = (tm.tm_year as i64) + 1900;
375 let mut m: i64 = (tm.tm_mon as i64) + 1;
376 let dy = (m - 1).div_euclid(12);
377 y += dy;
378 m -= dy * 12;
379 let y_adj = if m <= 2 { y - 1 } else { y };
380 let era = (if y_adj >= 0 { y_adj } else { y_adj - 399 }).div_euclid(400);
381 let yoe = y_adj - era * 400;
382 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + (tm.tm_mday as i64) - 1;
383 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
384 let days = era * 146_097 + doe - 719_468;
385 days * 86_400 + (tm.tm_hour as i64) * 3600 + (tm.tm_min as i64) * 60 + (tm.tm_sec as i64)
386}
387
388fn strftime_one(buf: &mut Vec<u8>, cc: &[u8; 4], oplen: usize, tm: &TmFields) {
400 use std::io::Write as _;
401 let spec = if oplen == 2 { cc[2] } else { cc[1] };
402 let year_full = (tm.tm_year as i64) + 1900;
403 let hour12 = {
404 let h = tm.tm_hour.rem_euclid(12);
405 if h == 0 { 12 } else { h }
406 };
407 const DAY_SHORT: [&[u8]; 7] = [b"Sun", b"Mon", b"Tue", b"Wed", b"Thu", b"Fri", b"Sat"];
408 const DAY_LONG: [&[u8]; 7] = [
409 b"Sunday", b"Monday", b"Tuesday", b"Wednesday", b"Thursday", b"Friday", b"Saturday",
410 ];
411 const MON_SHORT: [&[u8]; 12] = [
412 b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov",
413 b"Dec",
414 ];
415 const MON_LONG: [&[u8]; 12] = [
416 b"January", b"February", b"March", b"April", b"May", b"June", b"July", b"August",
417 b"September", b"October", b"November", b"December",
418 ];
419 let wday_idx = tm.tm_wday.rem_euclid(7) as usize;
420 let mon_idx = tm.tm_mon.rem_euclid(12) as usize;
421 match spec {
422 b'Y' => { let _ = write!(buf, "{}", year_full); }
423 b'y' => { let _ = write!(buf, "{:02}", year_full.rem_euclid(100)); }
424 b'C' => { let _ = write!(buf, "{:02}", year_full.div_euclid(100)); }
425 b'm' => { let _ = write!(buf, "{:02}", tm.tm_mon + 1); }
426 b'd' => { let _ = write!(buf, "{:02}", tm.tm_mday); }
427 b'e' => { let _ = write!(buf, "{:2}", tm.tm_mday); }
428 b'H' => { let _ = write!(buf, "{:02}", tm.tm_hour); }
429 b'I' => { let _ = write!(buf, "{:02}", hour12); }
430 b'k' => { let _ = write!(buf, "{:2}", tm.tm_hour); }
431 b'l' => { let _ = write!(buf, "{:2}", hour12); }
432 b'M' => { let _ = write!(buf, "{:02}", tm.tm_min); }
433 b'S' => { let _ = write!(buf, "{:02}", tm.tm_sec); }
434 b'w' => { let _ = write!(buf, "{}", tm.tm_wday); }
435 b'u' => {
436 let u = if tm.tm_wday == 0 { 7 } else { tm.tm_wday };
437 let _ = write!(buf, "{}", u);
438 }
439 b'j' => { let _ = write!(buf, "{:03}", tm.tm_yday + 1); }
440 b'a' => buf.extend_from_slice(DAY_SHORT[wday_idx]),
441 b'A' => buf.extend_from_slice(DAY_LONG[wday_idx]),
442 b'b' | b'h' => buf.extend_from_slice(MON_SHORT[mon_idx]),
443 b'B' => buf.extend_from_slice(MON_LONG[mon_idx]),
444 b'p' => buf.extend_from_slice(if tm.tm_hour < 12 { b"AM" } else { b"PM" }),
445 b'P' => buf.extend_from_slice(if tm.tm_hour < 12 { b"am" } else { b"pm" }),
446 b'D' | b'x' => {
447 let _ = write!(buf, "{:02}/{:02}/{:02}", tm.tm_mon + 1, tm.tm_mday, year_full.rem_euclid(100));
448 }
449 b'F' => {
450 let _ = write!(buf, "{}-{:02}-{:02}", year_full, tm.tm_mon + 1, tm.tm_mday);
451 }
452 b'T' | b'X' => {
453 let _ = write!(buf, "{:02}:{:02}:{:02}", tm.tm_hour, tm.tm_min, tm.tm_sec);
454 }
455 b'R' => { let _ = write!(buf, "{:02}:{:02}", tm.tm_hour, tm.tm_min); }
456 b'r' => {
457 let ampm: &[u8] = if tm.tm_hour < 12 { b"AM" } else { b"PM" };
458 let _ = write!(buf, "{:02}:{:02}:{:02} ", hour12, tm.tm_min, tm.tm_sec);
459 buf.extend_from_slice(ampm);
460 }
461 b'c' => {
462 let _ = write!(
463 buf,
464 "{} {} {:2} {:02}:{:02}:{:02} {}",
465 std::str::from_utf8(DAY_SHORT[wday_idx]).unwrap_or(""),
466 std::str::from_utf8(MON_SHORT[mon_idx]).unwrap_or(""),
467 tm.tm_mday,
468 tm.tm_hour,
469 tm.tm_min,
470 tm.tm_sec,
471 year_full,
472 );
473 }
474 b'n' => buf.push(b'\n'),
475 b't' => buf.push(b'\t'),
476 b'%' => buf.push(b'%'),
477 b'z' => buf.extend_from_slice(b"+0000"),
478 b'Z' => buf.extend_from_slice(b"UTC"),
479 b's' => { let _ = write!(buf, "{}", compose_utc(tm)); }
480 b'U' => {
481 let week = (tm.tm_yday + 7 - tm.tm_wday) / 7;
482 let _ = write!(buf, "{:02}", week);
483 }
484 b'W' => {
485 let mwday = if tm.tm_wday == 0 { 6 } else { tm.tm_wday - 1 };
486 let week = (tm.tm_yday + 7 - mwday) / 7;
487 let _ = write!(buf, "{:02}", week);
488 }
489 b'V' | b'g' | b'G' => {
490 let _ = write!(buf, "{:02}", 1);
491 }
492 _ => {}
493 }
494}
495
496pub(crate) fn os_execute(state: &mut LuaState) -> Result<usize, LuaError> {
510 let cmd = state.opt_arg_lstring(1, None)?;
511 match cmd {
512 None => {
513 let has_shell = state.global().os_execute_hook.is_some();
515 state.push(LuaValue::Bool(has_shell));
516 Ok(1)
517 }
518 Some(cmd_bytes) => {
519 let hook = state.global().os_execute_hook;
520 match hook {
521 Some(execute_fn) => {
522 let cmd_owned: Vec<u8> = cmd_bytes.to_vec();
524 match execute_fn(&cmd_owned) {
525 Ok(result) => {
526 if result.success {
527 state.push(LuaValue::Bool(true));
528 } else {
529 state.push(LuaValue::Nil);
530 }
531 let reason_str: &[u8] = match result.reason {
532 OsExecuteReason::Exit => b"exit",
533 OsExecuteReason::Signal => b"signal",
534 };
535 state.push_string(reason_str)?;
536 state.push(LuaValue::Int(result.code as i64));
537 Ok(3)
538 }
539 Err(e) => {
540 state.push(LuaValue::Nil);
541 let msg = match &e {
542 LuaError::Runtime(LuaValue::Str(s)) => s.as_bytes().to_vec(),
543 other => format!("{:?}", other).into_bytes(),
544 };
545 let s = state.intern_str(&msg)?;
546 state.push(LuaValue::Str(s));
547 state.push(LuaValue::Int(-1));
548 Ok(3)
549 }
550 }
551 }
552 None => {
553 state.push(LuaValue::Nil);
554 state.push_string(b"os.execute: not implemented in lua-stdlib")?;
555 state.push(LuaValue::Int(-1));
556 Ok(3)
557 }
558 }
559 }
560 }
561}
562
563pub(crate) fn os_remove(state: &mut LuaState) -> Result<usize, LuaError> {
567 let filename: Vec<u8> = state.check_arg_string(1)?.to_vec();
568 let hook = state.global().file_remove_hook;
570 match hook {
571 Some(remove_fn) => match remove_fn(&filename) {
572 Ok(()) => {
573 state.push(LuaValue::Bool(true));
574 Ok(1)
575 }
576 Err(e) => {
577 state.push(LuaValue::Nil);
578 let msg = match &e {
579 LuaError::Runtime(LuaValue::Str(s)) => s.as_bytes().to_vec(),
580 other => format!("{:?}", other).into_bytes(),
581 };
582 let s = state.intern_str(&msg)?;
583 state.push(LuaValue::Str(s));
584 Ok(2)
585 }
586 },
587 None => {
588 state.push(LuaValue::Nil);
589 state.push_string(b"os.remove: no filesystem hook registered")?;
590 Ok(2)
591 }
592 }
593}
594
595pub(crate) fn os_rename(state: &mut LuaState) -> Result<usize, LuaError> {
599 let fromname: Vec<u8> = state.check_arg_string(1)?.to_vec();
600 let toname: Vec<u8> = state.check_arg_string(2)?.to_vec();
601 let hook = state.global().file_rename_hook;
603 match hook {
604 Some(rename_fn) => match rename_fn(&fromname, &toname) {
605 Ok(()) => {
606 state.push(LuaValue::Bool(true));
607 return Ok(1);
608 }
609 Err(e) => {
610 state.push(LuaValue::Nil);
611 let msg = match &e {
612 LuaError::Runtime(LuaValue::Str(s)) => s.as_bytes().to_vec(),
613 other => format!("{:?}", other).into_bytes(),
614 };
615 let s = state.intern_str(&msg)?;
616 state.push(LuaValue::Str(s));
617 return Ok(2);
618 }
619 },
620 None => {}
621 }
622 state.push(LuaValue::Nil);
623 state.push_string(b"os.rename: no filesystem hook registered")?;
624 Ok(2)
625}
626
627pub(crate) fn os_tmpname(state: &mut LuaState) -> Result<usize, LuaError> {
635 let dir = host_temp_name(state)?;
636 state.push_string(&dir)?;
637 Ok(1)
638}
639
640pub(crate) fn os_getenv(state: &mut LuaState) -> Result<usize, LuaError> {
644 let name_bytes: Vec<u8> = state.check_arg_string(1)?.to_vec();
645
646 let result: Option<Vec<u8>> = match state.global().env_hook {
647 Some(env_fn) => env_fn(&name_bytes),
648 None => {
649 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
650 {
651 None
652 }
653
654 #[cfg(all(unix, not(all(target_arch = "wasm32", target_os = "unknown"))))]
655 {
656 use std::ffi::OsStr;
657 use std::os::unix::ffi::{OsStrExt, OsStringExt};
658 let os_name = OsStr::from_bytes(&name_bytes);
659 std::env::var_os(os_name).map(|v| v.into_vec())
660 }
661
662 #[cfg(all(not(unix), not(all(target_arch = "wasm32", target_os = "unknown"))))]
663 {
664 match std::str::from_utf8(&name_bytes) {
667 Ok(name_str) => std::env::var(name_str).ok().map(|v| v.into_bytes()),
668 Err(_) => None,
669 }
670 }
671 }
672 };
673
674 match result {
675 Some(val) => {
676 state.push_string(&val)?;
677 }
678 None => {
679 state.push(LuaValue::Nil);
680 }
681 }
682 Ok(1)
683}
684
685pub(crate) fn os_clock(state: &mut LuaState) -> Result<usize, LuaError> {
688 let seconds = cpu_seconds(state)?;
689 state.push(LuaValue::Float(seconds));
690 Ok(1)
691}
692
693fn cpu_seconds(state: &LuaState) -> Result<f64, LuaError> {
701 if let Some(clock_fn) = state.global().cpu_clock_hook {
702 return Ok(clock_fn());
703 }
704
705 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
706 {
707 let _ = state;
708 Err(LuaError::runtime(format_args!(
709 "CPU clock not available in this host"
710 )))
711 }
712
713 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
714 {
715 let _ = state;
716 use std::sync::OnceLock;
717 use std::time::Instant;
718 static START: OnceLock<Instant> = OnceLock::new();
719 Ok(START.get_or_init(Instant::now).elapsed().as_secs_f64())
720 }
721}
722
723pub(crate) fn os_date(state: &mut LuaState) -> Result<usize, LuaError> {
731 let format: Vec<u8> = state.opt_arg_lstring(1, Some(b"%c"))?.unwrap_or_default();
733 let s: &[u8] = &format[..];
734
735 let t: i64 = if matches!(state.type_at(2), LuaType::None | LuaType::Nil) {
736 unix_now(state)?
737 } else {
738 check_time(state, 2)?
739 };
740
741 let (_use_utc, s): (bool, &[u8]) = if s.first() == Some(&b'!') {
742 (true, &s[1..])
743 } else {
744 (false, s)
745 };
746
747 let stm = decompose_utc(t);
754
755 if s == b"*t" {
759 state.create_table(0, 9)?;
760 set_all_fields(state, &stm)?;
761 } else {
762 let mut result: Vec<u8> = Vec::new();
763 let mut pos: usize = 0;
764
765 while pos < s.len() {
766 if s[pos] != b'%' {
767 result.push(s[pos]);
768 pos += 1;
769 } else {
770 pos += 1;
771 let mut cc = [0u8; 4];
772 cc[0] = b'%';
773 let conv = &s[pos..];
778 let after = check_strftime_option(state, conv, &mut cc)?;
779 let oplen = conv.len() - after.len();
780 pos += oplen;
781 strftime_one(&mut result, &cc, oplen, &stm);
785 let _ = SIZE_TIME_FMT;
786 }
787 }
788 state.push_string(&result)?;
789 }
790 Ok(1)
791}
792
793pub(crate) fn os_time(state: &mut LuaState) -> Result<usize, LuaError> {
799 let t: i64;
800
801 if matches!(state.type_at(1), LuaType::None | LuaType::Nil) {
802 t = unix_now(state)?;
803 } else {
804 state.check_arg_type(1, LuaType::Table)?;
805 lua_vm::api::set_top(state, 1)?;
809
810 let tm_year = get_field(state, b"year", -1, 1900)?;
811 let tm_mon = get_field(state, b"month", -1, 1)?;
812 let tm_mday = get_field(state, b"day", -1, 0)?;
813 let tm_hour = get_field(state, b"hour", 12, 0)?;
814 let tm_min = get_field(state, b"min", 0, 0)?;
815 let tm_sec = get_field(state, b"sec", 0, 0)?;
816 let tm_isdst = get_bool_field(state, b"isdst")?;
817
818 let raw = TmFields {
819 tm_year,
820 tm_mon,
821 tm_mday,
822 tm_hour,
823 tm_min,
824 tm_sec,
825 tm_isdst,
826 ..TmFields::default()
827 };
828
829 t = compose_utc(&raw);
836 let stm = decompose_utc(t);
837
838 set_all_fields(state, &stm)?;
839 }
840
841 if t == -1 {
845 return Err(LuaError::runtime(format_args!(
846 "time result cannot be represented in this installation"
847 )));
848 }
849
850 state.push(LuaValue::Int(t));
851 Ok(1)
852}
853
854pub(crate) fn os_difftime(state: &mut LuaState) -> Result<usize, LuaError> {
861 let t1 = check_time(state, 1)?;
862 let t2 = check_time(state, 2)?;
863 state.push(LuaValue::Float((t1 - t2) as f64));
864 Ok(1)
865}
866
867pub(crate) fn os_setlocale(state: &mut LuaState) -> Result<usize, LuaError> {
871 const CAT_NAMES: &[&[u8]] = &[
872 b"all", b"collate", b"ctype", b"monetary", b"numeric", b"time",
873 ];
874
875 let locale: Option<Vec<u8>> = state.opt_arg_lstring(1, None)?;
876
877 let _op: usize = state.check_arg_option(2, Some(b"all"), CAT_NAMES)?;
878
879 let result_locale: Option<&[u8]> = match locale.as_deref() {
884 None => Some(b"C"), Some(b"C") | Some(b"POSIX") => Some(b"C"), Some(_) => None, };
888 match result_locale {
889 Some(s) => { state.push_string(s)?; }
890 None => state.push(LuaValue::Nil),
891 }
892 Ok(1)
893}
894
895pub(crate) fn os_exit(state: &mut LuaState) -> Result<usize, LuaError> {
901 let exit_code: i32 = if matches!(state.type_at(1), LuaType::Boolean) {
905 if state.to_boolean(1) { 0 } else { 1 } } else {
907 state.opt_arg_integer(1, 0)? as i32
908 };
909
910 if state.to_boolean(2) {
911 state.close();
912 }
913
914 std::panic::panic_any(LuaExit(exit_code));
920}
921
922pub type NativeFn = fn(&mut LuaState) -> Result<usize, LuaError>;
929
930pub const OS_LIB: &[(&[u8], NativeFn)] = &[
934 (b"clock", os_clock),
935 (b"date", os_date),
936 (b"difftime", os_difftime),
937 (b"execute", os_execute),
938 (b"exit", os_exit),
939 (b"getenv", os_getenv),
940 (b"remove", os_remove),
941 (b"rename", os_rename),
942 (b"setlocale", os_setlocale),
943 (b"time", os_time),
944 (b"tmpname", os_tmpname),
945];
946
947pub fn open_os(state: &mut LuaState) -> Result<usize, LuaError> {
954 state.register_lib(b"os", OS_LIB)?;
955 Ok(1)
956}
957
958