use std::cell::RefCell;
use std::fs::File;
use std::io::{self, BufRead, Read, Write};
use std::path::Path;
use std::sync::atomic::AtomicUsize;
#[cfg(target_os = "linux")]
use std::sync::atomic::{AtomicBool, Ordering};
use digest::Digest;
use md5::Md5;
#[cfg(target_os = "linux")]
mod openssl_evp {
use std::ffi::CStr;
use std::io;
use std::ptr;
use std::sync::OnceLock;
type EvpMdCtx = *mut libc::c_void;
type EvpMd = *const libc::c_void;
type Engine = *const libc::c_void;
type FnEvpMdGetter = unsafe extern "C" fn() -> EvpMd;
type FnEvpMdCtxNew = unsafe extern "C" fn() -> EvpMdCtx;
type FnEvpDigestInitEx = unsafe extern "C" fn(EvpMdCtx, EvpMd, Engine) -> libc::c_int;
type FnEvpDigestUpdate =
unsafe extern "C" fn(EvpMdCtx, *const libc::c_void, libc::size_t) -> libc::c_int;
type FnEvpDigestFinalEx =
unsafe extern "C" fn(EvpMdCtx, *mut u8, *mut libc::c_uint) -> libc::c_int;
type FnEvpMdCtxFree = unsafe extern "C" fn(EvpMdCtx);
struct OpenSslFns {
evp_md5: FnEvpMdGetter,
evp_sha1: FnEvpMdGetter,
evp_sha224: FnEvpMdGetter,
evp_sha256: FnEvpMdGetter,
evp_sha384: FnEvpMdGetter,
evp_sha512: FnEvpMdGetter,
evp_md_ctx_new: FnEvpMdCtxNew,
evp_digest_init_ex: FnEvpDigestInitEx,
evp_digest_update: FnEvpDigestUpdate,
evp_digest_final_ex: FnEvpDigestFinalEx,
evp_md_ctx_free: FnEvpMdCtxFree,
_handle: *mut libc::c_void, }
unsafe impl Send for OpenSslFns {}
unsafe impl Sync for OpenSslFns {}
static FNS: OnceLock<Option<OpenSslFns>> = OnceLock::new();
fn dlsym_checked(handle: *mut libc::c_void, name: &CStr) -> Option<*mut libc::c_void> {
let ptr = unsafe { libc::dlsym(handle, name.as_ptr()) };
if ptr.is_null() { None } else { Some(ptr) }
}
struct DlopenHandle(*mut libc::c_void);
impl Drop for DlopenHandle {
fn drop(&mut self) {
unsafe {
libc::dlclose(self.0);
}
}
}
fn try_load() -> Option<OpenSslFns> {
let handle = unsafe {
let h = libc::dlopen(
c"libcrypto.so.3".as_ptr(),
libc::RTLD_LAZY | libc::RTLD_LOCAL,
);
if h.is_null() {
let h = libc::dlopen(
c"libcrypto.so.1.1".as_ptr(),
libc::RTLD_LAZY | libc::RTLD_LOCAL,
);
if h.is_null() {
return None;
}
h
} else {
h
}
};
let guard = DlopenHandle(handle);
unsafe {
let evp_md5: FnEvpMdGetter = std::mem::transmute(dlsym_checked(handle, c"EVP_md5")?);
let evp_sha1: FnEvpMdGetter = std::mem::transmute(dlsym_checked(handle, c"EVP_sha1")?);
let evp_sha224: FnEvpMdGetter =
std::mem::transmute(dlsym_checked(handle, c"EVP_sha224")?);
let evp_sha256: FnEvpMdGetter =
std::mem::transmute(dlsym_checked(handle, c"EVP_sha256")?);
let evp_sha384: FnEvpMdGetter =
std::mem::transmute(dlsym_checked(handle, c"EVP_sha384")?);
let evp_sha512: FnEvpMdGetter =
std::mem::transmute(dlsym_checked(handle, c"EVP_sha512")?);
let evp_md_ctx_new: FnEvpMdCtxNew =
std::mem::transmute(dlsym_checked(handle, c"EVP_MD_CTX_new")?);
let evp_digest_init_ex: FnEvpDigestInitEx =
std::mem::transmute(dlsym_checked(handle, c"EVP_DigestInit_ex")?);
let evp_digest_update: FnEvpDigestUpdate =
std::mem::transmute(dlsym_checked(handle, c"EVP_DigestUpdate")?);
let evp_digest_final_ex: FnEvpDigestFinalEx =
std::mem::transmute(dlsym_checked(handle, c"EVP_DigestFinal_ex")?);
let evp_md_ctx_free: FnEvpMdCtxFree =
std::mem::transmute(dlsym_checked(handle, c"EVP_MD_CTX_free")?);
std::mem::forget(guard);
Some(OpenSslFns {
evp_md5,
evp_sha1,
evp_sha224,
evp_sha256,
evp_sha384,
evp_sha512,
evp_md_ctx_new,
evp_digest_init_ex,
evp_digest_update,
evp_digest_final_ex,
evp_md_ctx_free,
_handle: handle,
})
}
}
fn get_fns() -> Option<&'static OpenSslFns> {
FNS.get_or_init(try_load).as_ref()
}
pub fn is_available() -> bool {
get_fns().is_some()
}
struct EvpCtx {
ctx: EvpMdCtx,
free_fn: FnEvpMdCtxFree,
}
impl Drop for EvpCtx {
fn drop(&mut self) {
if !self.ctx.is_null() {
unsafe {
(self.free_fn)(self.ctx);
}
}
}
}
#[derive(Clone, Copy)]
pub enum EvpAlgorithm {
Md5,
Sha1,
Sha224,
Sha256,
Sha384,
Sha512,
}
impl EvpAlgorithm {
fn digest_len(self) -> usize {
match self {
EvpAlgorithm::Md5 => 16,
EvpAlgorithm::Sha1 => 20,
EvpAlgorithm::Sha224 => 28,
EvpAlgorithm::Sha256 => 32,
EvpAlgorithm::Sha384 => 48,
EvpAlgorithm::Sha512 => 64,
}
}
fn get_md(self, fns: &OpenSslFns) -> EvpMd {
unsafe {
match self {
EvpAlgorithm::Md5 => (fns.evp_md5)(),
EvpAlgorithm::Sha1 => (fns.evp_sha1)(),
EvpAlgorithm::Sha224 => (fns.evp_sha224)(),
EvpAlgorithm::Sha256 => (fns.evp_sha256)(),
EvpAlgorithm::Sha384 => (fns.evp_sha384)(),
EvpAlgorithm::Sha512 => (fns.evp_sha512)(),
}
}
}
}
pub fn hash_bytes(algo: EvpAlgorithm, data: &[u8]) -> io::Result<Vec<u8>> {
let fns = get_fns().ok_or_else(|| io::Error::other("OpenSSL not available"))?;
unsafe {
let md = algo.get_md(fns);
if md.is_null() {
return Err(io::Error::other("EVP_* returned null"));
}
let ctx = (fns.evp_md_ctx_new)();
if ctx.is_null() {
return Err(io::Error::other("EVP_MD_CTX_new failed"));
}
let _guard = EvpCtx {
ctx,
free_fn: fns.evp_md_ctx_free,
};
if (fns.evp_digest_init_ex)(ctx, md, ptr::null()) != 1 {
return Err(io::Error::other("EVP_DigestInit_ex failed"));
}
if !data.is_empty()
&& (fns.evp_digest_update)(ctx, data.as_ptr() as *const libc::c_void, data.len())
!= 1
{
return Err(io::Error::other("EVP_DigestUpdate failed"));
}
let mut out = vec![0u8; algo.digest_len()];
let mut out_len: libc::c_uint = 0;
if (fns.evp_digest_final_ex)(ctx, out.as_mut_ptr(), &mut out_len) != 1 {
return Err(io::Error::other("EVP_DigestFinal_ex failed"));
}
out.truncate(out_len as usize);
Ok(out)
}
}
pub fn hash_reader(algo: EvpAlgorithm, mut reader: impl std::io::Read) -> io::Result<Vec<u8>> {
let fns = get_fns().ok_or_else(|| io::Error::other("OpenSSL not available"))?;
unsafe {
let md = algo.get_md(fns);
if md.is_null() {
return Err(io::Error::other("EVP_* returned null"));
}
let ctx = (fns.evp_md_ctx_new)();
if ctx.is_null() {
return Err(io::Error::other("EVP_MD_CTX_new failed"));
}
let _guard = EvpCtx {
ctx,
free_fn: fns.evp_md_ctx_free,
};
if (fns.evp_digest_init_ex)(ctx, md, ptr::null()) != 1 {
return Err(io::Error::other("EVP_DigestInit_ex failed"));
}
super::STREAM_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
super::ensure_stream_buf(&mut buf);
loop {
let n = super::read_full(&mut reader, &mut buf)?;
if n == 0 {
break;
}
if (fns.evp_digest_update)(ctx, buf[..n].as_ptr() as *const libc::c_void, n)
!= 1
{
return Err(io::Error::other("EVP_DigestUpdate failed"));
}
}
Ok(())
})?;
let mut out = vec![0u8; algo.digest_len()];
let mut out_len: libc::c_uint = 0;
if (fns.evp_digest_final_ex)(ctx, out.as_mut_ptr(), &mut out_len) != 1 {
return Err(io::Error::other("EVP_DigestFinal_ex failed"));
}
out.truncate(out_len as usize);
Ok(out)
}
}
pub fn hash_reader_with_prefix(
algo: EvpAlgorithm,
prefix: &[u8],
mut reader: impl std::io::Read,
) -> io::Result<Vec<u8>> {
let fns = get_fns().ok_or_else(|| io::Error::other("OpenSSL not available"))?;
unsafe {
let md = algo.get_md(fns);
if md.is_null() {
return Err(io::Error::other("EVP_* returned null"));
}
let ctx = (fns.evp_md_ctx_new)();
if ctx.is_null() {
return Err(io::Error::other("EVP_MD_CTX_new failed"));
}
let _guard = EvpCtx {
ctx,
free_fn: fns.evp_md_ctx_free,
};
if (fns.evp_digest_init_ex)(ctx, md, ptr::null()) != 1 {
return Err(io::Error::other("EVP_DigestInit_ex failed"));
}
if !prefix.is_empty()
&& (fns.evp_digest_update)(
ctx,
prefix.as_ptr() as *const libc::c_void,
prefix.len(),
) != 1
{
return Err(io::Error::other("EVP_DigestUpdate failed"));
}
super::STREAM_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
super::ensure_stream_buf(&mut buf);
loop {
let n = super::read_full(&mut reader, &mut buf)?;
if n == 0 {
break;
}
if (fns.evp_digest_update)(ctx, buf[..n].as_ptr() as *const libc::c_void, n)
!= 1
{
return Err(io::Error::other("EVP_DigestUpdate failed"));
}
}
Ok(())
})?;
let mut out = vec![0u8; algo.digest_len()];
let mut out_len: libc::c_uint = 0;
if (fns.evp_digest_final_ex)(ctx, out.as_mut_ptr(), &mut out_len) != 1 {
return Err(io::Error::other("EVP_DigestFinal_ex failed"));
}
out.truncate(out_len as usize);
Ok(out)
}
}
pub fn hash_pipelined(
algo: EvpAlgorithm,
rx: &std::sync::mpsc::Receiver<(Vec<u8>, usize)>,
buf_tx: &std::sync::mpsc::SyncSender<Vec<u8>>,
) -> io::Result<Vec<u8>> {
let fns = get_fns().ok_or_else(|| io::Error::other("OpenSSL not available"))?;
unsafe {
let md = algo.get_md(fns);
if md.is_null() {
return Err(io::Error::other("EVP_* returned null"));
}
let ctx = (fns.evp_md_ctx_new)();
if ctx.is_null() {
return Err(io::Error::other("EVP_MD_CTX_new failed"));
}
let _guard = EvpCtx {
ctx,
free_fn: fns.evp_md_ctx_free,
};
if (fns.evp_digest_init_ex)(ctx, md, ptr::null()) != 1 {
return Err(io::Error::other("EVP_DigestInit_ex failed"));
}
while let Ok((buf, n)) = rx.recv() {
if (fns.evp_digest_update)(ctx, buf[..n].as_ptr() as *const libc::c_void, n) != 1 {
let _ = buf_tx.send(buf);
return Err(io::Error::other("EVP_DigestUpdate failed"));
}
let _ = buf_tx.send(buf);
}
let mut out = vec![0u8; algo.digest_len()];
let mut out_len: libc::c_uint = 0;
if (fns.evp_digest_final_ex)(ctx, out.as_mut_ptr(), &mut out_len) != 1 {
return Err(io::Error::other("EVP_DigestFinal_ex failed"));
}
out.truncate(out_len as usize);
Ok(out)
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum HashAlgorithm {
Sha1,
Sha224,
Sha256,
Sha384,
Sha512,
Md5,
Blake2b,
}
impl HashAlgorithm {
pub fn name(self) -> &'static str {
match self {
HashAlgorithm::Sha1 => "SHA1",
HashAlgorithm::Sha224 => "SHA224",
HashAlgorithm::Sha256 => "SHA256",
HashAlgorithm::Sha384 => "SHA384",
HashAlgorithm::Sha512 => "SHA512",
HashAlgorithm::Md5 => "MD5",
HashAlgorithm::Blake2b => "BLAKE2b",
}
}
}
fn hash_digest<D: Digest>(data: &[u8]) -> String {
hex_encode(&D::digest(data))
}
fn hash_reader_impl<D: Digest>(mut reader: impl Read) -> io::Result<String> {
STREAM_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
ensure_stream_buf(&mut buf);
let mut hasher = D::new();
loop {
let n = read_full(&mut reader, &mut buf)?;
if n == 0 {
break;
}
hasher.update(&buf[..n]);
}
Ok(hex_encode(&hasher.finalize()))
})
}
const HASH_READ_BUF: usize = 131072;
thread_local! {
static STREAM_BUF: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}
#[inline]
fn ensure_stream_buf(buf: &mut Vec<u8>) {
if buf.len() < HASH_READ_BUF {
buf.resize(HASH_READ_BUF, 0);
}
}
#[cfg(not(target_vendor = "apple"))]
#[inline]
fn ring_hash_bytes(algo: &'static ring::digest::Algorithm, data: &[u8]) -> io::Result<String> {
Ok(hex_encode(ring::digest::digest(algo, data).as_ref()))
}
#[cfg(not(target_vendor = "apple"))]
fn ring_hash_reader(
algo: &'static ring::digest::Algorithm,
mut reader: impl Read,
) -> io::Result<String> {
STREAM_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
ensure_stream_buf(&mut buf);
let mut ctx = ring::digest::Context::new(algo);
loop {
let n = read_full(&mut reader, &mut buf)?;
if n == 0 {
break;
}
ctx.update(&buf[..n]);
}
Ok(hex_encode(ctx.finish().as_ref()))
})
}
#[cfg(target_vendor = "apple")]
fn sha256_bytes(data: &[u8]) -> io::Result<String> {
Ok(hash_digest::<sha2::Sha256>(data))
}
#[cfg(target_os = "linux")]
fn sha256_bytes(data: &[u8]) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_bytes(openssl_evp::EvpAlgorithm::Sha256, data)?;
return Ok(hex_encode(&digest));
}
Ok(hash_digest::<sha2::Sha256>(data))
}
#[cfg(all(not(target_os = "linux"), not(target_vendor = "apple")))]
fn sha256_bytes(data: &[u8]) -> io::Result<String> {
ring_hash_bytes(&ring::digest::SHA256, data)
}
#[cfg(target_vendor = "apple")]
fn sha256_reader(reader: impl Read) -> io::Result<String> {
hash_reader_impl::<sha2::Sha256>(reader)
}
#[cfg(target_os = "linux")]
fn sha256_reader(reader: impl Read) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_reader(openssl_evp::EvpAlgorithm::Sha256, reader)?;
return Ok(hex_encode(&digest));
}
hash_reader_impl::<sha2::Sha256>(reader)
}
#[cfg(all(not(target_os = "linux"), not(target_vendor = "apple")))]
fn sha256_reader(reader: impl Read) -> io::Result<String> {
ring_hash_reader(&ring::digest::SHA256, reader)
}
#[cfg(target_vendor = "apple")]
fn sha1_bytes(data: &[u8]) -> io::Result<String> {
Ok(hash_digest::<sha1::Sha1>(data))
}
#[cfg(target_os = "linux")]
fn sha1_bytes(data: &[u8]) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_bytes(openssl_evp::EvpAlgorithm::Sha1, data)?;
return Ok(hex_encode(&digest));
}
ring_hash_bytes(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY, data)
}
#[cfg(all(not(target_os = "linux"), not(target_vendor = "apple")))]
fn sha1_bytes(data: &[u8]) -> io::Result<String> {
ring_hash_bytes(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY, data)
}
#[cfg(target_vendor = "apple")]
fn sha1_reader(reader: impl Read) -> io::Result<String> {
hash_reader_impl::<sha1::Sha1>(reader)
}
#[cfg(target_os = "linux")]
fn sha1_reader(reader: impl Read) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_reader(openssl_evp::EvpAlgorithm::Sha1, reader)?;
return Ok(hex_encode(&digest));
}
ring_hash_reader(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY, reader)
}
#[cfg(all(not(target_os = "linux"), not(target_vendor = "apple")))]
fn sha1_reader(reader: impl Read) -> io::Result<String> {
ring_hash_reader(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY, reader)
}
#[cfg(target_os = "linux")]
fn sha224_bytes(data: &[u8]) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_bytes(openssl_evp::EvpAlgorithm::Sha224, data)?;
return Ok(hex_encode(&digest));
}
Ok(hash_digest::<sha2::Sha224>(data))
}
#[cfg(not(target_os = "linux"))]
fn sha224_bytes(data: &[u8]) -> io::Result<String> {
Ok(hash_digest::<sha2::Sha224>(data))
}
#[cfg(target_os = "linux")]
fn sha224_reader(reader: impl Read) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_reader(openssl_evp::EvpAlgorithm::Sha224, reader)?;
return Ok(hex_encode(&digest));
}
hash_reader_impl::<sha2::Sha224>(reader)
}
#[cfg(not(target_os = "linux"))]
fn sha224_reader(reader: impl Read) -> io::Result<String> {
hash_reader_impl::<sha2::Sha224>(reader)
}
#[cfg(target_vendor = "apple")]
fn sha384_bytes(data: &[u8]) -> io::Result<String> {
Ok(hash_digest::<sha2::Sha384>(data))
}
#[cfg(target_os = "linux")]
fn sha384_bytes(data: &[u8]) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_bytes(openssl_evp::EvpAlgorithm::Sha384, data)?;
return Ok(hex_encode(&digest));
}
ring_hash_bytes(&ring::digest::SHA384, data)
}
#[cfg(all(not(target_os = "linux"), not(target_vendor = "apple")))]
fn sha384_bytes(data: &[u8]) -> io::Result<String> {
ring_hash_bytes(&ring::digest::SHA384, data)
}
#[cfg(target_vendor = "apple")]
fn sha384_reader(reader: impl Read) -> io::Result<String> {
hash_reader_impl::<sha2::Sha384>(reader)
}
#[cfg(target_os = "linux")]
fn sha384_reader(reader: impl Read) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_reader(openssl_evp::EvpAlgorithm::Sha384, reader)?;
return Ok(hex_encode(&digest));
}
ring_hash_reader(&ring::digest::SHA384, reader)
}
#[cfg(all(not(target_os = "linux"), not(target_vendor = "apple")))]
fn sha384_reader(reader: impl Read) -> io::Result<String> {
ring_hash_reader(&ring::digest::SHA384, reader)
}
#[cfg(target_vendor = "apple")]
fn sha512_bytes(data: &[u8]) -> io::Result<String> {
Ok(hash_digest::<sha2::Sha512>(data))
}
#[cfg(target_os = "linux")]
fn sha512_bytes(data: &[u8]) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_bytes(openssl_evp::EvpAlgorithm::Sha512, data)?;
return Ok(hex_encode(&digest));
}
ring_hash_bytes(&ring::digest::SHA512, data)
}
#[cfg(all(not(target_os = "linux"), not(target_vendor = "apple")))]
fn sha512_bytes(data: &[u8]) -> io::Result<String> {
ring_hash_bytes(&ring::digest::SHA512, data)
}
#[cfg(target_vendor = "apple")]
fn sha512_reader(reader: impl Read) -> io::Result<String> {
hash_reader_impl::<sha2::Sha512>(reader)
}
#[cfg(target_os = "linux")]
fn sha512_reader(reader: impl Read) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_reader(openssl_evp::EvpAlgorithm::Sha512, reader)?;
return Ok(hex_encode(&digest));
}
ring_hash_reader(&ring::digest::SHA512, reader)
}
#[cfg(all(not(target_os = "linux"), not(target_vendor = "apple")))]
fn sha512_reader(reader: impl Read) -> io::Result<String> {
ring_hash_reader(&ring::digest::SHA512, reader)
}
pub fn hash_bytes(algo: HashAlgorithm, data: &[u8]) -> io::Result<String> {
match algo {
HashAlgorithm::Sha1 => sha1_bytes(data),
HashAlgorithm::Sha224 => sha224_bytes(data),
HashAlgorithm::Sha256 => sha256_bytes(data),
HashAlgorithm::Sha384 => sha384_bytes(data),
HashAlgorithm::Sha512 => sha512_bytes(data),
HashAlgorithm::Md5 => md5_bytes(data),
HashAlgorithm::Blake2b => {
let hash = blake2b_simd::blake2b(data);
Ok(hex_encode(hash.as_bytes()))
}
}
}
#[cfg(target_os = "linux")]
pub fn hash_bytes_to_buf(algo: HashAlgorithm, data: &[u8], out: &mut [u8]) -> io::Result<usize> {
match algo {
HashAlgorithm::Md5 => {
if data.len() >= 4 * 1024 * 1024 && openssl_evp::is_available() {
let digest = openssl_evp::hash_bytes(openssl_evp::EvpAlgorithm::Md5, data)?;
hex_encode_to_slice(&digest, out);
return Ok(32);
}
let digest = Md5::digest(data);
hex_encode_to_slice(&digest, out);
Ok(32)
}
HashAlgorithm::Sha1 => {
let digest = sha1::Sha1::digest(data);
hex_encode_to_slice(&digest, out);
Ok(40)
}
HashAlgorithm::Sha224 => {
let digest = sha2::Sha224::digest(data);
hex_encode_to_slice(&digest, out);
Ok(56)
}
HashAlgorithm::Sha256 => {
let digest = sha2::Sha256::digest(data);
hex_encode_to_slice(&digest, out);
Ok(64)
}
HashAlgorithm::Sha384 => {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_bytes(openssl_evp::EvpAlgorithm::Sha384, data)?;
hex_encode_to_slice(&digest, out);
return Ok(96);
}
let digest = ring::digest::digest(&ring::digest::SHA384, data);
hex_encode_to_slice(digest.as_ref(), out);
Ok(96)
}
HashAlgorithm::Sha512 => {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_bytes(openssl_evp::EvpAlgorithm::Sha512, data)?;
hex_encode_to_slice(&digest, out);
return Ok(128);
}
let digest = ring::digest::digest(&ring::digest::SHA512, data);
hex_encode_to_slice(digest.as_ref(), out);
Ok(128)
}
HashAlgorithm::Blake2b => {
let hash = blake2b_simd::blake2b(data);
let bytes = hash.as_bytes();
hex_encode_to_slice(bytes, out);
Ok(bytes.len() * 2)
}
}
}
#[cfg(target_os = "linux")]
pub fn hash_file_raw_to_buf(algo: HashAlgorithm, path: &Path, out: &mut [u8]) -> io::Result<usize> {
use std::os::unix::ffi::OsStrExt;
let path_bytes = path.as_os_str().as_bytes();
let c_path = std::ffi::CString::new(path_bytes)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contains null byte"))?;
let mut flags = libc::O_RDONLY | libc::O_CLOEXEC;
if NOATIME_SUPPORTED.load(Ordering::Relaxed) {
flags |= libc::O_NOATIME;
}
let fd = unsafe { libc::open(c_path.as_ptr(), flags) };
if fd < 0 {
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EPERM) && flags & libc::O_NOATIME != 0 {
NOATIME_SUPPORTED.store(false, Ordering::Relaxed);
let fd2 = unsafe { libc::open(c_path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
if fd2 < 0 {
return Err(io::Error::last_os_error());
}
return hash_from_raw_fd_to_buf(algo, fd2, out);
}
return Err(err);
}
hash_from_raw_fd_to_buf(algo, fd, out)
}
#[cfg(target_os = "linux")]
fn hash_from_raw_fd_to_buf(algo: HashAlgorithm, fd: i32, out: &mut [u8]) -> io::Result<usize> {
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(fd, &mut stat) } != 0 {
let err = io::Error::last_os_error();
unsafe {
libc::close(fd);
}
return Err(err);
}
let size = stat.st_size as u64;
let is_regular = (stat.st_mode & libc::S_IFMT) == libc::S_IFREG;
if is_regular && size == 0 {
unsafe {
libc::close(fd);
}
return hash_bytes_to_buf(algo, &[], out);
}
if is_regular && size < TINY_FILE_LIMIT {
let mut buf = [0u8; 8192];
let mut total = 0usize;
while total < size as usize {
let n = unsafe {
libc::read(
fd,
buf[total..].as_mut_ptr() as *mut libc::c_void,
(size as usize) - total,
)
};
if n < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
unsafe {
libc::close(fd);
}
return Err(err);
}
if n == 0 {
break;
}
total += n as usize;
}
unsafe {
libc::close(fd);
}
return hash_bytes_to_buf(algo, &buf[..total], out);
}
if is_regular && size > 0 {
use std::os::unix::io::FromRawFd;
let file = unsafe { File::from_raw_fd(fd) };
let mmap_result = unsafe { memmap2::MmapOptions::new().map(&file) };
if let Ok(mmap) = mmap_result {
let _ = mmap.advise(memmap2::Advice::Sequential);
if mmap.advise(memmap2::Advice::PopulateRead).is_err() {
let _ = mmap.advise(memmap2::Advice::WillNeed);
}
return hash_bytes_to_buf(algo, &mmap, out);
}
let hash_str = hash_reader(algo, file)?;
let hex_bytes = hash_str.as_bytes();
out[..hex_bytes.len()].copy_from_slice(hex_bytes);
return Ok(hex_bytes.len());
}
use std::os::unix::io::FromRawFd;
let file = unsafe { File::from_raw_fd(fd) };
let hash_str = hash_reader(algo, file)?;
let hex_bytes = hash_str.as_bytes();
out[..hex_bytes.len()].copy_from_slice(hex_bytes);
Ok(hex_bytes.len())
}
#[cfg(target_os = "linux")]
fn md5_bytes(data: &[u8]) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_bytes(openssl_evp::EvpAlgorithm::Md5, data)?;
return Ok(hex_encode(&digest));
}
Ok(hash_digest::<Md5>(data))
}
#[cfg(not(target_os = "linux"))]
fn md5_bytes(data: &[u8]) -> io::Result<String> {
Ok(hash_digest::<Md5>(data))
}
#[cfg(target_os = "linux")]
fn md5_reader(reader: impl Read) -> io::Result<String> {
if openssl_evp::is_available() {
let digest = openssl_evp::hash_reader(openssl_evp::EvpAlgorithm::Md5, reader)?;
return Ok(hex_encode(&digest));
}
hash_reader_impl::<Md5>(reader)
}
#[cfg(not(target_os = "linux"))]
fn md5_reader(reader: impl Read) -> io::Result<String> {
hash_reader_impl::<Md5>(reader)
}
pub fn hash_reader<R: Read>(algo: HashAlgorithm, reader: R) -> io::Result<String> {
match algo {
HashAlgorithm::Sha1 => sha1_reader(reader),
HashAlgorithm::Sha224 => sha224_reader(reader),
HashAlgorithm::Sha256 => sha256_reader(reader),
HashAlgorithm::Sha384 => sha384_reader(reader),
HashAlgorithm::Sha512 => sha512_reader(reader),
HashAlgorithm::Md5 => md5_reader(reader),
HashAlgorithm::Blake2b => blake2b_hash_reader(reader, 64),
}
}
#[cfg(target_os = "linux")]
static NOATIME_SUPPORTED: AtomicBool = AtomicBool::new(true);
#[cfg(target_os = "linux")]
fn open_noatime(path: &Path) -> io::Result<File> {
use std::os::unix::fs::OpenOptionsExt;
if NOATIME_SUPPORTED.load(Ordering::Relaxed) {
match std::fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOATIME)
.open(path)
{
Ok(f) => return Ok(f),
Err(ref e) if e.raw_os_error() == Some(libc::EPERM) => {
NOATIME_SUPPORTED.store(false, Ordering::Relaxed);
}
Err(e) => return Err(e), }
}
File::open(path)
}
#[cfg(not(target_os = "linux"))]
fn open_noatime(path: &Path) -> io::Result<File> {
File::open(path)
}
#[cfg(target_os = "linux")]
#[inline]
fn open_and_stat(path: &Path) -> io::Result<(File, u64, bool)> {
let file = open_noatime(path)?;
let fd = {
use std::os::unix::io::AsRawFd;
file.as_raw_fd()
};
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(fd, &mut stat) } != 0 {
return Err(io::Error::last_os_error());
}
let is_regular = (stat.st_mode & libc::S_IFMT) == libc::S_IFREG;
let size = stat.st_size as u64;
Ok((file, size, is_regular))
}
#[cfg(not(target_os = "linux"))]
#[inline]
fn open_and_stat(path: &Path) -> io::Result<(File, u64, bool)> {
let file = open_noatime(path)?;
let metadata = file.metadata()?;
Ok((file, metadata.len(), metadata.file_type().is_file()))
}
#[cfg(target_os = "linux")]
const FADVISE_MIN_SIZE: u64 = 1024 * 1024;
const SMALL_FILE_LIMIT: u64 = 16 * 1024 * 1024;
const TINY_FILE_LIMIT: u64 = 8 * 1024;
thread_local! {
static SMALL_FILE_BUF: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(64 * 1024));
}
#[cfg(target_os = "linux")]
fn hash_file_pipelined(algo: HashAlgorithm, file: File, file_size: u64) -> io::Result<String> {
if file_size >= 64 * 1024 * 1024 {
hash_file_pipelined_read(algo, file, file_size)
} else {
hash_file_streaming(algo, file, file_size)
}
}
#[cfg(target_os = "linux")]
fn hash_file_streaming(algo: HashAlgorithm, file: File, file_size: u64) -> io::Result<String> {
use std::os::unix::io::AsRawFd;
let _ = unsafe {
libc::posix_fadvise(
file.as_raw_fd(),
0,
file_size as i64,
libc::POSIX_FADV_SEQUENTIAL,
)
};
hash_reader(algo, file)
}
#[cfg(target_os = "linux")]
fn hash_file_pipelined_read(
algo: HashAlgorithm,
mut file: File,
file_size: u64,
) -> io::Result<String> {
use std::os::unix::io::AsRawFd;
const PIPE_BUF_SIZE: usize = 4 * 1024 * 1024;
let _ = unsafe {
libc::posix_fadvise(
file.as_raw_fd(),
0,
file_size as i64,
libc::POSIX_FADV_SEQUENTIAL,
)
};
let (tx, rx) = std::sync::mpsc::sync_channel::<(Vec<u8>, usize)>(1);
let (buf_tx, buf_rx) = std::sync::mpsc::sync_channel::<Vec<u8>>(1);
let _ = buf_tx.send(vec![0u8; PIPE_BUF_SIZE]);
let reader_handle = std::thread::spawn(move || -> io::Result<()> {
while let Ok(mut buf) = buf_rx.recv() {
let mut total = 0;
while total < buf.len() {
match file.read(&mut buf[total..]) {
Ok(0) => break,
Ok(n) => total += n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
if total == 0 {
break;
}
if tx.send((buf, total)).is_err() {
break;
}
}
Ok(())
});
macro_rules! hash_pipelined_digest {
($hasher_init:expr) => {{
let mut hasher = $hasher_init;
while let Ok((buf, n)) = rx.recv() {
hasher.update(&buf[..n]);
let _ = buf_tx.send(buf);
}
Ok(hex_encode(&hasher.finalize()))
}};
}
let evp_algo = match algo {
HashAlgorithm::Md5 => Some(openssl_evp::EvpAlgorithm::Md5),
HashAlgorithm::Sha1 => Some(openssl_evp::EvpAlgorithm::Sha1),
HashAlgorithm::Sha224 => Some(openssl_evp::EvpAlgorithm::Sha224),
HashAlgorithm::Sha256 => Some(openssl_evp::EvpAlgorithm::Sha256),
HashAlgorithm::Sha384 => Some(openssl_evp::EvpAlgorithm::Sha384),
HashAlgorithm::Sha512 => Some(openssl_evp::EvpAlgorithm::Sha512),
HashAlgorithm::Blake2b => None,
};
let hash_result: io::Result<String> = if let Some(evp) =
evp_algo.filter(|_| openssl_evp::is_available())
{
let digest = openssl_evp::hash_pipelined(evp, &rx, &buf_tx)?;
Ok(hex_encode(&digest))
} else {
match algo {
HashAlgorithm::Blake2b => {
let mut state = blake2b_simd::Params::new().to_state();
while let Ok((buf, n)) = rx.recv() {
state.update(&buf[..n]);
let _ = buf_tx.send(buf);
}
Ok(hex_encode(state.finalize().as_bytes()))
}
HashAlgorithm::Md5 => hash_pipelined_digest!(Md5::new()),
HashAlgorithm::Sha224 => hash_pipelined_digest!(sha2::Sha224::new()),
HashAlgorithm::Sha256 => hash_pipelined_digest!(sha2::Sha256::new()),
HashAlgorithm::Sha1 => {
let mut ctx = ring::digest::Context::new(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY);
while let Ok((buf, n)) = rx.recv() {
ctx.update(&buf[..n]);
let _ = buf_tx.send(buf);
}
Ok(hex_encode(ctx.finish().as_ref()))
}
HashAlgorithm::Sha384 => {
let mut ctx = ring::digest::Context::new(&ring::digest::SHA384);
while let Ok((buf, n)) = rx.recv() {
ctx.update(&buf[..n]);
let _ = buf_tx.send(buf);
}
Ok(hex_encode(ctx.finish().as_ref()))
}
HashAlgorithm::Sha512 => {
let mut ctx = ring::digest::Context::new(&ring::digest::SHA512);
while let Ok((buf, n)) = rx.recv() {
ctx.update(&buf[..n]);
let _ = buf_tx.send(buf);
}
Ok(hex_encode(ctx.finish().as_ref()))
}
}
};
match reader_handle.join() {
Ok(Ok(())) => {}
Ok(Err(e)) => {
if hash_result.is_ok() {
return Err(e);
}
}
Err(payload) => {
let msg = if let Some(s) = payload.downcast_ref::<&str>() {
format!("reader thread panicked: {}", s)
} else if let Some(s) = payload.downcast_ref::<String>() {
format!("reader thread panicked: {}", s)
} else {
"reader thread panicked".to_string()
};
return Err(io::Error::other(msg));
}
}
hash_result
}
fn hash_regular_file(algo: HashAlgorithm, file: File, file_size: u64) -> io::Result<String> {
if file_size >= SMALL_FILE_LIMIT {
let mmap_result = unsafe { memmap2::MmapOptions::new().map(&file) };
if let Ok(mmap) = mmap_result {
#[cfg(target_os = "linux")]
{
let _ = mmap.advise(memmap2::Advice::Sequential);
if mmap.advise(memmap2::Advice::PopulateRead).is_err() {
let _ = mmap.advise(memmap2::Advice::WillNeed);
}
}
return hash_bytes(algo, &mmap);
}
#[cfg(target_os = "linux")]
{
return hash_file_pipelined(algo, file, file_size);
}
#[cfg(not(target_os = "linux"))]
{
return hash_reader(algo, file);
}
}
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
let _ = unsafe {
libc::posix_fadvise(
file.as_raw_fd(),
0,
file_size as i64,
libc::POSIX_FADV_SEQUENTIAL,
)
};
}
hash_file_small(algo, file, file_size as usize)
}
pub fn hash_file(algo: HashAlgorithm, path: &Path) -> io::Result<String> {
let (file, file_size, is_regular) = open_and_stat(path)?;
if is_regular && file_size == 0 {
return hash_bytes(algo, &[]);
}
if file_size > 0 && is_regular {
if file_size < TINY_FILE_LIMIT {
return hash_file_tiny(algo, file, file_size as usize);
}
return hash_regular_file(algo, file, file_size);
}
#[cfg(target_os = "linux")]
if file_size >= FADVISE_MIN_SIZE {
use std::os::unix::io::AsRawFd;
let _ = unsafe {
libc::posix_fadvise(
file.as_raw_fd(),
0,
file_size as i64,
libc::POSIX_FADV_SEQUENTIAL,
)
};
}
hash_reader(algo, file)
}
#[inline]
fn hash_file_tiny(algo: HashAlgorithm, mut file: File, size: usize) -> io::Result<String> {
let mut buf = [0u8; 8192];
let mut total = 0;
while total < size {
match file.read(&mut buf[total..size]) {
Ok(0) => break,
Ok(n) => total += n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
hash_bytes(algo, &buf[..total])
}
#[inline]
fn hash_file_small(algo: HashAlgorithm, mut file: File, size: usize) -> io::Result<String> {
SMALL_FILE_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
buf.clear();
buf.reserve(size);
unsafe {
buf.set_len(size);
}
let mut total = 0;
while total < size {
match file.read(&mut buf[total..size]) {
Ok(0) => break,
Ok(n) => total += n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
hash_bytes(algo, &buf[..total])
})
}
pub fn hash_stdin(algo: HashAlgorithm) -> io::Result<String> {
let stdin = io::stdin();
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
let fd = stdin.as_raw_fd();
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(fd, &mut stat) } == 0
&& (stat.st_mode & libc::S_IFMT) == libc::S_IFREG
&& stat.st_size > 0
{
unsafe {
libc::posix_fadvise(fd, 0, stat.st_size, libc::POSIX_FADV_SEQUENTIAL);
}
}
}
hash_reader(algo, stdin.lock())
}
pub fn should_use_parallel(paths: &[&Path]) -> bool {
paths.len() >= 2
}
#[cfg(target_os = "linux")]
pub fn readahead_files(paths: &[&Path]) {
use std::os::unix::io::AsRawFd;
for path in paths {
if let Ok(file) = open_noatime(path) {
if let Ok(meta) = file.metadata() {
let len = meta.len();
if meta.file_type().is_file() && len >= FADVISE_MIN_SIZE {
unsafe {
libc::posix_fadvise(
file.as_raw_fd(),
0,
len as i64,
libc::POSIX_FADV_WILLNEED,
);
}
}
}
}
}
}
#[cfg(not(target_os = "linux"))]
pub fn readahead_files(_paths: &[&Path]) {
}
pub fn blake2b_hash_data(data: &[u8], output_bytes: usize) -> String {
let hash = blake2b_simd::Params::new()
.hash_length(output_bytes)
.hash(data);
hex_encode(hash.as_bytes())
}
pub fn blake2b_hash_reader<R: Read>(mut reader: R, output_bytes: usize) -> io::Result<String> {
STREAM_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
ensure_stream_buf(&mut buf);
let mut state = blake2b_simd::Params::new()
.hash_length(output_bytes)
.to_state();
loop {
let n = read_full(&mut reader, &mut buf)?;
if n == 0 {
break;
}
state.update(&buf[..n]);
}
Ok(hex_encode(state.finalize().as_bytes()))
})
}
pub fn blake2b_hash_file(path: &Path, output_bytes: usize) -> io::Result<String> {
let (file, file_size, is_regular) = open_and_stat(path)?;
if is_regular && file_size == 0 {
return Ok(blake2b_hash_data(&[], output_bytes));
}
if file_size > 0 && is_regular {
if file_size < TINY_FILE_LIMIT {
return blake2b_hash_file_tiny(file, file_size as usize, output_bytes);
}
if file_size >= SMALL_FILE_LIMIT {
#[cfg(target_os = "linux")]
{
return blake2b_hash_file_pipelined(file, file_size, output_bytes);
}
#[cfg(not(target_os = "linux"))]
{
let mmap_result = unsafe { memmap2::MmapOptions::new().map(&file) };
if let Ok(mmap) = mmap_result {
return Ok(blake2b_hash_data(&mmap, output_bytes));
}
}
}
if file_size < SMALL_FILE_LIMIT {
return blake2b_hash_file_small(file, file_size as usize, output_bytes);
}
}
#[cfg(target_os = "linux")]
if file_size >= FADVISE_MIN_SIZE {
use std::os::unix::io::AsRawFd;
let _ = unsafe {
libc::posix_fadvise(
file.as_raw_fd(),
0,
file_size as i64,
libc::POSIX_FADV_SEQUENTIAL,
)
};
}
blake2b_hash_reader(file, output_bytes)
}
#[inline]
fn blake2b_hash_file_tiny(mut file: File, size: usize, output_bytes: usize) -> io::Result<String> {
let mut buf = [0u8; 8192];
let mut total = 0;
while total < size {
match file.read(&mut buf[total..size]) {
Ok(0) => break,
Ok(n) => total += n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
Ok(blake2b_hash_data(&buf[..total], output_bytes))
}
#[inline]
fn blake2b_hash_file_small(mut file: File, size: usize, output_bytes: usize) -> io::Result<String> {
SMALL_FILE_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
buf.clear();
buf.reserve(size);
unsafe {
buf.set_len(size);
}
let mut total = 0;
while total < size {
match file.read(&mut buf[total..size]) {
Ok(0) => break,
Ok(n) => total += n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
Ok(blake2b_hash_data(&buf[..total], output_bytes))
})
}
#[cfg(target_os = "linux")]
fn blake2b_hash_file_pipelined(
file: File,
file_size: u64,
output_bytes: usize,
) -> io::Result<String> {
match unsafe { memmap2::MmapOptions::new().map(&file) } {
Ok(mmap) => {
if file_size >= 2 * 1024 * 1024 {
let _ = mmap.advise(memmap2::Advice::HugePage);
}
let _ = mmap.advise(memmap2::Advice::Sequential);
if file_size >= 4 * 1024 * 1024 {
if mmap.advise(memmap2::Advice::PopulateRead).is_err() {
let _ = mmap.advise(memmap2::Advice::WillNeed);
}
} else {
let _ = mmap.advise(memmap2::Advice::WillNeed);
}
Ok(blake2b_hash_data(&mmap, output_bytes))
}
Err(_) => {
blake2b_hash_file_streamed(file, file_size, output_bytes)
}
}
}
#[cfg(target_os = "linux")]
fn blake2b_hash_file_streamed(
mut file: File,
file_size: u64,
output_bytes: usize,
) -> io::Result<String> {
use std::os::unix::io::AsRawFd;
const PIPE_BUF_SIZE: usize = 8 * 1024 * 1024;
unsafe {
libc::posix_fadvise(
file.as_raw_fd(),
0,
file_size as i64,
libc::POSIX_FADV_SEQUENTIAL,
);
}
let (tx, rx) = std::sync::mpsc::sync_channel::<(Vec<u8>, usize)>(1);
let (buf_tx, buf_rx) = std::sync::mpsc::sync_channel::<Vec<u8>>(1);
let _ = buf_tx.send(vec![0u8; PIPE_BUF_SIZE]);
let reader_handle = std::thread::spawn(move || -> io::Result<()> {
while let Ok(mut buf) = buf_rx.recv() {
let mut total = 0;
while total < buf.len() {
match file.read(&mut buf[total..]) {
Ok(0) => break,
Ok(n) => total += n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
if total == 0 {
break;
}
if tx.send((buf, total)).is_err() {
break;
}
}
Ok(())
});
let mut state = blake2b_simd::Params::new()
.hash_length(output_bytes)
.to_state();
while let Ok((buf, n)) = rx.recv() {
state.update(&buf[..n]);
let _ = buf_tx.send(buf);
}
let hash_result = Ok(hex_encode(state.finalize().as_bytes()));
match reader_handle.join() {
Ok(Ok(())) => {}
Ok(Err(e)) => {
if hash_result.is_ok() {
return Err(e);
}
}
Err(payload) => {
let msg = if let Some(s) = payload.downcast_ref::<&str>() {
format!("reader thread panicked: {}", s)
} else if let Some(s) = payload.downcast_ref::<String>() {
format!("reader thread panicked: {}", s)
} else {
"reader thread panicked".to_string()
};
return Err(io::Error::other(msg));
}
}
hash_result
}
pub fn blake2b_hash_stdin(output_bytes: usize) -> io::Result<String> {
let stdin = io::stdin();
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
let fd = stdin.as_raw_fd();
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(fd, &mut stat) } == 0
&& (stat.st_mode & libc::S_IFMT) == libc::S_IFREG
&& stat.st_size > 0
{
unsafe {
libc::posix_fadvise(fd, 0, stat.st_size, libc::POSIX_FADV_SEQUENTIAL);
}
}
}
blake2b_hash_reader(stdin.lock(), output_bytes)
}
enum FileContent {
Mmap(memmap2::Mmap),
Buf(Vec<u8>),
}
impl AsRef<[u8]> for FileContent {
fn as_ref(&self) -> &[u8] {
match self {
FileContent::Mmap(m) => m,
FileContent::Buf(v) => v,
}
}
}
fn open_file_content(path: &Path) -> io::Result<FileContent> {
let (file, size, is_regular) = open_and_stat(path)?;
if is_regular && size == 0 {
return Ok(FileContent::Buf(Vec::new()));
}
if is_regular && size > 0 {
if size < TINY_FILE_LIMIT {
let mut buf = vec![0u8; size as usize];
let mut total = 0;
let mut f = file;
while total < size as usize {
match f.read(&mut buf[total..]) {
Ok(0) => break,
Ok(n) => total += n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
buf.truncate(total);
return Ok(FileContent::Buf(buf));
}
let mmap_result = unsafe { memmap2::MmapOptions::new().map(&file) };
if let Ok(mmap) = mmap_result {
#[cfg(target_os = "linux")]
{
if size >= 2 * 1024 * 1024 {
let _ = mmap.advise(memmap2::Advice::HugePage);
}
let _ = mmap.advise(memmap2::Advice::Sequential);
if mmap.advise(memmap2::Advice::PopulateRead).is_err() {
let _ = mmap.advise(memmap2::Advice::WillNeed);
}
}
return Ok(FileContent::Mmap(mmap));
}
let mut buf = vec![0u8; size as usize];
let mut total = 0;
let mut f = file;
while total < size as usize {
match f.read(&mut buf[total..]) {
Ok(0) => break,
Ok(n) => total += n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
buf.truncate(total);
return Ok(FileContent::Buf(buf));
}
let mut buf = Vec::new();
let mut f = file;
f.read_to_end(&mut buf)?;
Ok(FileContent::Buf(buf))
}
fn read_remaining_to_vec(prefix: &[u8], mut file: File) -> io::Result<FileContent> {
let mut buf = Vec::with_capacity(prefix.len() + 65536);
buf.extend_from_slice(prefix);
file.read_to_end(&mut buf)?;
Ok(FileContent::Buf(buf))
}
fn open_file_content_fast(path: &Path) -> io::Result<FileContent> {
let mut file = open_noatime(path)?;
let mut small_buf = [0u8; 4096];
match file.read(&mut small_buf) {
Ok(0) => return Ok(FileContent::Buf(Vec::new())),
Ok(n) if n < small_buf.len() => {
let mut vec = Vec::with_capacity(n);
vec.extend_from_slice(&small_buf[..n]);
return Ok(FileContent::Buf(vec));
}
Ok(n) => {
let mut buf = vec![0u8; 65536];
buf[..n].copy_from_slice(&small_buf[..n]);
let mut total = n;
loop {
match file.read(&mut buf[total..]) {
Ok(0) => {
buf.truncate(total);
return Ok(FileContent::Buf(buf));
}
Ok(n) => {
total += n;
if total >= buf.len() {
return read_remaining_to_vec(&buf[..total], file);
}
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {
let mut buf = vec![0u8; 65536];
let mut total = 0;
loop {
match file.read(&mut buf[total..]) {
Ok(0) => {
buf.truncate(total);
return Ok(FileContent::Buf(buf));
}
Ok(n) => {
total += n;
if total >= buf.len() {
return read_remaining_to_vec(&buf[..total], file);
}
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
}
Err(e) => return Err(e),
}
}
pub fn blake2b_hash_files_many(paths: &[&Path], output_bytes: usize) -> Vec<io::Result<String>> {
use blake2b_simd::many::{HashManyJob, hash_many};
let use_fast = paths.len() >= 20;
let file_data: Vec<io::Result<FileContent>> = if paths.len() <= 10 {
paths.iter().map(|&path| open_file_content(path)).collect()
} else {
let num_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
.min(paths.len());
let chunk_size = (paths.len() + num_threads - 1) / num_threads;
std::thread::scope(|s| {
let handles: Vec<_> = paths
.chunks(chunk_size)
.map(|chunk| {
s.spawn(move || {
chunk
.iter()
.map(|&path| {
if use_fast {
open_file_content_fast(path)
} else {
open_file_content(path)
}
})
.collect::<Vec<_>>()
})
})
.collect();
handles
.into_iter()
.flat_map(|h| h.join().unwrap())
.collect()
})
};
let hash_results = {
let mut params = blake2b_simd::Params::new();
params.hash_length(output_bytes);
let ok_entries: Vec<(usize, &[u8])> = file_data
.iter()
.enumerate()
.filter_map(|(i, r)| r.as_ref().ok().map(|c| (i, c.as_ref())))
.collect();
let mut jobs: Vec<HashManyJob> = ok_entries
.iter()
.map(|(_, data)| HashManyJob::new(¶ms, data))
.collect();
hash_many(jobs.iter_mut());
let mut hm: Vec<Option<String>> = vec![None; paths.len()];
for (j, &(orig_i, _)) in ok_entries.iter().enumerate() {
hm[orig_i] = Some(hex_encode(jobs[j].to_hash().as_bytes()));
}
hm
};
hash_results
.into_iter()
.zip(file_data)
.map(|(hash_opt, result)| match result {
Ok(_) => Ok(hash_opt.unwrap()),
Err(e) => Err(e),
})
.collect()
}
pub fn blake2b_hash_files_parallel(
paths: &[&Path],
output_bytes: usize,
) -> Vec<io::Result<String>> {
let n = paths.len();
let sample_count = n.min(5);
let mut sample_max: u64 = 0;
let mut sample_total: u64 = 0;
for &p in paths.iter().take(sample_count) {
let size = std::fs::metadata(p).map(|m| m.len()).unwrap_or(0);
sample_total += size;
sample_max = sample_max.max(size);
}
let estimated_total = if sample_count > 0 {
sample_total * (n as u64) / (sample_count as u64)
} else {
0
};
if estimated_total < 1024 * 1024 && sample_max < SMALL_FILE_LIMIT {
return blake2b_hash_files_many(paths, output_bytes);
}
let mut indexed: Vec<(usize, &Path, u64)> = paths
.iter()
.enumerate()
.map(|(i, &p)| {
let size = std::fs::metadata(p).map(|m| m.len()).unwrap_or(0);
(i, p, size)
})
.collect();
indexed.sort_by(|a, b| b.2.cmp(&a.2));
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
for &(_, path, size) in indexed.iter().take(20) {
if size >= 1024 * 1024 {
if let Ok(file) = open_noatime(path) {
unsafe {
libc::readahead(file.as_raw_fd(), 0, size as usize);
}
}
}
}
}
let num_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
.min(n);
let work_idx = AtomicUsize::new(0);
std::thread::scope(|s| {
let work_idx = &work_idx;
let indexed = &indexed;
let handles: Vec<_> = (0..num_threads)
.map(|_| {
s.spawn(move || {
let mut local_results = Vec::new();
loop {
let idx = work_idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if idx >= indexed.len() {
break;
}
let (orig_idx, path, _size) = indexed[idx];
let result = blake2b_hash_file(path, output_bytes);
local_results.push((orig_idx, result));
}
local_results
})
})
.collect();
let mut results: Vec<Option<io::Result<String>>> = (0..n).map(|_| None).collect();
for handle in handles {
for (orig_idx, result) in handle.join().unwrap() {
results[orig_idx] = Some(result);
}
}
results
.into_iter()
.map(|opt| opt.unwrap_or_else(|| Err(io::Error::other("missing result"))))
.collect()
})
}
pub fn hash_files_auto(paths: &[&Path], algo: HashAlgorithm) -> Vec<io::Result<String>> {
let n = paths.len();
if n == 0 {
return Vec::new();
}
if n == 1 {
return vec![hash_file_nostat(algo, paths[0])];
}
let sample_size = paths
.iter()
.take(3)
.filter_map(|p| std::fs::metadata(p).ok())
.map(|m| m.len())
.max()
.unwrap_or(0);
if sample_size < 65536 {
#[cfg(target_os = "linux")]
{
let mut c_path_buf = Vec::with_capacity(256);
paths
.iter()
.map(|&p| hash_file_raw_nostat(algo, p, &mut c_path_buf))
.collect()
}
#[cfg(not(target_os = "linux"))]
{
paths.iter().map(|&p| hash_file_nostat(algo, p)).collect()
}
} else if n >= 20 {
hash_files_batch(paths, algo)
} else {
hash_files_parallel_fast(paths, algo)
}
}
pub fn hash_files_parallel(paths: &[&Path], algo: HashAlgorithm) -> Vec<io::Result<String>> {
let n = paths.len();
let mut indexed: Vec<(usize, &Path, u64)> = paths
.iter()
.enumerate()
.map(|(i, &p)| {
let size = std::fs::metadata(p).map(|m| m.len()).unwrap_or(0);
(i, p, size)
})
.collect();
indexed.sort_by(|a, b| b.2.cmp(&a.2));
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
for &(_, path, size) in indexed.iter().take(20) {
if size >= 1024 * 1024 {
if let Ok(file) = open_noatime(path) {
unsafe {
libc::readahead(file.as_raw_fd(), 0, size as usize);
}
}
}
}
}
let num_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
.min(n);
let work_idx = AtomicUsize::new(0);
std::thread::scope(|s| {
let work_idx = &work_idx;
let indexed = &indexed;
let handles: Vec<_> = (0..num_threads)
.map(|_| {
s.spawn(move || {
let mut local_results = Vec::new();
loop {
let idx = work_idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if idx >= indexed.len() {
break;
}
let (orig_idx, path, _size) = indexed[idx];
let result = hash_file(algo, path);
local_results.push((orig_idx, result));
}
local_results
})
})
.collect();
let mut results: Vec<Option<io::Result<String>>> = (0..n).map(|_| None).collect();
for handle in handles {
for (orig_idx, result) in handle.join().unwrap() {
results[orig_idx] = Some(result);
}
}
results
.into_iter()
.map(|opt| opt.unwrap_or_else(|| Err(io::Error::other("missing result"))))
.collect()
})
}
pub fn hash_files_parallel_fast(paths: &[&Path], algo: HashAlgorithm) -> Vec<io::Result<String>> {
let n = paths.len();
if n == 0 {
return Vec::new();
}
if n == 1 {
return vec![hash_file_nostat(algo, paths[0])];
}
#[cfg(target_os = "linux")]
readahead_files_all(paths);
let num_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
.min(n);
let work_idx = AtomicUsize::new(0);
std::thread::scope(|s| {
let work_idx = &work_idx;
let handles: Vec<_> = (0..num_threads)
.map(|_| {
s.spawn(move || {
let mut local_results = Vec::new();
loop {
let idx = work_idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if idx >= n {
break;
}
let result = hash_file_nostat(algo, paths[idx]);
local_results.push((idx, result));
}
local_results
})
})
.collect();
let mut results: Vec<Option<io::Result<String>>> = (0..n).map(|_| None).collect();
for handle in handles {
for (idx, result) in handle.join().unwrap() {
results[idx] = Some(result);
}
}
results
.into_iter()
.map(|opt| opt.unwrap_or_else(|| Err(io::Error::other("missing result"))))
.collect()
})
}
pub fn hash_files_batch(paths: &[&Path], algo: HashAlgorithm) -> Vec<io::Result<String>> {
let n = paths.len();
if n == 0 {
return Vec::new();
}
#[cfg(target_os = "linux")]
readahead_files_all(paths);
let use_fast = n >= 20;
let file_data: Vec<io::Result<FileContent>> = if n <= 10 {
paths
.iter()
.map(|&path| {
if use_fast {
open_file_content_fast(path)
} else {
open_file_content(path)
}
})
.collect()
} else {
let num_threads = std::thread::available_parallelism()
.map(|t| t.get())
.unwrap_or(4)
.min(n);
let chunk_size = (n + num_threads - 1) / num_threads;
std::thread::scope(|s| {
let handles: Vec<_> = paths
.chunks(chunk_size)
.map(|chunk| {
s.spawn(move || {
chunk
.iter()
.map(|&path| {
if use_fast {
open_file_content_fast(path)
} else {
open_file_content(path)
}
})
.collect::<Vec<_>>()
})
})
.collect();
handles
.into_iter()
.flat_map(|h| h.join().unwrap())
.collect()
})
};
let num_hash_threads = std::thread::available_parallelism()
.map(|t| t.get())
.unwrap_or(4)
.min(n);
let work_idx = AtomicUsize::new(0);
std::thread::scope(|s| {
let work_idx = &work_idx;
let file_data = &file_data;
let handles: Vec<_> = (0..num_hash_threads)
.map(|_| {
s.spawn(move || {
let mut local_results = Vec::new();
loop {
let idx = work_idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if idx >= n {
break;
}
let result = match &file_data[idx] {
Ok(content) => hash_bytes(algo, content.as_ref()),
Err(e) => Err(io::Error::new(e.kind(), e.to_string())),
};
local_results.push((idx, result));
}
local_results
})
})
.collect();
let mut results: Vec<Option<io::Result<String>>> = (0..n).map(|_| None).collect();
for handle in handles {
for (idx, result) in handle.join().unwrap() {
results[idx] = Some(result);
}
}
results
.into_iter()
.map(|opt| opt.unwrap_or_else(|| Err(io::Error::other("missing result"))))
.collect()
})
}
fn hash_stream_with_prefix(
algo: HashAlgorithm,
prefix: &[u8],
mut file: File,
) -> io::Result<String> {
if matches!(algo, HashAlgorithm::Blake2b) {
let mut state = blake2b_simd::Params::new().to_state();
state.update(prefix);
return STREAM_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
ensure_stream_buf(&mut buf);
loop {
let n = read_full(&mut file, &mut buf)?;
if n == 0 {
break;
}
state.update(&buf[..n]);
}
Ok(hex_encode(state.finalize().as_bytes()))
});
}
#[cfg(target_os = "linux")]
{
let evp_algo = match algo {
HashAlgorithm::Md5 => Some(openssl_evp::EvpAlgorithm::Md5),
HashAlgorithm::Sha1 => Some(openssl_evp::EvpAlgorithm::Sha1),
HashAlgorithm::Sha224 => Some(openssl_evp::EvpAlgorithm::Sha224),
HashAlgorithm::Sha256 => Some(openssl_evp::EvpAlgorithm::Sha256),
HashAlgorithm::Sha384 => Some(openssl_evp::EvpAlgorithm::Sha384),
HashAlgorithm::Sha512 => Some(openssl_evp::EvpAlgorithm::Sha512),
HashAlgorithm::Blake2b => None,
};
if let Some(evp) = evp_algo.filter(|_| openssl_evp::is_available()) {
let digest = openssl_evp::hash_reader_with_prefix(evp, prefix, file)?;
return Ok(hex_encode(&digest));
}
}
match algo {
HashAlgorithm::Sha224 => hash_stream_with_prefix_digest::<sha2::Sha224>(prefix, file),
HashAlgorithm::Sha256 => hash_stream_with_prefix_digest::<sha2::Sha256>(prefix, file),
HashAlgorithm::Md5 => hash_stream_with_prefix_digest::<md5::Md5>(prefix, file),
#[cfg(not(target_vendor = "apple"))]
HashAlgorithm::Sha1 => {
hash_stream_with_prefix_ring(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY, prefix, file)
}
#[cfg(target_vendor = "apple")]
HashAlgorithm::Sha1 => hash_stream_with_prefix_digest::<sha1::Sha1>(prefix, file),
#[cfg(not(target_vendor = "apple"))]
HashAlgorithm::Sha384 => hash_stream_with_prefix_ring(&ring::digest::SHA384, prefix, file),
#[cfg(target_vendor = "apple")]
HashAlgorithm::Sha384 => hash_stream_with_prefix_digest::<sha2::Sha384>(prefix, file),
#[cfg(not(target_vendor = "apple"))]
HashAlgorithm::Sha512 => hash_stream_with_prefix_ring(&ring::digest::SHA512, prefix, file),
#[cfg(target_vendor = "apple")]
HashAlgorithm::Sha512 => hash_stream_with_prefix_digest::<sha2::Sha512>(prefix, file),
HashAlgorithm::Blake2b => unreachable!(),
}
}
fn hash_stream_with_prefix_digest<D: digest::Digest>(
prefix: &[u8],
mut file: File,
) -> io::Result<String> {
STREAM_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
ensure_stream_buf(&mut buf);
let mut hasher = D::new();
hasher.update(prefix);
loop {
let n = read_full(&mut file, &mut buf)?;
if n == 0 {
break;
}
hasher.update(&buf[..n]);
}
Ok(hex_encode(&hasher.finalize()))
})
}
#[cfg(not(target_vendor = "apple"))]
fn hash_stream_with_prefix_ring(
algo: &'static ring::digest::Algorithm,
prefix: &[u8],
mut file: File,
) -> io::Result<String> {
STREAM_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
ensure_stream_buf(&mut buf);
let mut ctx = ring::digest::Context::new(algo);
ctx.update(prefix);
loop {
let n = read_full(&mut file, &mut buf)?;
if n == 0 {
break;
}
ctx.update(&buf[..n]);
}
Ok(hex_encode(ctx.finish().as_ref()))
})
}
pub fn hash_file_nostat(algo: HashAlgorithm, path: &Path) -> io::Result<String> {
let mut file = open_noatime(path)?;
let mut small_buf = [0u8; 4096];
match file.read(&mut small_buf) {
Ok(0) => return hash_bytes(algo, &[]),
Ok(n) if n < small_buf.len() => {
return hash_bytes(algo, &small_buf[..n]);
}
Ok(n) => {
let mut buf = [0u8; 65536];
buf[..n].copy_from_slice(&small_buf[..n]);
let mut total = n;
loop {
match file.read(&mut buf[total..]) {
Ok(0) => return hash_bytes(algo, &buf[..total]),
Ok(n) => {
total += n;
if total >= buf.len() {
return hash_stream_with_prefix(algo, &buf[..total], file);
}
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {
let mut buf = [0u8; 65536];
let mut total = 0;
loop {
match file.read(&mut buf[total..]) {
Ok(0) => return hash_bytes(algo, &buf[..total]),
Ok(n) => {
total += n;
if total >= buf.len() {
return hash_stream_with_prefix(algo, &buf[..total], file);
}
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
}
Err(e) => return Err(e),
}
}
#[cfg(target_os = "linux")]
fn hash_file_raw_nostat(
algo: HashAlgorithm,
path: &Path,
c_path_buf: &mut Vec<u8>,
) -> io::Result<String> {
use std::os::unix::ffi::OsStrExt;
let path_bytes = path.as_os_str().as_bytes();
c_path_buf.clear();
c_path_buf.reserve(path_bytes.len() + 1);
c_path_buf.extend_from_slice(path_bytes);
c_path_buf.push(0);
let mut flags = libc::O_RDONLY | libc::O_CLOEXEC;
if NOATIME_SUPPORTED.load(Ordering::Relaxed) {
flags |= libc::O_NOATIME;
}
let fd = unsafe { libc::open(c_path_buf.as_ptr() as *const libc::c_char, flags) };
if fd < 0 {
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EPERM) && flags & libc::O_NOATIME != 0 {
NOATIME_SUPPORTED.store(false, Ordering::Relaxed);
let fd2 = unsafe {
libc::open(
c_path_buf.as_ptr() as *const libc::c_char,
libc::O_RDONLY | libc::O_CLOEXEC,
)
};
if fd2 < 0 {
return Err(io::Error::last_os_error());
}
return hash_fd_small(algo, fd2);
}
return Err(err);
}
hash_fd_small(algo, fd)
}
#[cfg(target_os = "linux")]
#[inline]
fn hash_fd_small(algo: HashAlgorithm, fd: i32) -> io::Result<String> {
let mut buf = [0u8; 4096];
let n = loop {
let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
if ret >= 0 {
break ret;
}
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
unsafe {
libc::close(fd);
}
return Err(err);
};
let n = n as usize;
if n < buf.len() {
unsafe {
libc::close(fd);
}
return hash_bytes(algo, &buf[..n]);
}
use std::os::unix::io::FromRawFd;
let mut file = unsafe { File::from_raw_fd(fd) };
let mut big_buf = [0u8; 65536];
big_buf[..n].copy_from_slice(&buf[..n]);
let mut total = n;
loop {
match std::io::Read::read(&mut file, &mut big_buf[total..]) {
Ok(0) => return hash_bytes(algo, &big_buf[..total]),
Ok(n) => {
total += n;
if total >= big_buf.len() {
return hash_stream_with_prefix(algo, &big_buf[..total], file);
}
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
}
#[cfg(target_os = "linux")]
pub fn hash_file_raw(algo: HashAlgorithm, path: &Path) -> io::Result<String> {
use std::os::unix::ffi::OsStrExt;
let path_bytes = path.as_os_str().as_bytes();
let c_path = std::ffi::CString::new(path_bytes)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contains null byte"))?;
let mut flags = libc::O_RDONLY | libc::O_CLOEXEC;
if NOATIME_SUPPORTED.load(Ordering::Relaxed) {
flags |= libc::O_NOATIME;
}
let fd = unsafe { libc::open(c_path.as_ptr(), flags) };
if fd < 0 {
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EPERM) && flags & libc::O_NOATIME != 0 {
NOATIME_SUPPORTED.store(false, Ordering::Relaxed);
let fd2 = unsafe { libc::open(c_path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
if fd2 < 0 {
return Err(io::Error::last_os_error());
}
return hash_from_raw_fd(algo, fd2);
}
return Err(err);
}
hash_from_raw_fd(algo, fd)
}
#[cfg(target_os = "linux")]
fn hash_from_raw_fd(algo: HashAlgorithm, fd: i32) -> io::Result<String> {
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(fd, &mut stat) } != 0 {
let err = io::Error::last_os_error();
unsafe {
libc::close(fd);
}
return Err(err);
}
let size = stat.st_size as u64;
let is_regular = (stat.st_mode & libc::S_IFMT) == libc::S_IFREG;
if is_regular && size == 0 {
unsafe {
libc::close(fd);
}
return hash_bytes(algo, &[]);
}
if is_regular && size < TINY_FILE_LIMIT {
let mut buf = [0u8; 8192];
let mut total = 0usize;
while total < size as usize {
let n = unsafe {
libc::read(
fd,
buf[total..].as_mut_ptr() as *mut libc::c_void,
(size as usize) - total,
)
};
if n < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
unsafe {
libc::close(fd);
}
return Err(err);
}
if n == 0 {
break;
}
total += n as usize;
}
unsafe {
libc::close(fd);
}
return hash_bytes(algo, &buf[..total]);
}
use std::os::unix::io::FromRawFd;
let file = unsafe { File::from_raw_fd(fd) };
if is_regular && size > 0 {
return hash_regular_file(algo, file, size);
}
hash_reader(algo, file)
}
#[cfg(target_os = "linux")]
pub fn readahead_files_all(paths: &[&Path]) {
use std::os::unix::io::AsRawFd;
for path in paths {
if let Ok(file) = open_noatime(path) {
if let Ok(meta) = file.metadata() {
if meta.file_type().is_file() {
let len = meta.len();
unsafe {
libc::posix_fadvise(
file.as_raw_fd(),
0,
len as i64,
libc::POSIX_FADV_WILLNEED,
);
}
}
}
}
}
}
#[cfg(not(target_os = "linux"))]
pub fn readahead_files_all(_paths: &[&Path]) {}
pub fn print_hash(
out: &mut impl Write,
hash: &str,
filename: &str,
binary: bool,
) -> io::Result<()> {
let mode = if binary { b'*' } else { b' ' };
out.write_all(hash.as_bytes())?;
out.write_all(&[b' ', mode])?;
out.write_all(filename.as_bytes())?;
out.write_all(b"\n")
}
pub fn print_hash_zero(
out: &mut impl Write,
hash: &str,
filename: &str,
binary: bool,
) -> io::Result<()> {
let mode = if binary { b'*' } else { b' ' };
out.write_all(hash.as_bytes())?;
out.write_all(&[b' ', mode])?;
out.write_all(filename.as_bytes())?;
out.write_all(b"\0")
}
thread_local! {
static LINE_BUF: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(256));
}
#[inline]
pub fn write_hash_line(
out: &mut impl Write,
hash: &str,
filename: &str,
binary: bool,
zero: bool,
escaped: bool,
) -> io::Result<()> {
LINE_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
buf.clear();
let mode = if binary { b'*' } else { b' ' };
let term = if zero { b'\0' } else { b'\n' };
if escaped {
buf.push(b'\\');
}
buf.extend_from_slice(hash.as_bytes());
buf.push(b' ');
buf.push(mode);
buf.extend_from_slice(filename.as_bytes());
buf.push(term);
out.write_all(&buf)
})
}
#[inline]
pub fn write_hash_tag_line(
out: &mut impl Write,
algo_name: &str,
hash: &str,
filename: &str,
zero: bool,
) -> io::Result<()> {
LINE_BUF.with(|cell| {
let mut buf = cell.borrow_mut();
buf.clear();
let term = if zero { b'\0' } else { b'\n' };
buf.extend_from_slice(algo_name.as_bytes());
buf.extend_from_slice(b" (");
buf.extend_from_slice(filename.as_bytes());
buf.extend_from_slice(b") = ");
buf.extend_from_slice(hash.as_bytes());
buf.push(term);
out.write_all(&buf)
})
}
pub fn print_hash_tag(
out: &mut impl Write,
algo: HashAlgorithm,
hash: &str,
filename: &str,
) -> io::Result<()> {
out.write_all(algo.name().as_bytes())?;
out.write_all(b" (")?;
out.write_all(filename.as_bytes())?;
out.write_all(b") = ")?;
out.write_all(hash.as_bytes())?;
out.write_all(b"\n")
}
pub fn print_hash_tag_zero(
out: &mut impl Write,
algo: HashAlgorithm,
hash: &str,
filename: &str,
) -> io::Result<()> {
out.write_all(algo.name().as_bytes())?;
out.write_all(b" (")?;
out.write_all(filename.as_bytes())?;
out.write_all(b") = ")?;
out.write_all(hash.as_bytes())?;
out.write_all(b"\0")
}
pub fn print_hash_tag_b2sum(
out: &mut impl Write,
hash: &str,
filename: &str,
bits: usize,
) -> io::Result<()> {
if bits == 512 {
out.write_all(b"BLAKE2b (")?;
} else {
write!(out, "BLAKE2b-{} (", bits)?;
}
out.write_all(filename.as_bytes())?;
out.write_all(b") = ")?;
out.write_all(hash.as_bytes())?;
out.write_all(b"\n")
}
pub fn print_hash_tag_b2sum_zero(
out: &mut impl Write,
hash: &str,
filename: &str,
bits: usize,
) -> io::Result<()> {
if bits == 512 {
out.write_all(b"BLAKE2b (")?;
} else {
write!(out, "BLAKE2b-{} (", bits)?;
}
out.write_all(filename.as_bytes())?;
out.write_all(b") = ")?;
out.write_all(hash.as_bytes())?;
out.write_all(b"\0")
}
pub struct CheckOptions {
pub quiet: bool,
pub status_only: bool,
pub strict: bool,
pub warn: bool,
pub ignore_missing: bool,
pub warn_prefix: String,
}
pub struct CheckResult {
pub ok: usize,
pub mismatches: usize,
pub format_errors: usize,
pub read_errors: usize,
pub ignored_missing: usize,
}
pub fn check_file<R: BufRead>(
algo: HashAlgorithm,
reader: R,
opts: &CheckOptions,
out: &mut impl Write,
err_out: &mut impl Write,
) -> io::Result<CheckResult> {
let quiet = opts.quiet;
let status_only = opts.status_only;
let warn = opts.warn;
let ignore_missing = opts.ignore_missing;
let mut ok_count = 0;
let mut mismatch_count = 0;
let mut format_errors = 0;
let mut read_errors = 0;
let mut ignored_missing_count = 0;
let mut line_num = 0;
for line_result in reader.lines() {
line_num += 1;
let line = line_result?;
let line = line.trim_end();
if line.is_empty() {
continue;
}
let (expected_hash, filename) = match parse_check_line(line) {
Some(v) => v,
None => {
format_errors += 1;
if warn {
out.flush()?;
if opts.warn_prefix.is_empty() {
writeln!(
err_out,
"line {}: improperly formatted {} checksum line",
line_num,
algo.name()
)?;
} else {
writeln!(
err_out,
"{}: {}: improperly formatted {} checksum line",
opts.warn_prefix,
line_num,
algo.name()
)?;
}
}
continue;
}
};
let actual = match hash_file(algo, Path::new(filename)) {
Ok(h) => h,
Err(e) => {
if ignore_missing && e.kind() == io::ErrorKind::NotFound {
ignored_missing_count += 1;
continue;
}
read_errors += 1;
if !status_only {
out.flush()?;
writeln!(err_out, "{}: {}", filename, e)?;
writeln!(out, "{}: FAILED open or read", filename)?;
}
continue;
}
};
if actual.eq_ignore_ascii_case(expected_hash) {
ok_count += 1;
if !quiet && !status_only {
writeln!(out, "{}: OK", filename)?;
}
} else {
mismatch_count += 1;
if !status_only {
writeln!(out, "{}: FAILED", filename)?;
}
}
}
Ok(CheckResult {
ok: ok_count,
mismatches: mismatch_count,
format_errors,
read_errors,
ignored_missing: ignored_missing_count,
})
}
pub fn parse_check_line(line: &str) -> Option<(&str, &str)> {
let rest = line
.strip_prefix("MD5 (")
.or_else(|| line.strip_prefix("SHA1 ("))
.or_else(|| line.strip_prefix("SHA224 ("))
.or_else(|| line.strip_prefix("SHA256 ("))
.or_else(|| line.strip_prefix("SHA384 ("))
.or_else(|| line.strip_prefix("SHA512 ("))
.or_else(|| line.strip_prefix("BLAKE2b ("))
.or_else(|| {
if line.starts_with("BLAKE2b-") {
let after = &line["BLAKE2b-".len()..];
if let Some(sp) = after.find(" (") {
if after[..sp].bytes().all(|b| b.is_ascii_digit()) {
return Some(&after[sp + 2..]);
}
}
}
None
});
if let Some(rest) = rest {
if let Some(paren_idx) = rest.find(") = ") {
let filename = &rest[..paren_idx];
let hash = &rest[paren_idx + 4..];
return Some((hash, filename));
}
}
let line = line.strip_prefix('\\').unwrap_or(line);
if let Some(idx) = line.find(" ") {
let hash = &line[..idx];
let rest = &line[idx + 2..];
return Some((hash, rest));
}
if let Some(idx) = line.find(" *") {
let hash = &line[..idx];
let rest = &line[idx + 2..];
return Some((hash, rest));
}
None
}
pub fn parse_check_line_tag(line: &str) -> Option<(&str, &str, Option<usize>)> {
let paren_start = line.find(" (")?;
let algo_part = &line[..paren_start];
let rest = &line[paren_start + 2..];
let paren_end = rest.find(") = ")?;
let filename = &rest[..paren_end];
let hash = &rest[paren_end + 4..];
let bits = if let Some(dash_pos) = algo_part.rfind('-') {
algo_part[dash_pos + 1..].parse::<usize>().ok()
} else {
None
};
Some((hash, filename, bits))
}
#[inline]
fn read_full(reader: &mut impl Read, buf: &mut [u8]) -> io::Result<usize> {
let n = reader.read(buf)?;
if n == buf.len() || n == 0 {
return Ok(n);
}
let mut total = n;
while total < buf.len() {
match reader.read(&mut buf[total..]) {
Ok(0) => break,
Ok(n) => total += n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
Ok(total)
}
const fn generate_hex_table() -> [[u8; 2]; 256] {
let hex = b"0123456789abcdef";
let mut table = [[0u8; 2]; 256];
let mut i = 0;
while i < 256 {
table[i] = [hex[i >> 4], hex[i & 0xf]];
i += 1;
}
table
}
const HEX_TABLE: [[u8; 2]; 256] = generate_hex_table();
pub(crate) fn hex_encode(bytes: &[u8]) -> String {
let len = bytes.len() * 2;
let mut hex = String::with_capacity(len);
unsafe {
let buf = hex.as_mut_vec();
buf.set_len(len);
hex_encode_to_slice(bytes, buf);
}
hex
}
#[inline]
fn hex_encode_to_slice(bytes: &[u8], out: &mut [u8]) {
unsafe {
let ptr = out.as_mut_ptr();
for (i, &b) in bytes.iter().enumerate() {
let pair = *HEX_TABLE.get_unchecked(b as usize);
*ptr.add(i * 2) = pair[0];
*ptr.add(i * 2 + 1) = pair[1];
}
}
}