<commit_before>use crate::common::{BinarySerializable, CountingWriter, FixedSize, VInt};
use crate::directory::error::Incompatibility;
use crate::directory::read_only_source::ReadOnlySource;
use crate::directory::{AntiCallToken, TerminatingWrite};
use crate::Version;
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
use crc32fast::Hasher;
use std::io;
use std::io::Write;

type CrcHashU32 = u32;

#[derive(Debug, Clone, PartialEq)]
pub struct Footer {
    pub version: Version,
    pub meta: String,
    pub versioned_footer: VersionedFooter,
}

/// Serialises the footer to a byte-array
/// - versioned_footer_len : 4 bytes
///-  versioned_footer: variable bytes
/// - meta_len: 4 bytes
/// - meta: variable bytes
/// - version_len: 4 bytes
/// - version json: variable bytes
impl BinarySerializable for Footer {
    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
        BinarySerializable::serialize(&self.versioned_footer, writer)?;
        BinarySerializable::serialize(&self.meta, writer)?;
        let version_string =
            serde_json::to_string(&self.version).map_err(|_err| io::ErrorKind::InvalidInput)?;
        BinarySerializable::serialize(&version_string, writer)?;
        Ok(())
    }

    fn deserialize<R: io::Read>(reader: &mut R) -> io::Result<Self> {
        let versioned_footer = VersionedFooter::deserialize(reader)?;
        let meta = String::deserialize(reader)?;
        let version_json = String::deserialize(reader)?;
        let version = serde_json::from_str(&version_json)?;
        Ok(Footer {
            version,
            meta,
            versioned_footer,
        })
    }
}

impl Footer {
    pub fn new(versioned_footer: VersionedFooter) -> Self {
        let version = crate::VERSION.clone();
        let meta = version.to_string();
        Footer {
            version,
            meta,
            versioned_footer,
        }
    }

    pub fn append_footer<W: io::Write>(&self, mut write: &mut W) -> io::Result<()> {
        let mut counting_write = CountingWriter::wrap(&mut write);
        self.serialize(&mut counting_write)?;
        let written_len = counting_write.written_bytes();
        write.write_u32::<LittleEndian>(written_len as u32)?;
        Ok(())
    }

    pub fn extract_footer(source: ReadOnlySource) -> Result<(Footer, ReadOnlySource), io::Error> {
        if source.len() < 4 {
            return Err(io::Error::new(
                io::ErrorKind::UnexpectedEof,
                format!(
                    "File corrupted. The file is smaller than 4 bytes (len={}).",
                    source.len()
                ),
            ));
        }
        let (body_footer, footer_len_bytes) = source.split_from_end(u32::SIZE_IN_BYTES);
        let footer_len = LittleEndian::read_u32(footer_len_bytes.as_slice()) as usize;
        let body_len = body_footer.len() - footer_len;
        let (body, footer_data) = body_footer.split(body_len);
        let mut cursor = footer_data.as_slice();
        let footer = Footer::deserialize(&mut cursor)?;
        Ok((footer, body))
    }

    /// Confirms that the index will be read correctly by this version of tantivy
    /// Has to be called after `extract_footer` to make sure it's not accessing uninitialised memory
    pub fn is_compatible(&self) -> Result<(), Incompatibility> {
        let library_version = crate::version();
        match &self.versioned_footer {
            VersionedFooter::V1 {
                crc32: _crc,
                store_compression: compression,
            } => {
                if &library_version.store_compression != compression {
                    return Err(Incompatibility::CompressionMismatch {
                        library_compression_format: library_version.store_compression.to_string(),
                        index_compression_format: compression.to_string(),
                    });
                }
                Ok(())
            }
            VersionedFooter::UnknownVersion => Err(Incompatibility::IndexMismatch {
                library_version: library_version.clone(),
                index_version: self.version.clone(),
            }),
        }
    }
}

/// Footer that includes a crc32 hash that enables us to checksum files in the index
#[derive(Debug, Clone, PartialEq)]
pub enum VersionedFooter {
    UnknownVersion,
    V1 {
        crc32: CrcHashU32,
        store_compression: String,
    },
}

impl BinarySerializable for VersionedFooter {
    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
        let mut buf = Vec::new();
        match self {
            VersionedFooter::V1 {
                crc32,
                store_compression: compression,
            } => {
                // Serializes a valid `VersionedFooter` or panics if the version is unknown
                // [   version    |   crc_hash  | compression_mode ]
                // [    0..4      |     4..8    |     variable     ]
                BinarySerializable::serialize(&1u32, &mut buf)?;
                BinarySerializable::serialize(crc32, &mut buf)?;
                BinarySerializable::serialize(compression, &mut buf)?;
            }
            VersionedFooter::UnknownVersion => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "Cannot serialize an unknown versioned footer ",
                ));
            }
        }
        BinarySerializable::serialize(&VInt(buf.len() as u64), writer)?;
        writer.write_all(&buf[..])?;
        Ok(())
    }

    fn deserialize<R: io::Read>(reader: &mut R) -> io::Result<Self> {
        let len = VInt::deserialize(reader)?.0 as usize;
        let mut buf = vec![0u8; len];
        reader.read_exact(&mut buf[..])?;
        let mut cursor = &buf[..];
        let version = u32::deserialize(&mut cursor)?;
        if version == 1 {
            let crc32 = u32::deserialize(&mut cursor)?;
            let compression = String::deserialize(&mut cursor)?;
            Ok(VersionedFooter::V1 {
                crc32,
                store_compression: compression,
            })
        } else {
            Ok(VersionedFooter::UnknownVersion)
        }
    }
}

impl VersionedFooter {
    pub fn crc(&self) -> Option<CrcHashU32> {
        match self {
            VersionedFooter::V1 { crc32, .. } => Some(*crc32),
            VersionedFooter::UnknownVersion { .. } => None,
        }
    }
}

pub(crate) struct FooterProxy<W: TerminatingWrite> {
    /// always Some except after terminate call
    hasher: Option<Hasher>,
    /// always Some except after terminate call
    writer: Option<W>,
}

impl<W: TerminatingWrite> FooterProxy<W> {
    pub fn new(writer: W) -> Self {
        FooterProxy {
            hasher: Some(Hasher::new()),
            writer: Some(writer),
        }
    }
}

impl<W: TerminatingWrite> Write for FooterProxy<W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let count = self.writer.as_mut().unwrap().write(buf)?;
        self.hasher.as_mut().unwrap().update(&buf[..count]);
        Ok(count)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.writer.as_mut().unwrap().flush()
    }
}

impl<W: TerminatingWrite> TerminatingWrite for FooterProxy<W> {
    fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()> {
        let crc32 = self.hasher.take().unwrap().finalize();
        let footer = Footer::new(VersionedFooter::V1 {
            crc32,
            store_compression: crate::store::COMPRESSION.to_string(),
        });
        let mut writer = self.writer.take().unwrap();
        footer.append_footer(&mut writer)?;
        writer.terminate()
    }
}

#[cfg(test)]
mod tests {

    use super::CrcHashU32;
    use super::FooterProxy;
    use crate::common::BinarySerializable;
    use crate::directory::footer::{Footer, VersionedFooter};
    use crate::directory::TerminatingWrite;
    use byteorder::{ByteOrder, LittleEndian};
    use regex::Regex;

    #[test]
    fn test_versioned_footer() {
        let mut vec = Vec::new();
        let footer_proxy = FooterProxy::new(&mut vec);
        assert!(footer_proxy.terminate().is_ok());
        assert_eq!(vec.len(), 167);
        let footer = Footer::deserialize(&mut &vec[..]).unwrap();
        if let VersionedFooter::V1 {
            crc32: _,
            store_compression,
        } = footer.versioned_footer
        {
            assert_eq!(store_compression, crate::store::COMPRESSION);
        } else {
            panic!("Versioned footer should be V1.");
        }
        assert_eq!(&footer.version, crate::version());
    }

    #[test]
    fn test_serialize_deserialize_footer() {
        let mut buffer = Vec::new();
        let crc32 = 123456u32;
        let footer: Footer = Footer::new(VersionedFooter::V1 {
            crc32,
            store_compression: "lz4".to_string(),
        });
        footer.serialize(&mut buffer).unwrap();
        let footer_deser = Footer::deserialize(&mut &buffer[..]).unwrap();
        assert_eq!(footer_deser, footer);
    }

    #[test]
    fn footer_length() {
        let crc32 = 1111111u32;
        let versioned_footer = VersionedFooter::V1 {
            crc32,
            store_compression: "lz4".to_string(),
        };
        let mut buf = Vec::new();
        versioned_footer.serialize(&mut buf).unwrap();
        assert_eq!(buf.len(), 13);
        let footer = Footer::new(versioned_footer);
        let regex_ptn = Regex::new(
            "tantivy v[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.{0,10}, index_format v[0-9]{1,5}",
        )
        .unwrap();
        assert!(regex_ptn.is_match(&footer.meta));
    }

    #[test]
    fn versioned_footer_from_bytes() {
        let v_footer_bytes = vec![
            // versionned footer length
            12 | 128,
            // index format version
            1,
            0,
            0,
            0,
            // crc 32
            12,
            35,
            89,
            18,
            // compression format
            3 | 128,
            b'l',
            b'z',
            b'4',
        ];
        let mut cursor = &v_footer_bytes[..];
        let versioned_footer = VersionedFooter::deserialize(&mut cursor).unwrap();
        assert!(cursor.is_empty());
        let expected_crc: u32 = LittleEndian::read_u32(&v_footer_bytes[5..9]) as CrcHashU32;
        let expected_versioned_footer: VersionedFooter = VersionedFooter::V1 {
            crc32: expected_crc,
            store_compression: "lz4".to_string(),
        };
        assert_eq!(versioned_footer, expected_versioned_footer);
        let mut buffer = Vec::new();
        assert!(versioned_footer.serialize(&mut buffer).is_ok());
        assert_eq!(&v_footer_bytes[..], &buffer[..]);
    }

    #[test]
    fn versioned_footer_panic() {
        let v_footer_bytes = vec![6u8 | 128u8, 3u8, 0u8, 0u8, 1u8, 0u8, 0u8];
        let mut b = &v_footer_bytes[..];
        let versioned_footer = VersionedFooter::deserialize(&mut b).unwrap();
        assert!(b.is_empty());
        let expected_versioned_footer = VersionedFooter::UnknownVersion;
        assert_eq!(versioned_footer, expected_versioned_footer);
        let mut buf = Vec::new();
        assert!(versioned_footer.serialize(&mut buf).is_err());
    }

    #[test]
    #[cfg(not(feature = "lz4"))]
    fn compression_mismatch() {
        let crc32 = 1111111u32;
        let versioned_footer = VersionedFooter::V1 {
            crc32,
            store_compression: "lz4".to_string(),
        };
        let footer = Footer::new(versioned_footer);
        let res = footer.is_compatible();
        assert!(res.is_err());
    }
}
<commit_msg>Closes #758<commit_after>use crate::common::{BinarySerializable, CountingWriter, FixedSize, VInt};
use crate::directory::error::Incompatibility;
use crate::directory::read_only_source::ReadOnlySource;
use crate::directory::{AntiCallToken, TerminatingWrite};
use crate::Version;
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
use crc32fast::Hasher;
use std::io;
use std::io::Write;

const FOOTER_MAX_LEN: usize = 10_000;

type CrcHashU32 = u32;

#[derive(Debug, Clone, PartialEq)]
pub struct Footer {
    pub version: Version,
    pub meta: String,
    pub versioned_footer: VersionedFooter,
}

