diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:57:31 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:57:31 +0000 |
commit | dc0db358abe19481e475e10c32149b53370f1a1c (patch) | |
tree | ab8ce99c4b255ce46f99ef402c27916055b899ee /vendor/android-tzdata/src | |
parent | Releasing progress-linux version 1.71.1+dfsg1-2~progress7.99u1. (diff) | |
download | rustc-dc0db358abe19481e475e10c32149b53370f1a1c.tar.xz rustc-dc0db358abe19481e475e10c32149b53370f1a1c.zip |
Merging upstream version 1.72.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/android-tzdata/src')
-rw-r--r-- | vendor/android-tzdata/src/lib.rs | 29 | ||||
-rw-r--r-- | vendor/android-tzdata/src/tzdata.rs | 166 |
2 files changed, 195 insertions, 0 deletions
diff --git a/vendor/android-tzdata/src/lib.rs b/vendor/android-tzdata/src/lib.rs new file mode 100644 index 000000000..b6b0e36cf --- /dev/null +++ b/vendor/android-tzdata/src/lib.rs @@ -0,0 +1,29 @@ +//! Parser for the Android-specific tzdata file. + +mod tzdata; + +/// Tries to locate the `tzdata` file, parse it, and return the entry for the +/// requested time zone. +/// +/// # Errors +/// +/// Returns an [std::io::Error] if the `tzdata` file cannot be found and parsed, or +/// if it does not contain the requested timezone entry. +/// +/// # Example +/// +/// ```rust +/// # use std::error::Error; +/// # use android_tzdata::find_tz_data; +/// # +/// # fn main() -> Result<(), Box<dyn Error>> { +/// let tz_data = find_tz_data("Europe/Kiev")?; +/// // Check it's version 2 of the [Time Zone Information Format](https://www.ietf.org/archive/id/draft-murchison-rfc8536bis-02.html). +/// assert!(tz_data.starts_with(b"TZif2")); +/// # Ok(()) +/// # } +/// ``` +pub fn find_tz_data(tz_name: impl AsRef<str>) -> Result<Vec<u8>, std::io::Error> { + let mut file = tzdata::find_file()?; + tzdata::find_tz_data_in_file(&mut file, tz_name.as_ref()) +} diff --git a/vendor/android-tzdata/src/tzdata.rs b/vendor/android-tzdata/src/tzdata.rs new file mode 100644 index 000000000..92e4b621d --- /dev/null +++ b/vendor/android-tzdata/src/tzdata.rs @@ -0,0 +1,166 @@ +//! Logic was mainly ported from https://android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/util/ZoneInfoDB.java + +use core::{cmp::Ordering, convert::TryInto}; +use std::{ + fs::File, + io::{self, ErrorKind, Read, Seek, SeekFrom}, +}; + +// The database uses 32-bit (4 byte) integers. +const TZ_INT_SIZE: usize = 4; +// The first 12 bytes contain a special version string. +const MAGIC_SIZE: usize = 12; +const HEADER_SIZE: usize = MAGIC_SIZE + 3 * TZ_INT_SIZE; +// The database reserves 40 bytes for each id. +const TZ_NAME_SIZE: usize = 40; +const INDEX_ENTRY_SIZE: usize = TZ_NAME_SIZE + 3 * TZ_INT_SIZE; +const TZDATA_LOCATIONS: [TzdataLocation; 2] = [ + TzdataLocation { + env_var: "ANDROID_DATA", + path: "/misc/zoneinfo/", + }, + TzdataLocation { + env_var: "ANDROID_ROOT", + path: "/usr/share/zoneinfo/", + }, +]; + +#[derive(Debug)] +struct TzdataLocation { + env_var: &'static str, + path: &'static str, +} + +#[derive(Debug, Clone, Copy)] +struct Header { + index_offset: usize, + data_offset: usize, + _zonetab_offset: usize, +} + +#[derive(Debug)] +struct Index(Vec<u8>); + +#[derive(Debug, Clone, Copy)] +struct IndexEntry<'a> { + _name: &'a [u8], + offset: usize, + length: usize, + _raw_utc_offset: usize, +} + +pub(super) fn find_file() -> Result<File, io::Error> { + for location in &TZDATA_LOCATIONS { + if let Ok(env_value) = std::env::var(location.env_var) { + if let Ok(file) = File::open(format!("{}{}tzdata", env_value, location.path)) { + return Ok(file); + } + } + } + Err(io::Error::from(io::ErrorKind::NotFound)) +} + +pub(super) fn find_tz_data_in_file( + mut file: impl Read + Seek, + tz_name: &str, +) -> Result<Vec<u8>, io::Error> { + let header = Header::new(&mut file)?; + let index = Index::new(&mut file, header)?; + if let Some(entry) = index.find_entry(tz_name) { + file.seek(SeekFrom::Start((entry.offset + header.data_offset) as u64))?; + let mut tz_data = vec![0u8; entry.length]; + file.read_exact(&mut tz_data)?; + Ok(tz_data) + } else { + Err(io::Error::from(ErrorKind::NotFound)) + } +} + +impl Header { + fn new(mut file: impl Read + Seek) -> Result<Self, io::Error> { + let mut buf = [0; HEADER_SIZE]; + file.read_exact(&mut buf)?; + if !buf.starts_with(b"tzdata") || buf[MAGIC_SIZE - 1] != 0u8 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid magic number", + )); + } + Ok(Self { + index_offset: parse_tz_int(&buf, MAGIC_SIZE) as usize, + data_offset: parse_tz_int(&buf, MAGIC_SIZE + TZ_INT_SIZE) as usize, + _zonetab_offset: parse_tz_int(&buf, MAGIC_SIZE + 2 * TZ_INT_SIZE) as usize, + }) + } +} + +impl Index { + fn new(mut file: impl Read + Seek, header: Header) -> Result<Self, io::Error> { + file.seek(SeekFrom::Start(header.index_offset as u64))?; + let size = header.data_offset - header.index_offset; + let mut bytes = vec![0; size]; + file.read_exact(&mut bytes)?; + Ok(Self(bytes)) + } + + fn find_entry(&self, name: &str) -> Option<IndexEntry> { + let name_bytes = name.as_bytes(); + let name_len = name_bytes.len(); + if name_len > TZ_NAME_SIZE { + return None; + } + + let zeros = [0u8; TZ_NAME_SIZE]; + let cmp = |chunk: &&[u8]| -> Ordering { + // tz names always have TZ_NAME_SIZE bytes and are right-padded with 0s + // so we check that a chunk starts with `name` and the remaining bytes are 0 + chunk[..name_len] + .cmp(name_bytes) + .then_with(|| chunk[name_len..TZ_NAME_SIZE].cmp(&zeros[name_len..])) + }; + + let chunks: Vec<_> = self.0.chunks_exact(INDEX_ENTRY_SIZE).collect(); + chunks + .binary_search_by(cmp) + .map(|idx| IndexEntry::new(chunks[idx])) + .ok() + } +} + +impl<'a> IndexEntry<'a> { + fn new(bytes: &'a [u8]) -> Self { + Self { + _name: bytes[..TZ_NAME_SIZE] + .splitn(2, |&b| b == 0u8) + .next() + .unwrap(), + offset: parse_tz_int(bytes, TZ_NAME_SIZE) as usize, + length: parse_tz_int(bytes, TZ_NAME_SIZE + TZ_INT_SIZE) as usize, + _raw_utc_offset: parse_tz_int(bytes, TZ_NAME_SIZE + 2 * TZ_INT_SIZE) as usize, + } + } +} + +/// Panics if slice does not contain [TZ_INT_SIZE] bytes beginning at start. +fn parse_tz_int(slice: &[u8], start: usize) -> u32 { + u32::from_be_bytes(slice[start..start + TZ_INT_SIZE].try_into().unwrap()) +} + +#[cfg(test)] +mod test { + use super::*; + use std::fs::File; + use std::io::Cursor; + + #[test] + fn parse() { + let mut archive = File::open("tests/resources/tzdata.zip").unwrap(); + let mut zip = zip::ZipArchive::new(&mut archive).unwrap(); + let mut file = zip.by_index(0).unwrap(); + let mut data = Vec::new(); + file.read_to_end(&mut data).unwrap(); + let cursor = Cursor::new(data); + let tz = find_tz_data_in_file(cursor, "Europe/Kiev").unwrap(); + assert!(tz.starts_with(b"TZif2")); + } +} |