Implement more locale.h functions

This commit is contained in:
Wildan M
2025-12-27 22:24:41 +07:00
parent be446db7c3
commit a5035d68e3
5 changed files with 534 additions and 57 deletions
-3
View File
@@ -7,6 +7,3 @@ cpp_compat = true
[enum]
prefix_with_name = true
[export]
include = ["locale_t"]
+16
View File
@@ -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;
+346
View File
@@ -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 <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/locale.h.html>.
/// 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<u8>,
pub int_curr_symbol: CString,
pub currency_symbol: CString,
pub mon_decimal_point: CString,
pub mon_thousands_sep: CString,
pub mon_grouping: Vec<u8>,
pub positive_sign: CString,
pub negative_sign: CString,
}
unsafe impl Sync for LocaleData {}
impl LocaleData {
pub fn new(name: CString, toml: RawLocale) -> Box<Self> {
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<Self> {
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<String>) -> CString {
opt.and_then(|s| CString::new(s).ok())
.unwrap_or_else(|| CString::new("").unwrap())
}
fn to_grouping(opt: Option<Vec<c_char>>) -> Vec<u8> {
let mut v: Vec<u8> = 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<Self> {
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<String>,
pub thousands_sep: Option<String>,
pub grouping: Option<Vec<c_char>>,
pub int_curr_symbol: Option<String>,
pub currency_symbol: Option<String>,
pub mon_decimal_point: Option<String>,
pub mon_thousands_sep: Option<String>,
pub mon_grouping: Option<Vec<c_char>>,
pub positive_sign: Option<String>,
pub negative_sign: Option<String>,
pub int_frac_digits: Option<c_char>,
pub frac_digits: Option<c_char>,
pub p_cs_precedes: Option<c_char>,
pub p_sep_by_space: Option<c_char>,
pub n_cs_precedes: Option<c_char>,
pub n_sep_by_space: Option<c_char>,
pub p_sign_posn: Option<c_char>,
pub n_sign_posn: Option<c_char>,
pub int_p_cs_precedes: Option<c_char>,
pub int_p_sep_by_space: Option<c_char>,
pub int_n_cs_precedes: Option<c_char>,
pub int_n_sep_by_space: Option<c_char>,
pub int_p_sign_posn: Option<c_char>,
pub int_n_sign_posn: Option<c_char>,
}
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<c_char> {
val.split(',')
.filter_map(|s| s.trim().parse::<c_char>().ok())
.collect()
}
}
+140 -54
View File
@@ -1,74 +1,160 @@
//! `locale.h` implementation.
//!
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/locale.h.html>.
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap07.html>
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 <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/locale.h.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/localeconv.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/setlocale.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/newlocale.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/freelocale.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/duplocale.html>.
#[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
}
}
+32
View File
@@ -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<Box<LocaleData>, 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))
}