1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! Get the real on-disk size of a file, taking into account compression and
//! sparse allocation.
//!
//! On Unix this uses `std::fs::symlink_metadata()` and `std::os::unix::fs::MetadataExt`.
//!
//! On Windows it uses `GetCompressedFileSizeW()`.
//!
//! Returns the logical size on platforms other than Unix and Windows.

use std::fs::Metadata;
use std::path::Path;

#[cfg(unix)]
mod imp {
    use std::fs::Metadata;
    use std::os::unix::fs::MetadataExt;
    use std::path::Path;

    pub fn file_real_size<P: AsRef<Path>>(path: P) -> std::io::Result<u64> {
        Ok(std::fs::symlink_metadata(path)?.blocks() * 512)
    }

    pub fn file_real_size_fast<P: AsRef<Path>>(_path: P, metadata: &Metadata) -> std::io::Result<u64> {
        Ok(metadata.blocks() * 512)
    }
}

#[cfg(windows)]
mod imp {
    use std::fs::Metadata;
    use std::os::windows::ffi::OsStrExt;
    use std::path::Path;

    use winapi::shared::winerror::NO_ERROR;
    use winapi::um::fileapi::{GetCompressedFileSizeW, INVALID_FILE_SIZE};

    pub fn file_real_size<P: AsRef<Path>>(path: P) -> std::io::Result<u64> {
        let path = std::fs::canonicalize(path)?.into_os_string();
        let mut pathw: Vec<u16> = Vec::with_capacity(path.len() + 1);
        pathw.extend(path.encode_wide());
        pathw.push(0);

        let mut high: u32 = 0;
        let low = unsafe { GetCompressedFileSizeW(pathw.as_ptr(), &mut high) };

        if low == INVALID_FILE_SIZE {
            let err = std::io::Error::last_os_error();
            if err.raw_os_error().map(|e| e as u32).unwrap_or(NO_ERROR) != NO_ERROR {
                return Err(err);
            }
        }

        Ok(u64::from(high) << 32 | u64::from(low))
    }

    pub fn file_real_size_fast<P: AsRef<Path>>(path: P, _metadata: &Metadata) -> std::io::Result<u64> {
        file_real_size(path)
    }
}

#[cfg(not(any(windows, unix)))]
mod imp {
    use std::fs::Metadata;
    use std::path::Path;

    pub fn file_real_size<P: AsRef<Path>>(path: P) -> std::io::Result<u64> {
        std::fs::symlink_metadata(path)?.len()
    }

    pub fn file_real_size_fast<P: AsRef<Path>>(_path: P, metadata: &Metadata) -> std::io::Result<u64> {
        Ok(metadata.len())
    }
}

/// Get the on-disk size of the file at the given `path`.
///
/// ```rust
/// # fn main() -> std::io::Result<()> {
/// let realsize = filesize::file_real_size("Cargo.toml")?;
/// # Ok(())
/// # }
/// ```
pub fn file_real_size<P: AsRef<Path>>(path: P) -> std::io::Result<u64> {
    self::imp::file_real_size(path)
}

/// Get the on-disk size of the file at the given `path`, using a reference to
/// a previously-fetched `Metadata` struct if necessary.
///
/// This avoids an unnecessary `stat()` call on Unix if `Metadata` has already
/// been retrieved.
///
/// ```rust
/// # fn main() -> std::io::Result<()> {
/// let realsize = filesize::file_real_size_fast(
///     "Cargo.toml",
///      &std::fs::symlink_metadata("Cargo.toml")?
/// )?;
/// # Ok(())
/// # }
/// ```
pub fn file_real_size_fast<P: AsRef<Path>>(path: P, metadata: &Metadata) -> std::io::Result<u64> {
    self::imp::file_real_size_fast(path, metadata)
}

#[test]
fn it_seems_to_work() {
    assert!(
        file_real_size("Cargo.toml").expect("file_real_size")
            == file_real_size_fast(
                "Cargo.toml",
                &std::fs::symlink_metadata("Cargo.toml").expect("stat")
            )
            .expect("file_real_size_fast")
    );
}