From a5035d68e333001d0cd8a03eb678f24179820d12 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sat, 27 Dec 2025 22:24:41 +0700 Subject: [PATCH 1/4] Implement more locale.h functions --- src/header/locale/cbindgen.toml | 3 - src/header/locale/constants.rs | 16 ++ src/header/locale/data.rs | 346 ++++++++++++++++++++++++++++++++ src/header/locale/mod.rs | 194 +++++++++++++----- src/header/locale/redox.rs | 32 +++ 5 files changed, 534 insertions(+), 57 deletions(-) create mode 100644 src/header/locale/constants.rs create mode 100644 src/header/locale/data.rs create mode 100644 src/header/locale/redox.rs diff --git a/src/header/locale/cbindgen.toml b/src/header/locale/cbindgen.toml index e99e73db12..edb2300b9c 100644 --- a/src/header/locale/cbindgen.toml +++ b/src/header/locale/cbindgen.toml @@ -7,6 +7,3 @@ cpp_compat = true [enum] prefix_with_name = true - -[export] -include = ["locale_t"] diff --git a/src/header/locale/constants.rs b/src/header/locale/constants.rs new file mode 100644 index 0000000000..0652ba8dad --- /dev/null +++ b/src/header/locale/constants.rs @@ -0,0 +1,16 @@ +use crate::platform::types::c_int; + +pub const LC_COLLATE: c_int = 0; +pub const LC_CTYPE: c_int = 1; +pub const LC_MESSAGES: c_int = 2; +pub const LC_MONETARY: c_int = 3; +pub const LC_NUMERIC: c_int = 4; +pub const LC_TIME: c_int = 5; +pub const LC_ALL: c_int = 6; +pub const LC_COLLATE_MASK: c_int = 0x1; +pub const LC_CTYPE_MASK: c_int = 0x2; +pub const LC_MESSAGES_MASK: c_int = 0x4; +pub const LC_MONETARY_MASK: c_int = 0x8; +pub const LC_NUMERIC_MASK: c_int = 0x10; +pub const LC_TIME_MASK: c_int = 0x20; +pub const LC_ALL_MASK: c_int = 0b111111; diff --git a/src/header/locale/data.rs b/src/header/locale/data.rs new file mode 100644 index 0000000000..645b05d3a4 --- /dev/null +++ b/src/header/locale/data.rs @@ -0,0 +1,346 @@ +use core::str::FromStr; + +use alloc::{ + boxed::Box, + ffi::CString, + string::{String, ToString}, + vec::Vec, +}; + +use super::constants::*; +use crate::platform::types::{c_char, c_int}; + +/// See . +/// this struct is not ordered like in the posix spec for readability +#[repr(C)] +#[derive(Clone)] +pub struct lconv { + pub decimal_point: *mut c_char, + pub thousands_sep: *mut c_char, + pub grouping: *mut c_char, + pub int_curr_symbol: *mut c_char, + pub currency_symbol: *mut c_char, + pub mon_decimal_point: *mut c_char, + pub mon_thousands_sep: *mut c_char, + pub mon_grouping: *mut c_char, + pub positive_sign: *mut c_char, + pub negative_sign: *mut c_char, + pub int_frac_digits: c_char, + pub frac_digits: c_char, + pub p_cs_precedes: c_char, + pub p_sep_by_space: c_char, + pub n_cs_precedes: c_char, + pub n_sep_by_space: c_char, + pub p_sign_posn: c_char, + pub n_sign_posn: c_char, + pub int_p_cs_precedes: c_char, + pub int_p_sep_by_space: c_char, + pub int_n_cs_precedes: c_char, + pub int_n_sep_by_space: c_char, + pub int_p_sign_posn: c_char, + pub int_n_sign_posn: c_char, +} +unsafe impl Sync for lconv {} + +/// "POSIX" or "C" default +pub(crate) const fn posix_lconv() -> lconv { + lconv { + // numeric, non-monetary + decimal_point: b".\0".as_ptr() as *mut c_char, + thousands_sep: b"\0".as_ptr() as *mut c_char, + grouping: b"\0".as_ptr() as *mut c_char, + // local monetary + int_curr_symbol: b"\0".as_ptr() as *mut c_char, + currency_symbol: b"\0".as_ptr() as *mut c_char, + mon_decimal_point: b"\0".as_ptr() as *mut c_char, + mon_thousands_sep: b"\0".as_ptr() as *mut c_char, + mon_grouping: b"\0".as_ptr() as *mut c_char, + positive_sign: b"\0".as_ptr() as *mut c_char, + negative_sign: b"\0".as_ptr() as *mut c_char, + // delimiters, unspecified + int_frac_digits: c_char::MAX, + frac_digits: c_char::MAX, + p_cs_precedes: c_char::MAX, + p_sep_by_space: c_char::MAX, + n_cs_precedes: c_char::MAX, + n_sep_by_space: c_char::MAX, + p_sign_posn: c_char::MAX, + n_sign_posn: c_char::MAX, + // international format + int_p_cs_precedes: c_char::MAX, + int_p_sep_by_space: c_char::MAX, + int_n_cs_precedes: c_char::MAX, + int_n_sep_by_space: c_char::MAX, + int_p_sign_posn: c_char::MAX, + int_n_sign_posn: c_char::MAX, + } +} + +#[repr(C)] +pub(crate) struct LocaleData { + pub name: CString, + pub lconv: lconv, + // Owned memory buffers + pub decimal_point: CString, + pub thousands_sep: CString, + pub grouping: Vec, + pub int_curr_symbol: CString, + pub currency_symbol: CString, + pub mon_decimal_point: CString, + pub mon_thousands_sep: CString, + pub mon_grouping: Vec, + pub positive_sign: CString, + pub negative_sign: CString, +} +unsafe impl Sync for LocaleData {} + +impl LocaleData { + pub fn new(name: CString, toml: RawLocale) -> Box { + let mut data = Box::new(LocaleData { + name, + decimal_point: Self::to_cstring(toml.decimal_point), + thousands_sep: Self::to_cstring(toml.thousands_sep), + grouping: Self::to_grouping(toml.grouping), + int_curr_symbol: Self::to_cstring(toml.int_curr_symbol), + currency_symbol: Self::to_cstring(toml.currency_symbol), + mon_decimal_point: Self::to_cstring(toml.mon_decimal_point), + mon_thousands_sep: Self::to_cstring(toml.mon_thousands_sep), + mon_grouping: Self::to_grouping(toml.mon_grouping), + positive_sign: Self::to_cstring(toml.positive_sign), + negative_sign: Self::to_cstring(toml.negative_sign), + lconv: unsafe { core::mem::zeroed() }, + }); + + data.lconv.int_frac_digits = toml.int_frac_digits.unwrap_or(c_char::MAX); + data.lconv.frac_digits = toml.frac_digits.unwrap_or(c_char::MAX); + data.lconv.p_cs_precedes = toml.p_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.p_sep_by_space = toml.p_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.n_cs_precedes = toml.n_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.n_sep_by_space = toml.n_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.p_sign_posn = toml.p_sign_posn.unwrap_or(c_char::MAX); + data.lconv.n_sign_posn = toml.n_sign_posn.unwrap_or(c_char::MAX); + data.lconv.int_p_cs_precedes = toml.int_p_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.int_p_sep_by_space = toml.int_p_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.int_n_cs_precedes = toml.int_n_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.int_n_sep_by_space = toml.int_n_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.int_p_sign_posn = toml.int_p_sign_posn.unwrap_or(c_char::MAX); + data.lconv.int_n_sign_posn = toml.int_n_sign_posn.unwrap_or(c_char::MAX); + + data.update_lconv_pointers(); + data + } + + pub fn posix() -> Box { + LocaleData::new(CString::from_str("C").unwrap(), RawLocale::default()) + } + + fn update_lconv_pointers(&mut self) { + self.lconv.decimal_point = self.decimal_point.as_ptr() as *mut c_char; + self.lconv.thousands_sep = self.thousands_sep.as_ptr() as *mut c_char; + self.lconv.grouping = self.grouping.as_ptr() as *mut c_char; + self.lconv.int_curr_symbol = self.int_curr_symbol.as_ptr() as *mut c_char; + self.lconv.currency_symbol = self.currency_symbol.as_ptr() as *mut c_char; + self.lconv.mon_decimal_point = self.mon_decimal_point.as_ptr() as *mut c_char; + self.lconv.mon_thousands_sep = self.mon_thousands_sep.as_ptr() as *mut c_char; + self.lconv.mon_grouping = self.mon_grouping.as_ptr() as *mut c_char; + self.lconv.positive_sign = self.positive_sign.as_ptr() as *mut c_char; + self.lconv.negative_sign = self.negative_sign.as_ptr() as *mut c_char; + } + + pub fn copy_category(&mut self, other: &Self, category: c_int) { + match category { + LC_NUMERIC => { + self.decimal_point = other.decimal_point.clone(); + self.thousands_sep = other.thousands_sep.clone(); + self.grouping = other.grouping.clone(); + self.lconv.frac_digits = other.lconv.frac_digits; + } + LC_MONETARY => { + self.int_curr_symbol = other.int_curr_symbol.clone(); + self.currency_symbol = other.currency_symbol.clone(); + self.mon_decimal_point = other.mon_decimal_point.clone(); + self.mon_thousands_sep = other.mon_thousands_sep.clone(); + self.mon_grouping = other.mon_grouping.clone(); + self.positive_sign = other.positive_sign.clone(); + self.negative_sign = other.negative_sign.clone(); + self.lconv.int_frac_digits = other.lconv.int_frac_digits; + self.lconv.p_cs_precedes = other.lconv.p_cs_precedes; + self.lconv.p_sep_by_space = other.lconv.p_sep_by_space; + self.lconv.n_cs_precedes = other.lconv.n_cs_precedes; + self.lconv.n_sep_by_space = other.lconv.n_sep_by_space; + self.lconv.p_sign_posn = other.lconv.p_sign_posn; + self.lconv.n_sign_posn = other.lconv.n_sign_posn; + self.lconv.int_p_cs_precedes = other.lconv.int_p_cs_precedes; + self.lconv.int_p_sep_by_space = other.lconv.int_p_sep_by_space; + self.lconv.int_n_cs_precedes = other.lconv.int_n_cs_precedes; + self.lconv.int_n_sep_by_space = other.lconv.int_n_sep_by_space; + self.lconv.int_p_sign_posn = other.lconv.int_p_sign_posn; + self.lconv.int_n_sign_posn = other.lconv.int_n_sign_posn; + } + LC_ALL => { + *self = other.clone(); + } + _ => {} + } + + self.update_lconv_pointers(); + } + + fn to_cstring(opt: Option) -> CString { + opt.and_then(|s| CString::new(s).ok()) + .unwrap_or_else(|| CString::new("").unwrap()) + } + + fn to_grouping(opt: Option>) -> Vec { + let mut v: Vec = opt + .unwrap_or_default() + .into_iter() + .map(|x| x as u8) + .collect(); + v.push(0); + v + } +} + +impl Clone for LocaleData { + fn clone(&self) -> Self { + let mut data = Self { + name: self.name.clone(), + lconv: self.lconv.clone(), + decimal_point: self.decimal_point.clone(), + thousands_sep: self.thousands_sep.clone(), + grouping: self.grouping.clone(), + int_curr_symbol: self.int_curr_symbol.clone(), + currency_symbol: self.currency_symbol.clone(), + mon_decimal_point: self.mon_decimal_point.clone(), + mon_thousands_sep: self.mon_thousands_sep.clone(), + mon_grouping: self.mon_grouping.clone(), + positive_sign: self.positive_sign.clone(), + negative_sign: self.negative_sign.clone(), + }; + data.update_lconv_pointers(); + data + } +} + +#[derive(Clone)] +pub(crate) struct GlobalLocaleData { + // names per LC_* constant + pub names: [CString; 7], + pub data: LocaleData, +} + +impl GlobalLocaleData { + pub fn new() -> Box { + let data = LocaleData::posix(); + let names = [ + data.name.clone(), + data.name.clone(), + data.name.clone(), + data.name.clone(), + data.name.clone(), + data.name.clone(), + data.name.clone(), + ]; + let mut r = Box::new(GlobalLocaleData { data: *data, names }); + r.data.update_lconv_pointers(); + r + } + + pub fn get_name(&self, category: i32) -> Option<&CString> { + self.names.get(category as usize) + } + pub fn set_name(&mut self, category: i32, name: CString) -> Option<&CString> { + if self.names.get(category as usize).is_some() { + self.names[category as usize] = name; + self.names.get(category as usize) + } else { + None + } + } +} +unsafe impl Sync for GlobalLocaleData {} + +#[derive(Default)] +pub(crate) struct RawLocale { + pub decimal_point: Option, + pub thousands_sep: Option, + pub grouping: Option>, + pub int_curr_symbol: Option, + pub currency_symbol: Option, + pub mon_decimal_point: Option, + pub mon_thousands_sep: Option, + pub mon_grouping: Option>, + pub positive_sign: Option, + pub negative_sign: Option, + pub int_frac_digits: Option, + pub frac_digits: Option, + pub p_cs_precedes: Option, + pub p_sep_by_space: Option, + pub n_cs_precedes: Option, + pub n_sep_by_space: Option, + pub p_sign_posn: Option, + pub n_sign_posn: Option, + pub int_p_cs_precedes: Option, + pub int_p_sep_by_space: Option, + pub int_n_cs_precedes: Option, + pub int_n_sep_by_space: Option, + pub int_p_sign_posn: Option, + pub int_n_sign_posn: Option, +} +impl RawLocale { + pub fn parse(content: &str) -> Self { + let mut locale = RawLocale::default(); + + for line in content.lines() { + let trimmed = line.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + + let mut parts = trimmed.splitn(2, '='); + let key = parts.next().unwrap_or("").trim(); + let val = parts.next().unwrap_or("").trim(); + + if key.is_empty() || val.is_empty() { + continue; + } + + match key { + "decimal_point" => locale.decimal_point = Some(val.to_string()), + "thousands_sep" => locale.thousands_sep = Some(val.to_string()), + "int_curr_symbol" => locale.int_curr_symbol = Some(val.to_string()), + "currency_symbol" => locale.currency_symbol = Some(val.to_string()), + "mon_decimal_point" => locale.mon_decimal_point = Some(val.to_string()), + "mon_thousands_sep" => locale.mon_thousands_sep = Some(val.to_string()), + "positive_sign" => locale.positive_sign = Some(val.to_string()), + "negative_sign" => locale.negative_sign = Some(val.to_string()), + "grouping" => locale.grouping = Some(Self::parse_grouping(val)), + "mon_grouping" => locale.mon_grouping = Some(Self::parse_grouping(val)), + "int_frac_digits" => locale.int_frac_digits = val.parse().ok(), + "frac_digits" => locale.frac_digits = val.parse().ok(), + "p_cs_precedes" => locale.p_cs_precedes = val.parse().ok(), + "p_sep_by_space" => locale.p_sep_by_space = val.parse().ok(), + "n_cs_precedes" => locale.n_cs_precedes = val.parse().ok(), + "n_sep_by_space" => locale.n_sep_by_space = val.parse().ok(), + "p_sign_posn" => locale.p_sign_posn = val.parse().ok(), + "n_sign_posn" => locale.n_sign_posn = val.parse().ok(), + "int_p_cs_precedes" => locale.int_p_cs_precedes = val.parse().ok(), + "int_p_sep_by_space" => locale.int_p_sep_by_space = val.parse().ok(), + "int_n_cs_precedes" => locale.int_n_cs_precedes = val.parse().ok(), + "int_n_sep_by_space" => locale.int_n_sep_by_space = val.parse().ok(), + "int_p_sign_posn" => locale.int_p_sign_posn = val.parse().ok(), + "int_n_sign_posn" => locale.int_n_sign_posn = val.parse().ok(), + _ => {} + } + } + locale + } + + /// parse e.g. "3,3,0" + fn parse_grouping(val: &str) -> Vec { + val.split(',') + .filter_map(|s| s.trim().parse::().ok()) + .collect() + } +} diff --git a/src/header/locale/mod.rs b/src/header/locale/mod.rs index acc250f688..da839d7e32 100644 --- a/src/header/locale/mod.rs +++ b/src/header/locale/mod.rs @@ -1,74 +1,160 @@ //! `locale.h` implementation. //! //! See . +//! See -use core::ptr; +use alloc::{boxed::Box, ffi::CString, string::String}; +use core::{ptr, str::FromStr}; -use crate::platform::types::{c_char, c_int, c_void}; +use crate::{ + c_str::CStr, + error::{Errno, ResultExtPtrMut}, + fs::File, + header::errno, + io::Read, + platform::types::{c_char, c_int, c_void}, + sync::Once, +}; const EMPTY_PTR: *const c_char = "\0" as *const _ as *const c_char; // Can't use &str because of the mutability static mut C_LOCALE: [c_char; 2] = [b'C' as c_char, 0]; -//TODO: locale_t internals +mod constants; +use constants::*; +mod data; +use data::*; +// TODO: No parsing implementation that's compatible with glibc +mod redox; +use redox::*; + pub type locale_t = *mut c_void; - -/// See . -#[repr(C)] -pub struct lconv { - currency_symbol: *const c_char, - decimal_point: *const c_char, - frac_digits: c_char, - grouping: *const c_char, - int_curr_symbol: *const c_char, - int_frac_digits: c_char, - mon_decimal_point: *const c_char, - mon_grouping: *const c_char, - mon_thousands_sep: *const c_char, - negative_sign: *const c_char, - n_cs_precedes: c_char, - n_sep_by_space: c_char, - n_sign_posn: c_char, - positive_sign: *const c_char, - p_cs_precedes: c_char, - p_sep_by_space: c_char, - p_sign_posn: c_char, - thousands_sep: *const c_char, -} -unsafe impl Sync for lconv {} - -// Mutable because POSIX demands a mutable pointer, even though it warns -// against mutating it -static mut CURRENT_LOCALE: lconv = lconv { - currency_symbol: EMPTY_PTR, - decimal_point: ".\0" as *const _ as *const c_char, - frac_digits: c_char::max_value(), - grouping: EMPTY_PTR, - int_curr_symbol: EMPTY_PTR, - int_frac_digits: c_char::max_value(), - mon_decimal_point: EMPTY_PTR, - mon_grouping: EMPTY_PTR, - mon_thousands_sep: EMPTY_PTR, - negative_sign: EMPTY_PTR, - n_cs_precedes: c_char::max_value(), - n_sep_by_space: c_char::max_value(), - n_sign_posn: c_char::max_value(), - positive_sign: EMPTY_PTR, - p_cs_precedes: c_char::max_value(), - p_sep_by_space: c_char::max_value(), - p_sign_posn: c_char::max_value(), - thousands_sep: EMPTY_PTR, -}; +/// constant struct to "C" or "POSIX" locale +/// mutable because POSIX demands a mutable pointer +static mut POSIX_LOCALE: lconv = posix_lconv(); +pub const LC_GLOBAL_LOCALE: locale_t = -1isize as locale_t; +/// process-wide locale, used by setlocale() and localeconv() +static mut GLOBAL_LOCALE: *mut GlobalLocaleData = ptr::null_mut(); +/// thread-wide locale, used by uselocale() and localeconv() +#[thread_local] +static mut THREAD_LOCALE: *mut LocaleData = ptr::null_mut(); /// See . #[unsafe(no_mangle)] pub unsafe extern "C" fn localeconv() -> *mut lconv { - &raw mut CURRENT_LOCALE as *mut _ + let current = uselocale(ptr::null_mut()); + if current == LC_GLOBAL_LOCALE || current.is_null() { + if !GLOBAL_LOCALE.is_null() { + // safety: GLOBAL_LOCALE is never set to null again + &raw mut (*GLOBAL_LOCALE).data.lconv + } else { + &raw mut POSIX_LOCALE + } + } else { + let current = current as *mut LocaleData; + &raw mut (*current).lconv + } } /// See . #[unsafe(no_mangle)] -pub unsafe extern "C" fn setlocale(_option: c_int, _val: *const c_char) -> *mut c_char { - // TODO actually implement - &raw mut C_LOCALE as *mut c_char +pub unsafe extern "C" fn setlocale(category: c_int, locale: *const c_char) -> *mut c_char { + if GLOBAL_LOCALE.is_null() { + let new_global = GlobalLocaleData::new(); + GLOBAL_LOCALE = Box::into_raw(new_global); + }; + let Some(mut global) = GLOBAL_LOCALE.as_mut() else { + return ptr::null_mut(); + }; + + if locale.is_null() { + let Some(name) = global.get_name(category) else { + return ptr::null_mut(); + }; + return name.as_ptr() as *mut c_char; + } + + let name = CStr::from_ptr(locale).to_str().unwrap_or("C"); + + match load_locale_file(name) { + Ok(loc_ptr) => { + global.data.copy_category(&loc_ptr, category); + let Some(name) = global.set_name(category, CString::from_str(name).unwrap()) else { + return ptr::null_mut(); + }; + name.as_ptr() as *mut c_char + } + Err(_) => ptr::null_mut(), + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn uselocale(newloc: locale_t) -> locale_t { + let old_loc = if THREAD_LOCALE.is_null() { + LC_GLOBAL_LOCALE + } else { + THREAD_LOCALE as locale_t + }; + + if !newloc.is_null() { + THREAD_LOCALE = if newloc == LC_GLOBAL_LOCALE { + ptr::null_mut() + } else { + newloc as *mut LocaleData + }; + } + + old_loc +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn newlocale(mask: c_int, locale: *const c_char, base: locale_t) -> locale_t { + let name = CStr::from_ptr(locale).to_string_lossy().into_owned(); + let name = name.as_str(); + let mut new_locale = if name == "" || name == "C" || name == "POSIX" { + Ok(LocaleData::posix()) + } else { + load_locale_file(name) + }; + if base != LC_GLOBAL_LOCALE { + // borrowing here + let base = base as *const _ as *const LocaleData; + if let Ok(new_locale) = new_locale.as_mut() { + if let Some(base) = base.as_ref() { + // copy old values if not containing the mask + if (mask & LC_NUMERIC_MASK) == 0 { + new_locale.copy_category(base, LC_NUMERIC); + } + if (mask & LC_MONETARY_MASK) == 0 { + new_locale.copy_category(base, LC_MONETARY); + } + // TODO: other categories? + } + } + } + new_locale.or_errno_null_mut() as *mut _ +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn freelocale(loc: locale_t) { + if !loc.is_null() && loc != LC_GLOBAL_LOCALE { + drop(Box::from_raw(loc as *mut LocaleData)); + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn duplocale(loc: locale_t) -> locale_t { + if loc.is_null() { + // TODO: errno? + loc + } else if loc == LC_GLOBAL_LOCALE { + Box::into_raw(LocaleData::posix()) as locale_t + } else { + // borrowing here + let loc = loc as *const _ as *const LocaleData; + Box::into_raw(Box::from((*loc).clone())) as locale_t + } } diff --git a/src/header/locale/redox.rs b/src/header/locale/redox.rs new file mode 100644 index 0000000000..88abaf3030 --- /dev/null +++ b/src/header/locale/redox.rs @@ -0,0 +1,32 @@ +use core::str::FromStr; + +use alloc::{boxed::Box, ffi::CString, string::String}; + +use crate::{ + c_str::CStr, + error::Errno, + fs::File, + header::{ + errno, fcntl, + locale::{ + data::{LocaleData, RawLocale}, + locale_t, + }, + }, + io::Read, +}; + +pub fn load_locale_file(name: &str) -> Result, Errno> { + let mut path = String::from("/etc/locale.d/"); + path.push_str(name); + + let path_c = CString::new(path).map_err(|_| Errno(errno::EINVAL))?; + let mut content = String::new(); + + let mut file = File::open(path_c.as_c_str().into(), fcntl::O_RDONLY)?; + file.read_to_string(&mut content) + .map_err(|_| Errno(errno::EIO))?; + + let toml = RawLocale::parse(&content); + Ok(LocaleData::new(CString::from_str(name).unwrap(), toml)) +} From 19d95a226cc57254e969402bc8f91ca77cc10f4f Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sat, 27 Dec 2025 22:25:01 +0700 Subject: [PATCH 2/4] Add tests for new locale.h functions --- tests/Makefile | 4 +- .../duplocale.stderr} | 0 .../locale/duplocale.stdout} | 0 .../bins_dynamic/locale/newlocale.stderr | 0 .../bins_dynamic/locale/newlocale.stdout | 0 .../bins_dynamic/locale/setlocale.stderr | 0 .../setlocale.stdout} | 0 .../bins_static/locale/duplocale.stderr | 0 .../bins_static/locale/duplocale.stdout | 0 .../bins_static/locale/newlocale.stderr | 0 .../bins_static/locale/newlocale.stdout | 0 .../bins_static/locale/setlocale.stderr | 0 .../setlocale.stdout} | 0 tests/locale/duplocale.c | 39 +++++++++++++++++++ tests/locale/newlocale.c | 16 ++++++++ tests/{locale.c => locale/setlocale.c} | 0 16 files changed, 58 insertions(+), 1 deletion(-) rename tests/expected/bins_dynamic/{locale.stderr => locale/duplocale.stderr} (100%) rename tests/expected/{bins_static/locale.stderr => bins_dynamic/locale/duplocale.stdout} (100%) create mode 100644 tests/expected/bins_dynamic/locale/newlocale.stderr create mode 100644 tests/expected/bins_dynamic/locale/newlocale.stdout create mode 100644 tests/expected/bins_dynamic/locale/setlocale.stderr rename tests/expected/bins_dynamic/{locale.stdout => locale/setlocale.stdout} (100%) create mode 100644 tests/expected/bins_static/locale/duplocale.stderr create mode 100644 tests/expected/bins_static/locale/duplocale.stdout create mode 100644 tests/expected/bins_static/locale/newlocale.stderr create mode 100644 tests/expected/bins_static/locale/newlocale.stdout create mode 100644 tests/expected/bins_static/locale/setlocale.stderr rename tests/expected/bins_static/{locale.stdout => locale/setlocale.stdout} (100%) create mode 100644 tests/locale/duplocale.c create mode 100644 tests/locale/newlocale.c rename tests/{locale.c => locale/setlocale.c} (100%) diff --git a/tests/Makefile b/tests/Makefile index 7d2a7f9169..0f3489d4a8 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -46,7 +46,9 @@ EXPECT_NAMES=\ glob \ iso646 \ libgen \ - locale \ + locale/duplocale \ + locale/newlocale \ + locale/setlocale \ math \ regex \ select \ diff --git a/tests/expected/bins_dynamic/locale.stderr b/tests/expected/bins_dynamic/locale/duplocale.stderr similarity index 100% rename from tests/expected/bins_dynamic/locale.stderr rename to tests/expected/bins_dynamic/locale/duplocale.stderr diff --git a/tests/expected/bins_static/locale.stderr b/tests/expected/bins_dynamic/locale/duplocale.stdout similarity index 100% rename from tests/expected/bins_static/locale.stderr rename to tests/expected/bins_dynamic/locale/duplocale.stdout diff --git a/tests/expected/bins_dynamic/locale/newlocale.stderr b/tests/expected/bins_dynamic/locale/newlocale.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/locale/newlocale.stdout b/tests/expected/bins_dynamic/locale/newlocale.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/locale/setlocale.stderr b/tests/expected/bins_dynamic/locale/setlocale.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/locale.stdout b/tests/expected/bins_dynamic/locale/setlocale.stdout similarity index 100% rename from tests/expected/bins_dynamic/locale.stdout rename to tests/expected/bins_dynamic/locale/setlocale.stdout diff --git a/tests/expected/bins_static/locale/duplocale.stderr b/tests/expected/bins_static/locale/duplocale.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/locale/duplocale.stdout b/tests/expected/bins_static/locale/duplocale.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/locale/newlocale.stderr b/tests/expected/bins_static/locale/newlocale.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/locale/newlocale.stdout b/tests/expected/bins_static/locale/newlocale.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/locale/setlocale.stderr b/tests/expected/bins_static/locale/setlocale.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/locale.stdout b/tests/expected/bins_static/locale/setlocale.stdout similarity index 100% rename from tests/expected/bins_static/locale.stdout rename to tests/expected/bins_static/locale/setlocale.stdout diff --git a/tests/locale/duplocale.c b/tests/locale/duplocale.c new file mode 100644 index 0000000000..735fbbe353 --- /dev/null +++ b/tests/locale/duplocale.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +int main() { + struct lconv *lconv0 = localeconv(); + + locale_t locale0 = uselocale(NULL); + assert(locale0 == LC_GLOBAL_LOCALE); + + locale_t locale1 = uselocale(LC_GLOBAL_LOCALE); + assert(locale1 == LC_GLOBAL_LOCALE); + + struct lconv *lconv1 = localeconv(); + assert(lconv0 == lconv1); + + locale_t locale2 = newlocale(LC_ALL_MASK, "C", (locale_t)0); + assert(locale2 != LC_GLOBAL_LOCALE); + + struct lconv *lconv2 = localeconv(); + assert(lconv2 == lconv1); + + locale_t locale3 = duplocale(locale2); + assert(locale3 != locale2); + assert(locale3 != LC_GLOBAL_LOCALE); + + locale_t locale4 = uselocale(locale3); + assert(locale4 == locale1); + + struct lconv *lconv3 = localeconv(); + assert(lconv3 != lconv2); + + // should not crash + freelocale(locale3); + freelocale(locale2); + freelocale(locale1); + return 0; +} \ No newline at end of file diff --git a/tests/locale/newlocale.c b/tests/locale/newlocale.c new file mode 100644 index 0000000000..0dbb70b5f1 --- /dev/null +++ b/tests/locale/newlocale.c @@ -0,0 +1,16 @@ +#include +#include +#include +#include + +int main() { + locale_t locale0 = newlocale(LC_ALL_MASK, "C", (locale_t)0); + assert(locale0 != (locale_t)0); + + locale_t locale1 = newlocale(LC_ALL_MASK, "non-existent-locale", (locale_t)0); + assert(locale1 == (locale_t)0); + + // TODO: test with existing custom locale + + return 0; +} diff --git a/tests/locale.c b/tests/locale/setlocale.c similarity index 100% rename from tests/locale.c rename to tests/locale/setlocale.c From e67933ef1be765c536cd1f8dbab6479adafefcb5 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sun, 28 Dec 2025 03:24:20 +0700 Subject: [PATCH 3/4] Use POSIX localedef format --- src/header/locale/data.rs | 215 +++++++++++++++++++++++-------------- src/header/locale/mod.rs | 21 +++- src/header/locale/redox.rs | 32 ------ tests/locale/newlocale.c | 6 +- 4 files changed, 153 insertions(+), 121 deletions(-) delete mode 100644 src/header/locale/redox.rs diff --git a/src/header/locale/data.rs b/src/header/locale/data.rs index 645b05d3a4..aef2df910d 100644 --- a/src/header/locale/data.rs +++ b/src/header/locale/data.rs @@ -83,55 +83,55 @@ pub(crate) struct LocaleData { // Owned memory buffers pub decimal_point: CString, pub thousands_sep: CString, - pub grouping: Vec, + pub grouping: Vec, pub int_curr_symbol: CString, pub currency_symbol: CString, pub mon_decimal_point: CString, pub mon_thousands_sep: CString, - pub mon_grouping: Vec, + pub mon_grouping: Vec, pub positive_sign: CString, pub negative_sign: CString, } unsafe impl Sync for LocaleData {} impl LocaleData { - pub fn new(name: CString, toml: RawLocale) -> Box { + pub fn new(name: CString, defs: PosixLocaleDef) -> Box { let mut data = Box::new(LocaleData { name, - decimal_point: Self::to_cstring(toml.decimal_point), - thousands_sep: Self::to_cstring(toml.thousands_sep), - grouping: Self::to_grouping(toml.grouping), - int_curr_symbol: Self::to_cstring(toml.int_curr_symbol), - currency_symbol: Self::to_cstring(toml.currency_symbol), - mon_decimal_point: Self::to_cstring(toml.mon_decimal_point), - mon_thousands_sep: Self::to_cstring(toml.mon_thousands_sep), - mon_grouping: Self::to_grouping(toml.mon_grouping), - positive_sign: Self::to_cstring(toml.positive_sign), - negative_sign: Self::to_cstring(toml.negative_sign), + decimal_point: Self::to_cstring(defs.decimal_point), + thousands_sep: Self::to_cstring(defs.thousands_sep), + grouping: Self::to_grouping_char(defs.grouping), + int_curr_symbol: Self::to_cstring(defs.int_curr_symbol), + currency_symbol: Self::to_cstring(defs.currency_symbol), + mon_decimal_point: Self::to_cstring(defs.mon_decimal_point), + mon_thousands_sep: Self::to_cstring(defs.mon_thousands_sep), + mon_grouping: Self::to_grouping_char(defs.mon_grouping), + positive_sign: Self::to_cstring(defs.positive_sign), + negative_sign: Self::to_cstring(defs.negative_sign), lconv: unsafe { core::mem::zeroed() }, }); - data.lconv.int_frac_digits = toml.int_frac_digits.unwrap_or(c_char::MAX); - data.lconv.frac_digits = toml.frac_digits.unwrap_or(c_char::MAX); - data.lconv.p_cs_precedes = toml.p_cs_precedes.unwrap_or(c_char::MAX); - data.lconv.p_sep_by_space = toml.p_sep_by_space.unwrap_or(c_char::MAX); - data.lconv.n_cs_precedes = toml.n_cs_precedes.unwrap_or(c_char::MAX); - data.lconv.n_sep_by_space = toml.n_sep_by_space.unwrap_or(c_char::MAX); - data.lconv.p_sign_posn = toml.p_sign_posn.unwrap_or(c_char::MAX); - data.lconv.n_sign_posn = toml.n_sign_posn.unwrap_or(c_char::MAX); - data.lconv.int_p_cs_precedes = toml.int_p_cs_precedes.unwrap_or(c_char::MAX); - data.lconv.int_p_sep_by_space = toml.int_p_sep_by_space.unwrap_or(c_char::MAX); - data.lconv.int_n_cs_precedes = toml.int_n_cs_precedes.unwrap_or(c_char::MAX); - data.lconv.int_n_sep_by_space = toml.int_n_sep_by_space.unwrap_or(c_char::MAX); - data.lconv.int_p_sign_posn = toml.int_p_sign_posn.unwrap_or(c_char::MAX); - data.lconv.int_n_sign_posn = toml.int_n_sign_posn.unwrap_or(c_char::MAX); + data.lconv.int_frac_digits = defs.int_frac_digits.unwrap_or(c_char::MAX); + data.lconv.frac_digits = defs.frac_digits.unwrap_or(c_char::MAX); + data.lconv.p_cs_precedes = defs.p_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.p_sep_by_space = defs.p_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.n_cs_precedes = defs.n_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.n_sep_by_space = defs.n_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.p_sign_posn = defs.p_sign_posn.unwrap_or(c_char::MAX); + data.lconv.n_sign_posn = defs.n_sign_posn.unwrap_or(c_char::MAX); + data.lconv.int_p_cs_precedes = defs.int_p_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.int_p_sep_by_space = defs.int_p_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.int_n_cs_precedes = defs.int_n_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.int_n_sep_by_space = defs.int_n_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.int_p_sign_posn = defs.int_p_sign_posn.unwrap_or(c_char::MAX); + data.lconv.int_n_sign_posn = defs.int_n_sign_posn.unwrap_or(c_char::MAX); data.update_lconv_pointers(); data } pub fn posix() -> Box { - LocaleData::new(CString::from_str("C").unwrap(), RawLocale::default()) + LocaleData::new(CString::from_str("C").unwrap(), PosixLocaleDef::default()) } fn update_lconv_pointers(&mut self) { @@ -186,20 +186,19 @@ impl LocaleData { self.update_lconv_pointers(); } - fn to_cstring(opt: Option) -> CString { - opt.and_then(|s| CString::new(s).ok()) - .unwrap_or_else(|| CString::new("").unwrap()) + fn to_cstring(opt: Option) -> CString { + opt.unwrap_or_else(|| CString::new("").unwrap()) } - fn to_grouping(opt: Option>) -> Vec { - let mut v: Vec = opt - .unwrap_or_default() - .into_iter() - .map(|x| x as u8) - .collect(); + fn to_grouping_char(opt: Vec>) -> Vec { + let mut v: Vec = opt.into_iter().map(Self::to_char).collect(); v.push(0); v } + + fn to_char(opt: Option) -> c_char { + opt.unwrap_or(c_char::MAX) + } } impl Clone for LocaleData { @@ -262,17 +261,17 @@ impl GlobalLocaleData { unsafe impl Sync for GlobalLocaleData {} #[derive(Default)] -pub(crate) struct RawLocale { - pub decimal_point: Option, - pub thousands_sep: Option, - pub grouping: Option>, - pub int_curr_symbol: Option, - pub currency_symbol: Option, - pub mon_decimal_point: Option, - pub mon_thousands_sep: Option, - pub mon_grouping: Option>, - pub positive_sign: Option, - pub negative_sign: Option, +pub(crate) struct PosixLocaleDef { + pub decimal_point: Option, + pub thousands_sep: Option, + pub grouping: Vec>, + pub int_curr_symbol: Option, + pub currency_symbol: Option, + pub mon_decimal_point: Option, + pub mon_thousands_sep: Option, + pub mon_grouping: Vec>, + pub positive_sign: Option, + pub negative_sign: Option, pub int_frac_digits: Option, pub frac_digits: Option, pub p_cs_precedes: Option, @@ -288,59 +287,109 @@ pub(crate) struct RawLocale { pub int_p_sign_posn: Option, pub int_n_sign_posn: Option, } -impl RawLocale { +impl PosixLocaleDef { + //! See pub fn parse(content: &str) -> Self { - let mut locale = RawLocale::default(); + let mut locale = PosixLocaleDef::default(); + + let mut lines = content.lines(); + loop { + let Some(line) = lines.next() else { + break; + }; - for line in content.lines() { let trimmed = line.trim(); if trimmed.is_empty() || trimmed.starts_with('#') { continue; } - let mut parts = trimmed.splitn(2, '='); - let key = parts.next().unwrap_or("").trim(); - let val = parts.next().unwrap_or("").trim(); + let mut parts = trimmed.split_ascii_whitespace(); + let Some(key) = parts.next() else { + continue; + }; + let mut val: String = String::new(); + loop { + let Some(chunk) = parts.next() else { + break; + }; + if !chunk.ends_with('\\') { + val.push_str(chunk); + break; + } + // multiline values + val.push_str(&chunk[0..chunk.len() - 1]); + let Some(next_line) = lines.next() else { + break; + }; + parts = next_line.split_ascii_whitespace(); + } if key.is_empty() || val.is_empty() { continue; } match key { - "decimal_point" => locale.decimal_point = Some(val.to_string()), - "thousands_sep" => locale.thousands_sep = Some(val.to_string()), - "int_curr_symbol" => locale.int_curr_symbol = Some(val.to_string()), - "currency_symbol" => locale.currency_symbol = Some(val.to_string()), - "mon_decimal_point" => locale.mon_decimal_point = Some(val.to_string()), - "mon_thousands_sep" => locale.mon_thousands_sep = Some(val.to_string()), - "positive_sign" => locale.positive_sign = Some(val.to_string()), - "negative_sign" => locale.negative_sign = Some(val.to_string()), - "grouping" => locale.grouping = Some(Self::parse_grouping(val)), - "mon_grouping" => locale.mon_grouping = Some(Self::parse_grouping(val)), - "int_frac_digits" => locale.int_frac_digits = val.parse().ok(), - "frac_digits" => locale.frac_digits = val.parse().ok(), - "p_cs_precedes" => locale.p_cs_precedes = val.parse().ok(), - "p_sep_by_space" => locale.p_sep_by_space = val.parse().ok(), - "n_cs_precedes" => locale.n_cs_precedes = val.parse().ok(), - "n_sep_by_space" => locale.n_sep_by_space = val.parse().ok(), - "p_sign_posn" => locale.p_sign_posn = val.parse().ok(), - "n_sign_posn" => locale.n_sign_posn = val.parse().ok(), - "int_p_cs_precedes" => locale.int_p_cs_precedes = val.parse().ok(), - "int_p_sep_by_space" => locale.int_p_sep_by_space = val.parse().ok(), - "int_n_cs_precedes" => locale.int_n_cs_precedes = val.parse().ok(), - "int_n_sep_by_space" => locale.int_n_sep_by_space = val.parse().ok(), - "int_p_sign_posn" => locale.int_p_sign_posn = val.parse().ok(), - "int_n_sign_posn" => locale.int_n_sign_posn = val.parse().ok(), + "decimal_point" => locale.decimal_point = Self::parse_str(&val), + "thousands_sep" => locale.thousands_sep = Self::parse_str(&val), + "int_curr_symbol" => locale.int_curr_symbol = Self::parse_str(&val), + "currency_symbol" => locale.currency_symbol = Self::parse_str(&val), + "mon_decimal_point" => locale.mon_decimal_point = Self::parse_str(&val), + "mon_thousands_sep" => locale.mon_thousands_sep = Self::parse_str(&val), + "positive_sign" => locale.positive_sign = Self::parse_str(&val), + "negative_sign" => locale.negative_sign = Self::parse_str(&val), + "grouping" => locale.grouping = Self::parse_int_group(&val), + "mon_grouping" => locale.mon_grouping = Self::parse_int_group(&val), + "int_frac_digits" => locale.int_frac_digits = Self::parse_int(&val), + "frac_digits" => locale.frac_digits = Self::parse_int(&val), + "p_cs_precedes" => locale.p_cs_precedes = Self::parse_int(&val), + "p_sep_by_space" => locale.p_sep_by_space = Self::parse_int(&val), + "n_cs_precedes" => locale.n_cs_precedes = Self::parse_int(&val), + "n_sep_by_space" => locale.n_sep_by_space = Self::parse_int(&val), + "p_sign_posn" => locale.p_sign_posn = Self::parse_int(&val), + "n_sign_posn" => locale.n_sign_posn = Self::parse_int(&val), + "int_p_cs_precedes" => locale.int_p_cs_precedes = Self::parse_int(&val), + "int_p_sep_by_space" => locale.int_p_sep_by_space = Self::parse_int(&val), + "int_n_cs_precedes" => locale.int_n_cs_precedes = Self::parse_int(&val), + "int_n_sep_by_space" => locale.int_n_sep_by_space = Self::parse_int(&val), + "int_p_sign_posn" => locale.int_p_sign_posn = Self::parse_int(&val), + "int_n_sign_posn" => locale.int_n_sign_posn = Self::parse_int(&val), _ => {} } } locale } - /// parse e.g. "3,3,0" - fn parse_grouping(val: &str) -> Vec { - val.split(',') - .filter_map(|s| s.trim().parse::().ok()) - .collect() + /// parse e.g. `3;3;0` -> [ 3,3,0 ], `-1` -> [ None ] + fn parse_int_group(val: &str) -> Vec> { + val.split(';').map(Self::parse_int).collect() + } + + /// parse e.g. `-1` -> None, `1` -> Some(1) + fn parse_int(val: &str) -> Option { + let r = val.trim().parse::().ok(); + if r.is_some_and(|i| i < 0) { + return None; + } + r + } + + /// parse e.g. `""` + fn parse_str(val: &str) -> Option { + let mut r = String::new(); + let mut v = val.chars(); + if v.next() != Some('"') { + return None; + } + while let Some(c) = v.next() { + if c == '"' { + if v.next().is_some() { + return None; + } + break; + } + // TODO: Parse <..> + r.push(c); + } + CString::new(r).ok() } } diff --git a/src/header/locale/mod.rs b/src/header/locale/mod.rs index da839d7e32..f03ac3560f 100644 --- a/src/header/locale/mod.rs +++ b/src/header/locale/mod.rs @@ -1,7 +1,6 @@ //! `locale.h` implementation. //! //! See . -//! See use alloc::{boxed::Box, ffi::CString, string::String}; use core::{ptr, str::FromStr}; @@ -10,7 +9,7 @@ use crate::{ c_str::CStr, error::{Errno, ResultExtPtrMut}, fs::File, - header::errno, + header::{errno, fcntl}, io::Read, platform::types::{c_char, c_int, c_void}, sync::Once, @@ -24,9 +23,6 @@ mod constants; use constants::*; mod data; use data::*; -// TODO: No parsing implementation that's compatible with glibc -mod redox; -use redox::*; pub type locale_t = *mut c_void; /// constant struct to "C" or "POSIX" locale @@ -158,3 +154,18 @@ pub unsafe extern "C" fn duplocale(loc: locale_t) -> locale_t { Box::into_raw(Box::from((*loc).clone())) as locale_t } } + +pub fn load_locale_file(name: &str) -> Result, Errno> { + let mut path = String::from("/usr/share/i18n/locales/"); + path.push_str(name); + + let path_c = CString::new(path).map_err(|_| Errno(errno::EINVAL))?; + let mut content = String::new(); + + let mut file = File::open(path_c.as_c_str().into(), fcntl::O_RDONLY)?; + file.read_to_string(&mut content) + .map_err(|_| Errno(errno::EIO))?; + + let toml = PosixLocaleDef::parse(&content); + Ok(LocaleData::new(CString::from_str(name).unwrap(), toml)) +} diff --git a/src/header/locale/redox.rs b/src/header/locale/redox.rs deleted file mode 100644 index 88abaf3030..0000000000 --- a/src/header/locale/redox.rs +++ /dev/null @@ -1,32 +0,0 @@ -use core::str::FromStr; - -use alloc::{boxed::Box, ffi::CString, string::String}; - -use crate::{ - c_str::CStr, - error::Errno, - fs::File, - header::{ - errno, fcntl, - locale::{ - data::{LocaleData, RawLocale}, - locale_t, - }, - }, - io::Read, -}; - -pub fn load_locale_file(name: &str) -> Result, Errno> { - let mut path = String::from("/etc/locale.d/"); - path.push_str(name); - - let path_c = CString::new(path).map_err(|_| Errno(errno::EINVAL))?; - let mut content = String::new(); - - let mut file = File::open(path_c.as_c_str().into(), fcntl::O_RDONLY)?; - file.read_to_string(&mut content) - .map_err(|_| Errno(errno::EIO))?; - - let toml = RawLocale::parse(&content); - Ok(LocaleData::new(CString::from_str(name).unwrap(), toml)) -} diff --git a/tests/locale/newlocale.c b/tests/locale/newlocale.c index 0dbb70b5f1..26011386f4 100644 --- a/tests/locale/newlocale.c +++ b/tests/locale/newlocale.c @@ -10,7 +10,11 @@ int main() { locale_t locale1 = newlocale(LC_ALL_MASK, "non-existent-locale", (locale_t)0); assert(locale1 == (locale_t)0); - // TODO: test with existing custom locale + // TODO: locale files inside redoxer? +#ifndef __redox__ + locale_t locale2 = newlocale(LC_ALL_MASK, "en_US", (locale_t)0); + assert(locale2 != (locale_t)0); +#endif return 0; } From e0d41a17e7255ca742735b374b7ead74333a92c4 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sun, 28 Dec 2025 15:45:17 +0700 Subject: [PATCH 4/4] Disable test requiring custom locales --- tests/locale/newlocale.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/locale/newlocale.c b/tests/locale/newlocale.c index 26011386f4..c9d16b2cc6 100644 --- a/tests/locale/newlocale.c +++ b/tests/locale/newlocale.c @@ -10,11 +10,9 @@ int main() { locale_t locale1 = newlocale(LC_ALL_MASK, "non-existent-locale", (locale_t)0); assert(locale1 == (locale_t)0); - // TODO: locale files inside redoxer? -#ifndef __redox__ - locale_t locale2 = newlocale(LC_ALL_MASK, "en_US", (locale_t)0); - assert(locale2 != (locale_t)0); -#endif + // TODO: locale files inside redoxer (linux and redox)? + // locale_t locale2 = newlocale(LC_ALL_MASK, "en_US", (locale_t)0); + // assert(locale2 != (locale_t)0); return 0; }