/// Serialises the footer to a byte-array
/// - versioned_footer_len : 4 bytes
///-  versioned_footer: variable bytes
/// - meta_len: 4 bytes
/// - meta: variable bytes
/// - version_len: 4 bytes
/// - version json: variable bytes
impl BinarySerializable for Footer {
    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
        BinarySerializable::serialize(&self.versioned_footer, writer)?;
        BinarySerializable::serialize(&self.meta, writer)?;
        let version_string =
            serde_json::to_string(&self.version).map_err(|_err| io::ErrorKind::InvalidInput)?;
        BinarySerializable::serialize(&version_string, writer)?;
        Ok(())
    }

    fn deserialize<R: io::Read>(reader: &mut R) -> io::Result<Self> {
        let versioned_footer = VersionedFooter::deserialize(reader)?;
        let meta = String::deserialize(reader)?;
        let version_json = String::deserialize(reader)?;
        let version = serde_json::from_str(&version_json)?;
        Ok(Footer {
            version,
            meta,
            versioned_footer,
        })
    }
}

impl Footer {
    pub fn new(versioned_footer: VersionedFooter) -> Self {
        let version = crate::VERSION.clone();
        let meta = version.to_string();
        Footer {
            version,
            meta,
            versioned_footer,
        }
    }

    pub fn append_footer<W: io::Write>(&self, mut write: &mut W) -> io::Result<()> {
        let mut counting_write = CountingWriter::wrap(&mut write);
        self.serialize(&mut counting_write)?;
        let written_len = counting_write.written_bytes();
        write.write_u32::<LittleEndian>(written_len as u32)?;
        Ok(())
    }

    pub fn extract_footer(source: ReadOnlySource) -> Result<(Footer, ReadOnlySource), io::Error> {
        if source.len() < 4 {
            return Err(io::Error::new(
                io::ErrorKind::UnexpectedEof,
                format!(
                    "File corrupted. The file is smaller than 4 bytes (len={}).",
                    source.len()
                ),
            ));
        }
        let (body_footer, footer_len_bytes) = source.split_from_end(u32::SIZE_IN_BYTES);
        let footer_len = LittleEndian::read_u32(footer_len_bytes.as_slice()) as usize;
        let body_len = body_footer.len() - footer_len;
        let (body, footer_data) = body_footer.split(body_len);
        let mut cursor = footer_data.as_slice();
        let footer = Footer::deserialize(&mut cursor)?;
        Ok((footer, body))
    }

    /// Confirms that the index will be read correctly by this version of tantivy
    /// Has to be called after `extract_footer` to make sure it's not accessing uninitialised memory
    pub fn is_compatible(&self) -> Result<(), Incompatibility> {
        let library_version = crate::version();
        match &self.versioned_footer {
            VersionedFooter::V1 {
                crc32: _crc,
                store_compression: compression,
            } => {
                if &library_version.store_compression != compression {
                    return Err(Incompatibility::CompressionMismatch {
                        library_compression_format: library_version.store_compression.to_string(),
                        index_compression_format: compression.to_string(),
                    });
                }
                Ok(())
            }
            VersionedFooter::UnknownVersion => Err(Incompatibility::IndexMismatch {
                library_version: library_version.clone(),
                index_version: self.version.clone(),
            }),
        }
    }
}

/// Footer that includes a crc32 hash that enables us to checksum files in the index
#[derive(Debug, Clone, PartialEq)]
pub enum VersionedFooter {
    UnknownVersion,
    V1 {
        crc32: CrcHashU32,
        store_compression: String,
    },
}

impl BinarySerializable for VersionedFooter {
    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
        let mut buf = Vec::new();
        match self {
            VersionedFooter::V1 {
                crc32,
                store_compression: compression,
            } => {
                // Serializes a valid `VersionedFooter` or panics if the version is unknown
                // [   version    |   crc_hash  | compression_mode ]
                // [    0..4      |     4..8    |     variable     ]
                BinarySerializable::serialize(&1u32, &mut buf)?;
                BinarySerializable::serialize(crc32, &mut buf)?;
                BinarySerializable::serialize(compression, &mut buf)?;
            }
            VersionedFooter::UnknownVersion => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "Cannot serialize an unknown versioned footer ",
                ));
            }
        }
        BinarySerializable::serialize(&VInt(buf.len() as u64), writer)?;
        assert!(buf.len() <= FOOTER_MAX_LEN);
        writer.write_all(&buf[..])?;
        Ok(())
    }

    fn deserialize<R: io::Read>(reader: &mut R) -> io::Result<Self> {
        let len = VInt::deserialize(reader)?.0 as usize;
        if len > FOOTER_MAX_LEN {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "Footer seems invalid as it suggests a footer len of {}. File is corrupted, \
            or the index was created with a different & old version of tantivy.",
                    len
                ),
            ));
        }
        let mut buf = vec![0u8; len];
        reader.read_exact(&mut buf[..])?;
        let mut cursor = &buf[..];
        let version = u32::deserialize(&mut cursor)?;
        if version == 1 {
            let crc32 = u32::deserialize(&mut cursor)?;
            let compression = String::deserialize(&mut cursor)?;
            Ok(VersionedFooter::V1 {
                crc32,
                store_compression: compression,
            })
        } else {
            Ok(VersionedFooter::UnknownVersion)
        }
    }
}

impl VersionedFooter {
    pub fn crc(&self) -> Option<CrcHashU32> {
        match self {
            VersionedFooter::V1 { crc32, .. } => Some(*crc32),
            VersionedFooter::UnknownVersion { .. } => None,
        }
    }
}

pub(crate) struct FooterProxy<W: TerminatingWrite> {
    /// always Some except after terminate call
    hasher: Option<Hasher>,
    /// always Some except after terminate call
    writer: Option<W>,
}

impl<W: TerminatingWrite> FooterProxy<W> {
    pub fn new(writer: W) -> Self {
        FooterProxy {
            hasher: Some(Hasher::new()),
            writer: Some(writer),
        }
    }
}

impl<W: TerminatingWrite> Write for FooterProxy<W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let count = self.writer.as_mut().unwrap().write(buf)?;
        self.hasher.as_mut().unwrap().update(&buf[..count]);
        Ok(count)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.writer.as_mut().unwrap().flush()
    }
}

impl<W: TerminatingWrite> TerminatingWrite for FooterProxy<W> {
    fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()> {
        let crc32 = self.hasher.take().unwrap().finalize();
        let footer = Footer::new(VersionedFooter::V1 {
            crc32,
            store_compression: crate::store::COMPRESSION.to_string(),
        });
        let mut writer = self.writer.take().unwrap();
        footer.append_footer(&mut writer)?;
        writer.terminate()
    }
}

#[cfg(test)]
mod tests {

    use super::CrcHashU32;
    use super::FooterProxy;
    use crate::common::{BinarySerializable, VInt};
    use crate::directory::footer::{Footer, VersionedFooter};
    use crate::directory::TerminatingWrite;
    use byteorder::{ByteOrder, LittleEndian};
    use regex::Regex;
    use std::error::Error;
    use std::io;

    #[test]
    fn test_versioned_footer() {
        let mut vec = Vec::new();
        let footer_proxy = FooterProxy::new(&mut vec);
        assert!(footer_proxy.terminate().is_ok());
        assert_eq!(vec.len(), 167);
        let footer = Footer::deserialize(&mut &vec[..]).unwrap();
        if let VersionedFooter::V1 {
            crc32: _,
            store_compression,
        } = footer.versioned_footer
        {
            assert_eq!(store_compression, crate::store::COMPRESSION);
        } else {
            panic!("Versioned footer should be V1.");
        }
        assert_eq!(&footer.version, crate::version());
    }

    #[test]
    fn test_serialize_deserialize_footer() {
        let mut buffer = Vec::new();
        let crc32 = 123456u32;
        let footer: Footer = Footer::new(VersionedFooter::V1 {
            crc32,
            store_compression: "lz4".to_string(),
        });
        footer.serialize(&mut buffer).unwrap();
        let footer_deser = Footer::deserialize(&mut &buffer[..]).unwrap();
        assert_eq!(footer_deser, footer);
    }

    #[test]
    fn footer_length() {
        let crc32 = 1111111u32;
        let versioned_footer = VersionedFooter::V1 {
            crc32,
            store_compression: "lz4".to_string(),
        };
        let mut buf = Vec::new();
        versioned_footer.serialize(&mut buf).unwrap();
        assert_eq!(buf.len(), 13);
        let footer = Footer::new(versioned_footer);
        let regex_ptn = Regex::new(
            "tantivy v[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.{0,10}, index_format v[0-9]{1,5}",
        )
        .unwrap();
        assert!(regex_ptn.is_match(&footer.meta));
    }

    #[test]
    fn versioned_footer_from_bytes() {
        let v_footer_bytes = vec![
            // versionned footer length
            12 | 128,
            // index format version
            1,
            0,
            0,
            0,
            // crc 32
            12,
            35,
            89,
            18,
            // compression format
            3 | 128,
            b'l',
            b'z',
            b'4',
        ];
        let mut cursor = &v_footer_bytes[..];
        let versioned_footer = VersionedFooter::deserialize(&mut cursor).unwrap();
        assert!(cursor.is_empty());
        let expected_crc: u32 = LittleEndian::read_u32(&v_footer_bytes[5..9]) as CrcHashU32;
        let expected_versioned_footer: VersionedFooter = VersionedFooter::V1 {
            crc32: expected_crc,
            store_compression: "lz4".to_string(),
        };
        assert_eq!(versioned_footer, expected_versioned_footer);
        let mut buffer = Vec::new();
        assert!(versioned_footer.serialize(&mut buffer).is_ok());
        assert_eq!(&v_footer_bytes[..], &buffer[..]);
    }

    #[test]
    fn versioned_footer_panic() {
        let v_footer_bytes = vec![6u8 | 128u8, 3u8, 0u8, 0u8, 1u8, 0u8, 0u8];
        let mut b = &v_footer_bytes[..];
        let versioned_footer = VersionedFooter::deserialize(&mut b).unwrap();
        assert!(b.is_empty());
        let expected_versioned_footer = VersionedFooter::UnknownVersion;
        assert_eq!(versioned_footer, expected_versioned_footer);
        let mut buf = Vec::new();
        assert!(versioned_footer.serialize(&mut buf).is_err());
    }

    #[test]
    #[cfg(not(feature = "lz4"))]
    fn compression_mismatch() {
        let crc32 = 1111111u32;
        let versioned_footer = VersionedFooter::V1 {
            crc32,
            store_compression: "lz4".to_string(),
        };
        let footer = Footer::new(versioned_footer);
        let res = footer.is_compatible();
        assert!(res.is_err());
    }

    #[test]
    fn test_deserialize_too_large_footer() {
        let mut buf = vec![];
        assert!(FooterProxy::new(&mut buf).terminate().is_ok());
        let mut long_len_buf = [0u8; 10];
        let num_bytes = VInt(super::FOOTER_MAX_LEN as u64 + 1u64).serialize_into(&mut long_len_buf);
        buf[0..num_bytes].copy_from_slice(&long_len_buf[..num_bytes]);
        let err = Footer::deserialize(&mut &buf[..]).unwrap_err();
        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
        assert_eq!(
            err.description(),
            "Footer seems invalid as it suggests a footer len of 10001. File is corrupted, \
            or the index was created with a different & old version of tantivy."
        );
    }
}
<|endoftext|>


<commit_before>use std::fmt;
use std::time::Duration;

use number_prefix::NumberPrefix;

const SECOND: Duration = Duration::from_secs(1);
const MINUTE: Duration = Duration::from_secs(60);
const HOUR: Duration = Duration::from_secs(60 * 60);
const DAY: Duration = Duration::from_secs(24 * 60 * 60);
const WEEK: Duration = Duration::from_secs(7 * 24 * 60 * 60);
const YEAR: Duration = Duration::from_secs(365 * 24 * 60 * 60);

