Skip to main content

relibc/header/locale/
mod.rs

1//! `locale.h` implementation.
2//!
3//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/locale.h.html>.
4
5use alloc::{boxed::Box, ffi::CString, string::String};
6use core::{ptr, str::FromStr};
7
8use crate::{
9    c_str::CStr,
10    error::{Errno, ResultExtPtrMut},
11    fs::File,
12    header::{errno, fcntl},
13    io::Read,
14    platform::types::{c_char, c_int},
15};
16
17// Can't use &str because of the mutability
18static mut C_LOCALE: [c_char; 2] = [b'C' as c_char, 0];
19
20mod constants;
21use constants::*;
22mod data;
23use data::*;
24
25use super::bits_locale_t::locale_t;
26/// constant struct to "C" or "POSIX" locale
27/// mutable because POSIX demands a mutable pointer
28static mut POSIX_LOCALE: lconv = posix_lconv();
29pub const LC_GLOBAL_LOCALE: locale_t = -1isize as locale_t;
30/// process-wide locale, used by setlocale() and localeconv()
31static mut GLOBAL_LOCALE: *mut GlobalLocaleData = ptr::null_mut();
32/// thread-wide locale, used by uselocale() and localeconv()
33#[thread_local]
34pub(crate) static mut THREAD_LOCALE: *mut LocaleData = ptr::null_mut();
35
36/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/localeconv.html>.
37#[unsafe(no_mangle)]
38pub unsafe extern "C" fn localeconv() -> *mut lconv {
39    let current = unsafe { uselocale(ptr::null_mut()) };
40    if current == LC_GLOBAL_LOCALE || current.is_null() {
41        if !unsafe { GLOBAL_LOCALE.is_null() } {
42            // safety: GLOBAL_LOCALE is never set to null again
43            unsafe { &raw mut (*GLOBAL_LOCALE).data.lconv }
44        } else {
45            &raw mut POSIX_LOCALE
46        }
47    } else {
48        let current = current.cast::<LocaleData>();
49        unsafe { &raw mut (*current).lconv }
50    }
51}
52
53/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/setlocale.html>.
54#[unsafe(no_mangle)]
55pub unsafe extern "C" fn setlocale(category: c_int, locale: *const c_char) -> *mut c_char {
56    if unsafe { GLOBAL_LOCALE.is_null() } {
57        let new_global = GlobalLocaleData::new();
58        unsafe { GLOBAL_LOCALE = Box::into_raw(new_global) };
59    };
60    let Some(global) = (unsafe { GLOBAL_LOCALE.as_mut() }) else {
61        return ptr::null_mut();
62    };
63
64    if locale.is_null() {
65        let Some(name) = global.get_name(category) else {
66            return ptr::null_mut();
67        };
68        return name.as_ptr().cast_mut();
69    }
70
71    let name = unsafe { CStr::from_ptr(locale).to_str().unwrap_or("C") };
72
73    let locale_file = if name.is_empty() || name == "C" || name == "POSIX" {
74        // TODO: name == "" should read from LANG env
75        Ok(LocaleData::posix())
76    } else {
77        load_locale_file(name)
78    };
79
80    match locale_file {
81        Ok(loc_ptr) => {
82            global.data.copy_category(&loc_ptr, category);
83            let Some(name) = global.set_name(category, CString::from_str(name).unwrap()) else {
84                return ptr::null_mut();
85            };
86            name.as_ptr().cast_mut()
87        }
88        Err(_) => ptr::null_mut(),
89    }
90}
91
92/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/uselocale.html>.
93#[unsafe(no_mangle)]
94pub unsafe extern "C" fn uselocale(newloc: locale_t) -> locale_t {
95    let old_loc = if unsafe { THREAD_LOCALE.is_null() } {
96        LC_GLOBAL_LOCALE
97    } else {
98        (unsafe { THREAD_LOCALE }) as locale_t
99    };
100
101    if !newloc.is_null() {
102        unsafe {
103            THREAD_LOCALE = if newloc == LC_GLOBAL_LOCALE {
104                ptr::null_mut()
105            } else {
106                newloc.cast::<LocaleData>()
107            }
108        };
109    }
110
111    old_loc
112}
113
114/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/newlocale.html>.
115#[unsafe(no_mangle)]
116pub unsafe extern "C" fn newlocale(mask: c_int, locale: *const c_char, base: locale_t) -> locale_t {
117    let name = unsafe { CStr::from_ptr(locale) }
118        .to_string_lossy()
119        .into_owned();
120    let name = name.as_str();
121    let mut new_locale = if name.is_empty() || name == "C" || name == "POSIX" {
122        // TODO: name == "" should read from LANG env
123        Ok(LocaleData::posix())
124    } else {
125        load_locale_file(name)
126    };
127    if base != LC_GLOBAL_LOCALE {
128        // borrowing here
129        let base = base.cast_const().cast::<LocaleData>();
130        if let Ok(new_locale) = new_locale.as_mut()
131            && let Some(base) = unsafe { base.as_ref() }
132        {
133            // copy old values if not containing the mask
134            if (mask & LC_NUMERIC_MASK) == 0 {
135                new_locale.copy_category(base, LC_NUMERIC);
136            }
137            if (mask & LC_MONETARY_MASK) == 0 {
138                new_locale.copy_category(base, LC_MONETARY);
139            }
140            // TODO: other categories?
141        }
142    }
143    new_locale.or_errno_null_mut().cast()
144}
145
146/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/freelocale.html>.
147#[unsafe(no_mangle)]
148pub unsafe extern "C" fn freelocale(loc: locale_t) {
149    if !loc.is_null() && loc != LC_GLOBAL_LOCALE {
150        drop(unsafe { Box::from_raw(loc.cast::<LocaleData>()) });
151    }
152}
153
154/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/duplocale.html>.
155#[unsafe(no_mangle)]
156pub unsafe extern "C" fn duplocale(loc: locale_t) -> locale_t {
157    if loc.is_null() {
158        // TODO: errno?
159        loc
160    } else if loc == LC_GLOBAL_LOCALE {
161        Box::into_raw(LocaleData::posix()) as locale_t
162    } else {
163        // borrowing here
164        let loc = loc.cast_const().cast::<LocaleData>();
165        Box::into_raw(unsafe { Box::from((*loc).clone()) }) as locale_t
166    }
167}
168
169pub(crate) fn load_locale_file(name: &str) -> Result<Box<LocaleData>, Errno> {
170    let mut path = String::from("/usr/share/i18n/locales/");
171    path.push_str(name);
172
173    let path_c = CString::new(path).map_err(|_| Errno(errno::EINVAL))?;
174    let mut content = String::new();
175
176    let mut file = File::open(path_c.as_c_str().into(), fcntl::O_RDONLY)?;
177    file.read_to_string(&mut content)
178        .map_err(|_| Errno(errno::EIO))?;
179
180    let toml = PosixLocaleDef::parse(&content);
181    Ok(LocaleData::new(CString::from_str(name).unwrap(), toml))
182}