use crate::Result;
use crate::block::BlockDevice;
use super::attribute::{
TYPE_BITMAP, TYPE_DATA, TYPE_FILE_NAME, TYPE_INDEX_ROOT, TYPE_STANDARD_INFORMATION,
TYPE_VOLUME_NAME,
};
use super::boot::NTFS_OEM;
use super::mft;
use super::upcase_gen::build_upcase_blob;
pub const REC_MFT: u64 = 0;
pub const REC_MFTMIRR: u64 = 1;
pub const REC_LOGFILE: u64 = 2;
pub const REC_VOLUME: u64 = 3;
pub const REC_ATTRDEF: u64 = 4;
pub const REC_ROOT: u64 = 5;
pub const REC_BITMAP: u64 = 6;
pub const REC_BOOT: u64 = 7;
pub const REC_BADCLUS: u64 = 8;
pub const REC_SECURE: u64 = 9;
pub const REC_UPCASE: u64 = 10;
pub const REC_EXTEND: u64 = 11;
pub const FIRST_USER_RECORD: u64 = 16;
pub const DEFAULT_MFT_RECORD_SIZE: u32 = 1024;
pub const DEFAULT_INDEX_RECORD_SIZE: u32 = 4096;
pub const DEFAULT_CLUSTER_SIZE: u32 = 4096;
pub const INITIAL_MFT_CLUSTERS: u64 = 16;
pub const LOGFILE_BYTES: u64 = 64 * 1024;
#[derive(Debug, Clone)]
pub struct FormatOpts {
pub bytes_per_sector: u16,
pub sectors_per_cluster: u8,
pub volume_label: String,
pub volume_serial: u64,
}
impl Default for FormatOpts {
fn default() -> Self {
Self {
bytes_per_sector: 512,
sectors_per_cluster: 8,
volume_label: String::new(),
volume_serial: random_serial(),
}
}
}
fn random_serial() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0xDEAD_BEEF_CAFE_BABE);
let mix = &now as *const _ as usize as u64;
now ^ mix.rotate_left(11)
}
#[derive(Debug, Clone)]
pub struct BitmapAlloc {
pub bytes: Vec<u8>,
pub total: u64,
pub next_hint: u64,
}
impl BitmapAlloc {
pub fn new(total_clusters: u64) -> Self {
let bytes = vec![0u8; total_clusters.div_ceil(8) as usize];
Self {
bytes,
total: total_clusters,
next_hint: 0,
}
}
pub fn is_set(&self, cluster: u64) -> bool {
if cluster >= self.total {
return true;
}
let i = (cluster / 8) as usize;
let m = 1u8 << ((cluster % 8) as u8);
self.bytes[i] & m != 0
}
pub fn set(&mut self, cluster: u64) {
if cluster < self.total {
let i = (cluster / 8) as usize;
let m = 1u8 << ((cluster % 8) as u8);
self.bytes[i] |= m;
}
}
pub fn clear(&mut self, cluster: u64) {
if cluster < self.total {
let i = (cluster / 8) as usize;
let m = 1u8 << ((cluster % 8) as u8);
self.bytes[i] &= !m;
}
}
pub fn set_range(&mut self, start: u64, length: u64) {
for c in start..start.saturating_add(length).min(self.total) {
self.set(c);
}
}
pub fn allocate(&mut self, count: u64) -> Result<u64> {
if count == 0 {
return Ok(self.next_hint);
}
let starts = [self.next_hint, 0];
for &start in &starts {
let mut run_start: Option<u64> = None;
let mut run_len = 0u64;
for c in start..self.total {
if !self.is_set(c) {
if run_start.is_none() {
run_start = Some(c);
run_len = 0;
}
run_len += 1;
if run_len >= count {
let s = run_start.unwrap();
self.set_range(s, count);
self.next_hint = s + count;
return Ok(s);
}
} else {
run_start = None;
run_len = 0;
}
}
}
Err(crate::Error::InvalidImage(
"ntfs: out of free clusters during allocation".into(),
))
}
}
pub fn build_boot_sector(
opts: &FormatOpts,
total_sectors: u64,
mft_lcn: u64,
mftmirr_lcn: u64,
mft_record_field: i8,
index_record_field: i8,
) -> Vec<u8> {
let mut b = vec![0u8; opts.bytes_per_sector as usize];
b[0..3].copy_from_slice(&[0xEB, 0x52, 0x90]);
b[3..11].copy_from_slice(NTFS_OEM);
b[11..13].copy_from_slice(&opts.bytes_per_sector.to_le_bytes());
b[13] = opts.sectors_per_cluster;
b[14..16].copy_from_slice(&0u16.to_le_bytes());
b[21] = 0xF8;
b[24..26].copy_from_slice(&63u16.to_le_bytes());
b[26..28].copy_from_slice(&255u16.to_le_bytes());
let bpb_total = total_sectors.saturating_sub(1);
b[0x28..0x30].copy_from_slice(&bpb_total.to_le_bytes());
b[0x30..0x38].copy_from_slice(&mft_lcn.to_le_bytes());
b[0x38..0x40].copy_from_slice(&mftmirr_lcn.to_le_bytes());
b[0x40] = mft_record_field as u8;
b[0x44] = index_record_field as u8;
b[0x48..0x50].copy_from_slice(&opts.volume_serial.to_le_bytes());
b[510] = 0x55;
b[511] = 0xAA;
b
}
pub fn build_attrdef_payload() -> Vec<u8> {
#[allow(clippy::type_complexity)]
let entries: &[(&str, u32, u32, u32, u32, u64, u64)] = &[
("$STANDARD_INFORMATION", 0x10, 0, 0, 0x40, 48, 72),
("$ATTRIBUTE_LIST", 0x20, 0, 0, 0x40, 0, u64::MAX),
("$FILE_NAME", 0x30, 0, 2, 0x42, 68, 578),
("$OBJECT_ID", 0x40, 0, 0, 0x40, 0, 256),
("$SECURITY_DESCRIPTOR", 0x50, 0, 0, 0x40, 0, u64::MAX),
("$VOLUME_NAME", 0x60, 0, 0, 0x40, 2, 256),
("$VOLUME_INFORMATION", 0x70, 0, 0, 0x40, 12, 12),
("$DATA", 0x80, 0, 0, 0, 0, u64::MAX),
("$INDEX_ROOT", 0x90, 0, 0, 0x40, 0, u64::MAX),
("$INDEX_ALLOCATION", 0xA0, 0, 0, 0, 0, u64::MAX),
("$BITMAP", 0xB0, 0, 0, 0, 0, u64::MAX),
("$REPARSE_POINT", 0xC0, 0, 0, 0x40, 0, 16384),
("$EA_INFORMATION", 0xD0, 0, 0, 0x40, 8, 8),
("$EA", 0xE0, 0, 0, 0, 0, 65536),
("$LOGGED_UTILITY_STREAM", 0x100, 0, 0, 0x40, 0, 65536),
];
let mut out = Vec::with_capacity(entries.len() * 160);
for (name, type_code, dr, collation, flags, min_size, max_size) in entries {
let mut rec = vec![0u8; 160];
let mut idx = 0usize;
for u in name.encode_utf16() {
if idx + 2 > 128 {
break;
}
rec[idx..idx + 2].copy_from_slice(&u.to_le_bytes());
idx += 2;
}
rec[128..132].copy_from_slice(&type_code.to_le_bytes());
rec[132..136].copy_from_slice(&dr.to_le_bytes());
rec[136..140].copy_from_slice(&collation.to_le_bytes());
rec[140..144].copy_from_slice(&flags.to_le_bytes());
rec[144..152].copy_from_slice(&min_size.to_le_bytes());
rec[152..160].copy_from_slice(&max_size.to_le_bytes());
out.extend_from_slice(&rec);
}
out
}
pub fn emit_record(
rec_buf: &mut [u8],
rec_size: usize,
mft_record_number: u64,
flags: u16,
attrs: &[Vec<u8>],
sector_size: usize,
usn: u16,
) {
for b in rec_buf.iter_mut() {
*b = 0;
}
let rec = &mut rec_buf[..rec_size];
rec[0..4].copy_from_slice(b"FILE");
let usa_offset: u16 = 0x30;
rec[4..6].copy_from_slice(&usa_offset.to_le_bytes());
let sectors = rec_size / sector_size;
let usa_size = (sectors + 1) as u16;
rec[6..8].copy_from_slice(&usa_size.to_le_bytes());
rec[0x10..0x12].copy_from_slice(&1u16.to_le_bytes());
rec[0x12..0x14].copy_from_slice(&1u16.to_le_bytes());
let usa_end = usa_offset as usize + usa_size as usize * 2;
let first_attr_off = ((usa_end + 7) & !7) as u16;
rec[0x14..0x16].copy_from_slice(&first_attr_off.to_le_bytes());
rec[0x16..0x18].copy_from_slice(&flags.to_le_bytes());
rec[0x2C..0x30].copy_from_slice(&(mft_record_number as u32).to_le_bytes());
let mut cursor = first_attr_off as usize;
let mut next_attr_id: u16 = 1;
for a in attrs {
rec[cursor..cursor + a.len()].copy_from_slice(a);
rec[cursor + 14..cursor + 16].copy_from_slice(&next_attr_id.to_le_bytes());
next_attr_id = next_attr_id.wrapping_add(1);
cursor += a.len();
}
let term = [0xFFu8, 0xFF, 0xFF, 0xFF];
rec[cursor..cursor + 4].copy_from_slice(&term);
cursor += 4;
rec[0x28..0x2A].copy_from_slice(&next_attr_id.to_le_bytes());
let bytes_in_use = cursor as u32;
rec[0x18..0x1C].copy_from_slice(&bytes_in_use.to_le_bytes());
rec[0x1C..0x20].copy_from_slice(&(rec_size as u32).to_le_bytes());
mft::install_fixup(rec, sector_size, usn);
}
pub fn build_resident_attr(
type_code: u32,
name_utf16: &[u8],
value: &[u8],
flags: u16,
indexed_flag: u8,
) -> Vec<u8> {
let name_len_u16 = (name_utf16.len() / 2) as u8;
let name_off: u16 = if name_utf16.is_empty() { 0 } else { 0x18 };
let header_block_len = 0x18 + name_utf16.len();
let header_block_aligned = (header_block_len + 7) & !7;
let value_offset = header_block_aligned as u16;
let total = (header_block_aligned + value.len() + 7) & !7;
let mut hdr = vec![0u8; 16];
hdr[0..4].copy_from_slice(&type_code.to_le_bytes());
hdr[4..8].copy_from_slice(&(total as u32).to_le_bytes());
hdr[8] = 0; hdr[9] = name_len_u16;
hdr[10..12].copy_from_slice(&name_off.to_le_bytes());
hdr[12..14].copy_from_slice(&flags.to_le_bytes());
let mut resident = vec![0u8; 8];
resident[0..4].copy_from_slice(&(value.len() as u32).to_le_bytes());
resident[4..6].copy_from_slice(&value_offset.to_le_bytes());
resident[6] = indexed_flag;
hdr.extend_from_slice(&resident);
let mut buf = hdr;
if !name_utf16.is_empty() {
buf.extend_from_slice(name_utf16);
}
while buf.len() < header_block_aligned {
buf.push(0);
}
buf.extend_from_slice(value);
while buf.len() < total {
buf.push(0);
}
buf
}
#[allow(clippy::too_many_arguments)]
pub fn build_non_resident_attr(
type_code: u32,
name_utf16: &[u8],
runs: &[u8],
starting_vcn: u64,
last_vcn: u64,
allocated: u64,
real: u64,
initialized: u64,
flags: u16,
compression_unit: u8,
) -> Vec<u8> {
let name_len_u16 = (name_utf16.len() / 2) as u8;
let header_block_len = 0x40 + name_utf16.len();
let header_block_aligned = (header_block_len + 7) & !7;
let runs_off = header_block_aligned as u16;
let total = ((header_block_aligned + runs.len()) + 7) & !7;
let name_off: u16 = if name_utf16.is_empty() { 0 } else { 0x40 };
let mut hdr = vec![0u8; 16];
hdr[0..4].copy_from_slice(&type_code.to_le_bytes());
hdr[4..8].copy_from_slice(&(total as u32).to_le_bytes());
hdr[8] = 1; hdr[9] = name_len_u16;
hdr[10..12].copy_from_slice(&name_off.to_le_bytes());
hdr[12..14].copy_from_slice(&flags.to_le_bytes());
let mut nonresident = vec![0u8; 0x30];
nonresident[0x00..0x08].copy_from_slice(&starting_vcn.to_le_bytes());
nonresident[0x08..0x10].copy_from_slice(&last_vcn.to_le_bytes());
nonresident[0x10..0x12].copy_from_slice(&runs_off.to_le_bytes());
nonresident[0x12..0x13].copy_from_slice(&[compression_unit]);
nonresident[0x18..0x20].copy_from_slice(&allocated.to_le_bytes());
nonresident[0x20..0x28].copy_from_slice(&real.to_le_bytes());
nonresident[0x28..0x30].copy_from_slice(&initialized.to_le_bytes());
hdr.extend_from_slice(&nonresident);
let mut buf = hdr;
if !name_utf16.is_empty() {
buf.extend_from_slice(name_utf16);
}
while buf.len() < header_block_aligned {
buf.push(0);
}
buf.extend_from_slice(runs);
while buf.len() < total {
buf.push(0);
}
buf
}
pub fn encode_single_run(start_lcn: u64, length: u64) -> Vec<u8> {
let len_size = min_unsigned_bytes(length);
let off_size = min_signed_bytes(start_lcn as i64);
let mut out = Vec::with_capacity(1 + len_size + off_size + 1);
out.push(((off_size as u8) << 4) | (len_size as u8));
out.extend_from_slice(&length.to_le_bytes()[..len_size]);
let lcn_bytes = start_lcn.to_le_bytes();
out.extend_from_slice(&lcn_bytes[..off_size]);
out.push(0); out
}
pub fn encode_run_list(extents: &[(u64, u64)]) -> Vec<u8> {
let mut out = Vec::new();
let mut prev_lcn: i64 = 0;
for &(lcn, length) in extents {
let len_size = min_unsigned_bytes(length);
let delta = (lcn as i64) - prev_lcn;
let off_size = min_signed_bytes(delta);
out.push(((off_size as u8) << 4) | (len_size as u8));
out.extend_from_slice(&length.to_le_bytes()[..len_size]);
let delta_bytes = delta.to_le_bytes();
out.extend_from_slice(&delta_bytes[..off_size]);
prev_lcn = lcn as i64;
}
out.push(0);
out
}
fn min_unsigned_bytes(v: u64) -> usize {
if v == 0 {
return 1;
}
let mut n = 0usize;
let mut x = v;
while x != 0 {
n += 1;
x >>= 8;
}
n
}
fn min_signed_bytes(v: i64) -> usize {
if v == 0 {
return 1;
}
let mut n = 1usize;
while n < 8 {
let bits = (n as u32) * 8;
let lo = -(1i64 << (bits - 1));
let hi = (1i64 << (bits - 1)) - 1;
if v >= lo && v <= hi {
return n;
}
n += 1;
}
8
}
pub fn build_empty_index_root() -> Vec<u8> {
let index_block_size = DEFAULT_INDEX_RECORD_SIZE;
let cpib: i8 = 1;
let mut v = Vec::with_capacity(0x20);
v.extend_from_slice(&TYPE_FILE_NAME.to_le_bytes());
v.extend_from_slice(&1u32.to_le_bytes()); v.extend_from_slice(&index_block_size.to_le_bytes());
v.push(cpib as u8);
v.extend_from_slice(&[0u8; 3]);
let first_entry_offset = 16u32;
let term_entry_len = 16u32;
let bytes_in_use = 16u32 + term_entry_len;
let bytes_allocated = bytes_in_use;
let flags: u8 = 0; v.extend_from_slice(&first_entry_offset.to_le_bytes());
v.extend_from_slice(&bytes_in_use.to_le_bytes());
v.extend_from_slice(&bytes_allocated.to_le_bytes());
v.push(flags);
v.extend_from_slice(&[0u8; 3]);
let mut term = vec![0u8; 16];
term[0..8].copy_from_slice(&0u64.to_le_bytes());
term[8..10].copy_from_slice(&16u16.to_le_bytes());
term[10..12].copy_from_slice(&0u16.to_le_bytes());
term[12..16].copy_from_slice(&0x02u32.to_le_bytes());
v.extend_from_slice(&term);
v
}
pub fn build_si_value(filetime: u64, attrs: u32) -> Vec<u8> {
let mut v = vec![0u8; 48];
v[0..8].copy_from_slice(&filetime.to_le_bytes());
v[8..16].copy_from_slice(&filetime.to_le_bytes());
v[16..24].copy_from_slice(&filetime.to_le_bytes());
v[24..32].copy_from_slice(&filetime.to_le_bytes());
v[32..36].copy_from_slice(&attrs.to_le_bytes());
v
}
pub fn build_file_name_value(
parent_ref: u64,
name: &str,
flags: u32,
real_size: u64,
allocated_size: u64,
filetime: u64,
namespace: u8,
) -> Vec<u8> {
let name_utf16: Vec<u8> = name.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();
let mut v = vec![0u8; 66 + name_utf16.len()];
v[0..8].copy_from_slice(&parent_ref.to_le_bytes());
v[8..16].copy_from_slice(&filetime.to_le_bytes());
v[16..24].copy_from_slice(&filetime.to_le_bytes());
v[24..32].copy_from_slice(&filetime.to_le_bytes());
v[32..40].copy_from_slice(&filetime.to_le_bytes());
v[40..48].copy_from_slice(&allocated_size.to_le_bytes());
v[48..56].copy_from_slice(&real_size.to_le_bytes());
v[56..60].copy_from_slice(&flags.to_le_bytes());
v[64] = (name_utf16.len() / 2) as u8;
v[65] = namespace;
v[66..].copy_from_slice(&name_utf16);
v
}
pub fn pack_mft_ref(rec_no: u64, sequence: u16) -> u64 {
(rec_no & 0x0000_FFFF_FFFF_FFFF) | ((sequence as u64) << 48)
}
#[allow(clippy::too_many_arguments)]
pub fn build_attrdef_record(
rec_buf: &mut [u8],
rec_size: usize,
rec_no: u64,
parent_ref: u64,
attrdef_size: u64,
attrdef_lcn: u64,
attrdef_clusters: u64,
filetime: u64,
cluster_size: u64,
sector_size: usize,
name: &str,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let fn_value = build_file_name_value(
parent_ref,
name,
0x06, attrdef_size,
attrdef_clusters * cluster_size,
filetime,
1, );
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let data_runs = encode_single_run(attrdef_lcn, attrdef_clusters);
let data = build_non_resident_attr(
TYPE_DATA,
&[],
&data_runs,
0,
attrdef_clusters - 1,
attrdef_clusters * cluster_size,
attrdef_size,
attrdef_size,
0,
0,
);
emit_record(
rec_buf,
rec_size,
rec_no,
mft::RecordHeader::FLAG_IN_USE,
&[si, fname, data],
sector_size,
1,
);
}
const TYPE_VOLUME_INFORMATION: u32 = 0x70;
pub fn build_volume_information() -> Vec<u8> {
let mut v = vec![0u8; 12];
v[8] = 3;
v[9] = 1;
v
}
pub fn build_volume_record(
rec_buf: &mut [u8],
rec_size: usize,
parent_ref: u64,
label: &str,
filetime: u64,
sector_size: usize,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let fn_value = build_file_name_value(parent_ref, "$Volume", 0x06, 0, 0, filetime, 1);
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let label_utf16: Vec<u8> = label.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();
let vol_name = build_resident_attr(TYPE_VOLUME_NAME, &[], &label_utf16, 0, 0);
let vol_info = build_resident_attr(
TYPE_VOLUME_INFORMATION,
&[],
&build_volume_information(),
0,
0,
);
emit_record(
rec_buf,
rec_size,
REC_VOLUME,
mft::RecordHeader::FLAG_IN_USE,
&[si, fname, vol_name, vol_info],
sector_size,
1,
);
}
pub fn build_root_record(rec_buf: &mut [u8], rec_size: usize, filetime: u64, sector_size: usize) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let root_ref = pack_mft_ref(REC_ROOT, 1);
let fn_value = build_file_name_value(root_ref, ".", 0x10000006, 0, 0, filetime, 1); let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let i30_name: Vec<u8> = "$I30"
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let idx_root = build_resident_attr(TYPE_INDEX_ROOT, &i30_name, &build_empty_index_root(), 0, 0);
emit_record(
rec_buf,
rec_size,
REC_ROOT,
mft::RecordHeader::FLAG_IN_USE | mft::RecordHeader::FLAG_DIRECTORY,
&[si, fname, idx_root],
sector_size,
1,
);
}
#[allow(clippy::too_many_arguments)]
pub fn build_bitmap_record(
rec_buf: &mut [u8],
rec_size: usize,
parent_ref: u64,
bitmap_bytes: u64,
bitmap_lcn: u64,
bitmap_clusters: u64,
filetime: u64,
cluster_size: u64,
sector_size: usize,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let fn_value = build_file_name_value(
parent_ref,
"$Bitmap",
0x06,
bitmap_bytes,
bitmap_clusters * cluster_size,
filetime,
1,
);
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let runs = encode_single_run(bitmap_lcn, bitmap_clusters);
let data = build_non_resident_attr(
TYPE_DATA,
&[],
&runs,
0,
bitmap_clusters - 1,
bitmap_clusters * cluster_size,
bitmap_bytes,
bitmap_bytes,
0,
0,
);
emit_record(
rec_buf,
rec_size,
REC_BITMAP,
mft::RecordHeader::FLAG_IN_USE,
&[si, fname, data],
sector_size,
1,
);
}
pub fn build_boot_record(
rec_buf: &mut [u8],
rec_size: usize,
parent_ref: u64,
total_bytes: u64,
filetime: u64,
sector_size: usize,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let fn_value = build_file_name_value(
parent_ref,
"$Boot",
0x06,
sector_size as u64,
sector_size as u64,
filetime,
1,
);
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let runs = encode_single_run(0, 1);
let data = build_non_resident_attr(
TYPE_DATA,
&[],
&runs,
0,
0,
sector_size as u64,
sector_size as u64,
sector_size as u64,
0,
0,
);
let _ = total_bytes; emit_record(
rec_buf,
rec_size,
REC_BOOT,
mft::RecordHeader::FLAG_IN_USE,
&[si, fname, data],
sector_size,
1,
);
}
pub fn build_badclus_record(
rec_buf: &mut [u8],
rec_size: usize,
parent_ref: u64,
total_clusters: u64,
filetime: u64,
cluster_size: u64,
sector_size: usize,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let fn_value = build_file_name_value(parent_ref, "$BadClus", 0x06, 0, 0, filetime, 1);
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let data_unnamed = build_resident_attr(TYPE_DATA, &[], &[], 0, 0);
let bad_name: Vec<u8> = "$Bad"
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let mut sparse_runs = Vec::new();
let len_size = min_unsigned_bytes(total_clusters);
sparse_runs.push(len_size as u8); sparse_runs.extend_from_slice(&total_clusters.to_le_bytes()[..len_size]);
sparse_runs.push(0); let bad_data = build_non_resident_attr(
TYPE_DATA,
&bad_name,
&sparse_runs,
0,
total_clusters - 1,
total_clusters * cluster_size,
total_clusters * cluster_size,
0, super::attribute::ATTR_FLAG_SPARSE,
0,
);
emit_record(
rec_buf,
rec_size,
REC_BADCLUS,
mft::RecordHeader::FLAG_IN_USE,
&[si, fname, data_unnamed, bad_data],
sector_size,
1,
);
}
pub fn build_secure_record(
rec_buf: &mut [u8],
rec_size: usize,
parent_ref: u64,
filetime: u64,
sector_size: usize,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let fn_value = build_file_name_value(parent_ref, "$Secure", 0x06, 0, 0, filetime, 1);
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let sds_name: Vec<u8> = "$SDS"
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let sds = build_resident_attr(TYPE_DATA, &sds_name, &[], 0, 0);
let sdh_root = build_empty_secure_index_root();
let sdh_name: Vec<u8> = "$SDH"
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let sdh = build_resident_attr(TYPE_INDEX_ROOT, &sdh_name, &sdh_root, 0, 0);
let sii_root = build_empty_secure_index_root();
let sii_name: Vec<u8> = "$SII"
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let sii = build_resident_attr(TYPE_INDEX_ROOT, &sii_name, &sii_root, 0, 0);
emit_record(
rec_buf,
rec_size,
REC_SECURE,
mft::RecordHeader::FLAG_IN_USE,
&[si, fname, sds, sdh, sii],
sector_size,
1,
);
}
fn build_empty_secure_index_root() -> Vec<u8> {
let mut v = Vec::with_capacity(0x20);
v.extend_from_slice(&0u32.to_le_bytes());
v.extend_from_slice(&0u32.to_le_bytes());
v.extend_from_slice(&DEFAULT_INDEX_RECORD_SIZE.to_le_bytes());
v.push(1); v.extend_from_slice(&[0u8; 3]);
let first_entry_offset = 16u32;
let term_entry_len = 16u32;
let bytes_in_use = 16u32 + term_entry_len;
let bytes_allocated = bytes_in_use;
let flags: u8 = 0;
v.extend_from_slice(&first_entry_offset.to_le_bytes());
v.extend_from_slice(&bytes_in_use.to_le_bytes());
v.extend_from_slice(&bytes_allocated.to_le_bytes());
v.push(flags);
v.extend_from_slice(&[0u8; 3]);
let mut term = vec![0u8; 16];
term[8..10].copy_from_slice(&16u16.to_le_bytes());
term[12..16].copy_from_slice(&0x02u32.to_le_bytes());
v.extend_from_slice(&term);
v
}
#[allow(clippy::too_many_arguments)]
pub fn build_upcase_record(
rec_buf: &mut [u8],
rec_size: usize,
parent_ref: u64,
upcase_bytes: u64,
upcase_lcn: u64,
upcase_clusters: u64,
filetime: u64,
cluster_size: u64,
sector_size: usize,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let fn_value = build_file_name_value(
parent_ref,
"$UpCase",
0x06,
upcase_bytes,
upcase_clusters * cluster_size,
filetime,
1,
);
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let runs = encode_single_run(upcase_lcn, upcase_clusters);
let data = build_non_resident_attr(
TYPE_DATA,
&[],
&runs,
0,
upcase_clusters - 1,
upcase_clusters * cluster_size,
upcase_bytes,
upcase_bytes,
0,
0,
);
emit_record(
rec_buf,
rec_size,
REC_UPCASE,
mft::RecordHeader::FLAG_IN_USE,
&[si, fname, data],
sector_size,
1,
);
}
pub fn build_extend_record(
rec_buf: &mut [u8],
rec_size: usize,
parent_ref: u64,
filetime: u64,
sector_size: usize,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let fn_value = build_file_name_value(parent_ref, "$Extend", 0x10000006, 0, 0, filetime, 1);
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let i30_name: Vec<u8> = "$I30"
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let idx_root = build_resident_attr(TYPE_INDEX_ROOT, &i30_name, &build_empty_index_root(), 0, 0);
emit_record(
rec_buf,
rec_size,
REC_EXTEND,
mft::RecordHeader::FLAG_IN_USE | mft::RecordHeader::FLAG_DIRECTORY,
&[si, fname, idx_root],
sector_size,
1,
);
}
pub fn build_reserved_record(
rec_buf: &mut [u8],
rec_size: usize,
rec_no: u64,
parent_ref: u64,
name: &str,
filetime: u64,
sector_size: usize,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let fn_value = build_file_name_value(parent_ref, name, 0x06, 0, 0, filetime, 1);
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
emit_record(
rec_buf,
rec_size,
rec_no,
mft::RecordHeader::FLAG_IN_USE,
&[si, fname],
sector_size,
1,
);
}
#[allow(clippy::too_many_arguments)]
pub fn build_logfile_record(
rec_buf: &mut [u8],
rec_size: usize,
parent_ref: u64,
log_lcn: u64,
log_clusters: u64,
filetime: u64,
cluster_size: u64,
sector_size: usize,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let log_bytes = log_clusters * cluster_size;
let fn_value = build_file_name_value(
parent_ref, "$LogFile", 0x06, log_bytes, log_bytes, filetime, 1,
);
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let runs = encode_single_run(log_lcn, log_clusters);
let data = build_non_resident_attr(
TYPE_DATA,
&[],
&runs,
0,
log_clusters - 1,
log_bytes,
log_bytes,
log_bytes,
0,
0,
);
emit_record(
rec_buf,
rec_size,
REC_LOGFILE,
mft::RecordHeader::FLAG_IN_USE,
&[si, fname, data],
sector_size,
1,
);
}
#[allow(clippy::too_many_arguments)]
pub fn build_mft_record(
rec_buf: &mut [u8],
rec_size: usize,
parent_ref: u64,
mft_data_extents: &[(u64, u64)],
mft_records: u64,
mft_bitmap_lcn: u64,
mft_bitmap_clusters: u64,
filetime: u64,
cluster_size: u64,
sector_size: usize,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let fn_value = build_file_name_value(
parent_ref,
"$MFT",
0x06,
mft_records * rec_size as u64,
mft_records * rec_size as u64,
filetime,
1,
);
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let runs = encode_run_list(mft_data_extents);
let total_mft_clusters: u64 = mft_data_extents.iter().map(|(_, l)| l).sum();
let data = build_non_resident_attr(
TYPE_DATA,
&[],
&runs,
0,
total_mft_clusters - 1,
total_mft_clusters * cluster_size,
mft_records * rec_size as u64,
mft_records * rec_size as u64,
0,
0,
);
let bm_runs = encode_single_run(mft_bitmap_lcn, mft_bitmap_clusters);
let bm_bytes = mft_records.div_ceil(8);
let bitmap = build_non_resident_attr(
TYPE_BITMAP,
&[],
&bm_runs,
0,
mft_bitmap_clusters - 1,
mft_bitmap_clusters * cluster_size,
bm_bytes,
bm_bytes,
0,
0,
);
emit_record(
rec_buf,
rec_size,
REC_MFT,
mft::RecordHeader::FLAG_IN_USE,
&[si, fname, data, bitmap],
sector_size,
1,
);
}
#[allow(clippy::too_many_arguments)]
pub fn build_mftmirr_record(
rec_buf: &mut [u8],
rec_size: usize,
parent_ref: u64,
mirror_lcn: u64,
mirror_clusters: u64,
filetime: u64,
cluster_size: u64,
sector_size: usize,
) {
let si = build_resident_attr(
TYPE_STANDARD_INFORMATION,
&[],
&build_si_value(filetime, 0x06),
0,
0,
);
let bytes = mirror_clusters * cluster_size;
let fn_value = build_file_name_value(parent_ref, "$MFTMirr", 0x06, bytes, bytes, filetime, 1);
let fname = build_resident_attr(TYPE_FILE_NAME, &[], &fn_value, 0, 0);
let runs = encode_single_run(mirror_lcn, mirror_clusters);
let data = build_non_resident_attr(
TYPE_DATA,
&[],
&runs,
0,
mirror_clusters - 1,
bytes,
bytes,
bytes,
0,
0,
);
emit_record(
rec_buf,
rec_size,
REC_MFTMIRR,
mft::RecordHeader::FLAG_IN_USE,
&[si, fname, data],
sector_size,
1,
);
}
pub fn insert_into_index_root(
root_value: &[u8],
new_entry: &[u8],
max_resident_bytes: usize,
) -> Result<Vec<u8>> {
if root_value.len() < 32 {
return Err(crate::Error::InvalidImage(
"ntfs: $INDEX_ROOT too small to mutate".into(),
));
}
let header_meta = &root_value[..16];
let _first_entry_offset = u32::from_le_bytes(root_value[16..20].try_into().unwrap()) as usize;
let bytes_in_use = u32::from_le_bytes(root_value[20..24].try_into().unwrap()) as usize;
let flags = root_value[28];
let entries_start = 16 + 16; let entries_end = 16 + bytes_in_use;
let mut cursor = entries_start;
let mut entries: Vec<Vec<u8>> = Vec::new();
let mut terminator: Vec<u8> = Vec::new();
while cursor + 16 <= entries_end {
let entry_len =
u16::from_le_bytes(root_value[cursor + 8..cursor + 10].try_into().unwrap()) as usize;
if entry_len < 16 || cursor + entry_len > entries_end {
return Err(crate::Error::InvalidImage(
"ntfs: malformed $INDEX_ROOT entry length".into(),
));
}
let e_flags = u32::from_le_bytes(root_value[cursor + 12..cursor + 16].try_into().unwrap());
let is_last = e_flags & 0x02 != 0;
let slice = root_value[cursor..cursor + entry_len].to_vec();
if is_last {
terminator = slice;
break;
} else {
entries.push(slice);
}
cursor += entry_len;
}
if terminator.is_empty() {
return Err(crate::Error::InvalidImage(
"ntfs: $INDEX_ROOT missing terminator".into(),
));
}
entries.push(new_entry.to_vec());
let entries_total: usize = entries.iter().map(|e| e.len()).sum::<usize>() + terminator.len();
let new_bytes_in_use = 16 + entries_total;
let new_total = 16 + new_bytes_in_use;
if new_total > max_resident_bytes {
return Err(crate::Error::Unsupported(
"ntfs: directory index would overflow $INDEX_ROOT — promotion to $INDEX_ALLOCATION needed".into(),
));
}
let mut out = Vec::with_capacity(new_total);
out.extend_from_slice(header_meta);
out.extend_from_slice(&16u32.to_le_bytes());
out.extend_from_slice(&(new_bytes_in_use as u32).to_le_bytes());
out.extend_from_slice(&(new_bytes_in_use as u32).to_le_bytes());
out.push(flags);
out.extend_from_slice(&[0u8; 3]);
for e in &entries {
out.extend_from_slice(e);
}
out.extend_from_slice(&terminator);
Ok(out)
}
pub fn mirror_clusters(rec_size: u32, cluster_size: u32) -> u64 {
let mirror_bytes = 4 * rec_size as u64;
mirror_bytes.div_ceil(cluster_size as u64)
}
pub fn rewrite_resident_attr(
rec: &mut [u8],
rec_size: usize,
type_code: u32,
name: &str,
new_value: &[u8],
) -> Result<()> {
let hdr = mft::RecordHeader::parse(rec)?;
let bytes_in_use = hdr.bytes_in_use as usize;
let first = hdr.first_attribute_offset as usize;
let mut cursor = first;
loop {
if cursor + 4 > bytes_in_use {
return Err(crate::Error::InvalidImage(
"ntfs: attribute walk past bytes_in_use".into(),
));
}
let tc = u32::from_le_bytes(rec[cursor..cursor + 4].try_into().unwrap());
if tc == 0xFFFF_FFFF {
return Err(crate::Error::InvalidImage(format!(
"ntfs: attribute type 0x{:x} not found in record",
type_code
)));
}
let len = u32::from_le_bytes(rec[cursor + 4..cursor + 8].try_into().unwrap()) as usize;
let non_resident = rec[cursor + 8] != 0;
let name_len = rec[cursor + 9] as usize;
let name_off =
u16::from_le_bytes(rec[cursor + 10..cursor + 12].try_into().unwrap()) as usize;
let attr_name = if name_len == 0 {
String::new()
} else {
super::attribute::decode_utf16le(
&rec[cursor + name_off..cursor + name_off + name_len * 2],
)
};
if tc == type_code && attr_name == name && !non_resident {
let value_off =
u16::from_le_bytes(rec[cursor + 0x14..cursor + 0x16].try_into().unwrap()) as usize;
let header_block_len = value_off; let new_total = (header_block_len + new_value.len() + 7) & !7;
let old_total = len;
let after = cursor + old_total;
let new_after = cursor + new_total;
let tail_len = bytes_in_use - after;
let new_bytes_in_use = new_after + tail_len;
if new_bytes_in_use + 8 > rec_size {
return Err(crate::Error::Unsupported(
"ntfs: resident attribute rewrite would overflow MFT record".into(),
));
}
let tail = rec[after..bytes_in_use].to_vec();
rec[cursor + 0x10..cursor + 0x14]
.copy_from_slice(&(new_value.len() as u32).to_le_bytes());
rec[cursor + 4..cursor + 8].copy_from_slice(&(new_total as u32).to_le_bytes());
for b in &mut rec[cursor + value_off..after] {
*b = 0;
}
rec[cursor + value_off..cursor + value_off + new_value.len()]
.copy_from_slice(new_value);
for b in &mut rec[cursor + value_off + new_value.len()..new_after] {
*b = 0;
}
for (i, b) in tail.iter().enumerate() {
rec[new_after + i] = *b;
}
rec[0x18..0x1C].copy_from_slice(&(new_bytes_in_use as u32).to_le_bytes());
for b in &mut rec[new_bytes_in_use..rec_size] {
*b = 0;
}
return Ok(());
}
cursor += len;
}
}
#[derive(Debug, Clone)]
pub struct LayoutResult {
pub cluster_size: u32,
pub bytes_per_sector: u16,
pub sectors_per_cluster: u8,
pub total_clusters: u64,
pub mft_record_size: u32,
pub index_record_size: u32,
pub mft_extents: Vec<(u64, u64)>,
pub mft_records: u64,
pub mftmirr_lcn: u64,
pub bitmap_lcn: u64,
pub bitmap_clusters: u64,
pub bitmap: BitmapAlloc,
pub mft_bitmap_lcn: u64,
pub mft_bitmap_clusters: u64,
pub volume_serial: u64,
pub upcase_lcn: u64,
pub upcase_clusters: u64,
pub logfile_lcn: u64,
pub logfile_clusters: u64,
pub attrdef_lcn: u64,
pub attrdef_clusters: u64,
}
fn ceil_div(num: u64, den: u64) -> u64 {
num.div_ceil(den)
}
#[allow(clippy::too_many_lines)]
pub fn format_volume(dev: &mut dyn BlockDevice, opts: &FormatOpts) -> Result<LayoutResult> {
let total_size = dev.total_size();
if total_size < 4 * 1024 * 1024 {
return Err(crate::Error::InvalidArgument(
"ntfs: minimum supported volume size is 4 MiB".into(),
));
}
let bps = opts.bytes_per_sector as u32;
let spc = opts.sectors_per_cluster as u32;
if !bps.is_power_of_two() || !spc.is_power_of_two() {
return Err(crate::Error::InvalidArgument(
"ntfs: bytes_per_sector and sectors_per_cluster must be powers of two".into(),
));
}
let cluster_size = bps * spc;
if cluster_size != DEFAULT_CLUSTER_SIZE {
}
let total_sectors = total_size / bps as u64;
let total_clusters = total_size / cluster_size as u64;
let rec_size = DEFAULT_MFT_RECORD_SIZE;
let mft_record_field: i8 = -10; let index_record_field: i8 = -12; let mft_clusters = INITIAL_MFT_CLUSTERS;
let mft_records_capacity = mft_clusters * cluster_size as u64 / rec_size as u64;
let mft_lcn = 4u64;
let mut bitmap = BitmapAlloc::new(total_clusters);
bitmap.set_range(0, 1);
bitmap.set_range(mft_lcn, mft_clusters);
bitmap.next_hint = mft_lcn + mft_clusters;
let mirror_clusters_n = mirror_clusters(rec_size, cluster_size);
let mid_cluster = total_clusters / 2;
let mftmirr_lcn = {
let candidate = mid_cluster;
if candidate + mirror_clusters_n <= total_clusters
&& (candidate..candidate + mirror_clusters_n).all(|c| !bitmap.is_set(c))
{
bitmap.set_range(candidate, mirror_clusters_n);
candidate
} else {
bitmap.allocate(mirror_clusters_n)?
}
};
let logfile_clusters = ceil_div(LOGFILE_BYTES, cluster_size as u64);
let logfile_lcn = bitmap.allocate(logfile_clusters)?;
let attrdef = build_attrdef_payload();
let attrdef_clusters = ceil_div(attrdef.len() as u64, cluster_size as u64);
let attrdef_lcn = bitmap.allocate(attrdef_clusters)?;
let upcase = build_upcase_blob();
let upcase_clusters = ceil_div(upcase.len() as u64, cluster_size as u64);
let upcase_lcn = bitmap.allocate(upcase_clusters)?;
let bitmap_bytes = bitmap.bytes.len() as u64;
let bitmap_clusters = ceil_div(bitmap_bytes, cluster_size as u64);
let bitmap_lcn = bitmap.allocate(bitmap_clusters)?;
let mft_bitmap_bytes = ceil_div(mft_records_capacity, 8);
let mft_bitmap_clusters = ceil_div(mft_bitmap_bytes, cluster_size as u64).max(1);
let mft_bitmap_lcn = bitmap.allocate(mft_bitmap_clusters)?;
let filetime = unix_to_filetime(0);
let serial = opts.volume_serial;
let boot = build_boot_sector(
opts,
total_sectors,
mft_lcn,
mftmirr_lcn,
mft_record_field,
index_record_field,
);
dev.write_at(0, &boot)?;
let last_lba_offset = (total_sectors - 1) * bps as u64;
dev.write_at(last_lba_offset, &boot)?;
let mft_buf_size = (mft_clusters * cluster_size as u64) as usize;
let mut mft_buf = vec![0u8; mft_buf_size];
let parent_root_ref = pack_mft_ref(REC_ROOT, 1);
{
let r = &mut mft_buf
[(REC_MFT as usize) * rec_size as usize..(REC_MFT as usize + 1) * rec_size as usize];
build_mft_record(
r,
rec_size as usize,
parent_root_ref,
&[(mft_lcn, mft_clusters)],
mft_records_capacity,
mft_bitmap_lcn,
mft_bitmap_clusters,
filetime,
cluster_size as u64,
bps as usize,
);
}
{
let r = &mut mft_buf[(REC_MFTMIRR as usize) * rec_size as usize
..(REC_MFTMIRR as usize + 1) * rec_size as usize];
build_mftmirr_record(
r,
rec_size as usize,
parent_root_ref,
mftmirr_lcn,
mirror_clusters_n,
filetime,
cluster_size as u64,
bps as usize,
);
}
{
let r = &mut mft_buf[(REC_LOGFILE as usize) * rec_size as usize
..(REC_LOGFILE as usize + 1) * rec_size as usize];
build_logfile_record(
r,
rec_size as usize,
parent_root_ref,
logfile_lcn,
logfile_clusters,
filetime,
cluster_size as u64,
bps as usize,
);
}
{
let r = &mut mft_buf[(REC_VOLUME as usize) * rec_size as usize
..(REC_VOLUME as usize + 1) * rec_size as usize];
build_volume_record(
r,
rec_size as usize,
parent_root_ref,
&opts.volume_label,
filetime,
bps as usize,
);
}
{
let r = &mut mft_buf[(REC_ATTRDEF as usize) * rec_size as usize
..(REC_ATTRDEF as usize + 1) * rec_size as usize];
build_attrdef_record(
r,
rec_size as usize,
REC_ATTRDEF,
parent_root_ref,
attrdef.len() as u64,
attrdef_lcn,
attrdef_clusters,
filetime,
cluster_size as u64,
bps as usize,
"$AttrDef",
);
}
{
let r = &mut mft_buf
[(REC_ROOT as usize) * rec_size as usize..(REC_ROOT as usize + 1) * rec_size as usize];
build_root_record(r, rec_size as usize, filetime, bps as usize);
}
{
let r = &mut mft_buf[(REC_BITMAP as usize) * rec_size as usize
..(REC_BITMAP as usize + 1) * rec_size as usize];
build_bitmap_record(
r,
rec_size as usize,
parent_root_ref,
bitmap_bytes,
bitmap_lcn,
bitmap_clusters,
filetime,
cluster_size as u64,
bps as usize,
);
}
{
let r = &mut mft_buf
[(REC_BOOT as usize) * rec_size as usize..(REC_BOOT as usize + 1) * rec_size as usize];
build_boot_record(
r,
rec_size as usize,
parent_root_ref,
total_size,
filetime,
bps as usize,
);
}
{
let r = &mut mft_buf[(REC_BADCLUS as usize) * rec_size as usize
..(REC_BADCLUS as usize + 1) * rec_size as usize];
build_badclus_record(
r,
rec_size as usize,
parent_root_ref,
total_clusters,
filetime,
cluster_size as u64,
bps as usize,
);
}
{
let r = &mut mft_buf[(REC_SECURE as usize) * rec_size as usize
..(REC_SECURE as usize + 1) * rec_size as usize];
build_secure_record(
r,
rec_size as usize,
parent_root_ref,
filetime,
bps as usize,
);
}
{
let r = &mut mft_buf[(REC_UPCASE as usize) * rec_size as usize
..(REC_UPCASE as usize + 1) * rec_size as usize];
build_upcase_record(
r,
rec_size as usize,
parent_root_ref,
upcase.len() as u64,
upcase_lcn,
upcase_clusters,
filetime,
cluster_size as u64,
bps as usize,
);
}
{
let r = &mut mft_buf[(REC_EXTEND as usize) * rec_size as usize
..(REC_EXTEND as usize + 1) * rec_size as usize];
build_extend_record(
r,
rec_size as usize,
parent_root_ref,
filetime,
bps as usize,
);
}
for (rec_no, name) in
(12u64..=15u64).zip(["$Reserved12", "$Reserved13", "$Reserved14", "$Reserved15"])
{
let r = &mut mft_buf
[(rec_no as usize) * rec_size as usize..(rec_no as usize + 1) * rec_size as usize];
build_reserved_record(
r,
rec_size as usize,
rec_no,
parent_root_ref,
name,
filetime,
bps as usize,
);
}
dev.write_at(mft_lcn * cluster_size as u64, &mft_buf)?;
let mirror_size = (4 * rec_size as u64).min(mirror_clusters_n * cluster_size as u64) as usize;
dev.write_at(mftmirr_lcn * cluster_size as u64, &mft_buf[..mirror_size])?;
let mirror_byte_len = (mirror_clusters_n * cluster_size as u64) as usize;
if mirror_byte_len > mirror_size {
let zeros = vec![0u8; mirror_byte_len - mirror_size];
dev.write_at(
mftmirr_lcn * cluster_size as u64 + mirror_size as u64,
&zeros,
)?;
}
dev.zero_range(
logfile_lcn * cluster_size as u64,
logfile_clusters * cluster_size as u64,
)?;
{
let off = attrdef_lcn * cluster_size as u64;
dev.write_at(off, &attrdef)?;
let padded_len = attrdef_clusters * cluster_size as u64;
if (attrdef.len() as u64) < padded_len {
let pad = vec![0u8; (padded_len - attrdef.len() as u64) as usize];
dev.write_at(off + attrdef.len() as u64, &pad)?;
}
}
{
let off = upcase_lcn * cluster_size as u64;
dev.write_at(off, &upcase)?;
let padded_len = upcase_clusters * cluster_size as u64;
if (upcase.len() as u64) < padded_len {
let pad = vec![0u8; (padded_len - upcase.len() as u64) as usize];
dev.write_at(off + upcase.len() as u64, &pad)?;
}
}
{
let off = bitmap_lcn * cluster_size as u64;
dev.write_at(off, &bitmap.bytes)?;
let padded_len = bitmap_clusters * cluster_size as u64;
if (bitmap.bytes.len() as u64) < padded_len {
let pad = vec![0u8; (padded_len - bitmap.bytes.len() as u64) as usize];
dev.write_at(off + bitmap.bytes.len() as u64, &pad)?;
}
}
{
let mut mb = vec![0u8; mft_bitmap_bytes as usize];
for r in 0..16u64 {
mb[(r / 8) as usize] |= 1u8 << ((r % 8) as u8);
}
let off = mft_bitmap_lcn * cluster_size as u64;
dev.write_at(off, &mb)?;
let padded = mft_bitmap_clusters * cluster_size as u64;
if (mb.len() as u64) < padded {
let pad = vec![0u8; (padded - mb.len() as u64) as usize];
dev.write_at(off + mb.len() as u64, &pad)?;
}
}
Ok(LayoutResult {
cluster_size,
bytes_per_sector: opts.bytes_per_sector,
sectors_per_cluster: opts.sectors_per_cluster,
total_clusters,
mft_record_size: rec_size,
index_record_size: DEFAULT_INDEX_RECORD_SIZE,
mft_extents: vec![(mft_lcn, mft_clusters)],
mft_records: mft_records_capacity,
mftmirr_lcn,
bitmap_lcn,
bitmap_clusters,
bitmap,
mft_bitmap_lcn,
mft_bitmap_clusters,
volume_serial: serial,
upcase_lcn,
upcase_clusters,
logfile_lcn,
logfile_clusters,
attrdef_lcn,
attrdef_clusters,
})
}
pub fn unix_to_filetime(unix_secs: u32) -> u64 {
const FILETIME_EPOCH_DIFF: u64 = 11_644_473_600; let secs = unix_secs as u64 + FILETIME_EPOCH_DIFF;
secs * 10_000_000
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn min_unsigned_bytes_basic() {
assert_eq!(min_unsigned_bytes(0), 1);
assert_eq!(min_unsigned_bytes(0xFF), 1);
assert_eq!(min_unsigned_bytes(0x100), 2);
assert_eq!(min_unsigned_bytes(0xFFFF), 2);
assert_eq!(min_unsigned_bytes(0x10000), 3);
}
#[test]
fn min_signed_bytes_basic() {
assert_eq!(min_signed_bytes(0), 1);
assert_eq!(min_signed_bytes(127), 1);
assert_eq!(min_signed_bytes(-128), 1);
assert_eq!(min_signed_bytes(128), 2);
assert_eq!(min_signed_bytes(-129), 2);
}
#[test]
fn encode_single_run_basic() {
let r = encode_single_run(20, 1);
assert_eq!(r, vec![0x11, 0x01, 0x14, 0x00]);
}
#[test]
fn upcase_blob_size() {
let u = build_upcase_blob();
assert_eq!(u.len(), 128 * 1024);
}
#[test]
fn filetime_conversion_known() {
assert_eq!(unix_to_filetime(0), 116_444_736_000_000_000);
}
}