/// Wraps an std duration for human basic formatting.
#[derive(Debug)]
pub struct FormattedDuration(pub Duration);

/// Wraps an std duration for human readable formatting.
#[derive(Debug)]
pub struct HumanDuration(pub Duration);

/// Formats bytes for human readability
#[derive(Debug)]
pub struct HumanBytes(pub u64);

/// Formats bytes for human readability using SI prefixes
#[derive(Debug)]
pub struct DecimalBytes(pub u64);

/// Formats bytes for human readability using ISO/IEC prefixes
#[derive(Debug)]
pub struct BinaryBytes(pub u64);

/// Formats counts for human readability using commas
#[derive(Debug)]
pub struct HumanCount(pub u64);

impl fmt::Display for FormattedDuration {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut t = self.0.as_secs();
        let seconds = t % 60;
        t /= 60;
        let minutes = t % 60;
        t /= 60;
        let hours = t % 24;
        t /= 24;
        if t > 0 {
            let days = t;
            write!(f, "{}d {:02}:{:02}:{:02}", days, hours, minutes, seconds)
        } else {
            write!(f, "{:02}:{:02}:{:02}", hours, minutes, seconds)
        }
    }
}

// `HumanDuration` should be as intuitively understandable as possible.
// So we want to round, not truncate: otherwise 1 hour and 59 minutes
// would display an ETA of "1 hour" which underestimates the time
// remaining by a factor 2.
//
// To make the precision more uniform, we avoid displaying "1 unit"
// (except for seconds), because it would be displayed for a relatively
// long duration compared to the unit itself. Instead, when we arrive
// around 1.5 unit, we change from "2 units" to the next smaller unit
// (e.g. "89 seconds").
//
// Formally:
// * for n >= 2, we go from "n+1 units" to "n units" exactly at (n + 1/2) units
// * we switch from "2 units" to the next smaller unit at (1.5 unit minus half of the next smaller unit)

impl fmt::Display for HumanDuration {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut idx = 0;
        for (i, &(cur, _, _)) in UNITS.iter().enumerate() {
            idx = i;
            match UNITS.get(i + 1) {
                Some(&next) if self.0 + next.0 / 2 >= cur + cur / 2 => break,
                _ => continue,
            }
        }

        let (unit, name, alt) = UNITS[idx];
        // FIXME when `div_duration_f64` is stable
        let mut t = (self.0.as_secs_f64() / unit.as_secs_f64()).round() as usize;
        if idx < UNITS.len() - 1 {
            t = Ord::max(t, 2);
        }

        match (f.alternate(), t) {
            (true, _) => write!(f, "{}{}", t, alt),
            (false, 1) => write!(f, "{} {}", t, name),
            (false, _) => write!(f, "{} {}s", t, name),
        }
    }
}

const UNITS: &[(Duration, &str, &str)] = &[
    (YEAR, "year", "y"),
    (WEEK, "week", "w"),
    (DAY, "day", "d"),
    (HOUR, "hour", "h"),
    (MINUTE, "minute", "m"),
    (SECOND, "second", "s"),
];

impl fmt::Display for HumanBytes {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match NumberPrefix::binary(self.0 as f64) {
            NumberPrefix::Standalone(number) => write!(f, "{:.0}B", number),
            NumberPrefix::Prefixed(prefix, number) => write!(f, "{:.2}{}B", number, prefix),
        }
    }
}

impl fmt::Display for DecimalBytes {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match NumberPrefix::decimal(self.0 as f64) {
            NumberPrefix::Standalone(number) => write!(f, "{:.0}B", number),
            NumberPrefix::Prefixed(prefix, number) => write!(f, "{:.2}{}B", number, prefix),
        }
    }
}

impl fmt::Display for BinaryBytes {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match NumberPrefix::binary(self.0 as f64) {
            NumberPrefix::Standalone(number) => write!(f, "{:.0}B", number),
            NumberPrefix::Prefixed(prefix, number) => write!(f, "{:.2}{}B", number, prefix),
        }
    }
}

impl fmt::Display for HumanCount {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use fmt::Write;

        let num = self.0.to_string();
        let mut i = num.len();
        for c in num.chars() {
            f.write_char(c)?;
            i -= 1;
            if i > 0 && i % 3 == 0 {
                f.write_char(',')?;
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const MILLI: Duration = Duration::from_millis(1);

    #[test]
    fn human_duration_alternate() {
        for (unit, _, alt) in UNITS {
            assert_eq!(
                format!("2{}", alt),
                format!("{:#}", HumanDuration(2 * *unit))
            );
        }
    }

    #[test]
    fn human_duration_less_than_one_second() {
        assert_eq!("0 seconds", format!("{}", HumanDuration(Duration::ZERO)));
        assert_eq!("0 seconds", format!("{}", HumanDuration(MILLI)));
        assert_eq!("0 seconds", format!("{}", HumanDuration(499 * MILLI)));
        assert_eq!("1 second", format!("{}", HumanDuration(500 * MILLI)));
        assert_eq!("1 second", format!("{}", HumanDuration(999 * MILLI)));
    }

    #[test]
    fn human_duration_less_than_two_seconds() {
        assert_eq!("1 second", format!("{}", HumanDuration(1499 * MILLI)));
        assert_eq!("2 seconds", format!("{}", HumanDuration(1500 * MILLI)));
        assert_eq!("2 seconds", format!("{}", HumanDuration(1999 * MILLI)));
    }

    #[test]
    fn human_duration_one_unit() {
        assert_eq!("1 second", format!("{}", HumanDuration(SECOND)));
        assert_eq!("60 seconds", format!("{}", HumanDuration(MINUTE)));
        assert_eq!("60 minutes", format!("{}", HumanDuration(HOUR)));
        assert_eq!("24 hours", format!("{}", HumanDuration(DAY)));
        assert_eq!("7 days", format!("{}", HumanDuration(WEEK)));
        assert_eq!("52 weeks", format!("{}", HumanDuration(YEAR)));
    }

    #[test]
    fn human_duration_less_than_one_and_a_half_unit() {
        // this one is actually done at 1.5 unit - half of the next smaller unit - epsilon
        // and should display the next smaller unit
        let d = HumanDuration(MINUTE + MINUTE / 2 - SECOND / 2 - MILLI);
        assert_eq!("89 seconds", format!("{}", d));
        let d = HumanDuration(HOUR + HOUR / 2 - MINUTE / 2 - MILLI);
        assert_eq!("89 minutes", format!("{}", d));
        let d = HumanDuration(DAY + DAY / 2 - HOUR / 2 - MILLI);
        assert_eq!("35 hours", format!("{}", d));
        let d = HumanDuration(WEEK + WEEK / 2 - DAY / 2 - MILLI);
        assert_eq!("10 days", format!("{}", d));
        let d = HumanDuration(YEAR + YEAR / 2 - WEEK / 2 - MILLI);
        assert_eq!("78 weeks", format!("{}", d));
    }

    #[test]
    fn human_duration_one_and_a_half_unit() {
        // this one is actually done at 1.5 unit - half of the next smaller unit
        // and should still display "2 units"
        let d = HumanDuration(MINUTE + MINUTE / 2 - SECOND / 2);
        assert_eq!("2 minutes", format!("{}", d));
        let d = HumanDuration(HOUR + HOUR / 2 - MINUTE / 2);
        assert_eq!("2 hours", format!("{}", d));
        let d = HumanDuration(DAY + DAY / 2 - HOUR / 2);
        assert_eq!("2 days", format!("{}", d));
        let d = HumanDuration(WEEK + WEEK / 2 - DAY / 2);
        assert_eq!("2 weeks", format!("{}", d));
        let d = HumanDuration(YEAR + YEAR / 2 - WEEK / 2);
        assert_eq!("2 years", format!("{}", d));
    }

    #[test]
    fn human_duration_two_units() {
        assert_eq!("2 seconds", format!("{}", HumanDuration(2 * SECOND)));
        assert_eq!("2 minutes", format!("{}", HumanDuration(2 * MINUTE)));
        assert_eq!("2 hours", format!("{}", HumanDuration(2 * HOUR)));
        assert_eq!("2 days", format!("{}", HumanDuration(2 * DAY)));
        assert_eq!("2 weeks", format!("{}", HumanDuration(2 * WEEK)));
        assert_eq!("2 years", format!("{}", HumanDuration(2 * YEAR)));
    }

    #[test]
    fn human_duration_less_than_two_and_a_half_units() {
        let d = HumanDuration(2 * SECOND + SECOND / 2 - MILLI);
        assert_eq!("2 seconds", format!("{}", d));
        let d = HumanDuration(2 * MINUTE + MINUTE / 2 - MILLI);
        assert_eq!("2 minutes", format!("{}", d));
        let d = HumanDuration(2 * HOUR + HOUR / 2 - MILLI);
        assert_eq!("2 hours", format!("{}", d));
        let d = HumanDuration(2 * DAY + DAY / 2 - MILLI);
        assert_eq!("2 days", format!("{}", d));
        let d = HumanDuration(2 * WEEK + WEEK / 2 - MILLI);
        assert_eq!("2 weeks", format!("{}", d));
        let d = HumanDuration(2 * YEAR + YEAR / 2 - MILLI);
        assert_eq!("2 years", format!("{}", d));
    }

    #[test]
    fn human_duration_two_and_a_half_units() {
        let d = HumanDuration(2 * SECOND + SECOND / 2);
        assert_eq!("3 seconds", format!("{}", d));
        let d = HumanDuration(2 * MINUTE + MINUTE / 2);
        assert_eq!("3 minutes", format!("{}", d));
        let d = HumanDuration(2 * HOUR + HOUR / 2);
        assert_eq!("3 hours", format!("{}", d));
        let d = HumanDuration(2 * DAY + DAY / 2);
        assert_eq!("3 days", format!("{}", d));
        let d = HumanDuration(2 * WEEK + WEEK / 2);
        assert_eq!("3 weeks", format!("{}", d));
        let d = HumanDuration(2 * YEAR + YEAR / 2);
        assert_eq!("3 years", format!("{}", d));
    }

    #[test]
    fn human_duration_three_units() {
        assert_eq!("3 seconds", format!("{}", HumanDuration(3 * SECOND)));
        assert_eq!("3 minutes", format!("{}", HumanDuration(3 * MINUTE)));
        assert_eq!("3 hours", format!("{}", HumanDuration(3 * HOUR)));
        assert_eq!("3 days", format!("{}", HumanDuration(3 * DAY)));
        assert_eq!("3 weeks", format!("{}", HumanDuration(3 * WEEK)));
        assert_eq!("3 years", format!("{}", HumanDuration(3 * YEAR)));
    }

    #[test]
    fn human_count() {
        assert_eq!("42", format!("{}", HumanCount(42)));
        assert_eq!("7,654", format!("{}", HumanCount(7654)));
        assert_eq!("12,345", format!("{}", HumanCount(12345)));
        assert_eq!("1,234,567,890", format!("{}", HumanCount(1234567890)));
    }
}
<commit_msg>Write the HumanCount implementation with enumerate()<commit_after>use std::fmt;
use std::time::Duration;

use number_prefix::NumberPrefix;

const SECOND: Duration = Duration::from_secs(1);
const MINUTE: Duration = Duration::from_secs(60);
const HOUR: Duration = Duration::from_secs(60 * 60);
const DAY: Duration = Duration::from_secs(24 * 60 * 60);
const WEEK: Duration = Duration::from_secs(7 * 24 * 60 * 60);
const YEAR: Duration = Duration::from_secs(365 * 24 * 60 * 60);

/// Wraps an std duration for human basic formatting.
#[derive(Debug)]
pub struct FormattedDuration(pub Duration);

/// Wraps an std duration for human readable formatting.
#[derive(Debug)]
pub struct HumanDuration(pub Duration);

/// Formats bytes for human readability
#[derive(Debug)]
pub struct HumanBytes(pub u64);

/// Formats bytes for human readability using SI prefixes
#[derive(Debug)]
pub struct DecimalBytes(pub u64);

/// Formats bytes for human readability using ISO/IEC prefixes
#[derive(Debug)]
pub struct BinaryBytes(pub u64);

/// Formats counts for human readability using commas
#[derive(Debug)]
pub struct HumanCount(pub u64);

impl fmt::Display for FormattedDuration {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut t = self.0.as_secs();
        let seconds = t % 60;
        t /= 60;
        let minutes = t % 60;
        t /= 60;
        let hours = t % 24;
        t /= 24;
        if t > 0 {
            let days = t;
            write!(f, "{}d {:02}:{:02}:{:02}", days, hours, minutes, seconds)
        } else {
            write!(f, "{:02}:{:02}:{:02}", hours, minutes, seconds)
        }
    }
}

// `HumanDuration` should be as intuitively understandable as possible.
// So we want to round, not truncate: otherwise 1 hour and 59 minutes
// would display an ETA of "1 hour" which underestimates the time
// remaining by a factor 2.
//
// To make the precision more uniform, we avoid displaying "1 unit"
// (except for seconds), because it would be displayed for a relatively
// long duration compared to the unit itself. Instead, when we arrive
// around 1.5 unit, we change from "2 units" to the next smaller unit
// (e.g. "89 seconds").
//
// Formally:
// * for n >= 2, we go from "n+1 units" to "n units" exactly at (n + 1/2) units
// * we switch from "2 units" to the next smaller unit at (1.5 unit minus half of the next smaller unit)

impl fmt::Display for HumanDuration {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut idx = 0;
        for (i, &(cur, _, _)) in UNITS.iter().enumerate() {
            idx = i;
            match UNITS.get(i + 1) {
                Some(&next) if self.0 + next.0 / 2 >= cur + cur / 2 => break,
                _ => continue,
            }
        }

