From 770e7688188e921b93771f162953e515c2297520 Mon Sep 17 00:00:00 2001 From: Guillaume Gielly Date: Sun, 5 Jan 2025 18:20:42 +0100 Subject: [PATCH 1/7] Implement 'langinfo.h' --- src/header/langinfo/cbindgen.toml | 9 ++ src/header/langinfo/mod.rs | 164 ++++++++++++++++++++++++++++++ src/header/mod.rs | 2 +- 3 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 src/header/langinfo/cbindgen.toml create mode 100644 src/header/langinfo/mod.rs diff --git a/src/header/langinfo/cbindgen.toml b/src/header/langinfo/cbindgen.toml new file mode 100644 index 0000000000..7fc05f874e --- /dev/null +++ b/src/header/langinfo/cbindgen.toml @@ -0,0 +1,9 @@ +sys_includes = ["stddef.h", "stdint.h", "features.h"] +include_guard = "_RELIBC_LANGINFO_H" +language = "C" +style = "tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/langinfo/mod.rs b/src/header/langinfo/mod.rs new file mode 100644 index 0000000000..f32f8b26f4 --- /dev/null +++ b/src/header/langinfo/mod.rs @@ -0,0 +1,164 @@ +// langinfo.h implementation for Redox, following the POSIX standard. +// Following https://pubs.opengroup.org/onlinepubs/7908799/xsh/langinfo.h.html +// +// TODO : involve loading locale data. Currently, the implementation only supports the "C" locale. + +use core::ffi::c_char; + +/// TODO : the POSIX type for items used with `nl_langinfo` (not implemented), which is defined in +pub type nl_item = i32; + +// Constant definitions +const ITEMS: &[(nl_item, &[u8])] = &[ + (CODESET, b"UTF-8\0"), + (D_T_FMT, b"%a %b %e %H:%M:%S %Y\0"), + (D_FMT, b"%m/%d/%y\0"), + (T_FMT, b"%H:%M:%S\0"), + (T_FMT_AMPM, b"%I:%M:%S %p\0"), + (AM_STR, b"AM\0"), + (PM_STR, b"PM\0"), + (DAY_1, b"Sunday\0"), + (DAY_2, b"Monday\0"), + (DAY_3, b"Tuesday\0"), + (DAY_4, b"Wednesday\0"), + (DAY_5, b"Thursday\0"), + (DAY_6, b"Friday\0"), + (DAY_7, b"Saturday\0"), + (ABDAY_1, b"Sun\0"), + (ABDAY_2, b"Mon\0"), + (ABDAY_3, b"Tue\0"), + (ABDAY_4, b"Wed\0"), + (ABDAY_5, b"Thu\0"), + (ABDAY_6, b"Fri\0"), + (ABDAY_7, b"Sat\0"), + (MON_1, b"January\0"), + (MON_2, b"February\0"), + (MON_3, b"March\0"), + (MON_4, b"April\0"), + (MON_5, b"May\0"), + (MON_6, b"June\0"), + (MON_7, b"July\0"), + (MON_8, b"August\0"), + (MON_9, b"September\0"), + (MON_10, b"October\0"), + (MON_11, b"November\0"), + (MON_12, b"December\0"), + (ABMON_1, b"Jan\0"), + (ABMON_2, b"Feb\0"), + (ABMON_3, b"Mar\0"), + (ABMON_4, b"Apr\0"), + (ABMON_5, b"May\0"), + (ABMON_6, b"Jun\0"), + (ABMON_7, b"Jul\0"), + (ABMON_8, b"Aug\0"), + (ABMON_9, b"Sep\0"), + (ABMON_10, b"Oct\0"), + (ABMON_11, b"Nov\0"), + (ABMON_12, b"Dec\0"), + (ERA, b"\0"), + (ERA_D_FMT, b"\0"), + (ERA_D_T_FMT, b"\0"), + (ERA_T_FMT, b"\0"), + (ALT_DIGITS, b"\0"), + (RADIXCHAR, b".\0"), + (THOUSEP, b"\0"), + (CRNCYSTR, b".\0"), + (YESEXPR, b"^[yY]\0"), + (NOEXPR, b"^[nN]\0"), + (YESSTR, b"yes\0"), + (NOSTR, b"no\0"), +]; + +// Item constants +pub const CODESET: nl_item = 0; +pub const D_T_FMT: nl_item = 1; +pub const D_FMT: nl_item = 2; +pub const T_FMT: nl_item = 3; +pub const T_FMT_AMPM: nl_item = 4; +pub const AM_STR: nl_item = 5; +pub const PM_STR: nl_item = 6; + +pub const DAY_1: nl_item = 7; +pub const DAY_2: nl_item = 8; +pub const DAY_3: nl_item = 9; +pub const DAY_4: nl_item = 10; +pub const DAY_5: nl_item = 11; +pub const DAY_6: nl_item = 12; +pub const DAY_7: nl_item = 13; + +pub const ABDAY_1: nl_item = 14; +pub const ABDAY_2: nl_item = 15; +pub const ABDAY_3: nl_item = 16; +pub const ABDAY_4: nl_item = 17; +pub const ABDAY_5: nl_item = 18; +pub const ABDAY_6: nl_item = 19; +pub const ABDAY_7: nl_item = 20; + +pub const MON_1: nl_item = 21; +pub const MON_2: nl_item = 22; +pub const MON_3: nl_item = 23; +pub const MON_4: nl_item = 24; +pub const MON_5: nl_item = 25; +pub const MON_6: nl_item = 26; +pub const MON_7: nl_item = 27; +pub const MON_8: nl_item = 28; +pub const MON_9: nl_item = 29; +pub const MON_10: nl_item = 30; +pub const MON_11: nl_item = 31; +pub const MON_12: nl_item = 32; + +pub const ABMON_1: nl_item = 33; +pub const ABMON_2: nl_item = 34; +pub const ABMON_3: nl_item = 35; +pub const ABMON_4: nl_item = 36; +pub const ABMON_5: nl_item = 37; +pub const ABMON_6: nl_item = 38; +pub const ABMON_7: nl_item = 39; +pub const ABMON_8: nl_item = 40; +pub const ABMON_9: nl_item = 41; +pub const ABMON_10: nl_item = 42; +pub const ABMON_11: nl_item = 43; +pub const ABMON_12: nl_item = 44; + +pub const ERA: nl_item = 45; +pub const ERA_D_FMT: nl_item = 46; +pub const ERA_D_T_FMT: nl_item = 47; +pub const ERA_T_FMT: nl_item = 48; +pub const ALT_DIGITS: nl_item = 49; +pub const RADIXCHAR: nl_item = 50; +pub const THOUSEP: nl_item = 51; +pub const YESEXPR: nl_item = 52; +pub const NOEXPR: nl_item = 53; +pub const YESSTR: nl_item = 54; // Legaxy +pub const NOSTR: nl_item = 55; // Legacy +pub const CRNCYSTR: nl_item = 56; + +const fn is_valid_nl_item(item: nl_item) -> bool { + item >= CODESET && item <= CRNCYSTR +} + +/// Return a pointer to a C string for the specified `nl_item`. +/// Currently only supports the "C" locale. +/// +/// # Safety +/// - Returns a static string pointer valid for program lifetime +/// - Caller must not modify or free the returned pointer +/// - The returned pointer is always null-terminated +/// +/// # Returns +/// - Valid pointer to a null-terminated string for valid items +/// - Pointer to empty string for invalid items +#[no_mangle] +pub unsafe extern "C" fn nl_langinfo(item: nl_item) -> *const c_char { + if !is_valid_nl_item(item) { + return b"\0".as_ptr() as *const c_char; + } + + for &(id, string) in ITEMS { + if id == item { + return string.as_ptr() as *const c_char; + } + } + + b"\0".as_ptr() as *const c_char +} diff --git a/src/header/mod.rs b/src/header/mod.rs index 864c263fbf..bfd85d5eea 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -30,7 +30,7 @@ pub mod grp; // TODO: iconv.h pub mod inttypes; // iso646.h implemented in C -// TODO: langinfo.h +pub mod langinfo; // TODO: libintl.h pub mod libgen; pub mod limits; From ba225e20006f98a91e24cce203f1a4731397754a Mon Sep 17 00:00:00 2001 From: Guillaume Gielly Date: Sun, 5 Jan 2025 19:45:29 +0100 Subject: [PATCH 2/7] Refactor strftime() to use langinfo constants for locale-based formatting : - replaced hardcoded day and month names with calls to nl_langinfo, - integrated langinfo constants DAY_1, ABDAY_1, MON_1, ABMON_1) for weekday and month names, - used AM_STR and PM_STR for AM/PM strings in %p and %P specifiers, - add comments. --- src/header/time/strftime.rs | 195 ++++++++++++++++++++++++++++-------- 1 file changed, 151 insertions(+), 44 deletions(-) diff --git a/src/header/time/strftime.rs b/src/header/time/strftime.rs index 23d9fb5caa..8bce391ac9 100644 --- a/src/header/time/strftime.rs +++ b/src/header/time/strftime.rs @@ -1,10 +1,45 @@ +// strftime implementation for Redox, following the POSIX standard. +// Following https://pubs.opengroup.org/onlinepubs/7908799/xsh/strftime.html use alloc::string::String; +use super::tm; use crate::platform::{self, types::*, WriteByte}; -use super::tm; +// We use the langinfo constants +use crate::header::langinfo::{ + nl_item, + nl_langinfo, + ABDAY_1, + ABMON_1, + AM_STR, + DAY_1, + MON_1, + PM_STR, + // TODO : other constants if needed +}; +/// A helper that calls `nl_langinfo(item)` and converts the returned pointer +/// into a `&str`. If it fails or is null, returns an empty string "". +unsafe fn langinfo_to_str(item: nl_item) -> &'static str { + use core::ffi::CStr; + + let ptr = nl_langinfo(item); + if ptr.is_null() { + return ""; + } + match CStr::from_ptr(ptr).to_str() { + Ok(s) => s, + Err(_) => "", + } +} + +/// Formats time data according to the given `format` string. +/// +/// Use `langinfo` for locale-based day/month names, +/// but still hard-codes other aspects of the "C" locale (like numeric/date +/// formats) and ignores `%E` / `%O` variations. pub unsafe fn strftime(w: &mut W, format: *const c_char, t: *const tm) -> size_t { + /// Helper that actually parses the format string and writes output. pub unsafe fn inner_strftime( w: &mut W, mut format: *const c_char, @@ -22,11 +57,11 @@ pub unsafe fn strftime(w: &mut W, format: *const c_char, t: *const } }}; (recurse $fmt:expr) => {{ - let mut fmt = String::with_capacity($fmt.len() + 1); - fmt.push_str($fmt); - fmt.push('\0'); + let mut tmp = String::with_capacity($fmt.len() + 1); + tmp.push_str($fmt); + tmp.push('\0'); - if !inner_strftime(w, fmt.as_ptr() as *mut c_char, t) { + if !inner_strftime(w, tmp.as_ptr() as *mut c_char, t) { return false; } }}; @@ -36,106 +71,178 @@ pub unsafe fn strftime(w: &mut W, format: *const c_char, t: *const } }}; ($fmt:expr, $($args:expr),+) => {{ - // Would use write!() if I could get the length written + // Could use write!() if we didn't need the exact count if write!(w, $fmt, $($args),+).is_err() { return false; } }}; } - const WDAYS: [&str; 7] = [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ]; - const MONTHS: [&str; 12] = [ - "January", - "Febuary", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ]; while *format != 0 { + // If the character isn't '%', just copy it out. if *format as u8 != b'%' { w!(byte * format as u8); format = format.offset(1); continue; } + // Skip '%' format = format.offset(1); + // POSIX says '%E' and '%O' can modify numeric formats for locales, + // but we ignore them in this minimal "C" locale approach. if *format as u8 == b'E' || *format as u8 == b'O' { - // Ignore because these do nothing without locale format = format.offset(1); } match *format as u8 { + // Literal '%' b'%' => w!(byte b'%'), + + // Newline and tab expansions b'n' => w!(byte b'\n'), b't' => w!(byte b'\t'), - b'a' => w!(&WDAYS[(*t).tm_wday as usize][..3]), - b'A' => w!(WDAYS[(*t).tm_wday as usize]), - b'b' | b'h' => w!(&MONTHS[(*t).tm_mon as usize][..3]), - b'B' => w!(MONTHS[(*t).tm_mon as usize]), + + // Abbreviated weekday name: %a + b'a' => { + // `ABDAY_1 + tm_wday` is the correct langinfo ID for abbreviated weekdays + let s = langinfo_to_str(ABDAY_1 + (*t).tm_wday as i32); + w!(s); + } + + // Full weekday name: %A + b'A' => { + // `DAY_1 + tm_wday` is the correct langinfo ID for full weekdays + let s = langinfo_to_str(DAY_1 + (*t).tm_wday as i32); + w!(s); + } + + // Abbreviated month name: %b or %h + b'b' | b'h' => { + let s = langinfo_to_str(ABMON_1 + (*t).tm_mon as i32); + w!(s); + } + + // Full month name: %B + b'B' => { + let s = langinfo_to_str(MON_1 + (*t).tm_mon as i32); + w!(s); + } + + // Century: %C b'C' => { let mut year = (*t).tm_year / 100; - // Round up if (*t).tm_year % 100 != 0 { year += 1; } w!("{:02}", year + 19); } + + // Day of month: %d b'd' => w!("{:02}", (*t).tm_mday), + + // %D => same as %m/%d/%y b'D' => w!(recurse "%m/%d/%y"), + + // Day of month, space-padded: %e b'e' => w!("{:2}", (*t).tm_mday), + + // ISO 8601 date: %F => %Y-%m-%d b'F' => w!(recurse "%Y-%m-%d"), + + // Hour (00-23): %H b'H' => w!("{:02}", (*t).tm_hour), + + // Hour (01-12): %I b'I' => w!("{:02}", ((*t).tm_hour + 12 - 1) % 12 + 1), + + // Day of year: %j b'j' => w!("{:03}", (*t).tm_yday), + + // etc. b'k' => w!("{:2}", (*t).tm_hour), b'l' => w!("{:2}", ((*t).tm_hour + 12 - 1) % 12 + 1), b'm' => w!("{:02}", (*t).tm_mon + 1), b'M' => w!("{:02}", (*t).tm_min), - b'p' => w!(if (*t).tm_hour < 12 { "AM" } else { "PM" }), - b'P' => w!(if (*t).tm_hour < 12 { "am" } else { "pm" }), + + // AM/PM (uppercase): %p + b'p' => { + // Get "AM" / "PM" from langinfo + if (*t).tm_hour < 12 { + w!(langinfo_to_str(AM_STR)); + } else { + w!(langinfo_to_str(PM_STR)); + } + } + + // am/pm (lowercase): %P + b'P' => { + // Convert the AM_STR / PM_STR to lowercase + if (*t).tm_hour < 12 { + let am = langinfo_to_str(AM_STR).to_ascii_lowercase(); + w!(&am); + } else { + let pm = langinfo_to_str(PM_STR).to_ascii_lowercase(); + w!(&pm); + } + } + + // 12-hour clock with seconds + AM/PM: %r => %I:%M:%S %p b'r' => w!(recurse "%I:%M:%S %p"), + + // 24-hour clock without seconds: %R => %H:%M b'R' => w!(recurse "%H:%M"), - // Nothing is modified in mktime, but the C standard of course requires a mutable pointer ._. + + // Seconds since the Epoch: %s => calls mktime() to convert tm to time_t b's' => w!("{}", super::mktime(t as *mut tm)), + + // Seconds (00-60): %S (unchanged) b'S' => w!("{:02}", (*t).tm_sec), + + // 24-hour clock with seconds: %T => %H:%M:%S b'T' => w!(recurse "%H:%M:%S"), + + // Weekday (1-7, Monday=1): %u b'u' => w!("{}", ((*t).tm_wday + 7 - 1) % 7 + 1), + + // Sunday-based week of year: %U b'U' => w!("{}", ((*t).tm_yday + 7 - (*t).tm_wday) / 7), + + // Weekday (0-6, Sunday=0): %w b'w' => w!("{}", (*t).tm_wday), + + // Monday-based week of year: %W b'W' => w!("{}", ((*t).tm_yday + 7 - ((*t).tm_wday + 6) % 7) / 7), + + // Last two digits of year: %y b'y' => w!("{:02}", (*t).tm_year % 100), + + // Full year: %Y b'Y' => w!("{}", (*t).tm_year + 1900), - b'z' => w!("+0000"), // TODO - b'Z' => w!("UTC"), // TODO + + // Timezone offset: %z => currently hard-coded "+0000" + b'z' => w!("+0000"), // TODO: Add real timezone support + + // Timezone name: %Z => currently "UTC" + b'Z' => w!("UTC"), // TODO: Add real timezone name support + + // Date+time+TZ: %+ b'+' => w!(recurse "%a %b %d %T %Z %Y"), + + // Unrecognized format specifier => fail _ => return false, } + // Move past the format specifier format = format.offset(1); } true } - let mut w = platform::CountingWriter::new(w); - if !inner_strftime(&mut w, format, t) { + // Wrap the writer in a CountingWriter to return how many bytes were written. + let mut cw = platform::CountingWriter::new(w); + if !inner_strftime(&mut cw, format, t) { return 0; } - - w.written + cw.written } From ffd5edd283b69f5d5d856f598d26a465ed1f63d3 Mon Sep 17 00:00:00 2001 From: Guillaume Gielly Date: Sun, 5 Jan 2025 20:10:50 +0100 Subject: [PATCH 3/7] Add strptime() module and clean up unused strptime function --- src/header/time/mod.rs | 7 +- src/header/time/strptime.rs | 533 ++++++++++++++++++++++++++++++++++++ 2 files changed, 535 insertions(+), 5 deletions(-) create mode 100644 src/header/time/strptime.rs diff --git a/src/header/time/mod.rs b/src/header/time/mod.rs index 0abe45a680..5de0898acf 100644 --- a/src/header/time/mod.rs +++ b/src/header/time/mod.rs @@ -22,7 +22,9 @@ use core::{ pub use self::constants::*; pub mod constants; + mod strftime; +mod strptime; const YEARS_PER_ERA: time_t = 400; const DAYS_PER_ERA: time_t = 146097; @@ -465,11 +467,6 @@ pub unsafe extern "C" fn strftime( } } -// #[no_mangle] -pub extern "C" fn strptime(buf: *const c_char, format: *const c_char, tm: *mut tm) -> *mut c_char { - unimplemented!(); -} - #[no_mangle] pub unsafe extern "C" fn time(tloc: *mut time_t) -> time_t { let mut ts = timespec::default(); diff --git a/src/header/time/strptime.rs b/src/header/time/strptime.rs new file mode 100644 index 0000000000..5ffaf94b2e --- /dev/null +++ b/src/header/time/strptime.rs @@ -0,0 +1,533 @@ +// https://pubs.opengroup.org/onlinepubs/7908799/xsh/strptime.html + +use alloc::{string::String, vec::Vec}; +use core::{ + ffi::{c_char, c_int}, + mem::MaybeUninit, + ptr, slice, str, +}; + +use crate::{header::time::tm, platform::types::size_t}; + +/// For convenience, we define some helper constants for the C-locale. +static SHORT_DAYS: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +static LONG_DAYS: [&str; 7] = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +]; +static SHORT_MONTHS: [&str; 12] = [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", +]; +static LONG_MONTHS: [&str; 12] = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; + +// Macro for matching a character ignoring ASCII case +macro_rules! eq_icase { + ($c1:expr, $c2:expr) => { + $c1.eq_ignore_ascii_case(&$c2) + }; +} + +#[no_mangle] +pub extern "C" fn strptime(buf: *const c_char, format: *const c_char, tm: *mut tm) -> *mut c_char { + unsafe { + if buf.is_null() || format.is_null() || tm.is_null() { + return ptr::null_mut(); + } + + // Convert raw pointers into Rust slices/strings. + let mut input_str = cstr_to_str(buf); + let fmt_str = cstr_to_str(format); + + // Zero-initialize the output `tm` structure + // (equivalent to: tm_sec=0, tm_min=0, tm_hour=0, etc.) + ptr::write_bytes(tm, 0, 1); + + // We parse the format specifiers in a loop + let mut fmt_chars = fmt_str.chars().peekable(); + let mut index_in_input = 0; + + while let Some(fc) = fmt_chars.next() { + if fc != '%' { + // If it's a normal character, we expect it to match exactly in input + if input_str.len() <= index_in_input { + return ptr::null_mut(); // input ended too soon + } + let in_char = input_str.as_bytes()[index_in_input] as char; + if in_char != fc { + // mismatch + return ptr::null_mut(); + } + index_in_input += 1; + continue; + } + + // If we see '%', read the next character (format specifier) + let Some(spec) = fmt_chars.next() else { + // format string ended abruptly after '%' + return ptr::null_mut(); + }; + + // POSIX says `%E` or `%O` are "modified" specifiers for locale. + // We'll skip them if they appear (like strftime does) and read the next char. + let final_spec = if spec == 'E' || spec == 'O' { + match fmt_chars.next() { + Some(ch) => ch, + None => return ptr::null_mut(), + } + } else { + spec + }; + + // Handle known specifiers + match final_spec { + /////////////////////////// + // Whitespace: %n or %t // + /////////////////////////// + 'n' | 't' => { + // Skip over any whitespace in the input + while index_in_input < input_str.len() + && input_str.as_bytes()[index_in_input].is_ascii_whitespace() + { + index_in_input += 1; + } + } + + /////////////////////////// + // Literal % => "%%" // + /////////////////////////// + '%' => { + if index_in_input >= input_str.len() + || input_str.as_bytes()[index_in_input] as char != '%' + { + return ptr::null_mut(); + } + index_in_input += 1; + } + + /////////////////////////// + // Day of Month: %d / %e // + /////////////////////////// + 'd' | 'e' => { + // parse a 2-digit day (with or without leading zero) + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + (*tm).tm_mday = val as c_int; + index_in_input += len; + } + + /////////////////////////// + // Month: %m // + /////////////////////////// + 'm' => { + // parse a 2-digit month + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + // tm_mon is 0-based (0 = Jan, 1 = Feb,...) + (*tm).tm_mon = (val as c_int) - 1; + if (*tm).tm_mon < 0 || (*tm).tm_mon > 11 { + return ptr::null_mut(); + } + index_in_input += len; + } + + /////////////////////////// + // Year without century: %y + /////////////////////////// + 'y' => { + // parse a 2-digit year + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + // According to POSIX, %y in strptime is [00,99], and the "year" is 1900..1999 for [00..99], + // but the standard says: "values in [69..99] refer to 1969..1999, [00..68] => 2000..2068" + let fullyear = if val >= 69 { val + 1900 } else { val + 2000 }; + (*tm).tm_year = (fullyear - 1900) as c_int; + index_in_input += len; + } + + /////////////////////////// + // Year with century: %Y + /////////////////////////// + 'Y' => { + // parse up to 4-digit (or more) year + // We allow more than 4 digits if needed + let (val, len) = match parse_int(&input_str[index_in_input..], 4, true) { + Some(v) => v, + None => return ptr::null_mut(), + }; + (*tm).tm_year = (val as c_int) - 1900; + index_in_input += len; + } + + /////////////////////////// + // Hour (00..23): %H // + /////////////////////////// + 'H' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val > 23 { + return ptr::null_mut(); + } + (*tm).tm_hour = val as c_int; + index_in_input += len; + } + + /////////////////////////// + // Hour (01..12): %I // + /////////////////////////// + 'I' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val < 1 || val > 12 { + return ptr::null_mut(); + } + (*tm).tm_hour = val as c_int; + // We’ll interpret AM/PM with %p if it appears later + index_in_input += len; + } + + /////////////////////////// + // Minute (00..59): %M // + /////////////////////////// + 'M' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val > 59 { + return ptr::null_mut(); + } + (*tm).tm_min = val as c_int; + index_in_input += len; + } + + /////////////////////////// + // Seconds (00..60): %S // + /////////////////////////// + 'S' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val > 60 { + return ptr::null_mut(); + } + (*tm).tm_sec = val as c_int; + index_in_input += len; + } + + /////////////////////////// + // AM/PM: %p // + /////////////////////////// + 'p' => { + // Parse either "AM" or "PM" (case-insensitive) + // We'll read up to 2 or 3 letters from input ("AM", "PM") + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_am_pm(leftover) { + Some((is_pm, used)) => { + if (*tm).tm_hour == 12 { + // 12 AM => 00:xx, 12 PM => 12:xx + (*tm).tm_hour = if is_pm { 12 } else { 0 }; + } else { + // 1..11 AM => 1..11, 1..11 PM => 13..23 + if is_pm { + (*tm).tm_hour += 12; + } + } + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + + /////////////////////////// + // Weekday Name: %a/%A // + /////////////////////////// + 'a' => { + // Abbreviated day name (Sun..Sat) + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_weekday(leftover, true) { + Some((wday, used)) => { + (*tm).tm_wday = wday as c_int; + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + 'A' => { + // Full day name (Sunday..Saturday) + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_weekday(leftover, false) { + Some((wday, used)) => { + (*tm).tm_wday = wday as c_int; + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + + /////////////////////////// + // Month Name: %b/%B/%h // + /////////////////////////// + 'b' | 'h' => { + // Abbreviated month name + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_month(leftover, true) { + Some((mon, used)) => { + (*tm).tm_mon = mon as c_int; + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + 'B' => { + // Full month name + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_month(leftover, false) { + Some((mon, used)) => { + (*tm).tm_mon = mon as c_int; + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + + /////////////////////////// + // Day of year: %j // + /////////////////////////// + 'j' => { + // parse 3-digit day of year [001..366] + let (val, len) = match parse_int(&input_str[index_in_input..], 3, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val < 1 || val > 366 { + return ptr::null_mut(); + } + // store in tm_yday + (*tm).tm_yday = (val - 1) as c_int; + index_in_input += len; + } + + /////////////////////////// + // Date shortcuts: %D, %F, etc. + /////////////////////////// + 'D' => { + // Equivalent to "%m/%d/%y" + // We can do a mini strptime recursion or manually parse + // For simplicity, we'll do it inline here + let subfmt = "%m/%d/%y"; + let used = match apply_subformat(&input_str[index_in_input..], subfmt, tm) { + Some(v) => v, + None => return ptr::null_mut(), + }; + index_in_input += used; + } + 'F' => { + // Equivalent to "%Y-%m-%d" + let subfmt = "%Y-%m-%d"; + let used = match apply_subformat(&input_str[index_in_input..], subfmt, tm) { + Some(v) => v, + None => return ptr::null_mut(), + }; + index_in_input += used; + } + + /////////////////////////// + // Not implemented: %x, %X, %c, %r, %R, %T, etc. + /////////////////////////// + // If you want to implement these, do similarly to %D / %F or parse manually + 'x' | 'X' | 'c' | 'r' | 'R' | 'T' => { + // For brevity, we skip these. You can expand similarly. + // Return NULL if we don’t want to accept them: + return ptr::null_mut(); + } + + /////////////////////////// + // Timezone: %Z or %z + /////////////////////////// + 'Z' | 'z' => { + // Full/abbrev time zone name or numeric offset + // Implementation omitted. Real support is quite complicated. + return ptr::null_mut(); + } + + ////////// + // else // + ////////// + _ => { + // We do not recognize this specifier + return ptr::null_mut(); + } + } + } + + // If we got here, parsing was successful. Return pointer to the + // next unparsed character in `buf`. + let ret_ptr = buf.add(index_in_input); + ret_ptr as *mut c_char + } +} + +// ----------------------- +// Helper / Parsing Logic +// ----------------------- + +/// Convert a C char pointer to a Rust &str (assuming it's valid UTF-8). +/// Returns an empty string if invalid. +unsafe fn cstr_to_str<'a>(ptr: *const c_char) -> &'a str { + if ptr.is_null() { + return ""; + } + let len = strlen(ptr); + let bytes = slice::from_raw_parts(ptr as *const u8, len); + str::from_utf8(bytes).unwrap_or("") +} + +/// Minimal strlen for C-strings +unsafe fn strlen(mut ptr: *const c_char) -> usize { + let mut count = 0; + while !ptr.is_null() && *ptr != 0 { + ptr = ptr.add(1); + count += 1; + } + count +} + +/// Parse an integer from the beginning of `input_str`. +/// +/// - `width` is the maximum number of digits to parse +/// - `allow_variable_width` indicates if we can parse fewer digits +/// (e.g., `%Y` can have more than 4 digits, but also might parse "2023" or "12345"). +fn parse_int(input_str: &str, width: usize, allow_variable_width: bool) -> Option<(i32, usize)> { + let mut count = 0; + let mut value: i32 = 0; + let chars: Vec = input_str.chars().collect(); + + for c in chars.iter() { + if !c.is_ascii_digit() { + break; + } + value = value * 10 + (*c as u8 as i32 - '0' as i32); + count += 1; + if count == width && !allow_variable_width { + break; + } + } + if count == 0 { + return None; // no digits found + } + Some((value, count)) +} + +/// Handle AM/PM. Returns (is_pm, length_consumed). +/// Accepts "AM", "am", "PM", "pm" case-insensitively. +fn parse_am_pm(s: &str) -> Option<(bool, usize)> { + // Trim leading whitespace? + // For simplicity, we do not. If needed, handle it. + let s_up = s.to_ascii_uppercase(); + if s_up.starts_with("AM") { + return Some((false, 2)); + } + if s_up.starts_with("PM") { + return Some((true, 2)); + } + None +} + +/// Parse a weekday name from `s`. +/// - If `abbrev == true`, match short forms: "Sun".."Sat" +/// - Otherwise, match "Sunday".."Saturday" +/// Return (weekday_index, length_consumed). +fn parse_weekday(s: &str, abbrev: bool) -> Option<(usize, usize)> { + let list = if abbrev { &SHORT_DAYS } else { &LONG_DAYS }; + for (i, name) in list.iter().enumerate() { + if s.len() >= name.len() && s[0..name.len()].eq_ignore_ascii_case(name) { + return Some((i, name.len())); + } + } + None +} + +/// Parse a month name from `s`. +/// - If `abbrev == true`, match short forms: "Jan".."Dec" +/// - Otherwise, match "January".."December" +/// Return (month_index, length_consumed). +fn parse_month(s: &str, abbrev: bool) -> Option<(usize, usize)> { + let list = if abbrev { &SHORT_MONTHS } else { &LONG_MONTHS }; + for (i, name) in list.iter().enumerate() { + if s.len() >= name.len() && s[0..name.len()].eq_ignore_ascii_case(name) { + return Some((i, name.len())); + } + } + None +} + +/// Apply a small subformat (like "%m/%d/%y" or "%Y-%m-%d") to `input`. +/// Return how many characters of `input` were consumed or None on error. +unsafe fn apply_subformat(input: &str, subfmt: &str, tm: *mut tm) -> Option { + // We'll do a temporary strptime call on a substring. + // Then we see how many chars it consumed. + // If that call fails, we return None. + // Otherwise, we return the count. + + // Convert `input` to a null-terminated buffer temporarily + let mut tmpbuf = String::with_capacity(input.len() + 1); + tmpbuf.push_str(input); + tmpbuf.push('\0'); + + let mut tmpfmt = String::with_capacity(subfmt.len() + 1); + tmpfmt.push_str(subfmt); + tmpfmt.push('\0'); + + // We need a copy of the tm, so if partial parse fails, we don't override. + let old_tm = ptr::read(tm); // backup + + let consumed_ptr = strptime( + tmpbuf.as_ptr() as *const c_char, + tmpfmt.as_ptr() as *const c_char, + tm, + ); + + if consumed_ptr.is_null() { + // revert + *tm = old_tm; + return None; + } + + // consumed_ptr - tmpbuf.as_ptr() => # of bytes consumed + let diff = (consumed_ptr as usize) - (tmpbuf.as_ptr() as usize); + Some(diff) +} From cf106906b5351c106d05dbb6c7d8506c267a6179 Mon Sep 17 00:00:00 2001 From: Guillaume Gielly Date: Sun, 5 Jan 2025 22:30:35 +0100 Subject: [PATCH 4/7] Code cleanup --- .vscode/settings.json | 3 + src/header/time/strptime.rs | 735 ++++++++++++++++++------------------ 2 files changed, 377 insertions(+), 361 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..9ddf6b280c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.ignoreCMakeListsMissing": true +} \ No newline at end of file diff --git a/src/header/time/strptime.rs b/src/header/time/strptime.rs index 5ffaf94b2e..aa3972c381 100644 --- a/src/header/time/strptime.rs +++ b/src/header/time/strptime.rs @@ -1,14 +1,18 @@ // https://pubs.opengroup.org/onlinepubs/7908799/xsh/strptime.html +use crate::{ + header::{string::strlen, time::tm}, + platform::types::size_t, +}; use alloc::{string::String, vec::Vec}; use core::{ - ffi::{c_char, c_int}, + ffi::{c_char, c_int, c_void, CStr}, mem::MaybeUninit, - ptr, slice, str, + ptr, + ptr::NonNull, + slice, str, }; -use crate::{header::time::tm, platform::types::size_t}; - /// For convenience, we define some helper constants for the C-locale. static SHORT_DAYS: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; static LONG_DAYS: [&str; 7] = [ @@ -38,418 +42,428 @@ static LONG_MONTHS: [&str; 12] = [ "December", ]; -// Macro for matching a character ignoring ASCII case -macro_rules! eq_icase { - ($c1:expr, $c2:expr) => { - $c1.eq_ignore_ascii_case(&$c2) - }; -} - #[no_mangle] -pub extern "C" fn strptime(buf: *const c_char, format: *const c_char, tm: *mut tm) -> *mut c_char { - unsafe { - if buf.is_null() || format.is_null() || tm.is_null() { +pub unsafe extern "C" fn strptime( + buf: *const c_char, + format: *const c_char, + tm: *mut tm, +) -> *mut c_char { + // Validate inputs + let buf_ptr = if let Some(ptr) = NonNull::new(buf as *const c_void as *mut c_void) { + ptr + } else { + return ptr::null_mut(); + }; + // + let fmt_ptr = if let Some(ptr) = NonNull::new(format as *const c_void as *mut c_void) { + ptr + } else { + return ptr::null_mut(); + }; + + let tm_ptr = if let Some(ptr) = NonNull::new(tm) { + ptr + } else { + return ptr::null_mut(); + }; + + // Convert raw pointers into slices/strings. + let input_str = unsafe { + if buf.is_null() { return ptr::null_mut(); } + match CStr::from_ptr(buf).to_str() { + Ok(s) => s, + Err(_) => return ptr::null_mut(), // Not a valid UTF-8 + } + }; - // Convert raw pointers into Rust slices/strings. - let mut input_str = cstr_to_str(buf); - let fmt_str = cstr_to_str(format); + let fmt_str = unsafe { + if format.is_null() { + return ptr::null_mut(); + } + match CStr::from_ptr(format).to_str() { + Ok(s) => s, + Err(_) => return ptr::null_mut(), // Not a valid UTF-8 + } + }; - // Zero-initialize the output `tm` structure - // (equivalent to: tm_sec=0, tm_min=0, tm_hour=0, etc.) - ptr::write_bytes(tm, 0, 1); + // Zero-initialize the output `tm` structure + // (equivalent to: tm_sec=0, tm_min=0, tm_hour=0...) + ptr::write_bytes(tm, 0, 1); - // We parse the format specifiers in a loop - let mut fmt_chars = fmt_str.chars().peekable(); - let mut index_in_input = 0; + // We parse the format specifiers in a loop + let mut fmt_chars = fmt_str.chars().peekable(); + let mut index_in_input = 0; - while let Some(fc) = fmt_chars.next() { - if fc != '%' { - // If it's a normal character, we expect it to match exactly in input - if input_str.len() <= index_in_input { - return ptr::null_mut(); // input ended too soon + while let Some(fc) = fmt_chars.next() { + if fc != '%' { + // If it's a normal character, we expect it to match exactly in input + if input_str.len() <= index_in_input { + return ptr::null_mut(); // input ended too soon + } + let in_char = input_str.as_bytes()[index_in_input] as char; + if in_char != fc { + // mismatch + return ptr::null_mut(); + } + index_in_input += 1; + continue; + } + + // If we see '%', read the next character + let Some(spec) = fmt_chars.next() else { + // format string ended abruptly after '%' + return ptr::null_mut(); + }; + + // POSIX says `%E` or `%O` are modified specifiers for locale. + // We will skip them if they appear (like strftime does) and read the next char. + let final_spec = if spec == 'E' || spec == 'O' { + match fmt_chars.next() { + Some(ch) => ch, + None => return ptr::null_mut(), + } + } else { + spec + }; + + // Handle known specifiers + match final_spec { + /////////////////////////// + // Whitespace: %n or %t // + /////////////////////////// + 'n' | 't' => { + // Skip over any whitespace in the input + while index_in_input < input_str.len() + && input_str.as_bytes()[index_in_input].is_ascii_whitespace() + { + index_in_input += 1; } - let in_char = input_str.as_bytes()[index_in_input] as char; - if in_char != fc { - // mismatch + } + + /////////////////////////// + // Literal % => "%%" // + /////////////////////////// + '%' => { + if index_in_input >= input_str.len() + || input_str.as_bytes()[index_in_input] as char != '%' + { return ptr::null_mut(); } index_in_input += 1; - continue; } - // If we see '%', read the next character (format specifier) - let Some(spec) = fmt_chars.next() else { - // format string ended abruptly after '%' - return ptr::null_mut(); - }; - - // POSIX says `%E` or `%O` are "modified" specifiers for locale. - // We'll skip them if they appear (like strftime does) and read the next char. - let final_spec = if spec == 'E' || spec == 'O' { - match fmt_chars.next() { - Some(ch) => ch, + /////////////////////////// + // Day of Month: %d / %e // + /////////////////////////// + 'd' | 'e' => { + // parse a 2-digit day (with or without leading zero) + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, None => return ptr::null_mut(), - } - } else { - spec - }; + }; + (*tm).tm_mday = val as c_int; + index_in_input += len; + } - // Handle known specifiers - match final_spec { - /////////////////////////// - // Whitespace: %n or %t // - /////////////////////////// - 'n' | 't' => { - // Skip over any whitespace in the input - while index_in_input < input_str.len() - && input_str.as_bytes()[index_in_input].is_ascii_whitespace() - { - index_in_input += 1; - } + /////////////////////////// + // Month: %m // + /////////////////////////// + 'm' => { + // parse a 2-digit month + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + // tm_mon is 0-based (0 = Jan, 1 = Feb,...) + (*tm).tm_mon = (val as c_int) - 1; + if (*tm).tm_mon < 0 || (*tm).tm_mon > 11 { + return ptr::null_mut(); } + index_in_input += len; + } - /////////////////////////// - // Literal % => "%%" // - /////////////////////////// - '%' => { - if index_in_input >= input_str.len() - || input_str.as_bytes()[index_in_input] as char != '%' - { - return ptr::null_mut(); - } - index_in_input += 1; + ////////////////////////////// + // Year without century: %y // + ////////////////////////////// + 'y' => { + // parse a 2-digit year + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + // According to POSIX, %y in strptime is [00,99], and the "year" is 1900..1999 for [00..99], + // but the standard says: "values in [69..99] refer to 1969..1999, [00..68] => 2000..2068" + let fullyear = if val >= 69 { val + 1900 } else { val + 2000 }; + (*tm).tm_year = (fullyear - 1900) as c_int; + index_in_input += len; + } + + /////////////////////////// + // Year with century: %Y // + /////////////////////////// + 'Y' => { + // parse up to 4-digit (or more) year + // We allow more than 4 digits if needed + let (val, len) = match parse_int(&input_str[index_in_input..], 4, true) { + Some(v) => v, + None => return ptr::null_mut(), + }; + (*tm).tm_year = (val as c_int) - 1900; + index_in_input += len; + } + + /////////////////////////// + // Hour (00..23): %H // + /////////////////////////// + 'H' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val > 23 { + return ptr::null_mut(); } + (*tm).tm_hour = val as c_int; + index_in_input += len; + } - /////////////////////////// - // Day of Month: %d / %e // - /////////////////////////// - 'd' | 'e' => { - // parse a 2-digit day (with or without leading zero) - let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { - Some(v) => v, - None => return ptr::null_mut(), - }; - (*tm).tm_mday = val as c_int; - index_in_input += len; + /////////////////////////// + // Hour (01..12): %I // + /////////////////////////// + 'I' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val < 1 || val > 12 { + return ptr::null_mut(); } + (*tm).tm_hour = val as c_int; + // We’ll interpret AM/PM with %p if it appears later + index_in_input += len; + } - /////////////////////////// - // Month: %m // - /////////////////////////// - 'm' => { - // parse a 2-digit month - let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { - Some(v) => v, - None => return ptr::null_mut(), - }; - // tm_mon is 0-based (0 = Jan, 1 = Feb,...) - (*tm).tm_mon = (val as c_int) - 1; - if (*tm).tm_mon < 0 || (*tm).tm_mon > 11 { - return ptr::null_mut(); - } - index_in_input += len; + /////////////////////////// + // Minute (00..59): %M // + /////////////////////////// + 'M' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val > 59 { + return ptr::null_mut(); } + (*tm).tm_min = val as c_int; + index_in_input += len; + } - /////////////////////////// - // Year without century: %y - /////////////////////////// - 'y' => { - // parse a 2-digit year - let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { - Some(v) => v, - None => return ptr::null_mut(), - }; - // According to POSIX, %y in strptime is [00,99], and the "year" is 1900..1999 for [00..99], - // but the standard says: "values in [69..99] refer to 1969..1999, [00..68] => 2000..2068" - let fullyear = if val >= 69 { val + 1900 } else { val + 2000 }; - (*tm).tm_year = (fullyear - 1900) as c_int; - index_in_input += len; + /////////////////////////// + // Seconds (00..60): %S // + /////////////////////////// + 'S' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val > 60 { + return ptr::null_mut(); } + (*tm).tm_sec = val as c_int; + index_in_input += len; + } - /////////////////////////// - // Year with century: %Y - /////////////////////////// - 'Y' => { - // parse up to 4-digit (or more) year - // We allow more than 4 digits if needed - let (val, len) = match parse_int(&input_str[index_in_input..], 4, true) { - Some(v) => v, - None => return ptr::null_mut(), - }; - (*tm).tm_year = (val as c_int) - 1900; - index_in_input += len; - } - - /////////////////////////// - // Hour (00..23): %H // - /////////////////////////// - 'H' => { - let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { - Some(v) => v, - None => return ptr::null_mut(), - }; - if val > 23 { - return ptr::null_mut(); - } - (*tm).tm_hour = val as c_int; - index_in_input += len; - } - - /////////////////////////// - // Hour (01..12): %I // - /////////////////////////// - 'I' => { - let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { - Some(v) => v, - None => return ptr::null_mut(), - }; - if val < 1 || val > 12 { - return ptr::null_mut(); - } - (*tm).tm_hour = val as c_int; - // We’ll interpret AM/PM with %p if it appears later - index_in_input += len; - } - - /////////////////////////// - // Minute (00..59): %M // - /////////////////////////// - 'M' => { - let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { - Some(v) => v, - None => return ptr::null_mut(), - }; - if val > 59 { - return ptr::null_mut(); - } - (*tm).tm_min = val as c_int; - index_in_input += len; - } - - /////////////////////////// - // Seconds (00..60): %S // - /////////////////////////// - 'S' => { - let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { - Some(v) => v, - None => return ptr::null_mut(), - }; - if val > 60 { - return ptr::null_mut(); - } - (*tm).tm_sec = val as c_int; - index_in_input += len; - } - - /////////////////////////// - // AM/PM: %p // - /////////////////////////// - 'p' => { - // Parse either "AM" or "PM" (case-insensitive) - // We'll read up to 2 or 3 letters from input ("AM", "PM") - let leftover = &input_str[index_in_input..]; - let parsed_len = match parse_am_pm(leftover) { - Some((is_pm, used)) => { - if (*tm).tm_hour == 12 { - // 12 AM => 00:xx, 12 PM => 12:xx - (*tm).tm_hour = if is_pm { 12 } else { 0 }; - } else { - // 1..11 AM => 1..11, 1..11 PM => 13..23 - if is_pm { - (*tm).tm_hour += 12; - } + /////////////////////////// + // AM/PM: %p // + /////////////////////////// + 'p' => { + // Parse either "AM" or "PM" (no case-sensitive) + // We'll read up to 2 or 3 letters from input ("AM", "PM") + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_am_pm(leftover) { + Some((is_pm, used)) => { + if (*tm).tm_hour == 12 { + // 12 AM => 00:xx, 12 PM => 12:xx + (*tm).tm_hour = if is_pm { 12 } else { 0 }; + } else { + // 1..11 AM => 1..11, 1..11 PM => 13..23 + if is_pm { + (*tm).tm_hour += 12; } - used } - None => return ptr::null_mut(), - }; - index_in_input += parsed_len; - } - - /////////////////////////// - // Weekday Name: %a/%A // - /////////////////////////// - 'a' => { - // Abbreviated day name (Sun..Sat) - let leftover = &input_str[index_in_input..]; - let parsed_len = match parse_weekday(leftover, true) { - Some((wday, used)) => { - (*tm).tm_wday = wday as c_int; - used - } - None => return ptr::null_mut(), - }; - index_in_input += parsed_len; - } - 'A' => { - // Full day name (Sunday..Saturday) - let leftover = &input_str[index_in_input..]; - let parsed_len = match parse_weekday(leftover, false) { - Some((wday, used)) => { - (*tm).tm_wday = wday as c_int; - used - } - None => return ptr::null_mut(), - }; - index_in_input += parsed_len; - } - - /////////////////////////// - // Month Name: %b/%B/%h // - /////////////////////////// - 'b' | 'h' => { - // Abbreviated month name - let leftover = &input_str[index_in_input..]; - let parsed_len = match parse_month(leftover, true) { - Some((mon, used)) => { - (*tm).tm_mon = mon as c_int; - used - } - None => return ptr::null_mut(), - }; - index_in_input += parsed_len; - } - 'B' => { - // Full month name - let leftover = &input_str[index_in_input..]; - let parsed_len = match parse_month(leftover, false) { - Some((mon, used)) => { - (*tm).tm_mon = mon as c_int; - used - } - None => return ptr::null_mut(), - }; - index_in_input += parsed_len; - } - - /////////////////////////// - // Day of year: %j // - /////////////////////////// - 'j' => { - // parse 3-digit day of year [001..366] - let (val, len) = match parse_int(&input_str[index_in_input..], 3, false) { - Some(v) => v, - None => return ptr::null_mut(), - }; - if val < 1 || val > 366 { - return ptr::null_mut(); + used } - // store in tm_yday - (*tm).tm_yday = (val - 1) as c_int; - index_in_input += len; - } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } - /////////////////////////// - // Date shortcuts: %D, %F, etc. - /////////////////////////// - 'D' => { - // Equivalent to "%m/%d/%y" - // We can do a mini strptime recursion or manually parse - // For simplicity, we'll do it inline here - let subfmt = "%m/%d/%y"; - let used = match apply_subformat(&input_str[index_in_input..], subfmt, tm) { - Some(v) => v, - None => return ptr::null_mut(), - }; - index_in_input += used; - } - 'F' => { - // Equivalent to "%Y-%m-%d" - let subfmt = "%Y-%m-%d"; - let used = match apply_subformat(&input_str[index_in_input..], subfmt, tm) { - Some(v) => v, - None => return ptr::null_mut(), - }; - index_in_input += used; - } + /////////////////////////// + // Weekday Name: %a/%A // + /////////////////////////// + 'a' => { + // Abbreviated day name (Sun..Sat) + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_weekday(leftover, true) { + Some((wday, used)) => { + (*tm).tm_wday = wday as c_int; + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + 'A' => { + // Full day name (Sunday..Saturday) + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_weekday(leftover, false) { + Some((wday, used)) => { + (*tm).tm_wday = wday as c_int; + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } - /////////////////////////// - // Not implemented: %x, %X, %c, %r, %R, %T, etc. - /////////////////////////// - // If you want to implement these, do similarly to %D / %F or parse manually - 'x' | 'X' | 'c' | 'r' | 'R' | 'T' => { - // For brevity, we skip these. You can expand similarly. - // Return NULL if we don’t want to accept them: + /////////////////////////// + // Month Name: %b/%B/%h // + /////////////////////////// + 'b' | 'h' => { + // Abbreviated month name + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_month(leftover, true) { + Some((mon, used)) => { + (*tm).tm_mon = mon as c_int; + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + 'B' => { + // Full month name + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_month(leftover, false) { + Some((mon, used)) => { + (*tm).tm_mon = mon as c_int; + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + + /////////////////////////// + // Day of year: %j // + /////////////////////////// + 'j' => { + // parse 3-digit day of year [001..366] + let (val, len) = match parse_int(&input_str[index_in_input..], 3, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val < 1 || val > 366 { return ptr::null_mut(); } + // store in tm_yday + (*tm).tm_yday = (val - 1) as c_int; + index_in_input += len; + } - /////////////////////////// - // Timezone: %Z or %z - /////////////////////////// - 'Z' | 'z' => { - // Full/abbrev time zone name or numeric offset - // Implementation omitted. Real support is quite complicated. - return ptr::null_mut(); - } + ////////////////////////////////// + // Date shortcuts: %D, %F, etc. // + ////////////////////////////////// + 'D' => { + // Equivalent to "%m/%d/%y" + // We can do a mini strptime recursion or manually parse + // For simplicity, we'll do it inline here + let subfmt = "%m/%d/%y"; + let used = match apply_subformat(&input_str[index_in_input..], subfmt, tm) { + Some(v) => v, + None => return ptr::null_mut(), + }; + index_in_input += used; + } + 'F' => { + // Equivalent to "%Y-%m-%d" + let subfmt = "%Y-%m-%d"; + let used = match apply_subformat(&input_str[index_in_input..], subfmt, tm) { + Some(v) => v, + None => return ptr::null_mut(), + }; + index_in_input += used; + } - ////////// - // else // - ////////// - _ => { - // We do not recognize this specifier - return ptr::null_mut(); - } + ////////////////////////////////////////////////////////// + // TODO : not implemented: %x, %X, %c, %r, %R, %T, etc. // + ////////////////////////////////////////////////////////// + // Hint : if you want to implement these, do similarly to %D / %F (or parse manually) + 'x' | 'X' | 'c' | 'r' | 'R' | 'T' => { + // Return NULL if we don’t want to accept them : + return ptr::null_mut(); + } + + /////////////////////////// + // Timezone: %Z or %z // + /////////////////////////// + 'Z' | 'z' => { + // Full/abbrev time zone name or numeric offset + // Implementation omitted. Real support is quite complicated. + return ptr::null_mut(); + } + + ////////// + // else // + ////////// + _ => { + // We do not recognize this specifier + return ptr::null_mut(); } } - - // If we got here, parsing was successful. Return pointer to the - // next unparsed character in `buf`. - let ret_ptr = buf.add(index_in_input); - ret_ptr as *mut c_char } + + // If we got here, parsing was successful. Return pointer to the + // next unparsed character in `buf`. + let ret_ptr = buf.add(index_in_input); + ret_ptr as *mut c_char } // ----------------------- // Helper / Parsing Logic // ----------------------- -/// Convert a C char pointer to a Rust &str (assuming it's valid UTF-8). -/// Returns an empty string if invalid. -unsafe fn cstr_to_str<'a>(ptr: *const c_char) -> &'a str { - if ptr.is_null() { - return ""; - } - let len = strlen(ptr); - let bytes = slice::from_raw_parts(ptr as *const u8, len); - str::from_utf8(bytes).unwrap_or("") -} - -/// Minimal strlen for C-strings -unsafe fn strlen(mut ptr: *const c_char) -> usize { - let mut count = 0; - while !ptr.is_null() && *ptr != 0 { - ptr = ptr.add(1); - count += 1; - } - count -} - /// Parse an integer from the beginning of `input_str`. /// /// - `width` is the maximum number of digits to parse /// - `allow_variable_width` indicates if we can parse fewer digits /// (e.g., `%Y` can have more than 4 digits, but also might parse "2023" or "12345"). -fn parse_int(input_str: &str, width: usize, allow_variable_width: bool) -> Option<(i32, usize)> { +fn parse_int(input: &str, width: usize, allow_variable: bool) -> Option<(i32, usize)> { + let mut val = 0i32; + let mut chars = input.chars(); let mut count = 0; - let mut value: i32 = 0; - let chars: Vec = input_str.chars().collect(); - for c in chars.iter() { + while let Some(c) = chars.next() { if !c.is_ascii_digit() { break; } - value = value * 10 + (*c as u8 as i32 - '0' as i32); + + // Check for integer overflow + val = val.checked_mul(10)?.checked_add((c as u8 - b'0') as i32)?; + count += 1; - if count == width && !allow_variable_width { + if count == width && !allow_variable { break; } } + if count == 0 { - return None; // no digits found + None + } else { + Some((val, count)) } - Some((value, count)) } /// Handle AM/PM. Returns (is_pm, length_consumed). @@ -468,8 +482,8 @@ fn parse_am_pm(s: &str) -> Option<(bool, usize)> { } /// Parse a weekday name from `s`. -/// - If `abbrev == true`, match short forms: "Sun".."Sat" -/// - Otherwise, match "Sunday".."Saturday" +/// - if `abbrev == true`, match short forms: "Mont".."Sun" +/// - otherwise, match "Monday".."Sunday" /// Return (weekday_index, length_consumed). fn parse_weekday(s: &str, abbrev: bool) -> Option<(usize, usize)> { let list = if abbrev { &SHORT_DAYS } else { &LONG_DAYS }; @@ -499,8 +513,7 @@ fn parse_month(s: &str, abbrev: bool) -> Option<(usize, usize)> { /// Return how many characters of `input` were consumed or None on error. unsafe fn apply_subformat(input: &str, subfmt: &str, tm: *mut tm) -> Option { // We'll do a temporary strptime call on a substring. - // Then we see how many chars it consumed. - // If that call fails, we return None. + // Then we see how many chars it consumed. If that call fails, we return None. // Otherwise, we return the count. // Convert `input` to a null-terminated buffer temporarily From b5b5bd2522912409ad8f12b72fc58d5f33996373 Mon Sep 17 00:00:00 2001 From: Guillaume Gielly Date: Sun, 5 Jan 2025 23:02:04 +0100 Subject: [PATCH 5/7] Support building with #![deny(unsafe_op_in_unsafe_fn)] --- src/header/time/strptime.rs | 110 ++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 36 deletions(-) diff --git a/src/header/time/strptime.rs b/src/header/time/strptime.rs index aa3972c381..2e04a19c00 100644 --- a/src/header/time/strptime.rs +++ b/src/header/time/strptime.rs @@ -90,7 +90,9 @@ pub unsafe extern "C" fn strptime( // Zero-initialize the output `tm` structure // (equivalent to: tm_sec=0, tm_min=0, tm_hour=0...) - ptr::write_bytes(tm, 0, 1); + unsafe { + ptr::write_bytes(tm, 0, 1); + } // We parse the format specifiers in a loop let mut fmt_chars = fmt_str.chars().peekable(); @@ -160,10 +162,12 @@ pub unsafe extern "C" fn strptime( 'd' | 'e' => { // parse a 2-digit day (with or without leading zero) let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { - Some(v) => v, + Some(v) => unsafe { v }, None => return ptr::null_mut(), }; - (*tm).tm_mday = val as c_int; + unsafe { + (*tm).tm_mday = val as c_int; + } index_in_input += len; } @@ -177,9 +181,11 @@ pub unsafe extern "C" fn strptime( None => return ptr::null_mut(), }; // tm_mon is 0-based (0 = Jan, 1 = Feb,...) - (*tm).tm_mon = (val as c_int) - 1; - if (*tm).tm_mon < 0 || (*tm).tm_mon > 11 { - return ptr::null_mut(); + unsafe { + (*tm).tm_mon = (val as c_int) - 1; + if (*tm).tm_mon < 0 || (*tm).tm_mon > 11 { + return ptr::null_mut(); + } } index_in_input += len; } @@ -196,7 +202,9 @@ pub unsafe extern "C" fn strptime( // According to POSIX, %y in strptime is [00,99], and the "year" is 1900..1999 for [00..99], // but the standard says: "values in [69..99] refer to 1969..1999, [00..68] => 2000..2068" let fullyear = if val >= 69 { val + 1900 } else { val + 2000 }; - (*tm).tm_year = (fullyear - 1900) as c_int; + unsafe { + (*tm).tm_year = (fullyear - 1900) as c_int; + } index_in_input += len; } @@ -210,7 +218,9 @@ pub unsafe extern "C" fn strptime( Some(v) => v, None => return ptr::null_mut(), }; - (*tm).tm_year = (val as c_int) - 1900; + unsafe { + (*tm).tm_year = (val as c_int) - 1900; + } index_in_input += len; } @@ -225,7 +235,9 @@ pub unsafe extern "C" fn strptime( if val > 23 { return ptr::null_mut(); } - (*tm).tm_hour = val as c_int; + unsafe { + (*tm).tm_hour = val as c_int; + } index_in_input += len; } @@ -240,7 +252,9 @@ pub unsafe extern "C" fn strptime( if val < 1 || val > 12 { return ptr::null_mut(); } - (*tm).tm_hour = val as c_int; + unsafe { + (*tm).tm_hour = val as c_int; + } // We’ll interpret AM/PM with %p if it appears later index_in_input += len; } @@ -256,7 +270,9 @@ pub unsafe extern "C" fn strptime( if val > 59 { return ptr::null_mut(); } - (*tm).tm_min = val as c_int; + unsafe { + (*tm).tm_min = val as c_int; + } index_in_input += len; } @@ -271,7 +287,9 @@ pub unsafe extern "C" fn strptime( if val > 60 { return ptr::null_mut(); } - (*tm).tm_sec = val as c_int; + unsafe { + (*tm).tm_sec = val as c_int; + } index_in_input += len; } @@ -284,13 +302,17 @@ pub unsafe extern "C" fn strptime( let leftover = &input_str[index_in_input..]; let parsed_len = match parse_am_pm(leftover) { Some((is_pm, used)) => { - if (*tm).tm_hour == 12 { + if unsafe { (*tm).tm_hour } == 12 { // 12 AM => 00:xx, 12 PM => 12:xx - (*tm).tm_hour = if is_pm { 12 } else { 0 }; + unsafe { + (*tm).tm_hour = if is_pm { 12 } else { 0 }; + } } else { // 1..11 AM => 1..11, 1..11 PM => 13..23 if is_pm { - (*tm).tm_hour += 12; + unsafe { + (*tm).tm_hour += 12; + } } } used @@ -308,7 +330,9 @@ pub unsafe extern "C" fn strptime( let leftover = &input_str[index_in_input..]; let parsed_len = match parse_weekday(leftover, true) { Some((wday, used)) => { - (*tm).tm_wday = wday as c_int; + unsafe { + (*tm).tm_wday = wday as c_int; + } used } None => return ptr::null_mut(), @@ -320,7 +344,9 @@ pub unsafe extern "C" fn strptime( let leftover = &input_str[index_in_input..]; let parsed_len = match parse_weekday(leftover, false) { Some((wday, used)) => { - (*tm).tm_wday = wday as c_int; + unsafe { + (*tm).tm_wday = wday as c_int; + } used } None => return ptr::null_mut(), @@ -336,7 +362,9 @@ pub unsafe extern "C" fn strptime( let leftover = &input_str[index_in_input..]; let parsed_len = match parse_month(leftover, true) { Some((mon, used)) => { - (*tm).tm_mon = mon as c_int; + unsafe { + (*tm).tm_mon = mon as c_int; + } used } None => return ptr::null_mut(), @@ -348,7 +376,9 @@ pub unsafe extern "C" fn strptime( let leftover = &input_str[index_in_input..]; let parsed_len = match parse_month(leftover, false) { Some((mon, used)) => { - (*tm).tm_mon = mon as c_int; + unsafe { + (*tm).tm_mon = mon as c_int; + } used } None => return ptr::null_mut(), @@ -369,7 +399,9 @@ pub unsafe extern "C" fn strptime( return ptr::null_mut(); } // store in tm_yday - (*tm).tm_yday = (val - 1) as c_int; + unsafe { + (*tm).tm_yday = (val - 1) as c_int; + } index_in_input += len; } @@ -381,19 +413,21 @@ pub unsafe extern "C" fn strptime( // We can do a mini strptime recursion or manually parse // For simplicity, we'll do it inline here let subfmt = "%m/%d/%y"; - let used = match apply_subformat(&input_str[index_in_input..], subfmt, tm) { - Some(v) => v, - None => return ptr::null_mut(), - }; + let used = + match unsafe { apply_subformat(&input_str[index_in_input..], subfmt, tm) } { + Some(v) => v, + None => return ptr::null_mut(), + }; index_in_input += used; } 'F' => { // Equivalent to "%Y-%m-%d" let subfmt = "%Y-%m-%d"; - let used = match apply_subformat(&input_str[index_in_input..], subfmt, tm) { - Some(v) => v, - None => return ptr::null_mut(), - }; + let used = + match unsafe { apply_subformat(&input_str[index_in_input..], subfmt, tm) } { + Some(v) => v, + None => return ptr::null_mut(), + }; index_in_input += used; } @@ -427,7 +461,7 @@ pub unsafe extern "C" fn strptime( // If we got here, parsing was successful. Return pointer to the // next unparsed character in `buf`. - let ret_ptr = buf.add(index_in_input); + let ret_ptr = unsafe { buf.add(index_in_input) }; ret_ptr as *mut c_char } @@ -526,17 +560,21 @@ unsafe fn apply_subformat(input: &str, subfmt: &str, tm: *mut tm) -> Option Date: Tue, 7 Jan 2025 13:47:05 +0100 Subject: [PATCH 6/7] Refactor langinfo module to use a static string table for constants --- src/header/langinfo/mod.rs | 217 ++++++++++++------------------------- 1 file changed, 70 insertions(+), 147 deletions(-) diff --git a/src/header/langinfo/mod.rs b/src/header/langinfo/mod.rs index f32f8b26f4..f8415b4f49 100644 --- a/src/header/langinfo/mod.rs +++ b/src/header/langinfo/mod.rs @@ -5,160 +5,83 @@ use core::ffi::c_char; -/// TODO : the POSIX type for items used with `nl_langinfo` (not implemented), which is defined in +/// POSIX type for items used with `nl_langinfo` +/// In practice, this is an integer index into the string table. pub type nl_item = i32; -// Constant definitions -const ITEMS: &[(nl_item, &[u8])] = &[ - (CODESET, b"UTF-8\0"), - (D_T_FMT, b"%a %b %e %H:%M:%S %Y\0"), - (D_FMT, b"%m/%d/%y\0"), - (T_FMT, b"%H:%M:%S\0"), - (T_FMT_AMPM, b"%I:%M:%S %p\0"), - (AM_STR, b"AM\0"), - (PM_STR, b"PM\0"), - (DAY_1, b"Sunday\0"), - (DAY_2, b"Monday\0"), - (DAY_3, b"Tuesday\0"), - (DAY_4, b"Wednesday\0"), - (DAY_5, b"Thursday\0"), - (DAY_6, b"Friday\0"), - (DAY_7, b"Saturday\0"), - (ABDAY_1, b"Sun\0"), - (ABDAY_2, b"Mon\0"), - (ABDAY_3, b"Tue\0"), - (ABDAY_4, b"Wed\0"), - (ABDAY_5, b"Thu\0"), - (ABDAY_6, b"Fri\0"), - (ABDAY_7, b"Sat\0"), - (MON_1, b"January\0"), - (MON_2, b"February\0"), - (MON_3, b"March\0"), - (MON_4, b"April\0"), - (MON_5, b"May\0"), - (MON_6, b"June\0"), - (MON_7, b"July\0"), - (MON_8, b"August\0"), - (MON_9, b"September\0"), - (MON_10, b"October\0"), - (MON_11, b"November\0"), - (MON_12, b"December\0"), - (ABMON_1, b"Jan\0"), - (ABMON_2, b"Feb\0"), - (ABMON_3, b"Mar\0"), - (ABMON_4, b"Apr\0"), - (ABMON_5, b"May\0"), - (ABMON_6, b"Jun\0"), - (ABMON_7, b"Jul\0"), - (ABMON_8, b"Aug\0"), - (ABMON_9, b"Sep\0"), - (ABMON_10, b"Oct\0"), - (ABMON_11, b"Nov\0"), - (ABMON_12, b"Dec\0"), - (ERA, b"\0"), - (ERA_D_FMT, b"\0"), - (ERA_D_T_FMT, b"\0"), - (ERA_T_FMT, b"\0"), - (ALT_DIGITS, b"\0"), - (RADIXCHAR, b".\0"), - (THOUSEP, b"\0"), - (CRNCYSTR, b".\0"), - (YESEXPR, b"^[yY]\0"), - (NOEXPR, b"^[nN]\0"), - (YESSTR, b"yes\0"), - (NOSTR, b"no\0"), +// Static string table for langinfo constants +static STRING_TABLE: [&[u8]; 57] = [ + b"UTF-8\0", // CODESET + b"%a %b %e %H:%M:%S %Y\0", // D_T_FMT + b"%m/%d/%y\0", // D_FMT + b"%H:%M:%S\0", // T_FMT + b"%I:%M:%S %p\0", // T_FMT_AMPM + b"AM\0", // AM_STR + b"PM\0", // PM_STR + b"Sunday\0", // DAY_1 + b"Monday\0", // DAY_2 + b"Tuesday\0", // DAY_3 + b"Wednesday\0", // DAY_4 + b"Thursday\0", // DAY_5 + b"Friday\0", // DAY_6 + b"Saturday\0", // DAY_7 + b"Sun\0", // ABDAY_1 + b"Mon\0", // ABDAY_2 + b"Tue\0", // ABDAY_3 + b"Wed\0", // ABDAY_4 + b"Thu\0", // ABDAY_5 + b"Fri\0", // ABDAY_6 + b"Sat\0", // ABDAY_7 + b"January\0", // MON_1 + b"February\0", // MON_2 + b"March\0", // MON_3 + b"April\0", // MON_4 + b"May\0", // MON_5 + b"June\0", // MON_6 + b"July\0", // MON_7 + b"August\0", // MON_8 + b"September\0", // MON_9 + b"October\0", // MON_10 + b"November\0", // MON_11 + b"December\0", // MON_12 + b"Jan\0", // ABMON_1 + b"Feb\0", // ABMON_2 + b"Mar\0", // ABMON_3 + b"Apr\0", // ABMON_4 + b"May\0", // ABMON_5 + b"Jun\0", // ABMON_6 + b"Jul\0", // ABMON_7 + b"Aug\0", // ABMON_8 + b"Sep\0", // ABMON_9 + b"Oct\0", // ABMON_10 + b"Nov\0", // ABMON_11 + b"Dec\0", // ABMON_12 + b"\0", // ERA + b"\0", // ERA_D_FMT + b"\0", // ERA_D_T_FMT + b"\0", // ERA_T_FMT + b"\0", // ALT_DIGITS + b".\0", // RADIXCHAR + b"\0", // THOUSEP + b".\0", // CRNCYSTR + b"^[yY]\0", // YESEXPR + b"^[nN]\0", // NOEXPR + b"yes\0", // YESSTR + b"no\0", // NOSTR ]; -// Item constants -pub const CODESET: nl_item = 0; -pub const D_T_FMT: nl_item = 1; -pub const D_FMT: nl_item = 2; -pub const T_FMT: nl_item = 3; -pub const T_FMT_AMPM: nl_item = 4; -pub const AM_STR: nl_item = 5; -pub const PM_STR: nl_item = 6; - -pub const DAY_1: nl_item = 7; -pub const DAY_2: nl_item = 8; -pub const DAY_3: nl_item = 9; -pub const DAY_4: nl_item = 10; -pub const DAY_5: nl_item = 11; -pub const DAY_6: nl_item = 12; -pub const DAY_7: nl_item = 13; - -pub const ABDAY_1: nl_item = 14; -pub const ABDAY_2: nl_item = 15; -pub const ABDAY_3: nl_item = 16; -pub const ABDAY_4: nl_item = 17; -pub const ABDAY_5: nl_item = 18; -pub const ABDAY_6: nl_item = 19; -pub const ABDAY_7: nl_item = 20; - -pub const MON_1: nl_item = 21; -pub const MON_2: nl_item = 22; -pub const MON_3: nl_item = 23; -pub const MON_4: nl_item = 24; -pub const MON_5: nl_item = 25; -pub const MON_6: nl_item = 26; -pub const MON_7: nl_item = 27; -pub const MON_8: nl_item = 28; -pub const MON_9: nl_item = 29; -pub const MON_10: nl_item = 30; -pub const MON_11: nl_item = 31; -pub const MON_12: nl_item = 32; - -pub const ABMON_1: nl_item = 33; -pub const ABMON_2: nl_item = 34; -pub const ABMON_3: nl_item = 35; -pub const ABMON_4: nl_item = 36; -pub const ABMON_5: nl_item = 37; -pub const ABMON_6: nl_item = 38; -pub const ABMON_7: nl_item = 39; -pub const ABMON_8: nl_item = 40; -pub const ABMON_9: nl_item = 41; -pub const ABMON_10: nl_item = 42; -pub const ABMON_11: nl_item = 43; -pub const ABMON_12: nl_item = 44; - -pub const ERA: nl_item = 45; -pub const ERA_D_FMT: nl_item = 46; -pub const ERA_D_T_FMT: nl_item = 47; -pub const ERA_T_FMT: nl_item = 48; -pub const ALT_DIGITS: nl_item = 49; -pub const RADIXCHAR: nl_item = 50; -pub const THOUSEP: nl_item = 51; -pub const YESEXPR: nl_item = 52; -pub const NOEXPR: nl_item = 53; -pub const YESSTR: nl_item = 54; // Legaxy -pub const NOSTR: nl_item = 55; // Legacy -pub const CRNCYSTR: nl_item = 56; - -const fn is_valid_nl_item(item: nl_item) -> bool { - item >= CODESET && item <= CRNCYSTR -} - -/// Return a pointer to a C string for the specified `nl_item`. -/// Currently only supports the "C" locale. +/// Get a string from the langinfo table /// /// # Safety -/// - Returns a static string pointer valid for program lifetime -/// - Caller must not modify or free the returned pointer -/// - The returned pointer is always null-terminated -/// -/// # Returns -/// - Valid pointer to a null-terminated string for valid items -/// - Pointer to empty string for invalid items +/// - Caller must ensure `item` is a valid `nl_item` index. +/// - Returns a pointer to a null-terminated string, or an empty string if the item is invalid. #[no_mangle] pub unsafe extern "C" fn nl_langinfo(item: nl_item) -> *const c_char { - if !is_valid_nl_item(item) { - return b"\0".as_ptr() as *const c_char; + // Validate the item and perform the lookup + if (item as usize) < STRING_TABLE.len() { + STRING_TABLE[item as usize].as_ptr() as *const c_char + } else { + // Return a pointer to an empty string if the item is invalid + b"\0".as_ptr() as *const c_char } - - for &(id, string) in ITEMS { - if id == item { - return string.as_ptr() as *const c_char; - } - } - - b"\0".as_ptr() as *const c_char } From 420a902e2b77e737f755d87f024fc63d1b2c7f42 Mon Sep 17 00:00:00 2001 From: Guillaume Gielly Date: Tue, 7 Jan 2025 13:55:54 +0100 Subject: [PATCH 7/7] Readd item constants... --- src/header/langinfo/mod.rs | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/header/langinfo/mod.rs b/src/header/langinfo/mod.rs index f8415b4f49..318890369e 100644 --- a/src/header/langinfo/mod.rs +++ b/src/header/langinfo/mod.rs @@ -70,6 +70,70 @@ static STRING_TABLE: [&[u8]; 57] = [ b"no\0", // NOSTR ]; +// Item constants +pub const CODESET: nl_item = 0; +pub const D_T_FMT: nl_item = 1; +pub const D_FMT: nl_item = 2; +pub const T_FMT: nl_item = 3; +pub const T_FMT_AMPM: nl_item = 4; +pub const AM_STR: nl_item = 5; +pub const PM_STR: nl_item = 6; + +pub const DAY_1: nl_item = 7; +pub const DAY_2: nl_item = 8; +pub const DAY_3: nl_item = 9; +pub const DAY_4: nl_item = 10; +pub const DAY_5: nl_item = 11; +pub const DAY_6: nl_item = 12; +pub const DAY_7: nl_item = 13; + +pub const ABDAY_1: nl_item = 14; +pub const ABDAY_2: nl_item = 15; +pub const ABDAY_3: nl_item = 16; +pub const ABDAY_4: nl_item = 17; +pub const ABDAY_5: nl_item = 18; +pub const ABDAY_6: nl_item = 19; +pub const ABDAY_7: nl_item = 20; + +pub const MON_1: nl_item = 21; +pub const MON_2: nl_item = 22; +pub const MON_3: nl_item = 23; +pub const MON_4: nl_item = 24; +pub const MON_5: nl_item = 25; +pub const MON_6: nl_item = 26; +pub const MON_7: nl_item = 27; +pub const MON_8: nl_item = 28; +pub const MON_9: nl_item = 29; +pub const MON_10: nl_item = 30; +pub const MON_11: nl_item = 31; +pub const MON_12: nl_item = 32; + +pub const ABMON_1: nl_item = 33; +pub const ABMON_2: nl_item = 34; +pub const ABMON_3: nl_item = 35; +pub const ABMON_4: nl_item = 36; +pub const ABMON_5: nl_item = 37; +pub const ABMON_6: nl_item = 38; +pub const ABMON_7: nl_item = 39; +pub const ABMON_8: nl_item = 40; +pub const ABMON_9: nl_item = 41; +pub const ABMON_10: nl_item = 42; +pub const ABMON_11: nl_item = 43; +pub const ABMON_12: nl_item = 44; + +pub const ERA: nl_item = 45; +pub const ERA_D_FMT: nl_item = 46; +pub const ERA_D_T_FMT: nl_item = 47; +pub const ERA_T_FMT: nl_item = 48; +pub const ALT_DIGITS: nl_item = 49; +pub const RADIXCHAR: nl_item = 50; +pub const THOUSEP: nl_item = 51; +pub const YESEXPR: nl_item = 52; +pub const NOEXPR: nl_item = 53; +pub const YESSTR: nl_item = 54; // Legaxy +pub const NOSTR: nl_item = 55; // Legacy +pub const CRNCYSTR: nl_item = 56; + /// Get a string from the langinfo table /// /// # Safety