fast_ntfs 1.0.2

Forked a low-level NTFS filesystem library
Documentation
mod common;

use std::io::Cursor;

use fast_ntfs::attribute_value::NtfsAttributeValue;
use fast_ntfs::indexes::NtfsFileNameIndex;
use fast_ntfs::io::SeekFrom;
use fast_ntfs::{Ntfs, NtfsFile, NtfsReadSeek};

fn file_by_name<'n>(ntfs: &'n Ntfs, fs: &mut Cursor<Vec<u8>>, name: &str) -> NtfsFile<'n> {
    let root_directory = ntfs.root_directory(fs).unwrap();
    let index = root_directory.directory_index(fs).unwrap();
    let mut finder = index.finder();
    let entry = NtfsFileNameIndex::find(&mut finder, ntfs, fs, name)
        .unwrap()
        .unwrap();
    entry.to_file(ntfs, fs).unwrap()
}

#[test]
fn reads_complete_non_resident_value() {
    let mut fs = common::testfs1();
    let ntfs = Ntfs::new(&mut fs).unwrap();
    ntfs.read_upcase_table(&mut fs).unwrap();
    let file = file_by_name(&ntfs, &mut fs, "1000-bytes-file");
    let item = file.data(&mut fs, "").unwrap().unwrap();
    let attribute = item.to_attribute().unwrap();
    let mut value = attribute.value(&mut fs).unwrap();
    let mut buffer = [0xcc; 1001];

    assert!(!attribute.is_resident());
    assert_eq!(value.read(&mut fs, &mut buffer).unwrap(), 1000);
    assert_eq!(&buffer[..1000], b"12345".repeat(200));
    assert_eq!(buffer[1000], 0xcc);

    value.seek(&mut fs, SeekFrom::Start(1026)).unwrap();
    assert!(value.data_position().value().is_none());
}

#[test]
fn reads_sparse_data_runs_as_zeroes() {
    let mut fs = common::testfs1();
    let ntfs = Ntfs::new(&mut fs).unwrap();
    ntfs.read_upcase_table(&mut fs).unwrap();
    let file = file_by_name(&ntfs, &mut fs, "sparse-file");
    let item = file.data(&mut fs, "").unwrap().unwrap();
    let attribute = item.to_attribute().unwrap();
    let non_resident = match attribute.value(&mut fs).unwrap() {
        NtfsAttributeValue::NonResident(value) => value,
        _ => panic!("sparse data must be non-resident"),
    };
    let mut data_runs = non_resident.data_runs();

    assert!(
        data_runs
            .next()
            .unwrap()
            .unwrap()
            .data_position()
            .value()
            .is_some()
    );
    assert!(
        data_runs
            .next()
            .unwrap()
            .unwrap()
            .data_position()
            .value()
            .is_none()
    );
    assert!(
        data_runs
            .next()
            .unwrap()
            .unwrap()
            .data_position()
            .value()
            .is_some()
    );
    assert!(data_runs.next().is_none());

    let mut value = attribute.value(&mut fs).unwrap();
    let mut buffer = vec![0u8; 500_005];
    assert_eq!(value.read(&mut fs, &mut buffer).unwrap(), buffer.len());
    assert_eq!(&buffer[..5], b"12345");
    assert!(buffer[5..500_000].iter().all(|byte| *byte == 0));
    assert_eq!(&buffer[500_000..], b"11111");
}

#[test]
fn resident_and_non_resident_values_share_read_and_seek_semantics() {
    let mut fs = common::testfs1();
    let ntfs = Ntfs::new(&mut fs).unwrap();
    ntfs.read_upcase_table(&mut fs).unwrap();

    for (name, length) in [("file-with-12345", 5), ("1000-bytes-file", 1000)] {
        let file = file_by_name(&ntfs, &mut fs, name);
        let item = file.data(&mut fs, "").unwrap().unwrap();
        let attribute = item.to_attribute().unwrap();
        let mut value = attribute.value(&mut fs).unwrap();

        assert_eq!(value.len(), length);
        value.seek(&mut fs, SeekFrom::End(-5)).unwrap();

        let mut tail = [0u8; 5];
        value.read_exact(&mut fs, &mut tail).unwrap();
        assert_eq!(&tail, b"12345");
        assert_eq!(value.stream_position(), length);

        value.seek(&mut fs, SeekFrom::Start(length + 1)).unwrap();
        assert_eq!(value.stream_position(), length + 1);
        assert_eq!(value.read(&mut fs, &mut [0u8; 1]).unwrap(), 0);
    }
}