        let (unit, name, alt) = UNITS[idx];
        // FIXME when `div_duration_f64` is stable
        let mut t = (self.0.as_secs_f64() / unit.as_secs_f64()).round() as usize;
        if idx < UNITS.len() - 1 {
            t = Ord::max(t, 2);
        }

        match (f.alternate(), t) {
            (true, _) => write!(f, "{}{}", t, alt),
            (false, 1) => write!(f, "{} {}", t, name),
            (false, _) => write!(f, "{} {}s", t, name),
        }
    }
}

const UNITS: &[(Duration, &str, &str)] = &[
    (YEAR, "year", "y"),
    (WEEK, "week", "w"),
    (DAY, "day", "d"),
    (HOUR, "hour", "h"),
    (MINUTE, "minute", "m"),
    (SECOND, "second", "s"),
];

impl fmt::Display for HumanBytes {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match NumberPrefix::binary(self.0 as f64) {
            NumberPrefix::Standalone(number) => write!(f, "{:.0}B", number),
            NumberPrefix::Prefixed(prefix, number) => write!(f, "{:.2}{}B", number, prefix),
        }
    }
}

impl fmt::Display for DecimalBytes {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match NumberPrefix::decimal(self.0 as f64) {
            NumberPrefix::Standalone(number) => write!(f, "{:.0}B", number),
            NumberPrefix::Prefixed(prefix, number) => write!(f, "{:.2}{}B", number, prefix),
        }
    }
}

impl fmt::Display for BinaryBytes {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match NumberPrefix::binary(self.0 as f64) {
            NumberPrefix::Standalone(number) => write!(f, "{:.0}B", number),
            NumberPrefix::Prefixed(prefix, number) => write!(f, "{:.2}{}B", number, prefix),
        }
    }
}

impl fmt::Display for HumanCount {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use fmt::Write;

        let num = self.0.to_string();
        let len = num.len();
        for (idx, c) in num.chars().enumerate() {
            let pos = len - idx - 1;
            f.write_char(c)?;
            if pos > 0 && pos % 3 == 0 {
                f.write_char(',')?;
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const MILLI: Duration = Duration::from_millis(1);

    #[test]
    fn human_duration_alternate() {
        for (unit, _, alt) in UNITS {
            assert_eq!(
                format!("2{}", alt),
                format!("{:#}", HumanDuration(2 * *unit))
            );
        }
    }

    #[test]
    fn human_duration_less_than_one_second() {
        assert_eq!("0 seconds", format!("{}", HumanDuration(Duration::ZERO)));
        assert_eq!("0 seconds", format!("{}", HumanDuration(MILLI)));
        assert_eq!("0 seconds", format!("{}", HumanDuration(499 * MILLI)));
        assert_eq!("1 second", format!("{}", HumanDuration(500 * MILLI)));
        assert_eq!("1 second", format!("{}", HumanDuration(999 * MILLI)));
    }

    #[test]
    fn human_duration_less_than_two_seconds() {
        assert_eq!("1 second", format!("{}", HumanDuration(1499 * MILLI)));
        assert_eq!("2 seconds", format!("{}", HumanDuration(1500 * MILLI)));
        assert_eq!("2 seconds", format!("{}", HumanDuration(1999 * MILLI)));
    }

    #[test]
    fn human_duration_one_unit() {
        assert_eq!("1 second", format!("{}", HumanDuration(SECOND)));
        assert_eq!("60 seconds", format!("{}", HumanDuration(MINUTE)));
        assert_eq!("60 minutes", format!("{}", HumanDuration(HOUR)));
        assert_eq!("24 hours", format!("{}", HumanDuration(DAY)));
        assert_eq!("7 days", format!("{}", HumanDuration(WEEK)));
        assert_eq!("52 weeks", format!("{}", HumanDuration(YEAR)));
    }

    #[test]
    fn human_duration_less_than_one_and_a_half_unit() {
        // this one is actually done at 1.5 unit - half of the next smaller unit - epsilon
        // and should display the next smaller unit
        let d = HumanDuration(MINUTE + MINUTE / 2 - SECOND / 2 - MILLI);
        assert_eq!("89 seconds", format!("{}", d));
        let d = HumanDuration(HOUR + HOUR / 2 - MINUTE / 2 - MILLI);
        assert_eq!("89 minutes", format!("{}", d));
        let d = HumanDuration(DAY + DAY / 2 - HOUR / 2 - MILLI);
        assert_eq!("35 hours", format!("{}", d));
        let d = HumanDuration(WEEK + WEEK / 2 - DAY / 2 - MILLI);
        assert_eq!("10 days", format!("{}", d));
        let d = HumanDuration(YEAR + YEAR / 2 - WEEK / 2 - MILLI);
        assert_eq!("78 weeks", format!("{}", d));
    }

    #[test]
    fn human_duration_one_and_a_half_unit() {
        // this one is actually done at 1.5 unit - half of the next smaller unit
        // and should still display "2 units"
        let d = HumanDuration(MINUTE + MINUTE / 2 - SECOND / 2);
        assert_eq!("2 minutes", format!("{}", d));
        let d = HumanDuration(HOUR + HOUR / 2 - MINUTE / 2);
        assert_eq!("2 hours", format!("{}", d));
        let d = HumanDuration(DAY + DAY / 2 - HOUR / 2);
        assert_eq!("2 days", format!("{}", d));
        let d = HumanDuration(WEEK + WEEK / 2 - DAY / 2);
        assert_eq!("2 weeks", format!("{}", d));
        let d = HumanDuration(YEAR + YEAR / 2 - WEEK / 2);
        assert_eq!("2 years", format!("{}", d));
    }

    #[test]
    fn human_duration_two_units() {
        assert_eq!("2 seconds", format!("{}", HumanDuration(2 * SECOND)));
        assert_eq!("2 minutes", format!("{}", HumanDuration(2 * MINUTE)));
        assert_eq!("2 hours", format!("{}", HumanDuration(2 * HOUR)));
        assert_eq!("2 days", format!("{}", HumanDuration(2 * DAY)));
        assert_eq!("2 weeks", format!("{}", HumanDuration(2 * WEEK)));
        assert_eq!("2 years", format!("{}", HumanDuration(2 * YEAR)));
    }

    #[test]
    fn human_duration_less_than_two_and_a_half_units() {
        let d = HumanDuration(2 * SECOND + SECOND / 2 - MILLI);
        assert_eq!("2 seconds", format!("{}", d));
        let d = HumanDuration(2 * MINUTE + MINUTE / 2 - MILLI);
        assert_eq!("2 minutes", format!("{}", d));
        let d = HumanDuration(2 * HOUR + HOUR / 2 - MILLI);
        assert_eq!("2 hours", format!("{}", d));
        let d = HumanDuration(2 * DAY + DAY / 2 - MILLI);
        assert_eq!("2 days", format!("{}", d));
        let d = HumanDuration(2 * WEEK + WEEK / 2 - MILLI);
        assert_eq!("2 weeks", format!("{}", d));
        let d = HumanDuration(2 * YEAR + YEAR / 2 - MILLI);
        assert_eq!("2 years", format!("{}", d));
    }

    #[test]
    fn human_duration_two_and_a_half_units() {
        let d = HumanDuration(2 * SECOND + SECOND / 2);
        assert_eq!("3 seconds", format!("{}", d));
        let d = HumanDuration(2 * MINUTE + MINUTE / 2);
        assert_eq!("3 minutes", format!("{}", d));
        let d = HumanDuration(2 * HOUR + HOUR / 2);
        assert_eq!("3 hours", format!("{}", d));
        let d = HumanDuration(2 * DAY + DAY / 2);
        assert_eq!("3 days", format!("{}", d));
        let d = HumanDuration(2 * WEEK + WEEK / 2);
        assert_eq!("3 weeks", format!("{}", d));
        let d = HumanDuration(2 * YEAR + YEAR / 2);
        assert_eq!("3 years", format!("{}", d));
    }

    #[test]
    fn human_duration_three_units() {
        assert_eq!("3 seconds", format!("{}", HumanDuration(3 * SECOND)));
        assert_eq!("3 minutes", format!("{}", HumanDuration(3 * MINUTE)));
        assert_eq!("3 hours", format!("{}", HumanDuration(3 * HOUR)));
        assert_eq!("3 days", format!("{}", HumanDuration(3 * DAY)));
        assert_eq!("3 weeks", format!("{}", HumanDuration(3 * WEEK)));
        assert_eq!("3 years", format!("{}", HumanDuration(3 * YEAR)));
    }

    #[test]
    fn human_count() {
        assert_eq!("42", format!("{}", HumanCount(42)));
        assert_eq!("7,654", format!("{}", HumanCount(7654)));
        assert_eq!("12,345", format!("{}", HumanCount(12345)));
        assert_eq!("1,234,567,890", format!("{}", HumanCount(1234567890)));
    }
}
<|endoftext|>


<commit_before>#[legacy_modes];

fn f(arg: {mut a: int}) {
    arg.a = 100;
}

fn main() {
    let x = {mut a: 10};
    f(x);
    assert x.a == 100;
    x.a = 20;
    f(copy x);
    assert x.a == 20;
}
<commit_msg>xfail-fast another legacy_mode test<commit_after>// xfail-fast
#[legacy_modes];

fn f(arg: {mut a: int}) {
    arg.a = 100;
}

fn main() {
    let x = {mut a: 10};
    f(x);
    assert x.a == 100;
    x.a = 20;
    f(copy x);
    assert x.a == 20;
}
<|endoftext|>


<commit_before>#[macro_export]
macro_rules! get {
    ($x:expr, $y:expr) => (match $x {
        Ok(x) => x,
        Err(why) => return Err($y.to_string() + why.description()),
    });
}

macro_rules! get_option {
    ($x:expr, $y:expr) => (match $x {
        Some(x) => x,
        None => return Err($y.to_string()),
    });
}
<commit_msg>Add macro_export<commit_after>#[macro_export]
macro_rules! get {
    ($x:expr, $y:expr) => (match $x {
        Ok(x) => x,
        Err(why) => return Err($y.to_string() + why.description()),
    });
}

#[macro_export]
macro_rules! get_option {
    ($x:expr, $y:expr) => (match $x {
        Some(x) => x,
        None => return Err($y.to_string()),
    });
}
<|endoftext|>


<commit_before>//! Logging for Alacritty.
//!
//! The main executable is supposed to call `initialize()` exactly once during
//! startup. All logging messages are written to stdout, given that their
//! log-level is sufficient for the level configured in `cli::Options`.

use std::env;
use std::fs::{File, OpenOptions};
use std::io::{self, LineWriter, Stdout, Write};
use std::path::PathBuf;
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};

use glutin::event_loop::EventLoopProxy;
use log::{self, Level};

use crate::cli::Options;
use crate::event::Event;
use crate::message_bar::{Message, MessageType};

const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG";

pub fn initialize(
    options: &Options,
    event_proxy: EventLoopProxy<Event>,
) -> Result<Option<PathBuf>, log::SetLoggerError> {
    log::set_max_level(options.log_level);

    let logger = Logger::new(event_proxy);
    let path = logger.file_path();
    log::set_boxed_logger(Box::new(logger))?;

    Ok(path)
}

pub struct Logger {
    logfile: Mutex<OnDemandLogFile>,
    stdout: Mutex<LineWriter<Stdout>>,
    event_proxy: Mutex<EventLoopProxy<Event>>,
}

impl Logger {
    fn new(event_proxy: EventLoopProxy<Event>) -> Self {
        let logfile = Mutex::new(OnDemandLogFile::new());
        let stdout = Mutex::new(LineWriter::new(io::stdout()));

        Logger { logfile, stdout, event_proxy: Mutex::new(event_proxy) }
    }

