Skip to main content

rust_icu_udata/
lib.rs

1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::path::Path;
16use std::ffi;
17
18use {
19    rust_icu_common as common, rust_icu_sys as sys, rust_icu_sys::versioned_function,
20    std::convert::TryFrom, std::os::raw,
21};
22
23/// Variants of [UDataMemory].
24#[derive(Debug)]
25enum Rep {
26    /// The data memory is backed by a user-supplied buffer.
27    /// The Vec<u8> is never read; it exists solely to keep the buffer alive
28    /// while ICU holds a raw pointer to it via udata_setCommonData.
29    #[allow(dead_code)]
30    Buffer(Vec<u8>),
31    /// The data memory is backed by a resource file.
32    Resource(
33        // This would have been std::ptr::NonNull if we didn't have to
34        // implement Send and Sync.
35        // We only ever touch this pointer in Rust when we initialize
36        // Rep::Resource, and when we dealocate Rep::Resource.
37        *const sys::UDataMemory,
38    ),
39}
40
41// Safety: The *const sys::UDataMemory above is only used by the underlying C++
42// library.
43unsafe impl Send for Rep {}
44unsafe impl Sync for Rep {}
45
46/// Sets the directory from which to load ICU data resources.
47///
48/// Implements `u_setDataDirectory`.
49pub fn set_data_directory(dir: &Path) {
50    let dir_cstr = ffi::CString::new
51        (dir.to_str().expect("this should never be a runtime error"))
52        .expect("this should never be a runtim error");
53    unsafe {
54        versioned_function!(u_setDataDirectory)(dir_cstr.as_ptr())
55    };
56}
57
58/// The type of the ICU resource requested.  Some standard resources have their
59/// canned types. In case you run into one that is not captured here, use `Custom`,
60/// and consider sending a pull request to add the new resource type.
61pub enum Type {
62    /// An empty resource type. This is ostensibly allowed, but unclear when
63    /// it is applicable.
64    Empty,
65    /// The unpacked resource type, equivalent to "res" in ICU4C.
66    Res,
67    /// The cnv resource type, equivalent to "cnv" in ICU4C.
68    Cnv,
69    /// The "common" data type, equivalent to "dat" in ICU4C.
70    Dat,
71    /// A custom data type, in case none of the above fit your use case. It
72    /// is not clear whether this would ever be useful, but the ICU4C API
73    /// allows for it, so we must too.
74    Custom(String),
75}
76
77impl AsRef<str> for Type {
78    fn as_ref(&self) -> &str {
79        match self {
80            Type::Empty => &"",
81            Type::Res => &"res",
82            Type::Dat => &"dat",
83            Type::Cnv => &"cnv",
84            Type::Custom(ref s) => &s,
85        }
86    }
87}
88
89/// Implements `UDataMemory`.
90///
91/// Represents data memory backed by a borrowed memory buffer used for loading ICU data.
92/// [UDataMemory] is very much not thread safe, as it affects the global state of the ICU library.
93/// This suggests that the best way to use this data is to load it up in a main thread, or access
94/// it through a synchronized wrapper.
95#[derive(Debug)]
96pub struct UDataMemory {
97    // The internal representation of [UDataMemory].
98    // May vary, depending on the way the struct is created.
99    //
100    // See: [UDataMemory::try_from] and [UDataMemory::open].
101    rep: Rep,
102}
103
104impl Drop for UDataMemory {
105    // Implements `u_cleanup`.
106    fn drop(&mut self) {
107        if let Rep::Resource(r) = self.rep {
108            unsafe {
109                // Safety: there is no other way to close the memory that the
110                // underlying C++ library uses but to pass it into this function.
111                versioned_function!(udata_close)(r as *mut sys::UDataMemory)
112            };
113        }
114        // Without this, resource references will remain, but memory will be gone.
115        unsafe {
116            // Safety: no other way to call this function.
117            versioned_function!(u_cleanup)()
118        };
119    }
120}
121
122impl TryFrom<Vec<u8>> for crate::UDataMemory {
123    type Error = common::Error;
124    /// Makes a UDataMemory out of a buffer.
125    ///
126    /// Implements `udata_setCommonData`.
127    fn try_from(buf: Vec<u8>) -> Result<Self, Self::Error> {
128        let mut status = sys::UErrorCode::U_ZERO_ERROR;
129        // Expects that buf is a valid pointer and that it contains valid
130        // ICU data.  If data is invalid, an error status will be set.
131        // No guarantees for invalid pointers.
132        unsafe {
133            versioned_function!(udata_setCommonData)(
134                buf.as_ptr() as *const raw::c_void,
135                &mut status,
136            );
137        };
138        common::Error::ok_or_warning(status)?;
139        Ok(UDataMemory { rep: Rep::Buffer(buf) })
140    }
141}
142
143impl crate::UDataMemory {
144
145    /// Uses the resources from the supplied resource file.
146    ///
147    /// This may end up being more efficient compared to loading from a buffer,
148    /// as ostensibly the resources would be memory mapped to only the needed
149    /// parts.
150    ///
151    /// - The `path` is the file path at which to find the resource file. Ostensibly
152    /// specifying `None` here will load from the "default" ICU_DATA path.
153    /// I have not been able to confirm this.
154    ///
155    /// - The `a_type` is the type of the resource file. It is not clear whether
156    /// the resource file type is a closed or open set, so we provide for both
157    /// possibilities.
158    ///
159    /// - The `name` is the name of the resource file. It is documented nullable
160    ///   in the ICU documentation. Pass `None` here to pass nullptr to the
161    ///   underlying C API.
162    ///
163    /// Presumably using `UDataMemory::open(Some("/dir/too"), Type::Res, Some("filename")` would
164    /// attempt to load ICU data from `/dir/too/filename.res`, as well as some other
165    /// canonical permutations of the above.  The full documentation is
166    /// [here][1], although I could not confirm that the documentation is actually
167    /// describing what the code does.  Also, using `None` at appropriate places
168    /// seems to be intended to load data from [some "default" sites][2]. I have
169    /// however observed that the actual behavior diverges from that documentation.
170    ///
171    /// Implements `udata_open`.
172    ///
173    /// [1]: https://unicode-org.github.io/icu/userguide/icu_data/#how-data-loading-works
174    /// [2]: https://unicode-org.github.io/icu/userguide/icu_data/#icu-data-directory
175    pub fn open(path: Option<&Path>, a_type: Type, name: Option<&str>) -> Result<Self, common::Error> {
176        let mut status = sys::UErrorCode::U_ZERO_ERROR;
177
178        let path_cstr = path.map(|s| { ffi::CString::new(s.to_str().expect("should never be a runtime error")).unwrap()});
179        let name_cstr = name.map(|s| { ffi::CString::new(s).expect("should never be a runtime error") } );
180        let type_cstr = ffi::CString::new(a_type.as_ref()).expect("should never be a runtime errror");
181
182        let rep = Self::get_resource(
183            path_cstr.as_ref().map(|s| s.as_c_str()),
184            type_cstr.as_c_str(), 
185            name_cstr.as_ref().map(|s| s.as_c_str()), 
186            &mut status);
187        common::Error::ok_or_warning(status)?;
188
189        // Make sure that all CStrs outlive the call to Self::get_resource. It is
190        // all too easy to omit `path_cstr.as_ref()` above, resulting in *_cstr
191        // being destroyed before a call to Self::get_resource happens. Fun.
192        let (_a, _b, _c) = (path_cstr, name_cstr, type_cstr);
193        Ok(crate::UDataMemory{ rep })
194    }
195
196    fn get_resource(path: Option<&ffi::CStr>, a_type: &ffi::CStr, name: Option<&ffi::CStr>, status: &mut sys::UErrorCode) -> Rep {
197        unsafe {
198            // Safety: we do what we must to call the underlying unsafe C API, and only return an
199            // opaque enum, to ensure that no rust client code may touch the raw pointer.
200            assert!(common::Error::is_ok(*status));
201
202            // Would be nicer if there were examples of udata_open usage to
203            // verify this.
204            let rep: *const sys::UDataMemory = versioned_function!(udata_open)(
205                path.map(|s| s.as_ptr()).unwrap_or(std::ptr::null()),
206                a_type.as_ptr(),
207                name.map(|c| c.as_ptr()).unwrap_or(std::ptr::null()),
208                status);
209            // Sadly we can not use NonNull, as we can not make the resulting
210            // type Sync or Send.
211            assert!(!rep.is_null());
212            Rep::Resource(rep)
213        }
214    }
215}
216
217#[cfg(test)]
218mod tests {
219
220    use super::*;
221    use std::sync::{Mutex, Weak, Arc};
222    use std::thread;
223
224    // We don't use UDataMemory in threaded contexts, but our users do. So let's
225    // ensure we can do this.
226    #[test]
227    fn send_sync_impl() {
228        let memory: Arc<Mutex<Weak<UDataMemory>>>= Arc::new(Mutex::new(Weak::new()));
229        // Ensure Sync.
230        let _clone = memory.clone();
231        thread::spawn(move || {
232            // Ensure Send.
233            let _m = memory;
234        });
235    }
236
237    #[test]
238    fn send_impl() {
239        let memory: Weak<UDataMemory> = Weak::new();
240        let _clone = memory.clone();
241        thread::spawn(move || {
242            let _m = memory;
243        });
244    }
245}