pub(crate) enum LibcFile {}
#[allow(unsafe_code)]
unsafe extern "C" {
pub(crate) fn fopen(filename: *const u8, mode: *const u8) -> *mut LibcFile;
pub(crate) fn fclose(file: *mut LibcFile) -> i32;
pub(crate) fn fflush(file: *mut LibcFile) -> i32;
pub(crate) fn fread(ptr: *mut u8, size: usize, nmemb: usize, stream: *mut LibcFile) -> usize;
pub(crate) fn fwrite(ptr: *const u8, size: usize, nmemb: usize, stream: *mut LibcFile)
-> usize;
pub(crate) fn fgets(s: *mut u8, n: i32, stream: *mut LibcFile) -> *mut u8;
pub(crate) fn fseek(stream: *mut LibcFile, offset: i64, whence: i32) -> i32;
pub(crate) fn ftell(stream: *mut LibcFile) -> i64;
pub(crate) fn ferror(stream: *mut LibcFile) -> i32;
pub(crate) fn clearerr(stream: *mut LibcFile);
#[allow(dead_code)]
pub(crate) fn feof(stream: *mut LibcFile) -> i32;
pub(crate) fn getc(stream: *mut LibcFile) -> i32;
pub(crate) fn ungetc(c: i32, stream: *mut LibcFile) -> i32;
pub(crate) fn setvbuf(stream: *mut LibcFile, buf: *mut u8, mode: i32, size: usize) -> i32;
pub(crate) fn tmpfile() -> *mut LibcFile;
pub(crate) fn fscanf(stream: *mut LibcFile, format: *const u8, ...) -> i32;
pub(crate) fn fprintf(stream: *mut LibcFile, format: *const u8, ...) -> i32;
pub(crate) fn strlen(s: *const u8) -> usize;
pub(crate) fn strtod(nptr: *const u8, endptr: *mut *mut u8) -> f64;
pub(crate) fn localeconv() -> *const LConv;
pub(crate) fn strcoll(s1: *const u8, s2: *const u8) -> i32;
pub(crate) fn clock() -> ClockT;
pub(crate) fn mktime(tm: *mut Tm) -> TimeT;
pub(crate) fn strftime(s: *mut u8, max: usize, format: *const u8, tm: *const Tm) -> usize;
pub(crate) fn setlocale(category: i32, locale: *const u8) -> *const u8;
}
#[repr(C)]
pub(crate) struct LConv {
pub(crate) decimal_point: *const u8,
}
pub(crate) type TimeT = i64;
#[cfg(not(target_os = "windows"))]
pub(crate) type ClockT = i64;
#[cfg(target_os = "windows")]
pub(crate) type ClockT = i32;
#[cfg(not(target_os = "windows"))]
pub(crate) const CLOCKS_PER_SEC: f64 = 1_000_000.0;
#[cfg(target_os = "windows")]
pub(crate) const CLOCKS_PER_SEC: f64 = 1_000.0;
#[repr(C)]
#[allow(clippy::struct_field_names)]
pub(crate) struct Tm {
pub(crate) tm_sec: i32,
pub(crate) tm_min: i32,
pub(crate) tm_hour: i32,
pub(crate) tm_mday: i32,
pub(crate) tm_mon: i32,
pub(crate) tm_year: i32,
pub(crate) tm_wday: i32,
pub(crate) tm_yday: i32,
pub(crate) tm_isdst: i32,
#[cfg(not(target_os = "windows"))]
tm_gmtoff: i64,
#[cfg(not(target_os = "windows"))]
tm_zone: *const i8,
}
#[allow(clippy::derivable_impls)]
impl Default for Tm {
fn default() -> Self {
Self {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 0,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
#[cfg(not(target_os = "windows"))]
tm_gmtoff: 0,
#[cfg(not(target_os = "windows"))]
tm_zone: std::ptr::null(),
}
}
}
pub(crate) fn current_time() -> TimeT {
use std::time::{SystemTime, UNIX_EPOCH};
#[allow(clippy::cast_possible_wrap)]
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(d) => d.as_secs() as TimeT,
Err(_) => 0,
}
}
#[cfg(target_os = "linux")]
#[allow(unsafe_code)]
unsafe extern "C" {
static stdin: *mut LibcFile;
static stdout: *mut LibcFile;
static stderr: *mut LibcFile;
}
#[cfg(target_os = "macos")]
#[allow(unsafe_code)]
unsafe extern "C" {
#[link_name = "__stdinp"]
static stdin: *mut LibcFile;
#[link_name = "__stdoutp"]
static stdout: *mut LibcFile;
#[link_name = "__stderrp"]
static stderr: *mut LibcFile;
}
#[cfg(target_os = "windows")]
#[allow(unsafe_code)]
unsafe extern "C" {
fn __acrt_iob_func(index: u32) -> *mut LibcFile;
}
#[allow(unsafe_code)]
pub(crate) fn c_stdin() -> *mut LibcFile {
unsafe {
#[cfg(not(target_os = "windows"))]
{
stdin
}
#[cfg(target_os = "windows")]
{
__acrt_iob_func(0)
}
}
}
#[allow(unsafe_code)]
pub(crate) fn c_stdout() -> *mut LibcFile {
unsafe {
#[cfg(not(target_os = "windows"))]
{
stdout
}
#[cfg(target_os = "windows")]
{
__acrt_iob_func(1)
}
}
}
#[allow(unsafe_code)]
pub(crate) fn c_stderr() -> *mut LibcFile {
unsafe {
#[cfg(not(target_os = "windows"))]
{
stderr
}
#[cfg(target_os = "windows")]
{
__acrt_iob_func(2)
}
}
}
#[cfg(not(target_os = "windows"))]
#[allow(unsafe_code)]
unsafe extern "C" {
fn popen(command: *const u8, r#type: *const u8) -> *mut LibcFile;
fn pclose(stream: *mut LibcFile) -> i32;
}
#[cfg(target_os = "windows")]
#[allow(unsafe_code)]
unsafe extern "C" {
#[link_name = "_popen"]
fn popen(command: *const u8, r#type: *const u8) -> *mut LibcFile;
#[link_name = "_pclose"]
fn pclose(stream: *mut LibcFile) -> i32;
}
#[allow(unsafe_code)]
pub(crate) fn c_popen(command: *const u8, mode: *const u8) -> *mut LibcFile {
unsafe { popen(command, mode) }
}
#[allow(unsafe_code)]
pub(crate) fn c_pclose(stream: *mut LibcFile) -> i32 {
unsafe { pclose(stream) }
}
#[cfg(not(target_os = "windows"))]
#[allow(unsafe_code)]
unsafe extern "C" {
fn localtime_r(timep: *const TimeT, result: *mut Tm) -> *mut Tm;
fn gmtime_r(timep: *const TimeT, result: *mut Tm) -> *mut Tm;
}
#[cfg(target_os = "windows")]
#[allow(unsafe_code)]
unsafe extern "C" {
fn localtime_s(result: *mut Tm, timep: *const TimeT) -> i32;
fn gmtime_s(result: *mut Tm, timep: *const TimeT) -> i32;
}
#[allow(unsafe_code)]
pub(crate) fn c_localtime(timep: *const TimeT, result: *mut Tm) -> bool {
unsafe {
#[cfg(not(target_os = "windows"))]
{
!localtime_r(timep, result).is_null()
}
#[cfg(target_os = "windows")]
{
localtime_s(result, timep) == 0
}
}
}
#[allow(unsafe_code)]
pub(crate) fn c_gmtime(timep: *const TimeT, result: *mut Tm) -> bool {
unsafe {
#[cfg(not(target_os = "windows"))]
{
!gmtime_r(timep, result).is_null()
}
#[cfg(target_os = "windows")]
{
gmtime_s(result, timep) == 0
}
}
}
pub(crate) fn c_tmpname() -> Option<Vec<u8>> {
use std::fs::File;
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
let tmp_dir = std::env::temp_dir();
let pid = std::process::id();
for _ in 0..16 {
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let name = format!("lua_{pid}_{n}");
let path = tmp_dir.join(&name);
if File::create_new(&path).is_ok() {
return path.to_str().map(|s| s.as_bytes().to_vec());
}
}
None
}
pub(crate) mod locale {
#[cfg(not(target_os = "windows"))]
pub(crate) const LC_ALL: i32 = 6;
#[cfg(not(target_os = "windows"))]
pub(crate) const LC_COLLATE: i32 = 3;
#[cfg(not(target_os = "windows"))]
pub(crate) const LC_CTYPE: i32 = 0;
#[cfg(not(target_os = "windows"))]
pub(crate) const LC_MONETARY: i32 = 4;
#[cfg(not(target_os = "windows"))]
pub(crate) const LC_NUMERIC: i32 = 1;
#[cfg(not(target_os = "windows"))]
pub(crate) const LC_TIME: i32 = 2;
#[cfg(target_os = "windows")]
pub(crate) const LC_ALL: i32 = 0;
#[cfg(target_os = "windows")]
pub(crate) const LC_COLLATE: i32 = 1;
#[cfg(target_os = "windows")]
pub(crate) const LC_CTYPE: i32 = 2;
#[cfg(target_os = "windows")]
pub(crate) const LC_MONETARY: i32 = 3;
#[cfg(target_os = "windows")]
pub(crate) const LC_NUMERIC: i32 = 4;
#[cfg(target_os = "windows")]
pub(crate) const LC_TIME: i32 = 5;
}
pub(crate) mod bufmode {
#[cfg(not(target_os = "windows"))]
pub(crate) const IONBF: i32 = 2;
#[cfg(not(target_os = "windows"))]
pub(crate) const IOFBF: i32 = 0;
#[cfg(not(target_os = "windows"))]
pub(crate) const IOLBF: i32 = 1;
#[cfg(target_os = "windows")]
pub(crate) const IONBF: i32 = 0x0004;
#[cfg(target_os = "windows")]
pub(crate) const IOFBF: i32 = 0x0000;
#[cfg(target_os = "windows")]
pub(crate) const IOLBF: i32 = 0x0040;
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic, unsafe_code)]
mod tests {
use super::*;
#[test]
fn current_time_reasonable() {
let t = current_time();
assert!(t > 1_704_067_200);
}
#[test]
fn std_streams_not_null() {
assert!(!c_stdin().is_null());
assert!(!c_stdout().is_null());
assert!(!c_stderr().is_null());
}
#[test]
fn tmpname_succeeds() {
let name = c_tmpname().expect("c_tmpname should succeed");
assert!(!name.is_empty());
let name_str = std::str::from_utf8(&name).expect("valid utf8");
let _ = std::fs::remove_file(name_str);
}
#[test]
fn tm_default_zeroed() {
let tm = Tm::default();
assert_eq!(tm.tm_sec, 0);
assert_eq!(tm.tm_min, 0);
assert_eq!(tm.tm_hour, 0);
assert_eq!(tm.tm_mday, 0);
assert_eq!(tm.tm_mon, 0);
assert_eq!(tm.tm_year, 0);
assert_eq!(tm.tm_wday, 0);
assert_eq!(tm.tm_yday, 0);
assert_eq!(tm.tm_isdst, 0);
}
#[test]
fn c_localtime_roundtrip() {
let t: TimeT = 1_000_000_000; let mut tm = Tm::default();
assert!(c_localtime(&raw const t, &raw mut tm));
let t2 = unsafe { mktime(&raw mut tm) };
assert_eq!(t, t2);
}
#[test]
fn c_gmtime_epoch() {
let t: TimeT = 0; let mut tm = Tm::default();
assert!(c_gmtime(&raw const t, &raw mut tm));
assert_eq!(tm.tm_year, 70);
assert_eq!(tm.tm_mon, 0);
assert_eq!(tm.tm_mday, 1);
assert_eq!(tm.tm_hour, 0);
assert_eq!(tm.tm_min, 0);
assert_eq!(tm.tm_sec, 0);
}
#[test]
fn locale_constants_valid() {
let cats = [
locale::LC_ALL,
locale::LC_COLLATE,
locale::LC_CTYPE,
locale::LC_MONETARY,
locale::LC_NUMERIC,
locale::LC_TIME,
];
for &c in &cats {
assert!(c >= 0);
}
}
#[test]
fn bufmode_constants_distinct() {
assert_ne!(bufmode::IONBF, bufmode::IOFBF);
assert_ne!(bufmode::IONBF, bufmode::IOLBF);
assert_ne!(bufmode::IOFBF, bufmode::IOLBF);
}
#[cfg(not(target_os = "windows"))]
#[test]
fn popen_echo() {
let cmd = b"echo hello\0";
let mode = b"r\0";
let fp = c_popen(cmd.as_ptr(), mode.as_ptr());
assert!(!fp.is_null());
let mut buf = [0u8; 64];
let n = unsafe { fread(buf.as_mut_ptr(), 1, buf.len(), fp) };
assert!(n > 0);
assert!(
std::str::from_utf8(&buf[..n])
.expect("valid utf8")
.starts_with("hello")
);
assert_eq!(c_pclose(fp), 0);
}
}