    fn file_path(&self) -> Option<PathBuf> {
        if let Ok(logfile) = self.logfile.lock() {
            Some(logfile.path().clone())
        } else {
            None
        }
    }
}

impl log::Log for Logger {
    fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
        metadata.level() <= log::max_level()
    }

    fn log(&self, record: &log::Record<'_>) {
        if self.enabled(record.metadata()) && record.target().starts_with("alacritty") {
            let now = time::strftime("%F %T.%f", &time::now()).unwrap();

            let msg = if record.level() >= Level::Trace {
                format!(
                    "[{}] [{}] [{}:{}] {}\n",
                    now,
                    record.level(),
                    record.file().unwrap_or("?"),
                    record.line().map(|l| l.to_string()).unwrap_or_else(|| "?".into()),
                    record.args()
                )
            } else {
                format!("[{}] [{}] {}\n", now, record.level(), record.args())
            };

            if let Ok(ref mut logfile) = self.logfile.lock() {
                let _ = logfile.write_all(msg.as_ref());

                if record.level() <= Level::Warn {
                    #[cfg(not(windows))]
                    let env_var = format!("${}", ALACRITTY_LOG_ENV);
                    #[cfg(windows)]
                    let env_var = format!("%{}%", ALACRITTY_LOG_ENV);

                    let msg = format!(
                        "[{}] See log at {} ({}):\n{}",
                        record.level(),
                        logfile.path.to_string_lossy(),
                        env_var,
                        record.args(),
                    );
                    let message_type = match record.level() {
                        Level::Error => MessageType::Error,
                        Level::Warn => MessageType::Warning,
                        _ => unreachable!(),
                    };

                    if let Ok(event_proxy) = self.event_proxy.lock() {
                        let mut message = Message::new(msg, message_type);
                        message.set_target(record.target().to_owned());

                        let _ = event_proxy.send_event(Event::Message(message));
                    }
                }
            }

            if let Ok(ref mut stdout) = self.stdout.lock() {
                let _ = stdout.write_all(msg.as_ref());
            }
        }
    }

    fn flush(&self) {}
}

struct OnDemandLogFile {
    file: Option<LineWriter<File>>,
    created: Arc<AtomicBool>,
    path: PathBuf,
}

impl OnDemandLogFile {
    fn new() -> Self {
        let mut path = env::temp_dir();
        path.push(format!("Alacritty-{}.log", process::id()));

        // Set log path as an environment variable.
        env::set_var(ALACRITTY_LOG_ENV, path.as_os_str());

        OnDemandLogFile { path, file: None, created: Arc::new(AtomicBool::new(false)) }
    }

    fn file(&mut self) -> Result<&mut LineWriter<File>, io::Error> {
        // Allow to recreate the file if it has been deleted at runtime.
        if self.file.is_some() && !self.path.as_path().exists() {
            self.file = None;
        }

        // Create the file if it doesn't exist yet.
        if self.file.is_none() {
            let file = OpenOptions::new().append(true).create(true).open(&self.path);

            match file {
                Ok(file) => {
                    self.file = Some(io::LineWriter::new(file));
                    self.created.store(true, Ordering::Relaxed);
                    let _ =
                        writeln!(io::stdout(), "Created log file at \"{}\"", self.path.display());
                },
                Err(e) => {
                    let _ = writeln!(io::stdout(), "Unable to create log file: {}", e);
                    return Err(e);
                },
            }
        }

        Ok(self.file.as_mut().unwrap())
    }

    fn path(&self) -> &PathBuf {
        &self.path
    }
}

impl Write for OnDemandLogFile {
    fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
        self.file()?.write(buf)
    }

    fn flush(&mut self) -> Result<(), io::Error> {
        self.file()?.flush()
    }
}
<commit_msg>Add logging for crossfont<commit_after>//! Logging for Alacritty.
//!
//! The main executable is supposed to call `initialize()` exactly once during
//! startup. All logging messages are written to stdout, given that their
//! log-level is sufficient for the level configured in `cli::Options`.

use std::env;
use std::fs::{File, OpenOptions};
use std::io::{self, LineWriter, Stdout, Write};
use std::path::PathBuf;
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};

use glutin::event_loop::EventLoopProxy;
use log::{self, Level};

use crate::cli::Options;
use crate::event::Event;
use crate::message_bar::{Message, MessageType};

/// Name for the environment variable containing the log file's path.
const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG";
/// List of targets which will be logged by Alacritty.
const ALLOWED_TARGETS: [&str; 4] =
    ["alacritty_terminal", "alacritty_config", "alacritty", "crossfont"];

pub fn initialize(
    options: &Options,
    event_proxy: EventLoopProxy<Event>,
) -> Result<Option<PathBuf>, log::SetLoggerError> {
    log::set_max_level(options.log_level);

    let logger = Logger::new(event_proxy);
    let path = logger.file_path();
    log::set_boxed_logger(Box::new(logger))?;

    Ok(path)
}

pub struct Logger {
    logfile: Mutex<OnDemandLogFile>,
    stdout: Mutex<LineWriter<Stdout>>,
    event_proxy: Mutex<EventLoopProxy<Event>>,
}

impl Logger {
    fn new(event_proxy: EventLoopProxy<Event>) -> Self {
        let logfile = Mutex::new(OnDemandLogFile::new());
        let stdout = Mutex::new(LineWriter::new(io::stdout()));

        Logger { logfile, stdout, event_proxy: Mutex::new(event_proxy) }
    }

    fn file_path(&self) -> Option<PathBuf> {
        if let Ok(logfile) = self.logfile.lock() {
            Some(logfile.path().clone())
        } else {
            None
        }
    }

    /// Log a record to the message bar.
    fn message_bar_log(&self, record: &log::Record<'_>, logfile_path: &str) {
        let event_proxy = match self.event_proxy.lock() {
            Ok(event_proxy) => event_proxy,
            Err(_) => return,
        };

        #[cfg(not(windows))]
        let env_var = format!("${}", ALACRITTY_LOG_ENV);
        #[cfg(windows)]
        let env_var = format!("%{}%", ALACRITTY_LOG_ENV);

        let msg = format!(
            "[{}] See log at {} ({}):\n{}",
            record.level(),
            logfile_path,
            env_var,
            record.args(),
        );
        let message_type = match record.level() {
            Level::Error => MessageType::Error,
            Level::Warn => MessageType::Warning,
            _ => unreachable!(),
        };

        let mut message = Message::new(msg, message_type);
        message.set_target(record.target().to_owned());

        let _ = event_proxy.send_event(Event::Message(message));
    }
}

impl log::Log for Logger {
    fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
        metadata.level() <= log::max_level()
    }

    fn log(&self, record: &log::Record<'_>) {
        // Get target crate.
        let index = record.target().find(':').unwrap_or_else(|| record.target().len());
        let target = &record.target()[..index];

        // Only log our own crates.
        if !self.enabled(record.metadata()) || !ALLOWED_TARGETS.contains(&target) {
            return;
        }

        let now = time::strftime("%F %T.%f", &time::now()).unwrap();
        let msg = format!("[{}] [{:<5}] [{}] {}\n", now, record.level(), target, record.args());

        // Write to stdout.
        if let Ok(mut stdout) = self.stdout.lock() {
            let _ = stdout.write_all(msg.as_ref());
        }

        if let Ok(mut logfile) = self.logfile.lock() {
            // Write to logfile.
            let _ = logfile.write_all(msg.as_ref());

            // Write to message bar.
            if record.level() <= Level::Warn {
                self.message_bar_log(record, &logfile.path.to_string_lossy());
            }
        }
    }

    fn flush(&self) {}
}

struct OnDemandLogFile {
    file: Option<LineWriter<File>>,
    created: Arc<AtomicBool>,
    path: PathBuf,
}

impl OnDemandLogFile {
    fn new() -> Self {
        let mut path = env::temp_dir();
        path.push(format!("Alacritty-{}.log", process::id()));

        // Set log path as an environment variable.
        env::set_var(ALACRITTY_LOG_ENV, path.as_os_str());

        OnDemandLogFile { path, file: None, created: Arc::new(AtomicBool::new(false)) }
    }

    fn file(&mut self) -> Result<&mut LineWriter<File>, io::Error> {
        // Allow to recreate the file if it has been deleted at runtime.
        if self.file.is_some() && !self.path.as_path().exists() {
            self.file = None;
        }

        // Create the file if it doesn't exist yet.
        if self.file.is_none() {
            let file = OpenOptions::new().append(true).create(true).open(&self.path);

            match file {
                Ok(file) => {
                    self.file = Some(io::LineWriter::new(file));
                    self.created.store(true, Ordering::Relaxed);
                    let _ =
                        writeln!(io::stdout(), "Created log file at \"{}\"", self.path.display());
                },
                Err(e) => {
                    let _ = writeln!(io::stdout(), "Unable to create log file: {}", e);
                    return Err(e);
                },
            }
        }

        Ok(self.file.as_mut().unwrap())
    }

    fn path(&self) -> &PathBuf {
        &self.path
    }
}

impl Write for OnDemandLogFile {
    fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
        self.file()?.write(buf)
    }

    fn flush(&mut self) -> Result<(), io::Error> {
        self.file()?.flush()
    }
}
<|endoftext|>


<commit_before>// ToDO: add tests
<commit_msg>test(nproc): add a test<commit_after>use crate::common::util::*;

