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}