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..aef2df910d --- /dev/null +++ b/src/header/locale/data.rs @@ -0,0 +1,395 @@ +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, defs: PosixLocaleDef) -> Box { + let mut data = Box::new(LocaleData { + name, + 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 = 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(), PosixLocaleDef::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.unwrap_or_else(|| CString::new("").unwrap()) + } + + 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 { + 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 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, + 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 PosixLocaleDef { + //! See + pub fn parse(content: &str) -> Self { + let mut locale = PosixLocaleDef::default(); + + let mut lines = content.lines(); + loop { + let Some(line) = lines.next() else { + break; + }; + + let trimmed = line.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + + 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 = 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` -> [ 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 acc250f688..f03ac3560f 100644 --- a/src/header/locale/mod.rs +++ b/src/header/locale/mod.rs @@ -2,73 +2,170 @@ //! //! 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, fcntl}, + 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::*; + 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 + } +} + +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/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..c9d16b2cc6 --- /dev/null +++ b/tests/locale/newlocale.c @@ -0,0 +1,18 @@ +#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: 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; +} 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