#[test]
fn test_nproc() {
    let (_, mut ucmd) = at_and_ucmd!();
    let result = ucmd.run();
    assert!(result.success);
    let nproc: u8 = result.stdout.trim().parse().unwrap();
    assert!(nproc > 0);
}

#[test]
fn test_nproc_all_omp() {
    let (_, mut ucmd) = at_and_ucmd!();
    let result = ucmd.arg("--all").run();
    assert!(result.success);
    let nproc: u8 = result.stdout.trim().parse().unwrap();
    assert!(nproc > 0);

    let result = TestScenario::new(util_name!())
    .ucmd_keepenv()
    .env("OMP_NUM_THREADS", "1")
    .run();
    assert!(result.success);
    let nproc_omp: u8 = result.stdout.trim().parse().unwrap();
    assert!(nproc-1 == nproc_omp);

    let result = TestScenario::new(util_name!())
    .ucmd_keepenv()
    .env("OMP_NUM_THREADS", "1") // Has no effect
    .arg("--all")
    .run();
    assert!(result.success);
    let nproc_omp: u8 = result.stdout.trim().parse().unwrap();
    assert!(nproc == nproc_omp);
}

#[test]
fn test_nproc_ignore() {
    let (_, mut ucmd) = at_and_ucmd!();
    let result = ucmd.run();
    assert!(result.success);
    let nproc: u8 = result.stdout.trim().parse().unwrap();
    if nproc > 1 {
        // Ignore all CPU but one
        let result = TestScenario::new(util_name!())
            .ucmd_keepenv()
            .arg("--ignore")
            .arg((nproc - 1).to_string())
            .run();
        assert!(result.success);
        let nproc: u8 = result.stdout.trim().parse().unwrap();
        assert!(nproc == 1);
    }
}
<|endoftext|>


<commit_before>// FIXME: Something in panic handling fails validation with full-MIR
// compile-flags: -Zmir-emit-validate=0
//error-pattern: the evaluated program panicked

fn main() {
    assert_eq!(5, 6);
}
<commit_msg>ignore panic test on Windows<commit_after>//ignore-windows
// FIXME: Something in panic handling fails validation with full-MIR
// compile-flags: -Zmir-emit-validate=0
//error-pattern: the evaluated program panicked

fn main() {
    assert_eq!(5, 6);
}
<|endoftext|>


<commit_before>use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

struct Test {
    repo: &'static str,
    name: &'static str,
    sha: &'static str,
    lock: Option<&'static str>,
    packages: &'static [&'static str],
}

const TEST_REPOS: &'static [Test] = &[
    Test {
        name: "iron",
        repo: "https://github.com/iron/iron",
        sha: "cf056ea5e8052c1feea6141e40ab0306715a2c33",
        lock: None,
        packages: &[],
    },
    Test {
        name: "ripgrep",
        repo: "https://github.com/BurntSushi/ripgrep",
        sha: "ad9befbc1d3b5c695e7f6b6734ee1b8e683edd41",
        lock: None,
        packages: &[],
    },
    Test {
        name: "tokei",
        repo: "https://github.com/XAMPPRocky/tokei",
        sha: "a950ff128d5a435a8083b1c7577c0431f98360ca",
        lock: None,
        packages: &[],
    },
    Test {
        name: "xsv",
        repo: "https://github.com/BurntSushi/xsv",
        sha: "66956b6bfd62d6ac767a6b6499c982eae20a2c9f",
        lock: None,
        packages: &[],
    },
    Test {
        name: "servo",
        repo: "https://github.com/servo/servo",
        sha: "caac107ae8145ef2fd20365e2b8fadaf09c2eb3b",
        lock: None,
        // Only test Stylo a.k.a. Quantum CSS, the parts of Servo going into Firefox.
        // This takes much less time to build than all of Servo and supports stable Rust.
        packages: &["selectors"],
    },
];

fn main() {
    let args = env::args().collect::<Vec<_>>();
    let ref cargo = args[1];
    let out_dir = Path::new(&args[2]);
    let ref cargo = Path::new(cargo);

    for test in TEST_REPOS.iter().rev() {
        test_repo(cargo, out_dir, test);
    }
}

fn test_repo(cargo: &Path, out_dir: &Path, test: &Test) {
    println!("testing {}", test.repo);
    let dir = clone_repo(test, out_dir);
    if let Some(lockfile) = test.lock {
        fs::write(&dir.join("Cargo.lock"), lockfile).unwrap();
    }
    if !run_cargo_test(cargo, &dir, test.packages) {
        panic!("tests failed for {}", test.repo);
    }
}

fn clone_repo(test: &Test, out_dir: &Path) -> PathBuf {
    let out_dir = out_dir.join(test.name);

    if !out_dir.join(".git").is_dir() {
        let status = Command::new("git").arg("init").arg(&out_dir).status().expect("");
        assert!(status.success());
    }

    // Try progressively deeper fetch depths to find the commit
    let mut found = false;
    for depth in &[0, 1, 10, 100, 1000, 100000] {
        if *depth > 0 {
            let status = Command::new("git")
                .arg("fetch")
                .arg(test.repo)
                .arg("master")
                .arg(&format!("--depth={}", depth))
                .current_dir(&out_dir)
                .status()
                .expect("");
            assert!(status.success());
        }

        let status = Command::new("git")
            .arg("reset")
            .arg(test.sha)
            .arg("--hard")
            .current_dir(&out_dir)
            .status()
            .expect("");

        if status.success() {
            found = true;
            break;
        }
    }

    if !found {
        panic!("unable to find commit {}", test.sha)
    }
    let status =
        Command::new("git").arg("clean").arg("-fdx").current_dir(&out_dir).status().unwrap();
    assert!(status.success());

    out_dir
}

fn run_cargo_test(cargo_path: &Path, crate_path: &Path, packages: &[&str]) -> bool {
    let mut command = Command::new(cargo_path);
    command.arg("test");
    for name in packages {
        command.arg("-p").arg(name);
    }
    let status = command
        // Disable rust-lang/cargo's cross-compile tests
        .env("CFG_DISABLE_CROSS_TESTS", "1")
        // Relax #![deny(warnings)] in some crates
        .env("RUSTFLAGS", "--cap-lints warn")
        .current_dir(crate_path)
        .status()
        .expect("");

    status.success()
}
<commit_msg>Auto merge of #75795 - matthiaskrgr:cargotest_clippy, r=Dylan-DPC<commit_after>use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

struct Test {
    repo: &'static str,
    name: &'static str,
    sha: &'static str,
    lock: Option<&'static str>,
    packages: &'static [&'static str],
}

const TEST_REPOS: &[Test] = &[
    Test {
        name: "iron",
        repo: "https://github.com/iron/iron",
        sha: "cf056ea5e8052c1feea6141e40ab0306715a2c33",
        lock: None,
        packages: &[],
    },
    Test {
        name: "ripgrep",
        repo: "https://github.com/BurntSushi/ripgrep",
        sha: "ad9befbc1d3b5c695e7f6b6734ee1b8e683edd41",
        lock: None,
        packages: &[],
    },
    Test {
        name: "tokei",
        repo: "https://github.com/XAMPPRocky/tokei",
        sha: "a950ff128d5a435a8083b1c7577c0431f98360ca",
        lock: None,
        packages: &[],
    },
    Test {
        name: "xsv",
        repo: "https://github.com/BurntSushi/xsv",
        sha: "66956b6bfd62d6ac767a6b6499c982eae20a2c9f",
        lock: None,
        packages: &[],
    },
    Test {
        name: "servo",
        repo: "https://github.com/servo/servo",
        sha: "caac107ae8145ef2fd20365e2b8fadaf09c2eb3b",
        lock: None,
        // Only test Stylo a.k.a. Quantum CSS, the parts of Servo going into Firefox.
        // This takes much less time to build than all of Servo and supports stable Rust.
        packages: &["selectors"],
    },
];

fn main() {
    let args = env::args().collect::<Vec<_>>();
    let cargo = &args[1];
    let out_dir = Path::new(&args[2]);
    let cargo = &Path::new(cargo);

    for test in TEST_REPOS.iter().rev() {
        test_repo(cargo, out_dir, test);
    }
}

fn test_repo(cargo: &Path, out_dir: &Path, test: &Test) {
    println!("testing {}", test.repo);
    let dir = clone_repo(test, out_dir);
    if let Some(lockfile) = test.lock {
        fs::write(&dir.join("Cargo.lock"), lockfile).unwrap();
    }
    if !run_cargo_test(cargo, &dir, test.packages) {
        panic!("tests failed for {}", test.repo);
    }
}

fn clone_repo(test: &Test, out_dir: &Path) -> PathBuf {
    let out_dir = out_dir.join(test.name);

    if !out_dir.join(".git").is_dir() {
        let status = Command::new("git").arg("init").arg(&out_dir).status().unwrap();
        assert!(status.success());
    }

    // Try progressively deeper fetch depths to find the commit
    let mut found = false;
    for depth in &[0, 1, 10, 100, 1000, 100000] {
        if *depth > 0 {
            let status = Command::new("git")
                .arg("fetch")
                .arg(test.repo)
                .arg("master")
                .arg(&format!("--depth={}", depth))
                .current_dir(&out_dir)
                .status()
                .unwrap();
            assert!(status.success());
        }

        let status = Command::new("git")
            .arg("reset")
            .arg(test.sha)
            .arg("--hard")
            .current_dir(&out_dir)
            .status()
            .unwrap();

        if status.success() {
            found = true;
            break;
        }
    }

    if !found {
        panic!("unable to find commit {}", test.sha)
    }
    let status =
        Command::new("git").arg("clean").arg("-fdx").current_dir(&out_dir).status().unwrap();
    assert!(status.success());

    out_dir
}

fn run_cargo_test(cargo_path: &Path, crate_path: &Path, packages: &[&str]) -> bool {
    let mut command = Command::new(cargo_path);
    command.arg("test");
    for name in packages {
        command.arg("-p").arg(name);
    }
    let status = command
        // Disable rust-lang/cargo's cross-compile tests
        .env("CFG_DISABLE_CROSS_TESTS", "1")
        // Relax #![deny(warnings)] in some crates
        .env("RUSTFLAGS", "--cap-lints warn")
        .current_dir(crate_path)
        .status()
        .unwrap();

    status.success()
}
<|endoftext|>


<commit_before>#![feature(slice_bytes, path_relative_from)]

use std::fs::File;
use std::path::PathBuf;
use std::ops::DerefMut;

extern crate byteorder;
extern crate docopt;
extern crate env_logger;
#[macro_use] extern crate log;

extern crate disk;

use docopt::Docopt;

use disk::*;

static VERSION: &'static str = "0.0.1";
static USAGE: &'static str = "
Usage: mkdisk [options] <dir>

Creates a bootable disk image.

Options:
    -h, --help     Print this help message
    -v, --version  Print the version of mkdisk
    -s, --size=SIZE           The fixed size of the disk image [default: 4MiB]
    -o, --out=FILE            The output disk image file
    -b, --bootloader=FILE     The master bootloader to use for the first few sectors
    -v, --volume-loader=FILE  The volume bootloader to use for the partition

File sizes measured using KB = 1000, KiB=1024 etc
";

fn main() {
    env_logger::init().unwrap();

    let args: docopt::ArgvMap = Docopt::new(USAGE)
                      .and_then(|d| d.help(true)
                                     .version(Some(VERSION.into()))
                                     .parse())
                      .unwrap_or_else(|e| e.exit());

    let mut config = Config::new(args);
    config.exec();
}

struct Config {
    dsize: usize,
    src: PathBuf,

    boot_path: PathBuf,
    boot: File,

    voot_path: PathBuf,
    voot: File,

    out_path: PathBuf,
    out: File,
}

