From 9653a782d1da4269227187637a78d715070fb9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Wed, 28 Sep 2022 23:14:18 +0200 Subject: [PATCH 01/16] Implement for Haiku Tested on Haiku R1/beta3. --- src/lib.rs | 1 + src/platform.rs | 2 +- src/tz_haiku.rs | 256 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 src/tz_haiku.rs diff --git a/src/lib.rs b/src/lib.rs index ffff3a7..df5fa9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ path = "tz_illumos.rs" )] #[cfg_attr(target_os = "android", path = "tz_android.rs")] +#[cfg_attr(target_os = "haiku", path = "tz_haiku.rs")] mod platform; /// Error types diff --git a/src/platform.rs b/src/platform.rs index 82b7f17..5992bf3 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -5,5 +5,5 @@ pub fn get_timezone_inner() -> std::result::Result(T); + + impl Endian { + pub(super) fn get(self, endian: Endianness) -> u16 { + match endian { + Endianness::Little => u16::from_le(self.0), + Endianness::Big => u16::from_be(self.0), + } + } + } + + impl Endian { + pub(super) fn get(self, endian: Endianness) -> u32 { + match endian { + Endianness::Little => u32::from_le(self.0), + Endianness::Big => u32::from_be(self.0), + } + } + } +} + +pub type AreaId = i32; +pub type PortId = i32; +pub type TeamId = i32; +pub type TypeCode = [u8; 4]; +pub type Format = [u8; 4]; + +#[derive(Debug, Clone, Copy, Default)] +#[repr(C, packed)] +struct MessageHeader { + format: Format, + _what: Endian, + _flags: Endian, + + _target: Endian, + _current_specifier: Endian, + _area_id: Endian, + + _reply_port: Endian, + _reply_target: Endian, + _reply_team: Endian, + + _data_size: Endian, + field_count: Endian, + hash_table_size: Endian, + _hash_table: [Endian; 0], +} + +#[derive(Debug, Clone, Copy, Default)] +#[repr(C, packed)] +struct FieldHeader { + _flags: Endian, + name_length: Endian, + typ: TypeCode, + _count: Endian, + data_size: Endian, + _offset: Endian, + _next_field: Endian, +} + +/// SAFETY: `Self` must be packed, and not contain any references or niches +unsafe trait ReadPod: 'static + Copy + Default + Sized { + fn read_from(mut cursor: impl Read) -> std::io::Result { + // It would be possible to use MaybeUninit, but reading the config will be orders of + // magnitude slower than the overhead of nulling `this` first. + let mut this = Self::default(); + cursor.read(this.as_bytes_mut())?; + Ok(this) + } + + fn as_bytes_mut(&mut self) -> &mut [u8] { + let start = std::ptr::addr_of_mut!(*self) as *mut u8; + // SAFETY: the trait's contract ensures that `self` is POD + unsafe { core::slice::from_raw_parts_mut(start, size_of_val(self)) } + } +} + +unsafe impl ReadPod for MessageHeader {} +unsafe impl ReadPod for FieldHeader {} +unsafe impl ReadPod for Endian {} + +const LITTLE: Format = *b"HMF1"; +const BIG: Format = *b"1FMH"; +const RTSC: TypeCode = *b"RTSC"; // I have no idea what "RTSC" stands for. +const TIMEZONE: &[u8] = b"timezone\0"; + +#[derive(Debug, Clone, Copy)] +enum Endianness { + Little, + Big, +} + +impl TryFrom for Endianness { + type Error = crate::GetTimezoneError; + + fn try_from(value: Format) -> Result { + match value { + LITTLE => Ok(Endianness::Little), + BIG => Ok(Endianness::Big), + _ => Err(GetTimezoneError::FailedParsingString), + } + } +} + +pub(crate) fn get_timezone_inner() -> Result { + const CONFIG_PATH: &str = "/boot/home/config/settings/Time settings"; + + let mut buf = Vec::new(); + OpenOptions::new() + .read(true) + .open(CONFIG_PATH)? + .read_to_end(&mut buf)?; + decode_config(&buf) +} + +fn decode_config(config: &[u8]) -> Result { + // We cannot simply cast slices, because the data is most likely misaligned. + // Use a `Cursor` instead. + let mut cursor = Cursor::new(config); + let hdr = MessageHeader::read_from(&mut cursor)?; + let endian = Endianness::try_from(hdr.format)?; + + // Skip hash table (no need to search in an 1-element array) + let hash_table_size = hdr + .hash_table_size + .get(endian) + .checked_mul(size_of::() as _) + .ok_or(crate::GetTimezoneError::FailedParsingString)? + .try_into() + .map_err(|_| crate::GetTimezoneError::FailedParsingString)?; + cursor.consume(hash_table_size); + + // Iterate over the entries. + for _ in 0..hdr.field_count.get(endian) { + let field = FieldHeader::read_from(&mut cursor)?; + let name_length = field.name_length.get(endian) as u32; + let data_size = field.data_size.get(endian); + + // take a view of the current field and advance the cursor + let field_size = name_length + .checked_add(data_size) + .ok_or(crate::GetTimezoneError::FailedParsingString)?; + let mut field_cursor = cursor.clone().take(field_size as u64); + cursor.consume(field_size as usize); + + // The field does not contain the expected data, continue. + if field.typ != RTSC || name_length != TIMEZONE.len() as u32 { + continue; + } + let mut tz = [0u8; TIMEZONE.len()]; + field_cursor.read_exact(&mut tz)?; + if tz != TIMEZONE { + continue; + } + + // The name should not be empty, or excessively long. + let data_len = Endian::::read_from(&mut field_cursor)?.get(endian); + if data_len < 1 || data_len > 32 { + continue; + } + + // Try to read as UTF-8 (should be ASCII) + let mut buf = &mut [0u8; 32][..data_len as usize]; + field_cursor.read_exact(&mut buf)?; + if let Ok(name) = CStr::from_bytes_with_nul(buf) { + if let Ok(name) = name.to_str() { + return Ok(name.to_owned()); + } + } + } + Err(crate::GetTimezoneError::FailedParsingString) +} + +#[test] +fn test() { + #[rustfmt::skip] + let data = [ + // format + 0x48, 0x4d, 0x46, 0x31, + // what + 0x00, 0x00, 0x00, 0x00, + // flags + 0x01, 0x00, 0x00, 0x00, + // target + 0xff, 0xff, 0xff, 0xff, + // current_specifier + 0xff, 0xff, 0xff, 0xff, + // area_id + 0xff, 0xff, 0xff, 0xff, + // reply_port + 0xff, 0xff, 0xff, 0xff, + // reply_target + 0xff, 0xff, 0xff, 0xff, + // reply_team + 0xff, 0xff, 0xff, 0xff, + // data_size + 0x1b, 0x00, 0x00, 0x00, + // field_count + 0x01, 0x00, 0x00, 0x00, + // hash_table_size + 0x05, 0x00, 0x00, 0x00, + + // hash_table + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, + + // flags + 0x01, 0x00, + // name_length + 0x09, 0x00, + // type "RTSC" + 0x52, 0x54, 0x53, 0x43, + // count + 0x01, 0x00, 0x00, 0x00, + // data_size + 0x12, 0x00, 0x00, 0x00, + // offset + 0x00, 0x00, 0x00, 0x00, + // next_field + 0xff, 0xff, 0xff, 0xff, + + // "timezone" + NUL + 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x00, + + // length + "Europe/Berlin" + NUL + 0x0e, 0x00, 0x00, 0x00, + 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x00, + + // padding + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + ]; + + assert_eq!(decode_config(&data).unwrap(), "Europe/Berlin"); +} From e112085ec956918e306e83d3f4e5391f161cb1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Wed, 28 Sep 2022 23:17:46 +0200 Subject: [PATCH 02/16] Check Haiku in CI --- .github/workflows/rust.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2187697..c20576f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -212,6 +212,27 @@ jobs: - run: cargo check --all-targets - run: cargo clippy --all-targets -- -D warnings + no-docker-image-check-only: + strategy: + fail-fast: false + matrix: + target: + - i686-unknown-haiku + - x86_64-unknown-haiku + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Rust + id: actions-rs + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + components: rust-src + - run: cargo +nightly check --target ${{ matrix.target }} -Z build-std --examples + doc: runs-on: ubuntu-latest steps: From 8dbc0ea44b75a61215088331078f6a128d456421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Thu, 29 Sep 2022 03:13:51 +0200 Subject: [PATCH 03/16] Implement Haiku using system functions --- Cargo.toml | 6 + build.rs | 14 +++ include/impl_haiku.h | 9 ++ src/impl_haiku.cc | 40 +++++++ src/tz_haiku.rs | 265 +++---------------------------------------- 5 files changed, 85 insertions(+), 249 deletions(-) create mode 100644 build.rs create mode 100644 include/impl_haiku.h create mode 100644 src/impl_haiku.cc diff --git a/Cargo.toml b/Cargo.toml index 1e0b8ac..8bdfb4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,9 @@ wasm-bindgen = "0.2.70" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3" + +[target.'cfg(target_os = "haiku")'.dependencies] +cxx = "1.0.34" + +[target.'cfg(target_os = "haiku")'.build-dependencies] +cxx-build = "1.0.34" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..074b196 --- /dev/null +++ b/build.rs @@ -0,0 +1,14 @@ +fn main() { + #[cfg(target_os = "haiku")] + let _: () = { + cxx_build::bridge("src/tz_haiku.rs") + .file("src/impl_haiku.cc") + .flag_if_supported("-std=c++11") + .compile("tz_haiku"); + + println!("cargo:return-if-changed=include/impl_haiku.h"); + println!("cargo:return-if-changed=src/impl_haiku.cc"); + println!("cargo:return-if-changed=src/tz_haiku.rs"); + println!("cargo:rustc-link-lib=be"); + }; +} diff --git a/include/impl_haiku.h b/include/impl_haiku.h new file mode 100644 index 0000000..cd50fe8 --- /dev/null +++ b/include/impl_haiku.h @@ -0,0 +1,9 @@ +#pragma once + +#include "rust/cxx.h" + +#include + +namespace tz_haiku { + size_t get_tz(uint8_t *buf, size_t buf_len); +} diff --git a/src/impl_haiku.cc b/src/impl_haiku.cc new file mode 100644 index 0000000..ad5fef5 --- /dev/null +++ b/src/impl_haiku.cc @@ -0,0 +1,40 @@ +#include "iana-time-zone/include/impl_haiku.h" +#include "iana-time-zone/src/tz_haiku.rs.h" + +#include +#include +#include +#include +#include + +size_t ::tz_haiku::get_tz(uint8_t *buf, size_t buf_len) { + static_assert(sizeof(char) == sizeof(uint8_t), "Illegal char size"); + + if (!buf || !buf_len) { + return 0; + } + + BLocaleRoster *locale_roster(BLocaleRoster::Default()); + if (!locale_roster) { + return 0; + } + + BTimeZone tz(NULL, NULL); + if (locale_roster->GetDefaultTimeZone(&tz) != B_OK) { + return 0; + } + + BString bname(tz.ID()); + int32_t length(bname.Length()); + if (length <= 0 || size_t(length) > buf_len) { + return 0; + } + + const char *sname(bname.String()); + if (!sname) { + return 0; + } + + std::memcpy(buf, sname, length); + return length; +} diff --git a/src/tz_haiku.rs b/src/tz_haiku.rs index f6a3910..e06995b 100644 --- a/src/tz_haiku.rs +++ b/src/tz_haiku.rs @@ -1,256 +1,23 @@ -// Notice: Only Haiku's formats "MESSAGE_FORMAT_HAIKU{,_SWAPPED}" are implemented. -// BeOS's "MESSAGE_FORMAT_R5" and "MESSAGE_FORMAT_DANO" cannot be read. +#[cxx::bridge(namespace = "tz_haiku")] +mod ffi { + unsafe extern "C++" { + include!("iana-time-zone/include/impl_haiku.h"); -use std::convert::{TryFrom, TryInto}; -use std::ffi::CStr; -use std::fs::OpenOptions; -use std::io::{BufRead, Cursor, Read}; -use std::mem::{size_of, size_of_val}; - -use crate::GetTimezoneError; - -use self::opaque::Endian; - -// Put `Endian` into a module, so `Endian::0` is inaccessible. -mod opaque { - use super::Endianness; - - #[derive(Debug, Clone, Copy, Default)] - #[repr(transparent)] - pub(crate) struct Endian(T); - - impl Endian { - pub(super) fn get(self, endian: Endianness) -> u16 { - match endian { - Endianness::Little => u16::from_le(self.0), - Endianness::Big => u16::from_be(self.0), - } - } - } - - impl Endian { - pub(super) fn get(self, endian: Endianness) -> u32 { - match endian { - Endianness::Little => u32::from_le(self.0), - Endianness::Big => u32::from_be(self.0), - } - } - } -} - -pub type AreaId = i32; -pub type PortId = i32; -pub type TeamId = i32; -pub type TypeCode = [u8; 4]; -pub type Format = [u8; 4]; - -#[derive(Debug, Clone, Copy, Default)] -#[repr(C, packed)] -struct MessageHeader { - format: Format, - _what: Endian, - _flags: Endian, - - _target: Endian, - _current_specifier: Endian, - _area_id: Endian, - - _reply_port: Endian, - _reply_target: Endian, - _reply_team: Endian, - - _data_size: Endian, - field_count: Endian, - hash_table_size: Endian, - _hash_table: [Endian; 0], -} - -#[derive(Debug, Clone, Copy, Default)] -#[repr(C, packed)] -struct FieldHeader { - _flags: Endian, - name_length: Endian, - typ: TypeCode, - _count: Endian, - data_size: Endian, - _offset: Endian, - _next_field: Endian, -} - -/// SAFETY: `Self` must be packed, and not contain any references or niches -unsafe trait ReadPod: 'static + Copy + Default + Sized { - fn read_from(mut cursor: impl Read) -> std::io::Result { - // It would be possible to use MaybeUninit, but reading the config will be orders of - // magnitude slower than the overhead of nulling `this` first. - let mut this = Self::default(); - cursor.read(this.as_bytes_mut())?; - Ok(this) - } - - fn as_bytes_mut(&mut self) -> &mut [u8] { - let start = std::ptr::addr_of_mut!(*self) as *mut u8; - // SAFETY: the trait's contract ensures that `self` is POD - unsafe { core::slice::from_raw_parts_mut(start, size_of_val(self)) } - } -} - -unsafe impl ReadPod for MessageHeader {} -unsafe impl ReadPod for FieldHeader {} -unsafe impl ReadPod for Endian {} - -const LITTLE: Format = *b"HMF1"; -const BIG: Format = *b"1FMH"; -const RTSC: TypeCode = *b"RTSC"; // I have no idea what "RTSC" stands for. -const TIMEZONE: &[u8] = b"timezone\0"; - -#[derive(Debug, Clone, Copy)] -enum Endianness { - Little, - Big, -} - -impl TryFrom for Endianness { - type Error = crate::GetTimezoneError; - - fn try_from(value: Format) -> Result { - match value { - LITTLE => Ok(Endianness::Little), - BIG => Ok(Endianness::Big), - _ => Err(GetTimezoneError::FailedParsingString), - } + /// SAFETY: `buf` must be valid and be at least `buf_len` bytes long + unsafe fn get_tz(buf: *mut u8, buf_len: usize) -> usize; } } pub(crate) fn get_timezone_inner() -> Result { - const CONFIG_PATH: &str = "/boot/home/config/settings/Time settings"; - - let mut buf = Vec::new(); - OpenOptions::new() - .read(true) - .open(CONFIG_PATH)? - .read_to_end(&mut buf)?; - decode_config(&buf) -} - -fn decode_config(config: &[u8]) -> Result { - // We cannot simply cast slices, because the data is most likely misaligned. - // Use a `Cursor` instead. - let mut cursor = Cursor::new(config); - let hdr = MessageHeader::read_from(&mut cursor)?; - let endian = Endianness::try_from(hdr.format)?; - - // Skip hash table (no need to search in an 1-element array) - let hash_table_size = hdr - .hash_table_size - .get(endian) - .checked_mul(size_of::() as _) - .ok_or(crate::GetTimezoneError::FailedParsingString)? - .try_into() - .map_err(|_| crate::GetTimezoneError::FailedParsingString)?; - cursor.consume(hash_table_size); - - // Iterate over the entries. - for _ in 0..hdr.field_count.get(endian) { - let field = FieldHeader::read_from(&mut cursor)?; - let name_length = field.name_length.get(endian) as u32; - let data_size = field.data_size.get(endian); - - // take a view of the current field and advance the cursor - let field_size = name_length - .checked_add(data_size) - .ok_or(crate::GetTimezoneError::FailedParsingString)?; - let mut field_cursor = cursor.clone().take(field_size as u64); - cursor.consume(field_size as usize); - - // The field does not contain the expected data, continue. - if field.typ != RTSC || name_length != TIMEZONE.len() as u32 { - continue; - } - let mut tz = [0u8; TIMEZONE.len()]; - field_cursor.read_exact(&mut tz)?; - if tz != TIMEZONE { - continue; - } - - // The name should not be empty, or excessively long. - let data_len = Endian::::read_from(&mut field_cursor)?.get(endian); - if data_len < 1 || data_len > 32 { - continue; - } - - // Try to read as UTF-8 (should be ASCII) - let mut buf = &mut [0u8; 32][..data_len as usize]; - field_cursor.read_exact(&mut buf)?; - if let Ok(name) = CStr::from_bytes_with_nul(buf) { - if let Ok(name) = name.to_str() { - return Ok(name.to_owned()); - } - } + // The longest name in the IANA time zone database is 25 ASCII characters long. + let mut buf = [0u8; 32]; + // SAFETY: the buffer is valid and called function ensures that sizeof(u8) == sizeof(c_char) + let len = unsafe { ffi::get_tz(buf.as_mut_ptr(), buf.len()) }; + // The name should not be empty, or excessively long. + if len > 0 && len < buf.len() { + let s = std::str::from_utf8(&buf[..len]).map_err(|_| crate::GetTimezoneError::OsError)?; + Ok(s.to_owned()) + } else { + Err(crate::GetTimezoneError::OsError) } - Err(crate::GetTimezoneError::FailedParsingString) -} - -#[test] -fn test() { - #[rustfmt::skip] - let data = [ - // format - 0x48, 0x4d, 0x46, 0x31, - // what - 0x00, 0x00, 0x00, 0x00, - // flags - 0x01, 0x00, 0x00, 0x00, - // target - 0xff, 0xff, 0xff, 0xff, - // current_specifier - 0xff, 0xff, 0xff, 0xff, - // area_id - 0xff, 0xff, 0xff, 0xff, - // reply_port - 0xff, 0xff, 0xff, 0xff, - // reply_target - 0xff, 0xff, 0xff, 0xff, - // reply_team - 0xff, 0xff, 0xff, 0xff, - // data_size - 0x1b, 0x00, 0x00, 0x00, - // field_count - 0x01, 0x00, 0x00, 0x00, - // hash_table_size - 0x05, 0x00, 0x00, 0x00, - - // hash_table - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff, - - // flags - 0x01, 0x00, - // name_length - 0x09, 0x00, - // type "RTSC" - 0x52, 0x54, 0x53, 0x43, - // count - 0x01, 0x00, 0x00, 0x00, - // data_size - 0x12, 0x00, 0x00, 0x00, - // offset - 0x00, 0x00, 0x00, 0x00, - // next_field - 0xff, 0xff, 0xff, 0xff, - - // "timezone" + NUL - 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x00, - - // length + "Europe/Berlin" + NUL - 0x0e, 0x00, 0x00, 0x00, - 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x00, - - // padding - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - ]; - - assert_eq!(decode_config(&data).unwrap(), "Europe/Berlin"); } From 874da54fb2711dfffc3193c0998c3bab2facd77a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Thu, 29 Sep 2022 03:27:25 +0200 Subject: [PATCH 04/16] CI: Exclude haiku i686, but test -Zminimal-versions --- .github/workflows/rust.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c20576f..e4441fe 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -217,8 +217,11 @@ jobs: fail-fast: false matrix: target: - - i686-unknown-haiku + # - i686-unknown-haiku # Does not (cross) compile for 32bit - x86_64-unknown-haiku + versions: + - "" + - "-Zminimal-versions" runs-on: ubuntu-latest steps: - name: Checkout @@ -231,6 +234,10 @@ jobs: toolchain: nightly override: true components: rust-src + - name: Update lockfile + run: cargo generate-lockfile ${{ matrix.versions }} + env: + RUSTC_BOOTSTRAP: 1 - run: cargo +nightly check --target ${{ matrix.target }} -Z build-std --examples doc: From ac6d04354a52874258725f8e36218321bff7f822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Thu, 29 Sep 2022 16:14:41 +0200 Subject: [PATCH 05/16] Update src/tz_haiku.rs Co-authored-by: Ryan Lopopolo --- src/tz_haiku.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/tz_haiku.rs b/src/tz_haiku.rs index e06995b..a895981 100644 --- a/src/tz_haiku.rs +++ b/src/tz_haiku.rs @@ -14,10 +14,12 @@ pub(crate) fn get_timezone_inner() -> Result { // SAFETY: the buffer is valid and called function ensures that sizeof(u8) == sizeof(c_char) let len = unsafe { ffi::get_tz(buf.as_mut_ptr(), buf.len()) }; // The name should not be empty, or excessively long. - if len > 0 && len < buf.len() { - let s = std::str::from_utf8(&buf[..len]).map_err(|_| crate::GetTimezoneError::OsError)?; - Ok(s.to_owned()) - } else { - Err(crate::GetTimezoneError::OsError) + match buf.get(..len) { + Some(s) if s.is_empty() => Err(crate::GetTimezoneError::OsError), + Some(s) => { + let s = std::str::from_utf8(s).map_err(|_| crate::GetTimezoneError::OsError)?; + Ok(s.to_owned()) + } + None => Err(crate::GetTimezoneError::OsError), } } From 2ceab778e70f4cc2b45c28786b23a6102c86830d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Fri, 30 Sep 2022 05:43:29 +0200 Subject: [PATCH 06/16] Apply github:artichoke/clang-format --- .clang-format | 9 +++++++++ include/impl_haiku.h | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..444c17f --- /dev/null +++ b/.clang-format @@ -0,0 +1,9 @@ +--- +BasedOnStyle: Google +IndentWidth: 4 +ColumnLimit: 100 +--- +Language: Cpp +SortIncludes: false +TabWidth: 4 +UseTab: Never diff --git a/include/impl_haiku.h b/include/impl_haiku.h index cd50fe8..2d9c362 100644 --- a/include/impl_haiku.h +++ b/include/impl_haiku.h @@ -5,5 +5,5 @@ #include namespace tz_haiku { - size_t get_tz(uint8_t *buf, size_t buf_len); +size_t get_tz(uint8_t *buf, size_t buf_len); } From b6d8caa4fcb463109381e5101bbbff7f9be52429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Fri, 30 Sep 2022 05:45:08 +0200 Subject: [PATCH 07/16] Wrap C++ code in try-catch --- src/impl_haiku.cc | 58 +++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/impl_haiku.cc b/src/impl_haiku.cc index ad5fef5..16ecfb9 100644 --- a/src/impl_haiku.cc +++ b/src/impl_haiku.cc @@ -8,33 +8,37 @@ #include size_t ::tz_haiku::get_tz(uint8_t *buf, size_t buf_len) { - static_assert(sizeof(char) == sizeof(uint8_t), "Illegal char size"); - - if (!buf || !buf_len) { - return 0; - } - - BLocaleRoster *locale_roster(BLocaleRoster::Default()); - if (!locale_roster) { - return 0; - } - - BTimeZone tz(NULL, NULL); - if (locale_roster->GetDefaultTimeZone(&tz) != B_OK) { + try { + static_assert(sizeof(char) == sizeof(uint8_t), "Illegal char size"); + + if (!buf || !buf_len) { + return 0; + } + + BLocaleRoster *locale_roster(BLocaleRoster::Default()); + if (!locale_roster) { + return 0; + } + + BTimeZone tz(NULL, NULL); + if (locale_roster->GetDefaultTimeZone(&tz) != B_OK) { + return 0; + } + + BString bname(tz.ID()); + int32_t length(bname.Length()); + if (length <= 0 || size_t(length) > buf_len) { + return 0; + } + + const char *sname(bname.String()); + if (!sname) { + return 0; + } + + std::memcpy(buf, sname, length); + return length; + } catch (...) { return 0; } - - BString bname(tz.ID()); - int32_t length(bname.Length()); - if (length <= 0 || size_t(length) > buf_len) { - return 0; - } - - const char *sname(bname.String()); - if (!sname) { - return 0; - } - - std::memcpy(buf, sname, length); - return length; } From 9e068df80eee489347b5438063fad78214894e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Fri, 30 Sep 2022 05:46:43 +0200 Subject: [PATCH 08/16] Add comment about BString::String() --- src/impl_haiku.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/impl_haiku.cc b/src/impl_haiku.cc index 16ecfb9..34a1c1f 100644 --- a/src/impl_haiku.cc +++ b/src/impl_haiku.cc @@ -31,6 +31,8 @@ size_t ::tz_haiku::get_tz(uint8_t *buf, size_t buf_len) { return 0; } + // BString::String() returns a borrowed string. + // https://www.haiku-os.org/docs/api/classBString.html#ae4fe78b06c8e3310093b80305e14ba87 const char *sname(bname.String()); if (!sname) { return 0; From 742f223624a46326a7e4d304d4894c6c38950742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Fri, 30 Sep 2022 05:54:26 +0200 Subject: [PATCH 09/16] Use rust::Slice --- include/impl_haiku.h | 2 +- src/impl_haiku.cc | 8 ++++---- src/tz_haiku.rs | 10 ++++------ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/include/impl_haiku.h b/include/impl_haiku.h index 2d9c362..7bd9860 100644 --- a/include/impl_haiku.h +++ b/include/impl_haiku.h @@ -5,5 +5,5 @@ #include namespace tz_haiku { -size_t get_tz(uint8_t *buf, size_t buf_len); +size_t get_tz(rust::Slice buf); } diff --git a/src/impl_haiku.cc b/src/impl_haiku.cc index 34a1c1f..964c3ef 100644 --- a/src/impl_haiku.cc +++ b/src/impl_haiku.cc @@ -7,11 +7,11 @@ #include #include -size_t ::tz_haiku::get_tz(uint8_t *buf, size_t buf_len) { +size_t ::tz_haiku::get_tz(rust::Slice buf) { try { static_assert(sizeof(char) == sizeof(uint8_t), "Illegal char size"); - if (!buf || !buf_len) { + if (buf.empty()) { return 0; } @@ -27,7 +27,7 @@ size_t ::tz_haiku::get_tz(uint8_t *buf, size_t buf_len) { BString bname(tz.ID()); int32_t length(bname.Length()); - if (length <= 0 || size_t(length) > buf_len) { + if (length <= 0 || size_t(length) > buf.size()) { return 0; } @@ -38,7 +38,7 @@ size_t ::tz_haiku::get_tz(uint8_t *buf, size_t buf_len) { return 0; } - std::memcpy(buf, sname, length); + std::memcpy(buf.data(), sname, size_t(length)); return length; } catch (...) { return 0; diff --git a/src/tz_haiku.rs b/src/tz_haiku.rs index a895981..4e092c3 100644 --- a/src/tz_haiku.rs +++ b/src/tz_haiku.rs @@ -1,25 +1,23 @@ #[cxx::bridge(namespace = "tz_haiku")] mod ffi { + // SAFETY: in here "unsafe" is simply part of the syntax unsafe extern "C++" { include!("iana-time-zone/include/impl_haiku.h"); - /// SAFETY: `buf` must be valid and be at least `buf_len` bytes long - unsafe fn get_tz(buf: *mut u8, buf_len: usize) -> usize; + fn get_tz(buf: &mut [u8]) -> usize; } } pub(crate) fn get_timezone_inner() -> Result { // The longest name in the IANA time zone database is 25 ASCII characters long. let mut buf = [0u8; 32]; - // SAFETY: the buffer is valid and called function ensures that sizeof(u8) == sizeof(c_char) - let len = unsafe { ffi::get_tz(buf.as_mut_ptr(), buf.len()) }; + let len = ffi::get_tz(&mut buf); // The name should not be empty, or excessively long. match buf.get(..len) { - Some(s) if s.is_empty() => Err(crate::GetTimezoneError::OsError), + None | Some(b"") => Err(crate::GetTimezoneError::OsError), Some(s) => { let s = std::str::from_utf8(s).map_err(|_| crate::GetTimezoneError::OsError)?; Ok(s.to_owned()) } - None => Err(crate::GetTimezoneError::OsError), } } From 878a6b1f2758245c13a93985365c885397385535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Fri, 30 Sep 2022 06:03:35 +0200 Subject: [PATCH 10/16] CI: Add C lint --- .github/workflows/rust.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e4441fe..0f9b2ba 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -281,3 +281,21 @@ jobs: - run: if cargo build --lib --target x86_64-fortanix-unknown-sgx; then exit 1; fi # Should succeed: - run: cargo build --lib --target x86_64-fortanix-unknown-sgx --features fallback + + c: + name: Lint and format C + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Node.js runtime + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install npm + run: npm i -f -g npm@8.16.0 + + - name: Lint and check formatting with clang-format + run: npx github:artichoke/clang-format --check From 23d7589d7a494e8be7a09d58bc5f772b99c090cc Mon Sep 17 00:00:00 2001 From: Ryan Lopopolo Date: Thu, 29 Sep 2022 21:31:06 -0700 Subject: [PATCH 11/16] Bump minimal version of js-sys to 0.3.50 This fixes the compilation errors with -Zminimal-versions, but I don't know why. I couldn't find anything in the wasm-bindgen changelogs, but I found these: - https://github.com/libp2p/rust-libp2p/issues/2020 - https://github.com/libp2p/rust-libp2p/pull/2023 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8bdfb4b..bfef9ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ core-foundation-sys = "0.8.3" winapi = { version = "0.3.9", features = ["activation", "combaseapi", "objbase", "roapi", "winerror", "winstring"] } [target.'cfg(target_arch = "wasm32")'.dependencies] -js-sys = "0.3.46" +js-sys = "0.3.50" wasm-bindgen = "0.2.70" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] From 9327373d466412d74e0af17993658310451f327b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Fri, 30 Sep 2022 06:51:39 +0200 Subject: [PATCH 12/16] Add new variables for signed and unsigned length --- src/impl_haiku.cc | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/impl_haiku.cc b/src/impl_haiku.cc index 964c3ef..191afb8 100644 --- a/src/impl_haiku.cc +++ b/src/impl_haiku.cc @@ -26,8 +26,13 @@ size_t ::tz_haiku::get_tz(rust::Slice buf) { } BString bname(tz.ID()); - int32_t length(bname.Length()); - if (length <= 0 || size_t(length) > buf.size()) { + int32_t ilength(bname.Length()); + if (ilength <= 0) { + return 0; + } + + size_t length(ilength); + if (length > buf.size()) { return 0; } @@ -38,7 +43,7 @@ size_t ::tz_haiku::get_tz(rust::Slice buf) { return 0; } - std::memcpy(buf.data(), sname, size_t(length)); + std::memcpy(buf.data(), sname, length); return length; } catch (...) { return 0; From e6c12b34333e2206080798727ad997137ebb83c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Fri, 30 Sep 2022 09:47:26 +0200 Subject: [PATCH 13/16] Split Haiku code into its own crate --- Cargo.toml | 7 +++-- build.rs | 14 --------- haiku/Cargo.toml | 15 ++++++++++ haiku/LICENSE-APACHE | 1 + haiku/LICENSE-MIT | 1 + haiku/README.md | 8 +++++ haiku/build.rs | 11 +++++++ .../src/implementation.cc | 6 ++-- include/impl_haiku.h => haiku/src/interface.h | 2 +- haiku/src/lib.rs | 29 +++++++++++++++++++ src/tz_haiku.rs | 22 +------------- 11 files changed, 74 insertions(+), 42 deletions(-) delete mode 100644 build.rs create mode 100644 haiku/Cargo.toml create mode 120000 haiku/LICENSE-APACHE create mode 120000 haiku/LICENSE-MIT create mode 100644 haiku/README.md create mode 100644 haiku/build.rs rename src/impl_haiku.cc => haiku/src/implementation.cc (87%) rename include/impl_haiku.h => haiku/src/interface.h (75%) create mode 100644 haiku/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index bfef9ea..8f82fd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,8 @@ wasm-bindgen = "0.2.70" wasm-bindgen-test = "0.3" [target.'cfg(target_os = "haiku")'.dependencies] -cxx = "1.0.34" +iana-time-zone-haiku = { version = "0.1.0", path = "haiku" } -[target.'cfg(target_os = "haiku")'.build-dependencies] -cxx-build = "1.0.34" +[workspace] +members = [".", "haiku"] +default-members = ["."] diff --git a/build.rs b/build.rs deleted file mode 100644 index 074b196..0000000 --- a/build.rs +++ /dev/null @@ -1,14 +0,0 @@ -fn main() { - #[cfg(target_os = "haiku")] - let _: () = { - cxx_build::bridge("src/tz_haiku.rs") - .file("src/impl_haiku.cc") - .flag_if_supported("-std=c++11") - .compile("tz_haiku"); - - println!("cargo:return-if-changed=include/impl_haiku.h"); - println!("cargo:return-if-changed=src/impl_haiku.cc"); - println!("cargo:return-if-changed=src/tz_haiku.rs"); - println!("cargo:rustc-link-lib=be"); - }; -} diff --git a/haiku/Cargo.toml b/haiku/Cargo.toml new file mode 100644 index 0000000..0340bd5 --- /dev/null +++ b/haiku/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "iana-time-zone-haiku" +description = "iana-time-zone support crate for Haiku OS" +version = "0.1.0" +authors = ["René Kijewski "] +repository = "https://github.com/strawlab/iana-time-zone" +license = "MIT OR Apache-2.0" +readme = "README.md" +edition = "2018" + +[dependencies] +cxx = "1.0.34" + +[build-dependencies] +cxx-build = "1.0.34" diff --git a/haiku/LICENSE-APACHE b/haiku/LICENSE-APACHE new file mode 120000 index 0000000..965b606 --- /dev/null +++ b/haiku/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/haiku/LICENSE-MIT b/haiku/LICENSE-MIT new file mode 120000 index 0000000..76219eb --- /dev/null +++ b/haiku/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/haiku/README.md b/haiku/README.md new file mode 100644 index 0000000..7ad49ae --- /dev/null +++ b/haiku/README.md @@ -0,0 +1,8 @@ +# iana-time-zone-haiku + +[![Crates.io](https://img.shields.io/crates/v/iana-time-zone-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku) +[![Documentation](https://docs.rs/iana-time-zone/badge.svg)](https://docs.rs/iana-time-zone/) +[![Crate License](https://img.shields.io/crates/l/iana-time-zone-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku) +[![build](https://github.com/strawlab/iana-time-zone/workflows/build/badge.svg?branch=master)](https://github.com/strawlab/iana-time-zone/actions?query=branch%3Amaster) + +[iana-time-zone](https://github.com/strawlab/iana-time-zone) support crate for Haiku OS. diff --git a/haiku/build.rs b/haiku/build.rs new file mode 100644 index 0000000..bb015ba --- /dev/null +++ b/haiku/build.rs @@ -0,0 +1,11 @@ +fn main() { + cxx_build::bridge("src/lib.rs") + .file("src/implementation.cc") + .flag_if_supported("-std=c++11") + .compile("tz_haiku"); + + println!("cargo:return-if-changed=include/interface.h"); + println!("cargo:return-if-changed=src/implementation.cc"); + println!("cargo:return-if-changed=src/lib.rs"); + println!("cargo:rustc-link-lib=be"); +} diff --git a/src/impl_haiku.cc b/haiku/src/implementation.cc similarity index 87% rename from src/impl_haiku.cc rename to haiku/src/implementation.cc index 191afb8..b93b7b3 100644 --- a/src/impl_haiku.cc +++ b/haiku/src/implementation.cc @@ -1,5 +1,5 @@ -#include "iana-time-zone/include/impl_haiku.h" -#include "iana-time-zone/src/tz_haiku.rs.h" +#include "iana-time-zone-haiku/src/interface.h" +#include "iana-time-zone-haiku/src/lib.rs.h" #include #include @@ -7,7 +7,7 @@ #include #include -size_t ::tz_haiku::get_tz(rust::Slice buf) { +size_t ::iana_time_zone_haiku::get_tz(rust::Slice buf) { try { static_assert(sizeof(char) == sizeof(uint8_t), "Illegal char size"); diff --git a/include/impl_haiku.h b/haiku/src/interface.h similarity index 75% rename from include/impl_haiku.h rename to haiku/src/interface.h index 7bd9860..5913a40 100644 --- a/include/impl_haiku.h +++ b/haiku/src/interface.h @@ -4,6 +4,6 @@ #include -namespace tz_haiku { +namespace iana_time_zone_haiku { size_t get_tz(rust::Slice buf); } diff --git a/haiku/src/lib.rs b/haiku/src/lib.rs new file mode 100644 index 0000000..e0be5cd --- /dev/null +++ b/haiku/src/lib.rs @@ -0,0 +1,29 @@ +/// # iana-time-zone-haiku +/// +/// [![Crates.io](https://img.shields.io/crates/v/iana-time-zone-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku) +/// [![Documentation](https://docs.rs/iana-time-zone/badge.svg)](https://docs.rs/iana-time-zone/) +/// [![Crate License](https://img.shields.io/crates/l/iana-time-zone-haiku-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku) +/// [![build](https://github.com/strawlab/iana-time-zone/workflows/build/badge.svg?branch=master)](https://github.com/strawlab/iana-time-zone/actions?query=branch%3Amaster) +/// +/// [iana-time-zone](https://github.com/strawlab/iana-time-zone) support crate for Haiku OS. + +#[cxx::bridge(namespace = "iana_time_zone_haiku")] +mod ffi { + // SAFETY: in here "unsafe" is simply part of the syntax + unsafe extern "C++" { + include!("iana-time-zone-haiku/src/interface.h"); + + fn get_tz(buf: &mut [u8]) -> usize; + } +} + +pub fn get_timezone() -> Option { + // The longest name in the IANA time zone database is 25 ASCII characters long. + let mut buf = [0u8; 32]; + let len = ffi::get_tz(&mut buf); + // The name should not be empty, or excessively long. + match buf.get(..len)? { + b"" => None, + s => Some(std::str::from_utf8(s).ok()?.to_owned()), + } +} diff --git a/src/tz_haiku.rs b/src/tz_haiku.rs index 4e092c3..d78372b 100644 --- a/src/tz_haiku.rs +++ b/src/tz_haiku.rs @@ -1,23 +1,3 @@ -#[cxx::bridge(namespace = "tz_haiku")] -mod ffi { - // SAFETY: in here "unsafe" is simply part of the syntax - unsafe extern "C++" { - include!("iana-time-zone/include/impl_haiku.h"); - - fn get_tz(buf: &mut [u8]) -> usize; - } -} - pub(crate) fn get_timezone_inner() -> Result { - // The longest name in the IANA time zone database is 25 ASCII characters long. - let mut buf = [0u8; 32]; - let len = ffi::get_tz(&mut buf); - // The name should not be empty, or excessively long. - match buf.get(..len) { - None | Some(b"") => Err(crate::GetTimezoneError::OsError), - Some(s) => { - let s = std::str::from_utf8(s).map_err(|_| crate::GetTimezoneError::OsError)?; - Ok(s.to_owned()) - } - } + iana_time_zone_haiku::get_timezone().ok_or(crate::GetTimezoneError::OsError) } From 23f8006f999535cd43a4739dde6f031f34a5ccde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Fri, 30 Sep 2022 10:41:11 +0200 Subject: [PATCH 14/16] Don't fail to compile on other target OSes --- haiku/src/implementation.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/haiku/src/implementation.cc b/haiku/src/implementation.cc index b93b7b3..ae116fb 100644 --- a/haiku/src/implementation.cc +++ b/haiku/src/implementation.cc @@ -1,7 +1,10 @@ #include "iana-time-zone-haiku/src/interface.h" #include "iana-time-zone-haiku/src/lib.rs.h" +#ifdef __HAIKU__ + #include + #include #include #include @@ -49,3 +52,9 @@ size_t ::iana_time_zone_haiku::get_tz(rust::Slice buf) { return 0; } } + +#else + +size_t ::iana_time_zone_haiku::get_tz(rust::Slice) { return 0; } + +#endif From 1ca5cc2eab0b5ce1dc2f849286acd5a38e46a25a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Fri, 30 Sep 2022 11:28:08 +0200 Subject: [PATCH 15/16] Don't try to compile with `-Zminimal-versions` `iana-time-zone` depends on `iana-time-zone-haiku` depends on `cxx-build` depends on `codespan-reporting` depends on `termcolor`. With `-Zminimal-versions` `termcolor==1.0.0` is selected, but `codespan-reporting` actually needs `termcolor>=1.0.4`. We could either depend on `termcolor >= 1.0.4"` to fix the problem ... or we can ignore it. --- .github/workflows/rust.yml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0f9b2ba..86eb995 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -213,15 +213,6 @@ jobs: - run: cargo clippy --all-targets -- -D warnings no-docker-image-check-only: - strategy: - fail-fast: false - matrix: - target: - # - i686-unknown-haiku # Does not (cross) compile for 32bit - - x86_64-unknown-haiku - versions: - - "" - - "-Zminimal-versions" runs-on: ubuntu-latest steps: - name: Checkout @@ -234,11 +225,7 @@ jobs: toolchain: nightly override: true components: rust-src - - name: Update lockfile - run: cargo generate-lockfile ${{ matrix.versions }} - env: - RUSTC_BOOTSTRAP: 1 - - run: cargo +nightly check --target ${{ matrix.target }} -Z build-std --examples + - run: cargo +nightly check --target x86_64-unknown-haiku -Z build-std --examples doc: runs-on: ubuntu-latest From 23a6c570de360cbb8dc7d7d1e4b276fa416f0286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Fri, 30 Sep 2022 18:13:12 +0200 Subject: [PATCH 16/16] Add comment that we don't need to free BLocaleRoster::Default()'s result --- haiku/src/implementation.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/haiku/src/implementation.cc b/haiku/src/implementation.cc index ae116fb..282a423 100644 --- a/haiku/src/implementation.cc +++ b/haiku/src/implementation.cc @@ -18,6 +18,8 @@ size_t ::iana_time_zone_haiku::get_tz(rust::Slice buf) { return 0; } + // `BLocaleRoster::Default()` returns a reference to a statically allocated object. + // https://github.com/haiku/haiku/blob/8f16317/src/kits/locale/LocaleRoster.cpp#L143-L147 BLocaleRoster *locale_roster(BLocaleRoster::Default()); if (!locale_roster) { return 0;