impl Config {
    pub fn new(args: docopt::ArgvMap) -> Config {
        // default is 4MiB, as specified in USAGE
        let dsize = parse_size(args.get_str("-s"));

        let boot_path: PathBuf = match args.get_str("-b") {
            ""   => panic!("Master bootloader unspecified: use `-b` or `--bootloader`"),
            path => {
                path.into()
            },
        };
        let boot = File::open(&boot_path)
                        .unwrap_or_else(|e| panic!("Unable to open master bootloader `{}`: {}",
                                                   args.get_str("-b"), e));

        let voot_path: PathBuf = match args.get_str("-v") {
            ""   => panic!("Volume bootloader unspecified: use `-v` or `--volume-loader`"),
            path => {
                path.into()
            },
        };
        let voot = File::open(&voot_path)
                        .unwrap_or_else(|e| panic!("Unable to open volume bootloader `{}`: {}",
                                                   args.get_str("-b"), e));

        let src = args.get_str("<dir>").into();
        let out_path = PathBuf::from(match args.get_str("-o") {
            "" => {
                // if source dir is `bin/fs/`, then the output file becomes `bin/fs.disk`
                let mut path = PathBuf::from(&src);
                path.set_extension("disk");
                path
            },
            s  => PathBuf::from(s),
        });

        let out = File::create(&out_path)
                       .unwrap_or_else(|e| panic!("Unable to open output file `{:?}`: {}", out_path, e));

        Config {
            dsize: dsize,
            src: src,

            boot_path: boot_path,
            boot: boot,

            voot_path: voot_path,
            voot: voot,

            out_path: out_path,
            out: out,
        }
    }

    pub fn exec(&mut self) {
        use std::io::{Read, Seek};
        use std::ops::Deref;

        let smeta = ::std::fs::metadata(&self.src)
                              .unwrap_or_else(|e| panic!("Unable to query source `{}`: {}", &self.src.display(), e));
        if !smeta.is_dir() {
            panic!("Source path is not a folder: `{}`", &self.src.display());
        }

        let sectors = self.dsize / 512;

        // ensure room for filesystem
        assert!(sectors >= 128, "Minimum disk size is 64KiB");

        let mut disk = RamDisk::new(sectors);

        let mut bs_i = 0; // also use index to count size of bootmanager
        loop {
            let mut sector: [u8; 512] = [0; 512];
            match self.boot.read(&mut sector) {
                Ok(0) => { break; }
                Ok(n) => { }
                Err(e) => { panic!("Unable to read global bootloader `{}`: {}", self.out_path.display(), e); },
            }
            disk.write_sector(bs_i, &sector);
            bs_i += 1;
        }

        let pinfo = PartitionInfo {
            format: Format::Fat32,
            size: sectors - bs_i,
            start: bs_i,
            bootable: true,
        };
        disk::set_pinfo(&mut disk, 0, &pinfo).unwrap();

        // TODO: refactor
        { // borrowck strikes again!
            let mut partition = disk::get_partition(&mut disk, 0).unwrap();
            fs::fat::format(&mut partition).unwrap();

            // TODO: support something other than FAT32
            let mut vbr = *partition.read_sector(0).unwrap();

            // Volume Boot Record
            //
            // This sector contains the information necessary to boot a
            // partition which includes a filesystem. The filesystem header
            // occupies some of sector, but after it is the volume bootloader.
            //
            // The volume bootloader is entirely stored in one file,
            // but the first stage of it must be separated from the rest.
            // The first stage is placed in the Volume Boot Record after the
            // filesystem header.
            //
            // All of the contents of the volume bootloader file after the
            // first 512 bytes is placed in a reserved section of the
            // filesystem.

            // Read and offset first stage of volume bootloader
            self.voot.read(&mut vbr).unwrap(); // TODO: error handling

            // manually encode a jmp instruction
            vbr[0] = 0xEB; // relative jmp
            vbr[1] = 90 - 2; // jmp over filesystem header
            vbr[2] = 0x90; // NOP

            assert!(vbr[510] == 0x55, "Invalid volume bootloader signature");
            assert!(vbr[511] == 0xAA, "Invalid volume bootloader signature");

            partition.write_sector(0, &vbr).unwrap();

            // Read stage two of volume boot loader
            let mut i = 1; // skip first sector which has stage1
            loop {
                let mut sector: [u8; 512] = [0; 512];
                match self.boot.read(&mut sector) {
                    Ok(0) => { break; }
                    Ok(n) => { }
                    Err(e) => { panic!("Unable to read volume bootloader `{}`: {}", self.out_path.display(), e); },
                }
                partition.write_sector(i, &sector);
                i += 1;
            }

        }

        fn recurse<T: FileSystem + ?Sized>(fs: &mut T, src: &PathBuf, dir: PathBuf) {
            for item in ::std::fs::read_dir(&dir).unwrap() {
                let item = item.unwrap(); // TODO unwrap
                let rpath = item.path();
                let rc = rpath.clone();
                let vpath = rc.relative_from(src).unwrap();

                debug!("Config::exec::recurse() vpath: {:?}", &vpath);
                let ft = item.file_type().unwrap();
                if ft.is_dir() {
                    fs.make_dir(vpath).unwrap();
                    recurse(fs, src, rpath);
                } else if ft.is_file() {
                    use std::io::Read;

                    let mut file = match File::open(&rpath) {
                        Ok(f) => f,
                        Err(e) => panic!("Cannot open file `{:?}`: {}", rpath, e),
                    };

                    let mut v = Vec::new();
                    file.read_to_end(&mut v).unwrap();

                    fs.write_file(vpath, &v);
                }
            }
        }

        let mut fs = disk::mount(&mut disk, 0).unwrap();
        recurse(fs.deref_mut(), &self.src, self.src.clone());

        for sector in &*disk {
            use std::io::Write;
            self.out.write(sector);
        }
    }
}

// TODO: error handling
fn parse_size(s: &str) -> usize {
    let mut num = s.chars().take_while(|c| c.is_numeric())
                           .collect::<String>()
                           .parse::<usize>()
                           .unwrap_or_else(|_| panic!("Unable to interpret size: `{}`", s));

    const UNITS: [(&'static str, usize); 4] = [
        // update test::parse_size() when expanding
        ("kb",     1_000), ("kib", 1 << 10),
        ("mb", 1_000_000), ("mib", 1 << 20),
    ];

    let mut success = false;
    let unit = s.chars().skip_while(|c| c.is_numeric())
                        .collect::<String>()
                        .to_lowercase();

    // TODO: refactor
    for &(suffix, factor) in &UNITS {
        if unit == suffix {
            success = true;
            num *= factor;
            break;
        }
    }
    if !success {
        panic!("Unknown unit suffix: `{}`", unit);
    }

    num
}

#[cfg(test)]
mod test {
    #[test]
    fn parse_size() {
        assert_eq!(super::parse_size("1mib"),2 << 20);
        assert_eq!(super::parse_size("4MiB"),4 * 2 << 20);
        assert_eq!(super::parse_size("35kb"), 35 * 1_000);
    }
}
<commit_msg>Fix writing reserved sectors<commit_after>#![feature(slice_bytes, path_relative_from)]

use std::fs::File;
use std::path::PathBuf;
use std::ops::DerefMut;

extern crate byteorder;
extern crate docopt;
extern crate env_logger;
#[macro_use] extern crate log;

extern crate disk;

use docopt::Docopt;

use disk::*;

static VERSION: &'static str = "0.0.1";
static USAGE: &'static str = "
Usage: mkdisk [options] <dir>

Creates a bootable disk image.

Options:
    -h, --help     Print this help message
    -v, --version  Print the version of mkdisk
    -s, --size=SIZE           The fixed size of the disk image [default: 4MiB]
    -o, --out=FILE            The output disk image file
    -b, --bootloader=FILE     The master bootloader to use for the first few sectors
    -v, --volume-loader=FILE  The volume bootloader to use for the partition

File sizes measured using KB = 1000, KiB=1024 etc
";

fn main() {
    env_logger::init().unwrap();

    let args: docopt::ArgvMap = Docopt::new(USAGE)
                      .and_then(|d| d.help(true)
                                     .version(Some(VERSION.into()))
                                     .parse())
                      .unwrap_or_else(|e| e.exit());

    let mut config = Config::new(args);
    config.exec();
}

struct Config {
    dsize: usize,
    src: PathBuf,

    boot_path: PathBuf,
    boot: File,

    voot_path: PathBuf,
    voot: File,

    out_path: PathBuf,
    out: File,
}

impl Config {
    pub fn new(args: docopt::ArgvMap) -> Config {
        // default is 4MiB, as specified in USAGE
        let dsize = parse_size(args.get_str("-s"));

        let boot_path: PathBuf = match args.get_str("-b") {
            ""   => panic!("Master bootloader unspecified: use `-b` or `--bootloader`"),
            path => {
                path.into()
            },
        };
        let boot = File::open(&boot_path)
                        .unwrap_or_else(|e| panic!("Unable to open master bootloader `{}`: {}",
                                                   args.get_str("-b"), e));

        let voot_path: PathBuf = match args.get_str("-v") {
            ""   => panic!("Volume bootloader unspecified: use `-v` or `--volume-loader`"),
            path => {
                path.into()
            },
        };
        let voot = File::open(&voot_path)
                        .unwrap_or_else(|e| panic!("Unable to open volume bootloader `{}`: {}",
                                                   args.get_str("-b"), e));

        let src = args.get_str("<dir>").into();
        let out_path = PathBuf::from(match args.get_str("-o") {
            "" => {
                // if source dir is `bin/fs/`, then the output file becomes `bin/fs.disk`
                let mut path = PathBuf::from(&src);
                path.set_extension("disk");
                path
            },
            s  => PathBuf::from(s),
        });

        let out = File::create(&out_path)
                       .unwrap_or_else(|e| panic!("Unable to open output file `{:?}`: {}", out_path, e));

        Config {
            dsize: dsize,
            src: src,

            boot_path: boot_path,
            boot: boot,

            voot_path: voot_path,
            voot: voot,

            out_path: out_path,
            out: out,
        }
    }

    pub fn exec(&mut self) {
        use std::io::{Read, Seek};
        use std::ops::Deref;

        let smeta = ::std::fs::metadata(&self.src)
                              .unwrap_or_else(|e| panic!("Unable to query source `{}`: {}", &self.src.display(), e));
        if !smeta.is_dir() {
            panic!("Source path is not a folder: `{}`", &self.src.display());
        }

        let sectors = self.dsize / 512;

        // ensure room for filesystem
        assert!(sectors >= 128, "Minimum disk size is 64KiB");

        let mut disk = RamDisk::new(sectors);

        let mut bs_i = 0; // also use index to count size of bootmanager
        loop {
            let mut sector: [u8; 512] = [0; 512];
            match self.boot.read(&mut sector) {
                Ok(0) => { break; }
                Ok(n) => { }
                Err(e) => { panic!("Unable to read global bootloader `{}`: {}", self.boot_path.display(), e); },
            }
            disk.write_sector(bs_i, &sector);
            bs_i += 1;
        }

        let pinfo = PartitionInfo {
            format: Format::Fat32,
            size: sectors - bs_i,
            start: bs_i,
            bootable: true,
        };
        disk::set_pinfo(&mut disk, 0, &pinfo).unwrap();

        // TODO: refactor
        { // borrowck strikes again!
            let mut partition = disk::get_partition(&mut disk, 0).unwrap();
            fs::fat::format(&mut partition).unwrap();

            // TODO: support something other than FAT32
            let mut vbr = *partition.read_sector(0).unwrap();

            // Volume Boot Record
            //
            // This sector contains the information necessary to boot a
            // partition which includes a filesystem. The filesystem header
            // occupies some of sector, but after it is the volume bootloader.
            //
            // The volume bootloader is entirely stored in one file,
            // but the first stage of it must be separated from the rest.
            // The first stage is placed in the Volume Boot Record after the
            // filesystem header.
            //
            // All of the contents of the volume bootloader file after the
            // first 512 bytes is placed in a reserved section of the
            // filesystem.

            // Read and offset first stage of volume bootloader
            self.voot.read(&mut vbr).unwrap(); // TODO: error handling

            // manually encode a jmp instruction
            vbr[0] = 0xEB; // relative jmp
            vbr[1] = 90 - 2; // jmp over filesystem header
            vbr[2] = 0x90; // NOP

            assert!(vbr[510] == 0x55, "Invalid volume bootloader signature");
            assert!(vbr[511] == 0xAA, "Invalid volume bootloader signature");

            partition.write_sector(0, &vbr).unwrap();

            // Read stage two of volume boot loader
            let mut i = 1; // skip first sector which has stage1
            loop {
                let mut sector: [u8; 512] = [0; 512];
                match self.voot.read(&mut sector) {
                    Ok(0) => { break; }
                    Ok(n) => { }
                    Err(e) => { panic!("Unable to read volume bootloader `{}`: {}", self.voot_path.display(), e); },
                }
                partition.write_sector(i, &sector);
                i += 1;
            }
        }

        fn recurse<T: FileSystem + ?Sized>(fs: &mut T, src: &PathBuf, dir: PathBuf) {
            for item in ::std::fs::read_dir(&dir).unwrap() {
                let item = item.unwrap(); // TODO unwrap
                let rpath = item.path();
                let rc = rpath.clone();
                let vpath = rc.relative_from(src).unwrap();

                debug!("Config::exec::recurse() vpath: {:?}", &vpath);
                let ft = item.file_type().unwrap();
                if ft.is_dir() {
                    fs.make_dir(vpath).unwrap();
                    recurse(fs, src, rpath);
                } else if ft.is_file() {
                    use std::io::Read;

                    let mut file = match File::open(&rpath) {
                        Ok(f) => f,
                        Err(e) => panic!("Cannot open file `{:?}`: {}", rpath, e),
                    };

                    let mut v = Vec::new();
                    file.read_to_end(&mut v).unwrap();

                    fs.write_file(vpath, &v);
                }
            }
        }

        let mut fs = disk::mount(&mut disk, 0).unwrap();
        recurse(fs.deref_mut(), &self.src, self.src.clone());

        for sector in &*disk {
            use std::io::Write;
            self.out.write(sector);
        }
    }
}

// TODO: error handling
fn parse_size(s: &str) -> usize {
    let mut num = s.chars().take_while(|c| c.is_numeric())
                           .collect::<String>()
                           .parse::<usize>()
                           .unwrap_or_else(|_| panic!("Unable to interpret size: `{}`", s));

    const UNITS: [(&'static str, usize); 4] = [
        // update test::parse_size() when expanding
        ("kb",     1_000), ("kib", 1 << 10),
        ("mb", 1_000_000), ("mib", 1 << 20),
    ];

    let mut success = false;
    let unit = s.chars().skip_while(|c| c.is_numeric())
                        .collect::<String>()
                        .to_lowercase();

    // TODO: refactor
    for &(suffix, factor) in &UNITS {
        if unit == suffix {
            success = true;
            num *= factor;
            break;
        }
    }
    if !success {
        panic!("Unknown unit suffix: `{}`", unit);
    }

    num
}

#[cfg(test)]
mod test {
    #[test]
    fn parse_size() {
        assert_eq!(super::parse_size("1mib"),2 << 20);
        assert_eq!(super::parse_size("4MiB"),4 * 2 << 20);
        assert_eq!(super::parse_size("35kb"), 35 * 1_000);
    }
}
<|endoftext|>


<commit_before>use super::{LzfResult,LzfError};
use std::ptr;
use std::mem;

pub fn decompress(data: &[u8], out_len_should: usize) -> LzfResult<Vec<u8>> {
    let mut current_offset = 0;

    // We have sanity checks to not exceed this capacity.
    let mut output = Vec::with_capacity(out_len_should);
    unsafe { output.set_len(out_len_should) };
    let mut out_len : usize = 0;

    let in_len = data.len();

    while current_offset < in_len {
        let mut ctrl = unsafe{*data.get_unchecked(current_offset)} as usize;
        current_offset += 1;

        if ctrl < (1<<5) {
            ctrl += 1;

            if out_len + ctrl > out_len_should {
                return Err(LzfError::BufferTooSmall);
            }

            if current_offset+ctrl > in_len {
                return Err(LzfError::DataCorrupted);
            }

            // We can simply memcpy everything from the input to the output
            unsafe {
                let (src, _) : (*const u8, usize) = mem::transmute(&data[..]);
                let src = src.offset(current_offset as isize);
                let (dst, _) : (*mut u8, usize) = mem::transmute(&output[..]);
                let dst = dst.offset((out_len) as isize);
                ptr::copy_nonoverlapping(src, dst, ctrl);

                current_offset += ctrl;
                out_len += ctrl;
            }
        } else {
            let mut len = ctrl >> 5;

            let mut ref_offset = (((ctrl & 0x1f) << 8) + 1) as i32;

            if len == 7 {
                len += unsafe{*data.get_unchecked(current_offset)} as usize;
                current_offset += 1;

                if current_offset >= in_len {
                    return Err(LzfError::DataCorrupted);
                }
            }

            ref_offset += unsafe{*data.get_unchecked(current_offset)} as i32;
            current_offset += 1;

            if current_offset + len + 2 > out_len_should {
                return Err(LzfError::BufferTooSmall);
            }

            let mut ref_pos = (out_len as i32) - ref_offset;
            if ref_pos < 0 {
                return Err(LzfError::DataCorrupted);
            }

            let c = unsafe{*output.get_unchecked(ref_pos as usize)};
            output[out_len] = c;
            out_len += 1;
            ref_pos += 1;

            let c = unsafe{*output.get_unchecked(ref_pos as usize)};
            output[out_len] = c;
            out_len += 1;
            ref_pos += 1;

            // We can safely use copy_nonoverlapping here,
            // as we know that we only copy data from before our current insertion point.
            unsafe {
                let (src, _) : (*const u8, usize) = mem::transmute(&output[..]);
                let src = src.offset(ref_pos as isize);
                let (dst, _) : (*mut u8, usize) = mem::transmute(&output[..]);
                let dst = dst.offset((out_len) as isize);

                ptr::copy_nonoverlapping(src, dst, len);
                out_len += len;
            }
        }
    }

    // Set the real length now, user might have passed a bigger buffer in the first place.
    unsafe { output.set_len(out_len) };

    Ok(output)
}

#[test]
fn test_native_decompress_lorem() {
    use super::compress;

    let lorem = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod \
                 tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At \
                 vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, \
                 no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit \
                 amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut \
                 labore et dolore magna aliquyam erat, sed diam voluptua.";

    let compressed = compress(lorem.as_bytes()).unwrap();

    let decompressed = decompress(&compressed[..], lorem.len()).unwrap();
    assert_eq!(lorem.as_bytes(), &decompressed[..]);

    let decompressed = decompress(&compressed[..], 1000).unwrap();
    assert_eq!(lorem.len(), decompressed.len());
}
<commit_msg>Add same tests as for C version<commit_after>use super::{LzfResult,LzfError};
use std::ptr;
use std::mem;

pub fn decompress(data: &[u8], out_len_should: usize) -> LzfResult<Vec<u8>> {
    let mut current_offset = 0;

    // We have sanity checks to not exceed this capacity.
    let mut output = Vec::with_capacity(out_len_should);
    unsafe { output.set_len(out_len_should) };
    let mut out_len : usize = 0;

    let in_len = data.len();

    while current_offset < in_len {
        let mut ctrl = unsafe{*data.get_unchecked(current_offset)} as usize;
        current_offset += 1;

        if ctrl < (1<<5) {
            ctrl += 1;

            if out_len + ctrl > out_len_should {
                return Err(LzfError::BufferTooSmall);
            }

            if current_offset+ctrl > in_len {
                return Err(LzfError::DataCorrupted);
            }

            // We can simply memcpy everything from the input to the output
            unsafe {
                let (src, _) : (*const u8, usize) = mem::transmute(&data[..]);
                let src = src.offset(current_offset as isize);
                let (dst, _) : (*mut u8, usize) = mem::transmute(&output[..]);
                let dst = dst.offset((out_len) as isize);
                ptr::copy_nonoverlapping(src, dst, ctrl);

                current_offset += ctrl;
                out_len += ctrl;
            }
        } else {
            let mut len = ctrl >> 5;

            let mut ref_offset = (((ctrl & 0x1f) << 8) + 1) as i32;

            if len == 7 {
                len += unsafe{*data.get_unchecked(current_offset)} as usize;
                current_offset += 1;

                if current_offset >= in_len {
                    return Err(LzfError::DataCorrupted);
                }
            }

            ref_offset += unsafe{*data.get_unchecked(current_offset)} as i32;
            current_offset += 1;

            if current_offset + len + 2 > out_len_should {
                return Err(LzfError::BufferTooSmall);
            }

            let mut ref_pos = (out_len as i32) - ref_offset;
            if ref_pos < 0 {
                return Err(LzfError::DataCorrupted);
            }

            let c = unsafe{*output.get_unchecked(ref_pos as usize)};
            output[out_len] = c;
            out_len += 1;
            ref_pos += 1;

            let c = unsafe{*output.get_unchecked(ref_pos as usize)};
            output[out_len] = c;
            out_len += 1;
            ref_pos += 1;

            // We can safely use copy_nonoverlapping here,
            // as we know that we only copy data from before our current insertion point.
            unsafe {
                let (src, _) : (*const u8, usize) = mem::transmute(&output[..]);
                let src = src.offset(ref_pos as isize);
                let (dst, _) : (*mut u8, usize) = mem::transmute(&output[..]);
                let dst = dst.offset((out_len) as isize);

                ptr::copy_nonoverlapping(src, dst, len);
                out_len += len;
            }
        }
    }

    // Set the real length now, user might have passed a bigger buffer in the first place.
    unsafe { output.set_len(out_len) };

    Ok(output)
}

#[test]
fn test_decompress_lorem() {
    use super::compress;

    let lorem = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod \
                 tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At \
                 vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, \
                 no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit \
                 amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut \
                 labore et dolore magna aliquyam erat, sed diam voluptua.";

    let compressed = compress(lorem.as_bytes()).unwrap();

    let decompressed = decompress(&compressed[..], lorem.len()).unwrap();
    assert_eq!(lorem.as_bytes(), &decompressed[..]);

    let decompressed = decompress(&compressed[..], 1000).unwrap();
    assert_eq!(lorem.len(), decompressed.len());
}

#[test]
fn test_decompress_fails_with_short_buffer() {
    use super::compress;

    let lorem = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.";

    let compressed = match compress(lorem.as_bytes()) {
        Ok(c) => c,
        Err(err) => panic!("Compression failed with error {:?}", err)
    };

    match decompress(&compressed, 10) {
        Ok(_) => panic!("Decompression worked. That should not happen"),
        Err(err) => assert_eq!(LzfError::BufferTooSmall, err)
    }
}

#[test]
fn test_decompress_fails_for_corrupted_data() {
    let lorem = "Lorem ipsum dolor sit amet";

    match decompress(lorem.as_bytes(), lorem.len()) {
        Ok(_) => panic!("Decompression worked. That should not happen"),
        Err(err) => assert_eq!(LzfError::DataCorrupted, err)
    }
}
<|endoftext|>